如何通过补丁请求将另一个模型的现有项目添加为 M2M 字段?

How to add existing item from another model as a M2M field through patch request?

我正在尝试将 MenuItem 模型中的现有项目或新项目添加到使用 ManyToMany 字段链接的 ItemCategory 模型。如果 MenuItem 对象在数据库中不存在,它工作正常,但无法添加现有的 MenuItem,它说 menu_item 已经存在。

View

class ItemCategoryView(generics.RetrieveUpdateDestroyAPIView):
    """Update, Edit or delete ItemCategory objects"""
    serializer_class = ItemCategorySerializer
    queryset = ItemCategory.objects.all()

    lookup_field = 'id'

    def get(self, request, id=None):
        return self.retrieve(request)

    def put(self, request, id=None):
        return self.update(request, id)

    def delete(self, request, id):
        return self.destroy(request, id)

Serializer

class ItemCategorySerializer(serializers.ModelSerializer):
    """Serializer for ItemCategory objects"""
    item = MenuItemSerializer(many=True, required=False)

    class Meta:
        model = ItemCategory
        fields = ('id', 'name', 'item')
        read_only_fields = ('id', )

    # Creating with nested serializer
    def create(self, validated_data):
        menu_item = validated_data.pop('item', None)
        category = ItemCategory.objects.create(**validated_data)
        # if list of menu item info is passed
        if menu_item:
            items = []
            for item in menu_item:
                itm = MenuItem.objects.create(**item)
                items.append(itm)

            category.item.add(*items)
        return category

    def update(self, instance, validated_data):
        """Works perfectly in case if item doesn't exists"""
        new_items = validated_data.pop('item')
        items_to_be_added_to_category = []

        for new_item in new_items:
            try:
                item_obj = MenuItem.objects.get(**new_item)
            except MenuItem.DoesNotExist:
                item_obj = MenuItem.objects.create(**new_item)
            items_to_be_added_to_category.append(item_obj)

        instance.item.add(*items_to_be_added_to_category)
        instance.save()

        return instance

Failed TestCase

    def test_add_existing_item_to_category(self):
        """Test adding existing item object to category"""
        category_obj = sample_category("Mutton Special")
        item_obj, item_obj_info = sample_item(
                                              item_name="Mutton Sekuwa",
                                              payload_only=False
                                             )
        payload = {
            "name": category_obj.name,
            "item": [item_obj_info]
        }
        res = self.client.patch(
            reverse(UPDATE_ITEM_CATEGORY_URL, kwargs={'id': category_obj.id}),
            payload,
            format="json"
        )

        category_obj = ItemCategory.objects.get(id=category_obj.id)
        category_item = category_obj.item.all()
        serializer = ItemCategorySerializer(category_obj)


        self.assertEqual(res.data, serializer.data)
        self.assertIn(item_obj, category_item)
        self.assertEqual(res.status_code, status.HTTP_200_OK)

错误:

======================================================================
FAIL: test_add_existing_item_to_category (Menu.tests.test_category_api.ItemCategoryAPITest)
Test adding existing item object to category
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/rms/Menu/tests/test_category_api.py", line 117, in test_add_existing_item_to_category
    self.assertEqual(res.data, serializer.data)
AssertionError: {'item': [{'item_name': [ErrorDetail(string=[62 chars])]}]} != {'id': 2, 'name': 'Mutton Special', 'item': 
[]}

实际上问题在于 ItemCategorySerializer 中的项目验证不允许添加现有项目。

所以我通过 escaping 它的验证为 MenuItem 创建了一个单独的序列化程序,并在 MenuItemSerializer 上引入了名为 action 的新字段,它可用于 delete/remove 来自相同 [=] 的特定模型对象16=]端点。

class NestedMenuItemSerializer(serializers.ModelSerializer):
    """Serializer for updating item objects in ItemCategory"""
    ACTION_CHOICE = (
        ('remove', 'remove'),
        ('delete', 'delete')
    )

    action = serializers.ChoiceField(
        choices=ACTION_CHOICE,
        required=False
    )

    class Meta:
        model = MenuItem
        fields = (
            'id',
            'item_name',
            'price',
            'description',
            'item_type',
            'image',
            'action'
        )
        extra_kwargs = {
            'item_name': {'validators': []}, # escaping validation
            'action': {'validators': []}
        }
        read_only_fields = ('id', 'url')

class UpdateItemCategorySerializer(serializers.ModelSerializer):
    """Serializer for Creating ItemCategory objects"""
    item = NestedMenuItemSerializer(many=True, read_only=False)

    class Meta:
        model = ItemCategory
        fields = ('id', 'name', 'item')
        read_only_fields = ('id', )

    def update(self, instance, validated_data):
        instance.name = validated_data.get('name', instance.name)
        items_info = validated_data.pop('item')

        for item_info in items_info:
            action = item_info.pop('action', None)
            if action:
                item_obj = get_object_or_404(MenuItem, **item_info)
                if action is "remove":
                    instance.item.remove(item_obj)
                elif action is "delete":
                    item_obj.delete()
            else:
                item_name = item_info.pop('item_name', None)
                item_obj, _ = MenuItem.objects.get_or_create(
                    item_name=item_name,
                    defaults=item_info
                )
                instance.item.add(item_obj)
        instance.save()
        return instance