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

Commit

Permalink
Add reviewer tool specific search api (bug 892353)
Browse files Browse the repository at this point in the history
This allows us to remove reviewer-specific stuff from the
regular search API.
  • Loading branch information
diox committed Jul 25, 2013
1 parent b5f4c29 commit caeaa08
Show file tree
Hide file tree
Showing 10 changed files with 395 additions and 347 deletions.
68 changes: 66 additions & 2 deletions mkt/reviewers/api.py
@@ -1,13 +1,23 @@
import json

from tastypie import http
from tower import ugettext as _

import amo
from access import acl
from amo.urlresolvers import reverse

from mkt.api.authentication import OAuthAuthentication
from mkt.api.authorization import PermissionAuthorization
from mkt.api.base import MarketplaceResource
from mkt.api.base import MarketplaceResource, http_error
from mkt.reviewers.utils import AppsReviewing
from mkt.reviewers.forms import ApiReviewersSearchForm
from mkt.search.api import SearchResource
from mkt.search.views import _get_query
from mkt.webapps.utils import update_with_reviewer_data


class Wrapper(object):

def __init__(self, pk):
self.pk = pk

Expand All @@ -28,3 +38,57 @@ def get_resource_uri(self, bundle):
def obj_get_list(self, request, **kwargs):
return [Wrapper(r['app'].pk)
for r in AppsReviewing(request).get_apps()]


class ReviewersSearchResource(SearchResource):
class Meta(SearchResource.Meta):

This comment has been minimized.

Copy link
@cvan

cvan Jul 25, 2013

Contributor

add a newline above this

resource_name = 'search'
authorization = PermissionAuthorization('Apps', 'Review')
fields = ['device_types', 'id', 'is_packaged', 'latest_version',
'name', 'premium_type', 'price', 'slug', 'status']

def get_search_data(self, request):
form = ApiReviewersSearchForm(request.GET if request else None)
if not form.is_valid():
raise self.form_errors(form)
return form.cleaned_data

def get_feature_profile(self, request):
# We don't want automatic feature profile filtering in the reviewers
# API.
return None

def get_region(self, request):
# We don't want automatic region filtering in the reviewers API.
return None

def apply_filters(self, request, qs, data=None):
qs = super(ReviewersSearchResource, self).apply_filters(request, qs,
data=data)
for k in ('is_privileged', 'has_info_request', 'has_editor_comment'):
if data.get(k, None) is not None:
qs = qs.filter(**{
'latest_version.%s' % k: data[k]
})
if data.get('is_escalated', None) is not None:
qs = qs.filter(is_escalated=data['is_escalated'])
return qs

def get_query(self, request, base_filters=None):
form_data = self.get_search_data(request)
status = form_data.get('status')

if base_filters is None:
base_filters = {}
base_filters['status'] = status

This comment has been minimized.

Copy link
@cvan

cvan Jul 25, 2013

Contributor
base_filters['status'] = form_data.get('status')

region = self.get_region(request)
return _get_query(request, region, gaia=None, mobile=None, tablet=None,
new_idx=True, filters=base_filters)

def dehydrate(self, bundle):
bundle = super(ReviewersSearchResource, self).dehydrate(bundle)
bundle = update_with_reviewer_data(bundle, using_es=True)
# Filter out anything not present in Meta fields.
bundle.data = dict(((k, v) for k, v in bundle.data.items() if k in self._meta.fields))

This comment has been minimized.

Copy link
@cvan

cvan Jul 25, 2013

Contributor

94 characters long

return bundle
37 changes: 37 additions & 0 deletions mkt/reviewers/forms.py
Expand Up @@ -12,14 +12,24 @@
from amo.utils import raise_required
from editors.forms import NonValidatingChoiceField, ReviewLogForm
from editors.models import CannedResponse
from mkt.api.forms import CustomNullBooleanSelect
from mkt.reviewers.utils import ReviewHelper
from mkt.search.forms import ApiSearchForm


from .models import ThemeLock
from .tasks import approve_rereview, reject_rereview, send_mail


log = logging.getLogger('z.reviewers.forms')

