add endpoint for categories with creatured list (bug 855887)

This commit is contained in:
Allen Short 2013-04-04 11:33:33 -07:00
Родитель e48bb8bd1f
Коммит d94958b2b2
5 изменённых файлов: 119 добавлений и 11 удалений

Просмотреть файл

@ -6,6 +6,8 @@ Search API
This API allows search for apps by various properties.
.. _search-api:
Search
======
@ -17,8 +19,8 @@ The API accepts various query string parameters to filter or sort by
described below:
* `q` (optional): The query string to search for.
* `cat` (optional): The category ID to filter by. Use the category API to
find the ids of the categories.
* `cat` (optional): The category slug or ID to filter by. Use the
category API to find the ids of the categories.
* `device` (optional): Filters by supported device. One of 'desktop',
'mobile', 'tablet', or 'gaia'.
* `premium_types` (optional): Filters by whether the app is free or
@ -52,5 +54,22 @@ The API returns a list of the apps sorted by relevance (default) or
"premium_type": "free",
"resource_uri": null,
"slug": "marble-run"
}, ...
}, ...]
}
Category Listing With Featured Apps
===================================
.. http:get:: /api/v1/apps/search/creatured/
**Request**
Accepts the same parameters and returns the same objects as the normal
search interface: :ref:`search-api`. Includes 'creatured' list of
apps, listing featured apps for the requested category, if any.
**Response**:
:param meta: :ref:`meta-response-label`.
:param objects: A :ref:`listing <objects-response-label>` of :ref:`apps <app-response-label>` satisfying the search parameters.
:param creatured: A list of :ref:`apps <app-response-label>` featured for the requested category, if any
:status 200: successfully completed..

Просмотреть файл

@ -5,7 +5,7 @@ from tastypie.api import Api
from mkt.api.resources import (AppResource, CategoryResource, PreviewResource,
StatusResource, ValidationResource)
from mkt.ratings.resources import RatingResource
from mkt.search.api import SearchResource
from mkt.search.api import SearchResource, WithCreaturedResource
api = Api(api_name='apps')
@ -13,6 +13,7 @@ api.register(ValidationResource())
api.register(AppResource())
api.register(CategoryResource())
api.register(PreviewResource())
api.register(WithCreaturedResource())
api.register(SearchResource())
api.register(StatusResource())
api.register(RatingResource())

Просмотреть файл

@ -1,11 +1,15 @@
import json
from django.conf.urls import url
from tastypie import http
from tastypie.authorization import ReadOnlyAuthorization
from tastypie.utils import trailing_slash
from tower import ugettext as _
import amo
from access import acl
from addons.models import Category
from amo.helpers import absolutify
import mkt
@ -13,6 +17,7 @@ from mkt.api.authentication import OptionalOAuthAuthentication
from mkt.api.resources import AppResource
from mkt.search.views import _get_query, _filter_search
from mkt.search.forms import ApiSearchForm
from mkt.webapps.models import Webapp
class SearchResource(AppResource):
@ -30,18 +35,21 @@ class SearchResource(AppResource):
# At this time we don't have an API to the Webapp details.
return None
def get_list(self, request=None, **kwargs):
def search_form(self, request):
form = ApiSearchForm(request.GET if request else None)
if not form.is_valid():
raise self.form_errors(form)
return form.cleaned_data
def get_list(self, request=None, **kwargs):
form_data = self.search_form(request)
is_admin = acl.action_allowed(request, 'Admin', '%')
is_reviewer = acl.action_allowed(request, 'Apps', 'Review')
# Pluck out status and addon type first since it forms part of the base
# query, but only for privileged users.
status = form.cleaned_data['status']
addon_type = form.cleaned_data['type']
status = form_data['status']
addon_type = form_data['type']
base_filters = {
'type': addon_type,
@ -59,7 +67,7 @@ class SearchResource(AppResource):
region = getattr(request, 'REGION', mkt.regions.WORLDWIDE)
qs = _get_query(region, gaia=request.GAIA, mobile=request.MOBILE,
tablet=request.TABLET, filters=base_filters)
qs = _filter_search(request, qs, form.cleaned_data, region=region)
qs = _filter_search(request, qs, form_data, region=region)
paginator = self._meta.paginator_class(request.GET, qs,
resource_uri=self.get_resource_list_uri(),
limit=self._meta.limit)
@ -71,7 +79,8 @@ class SearchResource(AppResource):
page['objects'] = [self.full_dehydrate(bundle) for bundle in objs]
# This isn't as quite a full as a full TastyPie meta object,
# but at least it's namespaced that way and ready to expand.
return self.create_response(request, page)
to_be_serialized = self.alter_list_data_to_serialize(request, page)
return self.create_response(request, to_be_serialized)
def dehydrate_slug(self, bundle):
return bundle.obj.app_slug
@ -88,3 +97,38 @@ class SearchResource(AppResource):
if bundle.obj.is_packaged else None)
return bundle
def override_urls(self):
return [
url(r'^(?P<resource_name>%s)/with_creatured%s$' %
(self._meta.resource_name, trailing_slash()),
self.wrap_view('with_creatured'), name='api_with_creatured')
]
def with_creatured(self, request, **kwargs):
return WithCreaturedResource().dispatch('list', request, **kwargs)
class WithCreaturedResource(SearchResource):
class Meta(SearchResource.Meta):
authorization = ReadOnlyAuthorization()
authentication = OptionalOAuthAuthentication()
detail_allowed_methods = []
fields = SearchResource.Meta.fields + ['cat']
list_allowed_methods = ['get']
resource_name = 'search/with_creatured'
slug_lookup = None
def alter_list_data_to_serialize(self, request, data):
form_data = self.search_form(request)
region = getattr(request, 'REGION', mkt.regions.WORLDWIDE)
if not form_data['cat']:
data['creatured'] = []
return data
category = Category.objects.get(pk=form_data['cat'])
bundles = [self.build_bundle(obj=obj, request=request)
for obj in Webapp.featured(cat=category,
region=region)]
data['creatured'] = [self.full_dehydrate(bundle) for bundle in bundles]
return data

