Dealing with unique constraints in nested serializers.
This question comes pretty often on Stackoverflow /IRC / mailing list.
The issue is when one uses nested serializers while the nested model has a unique constraint. Here’s a Task
model that has a foreign key to a User:
Let’s build a serializer that nests a User
serializer:
Now -assuming we already have an admin user - if we create the following data:
{
"owner": {
"username": "admin"
},
"name": “demo”
}
we’re catching a unicity error:
HTTP 400 Bad Request
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"owner": {
"username": [
"A user with that username already exists."
]
}
}
Django REST framework adds a unicity constraint based on the model’s introspection. If we print the serializer, here’s what we’ll see:
TaskSerializer():
id = IntegerField(label='ID', read_only=True)
owner = UserSerializer():
username = CharField(
help_text='...', max_length=150,
validators=[
<django.contrib.auth.validators.UnicodeUsernameValidator object>, <UniqueValidator(queryset=User.objects.all())>
]
)
name = CharField(max_length=128)
As you may notice, there’s a UniqueSerializer
on the owner’s username.
Since this is a nested serializer, there’s no way Django REST framework can know whether this object will be created or taken from the database. The create/update actions apply to the TaskSerializer
, not to the UserSerializer
. Therefore the UniqueValidator
will be applied for every request since we may either create the User
or update an existing one.
If we want to regain the control over it, we need to explicitly remove that validator:
From now on, the unicity over Username
will not be checked during validation. It’ll be up to us to deal with it whenever required — ie when we’ll need to create the nested resource.