diff --git a/docs/topics/api/misc.rst b/docs/topics/api/misc.rst index b3562b0e11..348a40b183 100644 --- a/docs/topics/api/misc.rst +++ b/docs/topics/api/misc.rst @@ -50,7 +50,7 @@ A list of the featured apps on the Marketplace. GET /api/v1/home/featured/ :param dev: the device requesting the homepage, results will be tailored to the device which will be one of: `firefoxos` (Firefox OS), `desktop`, `android` (mobile). - :param category: the id of the category to filter on. + :param category: the id or slug of the category to filter on. :param limit: the number of responses. **Response** diff --git a/mkt/api/forms.py b/mkt/api/forms.py index 6b8404a8a4..d13a411d9a 100644 --- a/mkt/api/forms.py +++ b/mkt/api/forms.py @@ -83,6 +83,28 @@ class JSONField(forms.Field): return value +class SluggableModelChoiceField(forms.ModelChoiceField): + """ + A model choice field that can accept either a slug or a pk and adapts + itself based on that. Requries: `sluggable_to_field_name` to be set as + the field that we will base the slug on. + """ + + def __init__(self, *args, **kw): + if 'sluggable_to_field_name' not in kw: + raise ValueError('sluggable_to_field_name is required.') + self.sluggable_to_field_name = kw.pop('sluggable_to_field_name') + return super(SluggableModelChoiceField, self).__init__(*args, **kw) + + def to_python(self, value): + try: + if not value.isdigit(): + self.to_field_name = self.sluggable_to_field_name + except AttributeError: + pass + return super(SluggableModelChoiceField, self).to_python(value) + + def parse(file_, require_name=False, require_type=None): try: if not set(['data', 'type']).issubset(set(file_.keys())): diff --git a/mkt/api/tests/test_forms.py b/mkt/api/tests/test_forms.py index c7e5124326..dfc9ea1b96 100644 --- a/mkt/api/tests/test_forms.py +++ b/mkt/api/tests/test_forms.py @@ -1,11 +1,13 @@ import base64 -from nose.tools import eq_ +import mock +from nose.tools import eq_, ok_ from addons.models import Addon import amo import amo.tests -from mkt.api.forms import PreviewJSONForm, StatusForm +from mkt.api.forms import (PreviewJSONForm, SluggableModelChoiceField, + StatusForm) class TestPreviewForm(amo.tests.TestCase, amo.tests.AMOPaths): @@ -73,3 +75,26 @@ class TestSubmitForm(amo.tests.TestCase): self.addon.status = s status = StatusForm(instance=self.addon).fields['status'] eq_([k for k, v in status.choices], [k]) + + +class TestSluggableChoiceField(amo.tests.TestCase): + + def setUp(self): + self.fld = SluggableModelChoiceField(mock.Mock(), + sluggable_to_field_name='foo') + + def test_nope(self): + with self.assertRaises(ValueError): + SluggableModelChoiceField() + + def test_slug(self): + self.fld.to_python(value='asd') + ok_(self.fld.to_field_name, 'foo') + + def test_pk(self): + self.fld.to_python(value='1') + ok_(self.fld.to_field_name is None) + + def test_else(self): + self.fld.to_python(value=None) + ok_(self.fld.to_field_name is None) diff --git a/mkt/home/forms.py b/mkt/home/forms.py index 84c2f8fda9..2a39b88ab8 100644 --- a/mkt/home/forms.py +++ b/mkt/home/forms.py @@ -2,16 +2,17 @@ from django import forms from addons.models import Category from mkt import regions +from mkt.api.forms import SluggableModelChoiceField class Featured(forms.Form): CHOICES = ('android', 'desktop', 'firefoxos') - dev = forms.ChoiceField(choices=[(c, c) for c in CHOICES], - required=False) + dev = forms.ChoiceField(choices=[(c, c) for c in CHOICES], required=False) limit = forms.IntegerField(max_value=20, min_value=1, required=False) - category = forms.ModelChoiceField(queryset=Category.objects.all(), - required=False) + category = SluggableModelChoiceField(queryset=Category.objects.all(), + sluggable_to_field_name='slug', + required=False) region = forms.ChoiceField(choices=list(regions.REGIONS_DICT.items()), required=False) diff --git a/mkt/home/tests/test_api.py b/mkt/home/tests/test_api.py index 162b1eb191..7577805777 100644 --- a/mkt/home/tests/test_api.py +++ b/mkt/home/tests/test_api.py @@ -18,7 +18,7 @@ from mkt.zadmin.models import FeaturedApp, FeaturedAppRegion class TestForm(amo.tests.TestCase): def lookup(self, region, device): - form = Featured({'device': device}, region=region) + form = Featured({'dev': device}, region=region) ok_(form.is_valid(), form.errors) return form.as_featured() @@ -71,7 +71,8 @@ class TestFeaturedHomeHandler(BaseOAuth): super(TestFeaturedHomeHandler, self).setUp(api_name='home') self.list_url = list_url('featured') self.cat = Category.objects.create(name='awesome', - type=amo.ADDON_WEBAPP) + type=amo.ADDON_WEBAPP, + slug='awesome') # App, no category, worldwide region. self.app1 = Webapp.objects.create(status=amo.STATUS_PUBLIC, @@ -124,10 +125,16 @@ class TestFeaturedHomeHandler(BaseOAuth): self.assertSetEqual([o['slug'] for o in data['objects']], ['app-1', 'app-3']) - def test_get_category(self): - res = self.anon.get(self.list_url, data={'category': self.cat.pk}) + def _get_category(self, data): + res = self.anon.get(self.list_url, data=data) data = json.loads(res.content) eq_(res.status_code, 200) eq_(data['meta']['total_count'], 1) # App2 is in the category. eq_(data['objects'][0]['slug'], self.app2.app_slug) + + def test_get_category(self): + self._get_category({'category': self.cat.pk}) + + def test_get_slug(self): + self._get_category({'category': self.cat.slug})