如何计算Django模型中某些字段的平均值并将其发送到rest API?

Posted

技术标签:

【中文标题】如何计算Django模型中某些字段的平均值并将其发送到rest API?【英文标题】:How to calculate average of some field in Django models and send it to rest API? 【发布时间】:2021-10-27 09:13:44 【问题描述】:

我想计算平均评分(在评论模型中)并将其发送到我的 API。

Models.py

from django.db import models
from adminuser.models import Categories
from accounts.models import UserAccount as User
from django.core.validators import MaxValueValidator, MinValueValidator

# Create your models here.
class Gigs(models.Model):
    title = models.CharField(max_length=255)
    category = models.ForeignKey(Categories , on_delete=models.CASCADE)
    price = models.DecimalField(max_digits=6, decimal_places=2)
    details = models.TextField()
    seller = models.ForeignKey(User,default=None, on_delete=models.CASCADE)

class Reviews(models.Model):
    rating = models.SmallIntegerField( default=0,validators=[MaxValueValidator(5),MinValueValidator(1)])
    comment = models.CharField(max_length=500)
    item =  models.ForeignKey(Gigs , on_delete=models.CASCADE)
    buyer = models.ForeignKey(User ,default=None, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

Views.py

from django.shortcuts import render
from .models import Gigs,Reviews
from .serializers import GigsSerializer,ReviewsSerializer
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin, CreateModelMixin , RetrieveModelMixin , DestroyModelMixin, UpdateModelMixin
from rest_framework.permissions import AllowAny
# Create your views here.

#List and create (pk not required)
class GigsListAPI(GenericAPIView, ListModelMixin ):
    def get_queryset(self):
       username = self.kwargs['user']
       return Gigs.objects.filter(seller=username)
    serializer_class = GigsSerializer
    permission_classes = (AllowAny,)

    def get(self, request , *args, **kwargs):
        return self.list(request, *args, **kwargs)
class GigsListCategorywise(GenericAPIView, ListModelMixin ):
    def get_queryset(self):
       SearchedCategory = self.kwargs['category']
       return Gigs.objects.filter(category=SearchedCategory)
    serializer_class = GigsSerializer
    permission_classes = (AllowAny,)

    def get(self, request , *args, **kwargs):
        return self.list(request, *args, **kwargs)

class GigsListAll(GenericAPIView, ListModelMixin ):
    queryset = Gigs.objects.all()
    serializer_class = GigsSerializer
    permission_classes = (AllowAny,)

    def get(self, request , *args, **kwargs):
        return self.list(request, *args, **kwargs)

class GigsCreateAPI(GenericAPIView, CreateModelMixin):
    queryset = Gigs.objects.all()
    serializer_class = GigsSerializer
    permission_classes = (AllowAny,)

    def post(self, request , *args, **kwargs):
        return self.create(request, *args, **kwargs)

# Retrieve, update and delete (pk required)
class RUDGigsAPI(GenericAPIView, RetrieveModelMixin, UpdateModelMixin,  DestroyModelMixin):
    queryset = Gigs.objects.all()
    serializer_class = GigsSerializer
    permission_classes = (AllowAny,)

    def get(self, request , *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)
    
    def put(self, request , *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def put(self, request , *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)
    
    def delete(self, request , *args, **kwargs): 
        pk = kwargs.get('pk')
        p = Gigs.objects.get(id=pk)
        if p.images:
            p.images.delete() 
        return self.destroy(request, *args, **kwargs)



# VIEWS FOR REVIEWS MODEL
class ReviewsListAPI(GenericAPIView, ListModelMixin ):
    def get_queryset(self):
       item = self.kwargs['item']
       return Reviews.objects.filter(item=item)
    serializer_class = ReviewsSerializer
    permission_classes = (AllowAny,)

    def get(self, request , *args, **kwargs):
        return self.list(request, *args, **kwargs)

class ReviewsCreateAPI(GenericAPIView, CreateModelMixin):
    queryset = Reviews.objects.all()
    serializer_class = ReviewsSerializer
    permission_classes = (AllowAny,)

    def post(self, request , *args, **kwargs):
        return self.create(request, *args, **kwargs)

Serializers.py

from rest_framework import serializers
from .models import Gigs, Reviews

class GigsSerializer (serializers.ModelSerializer):
    class Meta:
        model = Gigs
        fields = ['id','title','category','price','details','seller','images']

class ReviewsSerializer (serializers.ModelSerializer):
    class Meta:
        model = Reviews
        fields = ['id','rating','comment','item','buyer','created_at']

我想计算评论表中某些演出或项目的平均评分,然后将其发送到 API。但我很困惑在哪里计算它(models.py 或 views.py),然后如何将它发送到我的 API。

【问题讨论】:

【参考方案1】:

好吧,我将详细解释这一点,平均评分可以视为 Gigs 中的虚拟字段,因此将其放在那里很有意义,所以让我们尝试一下:

class Gigs(models.Model):
    ...

    @property
    def average_rating(self):
        return self.reviews.aggregate(Avg('rating'))['rating_avg']

因此,当您要检索单个 Gig 时,这一切都很好,但问题是如果您需要列表 api 中的平均值,这会产生很多额外的查询(每个 Gig 1 个)。在这种情况下,最好在视图中批量执行,所以:

class GigsListAll(ListModelMixin, GenericAPIView): # you should put the mixin before the main class :D
    serializer_class = GigsSerializer
    permission_classes = (AllowAny,)

    def get_queryset(self):

        return Gigs.objects.all().annotate(_average_rating=Avg('reviews__rating') # pay attention, it was annotated as _average_rating

现在我们要更改模型中的虚拟字段,并检查我们是否预先计算了它,所以:

class Gigs(models.Model):
...

    @property
    def average_rating(self):
        if hasattr(self, '_average_rating'):
            return self._average_rating
        return self.reviews.aggregate(Avg('rating'))

终于在你的序列化器中使用它:

class GigsSerializer (serializers.ModelSerializer):
average_rating = serializers.SerializerMethodField()
def get_average_rating(self, obj):
    return obj.average_rating
class Meta:
    model = Gigs
    fields = ['id','title','category','price','details','seller','images','average_rating']

附言为外键设置相关名称是最佳实践,因此请像这样更改您的 reviews 模型:

class Reviews(models.Model):
    ...
    item = models.ForeignKey(Gigs , on_delete=models.CASCADE, related_name='reviews')

【讨论】:

先生,它给出了一个新错误(FieldError at /seller/GigsAPI/)(无法将关键字“reviews_set”解析为字段。选择有:类别、类别 ID、详细信息、ID、图像、订单、价格、评论、卖家、卖家 ID、标题) 先生,很抱歉我一次又一次地打扰您。但它给出了另一个错误(在序列化程序GigsSerializer 上尝试获取字段average_rating 的值时出现属性错误。序列化程序字段可能命名不正确,并且与Gigs 实例上的任何属性或键都不匹配。原始异常文本是:“Gigs”对象没有“评论”属性。) 另一个错误?先生,请帮助... ("在序列化程序GigsSerializer上尝试获取字段average_rating的值时出现KeyError。\n序列化程序字段可能命名不正确并且不匹配 Gigs 实例上的任何属性或键。\n原始异常文本为:'rating_avg'。") chat.***.com/rooms/236487/room-for-ehsan-and-ruma Ehsan Nouri 请帮我解决这个问题***.com/questions/69346669/…【参考方案2】:

首先给你的外键起一个名字,这样你就可以反转它:

class Reviews(models.Model):
    ...
    item =  models.ForeignKey(Gigs, on_delete=models.CASCADE, related_name='reviews')
    ...

然后你可以在你的序列化器中这样做:

from rest_framework import serializers
from .models import Gigs, Reviews
from django.db.models import Avg

class GigsSerializer (serializers.ModelSerializer):
    class Meta:
        model = Gigs
        fields = ['id','title','category','price','details','seller','images','avg_rating']

    avg_rating = serializers.SerializerMethodField()

    def get_avg_rating(self, ob):
        # reverse lookup on Reviews using item field
        return ob.reviews.all().aggregate(Avg('rating'))['rating__avg']

class ReviewsSerializer (serializers.ModelSerializer):
    class Meta:
        model = Reviews
        fields = ['id','rating','comment','item','buyer','created_at']

【讨论】:

先生,它在 /seller/GigsAPI/ 处给出错误 AttributeError 'Gigs' 对象没有属性 'item' 啊,对不起,你需要命名你的外键,这样你就可以反转它,将更新答案。【参考方案3】:

这是多余的,但我也在这里发布。

对象属性必须使用 SerializerMethodField 进行序列化。 要获取这些类型字段的值,序列化程序会查找名为 get_ 的方法,如果未定义它们,则会引发错误。 get_ 方法必须接受一个对象作为序列化程序用来传入当前序列化对象的参数,以便您可以访问其属性。 在这种情况下,它必须是:

class GigsSerializer(serializers.MethodSerializer):
    average_rating = serializers.SerializerMethodField()

    def get_average_rating(self, obj):
        return obj.average_rating

    class Meta:
        model = Gigs
        fields = ["""your fields here"""]

【讨论】:

以上是关于如何计算Django模型中某些字段的平均值并将其发送到rest API?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 django 的模型中获取一个字段

如何更新 django 模型实例的多个字段?

如何更新 django 模型实例的多个字段?

我如何从模型中获取字段并计算一些字段并将它们传递给我的模板?

如何将计算字段添加到 Django 模型

如何在 Django 管理员中为模型字段使用自定义表单字段?