user8795870
user8795870

Reputation: 63

How to update the serializer field for m2m relationship

I am trying to update submitted data by implementing the update() method on the a SerializerClass. However i do not know how to get access to the current ingredient of the current instance and also i do not know how to iterate through the validated data such that i can assign the key and values correctly. any advice would be appreciated!

My Serializer

class RecipeSerializer(ModelSerializer):
    ingredients = IngredientSerializer(many=True)
    owner = ReadOnlyField(source='owner.username')

    class Meta:
        fields = ['owner', 'name', 'description', 'image', 'ingredients']
        model = Recipe

    def create(self, validated_data):
        ingredient_datas = validated_data.pop('ingredients')
        recipe = Recipe.objects.create(**validated_data)
        for ingredient_data in ingredient_datas:
            recipe.ingredients.create(**ingredient_data)
        return recipe

    def update(self, instance, validated_data):
        instance.name = validated_data.get('name', instance.name)
        instance.description = validated_data.get('description', instance.description)
        instance.image = validated_data.get('image', instance.image)

        for ingredient in validated_data.get('ingredients'):
            for key, value in ingredient.items():
                print(key, value)
                instance.ingredient.name = value
                instance.ingredient.amount = value
        instance.save()
        return instance

My Models

class Recipe(models.Model):
    owner = models.ForeignKey('auth.User', related_name='recipes', on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    description = models.TextField(max_length=200, blank=True)
    image = models.CharField(max_length=100, blank=True)
    ingredients = models.ManyToManyField(Ingredient, related_name='recipes')

class Ingredient(models.Model):
    name = models.CharField(max_length=50)
    amount = models.IntegerField(default=1)

Some window shell ouputs

print(validated_data)

{'name': 'bedddef', 'description': 'gdddoot', 'image': 'httpdddlo', 'ingredients': [OrderedDict([('name', 'tomatoes'), ('amount', 2)]), OrderedDict([('name', 'bread'), ('amount', 3)])]}

for key, value in ingredient.items():
print(key, value)

name tomatoes
amount 2
name bread
amount 3

Recently (today) I recreated the entire api again because i lost it. I tried using the same code bear suggested for the update() method using Ingredient.objects.get_or_create() but i got an error saying.

MultipleObjectsReturned at /api/recipes/3/ get() returned more than one Ingredient -- it returned 2!

Because of the error i substituted get_or_create() for just create() would that be a bad solution?

   def update(self, instance, validated_data):
        instance.name = validated_data.get('name', instance.name)
        instance.descriptions = validated_data.get('descriptions', instance.descriptions)
        instance.image = validated_data.get('image', instance.image)
        ingredients = []
        for ingredient in validated_data.get('ingredients'):
            obj = Ingredient.objects.create(**ingredient)
            ingredients.append(obj)
        print(ingredients)
        instance.ingredients.set(ingredients)
        instance.save()
        return instance

Upvotes: 0

Views: 92

Answers (1)

user8060120
user8060120

Reputation:

you can use set method of the m2m RelatedManager

def update(self, instance, validated_data):
    instance.name = validated_data.get('name', instance.name)
    instance.description = validated_data.get('description', instance.description)
    instance.image = validated_data.get('image', instance.image)
    ingredients = []
    for ingredient in validated_data.get('ingredients'):
        obj, _ = Ingredient.objects.get_or_create(**ingredient)
        ingredients.append(obj)
    instance.ingredients.set(ingredients)
    #                 ^^^^^^
    instance.save()
    return instance

details: the get_or_create return tuple of the model instance and bool value is created new instance, but we don't need the bool, so to do the code more readable just assign the value of the variable _

obj, _ = Ingredient.objects.get_or_create(**ingredient)

Upvotes: 1

Related Questions