When and where is `Field.blank` checked by DRF? - django

I have a model
class SomeModel(models.Model):
emails = ArrayField(models.EmailField(), default=list)
And let's say I have the following Serializer of the model:
class SomeModelSerializer(serializers.ModelSerializer):
class Meta:
model = SomeModel
fields = ['emails']
The email field is not blank-able, i.e: It's required to set a value for it when submitting a Form of the model, or when making changes to its Admin page.
My understanding is that DRF relies as well on Django's internal machinery to validate whether emails is missing on the Serializer data or not. But the thing is that I can't find where (and when) this happens.
I've found that DRF is not calling the Model's clean() method anymore (link). But what baffles me is that changing the blank value on the field seems to have a direct impact on the Serializer. I have switched to blank=True, and then the Serializer would allow it to be saved without that field... Then I switched back to blank=False, and the Serializer would fail if emails is not present.
So do you have any idea of when and where DRF checks for a field's blank value?
Thanks!

As far as I know, it simply doesn't. Those are only used across forms and the django admin interface.
I always specify those things on the serializer level, by setting the appropiate arguments for my fields (doc), in this case it would be allow_blank.
I am building REST APIs with django, and the only case where the blank property on the model field catches me, is when fiddling around on the admin page.
However, there appears to be a package that could be of interest to you:
django-seriously.
I haven't used it, but it appears to call full_clean() on every save().
Of course, this has the disadvantage that you will probably loose DRFs nice error messages.

Related

Problems with django rest framework default field validation

I have the following problems for a nested model like this:
def Post(models.Model)
name = models.CharField(unique=True)
content = models.TextField()
def Comment(models.Model)
post = models.ForeignKey(Post)
content = models.CharField()
I created default model serializers with all fields.
Problems:
The default model serializer does not work for nested models. I have to explicitly write create/update. This has been explained in the documentation, so nothing against it. Although I think choosing sane default can cater to 99% of use cases (and for the rest, behaviour can be customisable). I will try to take a shot at this.
When I try to use json from existing post object, serializer is_valid() fails saying "unique constraint on name fails". But I wanted it to update and not create. Should is_valid not be create/update aware based on id being passed in json.
When creating a new nested json with many comments, is_valid() fails saying that "post is empty". Of course I will not have post id in the json, as post creation is yet to happen. So is_valid becomes useless. Should is_valid not depend on if id is passed in json? Also, I can not use data/validated_data without having is_valid pass.
Setting validators = [] also does not remove field validations. I have not yet found a way to suppress field validations.
I have gone through source code and documentation and spent more than a day to set up something so simple.
I must be missing something simple, so any help is appreciated.

Modify data before validation step with django rest framework

I have a simple Model that stores the user that created it with a ForeignKey. The model has a corresponding ModelSerializer and ModelViewSet.
The problem is that when the user submits a POST to create a new record, the user should be set by the backend. I tried overriding perform_create on the ModelViewSet to set the user, but it actually still fails during the validation step (which makes sense). It comes back saying the user field is required.
I'm thinking about overriding the user field on the ModelSerializer to be optional, but I feel like there's probably a cleaner and more efficient way to do this. Any ideas?
I came across this answer while looking for a way to update values before the control goes to the validator.
This might be useful for someone else - here's how I finally did it (DRF 3) without rewriting the whole validator.
class MyModelSerializer(serializers.ModelSerializer):
def to_internal_value(self, data):
data['user'] = '<Set Value Here>'
return super(MyModelSerializer, self).to_internal_value(data)
For those who're curious, I used this to round decimal values to precision defined in the model so that the validator doesn't throw errors.
You can make the user field as read_only.
This will ensure that the field is used when serializing a representation, but is not used when creating or updating an instance during deserialization.
In your serializers, you can do something like:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
extra_kwargs = {
'user' : {'read_only' : True} # define the 'user' field as 'read-only'
}
You can then override the perform_create() and set the user as per your requirements.
Old topic but it could be useful for someone.
If you want to alter your data before validation of serializer:
serializer.initial_data["your_key"] = "your data"
serializer.is_valid(raise_exception=True)

