django rest nested relation in post/put - django

I am new in django rest api developement. I have two models one is category and another is subcategories.
Here is my models
class Category(models.Model):
title = models.Charfield()
brief = models.TextField()
subcategories = model.ManyToManyField('Subcategory', blank=True)
My serializer class
class CategorySerializer(serializers.ModelSerializer):
title= serializer.Charfield()
subcategories = Relatedfield(many=True)
Now in view
def post(self, request, format = None):
data=request.DATA
serialize= CategorySerializer(data=request.DATA)
if serializer.valid():
serializer.save()
How to save nested data like {'title':"test",'subscategories':[{'description':'bla bla bla'},{'description':'test test'}]} in post method.
I have read this in documentation
Note: Nested serializers are only suitable for read-only
representations, as there are cases where they would have ambiguous or
non-obvious behavior if used when updating instances. For read-write
representations you should always use a flat representation, by using
one of the RelatedField subclasses.
Please let me suggest which is right way or solution to do nested relation post/put in django rest.

Have you tried creating a SubCategorySerializer and adding this as a field on CategorySerializer?
class SubcategorySerializer(serializers.ModelSerializer):
class Meta:
model = Subcategory
class CategorySerializer(serializers.ModelSerializer):
subcategories = SubcategorySerializer(many=True)
Docs: http://django-rest-framework.org/api-guide/relations.html#nested-relationships

Related

Need to fetch two specific field values from one model to another seperately in django API rest framework

model 1
class Users(models.Model):
employee_name = models.CharField(max_length=50)
dob=models.DateField(max_length=8)
email=models.EmailField(max_length=254,default=None)
pancard=models.CharField(max_length=25,default=None)
aadhar=models.CharField(max_length=20,default=None)
personal_email_id=models.EmailField(max_length=254,default=None)
phone = PhoneField(blank=True)
emergency_contact_no=models.IntegerField(default=None)
emergency_contact_name=models.CharField(max_length=100,null=True)
relation=models.CharField(max_length=25,default=None)
blood_group=models.CharField(max_length=25,choices=BLOOD_GROUP_CHOICES,null=True)
desingnation=models.ForeignKey(Designation,on_delete=CASCADE,related_name="desingnation")
billable_and_non_billable=models.CharField(max_length=25,choices=BILLABLE_and_NON_BILLABLE_CHOICES,default='Billable')
joining_date=models.DateField(max_length=15,null=True)
relieving_date=models.DateField(max_length=15,null=True)
def __str__(self):
return self.employee_name
model 2
class Consolidated(models.Model):
emp_name=models.ForeignKey(Users,on_delete=CASCADE)
proj_name=models.ForeignKey(Project,on_delete=CASCADE)
custom_name=models.ForeignKey(Client,on_delete=CASCADE)
Cons_date=models.ForeignKey(Add_Timelog,on_delete=CASCADE)
bill_no_bill=models.ForeignKey(Users,on_delete=CASCADE,related_name="billable_and_non_billable+")
def __str__(self):
return str(self.emp_name)
Serializers
class UserSerializers(serializers.ModelSerializer):
class Meta:
model= Users
fields = '__all__'
class Consolidated_serializers(serializers.ModelSerializer):
class Meta:
model=Consolidated
fields= '__all__'
Viewsets
class UserViewset(viewsets.ModelViewSet):
permission_classes=(permissions.IsAdminUser,)
queryset=models.Users.objects.all()
serializer_class=serializers.UserSerializers
class Consolidated_ViewSet(viewsets.ModelViewSet):
permission_classes=(permissions.IsAdminUser,)
queryset=models.Consolidated.objects.all()
serializer_class=serializers.Consolidated_serializers
Actually I was stucked in the middle, as I need to take the values from 'billable_and_non_billable' field from the Users model and display those values under Consolidated model bill_no_bill field. With the above code I can only take the employee_name values from the Users model to the emp_name of Consolidated model and the same value is getting displayed in the bill_no_bill field. Please help me find any ways for this problem as I am new to this Django. Basically its needs to be a API which operates GET method.
You are getting same value because you are using Foreign Key to the user model for both emp_name and bill_no_bill. Foreign keys are used for many to one relations between models.
See: https://docs.djangoproject.com/en/4.0/topics/db/examples/many_to_one/
So your Consolidated Model should be:
class Consolidated(models.Model):
employee=models.ForeignKey(Users,on_delete=CASCADE)
project=models.ForeignKey(Project,on_delete=CASCADE)
custom_name=models.ForeignKey(Client,on_delete=CASCADE)
cons_date=models.ForeignKey(Add_Timelog,on_delete=CASCADE)
To get the values of these fields you can use Nested Objects Representation in DRF serializers.
So the consolidated serializer becomes:
class ConsolidatedSerializer(serializers.ModelSerializer):
employee = UserSerializer()
class Meta:
model = Consolidated
fields = ['employee', 'project', 'custom', 'date']
The bill_no_bill field would be part of the returned object in viewset.
Alternatively you can override the to_representation method of serializer or SerializerMethodField

