Django Rest Framework 将对象附加到多对多字段而不删除前一个

问题描述

我是 DRF 的新手,不知道如何在不删除一个的情况下将对象附加到 多对多 字段。

我正在使用 PATCH 来更新字段 MONITORS,但是之前的值会被实际值替换。我想附加它。

API GET 是:

{
    "id": 1,"created": "2018-05-02T23:43:07.605000Z","modified": "2021-04-03T10:25:12.280896Z","companies_house_id": "","name": "Ellison PLC","description": "","date_founded": "2018-04-28","country": 4,"creator": 7,"monitors": [
        3
    ]
}

PATCH {"monitors":[11]}之后我得到:

{
    "id": 1,"monitors": [
        11
    ]
}

我希望最终的 GET API 是 "monitors": [3,11]

models.py

class Company(TimeStampedModel):
    companies_house_id = models.CharField(max_length=8,blank=True)
    name = models.CharField(max_length=200,blank=True)
    description = models.TextField(blank=True)
    date_founded = models.DateField(null=True,blank=True)
    country = models.ForeignKey(Country,on_delete=models.PROTECT,blank=True)
    creator = models.ForeignKey(
        settings.AUTH_USER_MODEL,on_delete=models.SET_NULL,null=True,related_name='companies_created'
    )
    monitors = models.ManyToManyField(
        settings.AUTH_USER_MODEL,blank=True,related_name='companies_monitored',help_text='Users who want to be notified of updates to this company'
    )

    def __unicode__(self):
        return u'{0}'.format(self.name)

serializers.py

from rest_framework import serializers
from .models import Company
from django.contrib.auth import get_user_model

class CompanySerializer(serializers.ModelSerializer):

    class Meta:
        model = Company
        fields = '__all__'

class UserSerializer(serializers.ModelSerializer):
    companies_monitored = CompanySerializer(many=True,read_only=True)
    # companies_moniotred = serializers.PrimaryKeyRelatedField(many=True,read_only=True)

    class Meta:
        model = get_user_model()
        fields = ('id','username','companies_monitored')

views.py

class CompanyDetailsView(generics.RetrieveUpdateAPIView):
    queryset = Company.objects.all()
    serializer_class = CompanySerializer


class UserList(generics.ListCreateAPIView):
    queryset = get_user_model().objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = get_user_model().objects.all()
    serializer_class = UserSerializer


    def get_object(self,username):
        username = get_object_or_404(get_user_model(),username=username)
        return username

    def get(self,request,username):
        username = self.get_object(username)
        serializer = UserSerializer(username)
        return Response(serializer.data)

urls.py

from django.conf.urls import url
from django.urls import path

from . import views
from .views import CompanyDetailsView,UserList,UserDetail
urlpatterns = [
    path('details/<int:pk>/',CompanyDetailsView.as_view(),name='company_details'),path('users/<str:username>/',UserDetail.as_view(),name='profile_view'),]

解决方法

PATCH 方法使用不同的序列化器。

class CompanyDetailsView(generics.RetrieveUpdateAPIView):
    queryset = Company.objects.all()
    serializer_class = CompanySerializer
    
    def get_serializer_class(self):
        if self.request.method == 'PATCH':
            return CompanyPatchSerializer
        return self.serializer_class

并在序列化程序中处理您需要的内容

class CompanyPatchSerializer(serializers.ModelSerializer):
    class Meta:
        model = Company
        fields = '__all__'

    @transaction.atomic
    def update(self,instance,validated_data):
        if 'monitors' in validated_data:
            monitor_ids = validated_data.pop('monitors')
            # todo: handle get_or_create
        return super().update(instance,validated_data)

我不喜欢隐式 m2m 模型,所以我的建议是添加 through 模型。

,

我不推荐覆盖补丁方法,但下面的代码应该可以完成这项工作

def patch(self,request,*args,**kwargs):
    request.data._mutable = True
    new_monitors =  request.data.get('monitors')
    existing_monitors = Company.objects.get(kwargs.get('id')).values_list('monitors__id',flat=True)
    all_monitors = existing_monitors + new_monitors
    request.data.update({'monitors': set(all_monitors)})
    return self.partial_update(request,**kwargs)