I am building simple form which allows user to change his basic (first and last name, email) data.
I want to be sure that:
emails still unique across database
user can leave his email untouched
user can change his email
I wanted to use ModelForm for this. I've finished with something like:
class UserDataForm(ModelForm):
class Meta:
model = User
fields = ['first_name', 'last_name', 'email']
def clean_email(self):
cd = self.cleaned_data
email = cd['email']
# Not sure how to check is there is an other account which uses this email EXCEPT this particular user account
I need to show validation error message when there is another account which uses same email AND this account isn't owned by user who is filling the form.
I don't know how to achieve this.
Try this:
class UserDataForm(ModelForm):
class Meta:
model = User
fields = ['first_name', 'last_name', 'email']
def clean_email(self):
cd = self.cleaned_data
email = cd['email']
# object is exists and email is not modified, so don't start validation flow
if self.instance.pk is not None and self.instance.email == email:
return cd
# check email is unique or not
if User.objects.filter(email=value).exists():
raise forms.ValidationError("Email address {} already exists!".format(value))
return cd
Look at this question, I think it will be helpful.
Another way try to check email in clean method:
def clean(self):
cleaned_data = self.cleaned_data
if 'email' in self.changed_data and User.objects.filter(email=value).exists():
raise forms.ValidationError("Email address {} already exists!".format(value))
return cleaned_data
Related
I am creating a REST api for user registration, and I have a nested serializer where I store additional information about a user.
The User serializer asks for first_name, last_name, email, and password.
The nested serializer asks for agreed_terms_of_service
email, password, and agreed_terms_of_service are required.
But if a user keys in their email and password and DOES NOT check the agreed_terms_of_service box, it returns and error, but still creates a user with the email and password.
Then when the user goes to 'remedy the situation', the email address is already in use.
If I update instead of create, I feel like I would run into a situation where people are overwriting other users...
I am wondering how people handle this with django rest serializers and what is the best practice?
VIEWS.PY
def serialize(self, request):
if request.method =='POST':
data = json.loads(request.body)
#first validation
if data['password'] != data['password2']:
raise serializers.ValidationError({'msgType':'error','message':'Passwords do not match.'})
#move to serializer
else:
serializer = userSerializer(data = data)
data['username'] = data['email']
if serializer.is_valid(raise_exception=True):
serializer.save()
response = {'msgType':'success', 'message':'Your account has been created successfully.'}
elif serializer.errors:
raise serializers.ValidationError({'msgType':'error', 'message': serializer.errors})
return Response(response)
SERIALIZERS.PY
class nestedSerializer(serializers.ModelSerializer):
class Meta:
model = Nested
fields = ('agreed_terms_of_service')
def validate(self, data):
return data
class userSerializer(serializers.ModelSerializer):
nested = nestedSerializer()
class Meta:
model = User
fields = ('pk','email', 'password', 'username','first_name','last_name','nested')
def validate(self, data):
email = data['email']
try:
User.objects.get(email = email)
except User.DoesNotExist:
return data
else:
raise serializers.ValidationError({'msgType':'error', 'message':'A user with this email address already exists.'})
return data
def create(self, validated_data):
nested_data = validated_data.pop('extend')
email = validated_data['email']
user = User.objects.create(**validated_data)
user.username = user.id
user.set_password(validated_data['password'])
user.save()
nested = Nested.objects.create(user=user, **nested_data)
return user
Models.py
class Nested(models.Model):
user = models.OneToOneField(User)
personalid = models.CharField(max_length=255)
agreed_terms_of_service = models.BooleanField()
city = models.CharField(max_length=255, blank=True, null=True)
Thank you for your help in advance. It is much appreciated.
First, I'd change your current validate() function to validate_email() (because all you're doing is validating that the email is not already in use). You should use validate() if you want access to multiple fields in your function. See the documentation here to read more about when you should use field-level validation and object-level validation: http://www.django-rest-framework.org/api-guide/serializers/#validation
Second, in your view, you do:
if data['password'] != data['password2']:
raise serializers.ValidationError({'msgType':'error','message':'Passwords do not match.'})
If you're verifying that "password" and "confirm password" field match, I'd do that check in the validate() function of your serializer (since you'll be accessing both the 'password' and the 'password2' field.
Third, in your create method, I'd use User.objects.create_user to create a user (create_user will handle the hashing of the password, etc. That way, you don't need to explicitly do user.set_password(validated_data['password'])). See the answer here for more information: How to create a user in Django?
Lastly, to address the main issue. Your "agreed_terms_of_service" is a Boolean field, which means it accepts both True and False. What I'd try is this:
class nestedSerializer(serializers.ModelSerializer):
class Meta:
model = Nested
fields = ('agreed_terms_of_service')
def validate_agreed_terms_of_service(self, data):
print(data) # to verify if data is even a boolean
if data == True or data == 'True':
return data
raise serializers.ValidationError({'msgType':'error', 'message':'Please accept the terms and conditions.'})
and in your create function for your userSerializer, add a print statement at the beginning to see if create is being executed before the "agreed_terms_of_service" validation.
def create(self, validated_data):
print("Creating the object before validating the nested field.")
# I'd be surprised if DRF executes a create function before
# even validating it's nested fields.
# rest of the create code goes here
When you add the statements above, what does it print for "data" and does it print "data" before "creating the object"?
I want to create a SINGLE form which gives the ability to the admin to create a new user with extended profile. Please note that, I don't want to use admin and registration apps.
I have extended the user with the UserProfile model. I have read all the documents related to extending user profile. But, I really don't know how to save these information.
I coded the following django form for this issue:
class CreateUserForm(forms.Form):
username = forms.CharField(max_length=30)
first_name = forms.CharField()
last_name = forms.CharField()
password1=forms.CharField(max_length=30,widget=forms.PasswordInput()) #render_value=False
password2=forms.CharField(max_length=30,widget=forms.PasswordInput())
email=forms.EmailField(required=False)
title = forms.ChoiceField(choices=TITLE_CHOICES)
def clean_username(self): # check if username dos not exist before
try:
User.objects.get(username=self.cleaned_data['username']) #get user from user model
except User.DoesNotExist :
return self.cleaned_data['username']
raise forms.ValidationError("this user exist already")
def clean(self): # check if password 1 and password2 match each other
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:#check if both pass first validation
if self.cleaned_data['password1'] != self.cleaned_data['password2']: # check if they match each other
raise forms.ValidationError("passwords dont match each other")
return self.cleaned_data
def save(self): # create new user
new_user=User.objects.create_user(username=self.cleaned_data['username'],
first_name=self.cleaned_data['first_name'],
last_name=self.cleaned_data['last_name'],
password=self.cleaned_data['password1'],
email=self.cleaned_data['email'],
)
return new_user
Is it OK? however it gives me an error in first_name and last_name. Says django doesn't expect first_name and last_name in save() method.
create_user only supports the username, email and password arguments. First call create_user, then add the extra values to the saved object.
new_user=User.objects.create_user(self.cleaned_data['username'],
self.cleaned_data['email'],
self.cleaned_data['password1'])
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.save()
I have a date time field called bk_time. Now, I would like to write custom validation for bk_time based on different users.
For example, I have users Staff_A, Staff_B and Superuser:
Staff_A can only set the time = Mon-Fri 9am-12am
Staff_B can only set the time = Monday only
Superuser no limitation
I have referred Django Doc Validators. But it seems not working for multiple validation
I have tried to write save_formsetDjango Doc Admin.But it seems not able to raise ValidationError
models.py
class Location(models.Model):
name = models.CharField('Location', max_length=100)
class Room(models.Model):
room_label = models.CharField('Room Lebel', max_length=100)
bk_time= models.DateTimeField('Booking Time')
admin.py
class RoomInline(admin.StackedInline):
model = Room
extra = 0
class LocationAdmin(admin.ModelAdmin):
list_display = ['id', 'name']
fields = ('name')
inlines = [RoomInline]
If this is relevant, I'm using Django 1.4.
I think this has to come on the form validation, and not on the field validation. This is because your validation depends on two independent fields.
In particular, this is very similar to an authentication: your validation depends on the user and on another field. Take a look how Django implements its authentication (from django.contrib.auth):
class AuthenticationForm(forms.Form):
[...]
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username and password:
self.user_cache = authenticate(username=username,
password=password)
if self.user_cache is None:
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login',
params={'username': self.username_field.verbose_name},
)
elif not self.user_cache.is_active:
raise forms.ValidationError(
self.error_messages['inactive'],
code='inactive',
)
return self.cleaned_data
In your case, you want to raise a ValidationError on a given constraint, and return cleaned_data otherwise.
Hi Here's a snippet from my admin.py
#admin.py
class UserForm(forms.ModelForm):
class Meta:
model = User
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError("This email already used")
return email
class UserAdmin(admin.ModelAdmin):
form = UserForm
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
I use this to check that a new user cannot be created with an email address already used. The problem is that when I edit an existing user the validation check fails, because there is a user with that mail address, but that's OK because it's the one I'm editing.
How can I tell the form validation to ignore the match against the current user?
Exclude the current instance from your query:
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exclude(pk=self.instance.pk).exists():
raise forms.ValidationError("This email already used")
return email
It's much more better to validate uniqueness using unique on model field.
You can use custom User model with unique email constraint.
Look at this for more info about implementing unique validation on your own https://stackoverflow.com/a/1560617/527064
I want to create a SINGLE form which gives the ability to the admin to create a new user with extended profile. Please note that, I don't want to use admin and registration apps.
I have extended the user with the UserProfile model. I have read all the documents related to extending user profile. But, I really don't know how to save these information.
I coded the following django form for this issue:
class CreateUserForm(forms.Form):
username = forms.CharField(max_length=30)
first_name = forms.CharField()
last_name = forms.CharField()
password1=forms.CharField(max_length=30,widget=forms.PasswordInput()) #render_value=False
password2=forms.CharField(max_length=30,widget=forms.PasswordInput())
email=forms.EmailField(required=False)
title = forms.ChoiceField(choices=TITLE_CHOICES)
def clean_username(self): # check if username dos not exist before
try:
User.objects.get(username=self.cleaned_data['username']) #get user from user model
except User.DoesNotExist :
return self.cleaned_data['username']
raise forms.ValidationError("this user exist already")
def clean(self): # check if password 1 and password2 match each other
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:#check if both pass first validation
if self.cleaned_data['password1'] != self.cleaned_data['password2']: # check if they match each other
raise forms.ValidationError("passwords dont match each other")
return self.cleaned_data
def save(self): # create new user
new_user=User.objects.create_user(username=self.cleaned_data['username'],
first_name=self.cleaned_data['first_name'],
last_name=self.cleaned_data['last_name'],
password=self.cleaned_data['password1'],
email=self.cleaned_data['email'],
)
return new_user
Is it OK? however it gives me an error in first_name and last_name. Says django doesn't expect first_name and last_name in save() method.
create_user only supports the username, email and password arguments. First call create_user, then add the extra values to the saved object.
new_user=User.objects.create_user(self.cleaned_data['username'],
self.cleaned_data['email'],
self.cleaned_data['password1'])
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.save()