Nested Serializers in Django rest framework - django

The code
I have these models :
class Activity(models.Model):
is_child_friendly = models.BooleanField()
free_wifi = models.BooleanField()
parking_avilable = models.BooleanField()
class RouteOrdering(models.Model):
route_id = models.ForeignKey(Route, on_delete=models.CASCADE)
activity_id = models.ForeignKey(Activity, on_delete=models.CASCADE, related_name='tracks')
day = models.IntegerField()
order = models.IntegerField()
class Meta:
ordering = ['order']
And these Serializers:
class ActivitySerializer(serializers.ModelSerializer):
class Meta:
model = Activity
class RouteOrderingSerializer(serializers.ModelSerializer):
class Meta:
model = RouteOrdering
fields = ('id','day','order','route_id','activity_id','tracks')
The problem
I want the RouteOrderingSerializer to return also the activity with the same activity_id. For this I thought I should use nested serializers.
What I've tried
I've tried adding a nested serializer like so:
activity = ActivitySerializer( read_only=True)
But this resulted no output in the endpoint. I know this is not the use case presented in the documentation, and this is probably why its noy working.
Ill be glad to see what code will work to get this done :)

I'm not sure why you got an error when you tried the following code. I assumed it was a bug related to the _id ending, but everything works fine when I run it.
class ActivitySerializer(serializers.ModelSerializer):
class Meta:
model = Activity
class RouteOrderingSerializer(serializers.ModelSerializer):
activity_id = ActivitySerializer( read_only=True)
class Meta:
model = RouteOrdering
fields = ('id','day','order','route_id','activity_id','tracks')
While you can, I don't recommend using a name ending in _id for a relation in Django. The issue is that Django internally stores all foreign keys under their <relation_name>_id. This allows various nice things, such as setting <object>.<relation_name>_id to an integer id and saving the object vs setting <object>.<relation_name> to the entire related object (which may require a db lookup to get in the first place). The one place I find this behaviour not intuitive is in serializers, where the default is to represent the relation as "<relation_name>": <id> instead of "<relation_name>_id": <id>, though you can explicitly declare the <relation_name>_id field and either remove or nest the <relation_name> field separately.
If you rename your relations to not have the trailing _id, the following will work:
class ActivitySerializer(serializers.ModelSerializer):
class Meta:
model = Activity
class RouteOrderingSerializer(serializers.ModelSerializer):
activity = ActivitySerializer( read_only=True)
class Meta:
model = RouteOrdering
fields = ('id','day','order','route_id','activity','tracks')
(Note that declaring route instead of route_id would be more in line with the default behaviour, but less clear in my opinion)

Try this:
class ActivitySerializer(serializers.ModelSerializer):
class Meta:
model = Activity
class RouteOrderingSerializer(serializers.ModelSerializer):
activity_id = ActivitySerializer()
class Meta:
model = RouteOrdering
fields = ('id','day','order','route_id','activity_id')

Related

How to use _set in SerializerMethod for instance?

