Package: drf-writable-nested
DRF Writable NestedThis 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)RequirementsPython (3.5, 3.6, 3.7, 3.8, 3.9, 3.10)Django (2.2, 3.0, 3.1, 3.2, 4.0)djangorestframework (3.8+)Installationpip install drf-writable-nestedUsageFor example, for the following model structure:fromdjango.dbimportmodelsclassSite(models.Model):url=models.CharField(max_length=100)classUser(models.Model):username=models.CharField(max_length=100)classAccessKey(models.Model):key=models.CharField(max_length=100)classProfile(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)classAvatar(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:fromrest_frameworkimportserializersfromdrf_writable_nested.serializersimportWritableNestedModelSerializerclassAvatarSerializer(serializers.ModelSerializer):image=serializers.CharField()classMeta:model=Avatarfields=('pk','image',)classSiteSerializer(serializers.ModelSerializer):url=serializers.CharField()classMeta:model=Sitefields=('pk','url',)classAccessKeySerializer(serializers.ModelSerializer):classMeta:model=AccessKeyfields=('pk','key',)classProfileSerializer(WritableNestedModelSerializer):# Direct ManyToMany relationsites=SiteSerializer(many=True)# Reverse FK relationavatars=AvatarSerializer(many=True)# Direct FK relationaccess_key=AccessKeySerializer(allow_null=True)classMeta:model=Profilefields=('pk','sites','avatars','access_key',)classUserSerializer(WritableNestedModelSerializer):# Reverse OneToOne relationprofile=ProfileSerializer()classMeta:model=Userfields=('pk','profile','username',)Also, you can useNestedCreateMixinorNestedUpdateMixinfrom 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'ssavemethod. Thesekwargsmust be of typedict. E g:# user_serializer created with 'data' as aboveuser=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.TestingTo run unit tests, run:# Setup the virtual environmentpython3-mvenvenvnamesourceenvname/bin/activate

pipinstalldjango
pipinstalldjango-rest-framework
pipinstall-rrequirements.txt# Run testspy.testKnown problems with solutionsValidation problem for nested serializers with unique fields on updateWe have a special mixinUniqueFieldsMixinwhich solves this problem.
The mixin movesUniqueValidator's from the validation stage to the save stage.If you want more details, you can read related issues and articles:https://github.com/beda-software/drf-writable-nested/issues/1http://www.django-rest-framework.org/api-guide/validators/#updating-nested-serializersExample of usage:classChild(models.Model):field=models.CharField(unique=True)classParent(models.Model):child=models.ForeignKey('Child')classChildSerializer(UniqueFieldsMixin,serializers.ModelSerializer):classMeta:model=ChildclassParentSerializer(NestedUpdateMixin,serializers.ModelSerializer):child=ChildSerializer()classMeta:model=ParentNote:UniqueFieldsMixinmust be applied only on serializer
which has unique fields.Mixin orderingWhen you are using both mixins
(UniqueFieldsMixinandNestedCreateMixinorNestedUpdateMixin)
you should putUniqueFieldsMixinahead.For example:classChildSerializer(UniqueFieldsMixin,NestedUpdateMixin,serializers.ModelSerializer):Update problem for nested fields with form-data inPATCHandPUTmethodsThere is a special problem while we try to update any model object with nested fields
within it viaPUTorPATCHusing form-data we can not update it. And it complains
about fields not provided. So far, we came to know that this is also a problem in DRF.
But we can follow a tricky way to solve it at least for now.
See the below solution about the problemIf you want more details, you can read related issues and articles:https://github.com/beda-software/drf-writable-nested/issues/106https://github.com/encode/django-rest-framework/issues/7262#issuecomment-737364846Example:# ModelsclassVoucher(models.Model):voucher_number=models.CharField(verbose_name="voucher number",max_length=10,default='')image=models.ImageField(upload_to="vouchers/images/",null=True,blank=True)classVoucherRow(models.Model):voucher=models.ForeignKey(to='voucher.Voucher',on_delete=models.PROTECT,verbose_name='voucher',related_name='voucherrows',null=True)account=models.CharField(verbose_name="fortnox account number",max_length=255)debit=models.DecimalField(verbose_name="amount",decimal_places=2,default=0.00,max_digits=12)credit=models.DecimalField(verbose_name="amount",decimal_places=2,default=0.00,max_digits=12)description=models.CharField(verbose_name="description",max_length=100,null=True,blank=True)# Serializers for these modelsclassVoucherRowSerializer(WritableNestedModelSerializer):classMeta:model=VoucherRowfields=('id','account','debit','credit','description',)classVoucherSerializer(serializers.ModelSerializer):voucherrows=VoucherRowSerializer(many=True,required=False,read_only=True)classMeta:model=Voucherfields=('id','participants','voucher_number','voucherrows','image')Now if you want to updateVoucherwithVoucherRowand voucher image then you need to do it
using form-data viaPUTorPATCHrequest where yourvoucherrowsfields are nested field.
With the current implementation of thedrf-writable-nesteddoesn't update it. Because it does
not support something like-voucherrows[1].account=1120
voucherrows[1].debit=1000.00
voucherrows[1].credit=0.00
voucherrows[1].description='Debited from Bank Account' 
voucherrows[2].account=1130
voucherrows[2].debit=0.00
voucherrows[2].credit=1000.00
voucherrows[2].description='Credited to Cash Account'This is not supported at least for now. So, we can achieve the result in a different way.
Instead of sending the array fields separately in this way we can convert the whole fields
along with values in ajsonstring like below and set it as value to the fieldvoucherrows."[{\"account\": 1120, \"debit\": 1000.00, \"credit\": 0.00, \"description\": \"Debited from Bank Account\"}, {\"account\": 1130, \"debit\": 0.00, \"credit\": 1000.00, \"description\": \"Credited to Cash Account\"}]"Now it'll be actually sent as a single field value to the application for the fieldvoucherrows.
From yourviewsyou need to parse it like below before sending it to the serializer-classVoucherViewSet(viewsets.ModelViewSet):serializer_class=VoucherSerializerqueryset=serializer_class.Meta.model.objects.all().order_by('-created_at')defupdate(self,request,*args,**kwargs):request.data.update({'voucherrows':json.loads(request.data.pop('voucherrows',None))})returnsuper().update(request,*args,**kwargs)Now, you'll get thevoucherrowsfield with data in the right format in your serializers.
Similar approach will be also applicable for generic views for django rest frameworkAuthors2014-2022, beda.software