How to include a parent object when serializing a Django Model? - django

I have two simple models with a foreign key relation like this:
class Foo(models.Model):
code = models.CharField(max_length=255)
class Bar(models.Model):
foo = models.ForeignKey(Foo)
description = models.CharField(max_length=255)
The current rest_framework-based serializers look like this:
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = models.Foo
fields = ('id', 'code')
class BarSerializer(serializers.ModelSerializer):
class Meta:
model = models.Bar
fields = ('id', 'foo', 'description')
So a GET request for a Bar will return something like this:
{
"id": 1,
"foo": 2,
"description": "[…]"
}
How do I change BarSerializer to instead return the full Foo object, like this:
{
"id": 1,
"foo": {
"id": 2,
"code": "[…]"
},
"description": "[…]"
}
?
Keep in mind I still need to be able to create a Bar by providing only a description and Foo ID. I've tried various things including specifying foo = FooSerializer() in BarSerializer. The problem is that when I want to create a new Bar and link it to an existing Foo as before, it complains that I've not provided Foo's code property.

Simple and Elegant solution
override to_represention() method as,
class BarSerializer(serializers.ModelSerializer):
class Meta:
model = models.Bar
fields = ('id', 'foo', 'description')
def to_representation(self, instance):
data = super().to_representation(instance)
data['foo'] = FooSerializer(instance.foo).data
return data
Orginal version
use depth=1 in BarSerializer serializer
class BarSerializer(serializers.ModelSerializer):
class Meta:
model = models.Bar
fields = ('id', 'foo', 'description')
depth = 1
Reference
1. depth [DRF-Doc]
Update-1
Use Two different Serializers for Read and Write operations.
class BarWriteSerializer(serializers.ModelSerializer):
class Meta:
model = Bar
fields = ('id', 'foo', 'description')
def to_representation(self, instance):
data = super().to_representation(instance)
data['foo'] = FooSerializer(instance.foo).data
return data
class BarReadSerializer(serializers.ModelSerializer):
class Meta:
model = Bar
fields = ('id', 'foo', 'description')
depth = 1
and in your views, override the get_serializer_class() method as,
from rest_framework import viewsets
class SampleViewset(viewsets.ModelViewSet):
queryset = Bar.objects.all()
def get_serializer_class(self):
if self.action == 'create':
return BarWriteSerializer
return BarReadSerializer
The payload to use while Bar creation,
{
"foo":1,# The "pk" of "Foo" instance
"description":"bar description"
}

Solution:
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = models.Foo
fields = ('id', 'code')
class BarSerializer(serializers.ModelSerializer):
foo = FooSerializer(models.Foo.objects.all(), read_only=True)
foo_id = serializers.PrimaryKeyRelatedField(
source='foo',
queryset=models.Foo.objects.all(),
write_only=True
)
class Meta:
model = models.Bar
fields = ('id', 'foo', 'foo_id', 'description')
Use a nested serializer as read_only to get the full Foo object.
Use a write_only field foo_id to use it for create/update.
Now, your request data will look like:
{ 'foo_id': 1, 'description': 'foo bar' }
Alternatively, if you dont want two fields, one for read and another for write, you can override the create/update methods of the serializer to capture the foo's id.
Example:
class BarSerializer(serializers.ModelSerializer):
foo = FooSerializer(Foo.objects.all())
class Meta:
model = models.Bar
fields = ('id', 'foo', 'description')
def create(self, validated_data):
foo = validated_data.pop('foo')
bar = Bar.objects.create(
foo=foo.id,
**validated_data
)
return bar
def update(self, instance, validated_data):
foo = validated_data.pop('foo')
instance.foo = foo
instance.update(
**validated_data
)
return instance
The request data, in this case, will be:
{ 'foo': {'id': '1', 'code': 'AAA'}, 'description': 'foo bar' }

Related

nested one to many serializer model django

