Django Rest Framework custom validation errors - validation

I'm trying to customize the default validation errors of DRF (3.x) for an account model.
My goal is to write a validation function in order to send back a customized error message.
I've tried the following:
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=False)
class Meta:
model = Account
fields = ('id', 'email', 'password',)
def validate_password(self, value):
"""
Validate Password.
"""
if not value:
raise serializers.ValidationError("Password cannot be empty!")
elif len(value) < 5:
raise serializers.ValidationError("Password to short...")
return value
The the length validation works fine, but the 'password is empty' validation is never thrown because the default error ('password', [u'This field may not be blank.']) is thrown before.
Is there any option to disable default errors or to force validation by my custom function first?
Thanks for help!

You can override the validation errors on a per-field basis by setting the error_messages argument when initializing the field. You need to pass a dictionary with the key being the error message name, and the value being the custom text for the error message.
In your case, you are looking to implement two error messages: required and min_length. You may also need to override blank, which is triggering your current error, unless you set allow_blank=True on the field.
So with those changes, your serializer would become
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(
write_only=True,
required=False,
min_length=5,
error_messages={
"blank": "Password cannot be empty.",
"min_length": "Password too short.",
},
)
class Meta:
model = Account
fields = ('id', 'email', 'password', )
I've replaced your len check with the min_length argument on the password field.
This offloads all of your validation to Django REST framework, which should make upgrades easier in the future. You can still override validate_password if you need additional custom validation, but for now I've removed it since it would be empty.

Found a solution:
I had to validate the values with the to_internal_value function because validation is run in a specific order (thanks to Kevin Brown):
order of validation
Now my improved code is as follows:
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=False)
class Meta:
model = Account
fields = ('id', 'email', 'password',)
def to_internal_value(self, data):
password = data.get('password')
"""
Validate Password.
"""
if not password:
raise serializers.ValidationError({'password': 'Password cannot be empty!'})
elif len(password) < 5:
raise serializers.ValidationError({'password': 'Password to short...'})
return {
'password': password
}
I hope this post is useful for somebody :)

Related

How to pass data between views and forms using form_valid

I have a simple UserRegistration view:
class UserRegistration(generic.CreateView):
form_class = RegisterForm
template_name = 'registration/registration.html'
success_url = reverse_lazy('home')
def form_valid(self, form):
user = form.save()
login(self.request, user, backend='django.contrib.auth.backends.ModelBackend')
return redirect(self.success_url)
Which logs the User in after they register. I wanted to send an email confirmation letting the User know they successfully signed up. So I added a method to my RegisterForm:
class RegisterForm(UserCreationForm):
email = forms.EmailField()
first_name = forms.CharField(max_length=100)
last_name = forms.CharField(max_length=100)
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2')
def registration_email(request):
send_mail(
'Welcome aboard!',
'Thanks for signing up...',
NOTIFICATION_EMAIL,
['example#email.com'],
fail_silently=True
)
Then I call it in my view with form.registration_email().
Now, this works but it's hardcoded. I want the recipient email to be the new User's email they just entered. That info should be available in the view, but how do I pass that to my form? I just can't figure out how to pass data from my views to my form methods, so that I can then call those methods in my view if form is valid.
Should I be doing it like this? Or should I be doing all this in form_valid instead? I also plan on using Celery in the future, so is there a preferred way with regards to that as well?
Where should to be send_email method depends on your logic in project.
In your case:
class RegisterForm(UserCreationForm):
...
def registration_email(self, request):
# it should be only after from.is_valid()
# to protect us:
if self.is_valid():
send_mail(
'Welcome aboard!',
'Thanks for signing up...',
NOTIFICATION_EMAIL,
[self.cleaned_data['email']],
fail_silently=True
)

How to set custom User field using allauth save_user method from POST data

So I have 2 types of users, and on one of the pages of my website I want to provide additional field cardholder and check if it's true during user sign-up via Google
class DefaultSocialAccountAdapterCustom(DefaultSocialAccountAdapter):
def save_user(self, request, sociallogin, form=None):
"""Check if it's cardholder registration"""
user = super().save_user(request, sociallogin, form)
if request.data.get('cardholder', False): # <-- how to get this POST value??
user.cardholder = True
user.save()
return user
but error I receive:
'WSGIRequest' object has no attribute 'data'
How can I read cardholder value from request during user sign up via social adapter and set cardholder field of user to True?
I solved my problem with custom serializer class of my GoogleAuthView:
class GoogleAuthView(SocialLoginView):
adapter_class = GoogleOAuth2Adapter
serializer_class = CardholderSocialLoginSerializer
here I'm setting custom field cardholder by overwriting serializer's validate() method like this:
class CardholderSocialLoginSerializer(SocialLoginSerializer):
"""Adds cardholder field for Google registration"""
cardholder = serializers.BooleanField(required=False, default=False)
def validate(self, attrs):
attrs = super().validate(attrs)
user = attrs['user']
if attrs.get('cardholder'):
user.cardholder = True
user.save()
return attrs
If you want to get the POST value for cardholder, i believe you will have to implement the following code....
if request.type == 'POST':
cardholder = request.POST['cardholder']
Hopefully it helps...

django rest-framework add fields to ModelSerializer