# We set 'any' here since we need to default this field
# to PUBLIC if not specified for consumer pages.
STATUS_CHOICES = [('any', _lazy(u'Any Status'))]
for status in amo.WEBAPPS_UNLISTED_STATUSES + (amo.STATUS_PUBLIC,):
STATUS_CHOICES.append((amo.STATUS_CHOICES_API[status],
amo.STATUS_CHOICES[status]))


class ReviewAppAttachmentForm(happyforms.Form):
attachment = forms.FileField(label=_lazy(u'Attachment:'))
Expand Down Expand Up @@ -241,3 +251,30 @@ class ThemeSearchForm(forms.Form):
required=False, label=_lazy(u'Search'),
widget=forms.TextInput(attrs={'autocomplete': 'off',
'placeholder': _lazy(u'Search')}))


class ApiReviewersSearchForm(ApiSearchForm):
status = forms.ChoiceField(required=False, choices=STATUS_CHOICES,
label=_lazy(u'Status'))
is_privileged = forms.NullBooleanField(required=False,
label=_lazy(u'Privileged App'),
widget=CustomNullBooleanSelect)
has_editor_comment = forms.NullBooleanField(
required=False,
label=_lazy(u'Contains Editor Comment'),
widget=CustomNullBooleanSelect)
has_info_request = forms.NullBooleanField(
required=False,
label=_lazy(u'More Information Requested'),
widget=CustomNullBooleanSelect)
is_escalated = forms.NullBooleanField(
required=False,
label=_lazy(u'Escalated'),
widget=CustomNullBooleanSelect)

def clean_status(self):
status = self.cleaned_data['status']
if status == 'any':
return 'any'

return amo.STATUS_CHOICES_API_LOOKUP.get(status, amo.STATUS_PENDING)
226 changes: 223 additions & 3 deletions mkt/reviewers/tests/test_api.py
@@ -1,23 +1,35 @@
from datetime import datetime

This comment has been minimized.

Copy link
@cvan

cvan Jul 25, 2013

Contributor

this should go below the import

import json

from django.conf import settings
from django.contrib.auth.models import User
from django.core.cache import cache

from mock import patch
from nose.tools import eq_
from test_utils import RequestFactory

from mkt.api.tests.test_oauth import BaseOAuth, get_absolute_url
import amo
import mkt.regions
from access.models import GroupUser
from addons.models import Category
from amo.tests import ESTestCase

from mkt.api.tests.test_oauth import BaseOAuth, get_absolute_url, OAuthClient
from mkt.api.base import get_url, list_url
from mkt.api.models import Access, generate
from mkt.constants.features import FeatureProfile
from mkt.reviewers.utils import AppsReviewing
from mkt.site.fixtures import fixture
from mkt.webapps.models import Webapp
from users.models import UserProfile


class TestAccount(BaseOAuth):
class TestReviewing(BaseOAuth):
fixtures = fixture('user_2519', 'webapp_337141')

def setUp(self):
super(TestAccount, self).setUp(api_name='reviewers')
super(TestReviewing, self).setUp(api_name='reviewers')
self.list_url = list_url('reviewing')
self.user = UserProfile.objects.get(pk=2519)
self.req = RequestFactory().get('/')
Expand Down Expand Up @@ -54,3 +66,211 @@ def test_some(self):
data = json.loads(res.content)
eq_(data['objects'][0]['resource_uri'],
get_absolute_url(get_url('app', '337141'), absolute=False))


class TestApiReviewer(BaseOAuth, ESTestCase):
fixtures = fixture('webapp_337141', 'user_2519')

def setUp(self, api_name='reviewers'):
super(TestApiReviewer, self).setUp(api_name=api_name)
self.user = User.objects.get(pk=2519)
self.profile = self.user.get_profile()
self.profile.update(read_dev_agreement=datetime.now())
self.grant_permission(self.profile, 'Apps:Review')

self.access = Access.objects.create(
key='test_oauth_key', secret=generate(), user=self.user)
self.client = OAuthClient(self.access, api_name=api_name)
self.url = list_url('search')

self.webapp = Webapp.objects.get(pk=337141)
self.category = Category.objects.create(name='test',
type=amo.ADDON_WEBAPP)

self.webapp.update(status=amo.STATUS_PENDING)
self.refresh('webapp')

def test_anonymous_access(self):
res = self.anon.get(self.url)
eq_(res.status_code, 401)