I am trying to create a relationship between databases
result : =>
[
{
"id":1,
"title":"mobile",
"category_two":[
{
"id":3,
"title":"brand"
},
{
"id":4,
"title":"memory"
}
]
}
]
and i expect : =>
[
{
"id":1,
"title":"mobile",
"category_two":[
{
"id":3,
"title":"brand",
"category_three":[
{
"id":1,
"title":"samsung"
},
{
"id":2,
"title":"apple"
}
]
},
{
"id":4,
"title":"memory",
"category_three":[
{
"id":1,
"title":"32gb"
},
{
"id":2,
"title":"64gb"
}
]
}
]
}
]
// views
class get_Category(APIView):
def get(self, request):
category = CategoryOne.objects.all()
serializer = CategoryTwoSerializer(category, many=True)
return Response(serializer.data)
//serializers
class CategoryOneSerializer(serializers.ModelSerializer):
class Meta:
model = CategoryOne
fields = '__all__'
class CategoryTwoSerializer(serializers.ModelSerializer):
category_two= CategoryOneSerializer(many=True,read_only=True)
class Meta:
model = CategoryTwo
fields = '__all__'
depth=5
class CategoryThreeSerializer(serializers.ModelSerializer):
category_three = CategoryTwoSerializer(many=True,read_only=True)
class Meta:
model = CategoryThree
fields = '__all__'
depth=5
// models
class CategoryOne(models.Model):
title = models.CharField(max_length=225)
def __str__(self):
return self.title
class CategoryTwo(models.Model):
title = models.CharField(max_length=255)
categoryone = models.ForeignKey(CategoryOne,related_name='category_two',on_delete=models.SET_NULL,null=True)
def __str__(self):
return self.title
class CategoryThree(models.Model):
title = models.CharField(max_length=255)
categorytwo = models.ForeignKey(CategoryTwo,related_name='category_three',on_delete=models.SET_NULL,null=True)
def __str__(self):
return self.title
Try
class CategoryOneSerializer(serializers.ModelSerializer):
category_two= CategoryTwoSerializer(many=True,read_only=True)
class Meta:
model = CategoryOne
fields = '__all__'
class CategoryTwoSerializer(serializers.ModelSerializer):
category_three = CategoryThreeSerializer(many=True,read_only=True)
class Meta:
model = CategoryTwo
fields = '__all__'
class CategoryThreeSerializer(serializers.ModelSerializer):
class Meta:
model = CategoryThree
fields = '__all__'
// views
class get_Category(APIView):
def get(self, request):
category = CategoryOne.objects.prefetch_related("category_two", "category_two__category_three")
serializer = CategoryOneSerializer(category, many=True)
return Response(serializer.data)
If you want category_two rows related to a specific category_one to be serialized with it, you essentially want to add a non existing data to CategoryOne.
Since means you need two add it to the serializer and specify how to serialize it.
Here you can access this data by using a related_name, therefore adding a field with the proper related_name will suffice for django to find the data you want.
The same wan be done the serializer CategoryThree tied the a specific CategoryTwo row.
Howerver doing this will produce a LOT of SQL queries since each time my_category_on.category_two.all() is called by the serializer, an sql query is needed.
The prefetch_related() is here to solve the issue

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

Django REST Framework: Show only latest of nested object, return as un-nested JSON

