· 9 years ago · Jan 19, 2017, 11:42 PM
1from rest_framework import serializers
2from rest_framework.utils import model_meta
3
4
5class ManyToManySerializer(serializers.ModelSerializer):
6 """
7 Many-to-many serializer that will loop through an infinite depth
8 and create, update and delete related entities.
9
10 Usage:
11 This class is required in the parent serializer.
12
13 The pk field is required implicitly on the child serializer to make sure it exists in the
14 validated data in the create and update methods. The name of this must match that of the
15 actual field on the model.
16
17 Example depth one level:
18
19 class ChildSerializer(serializers.ModelSerializer):
20 id = serializers.ModelField(model_field=Child()._meta.get_field('id'), required=False)
21
22 class Meta:
23 model = Child
24 fields = ('id', 'name')
25
26
27 class ParentSerializer(ManyToManySerializer):
28 children = ChildSerializer(many=True)
29
30 class Meta:
31 model = Parent
32 fields = ('id', 'name', 'children')
33
34 Here the ChildSerializer can also inherit from ManyToManySerializer to include a further depth and so on.
35 """
36
37 @staticmethod
38 def _delete_obsolete_many(instance, attr, value):
39 related_manager = getattr(instance, attr)
40
41 # find the primary key for the joining table
42 primary_key = related_manager.model._meta.pk
43
44 # get a list of primary keys in validated data that we want to keep
45 pks = [item.get(primary_key.name) for item in value if item.get(primary_key.name)]
46
47 # delete any existing many-to-many that are not wanted
48 related_manager.exclude(pk__in=pks).delete()
49
50 def _update_or_create_many(self, instance, attr, value):
51 related_manager = getattr(instance, attr)
52 info = model_meta.get_field_info(related_manager.model)
53
54 # find the primary key for the joining table
55 primary_key = related_manager.model._meta.pk
56
57 # loop data and either update if we have been passed a primary key or create one if we have not
58 for item in value:
59
60 # remove many-to-many relationships from validated_data as they
61 # need to be created once the instance is saved
62 many_to_many = {}
63 for field_name, relation_info in info.relations.items():
64 if relation_info.to_many and (field_name in item):
65 many_to_many[field_name] = item.pop(field_name)
66
67 # id of the item if there is one
68 item_pk = item.pop(primary_key.name) if item.get(primary_key.name) else None
69
70 # if we have an id we need to update it otherwise create it
71 if item_pk:
72 related_object = related_manager.get(pk=item_pk)
73 for m_attr, m_value in item.items():
74 setattr(related_object, m_attr, m_value)
75 related_object.save()
76 else:
77 related_object = related_manager.create(**item)
78
79 # save or delete many-to-many relationships after the instance is created.
80 if many_to_many:
81 for field_name, value in many_to_many.items():
82 self.set_many(related_object, field_name, value)
83
84 def set_many(self, instance, attr, value):
85 self._delete_obsolete_many(instance, attr, value)
86 self._update_or_create_many(instance, attr, value)
87
88 def create(self, validated_data):
89 model_class = self.Meta.model
90 info = model_meta.get_field_info(model_class)
91
92 # remove many-to-many relationships from validated_data as they
93 # need to be created once the instance is saved
94 many_to_many = {}
95 for field_name, relation_info in info.relations.items():
96 if relation_info.to_many and (field_name in validated_data):
97 many_to_many[field_name] = validated_data.pop(field_name)
98
99 instance = model_class.objects.create(**validated_data)
100
101 # save or delete many-to-many relationships after the instance is created.
102 if many_to_many:
103 for field_name, value in many_to_many.items():
104 self.set_many(instance, field_name, value)
105
106 return instance
107
108 def update(self, instance, validated_data):
109 info = model_meta.get_field_info(instance)
110
111 for attr, value in validated_data.items():
112 if attr in info.relations and info.relations[attr].to_many:
113 self.set_many(instance, attr, value)
114 else:
115 setattr(instance, attr, value)
116
117 instance.save()
118
119 return instance