Django Rest Framework - Group data by dynamic Key - django

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, }]

Related

Django Rest Framwork : three level relationship with nested serializer

I have double nested serializer situation...
i have three models : Reports, ReportPages and widgets , upon trying to create a specific endpoind that is :
payload {
"since_date": "some date",
"until_date": "some other date that is greater than since_date",
"report_pages": [
{
"page_number": "some number" (generated from front end, of type integer)
"widgets": [
{
"data": ["some array"],
"width": "some number",
"height": "some number",
"top_position": "some number",
"left_position": "some number",
"widget_type": "" (either "Label", "LineChart", "Bar" or "PieChart"),
}
]
}
]
}
I faced a problem with nested serializer, i was only able to create the first half which is :
payload {
"since_date": "some date",
"until_date": "some other date that is greater than since_date",
"report_pages": [
{
"page_number": "some number" (generated from front end, of type integer)
]
}
Knowing that the creation of a report is separate from this endpoint, so i override the update method and structure looks like this :
Report{
report_page{
widgets {}
}
}
My models :
class Reports(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=500, null=True)
since_date = models.DateTimeField(null=True)
until_date = models.DateTimeField(null=True)
def __str__(self):
return self.name
class ReportPages(models.Model):
id = models.AutoField(primary_key=True)
number = models.IntegerField(null=True)
report = models.ForeignKey(Reports, related_name="report_pages",on_delete=models.CASCADE)
def __str__(self):
return self.number
class Widgets(models.Model):
id = models.AutoField(primary_key=True, unique=True)
data = ArrayField(models.CharField(max_length=10, blank=True))
width = models.IntegerField(null=True)
height = models.IntegerField(null=True)
top_position = models.FloatField(null=True)
left_position = models.FloatField(null=True)
widget_type = models.CharField(max_length=500, null=True)
report_pages = models.ForeignKey(ReportPages,related_name="widgets", on_delete=models.CASCADE)
My views :
class Reports2ViewSet(viewsets.ModelViewSet):
queryset = Reports.objects.all()
serializer_class = Reports2Serializer
http_method_names = ['get','post','retrieve','put','patch']
My serializers :
class ReportWidgetsSerializer(serializers.ModelSerializer):
class Meta:
model = Widgets
fields = '__all__'
class ReportPagesSerializer(serializers.ModelSerializer):
widgets = ReportWidgetsSerializer(many=True)
class Meta:
model = ReportPages
fields = ('number','widgets',)
def update(self, instance, validated_data):
return super().update(instance, validated_data)
class Reports2Serializer(serializers.ModelSerializer):
report_pages = ReportPagesSerializer(many=True)
class Meta:
model = Reports
fields = ('report_pages','since_date','until_date',)
def update(self, instance, validated_data):
page_list = validated_data.pop('report_pages')
instance.since_date = validated_data.get('since_date', instance.since_date)
instance.until_date = validated_data.get('until_date', instance.until_date)
instance.save()
serializer = self.fields['report_pages']
widget_list = []
for page in page_list:
page['report'] = instance
widget_list = page.pop('widgets')
page = ReportPages.objects.create(**page)
print(page)
for widget in widget_list:
print('the widget is', widget)
widget['report_pages'] = page
widget = Widgets.objects.create(**widget)
print(widget)
# pages = serializer.create(page_list)
return instance
the error is :
{
"report_pages": [
{
"widgets": [
{
"report_pages": [
"This field is required."
]
}
]
},
{
"widgets": [
{
"report_pages": [
"This field is required."
]
}
]
}
]
}

Direct assignment to the reverse side issue with multiple nested serializer

I have this issue while running some test on my django project:
TypeError: Direct assignment to the reverse side of a related set is prohibited. Use files.set() instead.
Here is what I try to post
{
"daily_hash": "w54c6w546w5v46w5v4",
"modules": [
{
"module": "main",
"commits": [
{
"commit_hash": "11feb543f016114c700046d42b912910321230da",
"author": "Test name 1",
"subject": "[TICKET] Subject of the issue",
"files": []
},
{
"commit_hash": "093b19f710c6d2358b0812434dfcf1549c9c6fbb",
"author": "Test name 1",
"subject": "[TICKET] Subject of the issue",
"files": []
}
]
},
{
"module": "submodule",
"commits": [
{
"commit_hash": "dce22dea52a6a4b7160034d3f84a7af7b389ee96",
"author": "Test name 3",
"subject": "[TICKET] Subject of the issue",
"files": [
{
"name": "my_file_1.c"
},
{
"name": "my_file_2.c"
}
]
},
{
"commit_hash": "cee433fc4ab46464afb96d6ecae2839409fe0313",
"author": "Test name 4",
"subject": "[TICKET] Subject of the issue",
"files": []
},
{
"commit_hash": "4534f511b2a6a8c1632a1ab97b598d8e4dedada7",
"author": "Test name 1",
"subject": "[TICKET] Subject of the issue",
"files": []
}
]
}
]
}
You can find below my models.py:
from django.db import models
from status.models import Daily
class Component(models.Model):
"""
Component model
"""
module = models.CharField(max_length=40)
daily = models.ForeignKey(Daily, on_delete=models.CASCADE)
class Meta:
db_table = 'gds_component'
class Commit(models.Model):
"""
Commits model
"""
commit_hash = models.CharField(max_length=40)
author = models.CharField(max_length=60)
subject = models.CharField(max_length=250)
component = models.ForeignKey(
Component, related_name='commits',
on_delete=models.CASCADE)
class Meta:
db_table = 'gds_commit'
class File(models.Model):
"""
Commit files model
"""
name = models.CharField(max_length=250)
commit = models.ForeignKey(
Commit, related_name='files',
on_delete=models.CASCADE)
class Meta:
db_table = 'gds_commit_file'
My serializer here:
class FileSerializer(serializers.ModelSerializer):
class Meta:
model = models.File
exclude = ['commit']
class CommitSerializer(serializers.ModelSerializer):
files = FileSerializer(
required=False,
allow_null=True,
many=True
)
class Meta:
model = models.Commit
fields = ('commit_hash', 'author', 'subject', 'files')
def create(self, validated_data):
files_valid_data = validated_data.pop('files')
commit = models.Commit.objects.create(**validated_data)
for file_data in files_valid_data:
models.File.objects.create(commit=commit, **file_data)
return commit
class CompoSerializer(serializers.ModelSerializer):
commits = CommitSerializer(
required=False,
allow_null=True,
many=True
)
class Meta:
model = models.Component
fields = ('module', 'daily', 'commits')
def create(self, validated_data):
commits_valid_data = validated_data.pop('commits')
component = models.Component.objects.create(**validated_data)
for commit_data in commits_valid_data:
models.Commit.objects.create(component=component, **commit_data)
return component
And finally my view.py
#api_view(['POST'])
def post_commit(request):
if request.method == 'POST':
# Get the md5 hash to get it's id
valid_data = request.data
hash_data = valid_data.pop('daily_hash')
try:
daily_obj = Daily.objects.get(daily_key=hash_data)
except Daily.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
# Add daily_id to all modules
serializer_data = valid_data.pop('modules')
for elem in serializer_data:
elem['daily'] = daily_obj.id
# Serialize the data
serializer = serializers.CompoSerializer(data=serializer_data, many=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
# throw an error if something wrong hapen
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I tried so many things and now i'm totally lost. I think the issue is because I have multiple nested serializer but I'm not sure. Can anyone tell me the right direction to take ?
Best regards
Steph

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 rest framework return a custom object using ModelSerializer and ModelViewSet

I have three models, three serializers, one modelviewset below.
I am using django-rest-framework to make a rest api for android.
The restaurant model was created first. Then I created a star model and an image model.
What I want to do is to add star and image objects into restaurant objects.
finally I've got what I want result but I think my viewset code looks like wrong..
Is there another way not to use "for loop"?
Models
class Restaurant(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
address = models.CharField(max_length=255)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
weather = models.ForeignKey(Weather, on_delete=models.CASCADE)
distance = models.ForeignKey(Distance, on_delete=models.CASCADE)
description = models.TextField('DESCRIPTION')
def __str__(self):
return self.name
class Star(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
rating = models.IntegerField('RATING')
def __str__(self):
return self.restaurant
class RestaurantImage(models.Model):
id = models.AutoField(primary_key=True)
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
path = models.CharField(max_length=255)
Serializer
class StarSerializer(serializers.ModelSerializer):
class Meta:
model = Star
fields = ('id', 'restaurant', 'user', 'rating', )
class RestaurantDetailSerializer(serializers.ModelSerializer):
category = CategorySerializer()
weather = WeatherSerializer()
distance = DistanceSerializer()
class Meta:
model = Restaurant
fields = ('id', 'name', 'address', 'category', 'weather',
'distance', 'description', )
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = RestaurantImage
fields = ('id', 'path', 'restaurant')
ViewSet
class RestaurantDetailInfoViewSet(viewsets.ModelViewSet):
queryset = Restaurant.objects.all()
serializer_class = RestaurantSerializer
def list(self, request, *args, **kwargs):
restaurant_list = Restaurant.objects.all()
restaurant_result = []
for restaurant in restaurant_list:
restaurantInfo = Restaurant.objects.filter(id=restaurant.pk)
restaurant_serializer = RestaurantDetailSerializer(restaurantInfo, many=True)
ratingAverageValue = Star.objects.filter(restaurant=restaurant.pk).aggregate(Avg('rating'))
images = RestaurantImage.objects.filter(restaurant=restaurant.pk)
image_serializer = ImageSerializer(images, many=True)
restaurant_dic = {
'restaurant': restaurant_serializer.data,
'ratingAverage': ratingAverageValue['rating__avg']
if ratingAverageValue['rating__avg'] is not None else 0,
'images': image_serializer.data
}
restaurant_result.append(restaurant_dic)
return Response(restaurant_result)
Result
[
{
"restaurant": [
{
"id": 1,
"name": "restaurant1",
"address": "address1",
"category": {
"c_id": 1,
"name": "foodtype1"
},
"weather": {
"w_id": 1,
"name": "sunny"
},
"distance": {
"d_id": 1,
"name": "inside"
},
"description": "description1"
}
],
"ratingAverage": 2.6667,
"images": [
{
"id": 1,
"path": "imagepath",
"restaurant": 1
}
]
},
Solution:
class RestaurantDetailSerializer(serializers.ModelSerializer):
category = CategorySerializer()
weather = WeatherSerializer()
distance = DistanceSerializer()
images = ImageSerializer(many=True, read_only=True)
ratingAverage = serializers.SerializerMethodField(read_only=True)
def get_ratingAverage(self, restaurant):
ratingAvgVal = Star.objects.filter(
restaurant=restaurant
).aggregate(Avg('rating'))['rating__avg']
return ratingAvgVal if ratingAvgVal is not None else 0
class Meta:
model = Restaurant
fields = ('id', 'name', 'address', 'category', 'weather',
'distance', 'description', 'images', 'ratingAverage', )
Explanation:
Here, I have nested the ImageSerializer in the RestaurantSerializer class, since you needed all the fields you've defined in ImageSerializer.
Then, for ratingAverage, I have used the SerializerMethodField which returns the value calculated (your logic) in the method I've defined for it, i.e. get_ratingAverage, which takes the Restaurant instance reference passed as an argument to the method for the field.

How do I use RelatedField from Django Rest Framework when there is a reverse ForeignKey?

Here are my models:
class Property(models.Model):
id = [...]
address = [...]
class Property_Value(models.Model):
id = models.ForeignKey(Property)
amount = [...]
last_updated = [...]
def __unicode__(self):
return '%s: %s' % (self.last_updated, self.amount)
My serializer are:
class Property_ValueSerializer(serializers.ModelSerializer):
class Meta:
model = Property_Value
fields = ('last_updated', 'amount')
class PropertySerializer(serializers.ModelSerializer):
1) property_values = Property_ValueSerializer(source='property_value_set', many=True)
2) property_values = serializers.RelatedField(many=True, read_only=True)
class Meta:
model = Property
fields = ('id', 'address', 'property_values')
I would like to display a json like this:
[
{
"id": "2",
"address": "123 Apple Lane",
"property_values": [
{
"10/13/2016": "1709195.00"
}
]
}
]
If I use option 1, I get:
[
{
"id": "2",
"address": "123 Apple Lane",
"property_values": [
{
"last_updated": "10/13/2016",
"amount": "1709195.00"
}
]
}
]
If I user option 2 (which is what was suggested by the Rest tutorial, I get an Attribution Error:
AttributeError at /api/property/
'Property' object has no attribute 'property_values'
I'm not quite sure what I am doing wrong. Can someone point out what I am doing wrong? Thanks.
You can use StringRelatedField which returns the __unicode__ representation for the related fields. In your case __unicode__ method already returns the value in the way you are expecting in serializer.
class PropertySerializer(serializers.ModelSerializer):
property_values = serializers.StringRelatedField(many=True)
class Meta:
model = Property
fields = ('id', 'address', 'property_values')
class Property_Value(models.Model):
id = models.ForeignKey(Property, related_filed="property_values")
amount = [...]
last_updated = [...]
def __unicode__(self):
return '%s: %s' % (self.last_updated, self.amount)