Django REST ModelSerializer --- General Question

I am working through a tutorial that includes the building of an articles app. I have an Article model that I am serializing and I am curious about why I need to explicitly set certain fields when using a ModelSerializer.
Here is my model:
from django.db import models
from core.models import TimestampedModel
class Article(TimestampedModel):
slug = models.SlugField(db_index=True, max_length=255, unique=True)
title = models.CharField(db_index=True, max_length=255)
description = models.TextField()
body = models.TextField()
author = models.ForeignKey('profiles.Profile', on_delete=models.CASCADE, related_name='articles')
def __str__(self):
return self.title
Pretty standard stuff. Next step is to serialize the model data in my serializers.py file:
class ArticleSerializer(serializers.ModelSerializer):
author = ProfileSerializer(read_only=True) # Three fields from the Profile app
description = serializers.CharField(required=False)
slug = serializers.SlugField(required=False)
class Meta:
model = Article
fields = (
'author',
'body',
'createdAt',
'description',
'slug',
'title',
'updatedAt',
)
Specifically, why do I need to explicitly state the author, description, and slug fields if I am using serializers.ModelSerializer and pulling those fields in from my model in my class Meta: below?
Thanks!
In the Django-Rest-Framework documentation, drf-docs/model_serializer/specifying-which-fields-to-include it says:
If you only want a subset of the default fields to be used in a model serializer, you can do so using fields or exclude options, just as you would with a ModelForm. It is strongly recommended that you explicitly set all fields that should be serialized using the fields attribute. This will make it less likely to result in unintentionally exposing data when your models change.
Therefore by using fields = in the Serializer META, you can specify just the needed fields, and not returning vital fields like id, or exessive information like updated and created timestamps.
You can also instead of using fields, use exclude, which again takes in a tuple, but just excludes the fields you don't want.
These are especially useful when your database table contains a lot of information, returning all this information, especially if it is listed, can result in large return JSON's, where the frontend may only use a small percentage of the sent data.
DRF has designed their framework like this to specifically combat these problems.
In my opinion, we should define field in serializer for:
Your api use serializer don't need all data of your models. Then you can limit field can get by serializer. It faster if you have so much data.
You dont want public all field of your model. Example like id
Custom field in serializer like serializers.SerializerMethodField() must define in fields for work
Finally, iF you dont want, you can define serializer without define fields. Its will work normally

N duplicated queries nested model

I've got an Area model allowing sub areas (you might think of it as categories with subcategories). I reached this by nesting one field to self as foreign key.
class Area(models.Model):
area = models.CharField(max_length=120)
parent = models.ForeignKey('self', models.CASCADE, blank=True, null=True, related_name='subarea')
def __str__(self):
return self.area
With the django rest framwork I've manages to get the correct output. The problem is that when I analyze the request with django-toolbar multiple duplicated requests are made (N*Area(parent=None)). I've solved similar issues by using prefetch_related or select_related. But never done it with a nested model. Is there any way to solve this? Or is this design of the model bad?
I manage to serialize the correct output with the following view and
class ListArea(generics.ListCreateAPIView):
serializer_class = AreaSerializer
queryset = Area.objects.prefetch_related('parent').filter(parent=None)
and serializers
class SubAreaSerializer(serializers.ModelSerializer):
class Meta:
model = Area
fields = ('area','id')
class AreaSerializer(serializers.ModelSerializer):
subarea=SubAreaSerializer(many=True)
class Meta:
model = Area
fields = ('area','id','subarea')
Or might those extra calls be due to the browsable API?
Solution
I solved this with help of the following thread Django: Does prefetch_related() follow reverse relationship lookup?
Instead of
queryset = Area.objects.prefetch_related('parent').filter(parent=None)
I should use
queryset = Area.objects.prefetch_related('parent').prefetch_related('subarea')

