[Django]-Update M2M relationship django rest framework (many=true)

3👍

As you can read here, nested relations currently don’t support write operations. Use HyperlinkedRelatedField instead or write a custom serializer, that implements the features you need.

3👍

If you want to update the nested relation you can do like this,

      class SchoolSerializer(serializers.HyperlinkedModelSerializer):

                students = StudentSerializer(many=True, read_only=True)
                students_ids = serializers.PrimaryKeyRelatedField(many=True,\
                read_only=False, queryset=Student.objects.all(),\ 
                source='students')

                class Meta:
                    model = School
                    fields = ('name', 'image', 'address', 'url',\
                    'students', 'students_ids')

use PrimaryKeyRelatedField this will allow you to create, update, nested relations (Many-to-Many field) by just passing a list of id’s
students will give you nested data,
students_ids can be used for write operations
DRF Browsable api

0👍

This is a little outdated, but for future people looking for a potential solution to this problem, I found it useful to patch viewset.

You cannot read post params twice, which is the only thing preventing one from passing a Primary key for the related update and performing the m2m update in post_save

I made a custom viewset based on ModelViewSet with updated create and update statements:

In your app, you can create a module called viewsets.py:

# -*- coding: utf-8 -*-

from rest_framework import mixins
from rest_framework import status
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet

class RelatedCreateModelMixin(mixins.CreateModelMixin):

    '''
    Monkey patch the UpdateModel for ModelViewSet Mixin to support data
    transferrance from pre - to - save - to - post
    '''

    def create(self, request, *args, **kwargs):
        data = request.DATA
        serializer = self.get_serializer(data=data, files=request.FILES)

        if serializer.is_valid():
            self.pre_save(serializer.object, data=data)
            self.object = serializer.save(force_insert=True)
            self.post_save(self.object, created=True, data=data)
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED,
                            headers=headers)

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

class RelatedUpdateModelMixin(mixins.UpdateModelMixin):

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        self.object = self.get_object_or_none()

        data = request.DATA

        serializer = self.get_serializer(self.object, data=data,
                                         files=request.FILES, partial=partial)

        if not serializer.is_valid():
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

        try:
            self.pre_save(serializer.object, data=data)
        except ValidationError as err:
            # full_clean on model instance may be called in pre_save,
            # so we have to handle eventual errors.
            return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)

        if self.object is None:
            self.object = serializer.save(force_insert=True)
            self.post_save(self.object, data=data, created=True)
            return Response(serializer.data, status=status.HTTP_201_CREATED)

        self.object = serializer.save(force_update=True)
        self.post_save(self.object, created=False)
        return Response(serializer.data, status=status.HTTP_200_OK)


class RelatedModelViewSet(RelatedCreateModelMixin,
                          mixins.RetrieveModelMixin,
                          RelatedUpdateModelMixin,
                          mixins.DestroyModelMixin,
                          mixins.ListModelMixin,
                          GenericViewSet):
    pass

Then, in your view, use instead:

from MYAPP import viewsets

Which allows you to do something along the lines of:

def post_save(self, obj, *args, **kwargs):
    data = kwargs.get('data')
    model_id = data.get('id')
    parent_obj = Model.objects.get(id=model_id)
    method = self.request.method
    if method == 'POST':
        parent_obj.m2m.add(obj)
    elif method == 'PUT':
        parent_obj.m2m.remove(obj)

Not the most elegant solution, but I find it preferable to writing a custom serializer

👤BTC

Leave a comment