How to dynamically specify fields for nested serialization - django

When working with Django rest-framework, and object relationships, I often encounter situations in which I want to return different fields from a serializer depending on the context in which I'm calling that serializier. To provide an example, lets say I have some models representing artiste and album
class Artiste(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=30)
songs = models.ManyToManyField(Song, related_name='artiste')
albums = models.ManyToManyField(Album, related_name='artiste')
class Album(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=30)
description = models.TextField(blank=True, null=True)
These models being serialized by these serializers, which include field from the many to many relationship
class AlbumSerializer(serializers.ModelSerializer):
artiste = ArtisteSerializer(many=True)
class Meta:
model = Album
fields = ('id','title','description','artiste')
class ArtisteSerializer(serializers.ModelSerializer):
class Meta:
model = Artiste
fields = ('id', 'name', 'albums')
A request to my album endpoint might produce an object like this
"id": "5807bd12-254f-4508-b76f-02dc0740e809",
"title": "Test",
"description": "",
"artiste": [
{
"id": "31f09ef0-50ce-48b1-96a6-a6c234930ce5",
"name": "Alec Benjamin",
"albums": [
"5807bd12-254f-4508-b76f-02dc0740e809"
]
}
]
As you can see, using nested serialization ends up giving me more information that I actually want in this context. Given that I had more data for example, when looking at the information for an artiste, I'd love to have the information regarding all the albums that the artiste has made, but when looking at my album endpoint, I'd rather not have this information be included, especially here where the only album the artiste has made is the one I'm currently looking at anyways. As the system gets larger, this problem gets worse as i include songs, playlists etc
What I want to do, is to be able to specify what serialzier fields I want depending on the context in which I'm calling that serializer, as opposed to defining my serializer once and then always getting the same information even when its not needed.
my current approach to this is to use a SerializerMethodField and adding this to my album serializer
artiste = serializers.SerializerMethodField('get_artiste')
def get_artiste(self, obj):
return [(i.id ,i.name)for i in obj.artiste.all()]
Doing this allows me to explicitly state what I want to return in the context of my album serializer but this makes my artiste field read only, meaning that when trying to create an album, I cannot specify what artiste it belongs to. This issue also seems to be the case with StringRelatedField etc.
What is your approach to doing nested serialization? Is there any way to specify what fields should be returned from serialization in a specific situation without rewriting the serializer or making the field read only?
Sorry for the lengthy, probably stupid question and thanks for your help.

according to documentation serializers classes is very like regular django form classes so you only need to specify which fields you want that serializer to show and it will be done in your example it will be :
class ArtisteSerializer(serializers.ModelSerializer):
class Meta:
model = Artiste
fields = ('id', 'name')
and now this serializer will only show the related 'id' and 'name' of the given artiste model.
and there is much more that you can do here like special fields = '__all__'
which will show all fields of a given model or instead of using fields you can use exclude = ('album',) that just removes these fields from serializer .
also you can use django's models relationship here as well.
update :
to be able to use these serializers in different endpoints it is better to use python class inheritance create a base serializer and use different attributes on childs
class BaseArtisteSerializer(serializers.ModelSerializer):
class Meta:
model = Artiste
fields = ('id', 'name', 'artist')
class ArtisteSerializer(BaseArtisteSerializer):
class Meta(BaseArtisteSerializer.Meta):
fields = ('id', 'name')

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

usage of standard backward relation manager in serilizers

Question is about using standard Django backward related manager name in DRF.
I have following serializer
class ExtraUserSerializer(serializers.ModelSerializer):
boatmodel_set = serializers.PrimaryKeyRelatedField(many=True,
queryset=BoatModel.objects.all())
class Meta:
model = get_user_model()
fields = ("id", "username", 'boatmodel_set', )
This serializer represents primary model ExtraUser and boat_model set represents backward relationship to secondary model BoatModel. Related name “boatmodel_set” chosen simply because main core Django site uses this standard “secondarymodel_set” conventional backward related manager name so that in DRF part I had to use related_name = “ boatmodel_set” as well in order to not change code in the main part.
Question is - is it possible to keep related_name = “ boatmodel_set” but represent it in rendered json as , for example “boats”??
Thank you
Yes, you can just specify the source= parameter [drf-doc], and name the field differently, like:
class ExtraUserSerializer(serializers.ModelSerializer):
boats = serializers.PrimaryKeyRelatedField(
many=True,
queryset=BoatModel.objects.all(),
source='boatmodel_set'
)
class Meta:
model = get_user_model()
fields = ('id', 'username', 'boats')
Here the JSON-side will contain "boats": ..., whereas the model side will still use myuser.boatmodel_set.all().

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

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/

Django Rest framework serializer exclude foreign key with depth 2

I have made an api that returns an object as json data. I Am using the django-rest-framework and its serializer. Using the resources (ModelResource) I excluded some fields, like a property called 'owner'. One of the fields is a foreignkey to itselve. I want to show this field in the api (so I use depth=2), but I want to exclude the same fields as I excluded in the object returning.
Is there a nice way to do this (I have tried several things without the wanted result).
This is my (simplified) code:
in models.py:
class MyObject(models.Model):
name = models.CharField(max_length=256, blank=True)
parent = models.ForeignKey('self', blank=True, null=True, default=None)
and_some_otherfields = models.otherFields(....)
owner = models.ForeignKey(User, null=True, blank=True, related_name='myobject_owner')
in resource.py:
class MyObjectResource(ModelResource):
model = MyObject
exclude = ('owner','and some other fields',)
and in the view used to return the object it returns this:
data = Serializer(depth=2).serialize(my_object)
return Response(status.HTTP_200_OK, data)
In the response it leaves out the exclude fields (as I wanted and expected).
but in the field parent, the parent myobject with all fields I want to hide.
I Am looking for a way to indicate that for this parent object, the serializer should use the same Resource, or add the secundary fields to the exclude list....
If I use depth =1 it only shows whether it has a parent ([]), or null if not, and i do need to know at least the parent's ID.
Ah, i just found it:
I need to add in the resource for all fields I want to show what resource....
fields = ('name', ("parent","MyObjectResource") , 'and all the other fields you want to see as well...')
I found it here: google groups forum question
You can skip the exlude, it ignores it, and just add the fields you want to show, you do not have to define them, unless you need to indicate what resource to use.
So following is the final code of the resource.py part:
class MyObjectResource(ModelResource):
model = MyObject
fields = ('name', ("parent","MyObjectResource"), 'and all the other fields you want to see as well...')
Here is how an other solution could be.
class ProToPicturesSerial(serializers.ModelSerializer):
pro_pictures = PictureSerializer(many=True)
pro_videos = VideoSerializer(many=True)
city_pro = CitySerializer(many=True)
class Meta:
model = Province
fields = ('id', 'name', 'intro', 'description', 'display_url', 'pro_pictures', 'pro_videos', 'city_pro')