How to perform nested serialization in Django? - django

I am new to Django.I am using Django REST Framework and I need to get json from serializers in the following format:
{
"result" : 200,
"categories" : [{
"id" : "V001",
"name": "Vehicles",
"description": "All types of motor and non-motor vehicles",
"icon": "http://images.maa.ae/static/icons/vehicles.png",
"subcategories": [{
"id" : "V00101",
"name": "Cars",
"description": "Motor Cars",
"subcategories": [{
"id" : "V0010101",
"name": "Passenger Cars",
"description": "All types of passenger cars"
}]
},
{
"id" : "V00102",
"name": "Bikes",
"description": "Bikes",
"subcategories": [{
"id" : "V0010201",
"name": "Motor Bikes",
"description": "All kinds of motor bikes"
},
{
"id" : "V0010202",
"name": "Sports Bikes",
"description": "All kinds of sports bikes"
}]
}]
}]
}
My Model class is:
class Category(models.Model):
_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=128)
description = models.CharField(max_length=150,null=True, blank=True)
icon = models.ImageField(upload_to='icons',null=True, blank=True)
parent_id = models.ForeignKey('self', null=True, blank=True)
and my serializers class is:
class CategorySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Category
fields = ('_id', 'name', 'description', 'icon')
From above implementation I can get Array of Category objects in JSON format.
But I don't know how can I include 'subcategories' in my serializer class.Please help me to get an output similar to json shown in above format.

Use Django Rest Framework.
http://www.django-rest-framework.org/
Keep your code DRY
from rest_framework import serializers
class CategoriesSerializer(serializers.ModelSerializer):
class Meta:
model = Categories
Views
from rest_framework import viewsets
from .serializers import CategoriesSerializer
class CategoryViewSet(viewsets.ModelViewSet):
queryset = Categories.objects.all()
serializer_class = CategoriesSerializer
Example
models.py:
class Sprints(models.Model):
name = models.CharField(default='', blank=True, max_length=90)
description = models.TextField(default='')
class Tasks(models.Model):
date_posted = models.DateTimeField(auto_now_add=True)
name = models.CharField(default='', blank=True, max_length=90)
end = models.DateTimeField(null=True, blank=True)
sprint = models.ForeignKey(Sprints, related_name='tasks')
class Meta:
ordering = ['-date_posted']
unique_together = ['name', 'sprint']
serializers.py:
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Tasks
fields = ('id', 'name', 'date_posted', 'end')
class SprintSerializer(serializers.ModelSerializer):
tasks = TaskSerializer(many=True, read_only=True)
class Meta:
model = Sprints
fields = ('id', 'name', 'description', 'tasks')
views.py
class SprintsViews(viewset.ModelViewSet):
queryset = Sprints.objects.all()
serializer_class = SprintSerializer
You can also add a slug field to the models to make them easier to deal with, but this is how for now :)

You should consider having a look at THIS. The usage of depth meta attribute enables you to retrieve related objects to the depth you set.
By doing so, it automatically retrieves nested data.
It is very convenient to avoid using serializers in both sides and thus having ImportError caused by cycles too.

Use related_name and on_delete on your parent_id field in models.py file
class Category(models.Model):
_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=128)
description = models.CharField(max_length=150,null=True, blank=True)
icon = models.ImageField(upload_to='icons',null=True, blank=True)
parent_id = models.ForeignKey('self', null=True, blank=True, related_name='children', on_delete=models.CASCADE)
Now add the packege pip install djangorestframework-recursive on your project
serializers.py:
##################################
from rest_framework_recursive.fields import RecursiveField
##################################
class CategorySerializer(serializers.ModelSerializer):
children = RecursiveField(many=True)
class Meta:
model = Category
fields = ('_id', 'name', 'description', 'icon', 'parent_id', 'children')
views.py (Serializers view file):
class CategoryListAPIView(generics.ListAPIView):
serializer_class = CategorySerializer
queryset = Category.objects.filter(parent_id__isnull=True)
urls.py:
############################
############################
urlpatterns = [
path('category/', views.CategoryListAPIView.as_view(), name="categoryAPI"),
######################################
]

