diff options
| author | Chris McDonough <chrism@plope.com> | 2011-07-01 00:59:11 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2011-07-01 00:59:11 -0400 |
| commit | c1f3d0fd89eb7f62a7de365ca5c0ef5600ffd900 (patch) | |
| tree | 77f6157691367a297455d25b7198a019871d8580 | |
| parent | dc7bcb4b633718267a2509a580faf45efe338630 (diff) | |
| download | pyramid-c1f3d0fd89eb7f62a7de365ca5c0ef5600ffd900.tar.gz pyramid-c1f3d0fd89eb7f62a7de365ca5c0ef5600ffd900.tar.bz2 pyramid-c1f3d0fd89eb7f62a7de365ca5c0ef5600ffd900.zip | |
Add JSONP renderer
| -rw-r--r-- | CHANGES.txt | 6 | ||||
| -rw-r--r-- | docs/api/renderers.rst | 2 | ||||
| -rw-r--r-- | docs/narr/renderers.rst | 68 | ||||
| -rw-r--r-- | docs/whatsnew-1.1.rst | 3 | ||||
| -rw-r--r-- | pyramid/renderers.py | 68 | ||||
| -rw-r--r-- | pyramid/tests/test_renderers.py | 25 |
6 files changed, 171 insertions, 1 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 1f0549895..9bf8197ab 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -10,6 +10,12 @@ Bug Fixes tests which use DummyRequest instead of a "real" request, so they know things are deprecated without necessarily needing a functional test suite. +Features +-------- + +- Add JSONP renderer (see "JSONP renderer" in the Renderers chapter of the + documentation). + 1.1a3 (2011-06-26) ================== diff --git a/docs/api/renderers.rst b/docs/api/renderers.rst index 459639a46..c13694219 100644 --- a/docs/api/renderers.rst +++ b/docs/api/renderers.rst @@ -11,3 +11,5 @@ .. autofunction:: render_to_response +.. autoclass:: JSONP + diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 18cc8e539..f329a7af9 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -228,6 +228,74 @@ Views which use the JSON renderer can vary non-body response attributes by using the api of the ``request.response`` attribute. See :ref:`request_response_attr`. +.. _jsonp_renderer: + +JSONP Renderer +-------------- + +.. note:: This feature is new in Pyramid 1.1. + +:class:`pyramid.renderers.JSONP` is a `JSONP +<http://en.wikipedia.org/wiki/JSONP>`_ renderer factory helper which +implements a hybrid json/jsonp renderer. JSONP is useful for making +cross-domain AJAX requests. + +Unlike other renderers, a JSONP renderer needs to be configured at startup +time "by hand". Configure a JSONP renderer using the +:meth:`pyramid.config.Configurator.add_renderer` method: + +.. code-block:: python + + from pyramid.config import Configurator + + config = Configurator() + config.add_renderer('jsonp', JSONP(param_name='callback')) + +Once this renderer is registered via +:meth:`~pyramid.config.Configurator.add_renderer` as above, you can use +``jsonp`` as the ``renderer=`` parameter to ``@view_config`` or +:meth:`pyramid.config.Configurator.add_view``: + +.. code-block:: python + + from pyramid.view import view_config + + @view_config(renderer='jsonp') + def myview(request): + return {'greeting':'Hello world'} + +When a view is called that uses a JSONP renderer: + +- If there is a parameter in the request's HTTP query string (aka + ``request.GET``) that matches the ``param_name`` of the registered JSONP + renderer (by default, ``callback``), the renderer will return a JSONP + response. + +- If there is no callback parameter in the request's query string, the + renderer will return a 'plain' JSON response. + +Javscript library AJAX functionality will help you make JSONP requests. +For example, JQuery has a `getJSON function +<http://api.jquery.com/jQuery.getJSON/>`_, and has equivalent (but more +complicated) functionality in its `ajax function +<http://api.jquery.com/jQuery.ajax/>`_. + +For example (Javascript): + +.. code-block:: javascript + + var api_url = 'http://api.geonames.org/timezoneJSON' + + '?lat=38.301733840000004' + + '&lng=-77.45869621' + + '&username=fred' + + '&callback=?'; + jqhxr = $.getJSON(api_url); + +The string ``callback=?`` above in the the ``url`` param to the JQuery +``getAjax`` function indicates to jQuery that the query should be made as +a JSONP request; the ``callback`` parameter will be automatically filled +in for you and used. + .. index:: pair: renderer; chameleon diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 4d7567886..9895858cd 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -94,6 +94,9 @@ Default HTTP Exception View Minor Feature Additions ----------------------- +- A `JSONP <http://en.wikipedia.org/wiki/JSONP>`_ renderer. See + :ref:`jsonp_renderer` for more details. + - New authentication policy: :class:`pyramid.authentication.SessionAuthenticationPolicy`, which uses a session to store credentials. diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 64c522eb4..b201d32c2 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -127,7 +127,6 @@ def get_renderer(renderer_name, package=None): helper = RendererHelper(name=renderer_name, package=package) return helper.renderer - # concrete renderer factory implementations (also API) def json_renderer_factory(info): @@ -154,6 +153,73 @@ def string_renderer_factory(info): return value return _render +class JSONP(object): + """ `JSONP <http://en.wikipedia.org/wiki/JSONP>`_ renderer factory helper + which implements a hybrid json/jsonp renderer. JSONP is useful for + making cross-domain AJAX requests. + + Configure a JSONP renderer using the + :meth:`pyramid.config.Configurator.add_renderer` API at application + startup time: + + .. code-block:: python + + from pyramid.config import Configurator + + config = Configurator() + config.add_renderer('jsonp', JSONP(param_name='callback')) + + Once this renderer is registered via + :meth:`~pyramid.config.Configurator.add_renderer` as above, you can use + ``jsonp`` as the ``renderer=`` parameter to ``@view_config`` or + :meth:`pyramid.config.Configurator.add_view``: + + .. code-block:: python + + from pyramid.view import view_config + + @view_config(renderer='jsonp') + def myview(request): + return {'greeting':'Hello world'} + + When a view is called that uses the JSONP renderer: + + - If there is a parameter in the request's HTTP query string that matches + the ``param_name`` of the registered JSONP renderer (by default, + ``callback``), the renderer will return a JSONP response. + + - If there is no callback parameter in the request's query string, the + renderer will return a 'plain' JSON response. + + .. note:: This feature is new in Pyramid 1.1. + + See also: :ref:`jsonp_renderer`. + """ + + def __init__(self, param_name='callback'): + self.param_name = param_name + + def __call__(self, info): + """ Returns JSONP-encoded string with content-type + ``application/javascript`` if query parameter matching + ``self.param_name`` is present in request.GET; otherwise returns + plain-JSON encoded string with content-type ``application/json``""" + def _render(value, system): + request = system['request'] + val = json.dumps(value) + callback = request.GET.get(self.param_name) + if callback is None: + ct = 'application/json' + body = val + else: + ct = 'application/javascript' + body = '%s(%s)' % (callback, val) + response = request.response + if response.content_type == response.default_content_type: + response.content_type = ct + return body + return _render + # utility functions, not API class ChameleonRendererLookup(object): diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index 68369d570..18b4caa61 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -798,6 +798,31 @@ class Test_get_renderer(unittest.TestCase): result = self._callFUT('abc/def.pt', package=pyramid.tests) self.assertEqual(result, renderer) +class TestJSONP(unittest.TestCase): + def _makeOne(self, param_name='callback'): + from pyramid.renderers import JSONP + return JSONP(param_name) + + def test_render_to_jsonp(self): + renderer_factory = self._makeOne() + renderer = renderer_factory(None) + request = testing.DummyRequest() + request.GET['callback'] = 'callback' + result = renderer({'a':'1'}, {'request':request}) + self.assertEqual(result, 'callback({"a": "1"})') + self.assertEqual(request.response.content_type, + 'application/javascript') + + def test_render_to_json(self): + renderer_factory = self._makeOne() + renderer = renderer_factory(None) + request = testing.DummyRequest() + result = renderer({'a':'1'}, {'request':request}) + self.assertEqual(result, '{"a": "1"}') + self.assertEqual(request.response.content_type, + 'application/json') + + class Dummy: pass |
