Serializing ManyToMany relationship with intermediary model in Django Rest Framework - python

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!

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.

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

Override the method to_representation of RecipeSerializer
and pass the instance of many to many filed to their serializer with many is True. or
tags = serializers.HyperlinkedRelatedField(
many=True,read_only=True,
)

Related

How to get attribute of related object set in DRF serializer?

I am trying to nest data from a single attribute of a related set in one of my DRF serializers.
So far, I am only able to do it through an intermediary serializer, which seems messy. I would like to do it directly.
As of now, this is the structure of my result:
[
{
"id": 1,
"name": "Pinot Noir",
"wine_list": [
{
"id": 1,
"wine": {
"id": 1,
"name": "Juan Gil"
}
}
]
}
]
The extra "wine_list" is redundant and surely unnecessary. I have achieved this with the following code.
These are my models. I have simplified them to include the necessary information only.
class Varietal(models.Model):
name = models.CharField(max_length=50)
class Wine(models.Model):
name = models.CharField(max_length=100)
class WineVarietal(models.Model):
wine = models.ForeignKey(Wine, on_delete=models.CASCADE)
varietal = models.ForeignKey(Varietal, on_delete=models.CASCADE)
These are the serializers I am working with now.
class VarietalDetailWineSerializer(ModelSerializer):
class Meta:
model = Wine
fields = ['id', 'name']
class VarietalDetailWineVarietalSerializer(ModelSerializer):
wine = VarietalDetailWineSerializer()
class Meta:
model = WineVarietal
fields = ['id', 'wine']
class VarietalDetailSerializer(ModelSerializer):
wine_list = VarietalDetailWineVarietalSerializer(source='winevarietal_set', many=True)
class Meta:
model = Varietal
fields = ['id', 'name', 'wine_list']
Ideally, I would get something like this:
[
{
"id": 1,
"name": "Pinot Noir",
"wine": [
{
"id": 1,
"name": "Juan Gil"
}
]
}
]
With code somewhat like this:
class VarietalDetailWineSerializer(ModelSerializer):
class Meta:
model = Wine
fields = [
'id',
'name',
]
class VarietalDetailSerializer(ModelSerializer):
wine = VarietalDetailWineSerializer(source='winevarietal_set.wine', many=True)
class Meta:
model = Varietal
fields = [
'id',
'name',
'wine',
]
But that source value is invalid.
Peace.
One possible way to achieve this is to use serializerMethodField.
class VarietalDetailSerializer(ModelSerializer):
wine = SerializerMethodField()
class Meta:
model = Varietal
fields = [
'id',
'name',
'image',
'description',
'wine',
'active'
]
def get_wine(self, varietal):
wine_varietals = varietal.winevarietal_set.all()
wine = [wine_varietals.wine for wine_varietals in wine_varietals]
return VarietalDetailWineSerializer(instance=wine, many=True).data
Our main target is to add many-to-many fields response from a custom serializer method.

Django Rest Framework- retrieving a related field on reverse foreign key efficiently

