Django rest framework retrieving lists in serializers - python

Take the following example into account. I have two models, one Author and one Article. They look like so:
# author.py
from django.db import models
class Author(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
def __str__(self):
return self.first_name
# article.py
from django.db import models
from authors.models import Author
class Article(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
text = models.TextField()
def __str__(self):
return self.text
My serializers look like so:
from rest_framework import serializers
from .models import Article
from authors.serializers import AuthorSerializer
class ArticleSerializer(serializers.ModelSerializer):
author = AuthorSerializer()
class Meta:
model = Article
fields = '__all__'
from rest_framework import serializers
from .models import Author
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = '__all__'
Now, if I wish to get articles from my API, that's simple. Getting articles attaches the author and that's all well and good -- but how do I get the inverse? For instance, if I want to get authors with the latest 5 articles that they have written? So the intended output would be something like:
{
"first_name": "Sethen",
"last_name": "Maleno",
"articles": [
{
"text": "some article"
},
{
"text": "another article"
}
]
}

class ArticleWithoutAuthorSerializer(ArticleSerializer):
# Prevent author field serialization and endless nesting
author = None
class AuthorSerializer(ModelSerializer):
articles = SerializerMethodField()
def get_articles(self, author):
# Five recently posted articles
articles = author.article_set.order_by('-published')[:5]
return ArticleWithoutAuthorSerializer(articles, many=True).data
class Meta:
model = Author

Related

How to serialize Django model with 2 or more foreign keys?

I have two models in models.py:
from django.db import models
from django.contrib.auth.models import User
class Course(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
cost = models.IntegerField()
course_image = models.ImageField()
def __str__(self):
return self.name
class PurchasedCourse(models.Model):
purchased_by = models.ForeignKey(User, on_delete=models.CASCADE)
purchased_course = models.ForeignKey(Course, on_delete=models.CASCADE)
purchased_time = models.DateField(auto_now_add=True, blank=True)
def __str__(self):
return str(self.purchased_by) + ' => ' + str(self.purchased_course)
My serializers.py so far:
from rest_framework import serializers
from .models import *
class CourseSerializer(serializers.ModelSerializer):
class Meta:
model = Course
fields = '__all__'
class PurchasedCourseSerializer(serializers.ModelSerializer):
class Meta:
model = PurchasedCourse
fields = '__all__'
(it is just simple serializing)
My views.py:
from django.http import HttpResponse
from requests import Response
from .models import *
from .serializers import CourseSerializer, PurchasedCourseSerializer
from rest_framework import generics, viewsets
from rest_framework.authentication import BasicAuthentication, TokenAuthentication
from rest_framework.permissions import IsAuthenticated
class CourseViewSet(viewsets.ModelViewSet):
authentication_classes = [TokenAuthentication]
permission_classes = (IsAuthenticated,)
queryset = Course.objects.all()
serializer_class = CourseSerializer
class UserRelatedCourseViewSet(viewsets.ModelViewSet):
queryset = PurchasedCourse.objects.all()
serializer_class = PurchasedCourseSerializer
def get_queryset(self):
return PurchasedCourse.objects.filter(purchased_by__username=self.request.query_params['username'])
What I am getting so far is like { "purchased_by": 5, "purchased_course": 1 } in JSON format.
So, question is how to get all Course objects(with all fields) that depends only on logged in user using PurchasedCourseSerializer?
Something like:
[{
"id": 1;
"username": "testUsername";
course_set
[
{
"id": 2;
"name": "Computer Science";
"description": "Test description text...";
}
{
"id": 5;
"name": "History";
"description": "Test description text...";
}
]
}]
You can also use Nested Serializer. This allows you to explicitly define how the foreign key models should be serialized, and it keeps the logic in one place.
Try this:
class CourseSerializer(serializers.ModelSerializer):
class Meta:
model = Course
fields = "__all__"
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
class PurchasedCourseSerializer(serializers.ModelSerializer):
purchased_course = CourseSerializer()
purchased_by = UserSerializer()
class Meta:
model = PurchasedCourse
fields = "__all__"
Now, you can define the serialization of each foreign key model in its own serializer, and keep the logic organized and maintainable.
You can override the to_represention method in the serializer to change behavior of the field during reading, like this:
class PurchasedCourseSerializer(serializers.ModelSerializer):
username = serializers.CharField(source="purchased_by.username")
def to_representation(self, obj):
self.fields['purchased_course'] = CourseSerializer(obj.purchased_course )
return super().to_representation(obj)
class Meta:
model = PurchasedCourse
fields = '__all__'
FYI, the PurchasedCourse has a FK relation to Course, so one PurchasedCouse will have only one Course object attached to it, hence you can not show the list like representation.
But, if you are serializing from UserSerializer, then you can use the following implementation (through SerializerMethodField):
class UserSerializer(...):
courses = serializer.SerializerMethodField()
def get_courses(self, obj):
return CourseSerializer(Course.objects.filter(purchased_course__purchased_by=obj), many=True).data
You have basically two ways to have all attributes of a foreign key in your serialized JSON.
First solution
Using a to_representation function in your PurchasedCourse serializer, it would look something like this:
class PurchasedCourseSerializer(serializers.ModelSerializer):
class Meta:
model = PurchasedCourse
fields = "__all__"
def to_representation(self, instance):
rep = super().to_representation(instance)
rep["purchased_course"] = CourseSerializer(instance.purchased_course).data
rep["purchased_by"] = UserSerializer(instance.purchased_by).data
return rep
and this basically uses the serializers of your foreign keys to represent those fields with their full data or whatever fields you specify in those serializers.
Second solution
Using nested serializers is basically the same as the first solution but in a more clanky way - at least for me -
class PurchasedCourseSerializer(serializers.ModelSerializer):
purchased_course = CourseSerializer()
purchased_by = UserSerializer()
class Meta:
model = PurchasedCourse
fields = '__all__'
because the first solution gives you an easier and more clear way to manage which fields you particularly need this serializer to return.

Django REST RetrieveAPIView combining querysets

I'm trying to build a queryset which combines two query results, namely from Category and Course. Every Course has a Category foreign key. Is there a way to add the respective Courses to each Category?
Example:
{
"id": 61,
"name": "fgfdf",
"courses":
{
"id": 1,
"category": 61,
"title": "mytitle"
"active": true
},
{
...
}
}
Url
path('dict/<pk>/', DictView.as_view(), name='detail')
Models
class Category(models.Model):
name = models.CharField(max_length=255, blank=False, null=False)
class Course(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True)
title = models.CharField(max_length=255, blank=False, null=False)
active = models.BooleanField(default=True)
View
This is what I imagined but it's obviously incorrect, I've done some research but I couldn't find what I needed.
class DictView(RetrieveAPIView):
queryset = Category.objects.all()
serializer_class = CategorySerializer
def get_queryset(self):
queryset = Category.objects.all()
courses = list(Course.objects.filter(category=pk))
queryset['courses'] = courses;
return queryset
One way is defining serializers like this:
class CourseSerializer(serializers.ModelSerializer):
class Meta:
model = Course
fields = "__all__"
class CategorySerializer(serializers.ModelSerializer):
courses = CourseSerializer(source='course_set', many=True)
class Meta:
model = Category
fields = "__all__"
Then, you don't need to override get_queryset anymore.
If you wish to apply filters for courses, say you only want active courses, you can do the following:
class CategorySerializer(serializers.ModelSerializer):
courses = serializers.SerializerMethodField()
def get_courses(self, obj):
active_courses = obj.course_set.filter(active=True)
return CourseSerializer(active_courset, many=True).data
class Meta:
model = Category
fields = "__all__"

Django Rest Framework - serializers.SlugRelatedField does not return field value

I have the following Car model in my Django Rest Framework project:
from django.db import models
from django.contrib.auth.models import User
class Car(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100)
driver = models.ForeignKey(User, related_name='cars', on_delete = models.CASCADE)
def __str__(self):
return self.name
class Meta:
ordering = ['created']
As you can see, the Car model has a foreignkey relationship with the built-in User model. The field is called driver.
Now, using the serializer class I also wanted to print out the username of the driver. Therefore, I used serializers.SlugRelatedField like this:
class CarSerializer(serializers.ModelSerializer):
username = serializers.SlugRelatedField(read_only=True, slug_field='username')
class Meta:
model = Car
fields = ['id', 'name', 'username']
But in the JSON output I can not see the username value:
[
{
"id": 1,
"name": "BMW"
},
{
"id": 2,
"name": "Audi"
}
]
What is wrong here?
If you want a custom field SlugField can't be use as from the documents
A RegexField that validates the input against a URL matching pattern.
Expects fully qualified URLs of the form http:///.
Corresponds to django.db.models.fields.URLField. Uses Django's
django.core.validators.URLValidator for validation.
Signature: URLField(max_length=200, min_length=None,
allow_blank=False)
so it doesn't accept slug_field as an argument and this field only use to do regex for urls
This is how you would create a custom field to return from serializer:
class CarSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField() # custom serializer method
def get_username(self, obj):
return obj.driver.username
class Meta:
model = Car
fields = ['id', 'name', 'username']

Django - InheritanceManager is not working

I have the following simple models.py file:
from django.db import models
from model_utils.managers import InheritanceManager
class Clique(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100, blank=False)
class Post(models.Model):
created = models.DateTimeField(auto_now_add=True)
headline = models.TextField()
clique = models.ForeignKey(Clique,
on_delete=models.CASCADE,
blank=True,
null=True)
objects = InheritanceManager()
def __str__(self):
return self.headline
class VideoPost(Post):
video = models.BooleanField(default=True)
class ImagePost(Post):
image = models.BooleanField(default=True)
So, there is a Clique model which can contain multiple Post instances. The Post instances can be ImagePost or VideoPost. Therefore, ImagePost and VideoPost both inherit Post.
Now, let's say I want to retrieve the ImagePost subclass instances. So, I have the following view in my views.py file:
class PostList(generics.ListAPIView):
serializer_class = PostSerializer
def get_queryset(self):
return Post.objects.select_subclasses(ImagePost)
When I pass the endpoint posts/ in the url, then this view will be triggered and it should give me only the ImagePost instances, right ? But I also get the VideoPost instances from the database:
[
{
"clique": "http://127.0.0.1:8000/cliques/1/",
"comment_set": [],
"created": "2019-06-18T09:52:47.929623Z",
"headline": "FirstImagePost",
"id": 1,
"url": "http://127.0.0.1:8000/posts/1/"
},
{
"clique": "http://127.0.0.1:8000/cliques/1/",
"comment_set": [],
"created": "2019-06-18T09:53:20.266653Z",
"headline": "FirstVideoPost",
"id": 2,
"url": "http://127.0.0.1:8000/posts/2/"
}
]
Why is this happening ? I walked through the official doc here . Can somebody help
Just for the sake of completeness, my serializers.py file looks like the following:
from rest_framework import serializers
from posts.models import Post, VideoPost, ImagePost, Clique
class CliqueSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Clique
fields = ('id', 'name', 'post_set')
read_only_fields = ('post_set', )
class PostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Post
fields = ('url', 'id', 'created', 'headline', 'clique', 'comment_set',)
read_only_fields = ('comment_set',)
class VideoPostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = VideoPost
fields = '__all__'
class ImagePostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ImagePost
fields = '__all__'
From the documentation, it seems like select_subclasses does not filter by subclass type for you, it only converts it to the subclass if it matches what you supplied.
in your case
Post.objects.select_subclasses(ImagePost)
will convert all ImagePost to ImagePost instance, leaving the other ones as Post object, it doesn't filter it out.
from the doc here:
nearby_places = Place.objects.select_subclasses("restaurant")
# restaurants will be Restaurant instances, bars will still be Place instances
In your case you can simply do:
Post.objects.filter(imagepost__image=True).select_subclasses(ImagePost)
Though i don't think you need the select_subclasses(ImagePost) part

How to get foreignkey field name instead of id in django rest framework

Here is my models.py
from __future__ import unicode_literals
from django.db import models
class User(models.Model):
name = models.CharField(max_length=200)
company_name = models.ForeignKey('Company',on_delete=models.CASCADE,related_name='user')
def __str__(self):
return self.name
class Company(models.Model):
name = models.CharField(max_length=200)
phone_number = models.IntegerField(null=True,blank=True)
def __str__(self):
return self.name
class Catalog(models.Model):
name = models.CharField(max_length=200)
no_of_pcs = models.IntegerField(null=True,blank=True)
per_piece_price = models.DecimalField(null=True,blank=True,max_digits=10,decimal_places=2)
company_name = models.ForeignKey(Company,on_delete=models.CASCADE,related_name='catalog')
def __str__(self):
return self.name
here is my serializers.py
from rest_framework import serializers
from .models import *
from django.db.models import Sum,Count
class UserSerializer(serializers.ModelSerializer):
# name = serializers.StringRelatedField()
# company_name = serializers.CharField()
class Meta:
model = User
fields = '__all__'
here is my views.py
from __future__ import unicode_literals
from django.http import HttpResponse
from .models import *
import json
from django.http import JsonResponse, HttpResponse
from .serializers import *
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework import viewsets
class UserView(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
I am getting api like this..
Here i am getting id instead of company_name.
[
{
"id": 1,
"name": "soubhagya",
"company_name": 1
},
{
"id": 2,
"name": "nihar",
"company_name": 2
}
]
But i am expecting output--
like this:
[
{
"id": 1,
"name": "soubhagya",
"company_name": "google"
},
{
"id": 2,
"name": "nihar",
"company_name": "facebook"
}
]
I am expecting my api like this. with company_name.
And i wants to post the data from same api in rest framework
thanks,,
Since you'd defined the __str__() method in your Company model, you can use the StringRelatedField() as
class UserSerializer(serializers.ModelSerializer):
company_name = serializers.StringRelatedField()
class Meta:
model = User
fields = '__all__'
UPDATE
override the to_representation method
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
def to_representation(self, instance):
rep = super(UserSerializer, self).to_representation(instance)
rep['company_name'] = instance.company_name.name
return rep
simple solution is use source
class UserSerializer(serializers.ModelSerializer):
company_name = serializers.CharField(source='company_name.name')
Use depth.
class PhoneSerializer(serializers.ModelSerializer):
class Meta:
model = Phone
depth = 1
fields = '__all__'
class UserSerializer(serializers.ModelSerializer):
company_name = serializers.SerializerMethodField(source='get_company_name')
class Meta:
model = User
fields = '__all__'
def get_company_name(self, obj):
return obj.company_name.name
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
def to_representation(self, instance):
rep = super(UserSerializer, self).to_representation(instance)
rep['company_name'] = instance.company_name.name
return rep
Here is the documentation:
This worked for me:
company_name = serializers.SlugRelatedField(read_only=True, slug_field='name')

Categories