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.
Related
The problem is I have a 'details' field which should render into a nested relationship with it's parent serializer. I have tried a bunch of stuff and nothing seems to be working.
Here's my models:
class BusinessOrderModel(OrderToModel):
reference = models.IntegerField()
business_num = models.ForeignKey('BusinessModel', on_delete=models.CASCADE)
def __str__(self):
return str(self.reference)
class BusinessModel(models.Model):
Business_num = models.IntegerField(primary_key=True)
def __str__(self):
return str(self.Business_num)
class DetailModel(models.Model):
id = models.AutoField(primary_key=True)
detail = models.TextField()
order = models.ForeignKey('BusinessOrderModel', on_delete=models.CASCADE)
and here's my serializers which aren't working:
class DetailSerializer(serializers.ModelSerializer):
class Meta:
model = DetailModel
fields = ('id', 'detail')
class BusinessOrderSerializer(serializers.ModelSerializer):
details = DetailSerializer(many=True)
class Meta:
model = BusinessOrderModel
fields = ('reference', 'business_num', 'details')
I've tried many different things but I get this error:
Got AttributeError when attempting to get a value for field details
on serializer BusinessOrderSerializer. The serializer field might be
named incorrectly and not match any attribute or key on the
BusinessOrderModel instance. Original exception text was:
'BusinessOrderModel' object has no attribute 'details'.
Any help is much appreciated.
Thank you very much.
Using details to lookup reverse relationships only works if you set it as the related_name. The default for BusinessOrderModel to DetailModel will be detailmodel_set.
To make it accessible by calling details you should make this change:
class DetailModel(models.Model):
id = models.AutoField(primary_key=True)
detail = models.TextField()
order = models.ForeignKey('BusinessOrderModel', related_name="details", on_delete=models.CASCADE)
Now you can use DetailModel.objects.get(id=1).details.all()
You can also customize the query in your serializer:
class BusinessOrderSerializer(serializers.ModelSerializer):
details = SerializerMethodField()
class Meta:
model = BusinessOrderModel
fields = ('reference', 'business_num', 'details')
def get_details(self, obj):
return DetailSerializer(obj.details.filter(), many=True).data
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.
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
Given these models:
class Product(models.Model):
name = CharField(max_length=255)
class Order(models.Model):
product = models.ForeignKey(Product)
user = models.ForeignKey(User)
quantity = models.PositiveIntegerField()
A user can only have a single Order object per product. I would like an API call to show a list of products, with the user's order where available. Is there a way to do that?
The default serialisation lists ALL orders under order_set. I did get a bit ahead with this to filter by user:
class FilteredOrderSerializer(serialisers.ListSerializer):
def to_representation(self, data):
data = data.filter(user=self.context['request'].user)
return super(FilteredOrderSerializer, self).to_representation(data)
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
list_serializer_class = FilteredOrderSerializer
class ProductSerializer(serializers.ModelSerializer):
order_set = OrderSerializer(many=True, read_only=True)
class Meta:
model = Product
So now it only shows the authenticated user's order, but the resulting JSON looks something like
[
{
"name": "prod1",
"order_set": [
{
"quantity": 4
}
],
}
]
i.e. the field is still called order_set and it's a list, while I would like it to be called order and be either an object or null. So I'm not sure where this filtering should take place, nor how to define the field.
Edit: I'm using a simple viewset
class ProductViewSet(view sets.ReadOnlyModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
You need to add related_name field in your orders so you can
product.orders.all()
Then this should do.
class Order(models.Model):
product = models.ForeignKey(Product, related_name='orders')
user = models.ForeignKey(User)
quantity = models.PositiveIntegerField()
class ProductSerializer(serializers.ModelSerializer):
order = serializers.SerializerMethodField()
class Meta:
model = Product
fields = ('name', 'order')
def get_order(self, object):
try:
order = object.orders.get(user=self.context['request'].user)
return SceneDetailSerializer(order).data
except Order.DoesNotExist:
return None
Update: You can try out serializer method field. Not sure if self contains context and user references in it. You need to remove listfield from your order serializer. Let me know if it works.
I'm serialzing a Product model and its comments. Here's my simple code:
class ProductSerializer(serializers.HyperlinkedModelSerializer):
comment_set = CommentSerializer(many=True, read_only=True)
class Meta:
model = Product
fields = [
'title',
'comment_set'
]
class CommentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Comment
fields = [
'text',
]
class Comment(models.Model):
product = models.ForeignKey(Product, null=True, blank=True, db_index=True)
class Product(models.Model):
title = models.CharField(max_length=50)
...
Problem:
If the product has many comments. For example, 500 comments. All 500 of them got serialized.
How to limit the result to a number of my own choosing, like 100 comments?
I've done some research before posting this but only found questions about filtering.
Thank you.
Define a new method on the Product model that returns a query set with a limited number of comments.
Then pass that method as the source of the CommentSerializer inside your ProductSerializer.
class Product(models.Model):
title = models.CharField(max_length=50)
def less_comments(self):
return Comment.objects.all().filter(product=self).order_by("-id")[:100]
Then in the ProductSerializer:
class ProductSerializer(serializers.HyperlinkedModelSerializer):
comment_set = CommentSerializer(many=True, read_only=True, source="less_comments")
PS: Wrote the codes from memory, didn't test them. But should work.
You can write custom ListSerializer and put in CommentSerializer, then create custom field in ProductSerializer, wich source based on default related name:
class LimitedListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.all()[:100]
return super(FilteredListSerializer, self).to_representation(data)
class CommentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
list_serializer_class = LimitedListSerializer
model = Comment
fields = [
'text',]
class Product(serializers.HyperlinkedModelSerializer):
related_comments = CommentSerializer(many=True, read_only=True, source='comment_set')
when you pass many=True, list serrializer will be called.
You'll want to work on the CommentSerializer's queryset to control which ones you keep.
You'll be able to do that by overriding get_queryset. For example, to filter them against the current user. Note that I took this example because it highlights how to use the request's context to filter against:
class CommentSerializer(serializers.HyperlinkedModelSerializer):
def get_queryset(self):
user = self.context['request'].user
queryset = Comment.objects.filter(user=user)
return queryset
class Meta:
model = Comment
fields = [
'text',
]