I want to calculate the average rating by using SerializerMethodField().
The error in the following code is AttributeError: 'FeedbackModel' object has no attribute 'aggregate'
I think _set is missing but I don't know where to put it..!
class FeedbackSerializer(serializers.ModelSerializer):
feedback_by_user_profile_pic = serializers.ImageField(source='feedback_by.profile_pic')
average_rating = serializers.SerializerMethodField()
def get_average_rating(self,instance):
return instance.aggregate(average_rating=Avg('rating'))['average_rating']
class Meta:
model = FeedbackModel
fields = ['feedback_text','rating','date','feedback_by_user_profile_pic','average_rating']
Feedback Model
class FeedbackModel(models.Model):
feedback_text = models.CharField(max_length=1000)
rating = models.IntegerField()
date = models.DateField(auto_now=True)
feedback_by = models.ForeignKey(UserModel,on_delete=models.CASCADE)
business_account = models.ForeignKey(BusinessAccountModel,on_delete=models.CASCADE)
class Meta:
db_table = 'feedback'
BusinessAccountModel
class BusinessAccountModel(models.Model):
business_title = models.CharField(max_length=70)
business_description = models.CharField(max_length=500)
status = models.CharField(max_length=100)
note = models.CharField(max_length=200)
user = models.OneToOneField(UserModel,on_delete=models.CASCADE)
class Meta:
db_table = 'wp_business_acc'
BusiAccSerializer
class BusiAccSerializer(serializers.ModelSerializer):
class Meta:
model = BusinessAccountModel
fields = '__all__'
I think you need to add the average_rating field in the BusiAccSerializer, not in the FeedbackSerializer.
First you have to set the related_name attribute in the FeedbackModel.
class FeedbackModel(models.Model):
...
# here I added the `related_name` attribute
business_account = models.ForeignKey(BusinessAccountModel,on_delete=models.CASCADE, related_name="feedbacks")
And then in the BusiAccSerializer,
class BusiAccSerializer(serializers.ModelSerializer):
average_rating = serializers.SerializerMethodField(read_only = True)
def get_average_rating(self, obj):
return obj.feedbacks.aggregate(average_rating = Avg('rating'))['average_rating']
class Meta:
model = BusinessAccountModel
fields = (
'business_title', 'business_description', 'status', 'note', 'user', 'average_rating',
)
First of all, you've indicated that you need an average rating for a business account, but you can not get an average rating for an account without having a concrete business account, so you need to do it in the business account serializer.
David Lu has already answered how to do it in the BusiAccSerializer, but I have something to add:
What you've trying to do is to use a serializer method field to add some aggregated data to the output. This way of solving your problem has a major drawback: when you will try to serialize a list of BusinessAccountModels, the serializer will do a separate database call for each business account and it could be slow. You better need to specify an annotated queryset in your view like this:
BusinessAccountModel.objects.all().annotate(average_rating=Avg('feedbacks__rating'))
Then you will be able to use the result of calculation as a regular field in your serializer:
class BusiAccSerializer(serializers.ModelSerializer):
...
average_rating = serializers.FloatField(read_only=True)
This way there will be no additional database queries done by the serializer.

When don't we use "__all__" in ModelSerializer in Django Rest Framework

This is just my curiosity but I will be very happy if anyone answers my question.
I am using Django Rest Framework but I'm a beginner. In serializers.py, I use ModelSerializer and "all" to fields attribute.
This is an example.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
And then, I just thought
when don't we use "__all__" in serializers.py??
As long as we create models.py in advance, I think we usually use all fields in each Model.
I would like you to teach me when we omit specific fields that come from each Model.
Thank you.
So the second question is a bit harder to explain in a comment:
If we use some fields of all fields in Model, how do we store information of the rest of fields?
Various cases:
Fields with defaults:
class Log(models.Model):
message = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class LogSerializer(serializers.ModelSerializer):
class Meta:
model = Log
fields = ('message',)
For autogenerated, think user profile models via the post_save signal or calculated fields:
class OrderLine(models.Model):
order = models.ForeignKey(Order)
name = models.CharField(max_length=200)
quantity = models.IntegerField()
price = models.DecimalField()
class OrderLineSerializer(serializers.ModelSerializer):
order = serializers.PrimaryKeyRelatedField()
product = serializers.IntegerField()
class Meta:
model = OrderLine
fields = ('quantity', 'product', 'order')
In this case, the product is a primary key for a product. The serializer will have a save method that looks up the product and put it's name and price on the OrderLine. This is standard practice as you cannot reference a product in your orders, else your orders would change if you change (the price of) your product.
And derived from request:
class BlogPost(models.Model):
author = models.ForeignKey(User)
post = models.TextField()
class BlogPostSerializer(serializers.ModelSerializer):
class Meta:
model = BlogPost
fields = ('post',)
def create(self, validated_data):
instance = BlogPost(**validated_data)
instance.author = self.context['request'].user
instance.save()
return instance
This is pretty much the common cases.
There are many cases, but I think the two main ones are:
When you don't want all fields to be returned by the serializer.
When you need some method of the serializer to know its fields. In such case, you should traverse fields array, but it doesn't work if you use __all__, only if you have an actual list of fields.

nested serializer showing null data

