Как правильно обновить вложенный сериализатор "многие ко многим"?

Мне удалось воспроизвести метод create, чтобы добавить правильные вложенные сериализаторы в POST-запрос. Однако у меня все еще возникают проблемы с обновлением в PUT или PATCH. Когда я использую запрос PUT или PATCH и передаю все данные объекта или данные "брендов", он будет обновляться только в той позиции, в которой он передан. Таким образом, если у меня есть объект с 3 значениями:

"brands": [
            {
                "id": 1,
                "name": "Brand 1 Test"
            },
            {
                "id": 2,
                "name": "Brand 2 Test"
            },
            {
                "id": 3,
                "name": "Brand 3 Test"
            }
}

Если я пройду:

"brands": [
            {
                "id": 1,
                "name": "Brand 1 Test"
            },
            {
                "id": 2,
                "name": "Brand 2 Test"
            }

Это даст мне тот же список из 3 брендов. Но если я сделаю это в обратном порядке, он обновится и добавит 3-й бренд. Я не уверен, в чем причина. Вот код, который у меня есть:

Модели

class Brand(models.Model):
    name = models.CharField(max_length=500)

class Incentive(models.Model):
    name = models.CharField(max_length=500)
    brands = models.ManyToManyField(Brand, related_name='incentives_brand')
    start_dt = models.DateTimeField(auto_now_add=False, blank=True, null=True)
    end_dt = models.DateTimeField(auto_now_add=False, blank=True, null=True)

Serializers

class BrandSerializer(serializers.ModelSerializer):
    class Meta:
        model = Brand
        depth = 1
        fields = ['id', 'name']

class IncentiveSerializer(serializers.ModelSerializer):
    brands = BrandSerializer(many=True)
    
    class Meta:
        model = Incentive
        fields = ['id', 'name', 'brands', 'start_dt', 'end_dt']
    
    def create(self, validated_data):
        brands = validated_data.pop('brands', [])
        instance = Incentive.objects.create(**validated_data)
        for brand_data in brands:
            brand = Brand.objects.get(**brand_data)
            instance.brands.add(brand)
        return instance 

    def update(self, instance, validated_data):
        brands = validated_data.pop('brands', [])
        instance = super().update(instance, validated_data)
        for brand_data in brands:
            brand = Brand.objects.get(**brand_data)
            instance.brands.add(brand)
        return instance

Я думаю, что проблема кроется где-то здесь. Если потребуется дополнительный код, пожалуйста, дайте мне знать (например, представления, урлы). Я предполагаю, что в обновлении я не правильно очищаю список брендов. Я просто не могу его увидеть. Любая помощь будет оценена по достоинству.

Я думаю, что разгадка здесь в том, что вы делаете instance.brands.add, который делает именно это, добавляя. А не удаление, как вы заметили :)

У вас также есть set.

Со:

brand_objs = []
for brand_data in brands:
    brand = Brand.objects.get(**brand_data)
    brand_objs.append(brand)

instance.brands.set(brand_objs)

Но использование может быть разным, я могу представить, что вы также хотите иметь возможность просто добавить один или несколько брендов? Но для этого можно использовать разные конечные точки?

Пример конечных точек

api/incentive/1/brands # get
api/incentive/1/brands # post, set brands?
api/incentive/1/brands/add # add one or more?
api/incentive/1/brands/remove # remove specific one or more?

Добавьте instance.brands.clear() следующим образом:

Это очистит связанные бренды, чтобы вы могли обновить их заново.

def update(self, instance, validated_data):
    brands = validated_data.pop('brands', None)
    instance = super().update(instance, validated_data)
    # The condition below will update brands only if brands were 
    # specified in the request body
    if brands is not None:
        instance.brands.clear() # Clear related brands
        for brand_data in brands:
            brand = Brand.objects.get(**brand_data)
            instance.brands.add(brand)
        return instance
Вернуться на верх