Is there a better way to switch between HTML and JSON output in Pyramid?

dave picture dave · Jan 8, 2011 · Viewed 7.4k times · Source
# /test{.format} no longer seems to work...
config.add_route('test', '/test.{ext}', view='ms.views.test')

views.py:

from pyramid.response import Response
from pyramid.renderers import render

import json

def test(request):
    extension = request.matchdict['ext']
    variables = {'name' : 'blah', 'asd' : 'sdf'}

    if extension == 'html':
        output = render('mypackage:templates/blah.pt', variables, request=request)

    if extension == 'json':
        output = json.dumps(variables)

    return Response(output)

Is there an easier way to do this? With Pylons, it was a simple:

def test(self, format='html'):
    c.variables = {'a' : '1', 'b' : '2'}

    if format == 'json':
        return json.dumps(c.variables)

    return render('/templates/blah.html')

I suspect I'm approaching this the wrong way...?

Answer

andreypopp picture andreypopp · Jan 8, 2011

I think, the better way is to add the same view twice with difference renderers. Suppose we have the following view:

def my_view(request):
    return {"message": "Hello, world!"}

Now in our configuration we can add the same view twice:

from pyramid.config import Configurator
config = Configurator()
config.add_route('test', '/test', my_view, renderer="templates/my_template.mako")
config.add_route('test', '/test', my_view, renderer="json", xhr=True)

What we have now:

  1. View my_view will render template "templates/my_template.mako" with returned dict provided as context if we will point our browser to url /test.
  2. If we will make XHR request with my_view will be called again, but now returned dict will be encoded as JSON and transmitted back to caller (please read docs about checking if request was done via XHR).

The same idea we can use for defining different routes but with the same view attached to them:

from pyramid.config import Configurator
config = Configurator()
config.add_route('test', '/test', my_view, renderer="templates/my_template.mako")
config.add_route('test_json', '/test.json', my_view, renderer="json")

Now /test will trigger template rendering, but /test.json will return just JSON encoded string.

You can go further and make dispatching to the right renderer via accept argument of add_router method:

from pyramid.config import Configurator
config = Configurator()
config.add_route('test', '/test', my_view, renderer="templates/my_template.mako")
config.add_route('test', '/test', my_view, renderer="json", accept="application/json")

If request comes with header Accept set to application/json value JSON will be returned, otherwise you got rendered template.

Note, this will work only if you have predefined set of data formats in which you want to encode responses from your views, but it's the usual case. In case you need dynamical dispatching you can decorate your views with decorate argument of add_route which will choose the right renderer with your rules.