Nesting fields in serialization with Django REST framework - django

Given that I have a model like this:
class Case(models.Model):
opened = models.DateTimeField(auto_now_add=True)
client_first_name = models.CharField(max_length=50)
client_last_name = models.CharField(max_length=50)
I would like to group the client_* fields, so that the serialized JSON would look like this:
{
"opened": "2014-10-05T19:30:48.667Z",
"client": {
"first_name": "John",
"last_name": "Doe"
}
}
The following I tried, but doesn't work because client is not an actual field:
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Case
fields = ('client_first_name', 'client_last_name')
class CaseSerializer(serializers.ModelSerializer):
client = ClientSerializer()
class Meta:
model = Case
fields = ('opened', 'client')
What options do I have except for completely manual serialization? I prefer not to make a separate model for Client because this data really belongs in Case. Read-only is not good enough.

You can try like this:
class CaseSerializer(serializers.ModelSerializer):
client = serializers.SerializerMethodField('client')
class Meta:
model = Case
fields = ('opened', 'client')
def client(self, obj):
client_fields = {}
client_fields['first_name'] = obj.client_first_name
client_fields['last_name'] = obj.client_last_name
return client_fields

Related

django rest_framework how to display nested relationship

I'm trying to display foreign related fields like this example and it works
{
"reqid": 10,
"reqdate": "2022-12-05",
"reqdescription": "Aircon Not working",
"officerequestor": "OVCAA ",
"officeid": "PPD ",
"inspection": {
"insdate": "2022-12-06",
"diagnosis": "need to buy prism",
"inspector": "EMP-322 "
}
},
this is my serializers.py
class RequestAdditionDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = Inspection
fields = ['insdate',
'diagnosis',
'inspector'
]
class RequestorSerializer(serializers.ModelSerializer):
inspection = RequestAdditionDetailsSerializer(read_only=True)
class Meta:
model = Request
fields = ['reqid',
'reqdate',
'reqdescription',
'officerequestor',
'officeid',
'inspection'
]
My question is can I do this the other way around like this
{
"inspectid": 5,
"reqid": "10",
"insdate": "2022-12-06",
"diagnosis": "need to buy prism",
"inspector": "EMP-322",
"isinspected": {
"reqdescription": "Aircon Not working",
"reqdate": "2022-12-05",
"officerequestor": "OVCAA"
}
},
this is what I've tried, tbh I don't think this will work is there a solution for this.
if no maybe i'll add additional columns on inspection like reqdescription,reqdate etc.. just to show them
class InspectionAdditionalDetailsViewSerializer(serializers.ModelSerializer):
class Meta:
model = Request
fields = ['reqdescription',
'reqdate',
'officerequestor'
]
class InspectionSerializer(serializers.ModelSerializer):
request_details = InspectionAdditionalDetailsViewSerializer(read_only=True)
class Meta:
model = Inspection
fields = ['inspectid',
'reqid',
'insdate',
'diagnosis',
'inspector',
'isinspected',
'request_details'
]
this is my models.py
class Inspection(models.Model):
inspectid = models.AutoField(primary_key=True)
reqid = models.OneToOneField('Request', models.DO_NOTHING, db_column='reqid', blank=True, null=True)
class Meta:
managed = False
db_table = 'inspection'
class Request(models.Model):
reqid = models.AutoField(primary_key=True)
class Meta:
managed = False
db_table = 'request'
You have defined the OneToOne field name reqid therefore you should use it as serializer key.
Noted that Django will add _id to the field so it will become reqid_id in your database, it's best to name it req or request only to refer to related object.
class InspectionAdditionalDetailsViewSerializer(serializers.ModelSerializer):
class Meta:
model = Request
fields = [
'reqdescription',
'reqdate',
'officerequestor',
]
class InspectionSerializer(serializers.ModelSerializer):
reqid = InspectionAdditionalDetailsViewSerializer(read_only=True)
class Meta:
model = Inspection
fields = [
'inspectid',
'reqid',
'insdate',
'diagnosis',
'inspector',
'isinspected',
]

Django Serializer - Combine fields from two models