Related

DRF how to get items grouping by categories

I understand that my question is repeated often, but I'm stuck with this.
I want to make a simple API with DRF. I have two models:
models.py
class Rubrics(models.Model):
id = models.AutoField(primary_key=True)
rubric = models.CharField(max_length=255, blank=True, null=True)
class Books(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255, blank=True, null=True)
author = models.CharField(max_length=255, blank=True, null=True)
date = models.CharField(max_length=255, blank=True, null=True)
rubrics = models.ForeignKey('Rubrics', on_delete=models.DO_NOTHING, related_name='books', blank=True, null=True)
I'd like to view serialized results like this:
[
rubric1: [
{
title: "title1",
author:"author1"
},
book_obj2,
so on
],
rubric2: [
book_obj4,
book_obj5
]
]
views.py
class BooksByRubricView(APIView):
"""List of books by rubrics"""
def get(self, request):
last_date = Books.objects.latest("date").date
books_last = Books.objects.filter(date=last_date)
serializer = RubricsSerializer(books_last, many=True)
return Response(serializer.data)
I try a lot of examples in this theme:
#sorry this garbage
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Books
#fields = ("title",)
exclude = ()
class RubricsSerializer(serializers.ModelSerializer):
rubrics = BookSerializer(read_only=True)
class Meta:
model = Rubrics
fields = ("rubrics",)
#exclude = ()
"""
class RubricSerializer(serializers.ModelSerializer):
#rubrics = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
#books = RecursiveSerializer(many=True)
#print(books)
rubrics = RubricSerializer(read_only=True)
#books = BooksListSerializer(many=True)
class Meta:
model = Books
fields = ("title", "rubrics",)
#fields = ['rubric', 'rubrics']
#fields = ['books', 'rubrics']
"""
But maybe I don't understand the principles of reverse relationships and serializing in DRF. Please, tell me WAIDW. Thanks.
UPD.
I want to operate only on specified sets of data (for example, work with the last uploaded books), not on the whole table Books. So I want to view only rubrics, that are in this set, not all rubrics from table Rubrics. Yes, books have only one rubric id in a specified field, and the rubric has a one-to-many relationship to books.
Well, that's JSON I want to see (of course, all characters appearing in this work, are fictitious. Any resemblance to actual persons, living or dead, is purely coincidental.):
{
"rubric1": [{
"author": "Marley W. Watkins",
"title": "A Step-By-Step Guide to Exploratory Factor Analysis with Stata"
}, {
"author": "Robert E. Davis",
"title": "Auditing Information and Cyber Security Governance; A Controls-Based Approach"
}, {
"author": "Gerhard X. Ritter, Gonzalo Urcid",
"title": "Introduction to Lattice Algebra: With Applications in AI, Pattern Recognition, Image Analysis, and Biomimetic Neural Networks"
}],
"rubric2": [{
"author": "Richard Cross and JT Paasch",
"title": "The Routledge Companion to Medieval Philosophy"
}, {
"author": "Nicholas Allott (editor), Terje Lohndal (editor), Georges Rey (editor)",
"title": "A Companion to Chomsky"
}, {
"author": "Olakunle George",
"title": "A Companion to African Literatures"
}, {
"author": "Tim Byrnes, Ebubechukwu O. Ilo-Okeke",
"title": "Quantum Atom Optics: Theory and Applications to Quantum Technology"
}],
"rubric3": [{
"author": "Hiroyoshi Naito",
"title": "Organic Semiconductors for Optoelectronics"
}, {
"author": "Bassem R. Mahafza, Scott C. Winton",
"title": "Handbook of Radar Signal Analysis"
}, {
"author": "Sean McManus, Mike Cook",
"title": "Raspberry Pi For Dummies, 4th Edition"
}]
}
I realize it in plain Django:
class BooksByRubricView(APIView):
"""List of books by rubrics"""
def get(self, request):
last_date = Books.objects.using('books').latest("date").date
books_last = Books.objects.using('books').filter(date=last_date)
categories = []
res_dict = {}
for item in books_last:
categories.append(item.rubrics_id)
categories = set(categories)
for item in categories:
temp_list = []
temp_qs = books_last.filter(rubrics_id=item)
for i in temp_qs:
temp_list.append({"author": i["author"], "title": i["title"]})
res_dict["rubric"+str(item)]=list(temp_list)
# res = json.dumps(res_dict)
return JsonResponse(res_dict, safe=False, json_dumps_params={'ensure_ascii': False})
can I realize it with DRF serializers or simplest way is not f*cking any brains and return JSON as above?
Update2:
Well, after some magic with serializers and generic.APIView I've got a result not exactly expected, but very closest to that. Example (of course, all characters appearing in this work, are fictitious. Any resemblance to real persons, living or dead, is purely coincidental)
views.py
class BooksByRubricView2(generics.ListAPIView):
"""Books grouped by rubrics"""
serializer_class = RubricsSerializer2
queryset = Rubrics.objects.all()
#OR without generic
class BooksByRubricView3(APIView):
def get(self, request):
r = Rubrics.objects.all()
serializer=RubricsSerializer2(r,many=True)
return Response(serializer.data)
serializers.py
class FilteredListSerializer(serializers.ListSerializer):
"""Serializer to filter Book table, look for latest date for every rubric"""
def to_representation(self, data):
latest_data = data.latest("date").date
data = data.filter(date=latest_data)
return super(FilteredListSerializer, self).to_representation(data)
class BookSerializer2(serializers.ModelSerializer):
class Meta:
model = Books
list_serializer_class = FilteredListSerializer
fields = (
"title",
"author",
"date")
class RubricsSerializer2(serializers.ModelSerializer):
books = BookSerializer2(many=True, read_only=True)
class Meta:
model = Rubrics
fields = ("rubric", "books",)
result:
[
{
"rubric": "Computers",
"books": [
{
"title": "A Step-By-Step Guide to Exploratory Factor Analysis with Stata",
"author": "Marley W. Watkins",
"date": "2021-08-08"
},
{
"title": "Auditing Information and Cyber Security Governance; A Controls-Based Approach",
"author": "Robert E. Davis",
"date": "2021-08-08"
}
]
},
{
"rubric": "Education",
"books": [
{
"title": "The Routledge Companion to Medieval Philosophy",
"author": "Richard Cross and JT Paasch",
"date": "2021-08-08"
}
]
},
so on
}
It's a dirty way because every Rubric from table Rubrics creates its own query to table Books and each Rubric has its latest date. But DRF filtering will be the next step.
There is no field rubrics in Rubrics model, available fields are:
id,rubric from model itself,
books_set which represent all books that have FK reference to that Rubrics instance. Take a look at this official docs
Book have one rubric (rubrics foreign key), and rubrics have multiple books (books_set). I would also suggest changing FK name from rubrics to rubric since there can be only one.
Try this and work from there:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Books
#fields = ("title",)
exclude = ()
class RubricsSerializer(serializers.ModelSerializer):
class Meta:
model = Rubrics
fields = ("books_set",)
#exclude = ()
First i've tried to get the result that you wanted using your view and just dit not worked! So I've created another view:
from .serializers import *
from rest_framework import generics
from .models import *
class BooksByRubricView(generics.ListAPIView):
serializer_class = RubricsSerializer
queryset = Rubric.objects.all()
# You should try!
And course I also had to create a path (Just for tests! Ignore it!):
from django.urls import path
from .views import BooksByRubricView
urlpatterns = [
path('books/', BooksByRubricView.as_view(), name='books')
]
In your models:
from django.db import models
class Rubric(models.Model):
id = models.AutoField(primary_key=True) # Do not use plural in the models name!
rubric_name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
ordering = ['id'] # Create a ordering!
def __str__(self):
return self.rubric_name
class Book(models.Model):
rubric = models.ForeignKey(Rubric, related_name='rubrics', on_delete=models.CASCADE)
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255, blank=True, null=True)
author = models.CharField(max_length=255, blank=True, null=True)
date = models.CharField(max_length=255, blank=True, null=True)
class Meta:
ordering = ['id'] # Create a ordering!
In your serializers.py:
from rest_framework import serializers
from .models import *
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = (
"title",
"author",
"date"
)
class RubricsSerializer(serializers.ModelSerializer):
rubrics = BookSerializer(many=True, read_only=True)
class Meta:
model = Rubric
fields = (
"id", # Put an Id if you want!
"rubrics",
)
Maybe the only problem was with your view! So I just did all this to get the result that you want but localy; images of the result:

