Serializing a recursive ManyToMany model in Django - python

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.

Related

Can you optimize this code? (Django, python)

I'm adding 'added' field to check which categories User's Post(Outfit) is added to. It sounds horrible, so let's dive in to the code.
I want to optimize get_categories(self, obj) function.
class CategorySerializer(serializers.ModelSerializer):
added = serializers.BooleanField()
class Meta:
model = Category
fields = (
'id',
'name',
'added'
)
class OutfitDetailSerializer(serializers.ModelSerializer):
def get_categories(self, obj):
user = self.context['request'].user
categories = Category.objects.filter(owner=user)
added = categories.extra(select={'added': '1'}).filter(outfits__pk=obj.pk)
added = list(added.values('added', 'name', 'id'))
added_f = categories.extra(select={'added': '0'}).exclude(outfits__pk=obj.pk)
added_f = list(added_f.values('added', 'name', 'id'))
categories = added + added_f
return CategorySerializer(categories, many=True).data
The output is below!
"categories": [{
"id": 1,
"name": "Gym",
"added": true
}, {
"id": 2,
"name": "School",
"added": false
}, {
"id": 3,
"name": "hollymo",
"added": true
}, {
"id": 4,
"name": "Normal",
"added": false
}, {
"id": 6,
"name": "New Category",
"added": false
}
]
Here is models.py
class Outfit(models.Model):
...
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True)
content = models.CharField(max_length=30)
...
class Category(models.Model):
name = models.CharField(max_length=20)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True)
outfits = models.ManyToManyField(Outfit, related_name="categories", blank=True)
main_img = models.ImageField(
upload_to=upload_location_category,
null=True,
blank=True)
...
here the repo for test
If i get you right, you can get necessary data with django raw sql:
q = """\
SELECT yourappname_category.id,
yourappname_category.name,
COUNT(outfit_id) > 0 as added
FROM yourappname_category
LEFT JOIN yourappname_category_outfits
ON yourappname_category.id = yourappname_category_outfits.category_id
AND yourappname_category_outfits.outfit_id=%s
WHERE yourappname_category.owner_id=%s
GROUP BY yourappname_category.id, yourappname_category.name"""
categories = Category.objects.raw(q, [obj.id, user.id])
results = [{'id': c.id, 'name': c.name, 'added': c.added} for c in categories]
If I understand your use case correctly you just want "to check which categories User's Post(Outfit) is added to". For that you would only need to return the ones with added = true right? and then you could leave the added key out.
as in:
"categories": [{
"id": 1,
"name": "Gym"
}, {
"id": 3,
"name": "hollymo"
}
]
If so, you could just use:
import Category from category.models
class CategoriesSerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'name')
class OutfitDetailSerializer(serializers.ModelSerializer):
categories = CategoriesSerializer(many=True)
If instead your use case is to show a list of all categories and then do something with just the ones that the current outfit is added to, I'd suggest doing 2 API calls instead of your current logic; One with the answer I supplied above and one to get all categories. Then do that 'added' logic in your front-end as its presentation layer logic imo.
I'd certainly try to avoid doing raw SQL queries in Django, it cuts the purpose of migrations and is rarely necessary.

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 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"]
]
}

creating new object instead of assigning existing object POST in DRF

Recently we have been moving from traditional view to API Views so started implementing DRF.
so, my main problem is im having a model with a foreign key to another model
say
class Task(model.Models):
created_by=models.ForeignKey(User, null=true, blank=true)
assignees = models.ManyToManyField(User, related_name="assignees", blank=True)
class UserSerializer(serializer.ModelSerializer):
class Meta:
model = User
class TaskSerializer(serializer.ModelSerializer):
created_by = UserSerializer()
assignees = UserSerializer(many=True, required=False)
class Meta:
model=Task
now when i create a task with User(pk=10) already existing user.
A new user is created instead of assigning the existing user.
here is my input:
{
"created_by": {
"id": 10,
"email": "xyz#gmail.com",
"name": "abc def"
},
"assignees": []
}
here is the output:
{
"id":23,
"created_by": {
"id": 11,
"email": "xyz#gmail.com",
"name": "abc def"
},
"assignees": [
]
}
Got some help from Linovia: with this :- https://gist.github.com/xordoquy/78a2d0e2ec85e2d7aadf
but the test case fails.
could any one suggest me im in need of it.
Probably using PrimaryKeyRelatedField may do the work.
class TaskSerializer(serializer.ModelSerializer):
created_by = serializeres..PrimaryKeyRelatedField()
assignees = UserSerializer(many=True, required=False)
class Meta:
model=Task
And updating your json to:
{
"created_by": 10,
"assignees": []
}

Categories