Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework writable nested serializers

I'm writing a recipe organizer as a sample project for a class. I'm not very experienced with DRF other than using some very basic functionality. Here's the objective:

Create a new Recipe with associated Ingredients. Create the Ingredient objects at the same time as creating the Recipe object.

models.py:

class Ingredient(models.Model):     name = models.CharField(max_length=100)      def __str__(self):         return self.name   class Recipe(models.Model):     name = models.CharField(max_length=100)     description = models.TextField(blank=True, null=True, help_text="This is a quick description of your recipe")     directions = models.TextField(help_text="How to make the recipe")     ingredients = models.ManyToManyField(Ingredient)      def __str__(self):         return self.name 


serializers.py

class IngredientSerializer(serializers.ModelSerializer):      class Meta:         model = Ingredient   class RecipeSerializer(serializers.ModelSerializer):     ingredients = IngredientSerializer(many=True)      class Meta:         model = Recipe      def create(self, validated_data):         ingredients_data = validated_data.pop('ingredients')         recipe = Recipe.objects.create(**validated_data)         for ingredient_data in ingredients_data:             Ingredient.objects.create(**ingredient_data)         return recipe 

This successfully creates the Recipe object AND the Ingredients objects in the database, but doesn't associate the list of Ingredients with the Recipe. I assume this is because when I run ingredients_data = validated_data.pop('ingredients'), the validated_data dictionary gets its Ingredients removed, so when I create a new Recipe using validated_data, there aren't associated ingredients.

However I can't seem to figure out a way to keep ingredients associated with the recipe.

like image 867
bobbyz Avatar asked Jan 21 '15 22:01

bobbyz


People also ask

Do we need Serializers in Django REST framework?

Serializers in Django REST Framework are responsible for converting objects into data types understandable by javascript and front-end frameworks. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.

How do you pass extra context data to Serializers in Django REST framework?

In function based views we can pass extra context to serializer with "context" parameter with a dictionary. To access the extra context data inside the serializer we can simply access it with "self. context". From example, to get "exclude_email_list" we just used code 'exclude_email_list = self.

What is the difference between ModelSerializer and HyperlinkedModelSerializer?

The HyperlinkedModelSerializer class is similar to the ModelSerializer class except that it uses hyperlinks to represent relationships, rather than primary keys. By default the serializer will include a url field instead of a primary key field.


2 Answers

I figured out that ManyToMany relationships can't be established until all of the uncreated objects have been created. (See the Django Docs page on many-to-many relationships.)

Here's the working code:

serializers.py

class RecipeSerializer(serializers.ModelSerializer):     ingredients = IngredientSerializer(many=True)      class Meta:         model = Recipe      def create(self, validated_data):         ingredients_data = validated_data.pop('ingredients')         recipe = Recipe.objects.create(**validated_data)          for ingredient in ingredients_data:             ingredient, created = Ingredient.objects.get_or_create(name=ingredient['name'])             recipe.ingredients.add(ingredient)         return recipe 

UPDATE:

Per request of @StevePiercy, below is my update() code. However, I haven't looked at this in years and have no idea whatsoever if it is correct or good. I haven't been working in Python or Django for some time now, so take this with a grain of salt:

def update(self, instance, validated_data):     ingredients_data = validated_data.pop('ingredients')      instance.name = validated_data.get('name', instance.name)     instance.description = validated_data.get('description', instance.description)     instance.directions = validated_data.get('directions', instance.directions)     instance.photo = validated_data.get('photo', instance.photo)      ingredients_list = []      for ingredient in ingredients_data:         ingredient, created = Ingredient.objects.get_or_create(name=ingredient["name"])         ingredients_list.append(ingredient)      instance.ingredients = ingredients_list     instance.save()     return instance 
like image 125
bobbyz Avatar answered Sep 17 '22 08:09

bobbyz


Below is a helpful example for this question.

Change that part of code like this:

def create(self, validated_data):     ingredients_data = validated_data.pop('ingredients')     recipe = Recipe.objects.create(**validated_data)      for ingredient in ingredients_data:         ingredient, created = Ingredient.objects.get_or_create(name=ingredient['name'])         recipe.ingredients.add(ingredient)     return recipe 

And this is the method to edit, cause an error when you want to edit.

def update(self, instance, validated_data):     ingredients_data = validated_data.pop('ingredients')     instance.name = validated_data['name']     instance.description = validated_data['description']     instance.directions = validated_data['directions']      for ingredient in ingredients_data:         ingredient, created = Ingredient.objects.get_or_create(name=ingredient['name'])         recipe.ingredients.add(ingredient)     return instance 

Here is a link with an example. This code is similar to another answer, but if you want to try the code without any problems, you can use this repo. Good luck! DRF Nested serializers

like image 44
fercreek Avatar answered Sep 19 '22 08:09

fercreek