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

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

Related

How to add model instances to a Foreign Key field in Django Rest Framework

I need to get the ItemModel instances from the item model and store them in the foreign key field in the OrderModel but I am not sure how. I've tried to iterate through the item order list and add one but it doesn't display correctly. Any help would be much appreciated.
My goal is to display the data like this:
{
"payment_method": "test",
"confirmation": "14087147WA285750M",
"total_price": "15.00",
"is_paid": "True",
"order_created": "2021-07-09T19:51:18Z",
"item_order": [
{
"id": 2,
"name": "Carrots",
"image": "image link",
"slug": "carrots",
"price": "5.00",
"itemID": "ct1",
"quantity": 1
},
{
"id": 8,
"name": "Dog Food",
"image": "image link",
"slug": "dog-food",
"price": "10.00",
"itemID": "df4",
"quantity": 1
}
]
}
View:
#api_view(['POST'])
def create_order(request):
user = request.user
order = request.data
order_item = OrderModel.objects.create(
user=user,
payment_method=order['payment_method'],
confirmation=order['confirmation'],
total_price=order['total_price'],
is_paid=order['is_paid'],
order_created=order['order_created'],
item_order= """ The item model instance """
)
ordered_items = OrderSerializer(order_item, many=True).data
return Response(ordered_items)
Order Model:
class OrderModel(models.Model):
user = models.ForeignKey(CustomerModel, on_delete=models.CASCADE)
item_order = models.ForeignKey(
ItemModel, on_delete=models.CASCADE)
payment_method = models.CharField(max_length=50)
confirmation = models.CharField(max_length=255)
total_price = models.DecimalField(
max_digits=5, decimal_places=2)
is_paid = models.BooleanField(default=False)
has_been_sent = models.BooleanField(default=False)
order_created = models.DateTimeField()
def __str__(self):
return str(self.id)
Order Serializer:
class OrderSerializer(serializers.ModelSerializer):
item_order = ItemSerializer(many=True)
class Meta:
model = OrderModel
fields = ['payment_method', 'confirmation',
'total_price', 'is_paid', 'has_been_sent', 'order_created',
'item_order']
read_only_fields = ['id']

serializer only certain fields in foreing key relation

I'm trying to serializer two nested models linked by a foreing key:
class Category(models.Model):
sp = models.ForeignKey('species.Sp', on_delete=models.CASCADE, related_name='species_category')
category_name = models.CharField(max_length=50)
class Catch(models.Model):
weight = models.IntegerField()
category = models.ForeignKey('species.Category', on_delete=models.CASCADE,)
I know is possible to use depth option, but it serialize all the fields of the related model, for example:
class CatchesSerializer(serializers.ModelSerializer):
class Meta:
model = Catch
fields = ['id', 'weight', 'category', ]
depth = 1
returns
[
{
"id": 335,
"weight": 4710,
"category": {
"id": 1,
"category_name": "1",
"sp": 41
}
},
...
]
How is the way to serialize only certains fields of the related model? for example:
[
{
"id": 335,
"weight": 4710,
"category": {
"sp": 41,
}
},
...
]
Serializer can be nested, you can try:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['sp']
class CatchesSerializer(serializers.ModelSerializer):
category = CategorySerializer()
class Meta:
model = Catch
fields = ['id', 'weight', 'category']

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