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

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.

Related

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__"

Get nested serialized data as one

Actual Return
{
"store_product_mapping":{
"master_product":{
"name": "abc",
"description": "xyz"
}
}
"selling_price": 445
}
Serializers looks like:
class GrandParentSerializer(serializers.ModelSerializer):
class Meta:
model = Z
fields = ('name', 'description')
class ParnetSerializer(serializers.ModelSerializer):
master_product = GrandParentSerializer(many=False, read_only=False)
class Meta:
model = Y
fields = ('master_product',)
class ChildSerializer(serializers.ModelSerializer):
store_product_mapping = ParnetSerializer(many=False, read_only=False)
class Meta:
model = X
fields = ('store_product_mapping', 'selling_price')
Am using nested rest_framework.serializer.ModelSerializer to serialize the data from multiple levels but in response i need it to be like. Do I need to make a new dictionary and in a loop update the dictionary? Or is there a shortcut to do same?
{
"name": xyz
"description": abcd
"price": 10
}
You can use source argument like this:
class ChildSerializer(serializers.ModelSerializer):
name = serializers.CharField(source='store_product_mapping.master_product.name')
description = serializers.CharField(source='store_product_mapping.master_product.description')
class Meta:
model = X
fields = ('name', 'description', 'selling_price')

Can't get Django Rest Framework GET vs POST structure to differ

I have a model that looks like this:
class Pet(models.Model):
name = models.CharField(max_length=30)
description = models.CharField(max_length=200)
primary_contact = models.ForeignKey(Person)
My Serializer looks like this:
class PetSerializer(serializers.ModelSerializer):
class Meta:
model = Pet
fields = ('id', 'name', 'description', 'primary_contact')
My Viewset:
class PetViewSet(viewsets.ModelViewSet):
queryset = Pet.objects.all()
serializer_class = PetSerializer
My Problem:
If I do a "GET" at my endpoint, my result looks like this:
[
{
"id": 1,
"name": "Ripley",
"description": "Black / Tan Yorkie",
"primary_contact": 1
}
]
The primary_contact only brings back the ID of the Person object. This is exactly how I want the POSTing structure to look like. When I POST, I only want to supply the ID of the Person object. However, when I GET, I want the content to look like this:
[
{
"id": 1,
"name": "Ripley",
"description": "Black / Tan Yorkie",
"primary_contact": {
"id": 1,
"first_name": "MyFistName",
"last_name": "MyLastName",
"phone": "312-xxx-xxxx",
"email": "aarsan#abc123.com"
}
}
]
I can get the above structure by setting depth=2 in my serializer but then if I try to POST, it tries to create the primary_contact, which I not want to do since it already exists.
The workaround I've been using is creating a different endpoint for POST and GET when I have a foreign key, which I hope isn't the only way to do this.
You want a nested serializer. In this case you have to define a serializer for primary contact and link in with primary contact field of PetSerializer.
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = ('id', 'field2', 'field3', '...')
class PetSerializer(serializers.ModelSerializer):
primary_contact = ContactSerializer()
class Meta:
model = Pet
fields = ('id', 'name', 'description', 'primary_contact')
You can have several serializers in the same ViewSet, to achieve this you need to overwrite get_serializer_classmethod.
Take a look at this: http://www.django-rest-framework.org/api-guide/generic-views/#get_serializer_classself

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!
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,
)

Django Rest Framework nested model as list instead of dict

Is there a way to have a nested model as a List instead of Dict?
I'm trying to implement it with ListField but having a hard time.
Following a sample to better explain what I'm trying to do.
Sample Models:
class Album(models.Model):
name = models.CharField(max_length=250)
class Track(models.Model):
title = models.CharField(max_length=250)
number = models.IntegerField()
album = models.ForeignKey(Album, related_name="tracks")
Sample Serializer:
Class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ['number', 'title']
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True)
model = Album
fields = ['name', 'tracks']
Wrong output resulted from the code above:
{
"name": "ALBUM NAME",
"tracks": [
{
"number": 1,
"title": "TRACK TITLE"
},
{
"number": 2,
"title": "OTHER TRACK TITLE"
}
]
}
Desired output:
{
"name": "ALBUM NAME",
"tracks": [
[1, "TRACK TITLE"],
[2, "OTHER TRACK TITLE"]
]
}
Solution: an album serializer with one field that is the result of a method and can be anything (SerializerMethodField)
serializers.py
class AlbumSerializer(serializers.ModelSerializer):
track_list = serializers.SerializerMethodField()
class Meta:
model = Album
fields = ['name', 'track_list']
class get_track_list(self, obj):
output = []
for i in Track.objects.filter(album = obj.id):
output.append([i.number, i.title])
return output
This returns me an JSON with attributes "name" and "track_list", just the way I needed:
{
"name": "ALBUM NAME",
"track_list": [
[1, "TRACK TITLE"],
[2, "OTHER TRACK TITLE"]
]
}

Categories