Как добавить существующий элемент из другой модели в качестве поля M2M через запрос исправления? - PullRequest
0 голосов
/ 13 апреля 2020

Я пытаюсь добавить существующий или новый элемент из модели MenuItem в модель ItemCategory, которые связаны с помощью поля ManyToMany. Если объект MenuItem не существует в базе данных, он работает хорошо, но существующий MenuItem не может быть добавлен, он говорит, что что-то вроде menu_item уже существует.

Просмотр

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)

Сериализатор

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': 
[]}

1 Ответ

0 голосов
/ 29 апреля 2020

На самом деле проблема была в проверке элемента в ItemCategorySerializer, который не позволяет добавить существующий элемент.

Поэтому я создал отдельный сериализатор для MenuItem, избежав его проверки, и ввел новое поле под названием action в MenuItemSerializer который может использоваться для удаления / удаления указанного объекта модели c из той же конечной точки API.

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
...