I am trying to create a reference app in DRF 3 to demonstrate a nested serializer that can create/update models. The sample code below bombs with "*create() argument after ** must be a mapping, not list*" when trying to create the nested models. It is also no clear to me how I'd handle the .update() as in some cases I just want to be establish additional relationships (Persons).
The sample models:
from django.db import models class Person(models.Model): name = models.CharField(max_length=128) class Group(models.Model): name = models.CharField(max_length=128) persons = models.ManyToManyField(Person, through='Membership') class Membership(models.Model): person = models.ForeignKey(Person) group = models.ForeignKey(Group)
And the serializers and viewsets:
from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet from app.models import Group, Person class PersonSerializer(ModelSerializer): class Meta: model = Person class GroupSerializer(ModelSerializer): persons = PersonSerializer(many=True) def create(self, validated_data): persons = validated_data.pop('persons') group = Group.objects.create(**validated_data) if persons: # Bombs without this check Person.objects.create(group=group, **persons) # Errors here return group class Meta: model = Group class PersonModelViewSet(ModelViewSet): serializer_class = PersonSerializer queryset = Person.objects.all() class GroupModelViewSet(ModelViewSet): serializer_class = GroupSerializer queryset = Group.objects.all()
I am trying to POST some JSON that inserts a Group with two (related) Persons:
{ "persons": [ { "name" : "name 1" }, { "name" : "name 2" } ], "name": "group name 1" }
To serialize a queryset or list of objects instead of a single object instance, you should pass the many=True flag when instantiating the serializer. You can then pass a queryset or list of objects to be serialized.
The HyperlinkedModelSerializer class is similar to the ModelSerializer class except that it uses hyperlinks to represent relationships, rather than primary keys. By default the serializer will include a url field instead of a primary key field.
I have no clue if there is an easier way, but the only way I managed to get this to work is to reference the 'through' model "memberships" in the Group serializer and write custom code for .create() and .update(). This seems like a lot of work to just set M2M FK's. If someone knows a better way I'd love to hear it.
class GroupMembershipSerializer(ModelSerializer): class Meta: model = Membership fields = ('person',) class GroupCreateSerializer(ModelSerializer): memberships = GroupMembershipSerializer(many=True, required=False) def create(self, validated_data): person_data = validated_data.pop('memberships') group = Group.objects.create(**validated_data) for person in person_data: d=dict(person) Membership.objects.create(group=group, person=d['person']) return group def update(self, instance, validated_data): person_data = validated_data.pop('memberships') for item in validated_data: if Group._meta.get_field(item): setattr(instance, item, validated_data[item]) Membership.objects.filter(group=instance).delete() for person in person_data: d=dict(person) Membership.objects.create(group=instance, person=d['person']) instance.save() return instance class Meta: model = Group class GroupCreateModelViewSet(ModelViewSet): serializer_class = GroupCreateSerializer queryset = Group.objects.all()
So you can create a new Group with related Person(s) using:
{ "name" : "Group 1", "memberships" : [ { "person" : 1 }, { "person" : 2 } ] }
Use PrimaryKeyRelatedField
shown here:
http://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield
class GroupSerializer(serializers.ModelSerializer): persons = serializers.PrimaryKeyRelatedField( many=True, queryset=Person.objects.all()) class Meta: model = Group fields = ('name', 'persons')
Create each person first, for example. Person with ID 1, Name = "Bob". Person with ID 2, Name = "Tim". Then post them to the REST Endpoint using their primary keys
So:
# Group create() REST endpoint data to POST {'name': 'my group', 'persons': [1, 2]}
Now the people that you had created prior, are part of that Group.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With