Как вычислить среднее значение некоторого поля в моделях Dango и отправить его в rest API?

Я хочу подсчитать среднее значение оценок (в модели Reviews) и отправить его в мой 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

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

Я хочу вычислить среднее значение оценок некоторых выступлений или товаров в таблице reviews и затем отправить его в API. но я запутался, где его вычислять (models.py или views.py), а затем как отправить его в мой API.

Сначала дайте внешнему ключу имя, чтобы вы могли его перевернуть:

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

Ну, я собираюсь объяснить это подробнее, средний рейтинг можно рассматривать как виртуальное поле в Gigs, поэтому имеет смысл поместить его туда, так что давайте попробуем это сделать:

class Gigs(models.Model):
    ...

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

Итак, когда вы собираетесь получить один гиг, это хорошо и все, но проблема в том, что если вам нужно среднее значение в list api, это сделает много дополнительных запросов (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']

p.s. Лучшей практикой является установка связанного имени для внешних ключей, поэтому измените вашу модель reviews следующим образом:

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

Это излишне, но я размещаю это также здесь.

Свойства объектов должны быть сериализованы с помощью 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"""]
Вернуться на верх