I have the following models that represent a working group of users. Each working group has a leader and members:
class WorkingGroup(models.Model):
group_name = models.CharField(max_length=255)
leader = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
class WorkingGroupMember(models.Model):
group = models.ForeignKey(WorkingGroup, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
In DRF, I want to efficiently retrieve all groups (there are several hundred) as an array of the following json objects:
{
'id': <the_group_id>
'group_name': <the_group_name>
'leader': <id_of_leader>
'members': [<id_of_member_1>, <id_of_member_2>, ...]
}
To do so, I have set up the following serializer:
class WorkingGroupSerializer(serializers.ModelSerializer):
members = serializers.SerializerMethodField()
class Meta:
model = WorkingGroup
fields = ('id', 'group_name', 'leader', 'members',)
def get_members(self, obj):
return obj.workinggroupmember_set.all().values_list('user_id', flat=True)
So that in my view, I can do something like:
groups = WorkingGroup.objects.all().prefetch_related('workinggroupmember_set')
group_serializer = WorkingGroupSerializer(groups, many=True)
This works, and gives the desired result, however I am finding it does not scale well at all, as the prefetching workinggroupmember_set does not seem to be used inside of the get_members method (Silky is showing a single query to grab all WorkingGroup objects, and then a query for each workinggroupmember_set call in the get_members method). Is there a way to set up the members field in the serializer to grab a flattened/single field version of workinggroupmember_set without using a SerializerMethodField? Or some other way of doing this that lets me properly use prefetch?
Problem here that you are doing values_list on top of all which nullifies your prefetch_related. There is currently no way to do prefetch with values_list see https://code.djangoproject.com/ticket/26565. What you can do is to transition this into python code instead of SQL
class WorkingGroupSerializer(serializers.ModelSerializer):
members = serializers.SerializerMethodField()
class Meta:
model = WorkingGroup
fields = ('id', 'group_name', 'leader', 'members',)
def get_members(self, obj):
return [wgm.user_id for wgm in obj.workinggroupmember_set.all()]
In a recent project with DRF v3.9.1 and django 2.1, I needed to recursively expose all the children of an object, by having only a direct connection to the parent, which could have had multiple children.
Before, if I was to request the "tree" of an object, I was getting:
{
"uuid": "b85385c0e0a84785b6ca87ce50132659",
"name": "a",
"parent": null
}
By applying the serialization shown below I get:
{
"uuid": "b85385c0e0a84785b6ca87ce50132659",
"name": "a",
"parent": null
"children": [
{
"uuid": "efd26a820b4e4f7c8e56c812a7791fcb",
"name": "aa",
"parent": "b85385c0e0a84785b6ca87ce50132659"
"children": [
{
"uuid": "ca2441fc7abf49b6aa1f3ebbc2dae251",
"name": "aaa",
"parent": "efd26a820b4e4f7c8e56c812a7791fcb"
"children": [],
}
],
},
{
"uuid": "40e09c85775d4f1a8578bba9c812df0e",
"name": "ab",
"parent": "b85385c0e0a84785b6ca87ce50132659"
"children": [],
}
],
}
Here is the models.py of the recursive object:
class CategoryDefinition(BaseModelClass):
name = models.CharField(max_length=100)
parent = models.ForeignKey('self', related_name='children',
on_delete=models.CASCADE,
null=True, blank=True)
To get all the reverse objects in the foreign key, apply a field to the serializer class:
class DeepCategorySerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField()
class Meta:
model = models.CategoryDefinition
fields = '__all__'
def get_children(self, obj):
return [DeepCategorySerializer().to_representation(cat) for cat in obj.children.all()]
Then apply this serializer to a DRF view function or generics class, such as:
re_path(r'categories/(?P<pk>[\w\d]{32})/',
generics.RetrieveUpdateDestroyAPIView.as_view(
queryset=models.CategoryDefinition.objects.all(),
serializer_class=serializers.DeepCategorySerializer),
name='category-update'),

Nested serializer "Through model" in Django Rest Framework

I am having difficulty serializing an intermediary "pivot" model and attach to each item in an Many-to-many relation in Django Rest Framework.
Example:
models.py:
class Member(models.Model):
name = models.CharField(max_length = 20)
groups = models.ManyToManyField('Group', through='Membership')
class Group(models.Model):
name = models.CharField(max_length = 20)
class Membership(models.Model):
member = models.ForeignKey('Member')
group = models.ForeignKey('Group')
join_date = models.DateTimeField()
serializers.py:
class MemberSerializer(ModelSerializer):
class Meta:
model = Member
class GroupSerializer(ModelSerializer):
class Meta:
model = Group
class MembershipSerializer(ModelSerializer):
class Meta:
model = Membership
I tried to follow the answers:
Include intermediary (through model) in responses in Django Rest Framework
But it's not exactly what I need
I need to generate the following output
{
"id": 1,
"name": "Paul McCartney",
"groups": [
{
"id": 3,
"name": "Beatles",
"membership": {
"id": 2,
"member_id": 1,
"group_id": 3,
"join_date": "2018-08-08T13:43:45-0300"
}
}
]
}
In this output I'm returning the related "Through Model" for each item in groups.
How can I generate serialize models in this way?
Based on the way you would like to show your output, I suggest you change your models to:
class Group(models.Model):
name = models.CharField(max_length=20)
members = models.ManyToManyField(
'Membership',
related_name='groups',
related_query_name='groups',
)
class Member(models.Model):
name = models.CharField(max_length=20)
class Membership(models.Model):
group = models.ForeignKey(
'Group',
related_name='membership',
related_query_name='memberships',
)
join_date = models.DateTimeField()
How Group model and Member model are ManytoMany, does not have a problem you let the relationship in Group model. It will be the easiest to output it in serialize. related_name and related_query_name are used to make the serialization and point the nested relation.
And finally, your serialize could be like this (I exemplified it with a create method):
class MembershipSerializer(ModelSerializer):
class Meta:
fields = ("id", "join_date",)
class GroupSerializer(ModelSerializer):
memberships = MembershipSerializer(many=True)
class Meta:
model = Group
fields = ("id", "name", "memberships",)
class MemberSerializer(ModelSerializer):
groups = GroupSerializer(many=True)
class Meta:
model = Member
fields = ("id", "name", "groups")
def create(self):
groups_data = validated_data.pop('groups')
member = Member.objects.create(**validated_data)
for group in groups_data:
memberships_data = group.pop('memberships')
Group.objects.create(member=member, **group)
for memberhip in memberships:
Membership.objects.create(group=group, **memberships)
The output will be:
{
"id": 1,
"name": "Paul McCartney",
"groups": [
{
"id": 3,
"name": "Beatles",
"memberships": [
{
"id": 2,
"join_date": "2018-08-08T13:43:45-0300"
}
]
}
]
}
In this output, I am not "nesting" the parent id but you can make it too, just declare into fields attributes.
By looking at your output it seems you want to show membership inside groups and groups inside member. I would recommend editing the serializer to something like this.
class MemberSerializer(ModelSerializer):
groups = GroupSerializer(many=True)
class Meta:
model = Member
fields = ("id","name","groups")
class GroupSerializer(ModelSerializer):
membership = MembershipSerializer()
class Meta:
model = Group
fields = ("id","name","membership")
class MembershipSerializer(ModelSerializer):
class Meta:
model = Membership
fields = "__all__"

Django Rest Framework with Self-referencing many-to-many through unable to get the through model

I am trying to get the relations and the relationship for a particular instance. I need to get both the related objects id and the relationship information.
My models are based off of relationships from http://charlesleifer.com/blog/self-referencing-many-many-through/
Django 1.8.3 and Django Rest Framework 3.3.3
models.py
class Person(models.Model):
name = models.CharField(max_length=100)
relationships = models.ManyToManyField('self', through='Relationship',
symmetrical=False,
related_name='related_to')
class RelationshipType(models.Model):
name = models.CharField(max_length=255)
class Relationship(models.Model):
from_person = models.ForeignKey(Person, related_name='from_people')
to_person = models.ForeignKey(Person, related_name='to_people')
relationship_type = models.ForeignKey(RelationshipType)
serializers.py
class RelationshipTypeSerializer(serializers.ModelSerializer):
class Meta:
model = models.RelationshipType
class RelationshipSerializer(serializers.ModelSerializer):
relationship_type = RelationshipTypeSerializer()
to_person_id = serializers.ReadOnlyField(source='to_person.id')
to_person_name = serializers.ReadOnlyField(source='to_person.name')
class Meta:
model = models.Relationship
fields = (
'id',
'relationship_type',
'to_person_id',
'to_person_name',
)
class PersonSerializer(serializers.ModelSerializer):
annotated_relationships = RelationshipSerializer(source='relationships', many=True)
class Meta:
model = models.Person
fields = (
"id",
"name",
'annotated_relationships'
)
I am currently getting something like this:
{
"id": 88,
"name": "Person 88",
"annotated_relationships": [
{
"id": 128
},
{
"id": 130
}
]
}
What I want looks like this, though the format doesn't need to be this way as long as I can get the relationship information:
{
"id": 88,
"name": "Person 88",
"annotated_relationships": [
{
"id": 128,
"relationship_type":
{
"name": "friend",
},
"to_person_id": 34,
"to_person_name": "Jeremy",
},
{
"id": 130,
"relationship_type":
{
"name": "enemy",
},
"to_person_id": 73,
"to_person_name": "Trisha",
}
]
}
You can define a method
def get_relations(self):
return models.Relationship.objects.get(from_person=self)
Then do
annotated_relationships = RelationshipSerializer(source=get_relations,many=True)

Serializing a recursive ManyToMany model in Django

I'm writing a REST API for my Django app, and am having problems on serializing a recursive many-to-many relationship. I found some help on the Internet, but it all seems to be applicable only to recursive many-to-many relationships with no through model specified.
My models are as follows:
class Place(models.Model):
name = models.CharField(max_length=60)
other_places = models.ManyToManyField('self', through='PlaceToPlace', symmetrical=False)
def __str__(self):
return self.name
class PlaceToPlace(models.Model):
travel_time = models.BigIntegerField()
origin_place = models.ForeignKey(Place, related_name="destination_places")
destination_place = models.ForeignKey(Place, related_name="origin_places")
And I tried writing this serializer:
class PlaceToPlaceSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field(source='destination_places.id')
name = serializers.Field(source='destination_places.name')
class Meta:
model = PlaceToPlace
fields = ('id', 'name', 'travel_time')
class PlaceFullSerializer(serializers.ModelSerializer):
class Meta:
model = Place
fields = ('id', 'name')
And so I have to write something to serialize the related Place instances, so I'd get something like this:
[
{
"id": 1,
"name": "Place 1",
"places":
[
{
"id": 2,
"name": "Place 2",
"travel_time": 300
}
]
},
{
"id": 2,
"name": "Place 2",
"places":
[
{
"id": 1,
"name": "Place 1",
"travel_time": 300
}
]
}
]
But I can't figure how to write the serializer, so some help would be very appreciated.

Categories