Skip to content
This repository has been archived by the owner on Mar 15, 2018. It is now read-only.

Commit

Permalink
Added content ratings step to submission API (bug 936130)
Browse files Browse the repository at this point in the history
  • Loading branch information
robhudson committed Nov 26, 2013
1 parent 96826df commit 0293da6
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 26 deletions.
25 changes: 24 additions & 1 deletion docs/api/topics/submission.rst
Expand Up @@ -20,7 +20,9 @@ these steps and the user submitting the app must have accepted the `terms of use
contain all the data. Update the required fields.
4. :ref:`Create a screenshot <screenshot-post-label>`. For listing on the
Firefox Marketplace, at least one screenshot is needed.
5. :ref:`Ask for a review <enable-patch-label>`. All apps need to be reviewed,
5. :ref:`Attach content ratings <content-ratings>`. All apps need content
ratings before being reviewed.
6. :ref:`Ask for a review <enable-patch-label>`. All apps need to be reviewed,
this will add it to the review queue.

Validate
Expand Down Expand Up @@ -268,6 +270,27 @@ Screenshots or videos
:status 204: successfully deleted.
Content ratings
===============
.. note:: Requires authentication and a successfully created app.
.. _content-ratings:
.. http:post:: /api/v1/apps/(int:app_id)/content-ratings/
**Request**
:param submission_id: The submission ID received from IARC.
:type submission_id: string
:param security_code: The security code received from IARC.
:type security_cOde: string

This comment has been minimized.

Copy link
@cvan

cvan Nov 26, 2013

Contributor

c0de? lol

This comment has been minimized.

Copy link
@robhudson

robhudson Nov 26, 2013

Author Member

omg eagle 👀

**Response**
:status 201: successfully assigned content ratings.
:status 400: error processing the form.
Enabling an App
===============
Expand Down
51 changes: 47 additions & 4 deletions mkt/api/tests/test_handlers.py
Expand Up @@ -148,7 +148,6 @@ def test_upsell(self):
eq_(obj['resource_uri'],
reverse('app-detail', kwargs={'pk': upsell.id}))


def test_get(self):
self.create_app()
res = self.client.get(self.get_url)
Expand Down Expand Up @@ -212,7 +211,8 @@ def test_get_privacy_policy(self):
app = self.create_app()
data = self.base_data()
self.client.put(self.get_url, data=json.dumps(data))
res = self.client.get(reverse('app-privacy-policy-detail', args=[app.pk]))
res = self.client.get(reverse('app-privacy-policy-detail',
args=[app.pk]))
eq_(res.json['privacy_policy'], data['privacy_policy'])

def test_get_privacy_policy_slug(self):
Expand Down Expand Up @@ -267,8 +267,8 @@ def test_put_categories_worked(self):
def test_get_content_ratings(self):
rating = ratingsbodies.CLASSIND_12
app = self.create_app()
app.content_ratings.create(
ratings_body=ratingsbodies.CLASSIND.id, rating=rating.id)
app.content_ratings.create(ratings_body=ratingsbodies.CLASSIND.id,
rating=rating.id)
app.save()

res = self.client.get(self.get_url)
Expand All @@ -281,6 +281,49 @@ def test_get_content_ratings(self):
eq_(cr['br']['rating'], rating.name)
eq_(cr['br']['description'], unicode(rating.description))

def test_get_content_descriptors(self):
app = self.create_app()
app.set_descriptors(['has_esrb_blood', 'has_pegi_scary'])

res = self.client.get(self.get_url)
eq_(res.status_code, 200)
data = json.loads(res.content)

eq_(data['content_ratings']['descriptors'],
[{'label': 'esrb-blood', 'name': 'Blood', 'ratings_body': 'esrb'},
{'label': 'pegi-scary', 'name': 'Fear', 'ratings_body': 'pegi'}])

def test_get_interactive_elements(self):
app = self.create_app()
app.set_interactives(['has_social_networking', 'has_shares_info'])

res = self.client.get(self.get_url)
eq_(res.status_code, 200)
data = json.loads(res.content)

eq_(data['content_ratings']['interactive_elements'],
[{'label': 'shares-info', 'name': 'Shares Info'},
{'label': 'social-networking', 'name': 'Social Networking'}])

def test_post_content_ratings(self):
"""Test the @action on AppViewSet to attach the content ratings."""
app = self.create_app()
url = reverse('app-content-ratings', kwargs={'pk': app.pk})
res = self.client.post(url, data=json.dumps(
{'submission_id': 50, 'security_code': 'AB12CD3'}))
eq_(res.status_code, 204)
eq_(res.content, '')

def test_post_content_ratings_bad(self):
"""Test the @action on AppViewSet to attach the content ratings."""
app = self.create_app()
url = reverse('app-content-ratings', kwargs={'pk': app.pk})
# Missing `security_code`.
res = self.client.post(url, data=json.dumps({'submission_id': 50}))
eq_(res.status_code, 400)
eq_(json.loads(res.content),
{'security_code': ['This field is required.']})

def test_dehydrate(self):
app = self.create_app()
res = self.client.put(self.get_url, data=json.dumps(self.base_data()))
Expand Down
6 changes: 4 additions & 2 deletions mkt/submit/api.py
Expand Up @@ -13,8 +13,8 @@
from tastypie.serializers import Serializer

import amo
from amo.decorators import write
from addons.models import Addon, Preview
from amo.decorators import write
from files.models import FileUpload

from mkt.api.authentication import (OAuthAuthentication,
Expand All @@ -25,13 +25,15 @@
OwnerAuthorization)
from mkt.api.base import CORSResource, http_error, MarketplaceModelResource
from mkt.api.forms import NewPackagedForm, PreviewArgsForm, PreviewJSONForm
from mkt.submit.serializers import AppStatusSerializer
from mkt.developers import tasks
from mkt.developers.forms import NewManifestForm, PreviewForm
from mkt.submit.serializers import AppStatusSerializer
from mkt.webapps.models import Webapp


log = commonware.log.getLogger('z.api')


class HttpRequestTooBig(HttpResponse):
status_code = 413

Expand Down
3 changes: 0 additions & 3 deletions mkt/submit/tests/test_api.py
Expand Up @@ -8,13 +8,11 @@
from mock import patch

import amo.tests

from addons.models import AddonUser
from files.models import FileUpload
from users.models import UserProfile

from mkt.api.tests.test_oauth import BaseOAuth, RestOAuth

from mkt.site.fixtures import fixture
from mkt.webapps.models import Webapp

Expand Down Expand Up @@ -214,7 +212,6 @@ def get(self, expected_status=200):
data = json.loads(res.content)
return res, data


def test_verbs(self):
self._allowed_verbs(self.get_url, ['get', 'patch']) # FIXME disallow put

Expand Down
53 changes: 37 additions & 16 deletions mkt/webapps/api.py
@@ -1,42 +1,47 @@
from decimal import Decimal
import json
from decimal import Decimal

from django import forms as django_forms
from django.core.urlresolvers import reverse
from django.http import Http404

import commonware
from rest_framework import (exceptions, permissions, response, serializers,
viewsets)
from tower import ugettext_lazy as _lazy, ungettext as ngettext
status, viewsets)
from rest_framework.decorators import action
from rest_framework.response import Response
from tower import ungettext as ngettext

import amo
from addons.models import (AddonCategory, AddonUpsell, AddonUser, Category,
Preview)
import amo
from amo.utils import no_translation
from files.models import FileUpload, Platform
from market.models import AddonPremium, Price
from lib.metrics import record_action
from market.models import AddonPremium, Price

from mkt.api.authentication import (RestOAuthAuthentication,
RestSharedSecretAuthentication,
RestAnonymousAuthentication)
from mkt.api.authorization import (AllowAppOwner, AllowReviewerReadOnly, AnyOf)
from mkt.api.authentication import (RestAnonymousAuthentication,
RestOAuthAuthentication,
RestSharedSecretAuthentication)
from mkt.api.authorization import AllowAppOwner, AllowReviewerReadOnly, AnyOf
from mkt.api.base import CORSMixin, get_url, SlugOrIdMixin
from mkt.api.exceptions import HttpLegallyUnavailable
from mkt.api.fields import (LargeTextField, ReverseChoiceField,
TranslationSerializerField)
from mkt.constants.features import FeatureProfile
from mkt.developers import tasks
from mkt.developers.forms import IARCGetAppInfoForm
from mkt.purchase.utils import payments_enabled
from mkt.regions import (ALL_REGIONS_WITH_CONTENT_RATINGS, get_region,
REGIONS_DICT)
from mkt.submit.forms import mark_for_rereview
from mkt.webapps.models import (AddonExcludedRegion, AppFeatures,
get_excluded_in, reverse_version, Webapp)

from mkt.constants.features import FeatureProfile

log = commonware.log.getLogger('z.api')


class AppFeaturesSerializer(serializers.ModelSerializer):
class Meta:
model = AppFeatures
Expand Down Expand Up @@ -90,6 +95,7 @@ class SemiSerializerMethodField(serializers.SerializerMethodField):
def field_from_native(self, data, files, field_name, into):
into[field_name] = data.get(field_name, None)


class AppSerializer(serializers.ModelSerializer):

app_type = serializers.ChoiceField(
Expand All @@ -113,7 +119,9 @@ class AppSerializer(serializers.ModelSerializer):
manifest_url = serializers.CharField(source='get_manifest_url',
read_only=True)
name = TranslationSerializerField(required=False)
payment_account = serializers.HyperlinkedRelatedField(view_name='payment-account-detail', source='app_payment_account', required=False)
payment_account = serializers.HyperlinkedRelatedField(
view_name='payment-account-detail', source='app_payment_account',
required=False)
payment_required = serializers.SerializerMethodField(
'get_payment_required')
premium_type = ReverseChoiceField(
Expand Down Expand Up @@ -223,8 +231,8 @@ def get_versions(self, app):
v in app.versions.all())

def get_weekly_downloads(self, app):
if app.public_stats:
return app.weekly_downloads
if app.public_stats:
return app.weekly_downloads

def validate_categories(self, attrs, source):
if not attrs.get('categories'):
Expand Down Expand Up @@ -261,7 +269,6 @@ def save_device_types(self, obj, new_types):
if added_devices and obj.status in amo.WEBAPPS_APPROVED_STATUSES:
mark_for_rereview(obj, added_devices, removed_devices)


def save_categories(self, obj, categories):
before = set(obj.categories.values_list('id', flat=True))
# Add new categories.
Expand Down Expand Up @@ -389,7 +396,8 @@ class AppViewSet(CORSMixin, SlugOrIdMixin, viewsets.ModelViewSet):
serializer_class = AppSerializer
slug_field = 'app_slug'
cors_allowed_methods = ('get', 'put', 'post', 'delete')
permission_classes = [AnyOf(AllowAppOwner, AllowReviewerReadOnly, PublicAppReadOnly)]
permission_classes = [AnyOf(AllowAppOwner, AllowReviewerReadOnly,
PublicAppReadOnly)]
authentication_classes = [RestOAuthAuthentication,
RestSharedSecretAuthentication,
RestAnonymousAuthentication]
Expand Down Expand Up @@ -486,10 +494,23 @@ def list(self, request, *args, **kwargs):
serializer = self.get_pagination_serializer(page)
return response.Response(serializer.data)


def partial_update(self, request, *args, **kwargs):
raise exceptions.MethodNotAllowed('PATCH')

@action()
def content_ratings(self, request, *args, **kwargs):
app = self.get_object()
form = IARCGetAppInfoForm(data=request.DATA)

if form.is_valid():
try:
form.save(app)
return Response(status=status.HTTP_204_NO_CONTENT)
except django_forms.ValidationError:

This comment has been minimized.

Copy link
@cvan

cvan Nov 26, 2013

Contributor

are there other errors being thrown during form.save?

This comment has been minimized.

Copy link
@robhudson

robhudson Nov 26, 2013

Author Member

If anything is amiss in the form.save we throw the ValidationError and add the error to form.errors. But yet, there's potentially errors we don't know about from IARC side we don't know about yet.

This comment has been minimized.

Copy link
@cvan

cvan Nov 26, 2013

Contributor

ah, understood

pass

return Response(form.errors, status=status.HTTP_400_BAD_REQUEST)


class PrivacyPolicyViewSet(CORSMixin, SlugOrIdMixin, viewsets.GenericViewSet):
queryset = Webapp.objects.all()
Expand Down

0 comments on commit 0293da6

Please sign in to comment.