How to add data from one model to other django?

I am using generics to represent views
class PersonRetrieveView(generics.ListAPIView):
queryset = Person.objects.all()
serializer_class = PersonSerializer
and
class CommentRetrieveView(generics.RetrieveAPIView):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
Person data looks like
{
"id": 2,
"user": {
"first_name": "Testuser",
"last_name": "TestUser1",
"id": 2
},
"name": "Test 2",
"city": "California",
"status": "NotActive",
"phone_number": "9876543222",
"age": 22,
"height": 180
}
and Comment
{
"id": 1,
"comment": "test",
"person": 2
}
Comment linked to Person by id. How can i add data from comment to PersonRetrieveView ?
Serializers looks like this
class PersonSerializer(serializers.ModelSerializer):
user = UserSerializer()
# comment = CommentSerializer()
class Meta:
model = Person
fields = '__all__'
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
Model looks like
class Person(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
city = models.CharField(max_length=20)
status = models.CharField(max_length=9, default='NotActive')
phone_number = models.CharField(max_length=10)
class Comment(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
comment = models.CharField(max_length=255)
You have Many-to-one relationship and you did not set related_name on Foreign key so your comments are accessible through comment_set which can be used to nest serializer
class PersonSerializer(serializers.ModelSerializer):
user = UserSerializer()
comments = CommentSerializer(source='comment_set', many=True)
class Meta:
model = Person
fields = [ 'user', 'city', 'comments', ...]
Provided that your Person model has a relation to Comment models, simply add depth = 1 in your Person serializer as follows:
class PersonSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Person
fields = '__all__'
depth = 1

Different write and read operations with DRF

I am using Django Rest Framework for a project and I am running into a problem. When the frontend creates a Team they want to reference all relationships with an ID, but when getting the Team, they want the data from the relationship. How can I achieve this?
models:
class Team(models.Model):
class Meta:
db_table = "team"
team_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
class Organization(models.Model):
class Meta:
db_table = "organization"
organization_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
class Position(models.Model):
class Meta:
db_table = "position"
position_id = models.AutoField(primary_key=True)
team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="positions")
class Player(model.Model):
class Meta:
db_table = "player"
player_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
positions = models.ManyToManyField(Position, related_name="players")
serializers:
class TeamSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ["team_id", "name", "organization", "positions"]
positions = PositionSerializer(many=True) # This is merely for output. There is no need to create a position when a team is created.
organization = OrganizationSerializer() # Since an organization already exists I'd like to just give an organization_id when creating/editing a team.
# I don't think the other serializers matter here but can add them on request.
So when doing POST or PATCH on a team, I'd like the front end to be able to pass this payload
{
"name": "My Team",
"organization": 1
}
but when doing a GET on a team, I'd like the front end to receive this response.
{
"team_id": 1,
"name": "My Team",
"organization": {
"organization_id": 1,
"name": "My Organization"
},
"positions": [{
"position_id": 1,
"players": [{
"player_id": 1,
"name": "Member 1"
}
]
}
Is there a a way to achieve this?
In such situations define two serializers, one is for read operations and one is for write operations.
class TeamWriteSerializer(serializers.ModelSerializer):
# see, here no nested relationships...
class Meta:
model = Team
fields = ["name", "organization"]
class TeamReadSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ["team_id", "name", "organization", "positions"]
positions = PositionSerializer(many=True)
organization = OrganizationSerializer()
and now, use these two serializers properly in your views. For example, I hope you are using the ModelViewSet in views,
class TeamModelViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.request.method.lower() == 'get':
return TeamReadSerializer
else:
return TeamWriteSerializer

why many to many field relationship menu are not showing the proper fields?

recently i am converting my project into DRF and the serializer.py is following:
from rest_framework import serializers
from .models import Menu,MenuCategory,MenuItem
class MenuItemSerializer(serializers.ModelSerializer):
class Meta:
model = Menu
fields = ['name', 'additional_text', 'order']
class MenuCategorySerializer(serializers.ModelSerializer):
menuitem = serializers.StringRelatedField(many=True, read_only=True)
class Meta:
model = MenuCategory
fields = ['order', 'name', 'description','menuitem']
def create(self, MenuCategory, MenuItem,validated_data):
menucategory=MenuCategory.objects.create(**validated_data)
item_data = validated_data.pop('menuitem')
for it_data in item_data:
MenuItem.objects.create(menucategory=menucategory, **item_data)
return menucategory
class MenuSerializer(serializers.ModelSerializer):
menucategory = serializers.StringRelatedField(many=True, read_only=True)
class Meta:
model = Menu
fields = ['name', 'additional_text', 'order','menucategory']
def create(self,MenuCategory, Menu, validated_data):
category_data = validated_data.pop('menucategory')
menu= Menu.objects.create(**validated_data)
for cat_data in category_data:
MenuCategory.objects.create(menu=menu, **category_data)
return menu
When i browse http://localhost:8000/api/, it shows:
[
{
"name": "Breakfast",
"additional_text": "Served between 8:00am and11:00am",
"order": 1,
"menucategory": [
"MenuCategory object (3)",
"MenuCategory object (4)"
]
},
{
"name": "Lunch",
"additional_text": "Served between 12:00pm and 3:00pm",
"order": 2,
"menucategory": [
"MenuCategory object (1)",
"MenuCategory object (5)",
"MenuCategory object (7)"
]
},
{
"name": "Dinner",
"additional_text": "Served between 6:00pm and 10:00pm",
"order": 3,
"menucategory": [
"MenuCategory object (2)",
"MenuCategory object (6)",
"MenuCategory object (8)"
]
},
{
"name": "Drinks",
"additional_text": "Happy hour 3:00pm to 6:00 pm",
"order": 4,
"menucategory": [
"MenuCategory object (9)",
"MenuCategory object (10)",
"MenuCategory object (11)"
]
}
]
but the list shouldn't be showing this menuobject kind of things. it should show the item and category name with the price and other fields. here is my views.py:
from rest_framework import generics
from .models import Menu,MenuCategory,MenuItem
from .serializers import MenuCategorySerializer,MenuSerializer,MenuItemSerializer
class ListMenuCategory(generics.ListAPIView):
queryset = MenuCategory.objects.order_by('order')
serializer_class = MenuCategorySerializer
class ListMenu(generics.ListAPIView):
queryset = Menu.objects.order_by('order')
serializer_class = MenuSerializer
class ListMenuItem(generics.ListAPIView):
queryset = MenuItem.objects.order_by('order')
serializer_class = MenuItemSerializer
and my models.py as followed:
from django.db import models
class MenuItem(models.Model):
menu_category = models.ManyToManyField('MenuCategory', related_name='menuitem',help_text='The menus that this category belongs to, i.e. \'Lunch\'.')
name = models.CharField(max_length=48, help_text='Name of the item on the menu.')
description = models.CharField(max_length=128, null=True, blank=True, help_text='The description is a simple text description of the menu item.')
order = models.IntegerField(default=0, verbose_name='order', help_text='The order is to specify the order in which items show up on the menu.')
price = models.FloatField(help_text='The price is the cost of the item.')
class Meta:
ordering = ['order', 'name']
def __unicode__(self):
return self.name
class MenuCategory(models.Model):
menu = models.ManyToManyField('Menu', related_name='menucategory',help_text='The menus that this category belongs to, i.e. \'Lunch\'.')
name = models.CharField(max_length=48, help_text='Name of the item on the menu.')
description = models.CharField(max_length=128, null=True, blank=True, help_text='The description is a simple text description of the menu item.')
order = models.IntegerField(default=0, verbose_name='order', help_text='The order is to specify the order in which items show up on the menu.')
class Meta:
ordering = ['order', 'name']
def __unicode__(self):
return self.name
class Menu(models.Model):
name = models.CharField(max_length=32, verbose_name='menu category name')
additional_text = models.CharField(max_length=128, null=True, blank=True, help_text='The additional text is any bit of related information to go along with a menu category, i.e. the \'Pasta\' category might have details that say \'All entrees come with salad and bread\'.')
order = models.IntegerField(default=0, help_text='The order is the order that this category should appear in when rendered on the templates.')
class Meta:
ordering = ['order', 'name']
def __unicode__(self):
return self.name
how should i do the query and how can i fix my DRF api list?
You should use the serializer for the related model if you wish to use the related serializer, not a serializers.StringRelatedField
class MenuSerializer(serializers.ModelSerializer):
menucategory = MenuCategorySerializer(many=True, read_only=True)

How to create records when a model has one-to-many self referential relationship using TastyPie?

I am making POST requests using TastyPie. The Task model has a one-to-many self referential relationship via the parent_task_id field.
Model:
class Task(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
parent_task_id = models.ForeignKey(
"self",
on_delete=models.CASCADE,
null=True, blank=True)
In my api.py
class TaskResource(ModelResource):
parent_task_id_id = fields.ToOneField('self', 'id', null=True, full=True)
class Meta:
queryset = Task.objects.all()
authorization = Authorization()
allowed_methods = ['post']
resource_name = "create_task"
I am unable to create a Task when I specify the parent_task_id using Postman.
{
"title": "ABCDERT",
"description": "world this week",
"due_date": "2018-11-12 1:2:1",
"parent_task_id_id": "2"
}
This is the error message I am getting when I do that:
"error_message": "An incorrect URL was provided '2' for the 'CreateTaskResource' resource.",
You should specify parent_task's uri rather than id, like
{
"title": "ABCDERT",
"description": "world this week",
"due_date": "2018-11-12 1:2:1",
"parent_task_id_id": "/create_task/2"
}
In addition, it is incorrect to define the foreignkey field in resource in this way,
class TaskResource(ModelResource):
parent_task_id_id = fields.ToOneField('self', 'id', null=True, full=True)
you can see the docs for detail.
I adjust your example,like:
model:
class Task(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
parent_task = models.ForeignKey("self", on_delete=models.CASCADE, null=True, blank=True)
api.py
class TaskResource(ModelResource):
parent_task = fields.ToOneField('self', 'parent_task', null=True,
full=True)
class Meta:
queryset = Task.objects.all()
authorization = Authorization()
allowed_methods = ['post', 'get']
filtering = {'id': ALL, 'parent_task': ALL_WITH_RELATIONS}
resource_name = "task"
Create task
POST body like:
{
"title": "task2",
"description": "world this week",
"due_date": "2018-11-12 1:2:1",
"parent_task":"/api/v1/task/1/"
}
foreignkey query
GET parameter like:
0.0.0.0:8000/api/v1/task/?parent_task__id=1