Change Django Rest Framework serializers output structure? - django

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.

Related

How to serialize multiple model object?

I am creating an API using Django Restframework which needs data from multiple models. I got many answers for my requirement but it isn't working.
I have my models as follows
class Task(models.Model):
title = models.CharField(max_length=200)
completed = models.BooleanField(default=False, blank=True, null=True)
def __str__(self):
return self.title
class Task_extended(models.Model):
task_id = models.ForeignKey(Task, on_delete = models.CASCADE,related_name='task_extendeds')
field_3 = models.CharField(max_length=200)
field_5 = models.CharField(max_length=200)
field_4 = models.BooleanField(default=False, blank=True, null=True)
def __str__(self):
return self.field_3
Here's my view function
#api_view(['GET','POST'])
def taskList(request):
tasks = Task.objects.all()
serializer = TaskSerializer(tasks, many =True)
return Response(serializer.data)
Serializer.py
class TaskSerializer(serializers.ModelSerializer):
task_extendeds = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Task
fields = "__all__"
depth = 1
I am getting the json as following
[
{
"id": 2,
"task_extendeds": [
1,
2,
3
],
"title": "Start Rest Framework",
"completed": false
}
]
What changes should I do to Serializers.py so that my json output is as following
[
{
"id": 2,
"title": "Start Rest Framework",
"completed": false,
"task_extendeds": [
{
"field_3": "Field 3 Data",
"field_4": "Field 4 Data",
"field_5": "Field 5 Data"
},
{
"field_3": "Field 3 Data",
"field_4": "Field 4 Data",
"field_5": "Field 5 Data"
},
{
"field_3": "Field 3 Data",
"field_4": "Field 4 Data",
"field_5": "Field 5 Data"
}
],
}
]
The depth = 1 attribute in meta class should have got the work done according to other stackoverflow questions, but it isn't working.
You use a subserializer, so:
class Task_extendedSerializer(serializers.ModelSerializer):
class Meta:
model = Task_extended
fields = ['field_3', 'field_4', 'field_5']
class TaskSerializer(serializers.ModelSerializer):
task_extendeds = Task_extendedSerializer(many=True)
class Meta:
model = Task
fields = '__all__'
In the view you can boost efficiency by prefetching the task_extendeds:
#api_view(['GET'])
def taskList(request):
tasks = Task.objects.prefetch_related('task_extendeds')
serializer = TaskSerializer(tasks, many=True)
return Response(serializer.data)
Note: Models in Django are written in PascalCase, not snake_case,
so you might want to rename the model from Task_extended to TaskExtended.
Write a Task_extendedSerializer first then use it in TaskSerializer
class Task_extendedSerializer(serializers.ModelSerializer):
class Meta:
model = Task_extended
fields = ('field_3', 'field_4', 'field_5')
class TaskSerializer(serializers.ModelSerializer):
task_extendeds = Task_extendedSerializer()
class Meta:
model = Task
fields = ('id', 'title', 'completed', 'task_extendeds')

DJango query omtimization for foreign key

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)

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 - 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)
...

Invalid resource lookup data provided (mismatched type)

So I am trying to have my location filter url as api/v1/labels/?brand_location=Australia to filter brands with Australia only brands to return but keep receiving the error message:
{"error": "Invalid resource lookup data provided (mismatched type)."}
But when use api/v1/labels/?brand_location/=Australia it works but doest filter Australia only locations, it returns the full response not excluding locations.
So my questions are:
How can I remove the trailing slash? And get it to filter Australia only locations
Is this the right way to go about this? When using foreign keys with django tastypie?
My code below:
Models.py
class Brand(models.Model):
brand_location = models.ForeignKey('Location', null=True, blank=True, default="")
class Location(models.Model):
state_or_country = models.CharField(unique=True, max_length=200, blank=True, default="", verbose_name=_('Location'),
api.py
class LocationResource(ModelResource):
class Meta:
excludes = ['modified', 'id', 'created']
queryset = Location.objects.all()
resource_name = 'locations'
class LabelResource(ModelResource):
brand_location = fields.ForeignKey(LocationResource, 'brand_location', full=True)
class Meta:
filtering = {
"brand_location": ALL
}
queryset = Brand.objects.all()
resource_name = 'labels'
Snippet JSON Response
{
"labels": [
{
"brand_location": {
"state_or_country": "Australia"
}
}
],
"meta": {
"limit": 6,
"next": "/unlabel-network/unlabel-network-api/v1/labels/?limit=6&brand_location%2F=Australia&offset=6",
"offset": 0,
"previous": null,
"total_count": 128
}
}
api/v1/labels/?brand_location=Australia looks for Location.id=Australia.
Allow filtering deeper:
filtering = {
"brand_location": ALL_WITH_RELATIONS
}
and look for state_or_country field:
api/v1/labels/?brand_location__state_or_country=Australia
I just needed to add filtering to my LocationResource Then add ALL_WITH_RELATIONS to my filtering dict on my LabelResource
class LocationResource(ModelResource):
class Meta:
excludes = ['modified', 'id', 'created']
queryset = Location.objects.all()
resource_name = 'locations'
filtering = {
"state_or_country": ALL
}
class LabelResource(ModelResource):
brand_location = fields.ForeignKey(LocationResource, 'brand_location', full=True)
class Meta:
filtering = {
"brand_location": ALL,
"brand_location": ALL_WITH_RELATIONS
}
queryset = Brand.objects.all()
resource_name = 'labels'