Last active
January 17, 2022 16:57
-
-
Save KushGoyal/5b72c5986fd6713fbf074dabca337a9d to your computer and use it in GitHub Desktop.
Nested serializer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class PurchaseOrderSerializer(NestedSerializerMixin): | |
lines = PoLineSerializer(many=True, required=False) | |
class Meta: | |
model = PurchaseOrder | |
fields = '__all__' | |
class PoLineSerializer(serializers.ModelSerializer): | |
id = serializers.IntegerField(required=False) | |
class Meta: | |
model = PoLine | |
fields = '__all__' | |
list_serializer_class = ListUpdateSerializer | |
from_parent_fields = ['client_id', 'contact_id', 'warehouse_id', 'currency'] | |
foreign_key_field_name = 'purchase_order' | |
# set by the serializer using parent instance so marked as read only | |
read_only_fields = from_parent_fields + [foreign_key_field_name,] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from rest_framework import serializers | |
from django.contrib.contenttypes.models import ContentType | |
class ListUpdateSerializer(serializers.ListSerializer): | |
def set_parent_data(self, data, parent): | |
""" | |
This method is used to set data on the child objects from the parent object | |
There are 3. fields you can set on the meta class of the child serializer class: | |
1. from_parent_fields: this field is the list of common fields between the parent | |
and child model. The values are copied from parent to child | |
2. generic_foreign_key: this field is tuple of object_id and content_type_id field | |
3. foreign_key_field_name: this field is the field name of the foriegn key of the parent model on the child model | |
parent param is the instance of the parent model | |
data is the list of dicts for the validated_data in the parent serializer | |
""" | |
meta = self.child.Meta | |
for i in data: | |
if hasattr(meta, 'from_parent_fields'): | |
for field in meta.from_parent_fields: | |
if hasattr(parent, field): | |
i[field] = getattr(parent, field, None) | |
if hasattr(meta, 'generic_foreign_key'): | |
i[meta.generic_foreign_key[0]] = parent.pk | |
i[meta.generic_foreign_key[1]] = ContentType.objects.get_for_model(parent) | |
if hasattr(meta, 'foreign_key_field_name'): | |
i[meta.foreign_key_field_name] = parent | |
def update(self, instance, validated_data): | |
# Maps for id->instance and id->data item. | |
obj_mapping = {obj.pk: obj for obj in instance} | |
existing_pks = [] | |
ret = [] | |
# find existing pks | |
for data in validated_data: | |
if data.get('id', None): | |
pk = data['id'] | |
existing_pks.append(pk) | |
# Perform deletion of missing pks | |
self.child.Meta.model.objects.filter(id__in=set(obj_mapping.keys()) - set(existing_pks)).delete() | |
for data in validated_data: | |
if data.get('id', None): | |
# perform update | |
pk = data['id'] | |
obj = obj_mapping[pk] | |
ret.append(self.child.update(obj, data)) | |
else: | |
# perform create | |
ret.append(self.child.create(data)) | |
return ret |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.db.transaction import atomic | |
from rest_framework import serializers | |
class NestedSerializerMixin(serializers.Serializer): | |
@atomic | |
def create(self, validated_data): | |
list_data = {} | |
# remove the child data from the validated_data | |
for key, val in self.get_fields().items(): | |
# find the child fields and remove the data from validated_data dict | |
if isinstance(val, serializers.ListSerializer) and not val.read_only: | |
list_data[key] = validated_data.pop(key, []) | |
instance = super().create(validated_data) | |
# for each child data initialize the child list serializer and create the child objects | |
for key, data in list_data.items(): | |
serializer = self.get_fields()[key] | |
serializer.set_parent_data(data, instance) | |
serializer.create(data) | |
return instance | |
@atomic | |
def update(self, instance, validated_data): | |
list_data = {} | |
for key, val in self.get_fields().items(): | |
if isinstance(val, serializers.ListSerializer) and not val.read_only: | |
list_data[key] = validated_data.pop(key, []) | |
instance = super().update(instance, validated_data) | |
for key, data in list_data.items(): | |
serializer = self.get_fields()[key] | |
serializer.set_parent_data(data, instance) | |
serializer.update(getattr(instance, key).all(), data) | |
return instance |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment