fix up links, and changes in output
This commit is contained in:
Родитель
d621458831
Коммит
ab798c3e35
|
@ -1,5 +1,4 @@
|
|||
from functools import partial
|
||||
from mkt.api.base import CompatRelatedField
|
||||
|
||||
from rest_framework import fields, serializers
|
||||
|
||||
|
|
|
@ -196,7 +196,7 @@ class TestInstalled(BaseOAuth):
|
|||
self._allowed_verbs(self.list_url, ('get'))
|
||||
|
||||
def test_not_allowed(self):
|
||||
eq_(self.anon.get(self.list_url).status_code, 401)
|
||||
eq_(self.anon.get(self.list_url).status_code, 403)
|
||||
|
||||
def test_installed(self):
|
||||
ins = Installed.objects.create(user=self.user, addon_id=337141)
|
||||
|
@ -204,7 +204,7 @@ class TestInstalled(BaseOAuth):
|
|||
eq_(res.status_code, 200, res.content)
|
||||
data = json.loads(res.content)
|
||||
eq_(data['meta']['total_count'], 1)
|
||||
eq_(data['objects'][0]['id'], str(ins.addon.pk))
|
||||
eq_(data['objects'][0]['id'], ins.addon.pk)
|
||||
|
||||
def not_there(self):
|
||||
res = self.client.get(self.list_url)
|
||||
|
|
|
@ -27,7 +27,6 @@ class BaseAPI(TestCase):
|
|||
res.status_code)
|
||||
assert res.status_code in (401, 403, 405), msg
|
||||
|
||||
|
||||
def get_error(self, response):
|
||||
return json.loads(response.content)['error_message']
|
||||
|
||||
|
|
|
@ -12,7 +12,9 @@ from tastypie.exceptions import ImmediateHttpResponse
|
|||
|
||||
from access.models import Group, GroupUser
|
||||
from addons.models import AddonUser
|
||||
from amo.helpers import absolutify
|
||||
from amo.tests import app_factory, TestCase
|
||||
from amo.urlresolvers import reverse
|
||||
from test_utils import RequestFactory
|
||||
from users.models import UserProfile
|
||||
|
||||
|
@ -95,9 +97,8 @@ class TestOAuthAuthentication(TestCase):
|
|||
user=self.profile.user)
|
||||
|
||||
def call(self, client=None):
|
||||
url = ('api_dispatch_list', {'resource_name': 'app'})
|
||||
client = client or OAuthClient(self.access)
|
||||
url = client.get_absolute_url(url)
|
||||
url = absolutify(reverse('app-list'))
|
||||
return RequestFactory().get(url,
|
||||
HTTP_HOST='testserver',
|
||||
HTTP_AUTHORIZATION=client.sign('GET', url)[1]['Authorization'])
|
||||
|
|
|
@ -203,7 +203,7 @@ class Test3LeggedOAuthFlow(TestCase):
|
|||
return url, headers['Authorization']
|
||||
|
||||
def test_use_access_token(self):
|
||||
url = get_absolute_url(('api_dispatch_list', {'resource_name': 'app'}))
|
||||
url = absolutify(reverse('app-list'))
|
||||
t = Token.generate_new(ACCESS_TOKEN, creds=self.access,
|
||||
user=self.user2)
|
||||
url, auth_header = self._oauth_request_info(
|
||||
|
@ -217,7 +217,7 @@ class Test3LeggedOAuthFlow(TestCase):
|
|||
eq_(req.user, self.user2)
|
||||
|
||||
def test_bad_access_token(self):
|
||||
url = get_absolute_url(('api_dispatch_list', {'resource_name': 'app'}))
|
||||
url = absolutify(reverse('app-list'))
|
||||
Token.generate_new(ACCESS_TOKEN, creds=self.access, user=self.user2)
|
||||
url, auth_header = self._oauth_request_info(
|
||||
url, client_key=self.access.key,
|
||||
|
|
|
@ -23,7 +23,7 @@ from mkt.api.authorization import (AllowAppOwner, GroupPermission,
|
|||
switch)
|
||||
from mkt.api.authentication import (RestOAuthAuthentication,
|
||||
RestSharedSecretAuthentication)
|
||||
from mkt.api.base import AppViewSet, CompatRelatedField
|
||||
from mkt.api.base import AppViewSet
|
||||
from mkt.constants.payments import PAYMENT_STATUSES
|
||||
from mkt.developers.forms_payments import (BangoPaymentAccountForm,
|
||||
PaymentCheckForm)
|
||||
|
@ -133,9 +133,7 @@ class PaymentViewSet(RetrieveModelMixin, GenericViewSet):
|
|||
|
||||
|
||||
class UpsellSerializer(HyperlinkedModelSerializer):
|
||||
free = premium = CompatRelatedField(
|
||||
tastypie={'resource_name': 'app', 'api_name': 'apps'},
|
||||
view_name='api_dispatch_detail')
|
||||
free = premium = HyperlinkedRelatedField(view_name='app-detail')
|
||||
|
||||
class Meta:
|
||||
model = AddonUpsell
|
||||
|
@ -206,13 +204,9 @@ class AddonPaymentAccountPermission(BasePermission):
|
|||
|
||||
|
||||
class AddonPaymentAccountSerializer(HyperlinkedModelSerializer):
|
||||
addon = CompatRelatedField(
|
||||
source='addon',
|
||||
tastypie={'resource_name': 'app', 'api_name': 'apps'},
|
||||
view_name='api_dispatch_detail')
|
||||
addon = HyperlinkedRelatedField(view_name='app-detail')
|
||||
payment_account = HyperlinkedRelatedField(
|
||||
view_name='payment-account-detail')
|
||||
|
||||
class Meta:
|
||||
model = AddonPaymentAccount
|
||||
fields = ('addon', 'payment_account', 'provider',
|
||||
|
|
|
@ -54,7 +54,7 @@ payment_data.update(bank_data)
|
|||
class UpsellCase(TestCase):
|
||||
|
||||
def url(self, app):
|
||||
return get_absolute_url(get_url('app', pk=app.pk), absolute=False)
|
||||
return reverse('app-detail', kwargs={'pk': app.pk})
|
||||
|
||||
def setUp(self):
|
||||
self.free = Webapp.objects.get(pk=337141)
|
||||
|
|
|
@ -0,0 +1,299 @@
|
|||
from django.conf.urls import url
|
||||
|
||||
import commonware.log
|
||||
from tastypie import fields, http, paginator
|
||||
from tastypie.authorization import Authorization
|
||||
from tastypie.bundle import Bundle
|
||||
from tastypie.utils import trailing_slash
|
||||
|
||||
import amo
|
||||
from amo import get_user
|
||||
from lib.metrics import record_action
|
||||
|
||||
from mkt.api.authentication import (OptionalOAuthAuthentication,
|
||||
SharedSecretAuthentication)
|
||||
from mkt.api.authorization import (AnonymousReadOnlyAuthorization,
|
||||
AppOwnerAuthorization,
|
||||
OwnerAuthorization,
|
||||
PermissionAuthorization)
|
||||
from mkt.api.base import (CompatToOneField, CORSResource, http_error,
|
||||
MarketplaceModelResource)
|
||||
from mkt.ratings.forms import ReviewForm
|
||||
from mkt.regions import get_region, REGIONS_DICT
|
||||
from mkt.webapps.models import Webapp
|
||||
from reviews.models import Review, ReviewFlag
|
||||
|
||||
log = commonware.log.getLogger('z.api')
|
||||
|
||||
|
||||
class RatingPaginator(paginator.Paginator):
|
||||
def get_count(self):
|
||||
try:
|
||||
r = self.objects[0]
|
||||
except IndexError:
|
||||
return 0
|
||||
return r.addon.total_reviews
|
||||
|
||||
|
||||
class RatingResource(CORSResource, MarketplaceModelResource):
|
||||
app = CompatToOneField(None, 'addon', rest='app', readonly=True, null=True)
|
||||
user = CompatToOneField(None, 'user', url_name='account-settings',
|
||||
readonly=True, null=True,
|
||||
extra_fields=('display_name',))
|
||||
version = CompatToOneField(None, 'version', url_name='version-detail',
|
||||
readonly=True, null=True,
|
||||
extra_fields=('version',))
|
||||
report_spam = fields.CharField()
|
||||
|
||||
class Meta(MarketplaceModelResource.Meta):
|
||||
# Unfortunately, the model class name for ratings is "Review".
|
||||
queryset = Review.objects.valid()
|
||||
resource_name = 'rating'
|
||||
list_allowed_methods = ['get', 'post']
|
||||
detail_allowed_methods = ['get', 'put', 'delete']
|
||||
always_return_data = True
|
||||
authentication = (SharedSecretAuthentication(),
|
||||
OptionalOAuthAuthentication())
|
||||
authorization = AnonymousReadOnlyAuthorization()
|
||||
fields = ['rating', 'body', 'modified', 'created']
|
||||
paginator_class = RatingPaginator
|
||||
filtering = {
|
||||
'app': ('exact',),
|
||||
'user': ('exact',),
|
||||
'pk': ('exact',),
|
||||
}
|
||||
|
||||
ordering = ['created']
|
||||
|
||||
def dehydrate(self, bundle):
|
||||
bundle = super(RatingResource, self).dehydrate(bundle)
|
||||
if bundle.request.amo_user:
|
||||
amo_user = bundle.request.amo_user
|
||||
bundle.data['is_author'] = bundle.obj.user.pk == amo_user.pk
|
||||
bundle.data['has_flagged'] = (not bundle.data['is_author'] and
|
||||
bundle.obj.reviewflag_set.filter(user=amo_user).exists())
|
||||
return bundle
|
||||
|
||||
def dehydrate_report_spam(self, bundle):
|
||||
return self._build_reverse_url(
|
||||
'api_post_flag',
|
||||
kwargs={'api_name': self._meta.api_name,
|
||||
'resource_name': self._meta.resource_name,
|
||||
'review_id': bundle.obj.pk})
|
||||
|
||||
def _review_data(self, request, app, form):
|
||||
data = dict(addon_id=app.id, user_id=request.user.id,
|
||||
ip_address=request.META.get('REMOTE_ADDR', ''))
|
||||
if app.is_packaged:
|
||||
data['version_id'] = app.current_version.id
|
||||
data.update(**form.cleaned_data)
|
||||
return data
|
||||
|
||||
def get_app(self, ident):
|
||||
try:
|
||||
app = Webapp.objects.valid().get(id=ident)
|
||||
except (Webapp.DoesNotExist, ValueError):
|
||||
try:
|
||||
app = Webapp.objects.valid().get(app_slug=ident)
|
||||
except Webapp.DoesNotExist:
|
||||
raise self.non_form_errors([('app', 'Invalid app')])
|
||||
if not app.listed_in(region=REGIONS_DICT[get_region()]):
|
||||
raise self.non_form_errors([('app', 'Not available in this region')])
|
||||
return app
|
||||
|
||||
def build_filters(self, filters=None):
|
||||
"""
|
||||
If `addon__exact` is a filter and its value cannot be coerced into an
|
||||
int, assume that it's a slug lookup.
|
||||
|
||||
Run the query necessary to determine the app, and substitute the slug
|
||||
with the PK in the filter so tastypie will continue doing its thing.
|
||||
"""
|
||||
built = super(RatingResource, self).build_filters(filters)
|
||||
if 'addon__exact' in built:
|
||||
try:
|
||||
int(built['addon__exact'])
|
||||
except ValueError:
|
||||
app = self.get_app(built['addon__exact'])
|
||||
if app:
|
||||
built['addon__exact'] = str(app.pk)
|
||||
|
||||
if built.get('user__exact', None) == 'mine':
|
||||
# This is a cheat. Would prefer /mine/ in the URL.
|
||||
user = get_user()
|
||||
if not user:
|
||||
# You must be logged in to use "mine".
|
||||
raise http_error(http.HttpUnauthorized, 'You must be logged in to access "mine".')
|
||||
|
||||
built['user__exact'] = user.pk
|
||||
return built
|
||||
|
||||
def obj_create(self, bundle, request=None, **kwargs):
|
||||
"""
|
||||
Handle POST requests to the resource. If the data validates, create a
|
||||
new Review from bundle data.
|
||||
"""
|
||||
form = ReviewForm(bundle.data)
|
||||
|
||||
if not form.is_valid():
|
||||
raise self.form_errors(form)
|
||||
|
||||
app = self.get_app(bundle.data['app'])
|
||||
|
||||
# Return 409 if the user has already reviewed this app.
|
||||
qs = self._meta.queryset.filter(addon=app, user=request.user)
|
||||
if app.is_packaged:
|
||||
qs = qs.filter(version_id=bundle.data.get('version',
|
||||
app.current_version.id))
|
||||
if qs.exists():
|
||||
raise http_error(http.HttpConflict, 'You have already reviewed this app.')
|
||||
|
||||
# Return 403 if the user is attempting to review their own app:
|
||||
if app.has_author(request.user):
|
||||
raise http_error(http.HttpForbidden, 'You may not review your own app.')
|
||||
|
||||
# Return 403 if not a free app and the user hasn't purchased it.
|
||||
if app.is_premium() and not app.is_purchased(request.amo_user):
|
||||
raise http_error(
|
||||
http.HttpForbidden,
|
||||
"You may not review paid apps you haven't purchased.")
|
||||
|
||||
bundle.obj = Review.objects.create(**self._review_data(request, app,
|
||||
form))
|
||||
|
||||
amo.log(amo.LOG.ADD_REVIEW, app, bundle.obj)
|
||||
log.debug('[Review:%s] Created by user %s ' %
|
||||
(bundle.obj.id, request.user.id))
|
||||
record_action('new-review', request, {'app-id': app.id})
|
||||
|
||||
return bundle
|
||||
|
||||
def obj_update(self, bundle, request, **kwargs):
|
||||
"""
|
||||
Handle PUT requests to the resource. If authorized and the data
|
||||
validates, update the indicated resource with bundle data.
|
||||
"""
|
||||
obj = self.get_by_resource_or_404(request, **kwargs)
|
||||
if not OwnerAuthorization().is_authorized(request, object=obj):
|
||||
raise http_error(
|
||||
http.HttpForbidden,
|
||||
'You do not have permission to update this review.')
|
||||
|
||||
form = ReviewForm(bundle.data)
|
||||
if not form.is_valid():
|
||||
raise self.form_errors(form)
|
||||
|
||||
if 'app' in bundle.data:
|
||||
error = ('app', "Cannot update a rating's `app`")
|
||||
raise self.non_form_errors([error])
|
||||
|
||||
sup = super(RatingResource, self).obj_update(bundle, request, **kwargs)
|
||||
|
||||
amo.log(amo.LOG.EDIT_REVIEW, bundle.obj.addon, bundle.obj)
|
||||
log.debug('[Review:%s] Edited by %s' % (bundle.obj.id, request.user.id))
|
||||
|
||||
return sup
|
||||
|
||||
def obj_delete(self, request, **kwargs):
|
||||
obj = self.get_by_resource_or_404(request, **kwargs)
|
||||
if not (AppOwnerAuthorization().is_authorized(request,
|
||||
object=obj.addon)
|
||||
or OwnerAuthorization().is_authorized(request, object=obj)
|
||||
or PermissionAuthorization('Users',
|
||||
'Edit').is_authorized(request)
|
||||
or PermissionAuthorization('Addons',
|
||||
'Edit').is_authorized(request)):
|
||||
raise http_error(
|
||||
http.HttpForbidden,
|
||||
'You do not have permission to delete this review.')
|
||||
|
||||
log.info('Rating %s deleted from addon %s' % (obj.pk, obj.addon.pk))
|
||||
return super(RatingResource, self).obj_delete(request, **kwargs)
|
||||
|
||||
def get_object_list(self, request):
|
||||
qs = MarketplaceModelResource.get_object_list(self, request)
|
||||
# Mature regions show only reviews from within that region.
|
||||
if not request.REGION.adolescent:
|
||||
qs = qs.filter(client_data__region=request.REGION.id)
|
||||
return qs
|
||||
|
||||
def alter_list_data_to_serialize(self, request, data):
|
||||
if 'app' in request.GET:
|
||||
addon = self.get_app(request.GET['app'])
|
||||
data['info'] = {
|
||||
'average': addon.average_rating,
|
||||
'slug': addon.app_slug,
|
||||
'current_version': addon.current_version.version
|
||||
}
|
||||
|
||||
filters = dict(addon=addon)
|
||||
if addon.is_packaged:
|
||||
filters['version'] = addon.current_version
|
||||
|
||||
if not request.user.is_anonymous():
|
||||
filters['user'] = request.user
|
||||
existing_review = Review.objects.valid().filter(**filters)
|
||||
if addon.is_premium():
|
||||
can_rate = addon.has_purchased(request.amo_user)
|
||||
else:
|
||||
can_rate = not addon.has_author(request.user)
|
||||
data['user'] = {'can_rate': can_rate,
|
||||
'has_rated': existing_review.exists()}
|
||||
else:
|
||||
data['user'] = None
|
||||
|
||||
return data
|
||||
|
||||
def override_urls(self):
|
||||
# Based on 'nested resource' example in tastypie cookbook.
|
||||
return [
|
||||
url(r'^(?P<resource_name>%s)/(?P<review_id>\w[\w/-]*)/flag%s$' %
|
||||
(self._meta.resource_name, trailing_slash()),
|
||||
self.wrap_view('post_flag'), name='api_post_flag')
|
||||
]
|
||||
|
||||
def post_flag(self, request, **kwargs):
|
||||
return RatingFlagResource().dispatch('list', request,
|
||||
review_id=kwargs['review_id'])
|
||||
|
||||
|
||||
class FireplaceRatingResource(RatingResource):
|
||||
class Meta(RatingResource.Meta):
|
||||
pass
|
||||
|
||||
|
||||
class RatingFlagResource(CORSResource, MarketplaceModelResource):
|
||||
|
||||
class Meta(MarketplaceModelResource.Meta):
|
||||
queryset = ReviewFlag.objects.all()
|
||||
resource_name = 'rating_flag'
|
||||
list_allowed_methods = ['post']
|
||||
detail_allowed_methods = []
|
||||
authentication = (SharedSecretAuthentication(),
|
||||
OptionalOAuthAuthentication())
|
||||
authorization = Authorization()
|
||||
fields = ['review', 'flag', 'note', 'user']
|
||||
|
||||
def get_resource_uri(self, bundle_or_obj):
|
||||
if isinstance(bundle_or_obj, Bundle):
|
||||
obj = bundle_or_obj.obj
|
||||
else:
|
||||
obj = bundle_or_obj
|
||||
|
||||
return '/api/apps/ratings/%s/flag/%s%s' % (obj.review_id, obj.pk,
|
||||
trailing_slash())
|
||||
|
||||
def post_list(self, request, review_id=None, **kwargs):
|
||||
if ReviewFlag.objects.filter(review_id=review_id,
|
||||
user=request.amo_user).exists():
|
||||
return http.HttpConflict()
|
||||
return MarketplaceModelResource.post_list(
|
||||
self, request, review_id=review_id, **kwargs)
|
||||
|
||||
def obj_create(self, bundle, request=None, review_id=None, **kwargs):
|
||||
if 'note' in bundle.data and bundle.data['note'].strip():
|
||||
bundle.data['flag'] = ReviewFlag.OTHER
|
||||
Review.objects.filter(id=review_id).update(editorreview=True)
|
||||
return MarketplaceModelResource.obj_create(
|
||||
self, bundle, request=request, review_id=review_id,
|
||||
user=request.amo_user)
|
|
@ -6,7 +6,7 @@ from rest_framework.exceptions import PermissionDenied
|
|||
from reviews.models import Review, ReviewFlag
|
||||
|
||||
from mkt.account.serializers import AccountSerializer
|
||||
from mkt.api.base import CompatRelatedField
|
||||
from mkt.api.fields import SlugOrPrimaryKeyRelatedField, SplitField
|
||||
from mkt.api.exceptions import Conflict
|
||||
from mkt.regions import get_region, REGIONS_DICT
|
||||
from mkt.versions.api import SimpleVersionSerializer
|
||||
|
@ -14,10 +14,13 @@ from mkt.webapps.models import Webapp
|
|||
|
||||
|
||||
class RatingSerializer(serializers.ModelSerializer):
|
||||
app = CompatRelatedField(tastypie={'resource_name': 'app',
|
||||
'api_name': 'apps'},
|
||||
source='addon',
|
||||
slug_field='app_slug')
|
||||
app = SplitField(
|
||||
SlugOrPrimaryKeyRelatedField(slug_field='app_slug',
|
||||
queryset=Webapp.objects.all(),
|
||||
source='addon'),
|
||||
serializers.HyperlinkedRelatedField(view_name='app-detail',
|
||||
read_only=True,
|
||||
source='addon'))
|
||||
body = serializers.CharField()
|
||||
user = AccountSerializer(read_only=True)
|
||||
report_spam = serializers.SerializerMethodField('get_report_spam_link')
|
||||
|
@ -102,10 +105,10 @@ class RatingSerializer(serializers.ModelSerializer):
|
|||
|
||||
def validate_app(self, attrs, source):
|
||||
if not getattr(self, 'object'):
|
||||
# The CompatRelatedField does part of the job for us, but our
|
||||
# get_app_from_value does extra checks.
|
||||
app = attrs[source]
|
||||
attrs[source] = RatingSerializer.get_app_from_value(app.pk)
|
||||
else:
|
||||
attrs[source] = self.object.addon
|
||||
return attrs
|
||||
|
||||
|
||||
|
|
|
@ -382,8 +382,10 @@ class TestRatingResource(RestOAuth, amo.tests.AMOPaths):
|
|||
def test_update(self):
|
||||
self._create_default_review()
|
||||
new_data = {
|
||||
'app': self.app.id,
|
||||
'body': 'Totally rocking the free web.',
|
||||
'rating': 4
|
||||
'rating': 4,
|
||||
'version': self.app.current_version.id
|
||||
}
|
||||
log_review_id = amo.LOG.EDIT_REVIEW.id
|
||||
eq_(ActivityLog.objects.filter(action=log_review_id).count(), 0)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from mkt.api.base import CompatRelatedField
|
||||
from mkt.webapps.models import Webapp
|
||||
|
||||
|
||||
|
@ -9,8 +8,6 @@ class ReviewingSerializer(serializers.ModelSerializer):
|
|||
model = Webapp
|
||||
fields = ('resource_uri', )
|
||||
|
||||
resource_uri = CompatRelatedField(
|
||||
view_name='api_dispatch_detail', read_only=True,
|
||||
tastypie={'resource_name': 'app',
|
||||
'api_name': 'apps'},
|
||||
source='*')
|
||||
resource_uri = serializers.HyperlinkedRelatedField(view_name='app-detail',
|
||||
read_only=True,
|
||||
source='*')
|
||||
|
|
|
@ -14,10 +14,10 @@ import mkt.regions
|
|||
from access.models import GroupUser
|
||||
from addons.models import Category
|
||||
from amo.tests import ESTestCase
|
||||
from amo.urlresolvers import reverse
|
||||
|
||||
from mkt.api.tests.test_oauth import (BaseOAuth, get_absolute_url, OAuthClient,
|
||||
RestOAuth)
|
||||
from mkt.api.base import get_url, list_url
|
||||
from mkt.api.tests.test_oauth import BaseOAuth, OAuthClient, RestOAuth
|
||||
from mkt.api.base import list_url
|
||||
from mkt.api.models import Access, generate
|
||||
from mkt.constants.features import FeatureProfile
|
||||
from mkt.reviewers.api import ReviewersSearchResource
|
||||
|
@ -67,7 +67,7 @@ class TestReviewing(RestOAuth):
|
|||
res = self.client.get(self.list_url)
|
||||
data = json.loads(res.content)
|
||||
eq_(data['objects'][0]['resource_uri'],
|
||||
get_absolute_url(get_url('app', '337141'), absolute=False))
|
||||
reverse('app-detail', kwargs={'pk': 337141}))
|
||||
|
||||
|
||||
class TestApiReviewer(BaseOAuth, ESTestCase):
|
||||
|
|
|
@ -8,11 +8,13 @@ from django.core.urlresolvers import reverse
|
|||
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
|
||||
|
||||
|
@ -58,7 +60,7 @@ class TestAddValidationHandler(ValidationHandler):
|
|||
obj = FileUpload.objects.get(uuid=content['id'])
|
||||
eq_(obj.user, self.user)
|
||||
|
||||
@patch('mkt.api.resources.tasks.fetch_manifest')
|
||||
@patch('mkt.webapps.api.tasks.fetch_manifest')
|
||||
def test_fetch(self, fetch):
|
||||
self.create()
|
||||
assert fetch.called
|
||||
|
@ -212,6 +214,7 @@ class TestAppStatusHandler(RestOAuth, amo.tests.AMOPaths):
|
|||
data = json.loads(res.content)
|
||||
return res, data
|
||||
|
||||
|
||||
def test_verbs(self):
|
||||
self._allowed_verbs(self.get_url, ['get', 'patch']) # FIXME disallow put
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ from rest_framework.exceptions import ParseError
|
|||
import amo
|
||||
from mkt.api.authorization import (AllowAppOwner, AllowReadOnly, AnyOf,
|
||||
GroupPermission)
|
||||
from mkt.api.base import CompatRelatedField
|
||||
from mkt.constants import APP_FEATURES
|
||||
from mkt.features.api import AppFeaturesSerializer
|
||||
from versions.models import Version
|
||||
|
@ -20,9 +19,8 @@ class SimpleVersionSerializer(serializers.ModelSerializer):
|
|||
|
||||
|
||||
class VersionSerializer(serializers.ModelSerializer):
|
||||
addon = CompatRelatedField(view_name='api_dispatch_detail', read_only=True,
|
||||
tastypie={'resource_name': 'app',
|
||||
'api_name': 'apps'})
|
||||
addon = serializers.HyperlinkedRelatedField(view_name='app-detail',
|
||||
read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Version
|
||||
|
|
|
@ -37,11 +37,8 @@ class TestVersionSerializer(TestCase):
|
|||
ok_(all(k in native for k in added_keys))
|
||||
|
||||
def test_addon(self):
|
||||
eq_(self.native()['app'], reverse('api_dispatch_detail', kwargs={
|
||||
'resource_name': 'app',
|
||||
'api_name': 'apps',
|
||||
'pk': self.app.pk}
|
||||
))
|
||||
eq_(self.native()['app'], reverse('app-detail',
|
||||
kwargs={'pk': self.app.pk}))
|
||||
|
||||
def test_is_current_version(self):
|
||||
old_version = Version.objects.create(addon=self.app, version='0.1')
|
||||
|
@ -87,11 +84,8 @@ class TestVersionViewSet(RestOAuth):
|
|||
eq_(data['developer_name'], version.developer_name)
|
||||
eq_(data['is_current_version'],
|
||||
version == self.app.current_version)
|
||||
eq_(data['app'], reverse('api_dispatch_detail', kwargs={
|
||||
'resource_name': 'app',
|
||||
'api_name': 'apps',
|
||||
'pk': self.app.pk}
|
||||
))
|
||||
eq_(data['app'], reverse('app-detail',
|
||||
kwargs={'pk': self.app.pk}))
|
||||
|
||||
for key in features:
|
||||
ok_(getattr(version.features, 'has_' + key))
|
||||
|
|
|
@ -21,7 +21,7 @@ from mkt.api.authentication import (RestOAuthAuthentication,
|
|||
RestSharedSecretAuthentication,
|
||||
RestAnonymousAuthentication)
|
||||
from mkt.api.authorization import (AllowAppOwner, AllowReviewerReadOnly, AnyOf)
|
||||
from mkt.api.base import (CompatRelatedField, CORSMixin, get_url, SlugOrIdMixin)
|
||||
from mkt.api.base import CORSMixin, get_url, SlugOrIdMixin
|
||||
from mkt.api.exceptions import HttpLegallyUnavailable
|
||||
from mkt.api.fields import (LargeTextField, ReverseChoiceField,
|
||||
TranslationSerializerField)
|
||||
|
@ -113,9 +113,7 @@ 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')
|
||||
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(
|
||||
|
|
|
@ -240,13 +240,11 @@ class Webapp(Addon):
|
|||
|
||||
def get_api_url(self, action=None, api=None, resource=None, pk=False):
|
||||
"""Reverse a URL for the API."""
|
||||
kwargs = {'api_name': api or 'apps',
|
||||
'resource_name': resource or 'app'}
|
||||
if pk:
|
||||
kwargs['pk'] = self.pk
|
||||
key = self.pk
|
||||
else:
|
||||
kwargs['app_slug'] = self.app_slug
|
||||
return reverse('api_dispatch_%s' % (action or 'detail'), kwargs=kwargs)
|
||||
key = self.app_slug
|
||||
return reverse('app-detail', kwargs={'pk': key})
|
||||
|
||||
def get_url_path(self, more=False, add_prefix=True, src=None):
|
||||
# We won't have to do this when Marketplace absorbs all apps views,
|
||||
|
|
|
@ -139,6 +139,10 @@ class TestWebapp(amo.tests.TestCase):
|
|||
webapp = Webapp(app_slug='woo', pk=1)
|
||||
eq_(webapp.get_api_url(), '/api/v1/apps/app/woo/')
|
||||
|
||||
def test_get_api_url_pk(self):
|
||||
webapp = Webapp(pk=1)
|
||||
eq_(webapp.get_api_url(pk=True), '/api/v1/apps/app/1/')
|
||||
|
||||
def test_get_stats_url(self):
|
||||
webapp = Webapp(app_slug='woo')
|
||||
|
||||
|
|
|
@ -538,7 +538,7 @@ class TestDumpApps(amo.tests.TestCase):
|
|||
def test_dump_app(self):
|
||||
fn = dump_app(337141)
|
||||
result = json.load(open(fn, 'r'))
|
||||
eq_(result['id'], str(337141))
|
||||
eq_(result['id'], 337141)
|
||||
|
||||
def test_zip_apps(self):
|
||||
dump_app(337141)
|
||||
|
|
|
@ -124,7 +124,7 @@ class TestAppSerializer(amo.tests.TestCase):
|
|||
ratingsbodies.CLASSIND: ratingsbodies.CLASSIND_18,
|
||||
ratingsbodies.GENERIC: ratingsbodies.GENERIC_18,
|
||||
})
|
||||
res = app_to_dict(self.app)
|
||||
res = self.serialize(self.app)
|
||||
eq_(res['content_ratings']['ratings']['br'],
|
||||
{'body': 'CLASSIND',
|
||||
'body_label': 'classind',
|
||||
|
@ -140,14 +140,14 @@ class TestAppSerializer(amo.tests.TestCase):
|
|||
|
||||
def test_content_descriptors(self):
|
||||
self.app.set_descriptors(['has_esrb_blood', 'has_pegi_scary'])
|
||||
res = app_to_dict(self.app)
|
||||
res = self.serialize(self.app)
|
||||
eq_(res['content_ratings']['descriptors'],
|
||||
[{'label': 'esrb-blood', 'name': 'Blood', 'ratings_body': 'esrb'},
|
||||
{'label': 'pegi-scary', 'name': 'Fear', 'ratings_body': 'pegi'}])
|
||||
|
||||
def test_interactive_elements(self):
|
||||
self.app.set_interactives(['has_social_networking', 'has_shares_info'])
|
||||
res = app_to_dict(self.app)
|
||||
res = self.serialize(self.app)
|
||||
eq_(res['content_ratings']['interactive_elements'],
|
||||
[{'label': 'shares-info', 'name': 'Shares Info'},
|
||||
{'label': 'social-networking', 'name': 'Social Networking'}])
|
||||
|
|
|
@ -7,7 +7,8 @@ import commonware.log
|
|||
import amo
|
||||
from addons.models import AddonUser
|
||||
from amo.helpers import absolutify
|
||||
from amo.utils import find_language, no_translation
|
||||
from amo.utils import find_language
|
||||
from amo.urlresolvers import reverse
|
||||
from constants.applications import DEVICE_TYPES
|
||||
from market.models import Price
|
||||
from users.models import UserProfile
|
||||
|
@ -74,8 +75,6 @@ def es_app_to_dict(obj, region=None, profile=None, request=None):
|
|||
Return app data as dict for API where `app` is the elasticsearch result.
|
||||
"""
|
||||
# Circular import.
|
||||
from mkt.api.base import GenericObject
|
||||
from mkt.api.resources import AppResource, PrivacyPolicyResource
|
||||
from mkt.developers.models import AddonPaymentAccount
|
||||
from mkt.webapps.models import Installed, Webapp
|
||||
|
||||
|
@ -116,9 +115,8 @@ def es_app_to_dict(obj, region=None, profile=None, request=None):
|
|||
'name': get_attr_lang(src, 'name', obj.default_locale),
|
||||
'payment_required': False,
|
||||
'premium_type': amo.ADDON_PREMIUM_API[src.get('premium_type')],
|
||||
'privacy_policy': PrivacyPolicyResource().get_resource_uri(
|
||||
GenericObject({'pk': obj._id})
|
||||
),
|
||||
'privacy_policy': reverse('app-privacy-policy-detail',
|
||||
kwargs={'pk': obj._id}),
|
||||
'public_stats': obj.has_public_stats,
|
||||
'supported_locales': src.get('supported_locales', ''),
|
||||
'slug': obj.app_slug,
|
||||
|
@ -149,8 +147,9 @@ def es_app_to_dict(obj, region=None, profile=None, request=None):
|
|||
exclusions = obj.upsell.get('region_exclusions')
|
||||
if exclusions is not None and region not in exclusions:
|
||||
data['upsell'] = obj.upsell
|
||||
data['upsell']['resource_uri'] = AppResource().get_resource_uri(
|
||||
Webapp(id=obj.upsell['id']))
|
||||
data['upsell']['resource_uri'] = reverse(
|
||||
'app-detail',
|
||||
kwargs={'pk': obj.upsell['id']})
|
||||
|
||||
data['price'] = data['price_locale'] = None
|
||||
try:
|
||||
|
|
Загрузка…
Ссылка в новой задаче