Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serializing ManyToMany relationship with intermediary model in Django Rest Framework

I'm having some trouble serializing many to many relationships with a through argument in DRF3

Very basically I have recipes and ingredients, combined through an intermediate model that specifies the amount and unit used of a particular ingredient.

These are my models:

from django.db import models
from dry_rest_permissions.generics import authenticated_users, allow_staff_or_superuser
from core.models import Tag, NutritionalValue
from usersettings.models import Profile

class IngredientTag(models.Model):
    label = models.CharField(max_length=255)

    def __str__(self):
        return self.label


class Ingredient(models.Model):
    recipe = models.ForeignKey('Recipe', on_delete=models.CASCADE)
    ingredient_tag = models.ForeignKey(IngredientTag, on_delete=models.CASCADE)
    amount = models.FloatField()
    unit = models.CharField(max_length=255)


class RecipeNutrition(models.Model):
    nutritional_value = models.ForeignKey(NutritionalValue, on_delete=models.CASCADE)
    recipe = models.ForeignKey('Recipe', on_delete=models.CASCADE)
    amount = models.FloatField()


class Recipe(models.Model):
    name = models.CharField(max_length=255)
    ingredients = models.ManyToManyField(IngredientTag, through=Ingredient)
    tags = models.ManyToManyField(Tag, blank=True)
    nutritions = models.ManyToManyField(NutritionalValue, through=RecipeNutrition)
    owner = models.ForeignKey(Profile, on_delete=models.SET_NULL, blank=True, null=True)

    def __str__(self):
        return self.name

And these are currently my serializers:

from recipes.models import Recipe, IngredientTag, Ingredient
from rest_framework import serializers

class IngredientTagSerializer(serializers.ModelSerializer):
    class Meta:
        model = IngredientTag
        fields = ('id', 'label')

class IngredientSerializer(serializers.ModelSerializer):
    class Meta:
        model = Ingredient
        fields = ('amount', 'unit')

class RecipeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Recipe
        fields = ('id', 'url', 'name', 'ingredients', 'tags', 'nutritions', 'owner')
        read_only_fields = ('owner',)
        depth = 1

I've searched SO and the web quite a bit, but I can't figure it out. It would be great if someone could point me in the right direction.

I can get the list of ingredients to be returned like so:

{
    "count": 1,
    "next": null,
    "previous": null,
    "results": [
        {
            "id": 1,
            "url": "http://localhost:8000/recipes/1/",
            "name": "Hallo recept",
            "ingredients": [
                {
                    "id": 1,
                    "label": "Koek"
                }
            ],
            "tags": [],
            "nutritions": [],
            "owner": null
        }
    ]
}

But what I want is for the amount and unit to also be returned!

like image 570
Robert Broersma Avatar asked Mar 08 '16 12:03

Robert Broersma


People also ask

What is serializing in Django?

Django's serialization framework provides a mechanism for “translating” Django models into other formats. Usually these other formats will be text-based and used for sending Django data over a wire, but it's possible for a serializer to handle any format (text-based or not).

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.

What are Serializers explain Modelserializers?

The ModelSerializer class provides a shortcut that lets you automatically create a Serializer class with fields that correspond to the Model fields. The ModelSerializer class is the same as a regular Serializer class, except that: It will automatically generate a set of fields for you, based on the model.

What is serializer and deserializer 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.


2 Answers

I got what I wanted in the following way:

from recipes.models import Recipe, IngredientTag, Ingredient
from rest_framework import serializers

class IngredientTagSerializer(serializers.ModelSerializer):
    class Meta:
        model = IngredientTag
        fields = ('id', 'label')

class IngredientSerializer(serializers.ModelSerializer):
    ingredient_tag = IngredientTagSerializer()

    class Meta:
        model = Ingredient
        fields = ('amount', 'unit', 'ingredient_tag')

class RecipeSerializer(serializers.ModelSerializer):
    ingredients = IngredientSerializer(source='ingredient_set', many=True)

    class Meta:
        model = Recipe
        fields = ('url', 'name', 'ingredients', 'tags', 'nutritions', 'owner')
        read_only_fields = ('owner',)
        depth = 1

using the ingredient_tag's ingredient_set as a source for IngredientSerializer resulted in the response I required:

{
    "count": 1,
    "next": null,
    "previous": null,
    "results": [
        {
            "url": "http://localhost:8000/recipes/1/",
            "name": "Hallo recept",
            "ingredients": [
                {
                    "amount": 200.0,
                    "unit": "g",
                    "ingredient_tag": {
                        "id": 1,
                        "label": "Koek"
                    }
                },
                {
                    "amount": 500.0,
                    "unit": "kg",
                    "ingredient_tag": {
                        "id": 3,
                        "label": "Sugar"
                    }
                }
            ],
            "tags": [],
            "nutritions": [],
            "owner": null
        }
    ]
}

I don't know if this is the best way to go about it, so I'll wait til somebody who knows their DRF leaves a comment or perhaps someone posts something better before marking as answer.

like image 162
Robert Broersma Avatar answered Sep 29 '22 03:09

Robert Broersma


While serializing the nested relations, you also have to serialize specifically those ManyToManyField.

Let me give you a small example:

class RecipeSerializer(serializers.ModelSerializer):

    ingredients = serializers.SerializerMethodField()

    def get_ingredients(self, obj):
        serializer = IngredientSerializer(obj.ingredients)
        return serializer.data    

    class Meta:
        model = Recipe
        fields = ('id', 'url', 'name', 'ingredients', 'tags', 'nutritions', 'owner')
        read_only_fields = ('owner',)
        depth = 1

Whatever your nested relation is (like ingredients, tags or nutritions), you can serialize them by creating a serializer method field. In that method, You can use your specific serializer so that it gives the json you want.

Be careful with the method name. If your ManyToManyField is "ingredients", your method name should be "ingredients" because DRF works with "get_".

For further information, check this:

Django Rest Framework - SerializerMethodField

like image 35
Çağatay Barın Avatar answered Sep 29 '22 02:09

Çağatay Barın