What I'm trying to do in Django REST Framework: Return only latest nested object in list for an object and return it as JSON, with the sub-object un-nested.
My models:
class BaseObject(models.Model):
name = models.TextField()
object_type = models.ForeignKey(ObjectType)
class ObjectStatus(models.Model):
baseobject_id = models.ForeignKey('objects.BaseObject', related_name='status')
object_status = models.IntegerField()
object_status_timestamp = models.DateTimeField()
My serializers:
class ObjectStatusSimplifiedSerializer(serializers.ModelSerializer): #helper serializer to simplify status objects
class Meta:
model = ObjectStatus
fields = ['object_status', 'object_status_timestamp']
class ObjectStatusListSerializer(serializers.ModelSerializer): #request for last status of several objects
status = ObjectStatusSimplifiedSerializer(many=True)
class Meta:
model = BaseObject
fields = ['id', 'name', 'object_type', 'status']
My current view:
class ObjectStatusListView(generics.ListCreateAPIView):
serializer_class = ObjectStatusListSerializer
def get_queryset(self):
queryset = BaseObject.objects.all()
id = self.request.query_params.getlist('id')
if id:
queryset = queryset.filter(id__in=id)
return queryset
Current URL:
url(r'^objectstatus/status/list$', views.ObjectStatusListView.as_view()),
So now, when going to, for example, [...]/objectstatus/status/list?id=9, the result I get looks like this:
[
{
"id": 9,
"name": "r5",
"object_type": "router",
"status": [
{
"object_status": 1,
"object_status_timestamp": "2019-10-24T09:40:15.605391Z"
},
{
"object_status": 2,
"object_status_timestamp": "2019-10-24T09:40:28.133296Z"
},
{
"object_status": 3,
"object_status_timestamp": "2019-10-24T09:40:40.829486Z"
},
{
"object_status": 1,
"object_status_timestamp": "2019-10-24T09:40:53.333332Z"
}
]
}
]
What I want is to display only the object status with the most recent timestamp.
Also, I can't figure out how to flatten the JSON object, like this:
[
{
"id": 9,
"name": "r5",
"object_type": "router",
"object_status": 1,
"object_status_timestamp": "2019-10-24T09:40:53.333332Z"
}
]
With the following serializer, you should get the desired output. We filter the status list and get only the latest one and then we flatten the structure as you need.
class ObjectStatusListSerializer(serializers.ModelSerializer): #request for last status of several objects
status = serializers.SerializerMethodField(read_only=True)
class Meta:
model = BaseObject
fields = ['id', 'name', 'object_type', 'status']
def get_status(self, obj):
return ObjectStatusSimplifiedSerializer(instance=obj.status.order_by('object_status_timestamp').first()).data
def to_representation(self, obj):
"""Move fields from status to main object representation."""
representation = super().to_representation(obj)
status_representation = representation.pop('status')
for key in status_representation:
representation[key] = status_representation[key]
return representation
you can try change serializer to like this. I assum your ObjectType have field is name for line code object_type.name
class ObjectStatusSimplifiedSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField()
object_type = serializers.SerializerMethodField()
#staticmethod
def get_name(instance):
return instance.status.name
#staticmethod
def get_object_type(instance):
return instance.status.object_type.name
class Meta:
model = ObjectStatus
fields = ['id', 'name', 'object_type', 'object_status', 'object_status_timestamp']
class ObjectStatusListSerializer(serializers.ModelSerializer):
status = serializers.SerializerMethodField()
#staticmethod
def get_status(instance):
queryset = ObjectStatus.objects.filter(baseobject_id=instance).order_by('-object_status_timestamp')[:1]
if queryset.count():
return ObjectStatusSimplifiedSerializer(queryset, many=True).data
return []
class Meta:
model = BaseObject
fields = ['id', 'name', 'object_type', 'status']

Django nested Serializer filter to only one field, not all fields

