Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Initializing migration from django-piston to django-restframework (bug 974952) #1749

Merged
merged 1 commit into from

5 participants

@vinyll
Collaborator

Moving api.views to api.views_drf while migrating piston to DRF.

See bug 974952 for further details.
This is part 1: moving AddonDetailView and LanguageView.
The ticket is being splitted.

Latests tests results are here: https://ci-addons.allizom.org/job/marketplace-build-branch/31/ (passing)

@diox
Collaborator

Using renderers instead of having various render_* methods would be more "DRFesque" : http://www.django-rest-framework.org/api-guide/renderers

@magopian magopian commented on the diff
apps/api/renderers.py
((11 lines not shown))
+
+
+class JSONRenderer(BaseJSONRenderer):
+
+ encoder_class = JSONEncoder
+
+ def render(self, data, *args, **kwargs):
+ """
+ Serialize with JSONEncoder and reload the json to generate
+ a valid dict.
+ """
+ data = json.loads(json.dumps(data, cls=self.encoder_class))
+ return super(JSONRenderer, self).render(data, *args, **kwargs)
+
+
+class XMLTemplateRenderer(XMLRenderer):
@magopian Collaborator

can we have a docstring here to have a better understanding of the use for this class?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/api/renderers.py
((14 lines not shown))
+
+ encoder_class = JSONEncoder
+
+ def render(self, data, *args, **kwargs):
+ """
+ Serialize with JSONEncoder and reload the json to generate
+ a valid dict.
+ """
+ data = json.loads(json.dumps(data, cls=self.encoder_class))
+ return super(JSONRenderer, self).render(data, *args, **kwargs)
+
+
+class XMLTemplateRenderer(XMLRenderer):
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ request = renderer_context['request']
@magopian Collaborator

this (and the following line) will fail if render_context is None

@vinyll Collaborator
vinyll added a note

That's a feature , it should actually fail if renderer_context is None

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/api/renderers.py
((18 lines not shown))
+ """
+ Serialize with JSONEncoder and reload the json to generate
+ a valid dict.
+ """
+ data = json.loads(json.dumps(data, cls=self.encoder_class))
+ return super(JSONRenderer, self).render(data, *args, **kwargs)
+
+
+class XMLTemplateRenderer(XMLRenderer):
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ request = renderer_context['request']
+ response = renderer_context['response']
+ if not hasattr(self, 'template_name') and not response.template_name:
+ raise Exception('AddonXMLRenderer response requires '
+ 'a "template_name" kwarg.')
@magopian Collaborator

This exception message sounds weird. Maybe something like "A template_name attribute is needed"?

@vinyll Collaborator
vinyll added a note

Indeed. It's even nomore AddonXMLRenderer now but XMLTemplateRenderer.
I changed it, let me know what you think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/api/renderers.py
((20 lines not shown))
+ a valid dict.
+ """
+ data = json.loads(json.dumps(data, cls=self.encoder_class))
+ return super(JSONRenderer, self).render(data, *args, **kwargs)
+
+
+class XMLTemplateRenderer(XMLRenderer):
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ request = renderer_context['request']
+ response = renderer_context['response']
+ if not hasattr(self, 'template_name') and not response.template_name:
+ raise Exception('AddonXMLRenderer response requires '
+ 'a "template_name" kwarg.')
+ if not jingo._helpers_loaded:
+ jingo.load_helpers()
@magopian Collaborator

Is this needed? Does this piece of code run before the jingo helpers are loaded? If so, maybe add a comment?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/api/renderers.py
((25 lines not shown))
+
+class XMLTemplateRenderer(XMLRenderer):
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ request = renderer_context['request']
+ response = renderer_context['response']
+ if not hasattr(self, 'template_name') and not response.template_name:
+ raise Exception('AddonXMLRenderer response requires '
+ 'a "template_name" kwarg.')
+ if not jingo._helpers_loaded:
+ jingo.load_helpers()
+ for processor in get_standard_processors():
+ data.update(processor(request))
+ xml_env = jingo.env.overlay()
+ old_finalize = xml_env.finalize
+ xml_env.finalize = (
@magopian Collaborator

I'd love a comment here too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/api/views_drf.py
@@ -0,0 +1,74 @@
+"""
+This view is a port of the views.py file (using Piston) to DRF.
+It is a work in progress that is supposed to replace the views.py completely.
+"""
+from django.conf import settings
+
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from tower import ugettext_lazy
+
+from addons.models import Addon
+import amo
@magopian Collaborator

I believe you should put the import ... before the from ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/api/views_drf.py
((2 lines not shown))
+This view is a port of the views.py file (using Piston) to DRF.
+It is a work in progress that is supposed to replace the views.py completely.
+"""
+from django.conf import settings
+
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from tower import ugettext_lazy
+
+from addons.models import Addon
+import amo
+from amo.decorators import allow_cross_site_request
+import api
+
+from .utils import addon_to_dict
+from .renderers import JSONRenderer, XMLTemplateRenderer
@magopian Collaborator

