diff --git a/docs/topics/api/overview.rst b/docs/topics/api/overview.rst index a81106b573..7b8a8a133c 100644 --- a/docs/topics/api/overview.rst +++ b/docs/topics/api/overview.rst @@ -279,3 +279,4 @@ v4 API changelog On addons-dev and addons stage enviroments the previous behavior is available as `api/v4dev`. The `v4dev` api is not available on AMO production server. https://github.com/mozilla/addons-server/issues/9467 * 2018-10-04: added ``is_strict_compatibility_enabled`` to discovery API ``addons.current_version`` object. This change was also backported to the `v3` API. https://github.com/mozilla/addons-server/issues/9520 +* 2018-10-04: added ``is_deleted`` to the ratings API. This change was also backported to the `v3` API. https://github.com/mozilla/addons-server/issues/9371 diff --git a/docs/topics/api/ratings.rst b/docs/topics/api/ratings.rst index 73592a0f31..5109782958 100644 --- a/docs/topics/api/ratings.rst +++ b/docs/topics/api/ratings.rst @@ -64,6 +64,13 @@ Detail This endpoint allows you to fetch a rating by its id. +.. note:: + + Users with ``Addons:Edit`` permission will be able to see deleted ratings. + Use the ``is_deleted`` property to distinguish those from normal ratings + everyone is able to see. + + .. http:get:: /api/v4/ratings/rating/(int:id)/ .. _rating-detail-object: @@ -71,6 +78,7 @@ This endpoint allows you to fetch a rating by its id. :>json int id: The rating id. :>json object addon: A simplified :ref:`add-on ` object that contains only a few properties: ``id``, ``name``, ``icon_url`` and ``slug``. :>json string|null body: The text of the rating. + :>json boolean is_deleted: Boolean indicating whether the rating has been deleted or not. :>json boolean is_latest: Boolean indicating whether the rating is the latest posted by the user on the same add-on. :>json int previous_count: The number of ratings posted by the user on the same add-on before this one. :>json int score: The score the user gave as part of the rating. diff --git a/src/olympia/ratings/serializers.py b/src/olympia/ratings/serializers.py index c0d45f1ebb..10365c5b2f 100644 --- a/src/olympia/ratings/serializers.py +++ b/src/olympia/ratings/serializers.py @@ -28,6 +28,7 @@ class RatingAddonSerializer(SimpleAddonSerializer): class BaseRatingSerializer(serializers.ModelSerializer): addon = RatingAddonSerializer(read_only=True) body = serializers.CharField(allow_null=True, required=False) + is_deleted = serializers.BooleanField(read_only=True, source='deleted') is_developer_reply = serializers.SerializerMethodField() is_latest = serializers.BooleanField(read_only=True) previous_count = serializers.IntegerField(read_only=True) @@ -35,8 +36,8 @@ class BaseRatingSerializer(serializers.ModelSerializer): class Meta: model = Rating - fields = ('id', 'addon', 'body', 'created', 'is_developer_reply', - 'is_latest', 'previous_count', 'user') + fields = ('id', 'addon', 'body', 'created', 'is_deleted', + 'is_developer_reply', 'is_latest', 'previous_count', 'user') def __init__(self, *args, **kwargs): super(BaseRatingSerializer, self).__init__(*args, **kwargs) diff --git a/src/olympia/ratings/tests/test_serializers.py b/src/olympia/ratings/tests/test_serializers.py index ef1823eb50..c9980c049f 100644 --- a/src/olympia/ratings/tests/test_serializers.py +++ b/src/olympia/ratings/tests/test_serializers.py @@ -45,6 +45,7 @@ class TestBaseRatingSerializer(TestCase): assert result['body'] == unicode(self.rating.body) assert result['created'] == ( self.rating.created.replace(microsecond=0).isoformat() + 'Z') + assert result['is_deleted'] is False assert result['previous_count'] == int(self.rating.previous_count) assert result['is_developer_reply'] is False assert result['is_latest'] == self.rating.is_latest @@ -67,6 +68,46 @@ class TestBaseRatingSerializer(TestCase): # Check the default, when DRF_API_GATES['ratings-title-shim'] isn't set assert 'title' not in result + def test_deleted_rating_but_view_allowing_it_to_be_shown(self): + # We don't need to change self.view.should_access_deleted_ratings + # because the serializer is not fetching the rating itself, it's just + # serializing whatever instance we passed. + addon = addon_factory() + self.view.get_addon_object.return_value = addon + self.rating = Rating.objects.create( + addon=addon, user=self.user, rating=4, + version=addon.current_version, body=u'This is my rëview. Like ît?') + + self.rating.delete() + result = self.serialize() + + assert result['id'] == self.rating.pk + assert result['addon'] == { + 'id': addon.pk, + 'slug': addon.slug, + 'name': {'en-US': addon.name}, + 'icon_url': absolutify(addon.get_icon_url(64)), + } + assert result['body'] == unicode(self.rating.body) + assert result['created'] == ( + self.rating.created.replace(microsecond=0).isoformat() + 'Z') + assert result['is_deleted'] is True + assert result['previous_count'] == int(self.rating.previous_count) + assert result['is_developer_reply'] is False + assert result['is_latest'] == self.rating.is_latest + assert result['score'] == int(self.rating.rating) + assert result['reply'] is None + assert result['user'] == { + 'id': self.user.pk, + 'name': unicode(self.user.name), + 'url': None, + 'username': self.user.username, + } + assert result['version'] == { + 'id': self.rating.version.id, + 'version': self.rating.version.version + } + @override_settings(DRF_API_GATES={None: ('ratings-rating-shim',)}) def test_ratings_score_is_rating_with_gate(self): addon = addon_factory() @@ -152,6 +193,7 @@ class TestBaseRatingSerializer(TestCase): assert data['body'] == unicode(reply.body) assert data['created'] == ( reply.created.replace(microsecond=0).isoformat() + 'Z') + assert data['is_deleted'] is False assert data['is_developer_reply'] is True assert data['user'] == { 'id': reply_user.pk, @@ -219,13 +261,14 @@ class TestBaseRatingSerializer(TestCase): reply_user = user_factory() addon = addon_factory(users=[reply_user]) self.view.get_addon_object.return_value = addon - self.view.should_access_deleted_reviews = True + self.view.should_access_deleted_ratings = True self.rating = Rating.objects.create( addon=addon, user=self.user, version=addon.current_version, body=u'This is my rëview. Like ît ?') reply = Rating.objects.create( addon=addon, user=reply_user, version=addon.current_version, body=u'Thîs is a reply.', reply_to=self.rating) + reply.delete() result = self.serialize() @@ -236,6 +279,7 @@ class TestBaseRatingSerializer(TestCase): assert result['reply']['body'] == unicode(reply.body) assert result['reply']['created'] == ( reply.created.replace(microsecond=0).isoformat() + 'Z') + assert result['reply']['is_deleted'] is True assert result['reply']['user'] == { 'id': reply_user.pk, 'name': unicode(reply_user.name),