I have two serializers like below. The output for the below snippet is Workers and with associated Ticket Counter details with all fields (ticket_counter,ticket_counter_name,worker). But I just need only one field that is ticket_counter_name.
class WorkerSerializer(serializers.ModelSerializer):
ticket_counter = WorkerToCounterSerializer(many=True, read_only=True)
class Meta:
model = User
fields = (
'username',
'ticket_counter',
)
class WorkerToCounterSerializer(serializers.ModelSerializer):
ticket_counter = SerializerMethodField()
ticket_counter_name = serializers.CharField(source='ticket_counter.ticket_counter_name')
class Meta:
model = WorkerToTicketCounter
list_serializer_class = FilteredListSerializer
fields = (
'ticket_counter',
'ticket_counter_name',
'worker',
)
def get_ticket_counter(self, obj):
return obj.ticket_counter.pk
class FilteredListSerializer(ListSerializer):
def to_representation(self, data):
data = data.filter(worker_to_ticket_counter_is_deleted=False)[:1]
return super(FilteredListSerializer, self).to_representation(data)
What above snippet outputs
{
"username": "xxxxxxxxxxx",
"ticket_counter": [
{
"ticket_counter": 7,
"ticket_counter_name": "Entrance Counter",
"worker": 4,
}
]
}
But What I want is
{
"username": "xxxxxxxxxxx",
"ticket_counter": "Entrance Counter"
}
I just need the name of the ticket_counter_name. In my case, there can't be two ticket_counters for a worker. Obviously, it gives only one ticket_counter. Is it possible?
EDIT: using string StringRelatedField
{
"username": "xxxxxxxxxxx",
"ticket_counter": [
"Entrance Counter",
"xxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxx"
]
}
EDIT: WorkerToTicketCounter Model
class WorkerToTicketCounter(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
ticket_counter = models.ForeignKey(TicketCounter, related_name="workers")
worker = models.ForeignKey(User, related_name='ticket_counter')
worker_to_ticket_counter_is_deleted = models.BooleanField(default=False)
If I'm understood correctly, you only need a SerializerMethodField to perform both filtering and string represantion.
class WorkerSerializer(serializers.ModelSerializer):
ticket_counter = serializers.SerializerMethodField(read_only=True)
def get_ticket_counter(self, user):
qs = user.ticket_counter.filter(worker_to_ticket_counter_is_deleted=False)
if qs.exists() and hasattr(qs.first().ticket_counter, 'ticket_counter_name'):
return qs.first().ticket_counter.ticket_counter_name
return None
class Meta:
model = User
fields = ('username', 'ticket_counter',)
You can use StringRelatedField:
class WorkerSerializer(serializers.ModelSerializer):
ticket_counter = StringRelatedField(many=True, read_only=True)
class Meta:
model = User
fields = (
'username',
'ticket_counter',
)
Note to use StringRelatedField you should add __str__ method to your WorkerToTicketCounter model:
class WorkerToTicketCounter:
...
def __str__(self):
return self.ticket_counter.ticket_counter_name

Serializing a list of object as dictionnary in DjangoRestFramework

Using django & django-rest-framework, I have the following model (this is simplified but it's all there):
class Device(Model):
#stuff
class DeviceInformation(Model):
device = ForeignKey(Device, reverse='infos')
key = CharField(max_length=32)
value = CharField(max_length=1024)
When serializing a device through django-rest-framework's ModelSerializer, I get something like this:
{
//stuff
infos: [{
'key':'BatteryLevel',
'value':'80%'
},{
'key':'DeviceName',
'value':'my device'
}, //etc
]
}
Which is perfectly normal. However, it would make much more sense to serialize into something like this:
{
//stuff
infos: {
'BatteryLevel':'80%',
'DeviceName':'my device',
//etc
}
}
How do I do that? Is it even possible?
Note that I don't need to deserialize any of these information.
EDIT: my serializers are as follows:
class DeviceInfoSerializer(ModelSerializer):
class Meta:
model = DeviceInformation
fields = ('key', 'value')
read_only_fields = fields
class DeviceSerializer(HyperlinkedModelSerializer):
udid = serializers.CharField(read_only=True)
def __init__(self, *args, **kwargs):
super(DeviceSerializer, self).__init__(*args, **kwargs)
if hasattr(self, 'object') and self.object and not self.many:
self.data['infos'] = DeviceInfoSerializer(
self.object.infos.all(), many=True).data
class Meta:
model = Device
fields = ['udid', 'model', 'tracked']
read_only_fields = ('model', 'tracked')
slug_field = 'udid'
For your readonly-case, the best way is to use SerializerMethodField.
This would change your DeviceSerializer and remove the need for your DeviceInfoSerializer.
class DeviceSerializer(HyperlinkedModelSerializer):
udid = serializers.CharField(read_only=True)
infos = serializers.SerializerMethodField('get_infos')
def get_infos(self, obj):
return {
info.key: info.value
for info in obj.infos.all()
}
class Meta:
model = Device
fields = ['udid', 'model', 'tracked', 'infos']
read_only_fields = ('model', 'tracked', 'infos')
slug_field = 'udid'