Direct assignment to the reverse side issue with multiple nested serializer - django

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

Related

How to show fields from another serializer in Django rest framework

I want to show extra fields in response, showing similar products or related products in the category while am in a product detail page.
Eg:
If am viewing a single product detail page and in the bottom of page the related products list must be also show there. So while in response of related products, I want to showhighest_offer_price, product_offer_discount, category_offer_discount of ProductDetailserilaizer.
#Serializer.py
class RelatedProductSerializer(ModelSerializer):
class Meta:
model = Products
fields = ['product_name', 'slug', 'base_price', 'images']
class ProductDetailserializer(ModelSerializer):
product_offer_discount = SerializerMethodField()
category_offer_discount = SerializerMethodField()
highest_offer_price = SerializerMethodField()
description = ProductDescription()
extra_images = SerializerMethodField()
related_products = SerializerMethodField()
class Meta:
model = Products
fields = [
"id",
"product_name",
"slug",
"highest_offer_price",
"category_offer_discount",
"product_offer_discount",
"description",
"base_price",
"stock",
"is_available",
"images",
"extra_images",
"related_products"
]
def to_representation(self, instance):
print('INSTANCE', instance)
rep = super().to_representation(instance)
rep['description'] = ProductDescriptionSerializer(instance.description, many=True).data
return rep
def get_related_products(self, obj):
products= Products.objects.filter(category=obj.category).exclude(id=obj.id)
return RelatedProductSerializer(products, many=True, context=self.context).data
def get_extra_images(self, obj):
images = obj.extra_images.all()
return ProductImageSerializer(images, many=True, context=self.context).data
#Views.py
class ProductDetailView(APIView):
def get(self, request, category_slug, product_slug):
try:
single_product = Products.objects.get(
category__slug=category_slug, slug=product_slug
)
except:
raise exceptions.NotFoundError()
serializer = ProductDetailserializer(
single_product, context={
"request": request,
}
)
return Response(serializer.data)
#Response
{
"id": 1,
"product_name": "Pendant 1",
"slug": "pendant-1",
"highest_offer_price": null,
"category_offer_discount": null,
"product_offer_discount": null,
"description": [
{
"title": "First title",
"description": "First description pendant 1"
},
{
"title": "second title",
"description": "second description pendant 1"
}
],
"base_price": 2500,
"stock": 97,
"is_available": true,
"images": "http://127.0.0.1:8000/media/photos/products/pendant3.webp",
"extra_images": [
{
"images": "http://127.0.0.1:8000/media/photos/product-images/pendant3.webp"
},
{
"images": "http://127.0.0.1:8000/media/photos/product-images/pendant3_BQJRObf.webp"
},
{
"images": "http://127.0.0.1:8000/media/photos/product-images/pendant3_QGmLbXC.webp"
}
],
"related_products": [
{
"product_name": "Pendant 2",
"slug": "pendant-2",
"base_price": 3500,
"images": "http://127.0.0.1:8000/media/photos/products/pendant2.webp"
},
{
"product_name": "Pendant 3",
"slug": "pendant-3",
"base_price": 1500,
"images": "http://127.0.0.1:8000/media/photos/products/281031cw114n.webp"
},
{
"product_name": "pendant 4",
"slug": "pendant-4",
"base_price": 1500,
"images": "http://127.0.0.1:8000/media/photos/products/281031cw114n_Nxbx7lT.webp"
}
]
Am new to this and am complicating it too much i guess Because I have already wrote multiple serializers for Product model.
It's perfectly fine to have multiple serializers for the same model if the data differs between them.
If you're worrying about re-writing the same code multiple times, on different serializers, you could always make a base serializer for your Products model and then extend it as needed.
Like this:
# Make a BaseProductSerializer that inherits from Modelserializer and contains all fields and methods that all Products serializers need.
class BaseProductSerializer(Modelserializer):
product_offer_discount = SerializerMethodField()
category_offer_discount = SerializerMethodField()
highest_offer_price = SerializerMethodField()
def get_product_offer_discount(self):
return # whatever this method should do...
def get_category_offer_discount(self):
return # whatever this method should do...
def get_highest_offer_price(self):
return # whatever this method should do...
# Make a serializer for related products that inherits from BaseProductSerializer and specify the Meta data.
class RelatedProductSerializer(BaseProductSerializer):
class Meta:
model = Products
fields = [
'product_name',
'slug',
'base_price',
'images',
'highest_offer_price',
'product_offer_discount',
'category_offer_discount'
]
# Make a serializer for product details that inherits from BaseProductSerializer and add any extra fields/methods that you need.
class ProductDetailserializer(BaseProductSerializer):
description = ProductDescription()
extra_images = SerializerMethodField()
related_products = SerializerMethodField()
class Meta:
model = Products
fields = [
"id",
"product_name",
"slug",
"highest_offer_price",
"category_offer_discount",
"product_offer_discount",
"description",
"base_price",
"stock",
"is_available",
"images",
"extra_images",
"related_products"
]
def to_representation(self, instance):
print('INSTANCE', instance)
rep = super().to_representation(instance)
rep['description'] = ProductDescriptionSerializer(instance.description, many=True).data
return rep
def get_related_products(self, obj):
products= Products.objects.filter(category=obj.category).exclude(id=obj.id)
return RelatedProductSerializer(products, many=True, context=self.context).data
def get_extra_images(self, obj):
images = obj.extra_images.all()
return ProductImageSerializer(images, many=True, context=self.context).data
Hope this helps.

Get every m2m relation in model as different object in DRF serializer

I have a model Jurisdiction that has M2M relation with Franchise. And I use ModelViewSet on Jurisdiction.
models.py
class Franchise(models.Model):
name = models.CharField('Name', max_length=255, db_index=True)
class Jurisdiction(models.Model):
franchise = models.ManyToManyField(
Franchise,
verbose_name='Franchise',
related_name='jurisdictions',
blank=True
)
name = models.CharField(
"Jurisdiction name",
max_length=255,
db_index=True,
unique=True,
)
phone_number = models.CharField(
"Phone number",
max_length=12,
validators=[phone_number_regex],
unique=True,
)
My views.py
class JurisdictionViewSet(viewsets.ModelViewSet):
queryset = Jurisdiction.objects.all().prefetch_related('franchise')
serializer_class = JurisdictionSerializer
serializers.py
class JurisdictionSerializer(serializers.ModelSerializer):
franchise = FranchiseSerializer(many=True)
class Meta:
model = Jurisdiction
fields = (
'id', 'name', 'phone_number', 'franchise',
)
And serializer shows me:
{
"id": 1,
"name": "Test juris 1",
"phone_number": "200-000-1233",
"franchise": [
{
"id": 1,
"name": "Test franchise 1",
},
{
"id": 2,
"name": "Test franchise 2",
}
}
How I can get a list view of every m2m relation as a different object in serializer? I tried to change the to_representation method of serializer but nothing works
expected output:
[
{
"id": 1,
"name": "Test juris 1",
"phone_number": "200-000-1233",
"franchise_name": "Test franchise 1"
},
{
"id": 1,
"name": "Test juris 1",
"phone_number": "200-000-1233",
"franchise_name": "Test franchise 2"
}
]
You can customize the ListSerializer of your ModelSerializer. Specifically the to_representation method. For example:
from rest_framework import serializers
class CustomJurisdictionListSerializer(serializers.ListSerializer):
def to_representation(self, data):
iterable = data.all() if isinstance(data, models.Manager) else data
response = []
for item in iterable:
item_representation = self.child.to_representation(item)
for franchise in item.franchise.all():
representation = item_representation.copy()
representation['franchise_name'] = franchise.name
response.append(representation)
return response
Then remove franchise from JurisdictionSerializer and set the custom list serializer:
class JurisdictionSerializer(serializers.ModelSerializer):
class Meta:
model = Jurisdiction
fields = (
'id', 'name', 'phone_number',
)
list_serializer_class = CustomJurisdictionListSerializer

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