Situation
While working with validation in the Django REST Framework's ModelSerializer
, I have noticed that the Meta.model
fields are always validated, even when it does not necessarily make sense to do so. Take the following example for a User
model's serialization:
password
field and a confirm_password
field. If the two fields do not match, the user cannot be created. Likewise, if the requested username
already exists, the user cannot be created.validate
has been made in the serializer (see below), catching the non-matching password
and confirm_password
fieldsImplementation of validate
:
def validate(self, data): if data['password'] != data.pop('confirm_password'): raise serializers.ValidationError("Passwords do not match") return data
Problem
Even when the ValidationError
is raised by validate
, the ModelSerializer
still queries the database to check to see if the username
is already in use. This is evident in the error-list that gets returned from the endpoint; both the model and non-field errors are present.
Consequently, I would like to know how to prevent model validation until after non-field validation has finished, saving me a call to my database.
Attempt at solution
I have been trying to go through the DRF's source to figure out where this is happening, but I have been unsuccessful in locating what I need to override in order to get this to work.
DRF enforces data validation in the deserialization process, which is why you need to call is_valid() before accessing the validated data. If the data is invalid, errors are then appended to the serializer's error property and a ValidationError is thrown. There are two types of custom data validators: Custom field.
We can validate the serializer by calling the method " is_valid() ". It will return the boolean(True/False) value. If the serializer is not valid then we can get errors by using the attribute "errors".
The serializers in REST framework work very similarly to Django's Form and ModelForm classes. The two major serializers that are most popularly used are ModelSerializer and HyperLinkedModelSerialzer.
The HyperlinkedModelSerializer class is similar to the ModelSerializer class except that it uses hyperlinks to represent relationships, rather than primary keys. By default the serializer will include a url field instead of a primary key field.
Since most likely your username
field has unique=True
set, Django REST Framework automatically adds a validator that checks to make sure the new username is unique. You can actually confirm this by doing repr(serializer())
, which will show you all of the automatically generated fields, which includes the validators.
Validation is run in a specific, undocumented order
serializer.to_internal_value
and field.run_validators
)serializer.validate_[field]
is called for each fieldserializer.run_validation
followed by serializer.run_validators
)serializer.validate
is calledSo the problem that you are seeing is that the field-level validation is called before your serializer-level validation. While I wouldn't recommend it, you can remove the field-level validator by setting extra_kwargs
in your serilalizer's meta.
class Meta: extra_kwargs = { "username": { "validators": [], }, }
You will need to re-implement the unique
check in your own validation though, along with any additional validators that have been automatically generated.
I was also trying to understand how the control flows during serializer validation and after carefully going through the source code of djangorestframework-3.10.3 I came up with below request flow diagram. I have described the flow and what happens in the flow to the best of my understanding without going into too much detail as it can be looked up from source.
Ignore the incomplete method signatures. Only focusing on what methods are called on what classes.
Assuming you have an overridden is_valid
method on your serializer class (MySerializer(serializers.Serializer)
) when you call my_serializer.is_valid()
the following takes place.
MySerializer.is_valid()
is executed.BaseSerializer
) is_valid
method (like: super(MySerializer, self).is_valid(raise_exception)
in your MySerializer.is_valid()
method, that will be called.MySerializer
is extending serializers.Serializer
, the run_validation()
method from serializer.Serializers
is called. This is validating only the data dict the first. So we haven't yet started field level validations.validate_empty_values
from fields.Field
gets called. This again happens on the entire data
and not a single field.Serializer.to_internal_method
is called.field.run_validation()
method. If the field has overridden the Field.run_validation()
method then that will be called first. In case of a CharField
it is overridden and calls the run_validation
method of Field
base class. Step 6-2 in the figure.Field.validate_empty_values()
to_internal_value
of the type of field is called next.Field.run_validators()
method. I presume this is where the additional validators that we add on the field by specifying the validators = []
field option get executed one by oneSerializer.to_internal_value()
method. Now remember that we are doing the above for each field within that for loop. Now the custom field validators you wrote in your serializer (methods like validate_field_name
) are run. If an exception occurred in any of the previous steps, your custom validators wont run.read_only_defaults()
validate()
method on your object is run here.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With