I have the following serializer:
class AMXModXAdminsSerializer(mixins.GetCSConfigMixin, serializers.ModelSerializer):
admin = serializers.CharField(label='Admin', max_length=35, required=True, write_only=True)
password = serializers.CharField(label='Password', max_length=35, required=False, write_only=True)
access_flags = serializers.MultipleChoiceField(choices=ACCESS_FLAGS_OPTIONS, required=True, write_only=True)
account_flags = serializers.MultipleChoiceField(choices=ACCOUNT_FLAGS_OPTIONS, required=True, write_only=True)
class Meta:
model = CS16Server
fields = ('name', 'amxadmins', 'admin', 'password', 'access_flags', 'account_flags')
read_only_fields = ('name', 'amxadmins',)
When I try to access the url it complains:
Got AttributeError when attempting to get a value for field `admin` on serializer `AMXModXAdminsSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `CS16Server` instance.
Original exception text was: 'CS16Server' object has no attribute 'admin'.
If I add write_only to each field, the error will go away.
The thing is that I have a similar serializer, for the same model, with fields which do not belong to the model and it works perfectly without adding "write_only=True" to each field.
Any idea why one would work and another one no ?
What do u mean "when i access" ? post get put patch ?
Error says:
'CS16Server' object has no attribute 'admin'.
Does it ? if not , where do u intend to write it to ?
If model does not have admin field (as mentioned in error ) you need something like this:
class AMXModXAdminsSerializer(mixins.GetCSConfigMixin, serializers.ModelSerializer):
admin= serializers.SerializerMethodField()
fields ...
...
def get_admin(self, obj):
do somthing with self (contains the request) or the obj you're working on
return theOUTcome
If you set required=False it will not complain anymore because it will not try to get those fields values from db.

Django rest framework - Raising exception / Handling empty results while filtering

I have a user profile class and am checking if a user exists and if not want to create that user.
Am using the filter class for userprofile so that the client can call :
http://localhost:8000/users/?email=a#b.com
and if the result is empty will create a user with the email address.
Is there a way to intercept the query result and raise an exception when its empty and handle that to create the user.
If there is a better way would like to be corrected as well.
class UserQueryFilter(django_filters.rest_framework.FilterSet):
email = django_filters.CharFilter(name="user__email")
username = django_filters.CharFilter(name="user__username")
class Meta:
model = UserProfile
fields = ['email', 'username']
class UserViewSet(viewsets.ReadOnlyModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserSerializer
filter_class = UserQueryFilter
Any help is appreciated.
Thanks
Anand
Django Rest Framework provide a functionality that is disabled by default. Maybe it could give you another approach to resolve your problem: PUT as create
In other hand, if you really need to create the user through a GET request with a querystring, you can use a MethodFilter from django-filters, for example:
class UserFilters(FilterSet):
user = MethodFilter(action='filter_user')
class Meta:
model = User
fields = ['user']
def filter_user(self, queryset, value):
if not value:
# Here Raise Exception
else:
# Check if the user exists, if not create it
users = queryset.filter(Q(username=value) | Q(email=value))
if not users.exists:
user = User.objects.create(...)
return queryset.filter(pk=user.id)
else:
return users
Hope this can help you. I'm not pretty sure about it works in that exact way but it's the idea.
Personally, I recommend you that try to execute that tasks through a more appropriate request like POST or PUT and manage in the corresponding method.

DRF 3.0: UniqueTogetherValidator with a read-only field

In process of upgrading to Django REST Framework 3.0 from 2.4.4 and I want to have a read-only user field, but this is failing because 'user' is being required by the UniqueTogetherValidator (I think)
I have model (excuse typos, this is simplified and the code works fine IRL):
class ExampleModel(models.Model):
some_attr = models.PositiveSmallIntegerField()
other_attr = models.PositiveSmallIntegerField()
user = models.ForeignKey(User)
class Meta:
unique_together = ('some_attr', 'other_attr', 'user')
Viewset:
class ExampleViewSet(viewsets.ModelViewSet):
queryset = ExampleModel.objects.all()
serializer_class = ExampleSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def perform_update(self, serializer):
serializer.save(user=self.request.user)
Serializer:
class ExampleSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = ExampleModel
Now, I keep getting errors saying: {"user":["This field is required."]}, which was not the case before. In a slightly different example with the same basic problem, I get the assertion error May not set both 'read_only' and 'required' even though I am not setting user as required.
I receive the same error regardless if I add required=False for the user attribute in the serializer, or if I add user to the the excluded fields in the serializer's meta.
When I use the handy new serializer printing, I see:
class Meta:
validators = [UniqueTogetherValidator(queryset=ExampleModel.objects.all(), fields=('user', 'some_attr', 'other_attr'))]
which gets automatically added based on the model's unique_together. If I explicitly overwrite this and do not include 'user' in the fields for UniqueTogetherValidator then everything works as before.
Is this an intended consequence of the 3.0 update? Seems to me that adding request.user in the perform_create / perform_update is very standard DRF procedure as demonstrated in the tutorial. I realize not having the new validation just means failing at the DB level instead, and the new validation probably gives better error messages, but
Is there a solution other than to override the validation for every serializer where this is an issue?
Thanks in advance for any help!
This is a known issue that we are in the process of addressing within Django REST Framework. As of right now, there is a note in the documentation about UniqueTogtherValidator that says
Note: The UniqueTogetherValidation class always imposes an implicit constraint that all the fields it applies to are always treated as required. Fields with default values are an exception to this as they always supply a value even when omitted from user input.
This explains why you are seeing an error because the field is required, even though you are explicitly settings read_only=True. You may want to look into the CurrentUserDefault class, which may suit your needs while avoiding the issue with the UniqueTogetherValidator.
class ExampleSerializer(serializers.ModelSerializer):
user = UserSerializer(
read_only=True
default=serializers.CurrentUserDefault()
)
class Meta:
model = ExampleModel
This should do the same thing as your perform_create and perform_update hooks.

Resources