alphabetic order

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@magopian magopian commented on the diff
apps/api/views_drf.py
((7 lines not shown))
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from tower import ugettext_lazy
+
+from addons.models import Addon
+import amo
+from amo.decorators import allow_cross_site_request
+import api
+
+from .utils import addon_to_dict
+from .renderers import JSONRenderer, XMLTemplateRenderer
+
+
+ERROR = 'error'
+OUT_OF_DATE = ugettext_lazy(
+ u'The API version, {0:.1f}, you are using is not valid. '
@magopian Collaborator

The commas aren't needed, are they? Also, extraneous space at the end.

@vinyll Collaborator
vinyll added a note

Changing this here would require to update the tests, which we'll avoid while migrating the lib.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/api/views_drf.py
((18 lines not shown))
+
+
+ERROR = 'error'
+OUT_OF_DATE = ugettext_lazy(
+ u'The API version, {0:.1f}, you are using is not valid. '
+ u'Please upgrade to the current version {1:.1f} API.')
+
+
+class AddonDetailView(APIView):
+
+ renderer_classes = (XMLTemplateRenderer, JSONRenderer,)
+
+ @allow_cross_site_request
+ def get(self, request, addon_id, api_version, format=None):
+ self.format = format or request.QUERY_PARAMS.get(
+ getattr(settings, 'URL_FORMAT_OVERRIDE'), 'xml')
@magopian Collaborator

do we really need a setting for that? Do you think we'll need a different format name later?

@vinyll Collaborator
vinyll added a note

I wouldn't go for it if django_restframework would make it that way. We do use it for DRF so that it read ?format=json which is how AMO works right now.

@diox Collaborator
diox added a note

Add a comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/api/views_drf.py
((5 lines not shown))
+from django.conf import settings
+
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from tower import ugettext_lazy
+
+from addons.models import Addon
+import amo
+from amo.decorators import allow_cross_site_request
+import api
+
+from .utils import addon_to_dict
+from .renderers import JSONRenderer, XMLTemplateRenderer
+
+
+ERROR = 'error'
@magopian Collaborator

not sure about settings a module level constant when it's only used once.

@vinyll Collaborator
vinyll added a note

You'e right about that. I got it from views.py and will be present a few times in this module.
I'm afraid it would not be obvious to replace it there where migrating the rest of the views to views_drf.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/api/views_drf.py
((45 lines not shown))
+ return Response({'msg': 'Add-on not found!'},
+ template_name='api/message.xml', status=404)
+ if addon.is_disabled:
+ return Response({'error_level': ERROR, 'msg': 'Add-on disabled.'},
+ template_name='api/message.xml', status=404)
+ # Context.
+ if self.format == 'json':
+ context = addon_to_dict(addon)
+ else:
+ context = {
+ 'api_version': version,
+ 'addon': addon,
+ 'amo': amo,
+ 'version': version
+ }
+ return Response(context, template_name='api/addon_detail.xml')
@magopian Collaborator

rendering to json or not still uses the same .xml template?

@vinyll Collaborator
vinyll added a note

Actually JSON does not use a template at all as of now. I think it could be refactored when needed and get a nice way by that time.

@diox Collaborator
diox added a note

Add a comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@diox
Collaborator

I'd remove the 2 old views you are replacing in the same commit. Do the tests pass ? Do we have enough tests on these APIs ? We really don't want to change anything for consumers.

@davidbgk davidbgk commented on the diff
apps/api/views_drf.py
@@ -0,0 +1,78 @@
+"""
+This view is a port of the views.py file (using Piston) to DRF.
+It is a work in progress that is supposed to replace the views.py completely.
@davidbgk Collaborator

My take on that kind of migration is to move the actual views.py to views_piston.py then add new DRF-based views to views.py. Then move constants from views_piston to views and import from here if necessary (or maybe better to move those to constants.py too?) From my experience the final removal is way easier and thus not forgotten but YMMV.

@vinyll Collaborator
vinyll added a note

I liked the idea and therefore started to go for that. Now I think it's better to do it afterwards for these reasons:
1. there's quite a few files that are impacted. That would make this PR heavier, less readable and lose its first purpose: initlalising a first step to move to DRF.
2. Those dependencies with external files could be removed while rewriting the code along the refactoring.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/api/views_drf.py
((13 lines not shown))
+from amo.decorators import allow_cross_site_request
+import api
+
+from .renderers import JSONRenderer, XMLTemplateRenderer
+from .utils import addon_to_dict
+
+
+ERROR = 'error'
+OUT_OF_DATE = ugettext_lazy(
+ u'The API version, {0:.1f}, you are using is not valid. '
+ u'Please upgrade to the current version {1:.1f} API.')
+
+
+class AddonDetailView(APIView):
+
+ renderer_classes = (XMLTemplateRenderer, JSONRenderer,)
@davidbgk Collaborator

Extra comma.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/api/urls.py
((6 lines not shown))
name='api.addon_detail'),
- url(r'^get_language_packs$', class_view(views.LanguageView),
+ url(r'^get_language_packs$', views_drf.LanguageView.as_view(),
@andymckay Owner

Should this be behind a waffle flag? Some of these APIs are really high traffic and should be tested well. I'm not sure how well they are tested by QA, because they haven't changed in so long.

@diox Collaborator
diox added a note

This sounds like a good idea. I commented earlier that the older view should be removed, but you're right that we might want to be more careful here.

If we want a waffle flag however, it needs to be done in a wrapper function, we can't do that in urls.py. Also, do we have statsd logging in the current API, so get an idea of its performance ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/api/tests/test_urls.py
@@ -0,0 +1,16 @@
+import waffle
+from nose.tools import eq_
+
+from amo.tests import TestCase
+
+from ..urls import switch_to_drf
+
+
+class TestDRFSwitch(TestCase):
+
+ def test_module(self):
+ view = switch_to_drf('LanguageView')
+ eq_(view.__module__, 'api.views')
+ waffle.models.Switch.objects.create(name='drf', active=True)
@diox Collaborator
diox added a note

self.create_switch('drf')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/api/tests/test_views.py
@@ -43,6 +44,16 @@ def test_json_not_implemented():
eq_(api.views.APIView().render_json({}), '{"msg": "Not implemented yet."}')
+class DRFMixin(object):
+ """
+ Tests should be run with Piston and DRF.
+ Inheriting this class will waffle switch the tests to DRF views.
+ """
+ def setUp(self):
+ super(DRFMixin, self).setUp()
+ waffle.models.Switch.objects.create(name='drf', active=True)
@diox Collaborator
diox added a note

self.create_switch('drf')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/api/urls.py
@@ -52,11 +56,21 @@ def build_urls(base, appendages):
list_regexps = build_urls(base_list_regexp, appendages)
+def switch_to_drf(view_name):
+ """
+ Waffle switch to move from Piston to DRF.
+ """
+ if waffle.switch_is_active('drf'):
@diox Collaborator
diox added a note

I don't think that'll work. The function will be called directly when evaluating urls, and the result stored in urlconf cache, so you'll never be able to switch the waffle back without restarting the server.

If we truly want to be able to switch it we need a different kind of wrapper function, that is only evaluated when the view is called.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@diox diox commented on the diff
apps/api/tests/test_urls.py
@@ -0,0 +1,15 @@
+from mock import Mock
+from nose.tools import eq_
+
+from amo.tests import TestCase
+
+from ..urls import SwitchToDRF
+
+
+class TestDRFSwitch(TestCase):
+
+ def test_piston_view(self):
+ view = SwitchToDRF('LanguageView')
+ eq_(view(Mock(), 1).__module__, 'django.http')
@diox Collaborator
diox added a note

I would have mocked the 2 views and checked the call count on both. Saves you the trouble of really calling them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@diox diox commented on the diff
apps/api/tests/test_views.py
@@ -583,6 +593,10 @@ def test_cross_origin(self):
eq_(response['Access-Control-Allow-Methods'], 'GET')
+class DRFAPITest(DRFMixin, APITest):
@diox Collaborator
diox added a note

Add a comment. Also, because I'm paranoid, I'd love to have something to make sure the piston views aren't being called here. TestDRFSwitch helps but I'd feel better with something extra added here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/api/renderers.py
((26 lines not shown))
+class XMLTemplateRenderer(XMLRenderer):
+ """
+ Renders an XML template. Supports `template_name` kwargs from `Response`
+ object or as class attribute.
+ """
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ request = renderer_context['request']
+ response = renderer_context['response']
+ if not hasattr(self, 'template_name') and not response.template_name:
+ raise Exception('the Response object is missing a "template_name"'
+ ' attribute.')
+
+ template_name = response.template_name or self.template_name
+ # This is a renderer that ensures a proper escaping.
+ # Jingo.render does not do the job here.
@diox Collaborator
diox added a note

What's the issue with jingo rendering ? Why is that happening ?

@vinyll Collaborator
vinyll added a note

Using Jingo's render() just crashes HTML escaping tests.
This code was taken from views.py and actually solves it all.

@magopian Collaborator

if it's only an issue because of jingo, you could also exclude those templates from jingo, and have them automatically rendered using Django's template engine (check the JINGO_EXCLUDE_APPS and JINGO_EXCLUDE_PATHS).

Also, please note https://bugzilla.mozilla.org/show_bug.cgi?id=976727 that reverted the jingo update (I'll be working on fixing that next week).

@vinyll Collaborator
vinyll added a note

Used template do actually use djingo. We can't exclude it here.

@magopian Collaborator

Ah, I see, sorry, I misunderstood the issue.

@diox Collaborator
diox added a note

Can't you call render_xml_to_string() instead of copy/pasting it then ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
migrations/756-piston-to-drf.sql
@@ -0,0 +1,2 @@
+INSERT INTO waffle_switch_amo (name, active, note, created, modified)
+ VALUES ('drf', 1, 'Move from Piston to DRF.', NOW(), NOW());
@diox Collaborator
diox added a note

We probably don't want to activate it by default for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@vinyll
Collaborator

Anything else blocking?
r?

@diox
Collaborator

r+

@davidbgk davidbgk merged commit dd8b782 into mozilla:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 26, 2014
  1. @vinyll
This page is out of date. Refresh to see the latest.
View
37 apps/api/renderers.py
@@ -0,0 +1,37 @@
+import json
+
+from rest_framework.renderers import (JSONRenderer as BaseJSONRenderer,
+ XMLRenderer)
+
+from amo.utils import JSONEncoder
+
+from .views import render_xml_to_string
+
+
+class JSONRenderer(BaseJSONRenderer):
+
+ encoder_class = JSONEncoder
+
+ def render(self, data, *args, **kwargs):
+ """
+ Serialize with JSONEncoder and reload the json to generate
+ a valid dict.
+ """
+ data = json.loads(json.dumps(data, cls=self.encoder_class))
+ return super(JSONRenderer, self).render(data, *args, **kwargs)
+
+
+class XMLTemplateRenderer(XMLRenderer):
@magopian Collaborator

can we have a docstring here to have a better understanding of the use for this class?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ """
+ Renders an XML template. Supports `template_name` kwargs from `Response`
+ object or as class attribute.
+ """
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ request = renderer_context['request']
+ response = renderer_context['response']
+ if not hasattr(self, 'template_name') and not response.template_name:
+ raise Exception('the Response object is missing a "template_name"'
+ ' attribute.')
+ template_name = response.template_name or self.template_name
+ return render_xml_to_string(request, template_name, data)
View
15 apps/api/tests/test_urls.py
@@ -0,0 +1,15 @@
+from mock import Mock
+from nose.tools import eq_
+
+from amo.tests import TestCase
+
+from ..urls import SwitchToDRF
+
+
+class TestDRFSwitch(TestCase):
+
+ def test_piston_view(self):
+ view = SwitchToDRF('LanguageView')
+ eq_(view(Mock(), 1).__module__, 'django.http')
@diox Collaborator
diox added a note

I would have mocked the 2 views and checked the call count on both. Saves you the trouble of really calling them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ self.create_switch('drf', db=True)
+ eq_(view(Mock()).__module__, 'rest_framework.response')
View
35 apps/api/tests/test_views.py
@@ -43,6 +43,27 @@ def test_json_not_implemented():
eq_(api.views.APIView().render_json({}), '{"msg": "Not implemented yet."}')
+class DRFMixin(object):
+ """
+ Tests should be run with Piston and DRF.
+ Inheriting this class will waffle switch the tests to DRF views.
+
+ A `test_module_url` property must be set. It should point to any URL
+ referring to the testing view (a 404 is fine). The goal of this property
+ is to ensure that the test collection does use DRF.
+ """
+ def setUp(self):
+ super(DRFMixin, self).setUp()
+ self.create_switch('drf', db=True)
+
+ def test_drf_running(self):
+ """
+ This test makes sure that is it DRF that is running in this test suite.
+ """
+ response = self.client.get(self.test_module_url, follow=True)
+ self.assertTrue('rest_framework' in response.__module__)
+
+
class UtilsTest(TestCase):
fixtures = ['base/addon_3615']
@@ -583,6 +604,13 @@ def test_cross_origin(self):
eq_(response['Access-Control-Allow-Methods'], 'GET')
+class DRFAPITest(DRFMixin, APITest):
@diox Collaborator
diox added a note

Add a comment. Also, because I'm paranoid, I'd love to have something to make sure the piston views aren't being called here. TestDRFSwitch helps but I'd feel better with something extra added here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ """
+ Run all APITest tests with DRF.
+ """
+ test_module_url = reverse('api.addon_detail', args=['1.5', 999999])
+
+
class ListTest(TestCase):
"""Tests the list view with various urls."""
fixtures = ['base/apps', 'base/users', 'base/addon_3615', 'base/featured',
@@ -1303,3 +1331,10 @@ def test_localepicker(self, get_localepicker):
<strings><![CDATA[
title=اختر لغة
]]></strings>"""))
+
+
+class DRFLanguagePacks(DRFMixin, LanguagePacks):
+ """
+ Run all LanguagePack tests with DRF.
+ """
+ test_module_url = reverse('api.language', args=['1.5'])
View
22 apps/api/urls.py
@@ -1,10 +1,11 @@
from django.conf import settings
from django.conf.urls import include, patterns, url
+import waffle
from piston.resource import Resource
from addons.urls import ADDON_ID
-from api import authentication, handlers, views
+from api import authentication, handlers, views, views_drf
API_CACHE_TIMEOUT = getattr(settings, 'API_CACHE_TIMEOUT', 500)
@@ -52,11 +53,26 @@ def build_urls(base, appendages):
list_regexps = build_urls(base_list_regexp, appendages)
+class SwitchToDRF(object):
+ """
+ Waffle switch to move from Piston to DRF.
+ """
+ def __init__(self, view_name):
+ self.view_name = view_name
+
+ def __call__(self, *args, **kwargs):
+ if waffle.switch_is_active('drf'):
+ return (getattr(views_drf, self.view_name)
+ .as_view()(*args, **kwargs))
+ else:
+ return class_view(getattr(views, self.view_name))(*args, **kwargs)
+
+
api_patterns = patterns('',
# Addon_details
- url('addon/%s$' % ADDON_ID, class_view(views.AddonDetailView),
+ url('addon/%s$' % ADDON_ID, SwitchToDRF('AddonDetailView'),
name='api.addon_detail'),
- url(r'^get_language_packs$', class_view(views.LanguageView),
+ url(r'^get_language_packs$', SwitchToDRF('LanguageView'),
name='api.language'),)
for regexp in search_regexps:
View
78 apps/api/views_drf.py
@@ -0,0 +1,78 @@
+"""
+This view is a port of the views.py file (using Piston) to DRF.
+It is a work in progress that is supposed to replace the views.py completely.
@davidbgk Collaborator

My take on that kind of migration is to move the actual views.py to views_piston.py then add new DRF-based views to views.py. Then move constants from views_piston to views and import from here if necessary (or maybe better to move those to constants.py too?) From my experience the final removal is way easier and thus not forgotten but YMMV.

@vinyll Collaborator
vinyll added a note

I liked the idea and therefore started to go for that. Now I think it's better to do it afterwards for these reasons:
1. there's quite a few files that are impacted. That would make this PR heavier, less readable and lose its first purpose: initlalising a first step to move to DRF.
2. Those dependencies with external files could be removed while rewriting the code along the refactoring.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+"""
+from django.conf import settings
+
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from tower import ugettext_lazy
+
+import amo
+from addons.models import Addon
+from amo.decorators import allow_cross_site_request
+import api
+
+from .renderers import JSONRenderer, XMLTemplateRenderer
+from .utils import addon_to_dict
+
+
+ERROR = 'error'
+OUT_OF_DATE = ugettext_lazy(
+ u'The API version, {0:.1f}, you are using is not valid. '
@magopian Collaborator

The commas aren't needed, are they? Also, extraneous space at the end.

@vinyll Collaborator
vinyll added a note

Changing this here would require to update the tests, which we'll avoid while migrating the lib.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ u'Please upgrade to the current version {1:.1f} API.')
+
+
+class AddonDetailView(APIView):
+
+ renderer_classes = (XMLTemplateRenderer, JSONRenderer)
+
+ @allow_cross_site_request
+ def get(self, request, addon_id, api_version, format=None):
+ # `settings.URL_FORMAT_OVERRIDE` referers to
+ # https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/negotiation.py#L39
+ self.format = format or request.QUERY_PARAMS.get(
+ getattr(settings, 'URL_FORMAT_OVERRIDE'), 'xml')
+ version = float(api_version)
+ # Check valid version.
+ if version < api.MIN_VERSION or version > api.MAX_VERSION:
+ msg = OUT_OF_DATE.format(version, api.CURRENT_VERSION)
+ return Response({'msg': msg}, template_name='api/message.xml',
+ status=403)
+ # Retrieve addon.
+ try:
+ addon = (Addon.objects.id_or_slug(addon_id)
+ .exclude(type=amo.ADDON_WEBAPP).get())
+ except Addon.DoesNotExist:
+ return Response({'msg': 'Add-on not found!'},
+ template_name='api/message.xml', status=404)
+ if addon.is_disabled:
+ return Response({'error_level': ERROR, 'msg': 'Add-on disabled.'},
+ template_name='api/message.xml', status=404)
+ # Context.
+ if self.format == 'json':
+ context = addon_to_dict(addon)
+ else:
+ context = {
+ 'api_version': version,
+ 'addon': addon,
+ 'amo': amo,
+ 'version': version
+ }
+ # `template_name` is only used here for XML format. Refactor this if
+ # needed some other way.
+ return Response(context, template_name='api/addon_detail.xml')
+
+
+class LanguageView(APIView):
+
+ renderer_classes = (XMLTemplateRenderer,)
+
+ def get(self, request, api_version):
+ addons = Addon.objects.filter(status=amo.STATUS_PUBLIC,
+ type=amo.ADDON_LPAPP,
+ appsupport__app=self.request.APP.id,
+ disabled_by_user=False).order_by('pk')
+ return Response({'addons': addons, 'show_localepicker': True,
+ 'api_version': api_version},
+ template_name='api/list.xml')
View
3  lib/settings_base.py
@@ -1654,3 +1654,6 @@ def read_only_mode(env):
# The currently-recommended version of the API. Any requests to versions older
# than this will include the `API-Status: Deprecated` header.
API_CURRENT_VERSION = 1
+
+# Allow URL style format override. eg. "?format=json"
+URL_FORMAT_OVERRIDE = 'format'
View
2  migrations/756-piston-to-drf.sql
@@ -0,0 +1,2 @@
+INSERT INTO waffle_switch_amo (name, active, note, created, modified)
+ VALUES ('drf', 0, 'Move from Piston to DRF.', NOW(), NOW());
Something went wrong with that request. Please try again.