Django Rest update many to many by id

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 ?


tl;dr:

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():
    serializer.save()

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

models.py

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')

serializers.py

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():
    serializer.save()   
    return 'Saved successfully!'
else:
    print("serializer not valid")
    print(serializer.errors)
    print(serializer.data)
    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)

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