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:
- I have an endpoint that creates a user. As such, there is a
password
field and aconfirm_password
field. If the two fields do not match, the user cannot be created. Likewise, if the requestedusername
already exists, the user cannot be created. - The user POSTs improper values for each of the fields mentioned above
- An implementation of
validate
has been made in the serializer (see below), catching the non-matchingpassword
andconfirm_password
fields
Implementation 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.
I don't believe the above solutions work any more. In my case, my model has fields 'first_name' and 'last_name', but the API will only receive 'name'.
Setting 'extra_kwargs' and 'validators' in the Meta class seems to have no effect, first_name and last_name are allways deemed required, and validators are always called. I can't overload the first_name/last_name character fields with
as the names make sense. After many hours of frustration, I found the only way I could override the validators with a ModelSerializer instance was to override the class initializer as follows (forgive the incorrect indentation):
Now the doc says that allowing blank and null with character fields is a no no, but this is a serializer, not a model, and as the API gets called by all kinds of cowboys, I need to cover my bases.
Since most likely your
username
field hasunique=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 doingrepr(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
andfield.run_validators
)serializer.validate_[field]
is called for each fieldserializer.run_validation
followed byserializer.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.You will need to re-implement the
unique
check in your own validation though, along with any additional validators that have been automatically generated.