This is a writable nested model serializer for Django REST Framework which allows you to create/update your models with related nested data.
The following relations are supported:
- OneToOne (direct/reverse)
- ForeignKey (direct/reverse)
- ManyToMany (direct/reverse excluding m2m relations with through model)
- GenericRelation (this is always only reverse)
- Python (3.5, 3.6, 3.7, 3.8)
- Django (2.2, 3.0, 3.1)
- djangorestframework (3.8+)
pip install drf-writable-nested
For example, for the following model structure:
from django.db import models
class Site(models.Model):
url = models.CharField(max_length=100)
class User(models.Model):
username = models.CharField(max_length=100)
class AccessKey(models.Model):
key = models.CharField(max_length=100)
class Profile(models.Model):
sites = models.ManyToManyField(Site)
user = models.OneToOneField(User, on_delete=models.CASCADE)
access_key = models.ForeignKey(AccessKey, null=True, on_delete=models.CASCADE)
class Avatar(models.Model):
image = models.CharField(max_length=100)
profile = models.ForeignKey(Profile, related_name='avatars', on_delete=models.CASCADE)
We should create the following list of serializers:
from rest_framework import serializers
from drf_writable_nested.serializers import WritableNestedModelSerializer
class AvatarSerializer(serializers.ModelSerializer):
image = serializers.CharField()
class Meta:
model = Avatar
fields = ('pk', 'image',)
class SiteSerializer(serializers.ModelSerializer):
url = serializers.CharField()
class Meta:
model = Site
fields = ('pk', 'url',)
class AccessKeySerializer(serializers.ModelSerializer):
class Meta:
model = AccessKey
fields = ('pk', 'key',)
class ProfileSerializer(WritableNestedModelSerializer):
# Direct ManyToMany relation
sites = SiteSerializer(many=True)
# Reverse FK relation
avatars = AvatarSerializer(many=True)
# Direct FK relation
access_key = AccessKeySerializer(allow_null=True)
class Meta:
model = Profile
fields = ('pk', 'sites', 'avatars', 'access_key',)
class UserSerializer(WritableNestedModelSerializer):
# Reverse OneToOne relation
profile = ProfileSerializer()
class Meta:
model = User
fields = ('pk', 'profile', 'username',)
Also, you can use NestedCreateMixin
or NestedUpdateMixin
from this package
if you want to support only create or update logic.
For example, we can pass the following data with related nested fields to our main serializer:
data = {
'username': 'test',
'profile': {
'access_key': {
'key': 'key',
},
'sites': [
{
'url': 'http://google.com',
},
{
'url': 'http://yahoo.com',
},
],
'avatars': [
{
'image': 'image-1.png',
},
{
'image': 'image-2.png',
},
],
},
}
user_serializer = UserSerializer(data=data)
user_serializer.is_valid(raise_exception=True)
user = user_serializer.save()
This serializer will automatically create all nested relations and we receive a complete instance with filled data.
user_serializer = UserSerializer(instance=user)
print(user_serializer.data)
{
'pk': 1,
'username': 'test',
'profile': {
'pk': 1,
'access_key': {
'pk': 1,
'key': 'key'
},
'sites': [
{
'pk': 1,
'url': 'http://google.com',
},
{
'pk': 2,
'url': 'http://yahoo.com',
},
],
'avatars': [
{
'pk': 1,
'image': 'image-1.png',
},
{
'pk': 2,
'image': 'image-2.png',
},
],
},
}
It is also possible to pass through values to nested serializers from the call
to the base serializer's save
method. These kwargs
must be of type dict
. E g:
# user_serializer created with 'data' as above
user = user_serializer.save(
profile={
'access_key': {'key': 'key2'},
},
)
print(user.profile.access_key.key)
'key2'
Note: The same value will be used for all nested instances like default value but with higher priority.
To run unit tests, run:
pip install tox
tox -e py37-dj22-drf38
We have a special mixin UniqueFieldsMixin
which solves this problem.
The mixin moves UniqueValidator
's from the validation stage to the save stage.
If you want more details, you can read related issues and articles: beda-software#1 http://www.django-rest-framework.org/api-guide/validators/#updating-nested-serializers
class Child(models.Model):
field = models.CharField(unique=True)
class Parent(models.Model):
child = models.ForeignKey('Child')
class ChildSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
class Meta:
model = Child
class ParentSerializer(NestedUpdateMixin, serializers.ModelSerializer):
child = ChildSerializer()
class Meta:
model = Parent
Note: UniqueFieldsMixin
must be applied only on serializer
which has unique fields.
When you are using both mixins
(UniqueFieldsMixin
and NestedCreateMixin
or NestedUpdateMixin
)
you should put UniqueFieldsMixin
ahead.
For example:
class ChildSerializer(UniqueFieldsMixin, NestedUpdateMixin,
serializers.ModelSerializer):
2014-2020, beda.software