I have two models namely:
class B (models.Model):
id = models.BigAutoField(primary_key=True)
name = models.CharField(max_length=255)
class A (models.Model):
id = models.BigAutoField(primary_key=True)
name = models.CharField(max_length=255)
b = models.ForeignKey(B,on_delete=models.CASCADE,null=True)
I want a serializer which returns a response like this:
{
"id": 1,
"name": "test",
"b_id": 2,
"b_name": "test
}
the response format you want to get is not possible to my knowledge but you can use nestedSerializers to get both models in one response like
{
"id": 1,
"name": "test",
"b":{
"id":"test",
"name": "test"
}
}
if this is enough for you you can use the following code
serializers.py
class Bserializer(serializers.ModelSerializer):
class Meta:
model = B
fields = "__all__" #or only the fields you want
class Aserializer(serializers.ModelSerializer):
b = Bserializer()
class Meta:
model = B
fields = ["id","name", "b"]
Since b field in A model is a one to many relation so it should be a list of a items like this, also check docs
{
"b_id": 1,
"b_name": "test",
"a": [
'a_id1': 'a_name',
'a_id2': 'a_name'
]
}
If that is what you want you can do it like this:
class BSerializer(serializers.ModelSerializer):
a = serializers.StringRelatedField(many=True)
class Meta:
model = Album
fields = ['id', 'name', 'a']
This is directly from a project im working on now
class CoachLocationsSerializer(serializers.ModelSerializer):
base = LocationSerializer(read_only=True)
locations = LocationSerializer(read_only=True, many=True)
class Meta:
model = Coach
fields = ['base', 'locations']
You can achieve it without using nested serializers, like this:
class ASerializer(serializers.ModelSerializer):
b_name = serializers.CharField(source='b__name')
class Meta:
model = A
fields = ('id', 'name', 'b_id', 'b_name')
Also, note that you don't need to declare b_id in the serializer, since it's already a model field

Plain structure from OneToOne-related models (Django Rest Framework)

