diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py index aae2cab25..e7b814893 100644 --- a/djangorestframework/renderers.py +++ b/djangorestframework/renderers.py @@ -26,6 +26,7 @@ __all__ = ( 'BaseRenderer', 'TemplateRenderer', 'JSONRenderer', + 'JSONPRenderer', 'DocumentingHTMLRenderer', 'DocumentingXHTMLRenderer', 'DocumentingPlainTextRenderer', @@ -113,6 +114,46 @@ class JSONRenderer(BaseRenderer): return json.dumps(obj, cls=DateTimeAwareJSONEncoder, indent=indent, sort_keys=sort_keys) +class JSONPRenderer(BaseRenderer): + """ + Renderer which serializes to JSONP + """ + media_type = 'application/json-p' + format = 'json-p' + + callback_parameter = 'callback' + + def render(self, obj=None, media_type=None): + """ + Renders *obj* into serialized JSONP. + + The callback function name is taken from the 'callback' parameter + contained in the jsonp request. + """ + callback = self.view.request.GET.get(self.callback_parameter, self.callback_parameter) + + if obj is None: + serialized_obj = '' + else: + json_renderer = self._get_renderer(JSONRenderer.media_type) + if json_renderer is None: + serialized_obj = json.dumps(obj, cls=DateTimeAwareJSONEncoder) + else: + serialized_obj = json_renderer.render(obj, JSONRenderer.media_type) + + return "%s(%s);" % (callback, serialized_obj) + + def _get_renderer(self, media_type=None): + """ + Get first view renderer that serializes *media_type*. + """ + for r in self.view.renderers: + renderer = r(self.view) + if renderer.can_handle_response(media_type): + return renderer + return None + + class XMLRenderer(BaseRenderer): """ Renderer which serializes to XML. @@ -376,6 +417,7 @@ class DocumentingPlainTextRenderer(DocumentingTemplateRenderer): DEFAULT_RENDERERS = ( JSONRenderer, + JSONPRenderer, DocumentingHTMLRenderer, DocumentingXHTMLRenderer, DocumentingPlainTextRenderer, diff --git a/djangorestframework/tests/renderers.py b/djangorestframework/tests/renderers.py index e7091c69a..cd4c7552d 100644 --- a/djangorestframework/tests/renderers.py +++ b/djangorestframework/tests/renderers.py @@ -3,8 +3,10 @@ from django import http from django.test import TestCase from djangorestframework import status +from djangorestframework.views import View from djangorestframework.compat import View as DjangoView -from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer +from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ + JSONPRenderer from djangorestframework.parsers import JSONParser, YAMLParser from djangorestframework.mixins import ResponseMixin from djangorestframework.response import Response @@ -39,10 +41,16 @@ class MockView(ResponseMixin, DjangoView): response = Response(DUMMYSTATUS, DUMMYCONTENT) return self.render(response) +class MockGETView(View): + def get(self, request, **kwargs): + return {'foo':['bar','baz']} + urlpatterns = patterns('', url(r'^.*\.(?P.+)$', MockView.as_view(renderers=[RendererA, RendererB])), url(r'^$', MockView.as_view(renderers=[RendererA, RendererB])), + url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderers=[JSONRenderer, JSONPRenderer])), + url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderers=[JSONPRenderer])), ) @@ -188,8 +196,46 @@ class JSONRendererTests(TestCase): content = renderer.render(obj, 'application/json') (data, files) = parser.parse(StringIO(content)) - self.assertEquals(obj, data) + self.assertEquals(obj, data) + +class JSONPRendererTests(TestCase): + """ + Tests specific to the JSONP Renderer + """ + + urls = 'djangorestframework.tests.renderers' + + def test_without_callback_with_json_renderer(self): + """ + Test JSONP rendering with View JSON Renderer. + """ + resp = self.client.get('/jsonp/jsonrenderer', + HTTP_ACCEPT='application/json-p') + self.assertEquals(resp.status_code, 200) + self.assertEquals(resp['Content-Type'], 'application/json-p') + self.assertEquals(resp.content, 'callback(%s);' % _flat_repr) + + def test_without_callback_without_json_renderer(self): + """ + Test JSONP rendering without View JSON Renderer. + """ + resp = self.client.get('/jsonp/nojsonrenderer', + HTTP_ACCEPT='application/json-p') + self.assertEquals(resp.status_code, 200) + self.assertEquals(resp['Content-Type'], 'application/json-p') + self.assertEquals(resp.content, 'callback(%s);' % _flat_repr) + + def test_with_callback(self): + """ + Test JSONP rendering with callback function name. + """ + callback_func = 'myjsonpcallback' + resp = self.client.get('/jsonp/nojsonrenderer?callback='+callback_func, + HTTP_ACCEPT='application/json-p') + self.assertEquals(resp.status_code, 200) + self.assertEquals(resp['Content-Type'], 'application/json-p') + self.assertEquals(resp.content, '%s(%s);' % (callback_func, _flat_repr)) if YAMLRenderer: