How can I implement a nested representation using intermediate model? - django

I am quite new to Django and Django Rest Framework.
What I like to do in my project is display intermediate model's information using ListAPIView and also include detailed information about another Model connected to the intermediate model with a Foreign Key Relationship in the form of nested representation.
I have 3 models in my project, User, Content, Bookmark.
My model goes like below.
class MyUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
username = models.CharField(max_length=50)
joined_date = models.DateTimeField(auto_now_add=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
facebook_id = models.CharField(max_length=50, blank=True)
is_facebook = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
class Content(models.Model):
seq = models.CharField(max_length=20, unique=True)
title = models.CharField(max_length=100, null=True)
start_date = models.DateField(null=True)
end_date = models.DateField(null=True)
place = models.TextField(null=True)
realm_name = models.TextField(null=True)
area = models.TextField(null=True)
price = models.TextField(null=True)
content = models.TextField(null=True)
ticket_url = models.TextField(null=True)
phone = models.TextField(null=True)
thumbnail = models.TextField(null=True)
bookmarks = models.ManyToManyField(User, through='Bookmark')
The last model, an intermediate model to connect MyUser and Content.
class Bookmark(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.ForeignKey(Content, on_delete=models.CASCADE)
created_date = models.DateTimeField(auto_now_add=True)
description = models.CharField(max_length=200, null=True)
class Meta:
unique_together = (('user', 'content'),)
ordering = ['-created_date']
def __str__(self):
return '{} bookmarked by user {}'.format(self.content, self.user)
I want to use BookmarkListAPIView to show certain user's bookmark information and some detailed information of Contents that the user added such as title, price, and start_date.
Below is my Serializer and ListAPIView.
class BookmarkListView(generics.ListAPIView):
queryset = Bookmark.objects.all()
serializer_class = BookmarkSerializer
pagination_class = DefaultResultsSetPagination
def get_queryset(self):
user = self.request.user
return user.bookmark_set.all().order_by('-created_date')
class BookmarkedContentSerializer(serializers.ModelSerializer):
class Meta:
model = Content
fields = ('title', 'start_date', 'price')
class BookmarkSerializer(serializers.ModelSerializer):
content = BookmarkedContentSerializer(many=True, read_only=True)
#bookmark = BookmarkedContentSerializer(many=True, read_only=True)
class Meta:
model = Bookmark
fields = ('content', 'user')
Currently, the API gives me results just like below.
{
"count": 6,
"next": null,
"previous": null,
"results": [
{
"content": 8,
"user": 4
},
{
"content": 6,
"user": 4
},
{
"content": 1,
"user": 4
},
{
"content": 2,
"user": 4
},
{
"content": 3,
"user": 4
},
{
"content": 10,
"user": 4
}
]
}
As mentioned above, I want to use the content ID to fetch more detailed information about each bookmark instance.
It will look like
{
"count": 6,
"next": null,
"previous": null,
"results": [
{ "content_id": 4
"title": A,
"price": 10$,
"user": 4
},
{
"content_id": 6,
"title": B,
"price": 13$,
"user": 4
},
{
"content_id": 1,
"title": C,
"price": 4$,
"user": 4
},
]
}
I tried many things written in the DRF doc but was not able to find any materials related to my situation.
If you have any idea, please help me.

I think the key you're missing is the source keyword argument for each field. Here's some docs on it for you: http://www.django-rest-framework.org/api-guide/fields/#source
And I believe this implementation should work for you or at least be very close:
class BookmarkSerializer(serializers.ModelSerializer):
content_id = serializers.IntegerField(source='content.id')
title = serializers.CharField(source='content.title')
price = serializers.CharField(source='content.price')
user = serializers.IntegerField(source='user.id')
class Meta:
model = Bookmark
fields = ('content_id', 'user', title', 'price')

Related

Serialize a Post table which has a Like table connected to it by Fk

hi,since I am just noobie in DRF I need your help in serializing the Post table.
I have a Post model like this :
class Post(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
title = models.CharField(max_length=255)
descripotion = models.TextField(blank=True)
link = models.CharField(max_length=255, blank=True)
def __str__(self):
return self.title
And then there is a like Model which is related to Post model by FK
class Like(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
post= models.ForeignKey(
'Post',
on_delete=models.CASCADE
)
def __str__(self):
return self.id
and this is my serializers.py for Post table
class PostSerializers(serializers.ModelSerializer):
user = UserSerializer( required = False )
class Meta:
model = Post
fields = [
'id', 'title', 'link','user','descripotion']
read_only_fields = ['id']
this returns the following response
{
"id": 5,
"title": "string",
"link": "string",
"user": {
"email": "admin#mail.com",
"name": "sia"
},
"descripotion": "string"
}
But I want a response to have the Like counts in it
{
"id": 5,
"title": "string",
"link": "string",
"user": {
"email": "admin#mail.com",
"name": "sia"
},
"descripotion": "string",
"like_count": 50
}
How shoud I change the serializer to achieve this goal?
You can define a new callable field with a method as serializer field:
class PostSerializers(serializers.ModelSerializer):
user = UserSerializer(required=False)
like_count = serializers.SerializerMethodField('get_likes_count')
def get_likes_count(self, obj: Post) -> int:
"""
This function is called by the SerializerMethodField.
"""
return obj.like_set.all().count()
class Meta:
model = Post
fields = ['id', 'title', 'link','user','descripotion', 'like_count']
read_only_fields = ['id']

How to POST and GET a model with Foreign Key

I am working on a NextJS + Django REST Framework project where I have three models; Document, MySource, and QuestionBlock.
Document is created along with several “question_blocks” linked to the created document. They are created together, and I have already implemented this with nested serializers.
After the Document is created, I want to be able to POST a MySource model linked to the document. Then, when I make a GET request of a document, all mysource objects should be displayed as well.
POST request: notice how I just put the document’s id that I want to link with.
{
"url": "urlasdf",
"title": "tuitle",
"publisher": "afdfas ",
"desc": "qwefqwef",
"summary": "asdfasdf",
"document": "J9DY2pE"
}
GET request: I want the document GET request to show something like below.
"id": "LwpQr6Y",
"question_blocks": [
{
"id": 16,
"document": "LwpQr6Y",
"title": "question 4",
"content": "qweqgadssdffasdf asdf"
},
]
"mysource": [
{
"id": 16,
"url": "google.com",
etc. . .
},
],
"title": "renamed title",
"template": "commonapp",
"updated": "2022-05-19T02:16:00+0000",
"created": "2022-04-21T06:59:05+0000"
The weird part is that I do not see any errors with the code below, and the functionality itself is working properly. But when I attempt to GET the document which has at least one mysource object, it takes at a couple minutes to load, which is making me think there’s something wrong with my code that is perhaps making DRF repeat itself.
models.py
class Document(models.Model):
id = HashidAutoField(primary_key=True)
title = models.CharField(max_length=100, default="Untitled")
template = models.CharField(max_length=100, default="")
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
class QuestionBlock(models.Model):
id = models.AutoField(primary_key=True)
document = models.ForeignKey(
Document,
related_name="question_blocks",
on_delete=models.CASCADE,
null=True,
blank=True,
)
title = models.CharField(max_length=500, default="")
content = models.CharField(max_length=100000, default="", blank=True)
class MySource(models.Model):
id = models.AutoField(primary_key=True)
document = models.ForeignKey(
Document,
related_name="mysource",
on_delete=models.CASCADE,
null=True,
blank=True,
)
url = models.CharField(max_length=500, default="")
title = models.CharField(max_length=500, default="", blank=True)
publisher = models.CharField(max_length=500, default="", blank=True)
desc = models.CharField(max_length=500, default="", blank=True)
summary = models.CharField(max_length=500, default="", blank=True)
serializers.py
class MySourceSerializer(serializers.ModelSerializer):
class Meta:
model = MySource
fields = ("id", "url", "title", "publisher", "desc", "summary")
def to_representation(self, instance):
self.fields["document"] = DocumentSerializer(read_only=True)
return super(MySourceSerializer, self).to_representation(instance)
class DocumentSerializer(serializers.ModelSerializer):
id = HashidSerializerCharField(source_field="documents.Document.id", read_only=True)
question_blocks = QuestionBlockSerializer(many=True)
mysource = MySourceSerializer(many=True)
class Meta:
model = Document
fields = "__all__"
def create(self, validated_data):
question_blocks = validated_data.pop("question_blocks")
document = Document.objects.create(**validated_data)
for qBlock in question_blocks:
QuestionBlock.objects.create(document=document, **qBlock)
document.save()
return document
EDIT: adding the QuestionBlockSerializer for more context
class QuestionBlockSerializer(serializers.ModelSerializer):
document = serializers.PrimaryKeyRelatedField(
pk_field=HashidSerializerCharField(source_field="documents.Document.id"),
read_only=True,
)
class Meta:
model = QuestionBlock
fields = "__all__"
optional_fields = ["content"]
I think that an appropriate way to do this maybe be this one:
###imports
from django.forms.models import model_to_dict
class DocumentListingField(serializers.RelatedField):
def to_representation(self, instance):
return model_to_dict(instance.document)
and then in MySourceSerializer remove the to_representation function and update to something like this:
class MySourceSerializer(serializers.ModelSerializer):
document = DocumentListingField(many=False, read_only=True)
class Meta:
model = MySource
fields = (
"id", "url", "title", "publisher", "desc", "summary", "document")
*Edit: I added the read_only set to True, 'caus the those models are using Hashfields thar are not easily selializable.
**edit:The reason that cause the slow response it's becaus you have a circle call of serializators so the system never know when to stop.
right here:
class MySourceSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
...
self.fields["document"] = DocumentSerializer(read_only=True)
#Mysource its calling Documentserializer
...
#And here:
class DocumentSerializer(serializers.ModelSerializer):
...
mysource = MySourceSerializer(many=True) #this one its calling the MysorceSerialize, so there are a endless loop recursion
source : Django Rest Framework-Custom relational fields

How do I merge data of two objects in Django REST Framework

So let's say I have this 2 models
Poll:
class Poll(models.Model):
title = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_available = models.BooleanField(default=True)
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
Options:
class Option(models.Model):
title = models.CharField(max_length=255)
poll = models.ForeignKey(Poll, on_delete=models.CASCADE)
def __str__(self):
return f'{self.poll.title} - {self.title}'
These two objects are connected with ForeignKey. Now let's say I have a frontend that renders both options and the question (title of the poll) but I only want make a single API call to the backend. Basically I need the API to looked like this:
{
"title": "Which is the best frontend framework?",
"options":[
{
"id": 1,
"title": "React"
},
{
"id": 2,
"title": "Vue"
},
{
"id": 3,
"title": "Angular"
}
]
}
How what method/technique should I use to merge two objects like this?
Based on DRF docs
class OptionSerializer(serializers.ModelSerializer):
class Meta:
model = Option
fields = ['id', 'title']
class PollSerializer(serializers.ModelSerializer):
options = OptionSerializer(source='option_set', many=True)
class Meta:
model = Poll
fields = ['title', 'options']

Add related ForeignKey fields with serializer in Django REST Framework

I'm using Django 2.2 and Django REST Framework
I have three models like
class Plan(models.Model):
name = models.CharField(_('Plan Name'), max_length=100)
default = models.NullBooleanField(default=None, unique=True)
created = models.DateTimeField(_('created'), db_index=True)
quotas = models.ManyToManyField('Quota', through='PlanQuota')
class Quota(models.Model):
codename = models.CharField(max_length=50, unique=True)
name = models.CharFieldmax_length=100)
unit = models.CharField(max_length=100, blank=True)
class PlanQuota(models.Model):
plan = models.ForeignKey('Plan', on_delete=models.CASCADE)
quota = models.ForeignKey('Quota', on_delete=models.CASCADE)
value = models.IntegerField(default=1, null=True, blank=True)
I have to get all quota and their value from PlanQuota in the plan serializer while getting a list of plans.
I have the following serializer
class PlanQuotaSerialier(serializers.ModelSerializer):
class Meta:
model = PlanQuota
depth = 1
fields = ['quota', 'value']
class PlanListSerializer(serializers.ModelSerializer):
plan_quota = PlanQuotaSerialier(read_only=True, many=True)
class Meta:
model = Plan
depth = 1
fields = ['name', 'default', 'created', 'plan_quota']
But there is no plan_quota in the response.
How can I add all Quota and their value for each plan in a single query (SQL JOIN)?
Edit 2:
Adding source to the serializer field worked
plan_quota = PlanQuotaSerialier(source='planquota_set', many=True)
And the result is like
"results": [
{
"name": "Test Plan 1",
"default": true,
"plan_quotas": [
{
"quota": {
"id": 1,
"order": 0,
"codename": "TEST",
"name": "Test Domain",
"unit": "count",
"description": "",
"is_boolean": false,
"url": ""
},
"value": 10
},
]
}
]
Can I club all fields from quota with value field in the plan_quotas list?
class PlanQuota(models.Model):
plan = models.ForeignKey('Plan', on_delete=models.CASCADE, related_name='plan_quotas')
quota = models.ForeignKey('Quota', on_delete=models.CASCADE)
value = models.IntegerField(default=1, null=True, blank=True)
class PlanListSerializer(serializers.ModelSerializer):
class Meta:
model = Plan
depth = 1
fields = ['name', 'default', 'created', 'plan_quotas']
This is how I got it solved.
For the first query, added source
plan_quota = PlanQuotaSerialier(source='planquota_set', many=True)
For removing quota key, added to_presentation() in the PlanQuotaSerializer
def to_representation(self, instance):
representation = super().to_representation(instance)
if 'quota' in representation:
representation['quota']['quota_id'] = representation['quota'].pop('id')
representation.update(representation.pop('quota'))
return representation

Django Rest Framework - mapping serializers fields to a database column name

I am dealing with Django Rest Framework project and the generic response for my view is not what the app client expects.
The app client expects that the filed of the related models, appears as they are in the database. Example given: model City has a foreign key to Country model, represented by a country_id column.
Is there any option to "map" the serializers default fields into a custom one? I have check the Django Rest Framework documentation but I only found "serializer_field_mapping" but I don't know if it will fit my requirements and also I don't know how to use it.
Somehow I got a close approach of it, but only in the case for fetching data --creating / updating threw some errors that I did not get how to manage. :(
Bellow I attach my models.py file, plus the actual output and the desired output. Also, if it is possible, I would like to retrieve data related with Country / Region if exists combined with the database column field_id names.
Thanks in advance,
models.py
from django.db import models
from django.contrib.postgres.fields import ArrayField
class Country(models.Model):
name = models.CharField(max_length=150, unique=True, blank=False)
class Meta:
db_table = 'countries'
class Region(models.Model):
name = models.CharField(max_length=150, unique=True, blank=False)
code = models.CharField(max_length=150, blank=True)
class Meta:
db_table = 'regions'
class City(models.Model):
country = models.ForeignKey(Country)
region = models.ForeignKey(Region, null=True, blank=True, default=None)
name = models.CharField(max_length=150, unique=True, blank=False)
postal_codes = ArrayField(models.CharField(max_length=12, blank=True), null=True, blank=True, default=None)
def __str__(self):
if not self.region:
return '%s (%s)' % (self.name, self.country.name)
return '%s, %s (%s)' % (self.name, self.region.name, self.country.name)
class Meta:
db_table = 'cities'
Actual Output:
[
{
"id": 1,
"name": "San Francisco",
"postal_codes": null,
"country": 1,
"region": 1
},
{
"id": 2,
"name": "Palo Alto",
"postal_codes": null,
"country": 1,
"region": 1
},
{
"id": 3,
"name": "New York City",
"postal_codes": null,
"country": 1,
"region": 2
},
{
"id": 4,
"name": "London",
"postal_codes": null,
"country": 2,
"region": null
}
]
Desired Output:
[
{
"id": 1,
"country_id": 1,
"region_id": 1,
"name": "San Francisco",
"postal_codes": null
},
{
"id": 2,
"country_id": 1,
"region_id": 1,
"name": "Palo Alto",
"postal_codes": null
},
{
"id": 3,
"country_id": 1,
"region_id": 2,
"name": "New York City",
"postal_codes": null
},
{
"id": 4,
"country_id": 2,
"region_id": null,
"name": "London",
"postal_codes": null
}
]
You can use the source parameter of a field on your serializer to achieve this. For example:
from rest_framework import serializers
from .models import City
class CitySerializer(serializers.ModelSerializer):
country_id = serializers.IntegerField(source='country')
region_id = serializers.IntegerField(source='region')
class Meta:
model = City
fields = ('id', 'country_id', 'region_id', 'name', 'postal_codes')
EDIT: As Yaroslav pointed out, when doing it in this way, you don't need to include the source. Take note, however, that simply including country_id or region_id in the fields list is not sufficient. You still need to specify the field on the serializer, such as country_id = serializers.IntegerField() and also include it in the fields.
Thanks to all. Finally I got it. See code bellow. I answer my question due to there is no option to add large amount of lines on a answer comment.
serializers.py
from rest_framework import serializers
from .models import Country, Region, City
class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = ('id', 'name')
class RegionSerializer(serializers.ModelSerializer):
class Meta:
model = Region
fields = ('id', 'name', 'code')
class CitySerializer(serializers.ModelSerializer):
country_id = serializers.PrimaryKeyRelatedField(
queryset=Country.objects.all(),
required=True,
source='country',
)
region_id = serializers.PrimaryKeyRelatedField(
queryset=Region.objects.all(),
allow_null=True,
required=False,
source='region',
)
country = CountrySerializer(
read_only=False,
required=False,
)
region = RegionSerializer(
required=False,
allow_null=True,
read_only=True,
)
class Meta:
model = City
fields = (
'id',
'country', 'region',
'name', 'postal_codes',
'country_id', 'region_id',
)