Models:
class Person(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveSmallIntegerField()
# More Person fields
class Student(models.Model):
person = models.OneToOneField(
Person, on_delete=models.PROTECT, primary_key=True)
year_of_study = models.PositiveSmallIntegerField()
# More Student fields
Serializers:
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'
class StudentSerializer(serializers.ModelSerializer):
person = PersonSerializer()
class Meta:
model = Student
fields = '__all__'
Views:
class StudentView(viewsets.ReadOnlyModelViewSet):
renderer_classes = [JSONRenderer]
parser_classes = [JSONParser]
queryset = Student.objects.all()
serializer_class = StudentSerializer
Requesting single Student:
{
"person": {
"id": 1,
"name": "Example Name",
"age": 20
},
"year_of_study": 3
}
But I need to work with plain structure like:
{
"id": 1,
"name": "Example Name",
"age": 20,
"year_of_study": 3
}
Where (in serializer or in view or somewhere else) and how should I do it?
I only need GET requests (using ReadOnlyModelViewSet because of it). But if would also be nice to know how to create/update/delete such structure as well.
you can create serializer like below
class StudentSerializer(serializers.ModelSerializer):
name = serilizer.CharFiled(source="person.name")
person_id = serilizer.IntegerFiled(source="person.id")
age = serilizer.IntegerFiled(source="person.age")
class Meta:
model = Student
exclude = ('person',)
You have person = PersonSerializer() which outputs the person's data. Remove this line and it'll display just person_id.
Now if you want a combination of GETs returning person data but POSTs using the person_id, you can do the following:
person = PersonSerializer(read_only=True)
person_id = serializers.PrimaryKeyRelatedField(
queryset=Person.objects.all(),
source='person',
write_only=True,
)

how to write serializer for joined model?

i have applied join on two tables with following query,
VIEWS.PY
class performance(viewsets.ModelViewSet):
queryset = Leads.objects.select_related('channelId'
).values("channelId__channelName").annotate(tcount=Count('channelId'))
serializer_class = teamwise_lead_performance_serializer
but i am unable to catch response using this serializers,
SERIALIZER.PY
class channel_serializer(serializers.ModelSerializer):
class Meta:
model = Channels
fields = ['channelName']
class performance_serializer(serializers.ModelSerializer):
tcount = serializers.IntegerField()
channel = channel_serializer(many=True, read_only=True)
class Meta:
model = Leads
fields = ['tcount', 'channel']
actual results:
[
{
"tcount": 88
},
{
"tcount": 25
},
{
"tcount": 31
},
...
]
expected results:
[
{
"channelName": "abc",
"tcount": 88
},
{
"channelName": "def",
"tcount": 25
},
{
"channelName": "ghi",
"tcount": 31
},
...
]
i have tried the following:
How to join two models in django-rest-framework
Models.py
class Channels(models.Model):
id = models.IntegerField(primary_key=True)
channelName = models.CharField(max_length=20, default=None)
class Meta:
db_table = "table1"
class Leads(models.Model):
id = models.IntegerField(primary_key=True)
channelId = models.ForeignKey(Channels, on_delete=models.CASCADE, db_column='channelId')
class Meta:
db_table = "table2"
why is it not getting the channelName in response?
what am i doing wrong here?
Thank you for your suggestions
Edit
When I try Mehren's answer, I get the following error:
KeyError when attempting to get a value for field channelName on serializer performance_serializer. The serializer field might be named incorrectly and not match any attribute or key on the dict instance.Original exception text was: 'channelId'.
I managed to get it working with the following:
class performance_serializer(serializers.ModelSerializer):
tcount = serializers.IntegerField()
channelName = serializers.CharField(source='channelId__channelName')
class Meta:
model = Leads
fields = ['tcount', 'channelName']
class performance(viewsets.ModelViewSet):
queryset = Leads.objects.select_related('channelId'
).values("channelId__channelName").annotate(tcount=Count('channelId'))
serializer_class = performance_serializer
That being said, I would strongly encourage you to follow both PEP and Django naming conventions.
Here is what your code would look like following said conventions:
class Channel(models.Model):
id = models.IntegerField(primary_key=True)
channel_name = models.CharField(max_length=20, default=None)
class Lead(models.Model):
id = models.IntegerField(primary_key=True)
channel = models.ForeignKey(Channel, on_delete=models.CASCADE)
class PerformanceSerializer(serializers.ModelSerializer):
channel_count = serializers.IntegerField()
channel_name = serializers.CharField(source='channel__channel_name')
class Meta:
model = Lead
fields = ['channel_count', 'channel_name']
class PerformanceViewSet(viewsets.ModelViewSet):
queryset = Lead.objects.select_related('channel'
).values("channel__channel_name").annotate(channel_count=Count('channel'))
serializer_class = PerformanceSerializer
The main takeaway from this is to not change the default name of your ForeignKey columns! It makes working with related models much more confusing, and is possibly the reason for your problem in the first place (although I couldn't prove it).
If you want to only get the channelName, then it's better to use
channelName = serializers.CharField(source='channelId.channelName')
Also, please fix your syntax. You are not following the pep8 standards.
EDIT
class PerformanceSerializer(serializers.ModelSerializer):
tcount = serializers.IntegerField()
channelName = serializers.CharField(source='channelId.channelName')
class Meta:
model = Leads
fields = ['tcount', 'channelName']
EDIT
queryset = Leads.objects.select_related('channelId').values("channelId__channelName").annotate(tcount=Count('channelId'))
Remove the .values("channelId__channelName") part from your view

django-polymorphic-tree serializer

I would like to serialize all the nodes in my PolymorphicMPTTModel with their corresponding fields. Following the documentation django-polymorphic and django-mptt i get this:
{
"count":1,
"next":null,
"previous":null,
"results":[
{
"title":"Submenu",
"subcategories":[
{
"title":"Plato1",
"subcategories":[
]
},enter code here
{
"title":"Plato2",
"subcategories":[
]
}
]
}
]
}
The structure is fine, but the fields of the children are missing.
Models:
class Menu(PolymorphicMPTTModel):
parent = PolymorphicTreeForeignKey('self', blank=True, null=True, related_name='children', verbose_name='parent')
title = models.CharField("Title", max_length=200)
class SubMenu(Menu):
titulo = models.CharField("Titulo", max_length=200,default="not defined")
class Plato(Menu):
titulo = models.CharField("Titulo",max_length=200,default="not defined")
descripcion = models.TextField()
ingredientes = JSONField()
precio = models.PositiveSmallIntegerField(default=0)
# Extra settings:
can_have_children = False
Serializers:
class PlatoSerializer(serializers.ModelSerializer):
class Meta:
model = Plato
fields = ('titulo', 'descripcion', 'ingredientes', 'precio')
class SubMenuSerializer(serializers.ModelSerializer):
class Meta:
model = SubMenu
fields = ('titulo',)
class MenuItemModuleSerializer(serializers.ModelSerializer):
subcategories = serializers.ListSerializer(source="children",child=RecursiveField())
class Meta:
model = Menu
fields = ('title','subcategories')
View:
class MenuView(viewsets.ModelViewSet):
queryset = Menu.objects.all()
queryset = queryset.toplevel()
serializer_class = MenuItemModuleSerializer
I know I'm kinda late to the party but I had the same issue and finally found a solution that is working for me.
As django-rest-polymorphic states, you need a mapping between models and serializers:
class ProjectPolymorphicSerializer(PolymorphicSerializer):
model_serializer_mapping = {
Menu: MenuItemModuleSerializer,
SubMenu: SubMenuSerializer,
Plato: PlatoSerializer
}
Your RecursiveField() creates a serializer instance from its parent class. That makes all your child objects using the MenuItemModuleSerializer and thus missing child fields.
Every child needs to get mapped to its serializer using ProjectPolymorphicSerializer
RecursiveField(to='ProjectPolymorphicSerializer')
Change your MenuItemModuleSerializer to this:
class MenuItemModuleSerializer(serializers.ModelSerializer):
subcategories = serializers.ListSerializer(source="children", child=RecursiveField(to='ProjectPolymorphicSerializer'))
class Meta:
model = Menu
fields = ('title','subcategories')