Commit 1bdc5eac by Tom Christie

Add JSONP. Fixes #82

parent 500b0dcd
...@@ -26,6 +26,7 @@ __all__ = ( ...@@ -26,6 +26,7 @@ __all__ = (
'BaseRenderer', 'BaseRenderer',
'TemplateRenderer', 'TemplateRenderer',
'JSONRenderer', 'JSONRenderer',
'JSONPRenderer',
'DocumentingHTMLRenderer', 'DocumentingHTMLRenderer',
'DocumentingXHTMLRenderer', 'DocumentingXHTMLRenderer',
'DocumentingPlainTextRenderer', 'DocumentingPlainTextRenderer',
...@@ -113,6 +114,28 @@ class JSONRenderer(BaseRenderer): ...@@ -113,6 +114,28 @@ class JSONRenderer(BaseRenderer):
return json.dumps(obj, cls=DateTimeAwareJSONEncoder, indent=indent, sort_keys=sort_keys) return json.dumps(obj, cls=DateTimeAwareJSONEncoder, indent=indent, sort_keys=sort_keys)
class JSONPRenderer(JSONRenderer):
"""
Renderer which serializes to JSONP
"""
media_type = 'application/json-p'
format = 'json-p'
renderer_class = JSONRenderer
callback_parameter = 'callback'
def _get_callback(self):
return self.view.request.GET.get(self.callback_parameter, self.callback_parameter)
def _get_renderer(self):
return self.renderer_class(self.view)
def render(self, obj=None, media_type=None):
callback = self._get_callback()
json = self._get_renderer().render(obj, media_type)
return "%s(%s);" % (callback, json)
class XMLRenderer(BaseRenderer): class XMLRenderer(BaseRenderer):
""" """
Renderer which serializes to XML. Renderer which serializes to XML.
...@@ -376,6 +399,7 @@ class DocumentingPlainTextRenderer(DocumentingTemplateRenderer): ...@@ -376,6 +399,7 @@ class DocumentingPlainTextRenderer(DocumentingTemplateRenderer):
DEFAULT_RENDERERS = ( JSONRenderer, DEFAULT_RENDERERS = ( JSONRenderer,
JSONPRenderer,
DocumentingHTMLRenderer, DocumentingHTMLRenderer,
DocumentingXHTMLRenderer, DocumentingXHTMLRenderer,
DocumentingPlainTextRenderer, DocumentingPlainTextRenderer,
......
from django.conf.urls.defaults import patterns, url from django.conf.urls.defaults import patterns, url
from django import http
from django.test import TestCase from django.test import TestCase
from djangorestframework import status from djangorestframework import status
from djangorestframework.views import View
from djangorestframework.compat import View as DjangoView from djangorestframework.compat import View as DjangoView
from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer,\ from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
XMLRenderer XMLRenderer, JSONPRenderer
from djangorestframework.parsers import JSONParser, YAMLParser from djangorestframework.parsers import JSONParser, YAMLParser
from djangorestframework.mixins import ResponseMixin from djangorestframework.mixins import ResponseMixin
from djangorestframework.response import Response from djangorestframework.response import Response
from djangorestframework.utils.mediatypes import add_media_type_param
from StringIO import StringIO from StringIO import StringIO
import datetime import datetime
...@@ -21,31 +20,41 @@ DUMMYCONTENT = 'dummycontent' ...@@ -21,31 +20,41 @@ DUMMYCONTENT = 'dummycontent'
RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x
RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x
class RendererA(BaseRenderer): class RendererA(BaseRenderer):
media_type = 'mock/renderera' media_type = 'mock/renderera'
format="formata" format = "formata"
def render(self, obj=None, media_type=None): def render(self, obj=None, media_type=None):
return RENDERER_A_SERIALIZER(obj) return RENDERER_A_SERIALIZER(obj)
class RendererB(BaseRenderer): class RendererB(BaseRenderer):
media_type = 'mock/rendererb' media_type = 'mock/rendererb'
format="formatb" format = "formatb"
def render(self, obj=None, media_type=None): def render(self, obj=None, media_type=None):
return RENDERER_B_SERIALIZER(obj) return RENDERER_B_SERIALIZER(obj)
class MockView(ResponseMixin, DjangoView): class MockView(ResponseMixin, DjangoView):
renderers = (RendererA, RendererB) renderers = (RendererA, RendererB)
def get(self, request, **kwargs): def get(self, request, **kwargs):
response = Response(DUMMYSTATUS, DUMMYCONTENT) response = Response(DUMMYSTATUS, DUMMYCONTENT)
return self.render(response) return self.render(response)
class MockGETView(View):
def get(self, request, **kwargs):
return {'foo': ['bar', 'baz']}
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])), url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
url(r'^$', 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])),
) )
...@@ -92,7 +101,7 @@ class RendererIntegrationTests(TestCase): ...@@ -92,7 +101,7 @@ class RendererIntegrationTests(TestCase):
self.assertEquals(resp['Content-Type'], RendererB.media_type) self.assertEquals(resp['Content-Type'], RendererB.media_type)
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT)) self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS) self.assertEquals(resp.status_code, DUMMYSTATUS)
def test_specified_renderer_serializes_content_on_accept_query(self): def test_specified_renderer_serializes_content_on_accept_query(self):
"""The '_accept' query string should behave in the same way as the Accept header.""" """The '_accept' query string should behave in the same way as the Accept header."""
resp = self.client.get('/?_accept=%s' % RendererB.media_type) resp = self.client.get('/?_accept=%s' % RendererB.media_type)
...@@ -147,13 +156,7 @@ class RendererIntegrationTests(TestCase): ...@@ -147,13 +156,7 @@ class RendererIntegrationTests(TestCase):
self.assertEquals(resp.status_code, DUMMYSTATUS) self.assertEquals(resp.status_code, DUMMYSTATUS)
_flat_repr = '{"foo": ["bar", "baz"]}' _flat_repr = '{"foo": ["bar", "baz"]}'
_indented_repr = '{\n "foo": [\n "bar", \n "baz"\n ]\n}'
_indented_repr = """{
"foo": [
"bar",
"baz"
]
}"""
class JSONRendererTests(TestCase): class JSONRendererTests(TestCase):
...@@ -165,71 +168,106 @@ class JSONRendererTests(TestCase): ...@@ -165,71 +168,106 @@ class JSONRendererTests(TestCase):
""" """
Test basic JSON rendering. Test basic JSON rendering.
""" """
obj = {'foo':['bar','baz']} obj = {'foo': ['bar', 'baz']}
renderer = JSONRenderer(None) renderer = JSONRenderer(None)
content = renderer.render(obj, 'application/json') content = renderer.render(obj, 'application/json')
self.assertEquals(content, _flat_repr) self.assertEquals(content, _flat_repr)
def test_with_content_type_args(self): def test_with_content_type_args(self):
""" """
Test JSON rendering with additional content type arguments supplied. Test JSON rendering with additional content type arguments supplied.
""" """
obj = {'foo':['bar','baz']} obj = {'foo': ['bar', 'baz']}
renderer = JSONRenderer(None) renderer = JSONRenderer(None)
content = renderer.render(obj, 'application/json; indent=2') content = renderer.render(obj, 'application/json; indent=2')
self.assertEquals(content, _indented_repr) self.assertEquals(content, _indented_repr)
def test_render_and_parse(self): def test_render_and_parse(self):
""" """
Test rendering and then parsing returns the original object. Test rendering and then parsing returns the original object.
IE obj -> render -> parse -> obj. IE obj -> render -> parse -> obj.
""" """
obj = {'foo':['bar','baz']} obj = {'foo': ['bar', 'baz']}
renderer = JSONRenderer(None) renderer = JSONRenderer(None)
parser = JSONParser(None) parser = JSONParser(None)
content = renderer.render(obj, 'application/json') content = renderer.render(obj, 'application/json')
(data, files) = parser.parse(StringIO(content)) (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: if YAMLRenderer:
_yaml_repr = 'foo: [bar, baz]\n' _yaml_repr = 'foo: [bar, baz]\n'
class YAMLRendererTests(TestCase): class YAMLRendererTests(TestCase):
""" """
Tests specific to the JSON Renderer Tests specific to the JSON Renderer
""" """
def test_render(self): def test_render(self):
""" """
Test basic YAML rendering. Test basic YAML rendering.
""" """
obj = {'foo':['bar','baz']} obj = {'foo': ['bar', 'baz']}
renderer = YAMLRenderer(None) renderer = YAMLRenderer(None)
content = renderer.render(obj, 'application/yaml') content = renderer.render(obj, 'application/yaml')
self.assertEquals(content, _yaml_repr) self.assertEquals(content, _yaml_repr)
def test_render_and_parse(self): def test_render_and_parse(self):
""" """
Test rendering and then parsing returns the original object. Test rendering and then parsing returns the original object.
IE obj -> render -> parse -> obj. IE obj -> render -> parse -> obj.
""" """
obj = {'foo':['bar','baz']} obj = {'foo': ['bar', 'baz']}
renderer = YAMLRenderer(None) renderer = YAMLRenderer(None)
parser = YAMLParser(None) parser = YAMLParser(None)
content = renderer.render(obj, 'application/yaml') content = renderer.render(obj, 'application/yaml')
(data, files) = parser.parse(StringIO(content)) (data, files) = parser.parse(StringIO(content))
self.assertEquals(obj, data) self.assertEquals(obj, data)
class XMLRendererTestCase(TestCase): class XMLRendererTestCase(TestCase):
""" """
Tests specific to the XML Renderer Tests specific to the XML Renderer
...@@ -285,8 +323,7 @@ class XMLRendererTestCase(TestCase): ...@@ -285,8 +323,7 @@ class XMLRendererTestCase(TestCase):
content = renderer.render({'field': None}, 'application/xml') content = renderer.render({'field': None}, 'application/xml')
self.assertXMLContains(content, '<field></field>') self.assertXMLContains(content, '<field></field>')
def assertXMLContains(self, xml, string): def assertXMLContains(self, xml, string):
self.assertTrue(xml.startswith('<?xml version="1.0" encoding="utf-8"?>\n<root>')) self.assertTrue(xml.startswith('<?xml version="1.0" encoding="utf-8"?>\n<root>'))
self.assertTrue(xml.endswith('</root>')) self.assertTrue(xml.endswith('</root>'))
self.assertTrue(string in xml, '%r not in %r' % (string, xml)) self.assertTrue(string in xml, '%r not in %r' % (string, xml))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment