DJango query omtimization for foreign key - django

class MainCategory(models.Model):
main_category_id = models.PositiveSmallIntegerField(primary_key=True)
name = models.CharField(max_length=30)
image = models.URLField(null=True)
class Meta:
db_table = 'lookup_main_category'
def __str__(self):
return self.name
class SubCategory(models.Model):
sub_category_id = models.PositiveSmallIntegerField(primary_key=True)
main_category = models.ForeignKey(MainCategory, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
image = models.URLField(null=True)
class Meta:
db_table = 'lookup_sub_category'
def __str__(self):
return self.name
I have the above 2 models in my DJango REST framework project and I need an API output as below. What will be the optimum query without having typical for loop to get distinct main categories and then loop through it in the sub_category table?
[
{
"main_category_id": "10",
"main_category_name": "main_name1",
"image": "http://example/com1",
"sub_categories": [
{
"sub_category_id": "20",
"sub_category_name": "sub_name1",
"image": "http://example/com1"
},
{
"sub_category_id": "21",
"sub_category_name": "sub_name2",
"image": "http://example/com"
}
]
},
{
"main_category_id": "11",
"main_category_name": "main_name2",
"image": "http://example/com2",
"sub_categories": [
{
"sub_category_id": "22",
"sub_category_name": "sub_name2",
"image": "http://example/com2"
}
]
}
]

In this case you would take advantage of prefetch_related, which performs two SQL queries and does an in-memory JOIN.
queryset = MainCategory.objects.prefetch_related('subcategory_set')
Then you iterate over this queryset serializing each object with it's subcategories without hitting the DB.
More info. on official docs here: https://docs.djangoproject.com/en/3.0/ref/models/querysets/#prefetch-related

Finally this is how I managed to get the expected result.
Trick was to use the _set.all()
Official docs are here.
categories_result = []
main_categories = MainCategory.objects.all()
for main_category in main_categories:
main_cat_data = MainCategorySerializer(main_category, many=False)
sub_categories = main_category.subcategory_set.all()
sub_categories_data = SubCategorySerializer(sub_categories, many=True)
result_info = {'main_category': main_cat_data.data, 'sub_categories': sub_categories_data.data}
categories_result.append(result_info)

Related

Django Rest Framework - Group data by dynamic Key

I'm trying to format data when querying my API. I can retrieve my data like that :
"results": [
{
"Cat1": [
{
"job": String,
"position": Integer
}
]
},
{
"Cat1": [
{
"job": String,
"position": Integer
}
]
},
{
"Cat2": [
{
"job": String,
"position": Integer
}
]
}
]
But I want something like that:
"results": [
{
"Cat1": [
{
"job": String,
"position": Integer
},
{
"job": String,
"position": Integer
}
]
},
{
"Cat2": [
{
"job": String,
"position": Integer
}
]
}
]
I use a serializer like this:
class CustomSerializer(serializers.ModelSerializer):
category = CatSerializer()
job = JobSerializer()
class Meta:
model = MyModel
fields = '__all__'
def to_representation(self, value):
return {
value.category.name: [{"job": value.job.name,
"position": value.position, }]
cat1 and cat2 are dynamics, they are from another table. I don't understand how to create my arrays properly using those serializers. The category is a #Property field in my model who's a foreign key of job.
My models:
class MyModel(models.Model):
CHOICES = [(i, i) for i in range(4)]
partner = models.ForeignKey(Partner, on_delete=models.CASCADE)
job = models.ForeignKey(
Job, on_delete=models.CASCADE)
position = models.IntegerField(choices=CHOICES)
#property
def category(self):
return self.job.category.domain
def __str__(self):
return '%s | %s | %s | position: %s' % (self.partner.name, self.domain.name, self.job.name, self.position)
class Job(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
code = models.CharField(
max_length=255, unique=True)
name = models.CharField(
max_length=255)
class Category(models.Model):
domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
code = models.CharField(
max_length=5)
name = models.CharField(max_length=255)
hourly_rate = models.FloatField(
null=True, blank=True)
How should I deal with serializers to format my data properly ?
EDIT:
I ended with something like that, except for the ListSerializer.
I used 2 ModelSerilizers
class MyModelCustomSerializer(serializers.ModelSerializer):
position = serializers.IntegerField(read_only=True)
job = serializers.CharField(source='job.name', read_only=True)
class Meta:
model = MyModel
fields = ['job', 'position']
def to_representation(self, value):
return {"position": value.position,
"job": {"name": value.job.name, "slug": value.job.slug,
"title": value.job.seo_title}
}
And
class CategoryCustomSerializer(serializers.ModelSerializer):
models = MyModelustomerSerializer(many=True)
class Meta:
model = Category
fields = ['category', 'MyModel']
def to_representation(self, value):
filters = {'job__category__domain__name': value.name}
myModels = MyModel.objects.filter(**filters)
serializer = MyModelCustomSerializer(instance=myModels, many=True,)
return {value.name: serializer.data}
But if I try to use a jobSerializer who already exist instead of
"job": {"name": value.job.name, "slug": value.job.slug,
"title": value.job.seo_title}
},
I got this error: Object of type 'Job' is not JSON serializable, but it's working anyway because i don't need all fields
I would go the direction of implementing a custom ListSerializer for the ModelSerializer and overriding its to_representation method.
from rest_framework import serializers
from collections import OrderedDict
class CustomListSerializer(serializers.ListSerializer):
def to_representation(self, data):
iterable = data.all() if isinstance(data, models.Manager) else data
list_rep = OrderedDict()
for item in iterable:
child_rep = self.child.to_representation(item)
k, v = list(child_rep.items()).pop()
list_rep.setdefault(k, []).append(v)
return [
{k: v}
for k, v in list_rep.items()
]
Then set the model Meta to use it
class CustomSerializer(serializers.ModelSerializer):
category = CatSerializer()
job = JobSerializer()
class Meta:
model = MyModel
fields = '__all__'
list_serializer_class = CustomListSerializer
def to_representation(self, value):
return {
value.category.name: [{"job": value.job.name,
"position": value.position, }]

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 - Retrieve nested fields multiple levels deep using foreign keys

I'm struggling to write a Django GET that returns the following looking response:
{
"lists": [
{
"id": "123",
"list_order": [
{
"id": "123_1",
"order": 1,
"list_id": "123",
"item_id": 9876,
"item": {
"id": 9876,
"name": "item1",
"location": "California"
}
},
{
"id": "123_2",
"order": 2,
"list_id": "123",
"item_id": 2484,
"item": {
"id": 2484,
"name": "item2",
"location": "California"
}
}
],
"updated_date": "2018-03-15T00:00:00Z"
}
]
}
Given a list_id, the response returns the basic information on the list ("id", "updated_date"), as well as the order of items in the list. Inside each item in the list order, it also grabs the related item details (nested in "item"). I'm able to get this response without the "item" details ("id", "name", "location" fields) and with no error:
{
"lists": [
{
"id": "123",
"list_order": [
{
"id": "123_1",
"order": 1,
"list_id": "123",
"item_id": 9876
},
{
"id": "123_2",
"order": 2,
"list_id": "123",
"item_id": 2484
}
],
"updated_date": "2018-03-15T00:00:00Z"
}
]
}
Again there is no error, and I can retrieve the first nested level without any issue. The problem is getting the "item" information to show within each "list_order". Below are my models, serializers, and views.
models.py
class Lists(models.Model):
id = models.CharField(null=False, primary_key=True, max_length=900)
updated_date = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_lists'
class Items(models.Model):
id = models.BigIntegerField(primary_key=True)
name = models.TextField(blank=True, null=True)
location = models.TextField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_items'
class ListOrder(models.Model):
id = models.CharField(null=False, primary_key=True, max_length=900)
list_id = models.ForeignKey(Lists, db_column='list_id', related_name='list_order')
item_id = models.ForeignKey(Items, db_column='item_id', related_name='items')
order = models.BigIntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_list_order'
serializers.py
class ItemsSerializer(serializers.ModelSerializer):
class Meta:
model = Items
fields = '__all__'
class ListOrderSerializer(serializers.ModelSerializer):
item = ItemsSerializer(many=False, read_only=True)
class Meta:
model = ListOrder
fields = '__all__'
class ListsSerializer(serializers.ModelSerializer):
list_order = ListOrderSerializer(many=True, read_only=True)
class Meta:
model = Lists
fields = '__all__'
views.py
class ListsViewSet(generics.ListCreateAPIView):
"""
API endpoint that returns a list with its meta-information
"""
queryset = Lists.objects.all()
serializer_class = ListsSerializer
def get_queryset(self):
list_id = self.kwargs['list_id']
filters = [Q(id=list_id)]
return Lists.objects.filter(*filters)
def list(self, request, list_id):
queryset = self.get_queryset()
list_serializer = ListsSerializer(queryset, many=True)
return Response({ 'lists': list_serializer.data })
I'm pretty new to Django and like what it offers so far, though maybe I'm thinking of doing this in too much of a "SQL" way. I've read about select_related() and prefetch_related(), but not sure how I would apply it to this case. Any assistance is greatly appreciated and let me know if there's any other information I can provide.
In your ListOrderSerializer you are trying to serialize item. while in ListOrder model you used the field name item_id
Solution:
In ListOrderSerializer:
class ListOrderSerializer(serializers.ModelSerializer):
item_id = ItemsSerializer(many=False, read_only=True)
...

Django Rest Framework - Return row and its adjacent rows on GET

I would like to return a given row and the row before and after it (sorted by file_created_time desc) when calling GET with a uid parameter representing the row.
URL ex: https://<domain>/api/videos/cbf02e8c-b2f5-4cd8-b3ec-87417eae2f7d?with_adjacent=true
{
"uid": "fd5d5936-8183-495f-9a9d-8ffca25a9bab",
"is_thumbnail": true,
"file_name": "2018-02-03_05-00-40.jpg",
"file_path": "thumbnails/2018-02-03_05-00-40.jpg",
"file_created_time": "2018-02-03T05:00:40-07:00",
"owner": "system_user",
"created_time": "2018-02-04T14:49:29.355156-07:00"
},
{
"uid": "cbf02e8c-b2f5-4cd8-b3ec-87417eae2f7d",
"is_thumbnail": true,
"file_name": "2018-02-03_01-09-30.jpg",
"file_path": "thumbnails/2018-02-03_01-09-30.jpg",
"file_created_time": "2018-02-03T01:09:30-07:00",
"owner": "system_user",
"created_time": "2018-02-04T14:49:30.464810-07:00"
},
{
"uid": "ed626576-cc9d-4434-9f44-93a4f8f525ad",
"is_thumbnail": true,
"file_name": "2018-02-03_00-59-15.jpg",
"file_path": "thumbnails/2018-02-03_00-59-15.jpg",
"file_created_time": "2018-02-03T00:59:15-07:00",
"owner": "system_user",
"created_time": "2018-02-04T14:49:32.611105-07:00"
}
Given the model:
class Videos(models.Model):
"""This class represents the Videos model."""
uid = models.UUIDField(
primary_key=True, default=uuid.uuid4, editable=False)
is_thumbnail = models.BooleanField(default=False)
file_name = models.CharField(unique=True, max_length=64)
file_path = models.CharField(unique=True, max_length=256)
file_created_time = models.DateTimeField()
owner = models.ForeignKey('auth.User',
related_name='videos',
on_delete=models.CASCADE)
created_time = models.DateTimeField(auto_now_add=True)
def __str__(self):
"""Return a human readable representation of the model instance."""
return "{}".format(self.file_name)
And View:
class DetailsView(generics.RetrieveUpdateDestroyAPIView):
"""This class handles the http GET, PUT and DELETE requests."""
queryset = Videos.objects.all()
serializer_class = VideosSerializer
permission_classes = (permissions.IsAuthenticated, IsOwner)
lookup_field = 'uid'
I'm happy the adjust the view as necessary, that's just what I have now.
Edit:
To further complicate this, this model contains rows where is_thumbnail is either true or false. Thus, assuming I change my pk to an id, if I want to select an is_thumbnail = True row sorted by file_created_time, there's no guarantee that its adjacent rows are pk +/- 1.
Apart from my other answer, I found a better way (think so) to achieve the result.I'm only adding the key part of the snippet
....
vid_1 = Videos.objects.get(uid=input_uid)
vid_2 = vid_1.get_next_by_file_created_time()
vid_3 = vid_1.get_previous_by_file_created_time()
queryset = [vid_1, vid_2, vid_3]
return Response(VideosSerializer(queryset,many=True).data)
We could get these kinds of things only if DateTime field is not null.
for more, refer this official documentation and stackoverflow answer

Change Django Rest Framework serializers output structure?

I have a Django model like this:
class Sections(models.Model):
section_id = models.CharField(max_length=127, null=True, blank=True)
title = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
class Risk(models.Model):
title = models.CharField(max_length=256, null=False, blank=False)
section = models.ForeignKey(Sections, related_name='risks')
class Actions(models.Model):
title = models.CharField(max_length=256, null=False, blank=False)
section = models.ForeignKey(Sections, related_name='actions')
And serializers like that :
class RiskSerializer(serializers.ModelSerializer):
class Meta:
model = Risk
fields = ('id', 'title',)
class ActionsSerializer(serializers.ModelSerializer):
class Meta:
model = Actions
fields = ('id', 'title',)
class RiskActionPerSectionsSerializer(serializers.ModelSerializer):
risks = RiskSerializer(many=True, read_only=True)
actions = ActionsSerializer(many=True, read_only=True)
class Meta:
model = Sections
fields = ('section_id', 'risks', 'actions')
depth = 1
def to_representation(self, instance):
response_dict = dict()
response_dict[instance.section_id] = {
'actions': HealthCoachingActionsSerializer(instance.actions.all(), many=True).data,
'risks': HealthCoachingRiskSerializer(instance.risks.all(), many=True).data
}
return response_dict
When accessing the RiskActionPerSectionSerializer over a view, I get the following output:
[
{
"section 1": {
"risks": [],
"actions": []
}
},
{
"section 2": {
"risks": [],
"actions": []
}
},
.....
]
It s fine but I would prefer to have that :
[
"section 1": {
"risks": [],
"actions": []
},
"section 2": {
"risks": [],
"actions": []
}
.....
]
Also why is it returning an array by default [] and not an object for example like:
{
"count": 0,
"next": null,
"previous": null,
"results": []
}
You need to override the to_representation() method of your serializer class. It is mention in brief in this official documentation link:
http://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior
Other option is overriding the get and post methods of your class APIView or it's inherited class
You can't store key/value pairs in list, ['key': {}] won't work, you should access to item by index instead, like in your representation.
Array returned because of many=True which means you put many objects in serializer and it wait sequence from you.
I can't see the ORM query that resulted in this output, but Your query is returning multiple items that match your criteria. Default behaviour of any frameworks, let alone DRF, is to put them inside an array and return them as an "Array of objects" as in your result.