Просмотреть файл

@ -6,6 +6,8 @@ from tower import ugettext_lazy as _lazy
from addons.models import Category
import amo
from mkt.api.forms import SluggableModelChoiceField
ADDON_CHOICES = [(v, v) for k, v in amo.MKT_ADDON_TYPES_API.items()]
@ -143,8 +145,9 @@ class ApiSearchForm(forms.Form):
label=_lazy(u'Add-on type'))
status = forms.ChoiceField(required=False, choices=STATUS_CHOICES,
label=_lazy(u'Status'))
cat = forms.TypedChoiceField(required=False, coerce=int, empty_value=None,
choices=[], label=_lazy(u'Category'))
cat = SluggableModelChoiceField(queryset=Category.objects.all(),
sluggable_to_field_name='slug',
required=False)
device = forms.ChoiceField(
required=False, choices=DEVICE_CHOICES, label=_lazy(u'Device type'))
premium_types = forms.MultipleChoiceField(
@ -169,6 +172,10 @@ class ApiSearchForm(forms.Form):
'limit': 200,
})
def clean_cat(self):
if self.cleaned_data['cat']:
return self.cleaned_data['cat'].pk
def clean_type(self):
return amo.MKT_ADDON_TYPES_API_LOOKUP.get(self.cleaned_data['type'],
amo.ADDON_WEBAPP)

Просмотреть файл

@ -8,6 +8,8 @@ from nose.tools import eq_
import amo
from addons.models import AddonCategory, AddonDeviceType, Category
from amo.tests import ESTestCase
import mkt.regions
from mkt.api.base import list_url
from mkt.api.models import Access, generate
from mkt.api.tests.test_oauth import BaseOAuth, OAuthClient
from mkt.search.forms import DEVICE_CHOICES_IDS
@ -250,3 +252,38 @@ class TestApiReviewer(BaseOAuth, ESTestCase):
eq_(res.status_code, 400)
error = json.loads(res.content)['error_message']
eq_(error.keys(), ['type'])
class TestCategoriesWithCreatured(BaseOAuth, ESTestCase):
fixtures = fixture('webapp_337141', 'user_2519')
list_url = list_url('search/with_creatured')
def test_creatured_plus_category(self):
cat = Category.objects.create(type=amo.ADDON_WEBAPP, slug='shiny')
app2 = amo.tests.app_factory()
AddonCategory.objects.get_or_create(addon=app2, category=cat)
AddonCategory.objects.get_or_create(addon_id=337141, category=cat)
self.make_featured(app=app2, category=cat,
region=mkt.regions.US)
self.refresh()
res = self.client.get(self.list_url + ({'cat': 'shiny'},))
eq_(res.status_code, 200)
data = json.loads(res.content)
eq_(len(data['objects']), 2)
eq_(len(data['creatured']), 1)
eq_(int(data['creatured'][0]['id']), app2.pk)
def test_no_category(self):
cat = Category.objects.create(type=amo.ADDON_WEBAPP, slug='shiny')
app = amo.tests.app_factory()
AddonCategory.objects.get_or_create(addon=app, category=cat)
self.make_featured(app=app, category=cat,
region=mkt.regions.US)
self.refresh()
res = self.client.get(self.list_url)
eq_(res.status_code, 200)
data = json.loads(res.content)
eq_(len(data['objects']), 2)
eq_(len(data['creatured']), 0)