def test_non_reviewer_access(self):
GroupUser.objects.filter(group__rules='Apps:Review',
user=self.profile).delete()
res = self.client.get(self.url)
eq_(res.status_code, 401)

def test_owner_still_non_reviewer_access(self):
user = Webapp.objects.get(pk=337141).authors.all()[0].user
access = Access.objects.create(
key='test_oauth_key_owner', secret=generate(), user=user)
client = OAuthClient(access, api_name='reviewers')
res = client.get(self.url)
eq_(res.status_code, 401)

def test_status(self):
res = self.client.get(self.url)
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

res = self.client.get(self.url + ({'status': 'pending'},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

res = self.client.get(self.url + ({'status': 'rejected'},))
eq_(res.status_code, 200)
objs = res.json['objects']
eq_(len(objs), 0)

self.webapp.update(status=amo.STATUS_REJECTED)
self.refresh('webapp')

res = self.client.get(self.url + ({'status': 'rejected'},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

self.webapp.update(status=amo.STATUS_PUBLIC)
self.refresh('webapp')

res = self.client.get(self.url + ({'status': 'public'},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

res = self.client.get(self.url + ({'status': 'any'},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

res = self.client.get(self.url + ({'status': 'vindaloo'},))
eq_(res.status_code, 400)
error = res.json['error_message']
eq_(error.keys(), ['status'])

def test_is_privileged(self):
res = self.client.get(self.url + ({'is_privileged': True},))
eq_(res.status_code, 200)
objs = res.json['objects']
eq_(len(objs), 0)

res = self.client.get(self.url + ({'is_privileged': False},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

res = self.client.get(self.url + ({'is_privileged': None},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

@patch('versions.models.Version.is_privileged', True)
def test_is_privileged_true(self):
self.webapp.save()
self.refresh('webapp')

res = self.client.get(self.url + ({'is_privileged': False},))
eq_(res.status_code, 200)
objs = res.json['objects']
eq_(len(objs), 0)

res = self.client.get(self.url + ({'is_privileged': True},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

res = self.client.get(self.url + ({'is_privileged': None},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

def test_is_escalated(self):
res = self.client.get(self.url + ({'is_escalated': True},))
eq_(res.status_code, 200)
objs = res.json['objects']
eq_(len(objs), 0)

res = self.client.get(self.url + ({'is_escalated': False},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

res = self.client.get(self.url + ({'is_escalated': None},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

def test_has_editors_comment(self):
res = self.client.get(self.url + ({'has_editor_comment': True},))
eq_(res.status_code, 200)
objs = res.json['objects']
eq_(len(objs), 0)

res = self.client.get(self.url + ({'has_editor_comment': False},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

res = self.client.get(self.url + ({'has_editor_comment': None},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

def test_has_info_request(self):
res = self.client.get(self.url + ({'has_info_request': True},))
eq_(res.status_code, 200)
objs = res.json['objects']
eq_(len(objs), 0)

res = self.client.get(self.url + ({'has_info_request': False},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

res = self.client.get(self.url + ({'has_info_request': None},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

def test_addon_type(self):
res = self.client.get(self.url + ({'type': 'app'},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

res = self.client.get(self.url + ({'type': 'theme'},))
eq_(res.status_code, 200)
objs = res.json['objects']
eq_(len(objs), 0)

res = self.client.get(self.url + ({'type': 'vindaloo'},))
eq_(res.status_code, 400)
error = res.json['error_message']
eq_(error.keys(), ['type'])

def test_no_region_filtering(self):
self.webapp.addonexcludedregion.create(region=mkt.regions.BR.id)
self.webapp.save()
self.refresh('webapp')

res = self.client.get(self.url + ({'region': 'br'},))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

def test_no_feature_profile_filtering(self):
self.create_switch('buchets')
feature_profile = FeatureProfile().to_signature()
qs = {'q': 'something', 'pro': feature_profile, 'dev': 'firefoxos'}

# Enable an app feature that doesn't match one in our profile.
self.webapp.current_version.features.update(has_pay=True)
self.webapp.save()
self.refresh('webapp')

res = self.client.get(self.url + (qs,))
eq_(res.status_code, 200)
obj = res.json['objects'][0]
eq_(obj['slug'], self.webapp.app_slug)

0 comments on commit caeaa08

Please sign in to comment.