DjangoRestFramework ModelSerializer: field-level validation is not working - django

This is my serializers.py (I want to create a serializer for the built-in User model):
from rest_framework import serializers
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'password', 'email', )
def validate_username(self, username):
if not re.search(r'^\w+$', username): #checks if all the characters in username are in the regex. If they aren't, it returns None
raise serializers.ValidationError('Username can only contain alphanumeric characters and the underscore.')
try:
User.objects.get(username=username)
except ObjectDoesNotExist:
return username
raise serializers.ValidationError('Username is already taken.')
The issue is, when I try to create a user using a username which already exists, it returns the following dictionary:
{'username': [u'This field must be unique.']}
rather than saying
{'username': [u'Username is already taken']}
I recreated the validate_username function to this (for testing purposes):
def validate_username(self, username):
raise serializers.ValidationError('Testing to see if an error is raised.')
and it doesn't raise an error. Any idea why DjangoRestFramework is ignoring the validate_username function?
Edit: Note that I am using a ModelSerializer (in the tutorial here: http://www.django-rest-framework.org/api-guide/serializers/#validation it talks about field-level validation only for a Serializer, not a ModelSerializer). Note sure if it makes a difference or not.

Field-level validation is called before serializer-level validation.
So model User having username as unique=True, the field-level validation will raise exception because of username being already present. DRF's UniqueValidator does this work of raising exception when a field is not unique.
As per DRF source code,
class UniqueValidator:
"""
Validator that corresponds to `unique=True` on a model field.
Should be applied to an individual field on the serializer.
"""
message = _('This field must be unique.')
Since these validators run before serializer-level validation, your validate_username is never called.

Try adding the following line in your serializer to do this validator working.
class UserSerializer(serializers.ModelSerializer):
username = serializers.CharField(max_length=32)
class Meta:
model = User
fields = ('username', 'password', 'email', )

Related

Django Rest Framework - Check Password to Validate Form

I'm trying to validate a form through DRF, but it would require the user to enter their password for confirmation. I can't seem to get it to work. Here is my current View and Serializer. Its for a 'change email' form, two fields required, the email and user password. It's for a seperate email model. The serializer:
class UpdateEmailAddressSerializer(serializers.ModelSerializer):
class Meta:
model = EmailAddress
fields = ('email',)
And the APIView:
class UpdateEmailAPI(APIView):
permission_classes = (IsAuthenticated,)
serializer_class = UpdateEmailAddressSerializer
def post(self, request, user, format=None):
user = User.objects.get(username=user)
serializer = UpdateEmailAddressSerializer(data=request.data, instance=user)
if serializer.is_valid():
## logic to check and send email
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I'm not sure where to place the password or what to do with it. Its from the User model itself. When I attempted to add password to the fields in the UpdateEmail serializer it ended up updating the User password with plain text and making that user object unable to use that password.
I just want to check the password of the user for confirmation of this form. Is there an obvious way to do this?
EDIT
When I attempt to bring 'password' into the serializer, an error tells "Field name password is not valid for model EmailAddress." So when I attempt to bring it in e.g.
password = serializers.CharField(required=True)
or try:
## UserPasswordSerializer
class UserPasswordSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'password',
)
## In UpdateEmailAddressSerializer
password = UserPasswordSerializer()
I get this error when submitting the form on DRF:
Got AttributeError when attempting to get a value for field
password on serializer UpdateEmailAddressSerializer. The
serializer field might be named incorrectly and not match any
attribute or key on the EmailAddress instance. Original exception
text was: 'EmailAddress' object has no attribute 'password'
So it seems to be telling me password isn't part of EmailAddress model which is correct. But I cant figure out how to simply check the password alongside the form post without making it part of EmailAddress.
I think you can try like this:
class UpdateEmailAddressSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = EmailAddress
fields = ('email', 'password',)
def create(self, validated_data):
validated_data.pop('password', None)
return super(UpdateEmailAddressSerializer, self).create(validated_data)
def update(self, instance, validated_data):
if instance.check_password(validated_data.get('password')):
instance.email = validated_data.get('email', instance.email)
# else throw validation error
return instance

