Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DRF 3 - Creating Many-to-Many update/create serializer with though table

Tags:

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" } 
like image 923
LiteWait Avatar asked Feb 24 '15 20:02

LiteWait


People also ask

How do I pass Queryset to serializer?

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.

What is the difference between ModelSerializer and HyperlinkedModelSerializer?

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.


2 Answers

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 }      ]  } 
like image 146
LiteWait Avatar answered Oct 18 '22 08:10

LiteWait


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.

like image 26
Aaron Lelevier Avatar answered Oct 18 '22 10:10

Aaron Lelevier