Home > Net >  How to use _set in SerializerMethod for instance?
How to use _set in SerializerMethod for instance?

Time:06-03

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__'

CodePudding user response:

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',
        )

CodePudding user response:

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