nested serializer showing null data
from rest_framework import serializers
from .models import PlayerTable, ChildTable
class ChildTableSerializer(serializers.ModelSerializer):
# x= ChildTable.objects.all().values
class Meta:
model = ChildTable
fields = ('season','goals','fk')
# fields =('fk',)
class PlayerTableSerializer(serializers.ModelSerializer):
player_details = ChildTableSerializer(many=True, read_only=True)
class Meta:
model = PlayerTable
fields = ('player_details',)
please help data getting by serializer is null
what is the field 'player-details'? It's not a field on your PlayerTable model. You need to use the name of the related field. In your case since you have a ForeignKey relationship ChildTable --> PlayerTable and you haven't specified the related_name, it's childtable_set. So if you do this it should work:
class PlayerTableSerializer(serializers.ModelSerializer):
childtable_set = ChildTableSerializer(many=True, read_only=True)
class Meta:
model = PlayerTable
fields = ('childtable_set',)
Alternatively, change your models naming to be more aligned with Django conventions:
class PlayerDetail(models.Model):
player = models.ForeignKey(Player, db_column="fk", related_name="player_details", null=True, blank=True, on_delete=models.CASCADE)
...
class Meta:
managed = False
db_table = "child_table"
class Player(models.Model):
name = models.CharField(db_column="player_name", ...)
class Meta:
db_table = "player_table"
then your serializer would work because the relation is player_details. This also has the advantage that when you do details.player you get the player object (now, you have to do details.fk but that actually doesn't return the foreign key value, it returns the Player object). Also your models have more pythonic names (Player not PlayerTable). Your code will be much more readable.

How to fetch all records from 2 tables

I have 2 models
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
employee_id = models.CharField(max_length=13, unique=True)
class UserRole(models.Model):
employee_id = models.ForeignKey(CustomUser, to_field='employee_id', unique=True, on_delete=models.CASCADE)
supervisor_id = models.CharField(max_length=20, null=True)
and have defined seriallizers for both models
class UserSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = models.CustomUser
class UserRoleSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = models.UserRole
consider i have 5 records in both tables
How to fetch all records from both tables (email, employee_id, supervisor_id )
like, where CustomUser.employee_id = UserRole.employee_id
I tried with
models.CustomUser.objects.select_related('UserRole')
But, not able to fetch records from UserRole table.
UserRole.objects.select_related('employee_id')
-- this is fetching only userRole records
Thanks in Advance
You don't get automatic joins in Django, no need for it because you can just follow the ForeignKey relation on the object (the_object_you_get.its_foreign_key). Now, of course, that will hit the DB twice. if you'd rather avoid that, you can use something like .prefetch_related('employee_id') on whatever queryset you need to use. This will prevent you from hitting the DB multiple times (at the price of one larger initial query of course).
Finally, if you wanna serialize a ForeignKey relation, the answer Shakil gave is pretty comprehensive. Only thing is that you don't necessarily need to set the employee_id field as read_only=True (a bit of a limitation). You can also override the UserRoleSerializer .create() the method so that it calls the .create() method of UserSerializer.
I think from UserRole you want to all information about employe's too. What i am thinking you want to get all details information of employee_id foreign_key relation. This can be done by
UserRole.objects.all()
with the following serializers.
class UserSerializer(serializers.ModelSerializer):
class Meta:
fields = ('email','employee_id',)
model = models.CustomUser
class UserRoleSerializer(serializers.ModelSerializer):
employee_id = UserSerializer()
class Meta:
fields = ('employee_id','supervisor_id',)
model = models.UserRole

Making general Filter in Djando Rest Framework

I want to make a common filter to my models because I need to filter all my objects to return a gap between time_start and time_end, but apparently it doesn't work.
I'm not sure if it's even possible(But I hope so, because it won't by DRY otherwise).
models.py
class Time(models.Model):
time = models.TimeField()
class Meta:
abstract=True
class Mark(Time):
value = models.IntegerField(verbose_name="mark")
teacher = models.CharField(max_length=20)
subject = models.CharField(max_length=20)
serializers.py
class MarkSerializer(serializers.ModelSerializer):
class Meta:
model = Mark
fields = ('id', 'time','value', 'teacher', 'subject')
filers.py
class DataFilter(django_filters.FilterSet):
start_time = django_filters.TimeFilter(name="time", lookup_expr='gte')
end_time = django_filters.TimeFilter(name="time", lookup_expr='lte')
class Meta:
model = Time
fields = ['start_time', 'end_time']
views.py
class MarkViewSet(viewsets.ModelViewSet):
serializer_class = MarkSerializer
queryset = Mark.objects.all()
filter_class = DataFilter
I try to get needed marks through:
127.0.0.1:8000/api/v0/marks/?time_start=11:40:00&time_end=12:00:00
but it returns all the objects that I have not the filtered ones.
Thanks in advance.
You have passed the filter params wrong, it should be the name of the field you described in the filter class DataFilter.
Hit this endpoint in the browser,
127.0.0.1:8000/api/v0/marks/?start_time=11:40:00&end_time=12:00:00