Expose latest unlisted version in API for authors/unlisted reviewers
This commit is contained in:
Родитель
2248a2d5ab
Коммит
fe0cd88b38
|
@ -125,6 +125,7 @@ This endpoint allows you to fetch a specific add-on by id, slug or guid.
|
|||
:>json boolean is_source_public: Whether the add-on source is publicly viewable or not.
|
||||
:>json string|object|null name: The add-on name (See :ref:`translated fields <api-overview-translations>`).
|
||||
:>json string last_updated: The date of the last time the add-on was updated by its developer(s).
|
||||
:>json object|null latest_unlisted_version: Object holding the latest unlisted :ref:`version <version-detail-object>` of the add-on. This field is only present if the user has unlisted reviewer permissions, or is listed as a developer of the add-on.
|
||||
:>json array previews: Array holding information about the previews for the add-on.
|
||||
:>json int previews[].id: The id for a preview.
|
||||
:>json string|object|null previews[].caption: The caption describing a preview (See :ref:`translated fields <api-overview-translations>`).
|
||||
|
|
|
@ -78,6 +78,7 @@ class AddonIndexer(BaseSearchIndexer):
|
|||
'is_experimental': {'type': 'boolean'},
|
||||
'is_listed': {'type': 'boolean'},
|
||||
'last_updated': {'type': 'date'},
|
||||
'latest_unlisted_version': version_mapping,
|
||||
'listed_authors': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
|
@ -160,7 +161,7 @@ class AddonIndexer(BaseSearchIndexer):
|
|||
} for file_ in version_obj.all_files],
|
||||
'reviewed': version_obj.reviewed,
|
||||
'version': version_obj.version,
|
||||
}
|
||||
} if version_obj else None
|
||||
|
||||
@classmethod
|
||||
def extract_compatibility_info(cls, version_obj):
|
||||
|
@ -234,11 +235,8 @@ class AddonIndexer(BaseSearchIndexer):
|
|||
obj.current_version.supported_platforms]
|
||||
else:
|
||||
data['has_version'] = None
|
||||
if obj.current_beta_version:
|
||||
data['current_beta_version'] = cls.extract_version(
|
||||
obj, obj.current_beta_version)
|
||||
else:
|
||||
data['current_beta_version'] = None
|
||||
data['current_beta_version'] = cls.extract_version(
|
||||
obj, obj.current_beta_version)
|
||||
data['listed_authors'] = [
|
||||
{'name': a.name, 'id': a.id, 'username': a.username}
|
||||
for a in obj.listed_authors
|
||||
|
@ -247,6 +245,9 @@ class AddonIndexer(BaseSearchIndexer):
|
|||
data['has_eula'] = bool(obj.eula)
|
||||
data['has_privacy_policy'] = bool(obj.privacy_policy)
|
||||
|
||||
data['latest_unlisted_version'] = cls.extract_version(
|
||||
obj, obj.latest_unlisted_version)
|
||||
|
||||
# We can use all_previews because the indexing code goes through the
|
||||
# transformer that sets it.
|
||||
data['previews'] = [{'id': preview.id, 'modified': preview.modified}
|
||||
|
|
|
@ -954,6 +954,12 @@ class Addon(OnChangeMixin, ModelBase):
|
|||
pass
|
||||
return None
|
||||
|
||||
@amo.cached_property(writable=True)
|
||||
def latest_unlisted_version(self):
|
||||
"""Shortcut property for Addon.find_latest_version(
|
||||
channel=RELEASE_CHANNEL_UNLISTED)."""
|
||||
return self.find_latest_version(channel=amo.RELEASE_CHANNEL_UNLISTED)
|
||||
|
||||
@amo.cached_property
|
||||
def binary(self):
|
||||
"""Returns if the current version has binary files."""
|
||||
|
|
|
@ -179,8 +179,8 @@ class AddonSerializer(serializers.ModelSerializer):
|
|||
'is_experimental',
|
||||
'is_listed',
|
||||
'is_source_public',
|
||||
'name',
|
||||
'last_updated',
|
||||
'name',
|
||||
'previews',
|
||||
'public_stats',
|
||||
'ratings',
|
||||
|
@ -275,9 +275,15 @@ class AddonSerializer(serializers.ModelSerializer):
|
|||
return False
|
||||
|
||||
|
||||
class ESAddonSerializer(BaseESSerializer, AddonSerializer):
|
||||
previews = ESPreviewSerializer(many=True, source='all_previews')
|
||||
class AddonSerializerWithUnlistedData(AddonSerializer):
|
||||
latest_unlisted_version = SimpleVersionSerializer()
|
||||
|
||||
class Meta:
|
||||
model = Addon
|
||||
fields = AddonSerializer.Meta.fields + ('latest_unlisted_version',)
|
||||
|
||||
|
||||
class ESBaseAddonSerializer(BaseESSerializer):
|
||||
datetime_fields = ('created', 'last_updated', 'modified')
|
||||
translated_fields = ('name', 'description', 'homepage', 'summary',
|
||||
'support_email', 'support_url')
|
||||
|
@ -351,12 +357,15 @@ class ESAddonSerializer(BaseESSerializer, AddonSerializer):
|
|||
|
||||
# Attach related models (also faking them). `current_version` is a
|
||||
# property we can't write to, so we use the underlying field which
|
||||
# begins with an underscore. `current_beta_version` is a
|
||||
# cached_property so we can directly write to it.
|
||||
# begins with an underscore. `current_beta_version` and
|
||||
# `latest_unlisted_version` are writeable cached_property so we can
|
||||
# directly write to them.
|
||||
obj.current_beta_version = self.fake_version_object(
|
||||
obj, data.get('current_beta_version'))
|
||||
obj._current_version = self.fake_version_object(
|
||||
obj, data.get('current_version'))
|
||||
obj.latest_unlisted_version = self.fake_version_object(
|
||||
obj, data.get('latest_unlisted_version'))
|
||||
|
||||
data_authors = data.get('listed_authors', [])
|
||||
obj.listed_authors = [
|
||||
|
@ -398,6 +407,15 @@ class ESAddonSerializer(BaseESSerializer, AddonSerializer):
|
|||
return obj
|
||||
|
||||
|
||||
class ESAddonSerializer(ESBaseAddonSerializer, AddonSerializer):
|
||||
previews = ESPreviewSerializer(many=True, source='all_previews')
|
||||
|
||||
|
||||
class ESAddonSerializerWithUnlistedData(
|
||||
ESBaseAddonSerializer, AddonSerializerWithUnlistedData):
|
||||
previews = ESPreviewSerializer(many=True, source='all_previews')
|
||||
|
||||
|
||||
class StaticCategorySerializer(serializers.Serializer):
|
||||
"""Serializes a `StaticCategory` as found in constants.categories"""
|
||||
id = serializers.IntegerField()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from datetime import timedelta
|
||||
from itertools import chain
|
||||
|
||||
from olympia import amo
|
||||
|
@ -47,9 +48,9 @@ class TestAddonIndexer(TestCase):
|
|||
complex_fields = [
|
||||
'app', 'boost', 'category', 'current_beta_version',
|
||||
'current_version', 'description', 'has_eula', 'has_privacy_policy',
|
||||
'has_theme_rereview', 'has_version', 'listed_authors', 'name',
|
||||
'name_sort', 'platforms', 'previews', 'public_stats', 'ratings',
|
||||
'summary', 'tags',
|
||||
'has_theme_rereview', 'has_version', 'latest_unlisted_version',
|
||||
'listed_authors', 'name', 'name_sort', 'platforms', 'previews',
|
||||
'public_stats', 'ratings', 'summary', 'tags',
|
||||
]
|
||||
|
||||
# Fields that need to be present in the mapping, but might be skipped
|
||||
|
@ -142,6 +143,7 @@ class TestAddonIndexer(TestCase):
|
|||
assert extracted['current_beta_version'] is None
|
||||
assert extracted['current_version']
|
||||
assert extracted['has_theme_rereview'] is None
|
||||
assert extracted['latest_unlisted_version'] is None
|
||||
assert extracted['listed_authors'] == [
|
||||
{'name': u'55021 التطب', 'id': 55021, 'username': '55021'}]
|
||||
assert extracted['platforms'] == [PLATFORM_ALL.id]
|
||||
|
@ -165,10 +167,16 @@ class TestAddonIndexer(TestCase):
|
|||
assert extracted['has_privacy_policy'] is False
|
||||
|
||||
def test_extract_version_and_files(self):
|
||||
current_beta_version = version_factory(
|
||||
addon=self.addon, file_kw={'status': amo.STATUS_BETA})
|
||||
version = self.addon.current_version
|
||||
file_factory(version=version, platform=PLATFORM_MAC.id)
|
||||
current_beta_version = version_factory(
|
||||
addon=self.addon, file_kw={'status': amo.STATUS_BETA})
|
||||
unlisted_version = version_factory(
|
||||
addon=self.addon, channel=amo.RELEASE_CHANNEL_UNLISTED)
|
||||
# FIXME: remove this next line once current_version is modified to only
|
||||
# return listed versions.
|
||||
unlisted_version.update(
|
||||
created=version.created - timedelta(days=42))
|
||||
extracted = self._extract()
|
||||
|
||||
assert extracted['current_version']
|
||||
|
@ -218,6 +226,29 @@ class TestAddonIndexer(TestCase):
|
|||
assert extracted_file['size'] == file_.size
|
||||
assert extracted_file['status'] == file_.status
|
||||
|
||||
version = unlisted_version
|
||||
assert extracted['latest_unlisted_version']
|
||||
assert extracted['latest_unlisted_version']['id'] == version.pk
|
||||
assert extracted['latest_unlisted_version']['compatible_apps'] == {
|
||||
FIREFOX.id: {
|
||||
'min': 4009900200100L,
|
||||
'max': 5009900200100L,
|
||||
'max_human': '5.0.99',
|
||||
'min_human': '4.0.99',
|
||||
}
|
||||
}
|
||||
assert (
|
||||
extracted['latest_unlisted_version']['version'] == version.version)
|
||||
for idx, file_ in enumerate(version.all_files):
|
||||
extracted_file = extracted['latest_unlisted_version']['files'][idx]
|
||||
assert extracted_file['id'] == file_.pk
|
||||
assert extracted_file['created'] == file_.created
|
||||
assert extracted_file['filename'] == file_.filename
|
||||
assert extracted_file['hash'] == file_.hash
|
||||
assert extracted_file['platform'] == file_.platform
|
||||
assert extracted_file['size'] == file_.size
|
||||
assert extracted_file['status'] == file_.status
|
||||
|
||||
def test_extract_translations(self):
|
||||
translations_name = {
|
||||
'en-US': u'Name in ënglish',
|
||||
|
|
|
@ -425,6 +425,32 @@ class TestAddonModels(TestCase):
|
|||
a = Addon.objects.get(pk=3723)
|
||||
assert a.current_version is None
|
||||
|
||||
def test_latest_unlisted_version(self):
|
||||
addon = Addon.objects.get(pk=3615)
|
||||
an_unlisted_version = version_factory(
|
||||
addon=addon, version='3.0', channel=amo.RELEASE_CHANNEL_UNLISTED)
|
||||
an_unlisted_version.update(created=self.days_ago(2))
|
||||
a_newer_unlisted_version = version_factory(
|
||||
addon=addon, version='4.0', channel=amo.RELEASE_CHANNEL_UNLISTED)
|
||||
a_newer_unlisted_version.update(created=self.days_ago(1))
|
||||
version_factory(
|
||||
addon=addon, version='5.0', channel=amo.RELEASE_CHANNEL_UNLISTED,
|
||||
file_kw={'status': amo.STATUS_DISABLED})
|
||||
assert addon.latest_unlisted_version == a_newer_unlisted_version
|
||||
|
||||
# Make sure the property is cached.
|
||||
an_even_newer_unlisted_version = version_factory(
|
||||
addon=addon, version='6.0', channel=amo.RELEASE_CHANNEL_UNLISTED)
|
||||
assert addon.latest_unlisted_version == a_newer_unlisted_version
|
||||
|
||||
# Make sure it can be deleted to reset it.
|
||||
del addon.latest_unlisted_version
|
||||
assert addon.latest_unlisted_version == an_even_newer_unlisted_version
|
||||
|
||||
# Make sure it's writeable.
|
||||
addon.latest_unlisted_version = an_unlisted_version
|
||||
assert addon.latest_unlisted_version == an_unlisted_version
|
||||
|
||||
def test_find_latest_version(self):
|
||||
"""
|
||||
Tests that we get the latest version of an addon.
|
||||
|
|
|
@ -12,7 +12,8 @@ from olympia.addons.indexers import AddonIndexer
|
|||
from olympia.addons.models import (
|
||||
Addon, AddonCategory, AddonUser, Category, Persona, Preview)
|
||||
from olympia.addons.serializers import (
|
||||
AddonSerializer, ESAddonSerializer, VersionSerializer)
|
||||
AddonSerializer, AddonSerializerWithUnlistedData, ESAddonSerializer,
|
||||
ESAddonSerializerWithUnlistedData, VersionSerializer)
|
||||
from olympia.addons.utils import generate_addon_guid
|
||||
from olympia.constants.categories import CATEGORIES
|
||||
from olympia.versions.models import ApplicationsVersions, AppVersion, License
|
||||
|
@ -127,6 +128,9 @@ class AddonSerializerOutputTestMixin(object):
|
|||
|
||||
assert result['current_beta_version'] is None
|
||||
|
||||
# In this serializer latest_unlisted_version is omitted.
|
||||
assert 'latest_unlisted_version' not in result
|
||||
|
||||
assert result['current_version']
|
||||
self._test_version(
|
||||
self.addon.current_version, result['current_version'])
|
||||
|
@ -154,9 +158,9 @@ class AddonSerializerOutputTestMixin(object):
|
|||
assert result['is_experimental'] == self.addon.is_experimental is False
|
||||
assert result['is_listed'] == self.addon.is_listed
|
||||
assert result['is_source_public'] == self.addon.view_source
|
||||
assert result['name'] == {'en-US': self.addon.name}
|
||||
assert result['last_updated'] == (
|
||||
self.addon.last_updated.isoformat() + 'Z')
|
||||
assert result['name'] == {'en-US': self.addon.name}
|
||||
|
||||
assert result['previews']
|
||||
assert len(result['previews']) == 2
|
||||
|
@ -200,6 +204,34 @@ class AddonSerializerOutputTestMixin(object):
|
|||
|
||||
return result
|
||||
|
||||
def test_latest_unlisted_version(self):
|
||||
self.addon = addon_factory()
|
||||
version_factory(
|
||||
addon=self.addon, channel=amo.RELEASE_CHANNEL_UNLISTED,
|
||||
version='1.1')
|
||||
assert self.addon.latest_unlisted_version
|
||||
|
||||
result = self.serialize()
|
||||
# In this serializer latest_unlisted_version is omitted even if there
|
||||
# is one, because it's limited to users with specific rights.
|
||||
assert 'latest_unlisted_version' not in result
|
||||
|
||||
def test_latest_unlisted_version_with_rights(self):
|
||||
self.serializer_class = self.serializer_class_with_unlisted_data
|
||||
|
||||
self.addon = addon_factory()
|
||||
version_factory(
|
||||
addon=self.addon, channel=amo.RELEASE_CHANNEL_UNLISTED,
|
||||
version='1.1')
|
||||
assert self.addon.latest_unlisted_version
|
||||
|
||||
result = self.serialize()
|
||||
# In this serializer latest_unlisted_version is present.
|
||||
assert result['latest_unlisted_version']
|
||||
self._test_version(
|
||||
self.addon.latest_unlisted_version,
|
||||
result['latest_unlisted_version'])
|
||||
|
||||
def test_current_beta_version(self):
|
||||
self.addon = addon_factory()
|
||||
|
||||
|
@ -360,14 +392,21 @@ class AddonSerializerOutputTestMixin(object):
|
|||
|
||||
|
||||
class TestAddonSerializerOutput(AddonSerializerOutputTestMixin, TestCase):
|
||||
serializer_class = AddonSerializer
|
||||
serializer_class_with_unlisted_data = AddonSerializerWithUnlistedData
|
||||
|
||||
def serialize(self):
|
||||
self.serializer = self.serializer_class(
|
||||
context={'request': self.request})
|
||||
# Manually reload the add-on first to clear any cached properties.
|
||||
self.addon = Addon.unfiltered.get(pk=self.addon.pk)
|
||||
serializer = AddonSerializer(context={'request': self.request})
|
||||
return serializer.to_representation(self.addon)
|
||||
return self.serializer.to_representation(self.addon)
|
||||
|
||||
|
||||
class TestESAddonSerializerOutput(AddonSerializerOutputTestMixin, ESTestCase):
|
||||
serializer_class = ESAddonSerializer
|
||||
serializer_class_with_unlisted_data = ESAddonSerializerWithUnlistedData
|
||||
|
||||
def tearDown(self):
|
||||
super(TestESAddonSerializerOutput, self).tearDown()
|
||||
self.empty_index('default')
|
||||
|
@ -382,11 +421,13 @@ class TestESAddonSerializerOutput(AddonSerializerOutputTestMixin, ESTestCase):
|
|||
return qs.filter('term', id=self.addon.pk).execute()[0]
|
||||
|
||||
def serialize(self):
|
||||
self.serializer = self.serializer_class(
|
||||
context={'request': self.request})
|
||||
|
||||
obj = self.search()
|
||||
|
||||
with self.assertNumQueries(0):
|
||||
serializer = ESAddonSerializer(context={'request': self.request})
|
||||
result = serializer.to_representation(obj)
|
||||
result = self.serializer.to_representation(obj)
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
@ -1750,10 +1750,53 @@ class TestAddonViewSetDetail(AddonAndVersionViewSetDetailMixin, TestCase):
|
|||
assert result['slug'] == 'my-addon'
|
||||
assert result['last_updated'] == (
|
||||
self.addon.last_updated.isoformat() + 'Z')
|
||||
return result
|
||||
|
||||
def _set_tested_url(self, param):
|
||||
self.url = reverse('addon-detail', kwargs={'pk': param})
|
||||
|
||||
def test_hide_latest_unlisted_version_anonymous(self):
|
||||
unlisted_version = version_factory(
|
||||
addon=self.addon, channel=amo.RELEASE_CHANNEL_UNLISTED)
|
||||
unlisted_version.update(created=self.days_ago(1))
|
||||
result = self._test_url()
|
||||
assert 'latest_unlisted_version' not in result
|
||||
|
||||
def test_hide_latest_unlisted_version_simple_reviewer(self):
|
||||
user = UserProfile.objects.create(username='reviewer')
|
||||
self.grant_permission(user, 'Addons:Review')
|
||||
self.client.login_api(user)
|
||||
|
||||
unlisted_version = version_factory(
|
||||
addon=self.addon, channel=amo.RELEASE_CHANNEL_UNLISTED)
|
||||
unlisted_version.update(created=self.days_ago(1))
|
||||
result = self._test_url()
|
||||
assert 'latest_unlisted_version' not in result
|
||||
|
||||
def test_show_latest_unlisted_version_author(self):
|
||||
user = UserProfile.objects.create(username='author')
|
||||
AddonUser.objects.create(user=user, addon=self.addon)
|
||||
self.client.login_api(user)
|
||||
|
||||
unlisted_version = version_factory(
|
||||
addon=self.addon, channel=amo.RELEASE_CHANNEL_UNLISTED)
|
||||
unlisted_version.update(created=self.days_ago(1))
|
||||
result = self._test_url()
|
||||
assert result['latest_unlisted_version']
|
||||
assert result['latest_unlisted_version']['id'] == unlisted_version.pk
|
||||
|
||||
def test_show_latest_unlisted_version_unlisted_reviewer(self):
|
||||
user = UserProfile.objects.create(username='author')
|
||||
self.grant_permission(user, 'Addons:ReviewUnlisted')
|
||||
self.client.login_api(user)
|
||||
|
||||
unlisted_version = version_factory(
|
||||
addon=self.addon, channel=amo.RELEASE_CHANNEL_UNLISTED)
|
||||
unlisted_version.update(created=self.days_ago(1))
|
||||
result = self._test_url()
|
||||
assert result['latest_unlisted_version']
|
||||
assert result['latest_unlisted_version']['id'] == unlisted_version.pk
|
||||
|
||||
|
||||
class TestVersionViewSetDetail(AddonAndVersionViewSetDetailMixin, TestCase):
|
||||
client_class = APITestClient
|
||||
|
@ -2094,11 +2137,17 @@ class TestAddonSearchView(ESTestCase):
|
|||
assert result['slug'] == 'my-addon'
|
||||
assert result['last_updated'] == addon.last_updated.isoformat() + 'Z'
|
||||
|
||||
# latest_unlisted_version should never be exposed in public search.
|
||||
assert 'latest_unlisted_version' not in result
|
||||
|
||||
result = data['results'][1]
|
||||
assert result['id'] == addon2.pk
|
||||
assert result['name'] == {'en-US': u'My second Addôn'}
|
||||
assert result['slug'] == 'my-second-addon'
|
||||
|
||||
# latest_unlisted_version should never be exposed in public search.
|
||||
assert 'latest_unlisted_version' not in result
|
||||
|
||||
def test_empty(self):
|
||||
data = self.perform_search(self.url)
|
||||
assert data['count'] == 0
|
||||
|
|
|
@ -33,6 +33,7 @@ from rest_framework.viewsets import GenericViewSet
|
|||
from session_csrf import anonymous_csrf_exempt
|
||||
|
||||
from olympia import amo
|
||||
from olympia.access import acl
|
||||
from olympia.amo import messages
|
||||
from olympia.amo.decorators import post_required
|
||||
from olympia.amo.forms import AbuseForm
|
||||
|
@ -65,8 +66,8 @@ from .indexers import AddonIndexer
|
|||
from .models import Addon, Persona, FrozenAddon
|
||||
from .serializers import (
|
||||
AddonEulaPolicySerializer, AddonFeatureCompatibilitySerializer,
|
||||
AddonSerializer, ESAddonSerializer, VersionSerializer,
|
||||
StaticCategorySerializer)
|
||||
AddonSerializer, AddonSerializerWithUnlistedData, ESAddonSerializer,
|
||||
VersionSerializer, StaticCategorySerializer)
|
||||
from .utils import get_creatured_ids, get_featured_ids
|
||||
|
||||
|
||||
|
@ -615,13 +616,14 @@ class AddonViewSet(RetrieveModelMixin, GenericViewSet):
|
|||
AllowReviewer, AllowReviewerUnlisted),
|
||||
]
|
||||
serializer_class = AddonSerializer
|
||||
serializer_class_with_unlisted_data = AddonSerializerWithUnlistedData
|
||||
addon_id_pattern = re.compile(
|
||||
# Match {uuid} or something@host.tld ("something" being optional)
|
||||
# guids. Copied from mozilla-central XPIProvider.jsm.
|
||||
r'^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}'
|
||||
r'|[a-z0-9-\._]*\@[a-z0-9-\._]+)$', re.IGNORECASE)
|
||||
# Permission classes disallow access to non-public/unlisted add-ons unless
|
||||
# logged in as a reviewer/addon owner/admin, so the unfiltered queryset
|
||||
# logged in as a reviewer/addon owner/admin, so the with_unlisted queryset
|
||||
# is fine here.
|
||||
queryset = Addon.with_unlisted.all()
|
||||
lookup_value_regex = '[^/]+' # Allow '.' for email-like guids.
|
||||
|
@ -634,6 +636,17 @@ class AddonViewSet(RetrieveModelMixin, GenericViewSet):
|
|||
return Addon.unfiltered.all()
|
||||
return super(AddonViewSet, self).get_queryset()
|
||||
|
||||
def get_serializer_class(self):
|
||||
# Override serializer to use serializer_class_with_unlisted_data if
|
||||
# we are allowed to access unlisted data.
|
||||
obj = getattr(self, 'instance')
|
||||
request = self.request
|
||||
if (acl.check_unlisted_addons_reviewer(request) or
|
||||
(obj and request.user.is_authenticated() and
|
||||
obj.authors.filter(pk=request.user.pk).exists())):
|
||||
return self.serializer_class_with_unlisted_data
|
||||
return self.serializer_class
|
||||
|
||||
def get_lookup_field(self, identifier):
|
||||
lookup_field = 'pk'
|
||||
if identifier and not identifier.isdigit():
|
||||
|
@ -650,7 +663,8 @@ class AddonViewSet(RetrieveModelMixin, GenericViewSet):
|
|||
identifier = self.kwargs.get('pk')
|
||||
self.lookup_field = self.get_lookup_field(identifier)
|
||||
self.kwargs[self.lookup_field] = identifier
|
||||
return super(AddonViewSet, self).get_object()
|
||||
self.instance = super(AddonViewSet, self).get_object()
|
||||
return self.instance
|
||||
|
||||
@detail_route()
|
||||
def feature_compatibility(self, request, pk=None):
|
||||
|
|
|
@ -672,6 +672,9 @@ def addon_factory(
|
|||
addon = Addon.objects.create(type=type_, **kwargs)
|
||||
|
||||
# Save 2.
|
||||
if 'channel' not in version_kw and 'is_listed' in kw:
|
||||
version_kw['channel'] = (amo.RELEASE_CHANNEL_LISTED if kw['is_listed']
|
||||
else amo.RELEASE_CHANNEL_UNLISTED)
|
||||
version = version_factory(file_kw, addon=addon, **version_kw)
|
||||
addon.update_version()
|
||||
addon.status = status
|
||||
|
|
|
@ -5,8 +5,9 @@ from django.conf import settings
|
|||
from django.core.urlresolvers import reverse
|
||||
from django.test import override_settings
|
||||
|
||||
from olympia import amo
|
||||
from olympia.amo.tests import version_factory
|
||||
from olympia.accounts.tests.test_views import BaseAuthenticationView
|
||||
from olympia.addons.tests.test_views import AddonAndVersionViewSetDetailMixin
|
||||
from olympia.addons.utils import generate_addon_guid
|
||||
from olympia.amo.tests import (
|
||||
addon_factory, APITestClient, ESTestCase, TestCase)
|
||||
|
@ -93,12 +94,16 @@ class TestInternalAddonSearchView(ESTestCase):
|
|||
assert result['name'] == {'en-US': u'My Addôn'}
|
||||
assert result['slug'] == 'my-addon'
|
||||
assert result['last_updated'] == addon.last_updated.isoformat() + 'Z'
|
||||
assert result['latest_unlisted_version']
|
||||
assert (result['latest_unlisted_version']['id'] ==
|
||||
addon.latest_unlisted_version.pk)
|
||||
|
||||
result = data['results'][1]
|
||||
assert result['id'] == addon2.pk
|
||||
assert result['name'] == {'en-US': u'My second Addôn'}
|
||||
assert result['slug'] is None # Because it was deleted.
|
||||
assert result['status'] == 'deleted'
|
||||
assert result['latest_unlisted_version'] is None
|
||||
|
||||
def test_empty(self):
|
||||
data = self.perform_search_with_senior_editor(self.url)
|
||||
|
@ -168,14 +173,17 @@ class TestInternalAddonSearchView(ESTestCase):
|
|||
assert result['name'] == {'en-US': u'By second Addôn'}
|
||||
|
||||
|
||||
class TestAddonViewSetDetail(AddonAndVersionViewSetDetailMixin, TestCase):
|
||||
class TestInternalAddonViewSetDetail(TestCase):
|
||||
client_class = APITestClient
|
||||
|
||||
def setUp(self):
|
||||
super(TestAddonViewSetDetail, self).setUp()
|
||||
super(TestInternalAddonViewSetDetail, self).setUp()
|
||||
self.addon = addon_factory(
|
||||
guid=generate_addon_guid(), name=u'My Addôn', slug='my-addon')
|
||||
self._set_tested_url(self.addon.pk)
|
||||
user = UserProfile.objects.create(username='reviewer-admin-tools')
|
||||
self.grant_permission(user, 'ReviewerAdminTools:View')
|
||||
self.client.login_api(user)
|
||||
|
||||
def _test_url(self):
|
||||
response = self.client.get(self.url)
|
||||
|
@ -186,10 +194,93 @@ class TestAddonViewSetDetail(AddonAndVersionViewSetDetailMixin, TestCase):
|
|||
assert result['slug'] == 'my-addon'
|
||||
assert result['last_updated'] == (
|
||||
self.addon.last_updated.isoformat() + 'Z')
|
||||
assert 'latest_unlisted_version' in result
|
||||
return result
|
||||
|
||||
def _set_tested_url(self, param):
|
||||
self.url = reverse('internal-addon-detail', kwargs={'pk': param})
|
||||
|
||||
def test_get_by_id(self):
|
||||
self._test_url()
|
||||
|
||||
def test_get_by_slug(self):
|
||||
self._set_tested_url(self.addon.slug)
|
||||
self._test_url()
|
||||
|
||||
def test_get_by_guid(self):
|
||||
self._set_tested_url(self.addon.guid)
|
||||
self._test_url()
|
||||
|
||||
def test_get_by_guid_uppercase(self):
|
||||
self._set_tested_url(self.addon.guid.upper())
|
||||
self._test_url()
|
||||
|
||||
def test_get_by_guid_email_format(self):
|
||||
self.addon.update(guid='my-addon@example.tld')
|
||||
self._set_tested_url(self.addon.guid)
|
||||
self._test_url()
|
||||
|
||||
def test_get_by_guid_email_short_format(self):
|
||||
self.addon.update(guid='@example.tld')
|
||||
self._set_tested_url(self.addon.guid)
|
||||
self._test_url()
|
||||
|
||||
def test_get_by_guid_email_really_short_format(self):
|
||||
self.addon.update(guid='@example')
|
||||
self._set_tested_url(self.addon.guid)
|
||||
self._test_url()
|
||||
|
||||
def test_get_anonymous(self):
|
||||
self.client.logout_api()
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_get_no_rights(self):
|
||||
self.client.logout_api()
|
||||
user = UserProfile.objects.create(username='simpleuser')
|
||||
self.client.login_api(user)
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_get_no_rights_even_if_reviewer(self):
|
||||
self.client.logout_api()
|
||||
user = UserProfile.objects.create(username='reviewer')
|
||||
self.grant_permission(user, 'Addons:Review')
|
||||
self.client.login_api(user)
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_get_no_rights_even_if_author(self):
|
||||
self.client.logout_api()
|
||||
user = UserProfile.objects.create(username='author')
|
||||
self.addon.addonuser_set.create(user=user, addon=self.addon)
|
||||
self.client.login_api(user)
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_get_not_listed(self):
|
||||
self.addon.update(is_listed=False)
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_get_deleted(self):
|
||||
self.addon.delete()
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_get_addon_not_found(self):
|
||||
self._set_tested_url(self.addon.pk + 42)
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_show_latest_unlisted_version_unlisted(self):
|
||||
unlisted_version = version_factory(
|
||||
addon=self.addon, channel=amo.RELEASE_CHANNEL_UNLISTED)
|
||||
unlisted_version.update(created=self.days_ago(1))
|
||||
result = self._test_url()
|
||||
assert result['latest_unlisted_version']
|
||||
assert result['latest_unlisted_version']['id'] == unlisted_version.pk
|
||||
|
||||
|
||||
class TestLoginStartView(TestCase):
|
||||
|
||||
|
|
|
@ -2,12 +2,11 @@ from django.conf.urls import include, patterns, url
|
|||
|
||||
from rest_framework.routers import SimpleRouter
|
||||
|
||||
from olympia.addons.views import AddonViewSet
|
||||
|
||||
from . import views
|
||||
|
||||
addons = SimpleRouter()
|
||||
addons.register(r'addon', AddonViewSet, base_name='internal-addon')
|
||||
addons.register(r'addon', views.InternalAddonViewSet,
|
||||
base_name='internal-addon')
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
|
|
|
@ -3,7 +3,10 @@ import logging
|
|||
from django.conf import settings
|
||||
|
||||
from olympia.accounts.views import LoginBaseView, LoginStartBaseView
|
||||
from olympia.addons.views import AddonSearchView
|
||||
from olympia.addons.models import Addon
|
||||
from olympia.addons.views import AddonViewSet, AddonSearchView
|
||||
from olympia.addons.serializers import (
|
||||
AddonSerializerWithUnlistedData, ESAddonSerializerWithUnlistedData)
|
||||
from olympia.api.authentication import JSONWebTokenAuthentication
|
||||
from olympia.api.permissions import AnyOf, GroupPermission
|
||||
from olympia.search.filters import (
|
||||
|
@ -16,9 +19,10 @@ class InternalAddonSearchView(AddonSearchView):
|
|||
# AddonSearchView disables auth classes so we need to add it back.
|
||||
authentication_classes = [JSONWebTokenAuthentication]
|
||||
|
||||
# Similar to AddonSearchView but without the PublicContentFilter and with
|
||||
# InternalSearchParameterFilter instead of SearchParameterFilter to allow
|
||||
# searching by status.
|
||||
# Similar to AddonSearchView but without the ReviewedContentFilter (
|
||||
# allowing unlisted, deleted, unreviewed addons to show up) and with
|
||||
# InternalSearchParameterFilter instead of SearchParameterFilter (allowing
|
||||
# to search by status).
|
||||
filter_backends = [
|
||||
SearchQueryFilter, InternalSearchParameterFilter, SortingFilter
|
||||
]
|
||||
|
@ -26,6 +30,20 @@ class InternalAddonSearchView(AddonSearchView):
|
|||
# Restricted to specific permissions.
|
||||
permission_classes = [AnyOf(GroupPermission('AdminTools', 'View'),
|
||||
GroupPermission('ReviewerAdminTools', 'View'))]
|
||||
# Can display unlisted data.
|
||||
serializer_class = ESAddonSerializerWithUnlistedData
|
||||
|
||||
|
||||
class InternalAddonViewSet(AddonViewSet):
|
||||
# Restricted to specific permissions.
|
||||
permission_classes = [AnyOf(GroupPermission('AdminTools', 'View'),
|
||||
GroupPermission('ReviewerAdminTools', 'View'))]
|
||||
|
||||
# Internal tools allow access to everything, including deleted add-ons.
|
||||
queryset = Addon.unfiltered.all()
|
||||
|
||||
# Can display unlisted data.
|
||||
serializer_class = AddonSerializerWithUnlistedData
|
||||
|
||||
|
||||
class LoginStartView(LoginStartBaseView):
|
||||
|
|
Загрузка…
Ссылка в новой задаче