Validation for model forms

I am trying to do validation for model forms to check if both 'email' and 'confirm_email' have same value. I tried searching online but getting some errors. I am making custom validators in models.py file.
Can you please help me with that. What would be the best way of validating model forms.
Here is my code.
MODELS.PY
from django.db import models
from django.core import validators
from django.core.exceptions import ValidationError
# Create your models here.
def validate_equal(self, email, confirm_email):
if email != confirm_email:
raise ValidationError(
('email does not match'),
params={'email': email, 'confirm_email': confirm_email}
)
class NewSubscriber(models.Model):
first_name = models.CharField(max_length=128)
last_name = models.CharField(max_length=128)
email = models.EmailField(max_length=254,unique=True)
confirm_email = models.EmailField(max_length=254, validators=[validate_equal('self', 'email', 'confirm_email')])
You can't do validation like that, especially when you want to compare fields. All you're doing here is passing the literal strings 'email' and 'confirm_email' (as well as 'self', for some reason) - and you're calling the validation function at define time.
Instead, use a clean method on the form itself.
class NewSubscriberForm(forms.ModelForm):
class Meta:
fields = '__all__'
def clean(self):
if self.cleaned_data['email'] != self.cleaned_data['confirm_email']:
raise forms.ValidationError('email does not match')
return self.cleaned_data

Django - custom validation for different users

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.

Django 1.5: UserCreationForm & Custom Auth Model

I'm using Django 1.5 & Python 3.2.3.
I've got a custom Auth setup, which uses an email address instead of a username. There's no username defined in the model at all. That works fine. Yet, when I build a user creation form, it adds in a username field anyway. So I tried defining exactly which fields I want displayed, but it's still forcing a username field into the form anyway.... even tho it doesn't even exist in the custom auth model. How can I make it stop doing that?
My form for this is defined like so:
class UserCreateForm(UserCreationForm):
class Meta:
model = MyUsr
fields = ('email','fname','surname','password1','password2',
'activation_code','is_active')
At the docs, the Custom Users and Builtin Forms says it "Must be re-written for any custom user model." and I think that's what I'm doing here. Neither this, nor the UserCreationForm documentation say anything more about this though. So I don't know what I'm missing. I didn't find anything via Google either.
Your UserCreationForm should look something like
# forms.py
from .models import CustomUser
class UserCreationForm(forms.ModelForm):
password1 = forms.CharField(label="Password", widget=forms.PasswordInput)
password2 = forms.CharField(label="Password confirmation", widget=forms.PasswordInput)
class Meta:
model = CustomUserModel
# Note - include all *required* CustomUser fields here,
# but don't need to include password1 and password2 as they are
# already included since they are defined above.
fields = ("email",)
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
msg = "Passwords don't match"
raise forms.ValidationError("Password mismatch")
return password2
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
You'll also want a user change form, which won't overrwrite the password field:
class UserChangeForm(forms.ModelForm):
password = ReadOnlyPasswordHashField()
class Meta:
model = CustomUser
def clean_password(self):
# always return the initial value
return self.initial['password']
Define these in your admin like this:
#admin.py
from .forms import UserChangeForm, UserAddForm
class CustomUserAdmin(UserAdmin):
add_form = UserCreationForm
form = UserChangeForm
You'll also need to override list_display, list_filter, search_fields, ordering, filter_horizontal, fieldsets, and add_fieldsets (everything in django.contrib.auth.admin.UserAdmin that mentions username, I think I listed all of it).
You need to create your form from sctratch, it should not extend the UserCreationForm. The UserCreationForm have a username field explicitly defined in it as well as some other fields. You can look at it here.

Django admin: Unique email verification fails against self

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