Model datetime field validation for fields with auto_now

I am very new to django and python in general, and I was trying to learn rest_framework to create RESTful APIs.
So i have a model like this:
class Listing(models.Model):
listingid = models.BigIntegerField(primary_key=True)
sellerid = models.IntegerField()
createdon = models.DateTimeField(auto_now_add=True, editable=False)
expirydate = models.DateTimeField(null=True)
validationstatus = models.SmallIntegerField(default=0)
listingstatus = models.SmallIntegerField(
choices=((0, 'Active'),
(1, 'Hidden'),
(2, 'Suspended'),
(4, 'Expired'),
(5, 'Deleted'),
),
default=0)
Now i need to validate that the expirydate is always greater than the createdon date.
I know i can do this in the views, I guess that would not be a good idea, since now the validation only exists in the views.
So that leaves me with the serializers and the model.
I know I can override the save method to do check this like so:
class MasterListing(models.Model):
# fields here..
def save(self, *args, **kwargs):
if self.expirydate > self.createdon:
super().save(*args, **kwargs)
return ValidationError("Expiry date cannot be greater than created date ("++")")
but I dont know if this would be a good idea, since now I am raising an error which the programmer may forget to catch. I am also not sure if the fields would be populated when this method would run.
Another way I read about in the docs is the clean method which i couldn't really understand so well.
Can anyone guide me on how to handle situations like this when you are working with the rest_framework?
Some of the things I have read about validation till now:
Serializer Validation
Field level validation
Validators
Model Validation
override clean method
override save method
Just do it manually in the views
There seem to be so many options, and I might have even left a few, I could not clearly get an idea of when to use where.
I am sorry if this is a little on the beginner level, but i am new to frameworks and django seems to be very different from what i was doing in PHP. Any advice is welcome!
Edit: I will be using django for the rest_framework only and nothing else, since we only want to build RESTful APIs.
Django REST framework used to call Model.clean, which was previously the recommended place for putting validation logic that needed to be used in Django forms and DRF serializers. As of DRF 3.0, this is no longer the case and Model.clean will no longer be called during the validation cycle. With that change, there are now two possible places to put in custom validation logic that works on multiple fields.
If you are only using Django REST framework for validation, and you don't have any other areas where data needs to be manually validated (like a ModelForm, or in the Django admin), then you should look into Django REST framework's validation framework.
class MySerializer(serializers.ModelSerializer):
# ...
def validate(self, data):
# The keys can be missing in partial updates
if "expirydate" in data and "createdon" in data:
if data["expirydate"] < data["createdon"]:
raise serializers.ValidationError({
"expirydata": "Expiry date cannot be greater than created date",
})
return super(MySerializer, self).validate(data)
If you need to use Django REST framework in combination with a Django component that uses model-level validation (like the Django admin), you have two options.
Duplicate your logic in both Model.clean and Serializer.validate, violating the DRY principle and opening yourself up to future issues.
Do your validation in Model.save and hope that nothing strange happens later.
but I dont know if this would be a good idea, since now I am raising an error which the programmer may forget to catch.
I would venture to say that it would be better for the error to be raised than for the saved data to possibly become invalid on purpose. Once you start allowing invalid data, you have to put in checks anywhere the data is used to fix it. If you don't allow it to go into an invalid state, you don't run into that issue.
I am also not sure if the fields would be populated when this method would run.
You should be able to assume that if an object is going to be saved, the fields have already been populated with their values.
If you would like to both Model Validation and Serializer validation using Django REST Framework 3.0, you can force your serializer to use the Model validation like this (so you don't repeat yourself):
import rest_framework, django
from rest_framework import serializers
class MySerializer(serializers.ModelSerializer):
def validate(self, data):
for key, val in data.iteritems():
setattr(self.instance, key, val)
try:
self.instance.clean()
except django.core.exceptions.ValidationError as e:
raise rest_framework.exceptions.ValidationError(e.message_dict)
return data
I thought about generating a new function from my model's clean() function's code, and have it either spit out django.core.exceptions.ValidationError or rest_framework.exceptions.ValidationError, based on a parameter source (or something) to the function. Then I would call it from the model, and from the serializer. But that hardly seemed better to me.
If you want to make sure that your data is valid on the lowest level, use Model Validation (it should be run by the serializer class as well as by (model)form classes (eg. admin)).
If you want the validation to happen only in your API/forms put it in a serializer/form class. So the best place to put your validation should be Model.clean().
Validation should never actually happen in views, as they shouldn't get too bloated and the real business logic should be encapsulated in either models or forms.

Django model field validation without a custom form

I am using a django DateField in my model.
class CalWeek(models.Model):
start_monday = models.DateField(verbose_name="Start monday")
I have a custom validation method that is specific to my modelField: I want to make sure that it is actually a Monday. I currently have no reason to use a custom ModelForm in the admin--the one Django generates is just fine. Creating a custom form class just so i can utilize the clean_start_monday(self)1 sugar that django Form classes provide seems like a lot of work just to add some field validation. I realize I can override the model's clean method and raise a ValidationError there. However, this is not ideal: these errors get attributed as non-field errors and end up at the top of the page, not next to the problematic user input--not an ideal UX.
Is there an easy way to validate a specific model field and have your error message show up next to the field in the admin, without having to use a custom form class?
You can look into Django Validators.
https://docs.djangoproject.com/en/dev/ref/validators/
You would put the validator before the class, then set the validator in the Field.
def validate_monday(date):
if date.weekday() != 0:
raise ValidationError("Please select a Monday.")
class CalWeek(models.Model):
start_date = models.DateField(validators=[validate_monday])

does django models offer something similar to forms' clean_<fieldname>()?

I am trying to move all business-logic-related validations to models, instead of leaving them in forms. But here I have a tricky situation, for which I like to consult with the SO community.
In my SignupForm (a model form), I have the following field-specific validation to make sure the input email does not exist already.
def clean_email(self):
email = self.cleaned_data['email']
if ExtendedUser.objects.filter(email=email).exists():
raise ValidationError('This email address already exists.')
return email
If I were to move this validation to the models, according to the official doc, I would put it in clean() of the corresponding model, ExtendedUser. But the doc also mentions the following:
Any ValidationError exceptions raised by Model.clean() will be stored
in a special key error dictionary key, NON_FIELD_ERRORS, that is used
for errors that are tied to the entire model instead of to a specific
field
That means, with clean(), I cannot associate the errors raised from it with specific fields. I was wondering if models offer something similar to forms' clean_<fieldname>(). If not, where would you put this validation logic and why?
You could convert your clean method into a validator and include it when you declare the field.
Another option is to subclass the model field and override its clean method.
However there is no direct equivalent of defining clean_<field name> methods as you can do for forms. You can't even assign errors to individual fields, as you can do for forms
As stated in the comment I believe you should handle this validation at the modelform level. If you still feel like it would be better to do it closer to the model, and since they can't be changed, I would advise a change directly at the db level:
ALTER TABLE auth_user ADD UNIQUE (email)
Which is the poor way to add the unique=True constraint to the User model without monkey patching auth.
As requested, I think that a good way to go about customizing different forms should be done by inheriting from a base modelform. A good example of this is found in django-registration. The only difference is that instead of the parent form inheriting from forms.Form you would make it a modelForm:
class MyBaseModelForm(ModelForm):
class Meta:
model = MyModel
You could then inherit from it and make different forms from this base model:
class OtherFormWithCustomClean(MyBaseModelForm):
def clean_email(self):
email = self.cleaned_data['email']
if ExtendedUser.objects.filter(email=email).exists():
raise ValidationError('This email address already exists.')
return email