In the site I'm developing I have a form to register users that extends the userCreationForm. Said form only adds fields that are not in the default, but they are in the model such as email, first_name and last_name.
class RegisterForm(UserCreationForm):
email = forms.EmailField(label = "Email", required=True)
first_name = forms.CharField(label = "First name", required = True )
last_name = forms.CharField(label = "Last name", required = True)
class Meta:
model = User
fields = ("username", "first_name", "last_name", "email", "password1", "password2" )
def __init__(self, *args, **kwargs):
super(RegisterForm, self).__init__(*args, **kwargs)
for fieldname in ['username', 'password1', 'password2']:
self.fields[fieldname].help_text = None
My question is if there's a way to add another field like an imagefield, date of birth, etc. so that I can make a more complete user profile? Or do I have to create my own user model?
The Django idiomatic way of adding more fields to the User model is to make a second model where User is a ForeignKey. So:
from django.db import models
from django.contrib.auth.models import User
class MyUser(models.Model):
user = models.ForeignKey(User)
# ... other fields. i.e. imagefield, date of birth (dob), etc ..
# example:
dob = models.DateField()
Related
I have a doubt with the serializers and so far I have not been able to solve it. I explain the doubt with the following example:
I have a User model and this model has the following attributes: username, password, first_name, last_name, age, gender. I also have a serializer, which is called UserSerializer. The UserSerializer should do the following:
When inserting information into the database, UserSerializer should only take into account the fields: username, password, first_name, last_name.
When retrieving information from the database, UserSerializer should only take into account the fields: age, gender.
When updating the database information, UserSerializer should only take into account the fields: password.
My solution:
class UserSerializer:
class UserCreateSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'password', 'first_name', 'last_name']
class UserGetSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['age', 'gender']
class UserUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['password']
Question:
The question: is there any way to synthesize the three serializers into one, or what is the best solution for this problem?
Thank you very much.
Use write_only and read_only parameters in serializer fields to specify fields for creating and retrieving
class UserSerializer(serializers.ModelSerializer):
username = serializers.CharField(write_only=True, required=False)
password = serializers.CharField(write_only=True)
first_name = serializers.CharField(write_only=True, required=False)
last_name = serializers.CharField(write_only=True, required=False)
age = serializers.IntegerField(read_only=True)
gender = serializers.CharField(read_only=True)
class Meta:
model = User
fields = ['username', 'password', 'first_name', 'last_name', 'age', 'gender']
If any field is optional, then you can add the required=False
I have a one-to-one relationship set up between a User model, and a UserProfile model. The UserProfile model contains additional information about the user, and is created immediately after a User model saves. The OneToOne field is set on the UserProfile.
The serialized User data appears fine when I access any of the views concering the UserProfile, but I would also like the UserProfile data to appear on User views, and I was wondering how to specify this in the UserSerializer.
EDIT: I need the UserProfile data so that I can do an easy lookup on the UserProfile id to change information about the User. I will have the User id client side. Ideally, I'd like to look up the User with that id, find the UserProfile id (amongst other UserProfile data), so that I can then change the data for the UserProfile that is associated with the User.
Example: user 1 is authenticated. I gather some information following registration, so I call the view /api/users/1/, which tells me that the user profile with an id of 20 is associated with that user. I can then update that user profile with the new data. I can't always assume that user id 1 will be associated with user profile id 1.
EDIT 2: Solved this similar to Angela's answer like this:
class UserSerializer(serializers.ModelSerializer):
user_profile = UserProfileSerializer(source="userprofile")
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups",
"user_profile"
)
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups"
)
class UserProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = UserProfile
fields = (
"user",
"current_city",
"current_country"
)
models.py
class UserProfile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE
)
avatar = models.FileField(
upload_to="uploads",
blank=True
)
age = models.IntegerField(
null=True
)
created_at = models.DateTimeField(
auto_now_add=True
)
current_city = models.CharField(
max_length=255,
blank=True
)
current_country = models.CharField(
max_length=255,
blank=True
)
I think, you should use Two UserProfileSerializer for this purpose as,
#serializer.py
class UserProfileSerializer_Temp(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = (
"user",
"current_city",
"current_country"
)
class UserSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
user_profile = kwargs.pop('user_profile', True)
super().__init__(*args, **kwargs)
if user_profile is False:
self.fields.pop('user_profile', None)
user_profile = UserProfileSerializer_Temp(source='userprofile_set')
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups",
"user_profile"
)
class UserProfileSerializer_New(UserProfileSerializer_Temp):
user = UserSerializer(user_profile=False)
#views.py
class UserProfileAPI(ModelViewSet):
serializer_class = UserProfileSerializer_New
queryset = UserProfile.objects.all()
class UserAPI(ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
What's the relevence of user_profile=False ?
It's act like a simple flag. If we do not pass something like that from UserProfileSerializer_New, it will serializer the UserProfile twice. See this Screenshot --- image
Cureent Response
1 . UserAPI
2 . UserProfileAPI
So to have the userprofile information available for viewing in the user serializer and also have user info in the user serializer, I would recommend you use the serializer source arguements as follows -
Recommended Approach
This is based on the assumption that when you update user profile, you dont want to update any info in the actual user model. You use the user endpoint to do that.
class UserProfileSerializer(serializers.ModelSerializer):
first_name = serializers.CharField(source='user.first_name',read_only=True)
last_name = serializers.CharField(source='user.second_name',read_only=True)
...... #(other user fields you want to include)
......
......
class Meta:
model = UserProfile
fields = (
"first_name",
"last_name",
"current_city",
"current_country",
......., # any other user fields
.......
)
class UserSerializer(serializers.ModelSerializer):
user_profile = UserProfileSerializer(partial=True, required=False)
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups",
"user_profile"
)
This will give you a nested representation of the user_profile inside your user object, which will also have the source fields, but you can choose to ignore that.
Another approach
Otherwise if you really want nested structures in both the serializers, you can use different serializers, but not sure what your use case is for this.
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = (
"current_city",
"current_country",
......., # any other user_profile only fields
.......
)
class UserSerializer(serializers.ModelSerializer):
user_profile = UserProfileSerializer(partial=True, required=False)
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups",
"user_profile"
)
class UserSerializerNew(serializers.ModelSerializer):
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups"
)
class UserProfileSerializerNew(serializers.ModelSerializer):
user = UserSerializerNew(read_only=True)
class Meta:
model = UserProfile
fields = (
"current_city",
"current_country",
"user"
)
I want to update records that are related with foreign Keys or related by any means. I know how to update single model records but I am not able to do the same in case of related models.
My Models :
class UserProfile(models.Model):
user = models.OneToOneField(User)
subject = models.ManyToManyField('Subjects')
phone = models.CharField(max_length=20)
address = models.TextField()
def __unicode__(self):
return self.user.username
class Subjects(models.Model):
name = models.CharField(max_length=100)
code = models.IntegerField()
def __unicode__(self):
return self.name
My serializers :
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'id',
'first_name',
'username',
'email',
'is_active',
)
class SubjectSerializer(serializers.ModelSerializer):
class Meta:
model = Subjects
fields = (
'name',
'code',
)
class UserProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
subject = SubjectSerializer(many=True)
class Meta:
model = UserProfile
fields = (
'user',
'subject',
'phone',
'address',
)
Views :
class UserProfileList(viewsets.ModelViewSet):
serializer_class = UserProfileSerializer
queryset = UserProfile.objects.all()
urls
router.register(r'api/user/profile', UserProfileList)
I can see the first record as /api/user/profile/1/ But when I'll try to update record from Browsable Rest Api it gives me the error user with same username already exist.
Edit :
I want to update UserProfile Model using UserProfileSerializer . Simple create new records , Update existing one and delete.
You do have a constraint the username that makes it impossible to create again.
You need to remove it by altering validators UserSerializer.username.
Make sure you don't remove others constraints by printing the serializer:
>>> print(UserSerializer())
>>> UserSerializer():
id = IntegerField(label='ID', read_only=True)
first_name = CharField(allow_blank=True, max_length=30, required=False)
username = CharField(help_text='Required. 30 characters or fewer. Letters, digits and #/./+/-/_ only.', max_length=30, validators=[<django.core.validators.RegexValidator object>, <UniqueValidator(queryset=User.objects.all())>])
email = EmailField(allow_blank=True, label='Email address', max_length=254, required=False)
is_active = BooleanField(help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', label='Active', required=False)
As you can notice, we do have a UniqueValidator on the username as well as a RegexValidator
You'll need to alter the class:
from django.core import validators
from django.utils.translation import ugettext_lazy as _
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'id',
'first_name',
'username',
'email',
'is_active',
)
extra_kwargs = {
'username': {
'validators': [
validators.RegexValidator(
r'^[\w.#+-]+$',
_('Enter a valid username. This value may contain only '
'letters, numbers ' 'and #/./+/-/_ characters.')
),]
}
}
Note that the validator here is taken out of the Django definition of that field (https://github.com/django/django/blob/master/django/contrib/auth/models.py#L309)
Now if you print again the serializer you should see that the unique constraint is removed:
>>> print(UserSerializer())
>>> UserSerializer():
id = IntegerField(label='ID', read_only=True)
first_name = CharField(allow_blank=True, max_length=30, required=False)
username = CharField(help_text='Required. 30 characters or fewer. Letters, digits and #/./+/-/_ only.', max_length=30, validators=[<django.core.validators.RegexValidator object>])
email = EmailField(allow_blank=True, label='Email address', max_length=254, required=False)
is_active = BooleanField(help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', label='Active', required=False)
I created two custom user models (AbstractBaseUser and a separate for extra information). Is there a way to combine the two models to create one form that the user can use to update all of their information between the two models?
For example, it would be ideal to have something like this (although I know not possible):
class ProfileChangeForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['username', 'email', first_name', 'last_name', 'bio', 'website']
Thank you in advance for your help! The models are below:
MyUser:
class MyUser(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=30, unique=True)
email = models.EmailField(max_length=255, unique=True)
first_name = models.CharField(max_length=120, null=True, blank=True)
last_name = models.CharField(max_length=120, null=True, blank=True)
UserProfile:
class UserProfile(models.Model):
user = models.OneToOneField(MyUser)
bio = models.TextField(null=True, blank=True)
website = models.CharField(max_length=120, null=True, blank=True)
Following solution worked for me. I used formsets to create this solution.
My models were as follows,
Models:
#Custom user model
class CustomUserManager(BaseUserManager):
def create_user(self, email, password, **extra_fields):
if not email:
raise ValueError(_('The Email must be set'))
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save()
return user
class CustomUser(AbstractUser):
username = None
email = models.EmailField(_('email address'), unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = CustomUserManager()
#Related model(One-to-One relationship with custom user)
class Student(models.Model):
user = models.OneToOneField(CustomUser,on_delete = models.CASCADE)
first_name = models.CharField(max_length=50)
middle_name = models.CharField(max_length=50,blank=True,null=True)
last_name = models.CharField(max_length=50,blank=True,null=True)
After that I created two ModelForms
Forms
from django.contrib.auth.forms import UserCreationForm
from .models import CustomUser,Student
from django import forms
# Form for custom user
class SignUpForm(UserCreationForm):
class Meta:
model = CustomUser
fields = ('email', 'password1', 'password2')
class StudentCreationForm(forms.ModelForm):
class Meta:
model = Student
fields = ['user','first_name','middle_name','last_name']
Now the main part, I created a simple inline formset factory to handle Student model as an inline form.
Formset
from django.forms import inlineformset_factory
from .models import CustomUser,Student
from .forms import StudentCreationForm
# As parameters I provided parent Model(CustomUser),child Model(Student) and the Child
# ModelForm(StudentCreationForm)
StudentCreationFormSet = inlineformset_factory(CustomUser, Student,form=StudentCreationForm,extra=1,can_delete = False)
In views, I created the SignUpForm and StudentCreationFormSet object respectively. And in the POST request first I validated the CustomUser form and saved it without comitting it(commit=False). I created an object of custom user and passed it as a instance to the StudentCreationFormSet to validate the related form. If everything goes fine my both forms will be saved else the errors will be shown in the template.
View
from django.shortcuts import render,redirect
from .forms import SignUpForm
from .formsets import StudentCreationFormSet
def createAccountView(request):
student_account_form = SignUpForm()
student_details_formset = StudentCreationFormSet()
if request.method == 'POST':
student_account_form = SignUpForm(request.POST)
if student_account_form.is_valid():
# Validating Custom User form and creating object of it(not comitting as formset needed to be verified)
student_account = student_account_form.save(commit=False)
# Getting Custom User object as passing it as instance to formset
student_details_formset = StudentCreationFormSet (request.POST,instance=student_account)
if student_details_formset.is_valid():
student_account_form.save()
student_details_formset.save()
return redirect('login')
else:
student_details_formset = StudentCreationFormSet (request.POST)
context = {
'student_account_form':student_account_form,
'student_details_form':student_details_formset
}
return render(request, 'account/createStudentPage.html',context=context)
Also note that I am passing both the form and formset in single post request.
Template (createStudentPage.html)
<form method="POST" >
{% csrf_token %}
{{ student_account_form.as_p }}
{{ student_details_form.as_p }}
<button type="submit">Sign Up</button>
</form>
I think you can do something like :
class ProfileChangeForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['user__username', 'user__email', 'user__first_name', 'user__last_name', 'bio', 'website']
I have two django models related with a OneToOneField. I'll show the relevant fields:
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True, db_index=True)
And
class PasswordRecovery(models.Model):
user = models.OneToOneField(User, primary_key=True)
token = models.CharField(default=utils.make_token)
password = models.CharField(max_length=128)
Then I have a form for the PasswordRecovery model
class PasswordRecoveryForm(forms.ModelForm):
password_check = forms.CharField(max_length=128)
class Meta:
model = PasswordRecovery
fields = ('user', 'password', 'password_check')
A few details are omitted here. Tell me if you think they are relevant.
The problem is that the user field shows as a 'select' element and I would like it to be an email field. Instead of being able to chose a user from a lista I want it instead to required the email to be typed.
How can I do this?
Thanks in advance
You should override the clean_user method to pass the User objects
class PasswordRecoveryForm(forms.ModelForm):
user = forms.EmailField(required=True)
password_check = forms.CharField(max_length=128)
class Meta:
model = PasswordRecovery
fields = ('user', 'password', 'password_check')
def clean_user(self):
user_email = self.cleaned_data['user']
if User.objects.get(email__iexact=user_email):
return User.objects.get(email__iexact=user_email)
return None
#If you wanna to fill in the email add this
def __init__(self, *args, **kwargs):
super(PasswordRecoveryForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields['user'].initial = self.instance.user.email