Как вычислить среднее значение некоторого поля в моделях 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"""]