return grouped ratings with addon detail api (#16468)

This commit is contained in:
Andrew Williamson 2021-02-08 15:12:50 +00:00 коммит произвёл GitHub
Родитель 7a3be4cf86
Коммит dd050597d3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 80 добавлений и 13 удалений

Просмотреть файл

@ -136,6 +136,7 @@ This endpoint allows you to fetch a specific add-on by id, slug or guid.
:query string app: Used in conjunction with ``appversion`` below to alter ``current_version`` behaviour. Need to be a valid :ref:`add-on application <addon-detail-application>`.
:query string appversion: Make ``current_version`` return the latest public version of the add-on compatible with the given application version, if possible, otherwise fall back on the generic implementation. Pass the full version as a string, e.g. ``46.0``. Only valid when the ``app`` parameter is also present. Currently only compatible with language packs through the add-on detail API, ignored for other types of add-ons and APIs.
:query string lang: Activate translations in the specific language for that query. (See :ref:`Translated Fields <api-overview-translations>`)
:query boolean show_grouped_ratings: Whether or not to show ratings aggregates in the ``ratings`` object (Use "true"/"1" as truthy values, "0"/"false" as falsy ones).
:>json int id: The add-on id on AMO.
:>json array authors: Array holding information about the authors for the add-on.
:>json int authors[].id: The id for an author.
@ -181,6 +182,12 @@ This endpoint allows you to fetch a specific add-on by id, slug or guid.
:>json string ratings_url: The URL to the user ratings list page for the add-on.
:>json float ratings.average: The average user rating for the add-on.
:>json float ratings.bayesian_average: The bayesian average user rating for the add-on.
:>json object ratings.grouped_counts: Object with aggregate counts for ratings. Only included when ``show_grouped_ratings`` is present in the request.
:>json int ratings.grouped_counts.1: the count of ratings with a score of 1.
:>json int ratings.grouped_counts.2: the count of ratings with a score of 2.
:>json int ratings.grouped_counts.3: the count of ratings with a score of 3.
:>json int ratings.grouped_counts.4: the count of ratings with a score of 4.
:>json int ratings.grouped_counts.5: the count of ratings with a score of 5.
:>json boolean requires_payment: Does the add-on require payment, non-free services or software, or additional hardware.
:>json string review_url: The URL to the reviewer review page for the add-on.
:>json string slug: The add-on slug.

Просмотреть файл

@ -410,6 +410,7 @@ These are `v5` specific changes - `v4` changes apply also.
* 2021-01-28: made ``description_text`` in discovery endpoint a translated field in the response. (It was always localized, we just didn't return it as such). https://github.com/mozilla/addons-server/issues/8712
* 2021-02-04: dropped /shelves/sponsored endpoint https://github.com/mozilla/addons-server/issues/16390
* 2021-02-11: removed Stripe webhook endpoint https://github.com/mozilla/addons-server/issues/16391
* 2021-02-11: added ``show_grouped_ratings`` to addon detail endpoint. https://github.com/mozilla/addons-server/issues/16459
.. _`#11380`: https://github.com/mozilla/addons-server/issues/11380/
.. _`#11379`: https://github.com/mozilla/addons-server/issues/11379/

Просмотреть файл

@ -31,6 +31,7 @@ from olympia.constants.promoted import PROMOTED_GROUPS, RECOMMENDED
from olympia.files.models import File
from olympia.promoted.models import PromotedAddon
from olympia.search.filters import AddonAppVersionQueryParam
from olympia.ratings.utils import get_grouped_ratings
from olympia.users.models import UserProfile
from olympia.versions.models import (
ApplicationsVersions,
@ -505,12 +506,17 @@ class AddonSerializer(serializers.ModelSerializer):
return {str(size): absolutify(get_icon(size)) for size in amo.ADDON_ICON_SIZES}
def get_ratings(self, obj):
return {
ratings = {
'average': obj.average_rating,
'bayesian_average': obj.bayesian_rating,
'count': obj.total_ratings,
'text_count': obj.text_ratings_count,
}
if (request := self.context.get('request', None)) and (
grouped := get_grouped_ratings(request, obj)
):
ratings['grouped_counts'] = grouped
return ratings
def get_is_source_public(self, obj):
return False
@ -742,6 +748,14 @@ class ESAddonSerializer(BaseESSerializer, AddonSerializer):
# to_representation() is called, so it's present on all objects.
return obj._es_meta['score']
def get_ratings(self, obj):
return {
'average': obj.average_rating,
'bayesian_average': obj.bayesian_rating,
'count': obj.total_ratings,
'text_count': obj.text_ratings_count,
}
def to_representation(self, obj):
data = super(ESAddonSerializer, self).to_representation(obj)
request = self.context.get('request')

Просмотреть файл

@ -49,6 +49,7 @@ from olympia.constants.categories import CATEGORIES
from olympia.constants.licenses import LICENSES_BY_BUILTIN
from olympia.constants.promoted import RECOMMENDED
from olympia.files.models import WebextPermission
from olympia.ratings.models import Rating
from olympia.versions.models import (
ApplicationsVersions,
AppVersion,
@ -735,6 +736,17 @@ class AddonSerializerOutputTestMixin(object):
result = self.serialize()
assert 'created' not in result
def test_grouped_ratings(self):
self.addon = addon_factory()
self.request = self.get_request('/', {'show_grouped_ratings': 1})
result = self.serialize()
assert result['ratings']['grouped_counts'] == {1: 0, 2: 0, 3: 0, 4: 0, 5: 0}
Rating.objects.create(addon=self.addon, rating=2, user=user_factory())
Rating.objects.create(addon=self.addon, rating=2, user=user_factory())
Rating.objects.create(addon=self.addon, rating=5, user=user_factory())
result = self.serialize()
assert result['ratings']['grouped_counts'] == {1: 0, 2: 2, 3: 0, 4: 0, 5: 1}
class TestAddonSerializerOutput(AddonSerializerOutputTestMixin, TestCase):
serializer_class = AddonSerializer
@ -1000,6 +1012,13 @@ class TestESAddonSerializerOutput(AddonSerializerOutputTestMixin, ESTestCase):
result = self.serialize()
assert '_score' not in result
def test_grouped_ratings(self):
# as grouped ratings aren't stored in ES, we don't support this
self.addon = addon_factory()
self.request = self.get_request('/', {'show_grouped_ratings': 1})
result = self.serialize()
assert 'grouped_counts' not in result['ratings']
class TestVersionSerializerOutput(TestCase):
def setUp(self):

Просмотреть файл

@ -644,6 +644,24 @@ class TestAddonViewSetDetail(AddonAndVersionViewSetDetailMixin, TestCase):
data = json.loads(force_str(response.content))
assert data == {'detail': 'Invalid "app" parameter.'}
def test_with_grouped_ratings(self):
assert 'grouped_counts' not in self.client.get(self.url).json()['ratings']
response = self.client.get(self.url, {'show_grouped_ratings': 'true'})
assert 'grouped_counts' in response.json()['ratings']
assert response.json()['ratings']['grouped_counts'] == {
'1': 0,
'2': 0,
'3': 0,
'4': 0,
'5': 0,
}
response = self.client.get(self.url, {'show_grouped_ratings': '58.0'})
assert response.status_code == 400
data = json.loads(force_str(response.content))
assert data == {'detail': 'show_grouped_ratings parameter should be a boolean'}
class TestVersionViewSetDetail(AddonAndVersionViewSetDetailMixin, TestCase):
client_class = APITestClient

Просмотреть файл

@ -0,0 +1,16 @@
from rest_framework import serializers
from rest_framework.exceptions import ParseError
from .models import GroupedRating
def get_grouped_ratings(request, addon):
if 'show_grouped_ratings' in request.GET:
try:
show_grouped_ratings = serializers.BooleanField().to_internal_value(
request.GET['show_grouped_ratings']
)
except serializers.ValidationError:
raise ParseError('show_grouped_ratings parameter should be a boolean')
if show_grouped_ratings and addon:
return dict(GroupedRating.get(addon.id))

Просмотреть файл

@ -26,9 +26,10 @@ from olympia.api.permissions import (
from olympia.api.throttling import GranularUserRateThrottle
from olympia.api.utils import is_gate_active
from .models import GroupedRating, Rating, RatingFlag
from .models import Rating, RatingFlag
from .permissions import CanDeleteRatingPermission
from .serializers import RatingFlagSerializer, RatingSerializer, RatingSerializerReply
from .utils import get_grouped_ratings
class RatingThrottle(GranularUserRateThrottle):
@ -226,17 +227,8 @@ class RatingViewSet(AddonChildMixin, ModelViewSet):
def get_paginated_response(self, data):
request = self.request
extra_data = {}
if 'show_grouped_ratings' in request.GET:
try:
show_grouped_ratings = serializers.BooleanField().to_internal_value(
request.GET['show_grouped_ratings']
)
except serializers.ValidationError:
raise ParseError('show_grouped_ratings parameter should be a boolean')
if show_grouped_ratings and self.get_addon_object():
extra_data['grouped_ratings'] = dict(
GroupedRating.get(self.addon_object.id)
)
if grouped_rating := get_grouped_ratings(request, self.get_addon_object()):
extra_data['grouped_ratings'] = grouped_rating
if 'show_permissions_for' in request.GET and is_gate_active(
self.request, 'ratings-can_reply'
):