Django rest framework, seriialize multiple fields at once

Suppose you want to include 5 images and the total image count for your restaurant when you serialize a restaurant.
You can query DB for each field in separate method,
but it would be beneficial to work in one method to utilize a QuerySet.
Is there a way to serialize multiple fields at once in DRF?
If all of these fall under one model then building a simple model serializer will account for them all. Take for instance:
# Model
class Restaurant(models.Model):
#property
def image_count(self):
queryset = apps.get_model('app_label', 'Image')
count = queryset.objects.filter(restaurant=self).count()
return count
class Image(models.Model):
restaurant = models.ForeignKey(Restaurant, related_name="image_set")
image = models.ImageField()
Serializers.py
class RestaurantSerializer(serializers.ModelSerializer):
class Meta:
model = Restaurant
fields = ['image_count', ]
# Property allows something as such to be serialized, the method could also just use the related name.
If you want to further serialize the Image object you must read http://www.django-rest-framework.org/api-guide/relations/

How to order a Django Rest Framework ManyToMany related field?

I have a Django Rest Framework application with the following (simplified) models.py:
class Photo(models.Model):
...
class Album(models.Model):
...
photos = models.ManyToManyField(Photo, through='PhotoInAlbum', related_name='albums')
class PhotoInAlbum(models.Model):
photo = models.ForeignKey(Photo)
album = models.ForeignKey(Album)
order = models.IntegerField()
class Meta:
ordering = ['album', 'order']
And in my serializers.py, I have the following:
class AlbumSerializer(serializers.ModelSerializer):
...
photos = serializers.PrimaryKeyRelatedField('photos', many=True)
My question is, how can I have AlbumSerializer return the photos ordered by the field order?
The best solution to customise the queryset is using serializers.SerializerMethodField, but what shezi's reply is not exactly right. You need to return serializer.data from SerializerMethodField. So the solution should be like this:
class PhotoInAlbumSerializer(serialisers.ModelSerializer):
class Meta:
model = PhotoInAlbum
class AlbumSerializer(serializers.ModelSerializer):
# ...
photos = serializers.SerializerMethodField('get_photos_list')
def get_photos_list(self, instance):
photos = PhotoInAlbum.objects\
.filter(album_id=instance.id)\
.order_by('order')\
.values_list('photo_id', flat=True)
return PhotoInAlbumSerializer(photos, many=True, context=self.context).data
It looks as if the RelatedManager that handles the relationship for ManyToManyFields does not respect ordering on the through model.
Since you cannot easily add an ordering parameter to the serializer field, the easiest way to achieve ordering is by using a serializer method:
class AlbumSerializer(serializers.modelSerializer):
# ...
photos = serializers.SerializerMethodField('get_photos_list')
def get_photos_list(self, instance):
return PhotoInAlbum.objects\
.filter(album_id=instance.id)\
.order_by('order')\
.values_list('photo_id', flat=True)
Generally, the easiest way is to do this in your AlbumView or AlbumViewSet.
You can do this by filtering - in this case you should define a get_queryset method in your AlbumViewSet.
Anyway, this is a good solution as long as you only GET the data. If you want to have POST and PUT methods working with ordering the photos, you can do it in two ways:
stay with ManyToMany relation - patch the create method in AlbumViewSet and __create_items and restore_object method in AlbumSerializer
or
replace it with something more sophisticated - use django-sortedm2m field.
Note that the second solution does not mess with AlbumViewSet (and even AlbumSerializer!) - ordering logic stays in the relation field code.