nested one to many serializer model django - 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

Related

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 in one serialize pull out child objects

This my models
class Dictionary(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
parentId = models.UUIDField(editable=True, null=True)
name = models.CharField(max_length=100)
date_create = models.DateTimeField(auto_now=True)
date_end = models.DateTimeField(auto_now=False, null=True)
class Teacher(models.Model):
name = models.CharField(max_length=100)
message = models.CharField(max_length=300)
status = models.OneToOneField(Dictionary, on_delete=models.CASCADE)
this is my urls
from django.urls import path
from . import views
urlpatterns = [
path('get', views.GetViewSet.as_view({'get': 'list'})),
]
This is ViewSet
class GetViewSet(viewsets.ModelViewSet):
MyApiObj = null
#property
def api_object(self):
return namedtuple("ApiObject", self.request.data.keys())(*self.request.data.values())
def get_serializer_class(self):
GeneralSerializer.Meta.model = apps.get_model(app_label=self.MyApiObj.app, model_name=self.MyApiObj.object)
return GeneralSerializer
def post(self, request):
self.MyApiObj = self.api_object
return self.select_api()
def select_api(self):
queryset = QueryHelper.select(self.MyApiObj)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Serializer
class GeneralSerializer(serializers.ModelSerializer):
class Meta:
model = None
fields = '__all__'
My post parameters to django
{
"app":"leads",
"object":"Teacher",
"settings":{
},
"data":{
}
}
answer:
[
{
"id": 1,
"name": "John",
"message": "Hi everyone",
"status": "e3b86ed4-8794-413b-994c-b1ec0a43eebe"
}
]
Problem is Dictionary(status) model give me id(uuid) but i need whole object without creating new serializer for Dictionary. i do univeral serializer for all models in my app
Try this:
class DictionarySerializer(serializers.ModelSerializer):
class Meta:
model = Dictionary
fields = '__all__'
class GeneralSerializer(serializers.ModelSerializer):
status = DictionarySerializer(required=True)
class Meta:
model = None
fields = '__all__'
But it is not good for me because 1) Without other serializer 2) I need Universal serializer for all models and with child model in all models of my project. Help me please)
I need something like this
[
{
"id": 1,
"status": {
"id": "e3b86ed4-8794-413b-994c-b1ec0a43eebe",
"parentId": "dc6cf7da-b82c-11e9-a2a3-2a2ae2dbcce4",
"name": "Spravochnik1",
"date_create": "2019-08-06T09:30:49.355439Z",
"date_end": "2019-08-06T09:29:57Z"
},
"name": "John",
"message": "Hi everyone"
}
]
for nested serialization check full ref here
and for your case add depth = 1
class GeneralSerializer(serializers.ModelSerializer):
status = DictionarySerializer(required=True)
class Meta:
model = None
fields = '__all__'
depth = 1

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

Django Rest Framework: writable nested serializer

I need to manage a JSON like this:
{
"name": "drink_type",
"description": "A type of drink",
"values": [
{
"value": "Coca-Cola",
"synonyms": [
"coca cola",
"coke"
]
},
{
"value": "Beer",
"synonyms": [
"beer"
]
}
]
}
models.py:
class Entity(models.Model):
name = models.CharField(max_length=50)
description = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
class Value(models.Model):
entity = models.ForeignKey(Entity, related_name='values', on_delete=models.CASCADE)
value = models.CharField(max_length=50)
created = models.DateTimeField(auto_now_add=True)
class Synonymous(models.Model):
value = models.ForeignKey(Value, related_name='synonyms', on_delete=models.CASCADE)
synonymous = models.CharField(max_length=50)
created = models.DateTimeField(auto_now_add=True)
serializers.py:
class ValueSerializer(serializers.ModelSerializer):
synonyms = serializers.SlugRelatedField(
many=True,
slug_field='synonymous',
queryset=Synonymous.objects.all()
)
class Meta:
model = Value
fields = ('value', 'synonyms')
def create(self, validated_data):
synonyms_data = validated_data.pop('synonyms')
value = Value.objects.create(**validated_data)
for synonyomous_data in synonyms_data:
Synonymous.objects.create(value=value, **synonyomous_data)
return value
class EntitySerializer(serializers.ModelSerializer):
values = ValueSerializer(many=True)
class Meta:
model = Entity
fields = ('id', 'name', 'description', 'values')
def create(self, validated_data):
values_data = validated_data.pop('values')
entity = Entity.objects.create(**validated_data)
for value_data in values_data:
# How can I call the create method of values?
pass
return entity
views.py
#api_view(['GET', 'POST'])
def entity_list(request, format=None):
"""
List all entities, or create a new entity.
"""
if request.method == 'GET':
entities = Entity.objects.all()
serializer = EntitySerializer(entities, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = EntitySerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The serializers works fine when the GET view is called, but when I try to create a new Entity using the POST view, I'm not able to call the create method of ValueSerializer Class and the data created is like this:
{
"name": "drink_type",
"description": "A type of drink",
"values": []
}
Someone can help me?
Thank you!
class SynonymousSerializer(serializers.ModelSerializer):
class Meta:
model = Synonymous
fields = '__all__'
class ValueSerializer(serializers.ModelSerializer):
entity = serializers.PrimaryKeyRelatedField(queryset = Entity.objects.all())
synonyms = ValueSerializer(many=True)
class Meta:
model = Value
fields = ('id', 'synonyms')
class EntitySerializer(serializers.ModelSerializer):
values = ValueSerializer(many=True)
class Meta:
model = Entity
fields = ('id','values')
q = Entity.objects.all()
serializer = EntitySerializer(q)
print serializer.data

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'