[Django]-Django Rest update many to many by id


A few things to note.

First, you don’t have an explicit through table in your example. Therefore you can skip that part.

Second, you are trying to use nested serializers which are far more complex than what you’re trying to achieve.

You can simply read/write related id by using a PrimaryKeyRelatedField:

class MasterSerializer(serializers.ModelSerializer):
    skills_ids = serializers.PrimaryKeyRelatedField(many=True, read_only=False, queryset=Skill.objects.all(), source='skills')

Which should be able to read/write:

{id: 123, first_name: "John", "skill_ids": [1, 2, 3]}

Note that the mapping from JSON’s "skill_ids" to model’s "skills" is done by using the optional argument source


I will try to bring some light in terms of design: in Django if you specify the model for a ManyToManyRelation, then the relation field on the model becomes read-only. If you need to alter the associations you do it directly on the through model, by deleting or registering new records.

This means that you may need to use a completely different serializer for the through model, or to write custom update/create methods.

There are some sets back with custom through model, are you sure you’re not good enough with the default implementation of ManyToManyFields ?




For a much simpler, one-liner solution for M2M, I sussed out a solution of the form:

serializer = ServiceSerializer(instance=inst, data={'name':'updated', 'countries': [1,3]}, partial=True)
if serializer.is_valid():

For a more complete example, I have included the following:


from django.db import models

class Country(models.Model):
    name = models.CharField(max_length=50, null=False, blank=False)

class Service(models.Model):
    name = models.CharField(max_length=20, null=True)
    countries = models.ManyToManyField('Country')


from rest_framework import serializers
from .models import *

class CountrySerializer(serializers.ModelSerializer):
    class Meta:
        model = Country
        fields = ('name',)

class ServiceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Service
        fields = ('name', 'countries',)

Make sure some dummy service and country instances are created for testing. Then you can update an instance in a function like so:

Update example

# get an object instance by key:
inst = ServiceOffering.objects.get(pk=1)

# Pass the object instance to the serializer and a dictionary
# Stating the fields and values to update. The key here is
# Passing an instance object and the 'partial' argument:
serializer = ServiceSerializer(instance=inst, data={'name':'updated', 'countries': [1,3]}, partial=True)

# validate the serializer and save
if serializer.is_valid():
    return 'Saved successfully!'
    print("serializer not valid")
    return "Save failed"

If you inspect the relevant tables, the updates are carried through including to the M2M bridging table.

To extend this example, we could create an object instance in a very similar way:

### Create a new instance example:
# get the potential drop down options:
countries = ['Germany', 'France']

# get the primary keys of the objects:
countries = list(Country.objects.filter(name__in=countries).values_list('pk', flat=True))

# put in to a dictionary and serialize:
data = {'countries': countries, 'name': 'hello-world'}
serializer = ServiceOfferingSerializer(data=data)


I have dealt with this issue for quite some time and I have found that the best way to solve the general problem of updating any many to many field is by working around it.

In my case there is a model called Listing and a user can make a Subscription(the other model) to an instance of the Listing model. The Subscription works with a Generic Foreign Key and the Listing imports the Subscriptions of the users via Many2Many.

Instead of making a PUT request to the Listing Model via API, I simply add the Subscription instance to the right model in the POST Method of the API View of Subscription. Here is my adjusted code:

    class Listing(models.Model):

      user = models.ForeignKey(settings.AUTH_USER_MODEL)
      slug = models.SlugField(unique=True, blank=True)
      timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)

      title = models.CharField(max_length=200)
      price = models.CharField(max_length=50, null=True, blank=True)
      subscriptions = models.ManyToManyField(Subscription, blank=True)

    class Subscription(models.Model):
      user = models.ForeignKey(settings.AUTH_USER_MODEL)
      content_type = models.ForeignKey(ContentType)
      object_id = models.PositiveIntegerField()
      content_object = GenericForeignKey('content_type', 'object_id')
      timestamp = models.DateTimeField(auto_now_add=True)

    class APISubscriptionCreateView(APIView): #Retrieve Detail

      def post(self, request, format=None):
        serializer = SubscriptionCreateSerializer(data=request.data)
        if serializer.is_valid():
            sub = serializer.save(user=self.request.user)
            object_id = request.data['object_id']
            lis = Listing.objects.get(pk=object_id)

            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors,    status=status.HTTP_400_BAD_REQUEST)

I hope this will help, it took me a while to figure this out

Leave a comment