20
# /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...?

X-Istence
  • 16,324
  • 6
  • 57
  • 74
dave
  • 7,717
  • 19
  • 68
  • 100
  • What's your complaint? Are you complaining that pyramid has different API's from pylons? If you don't like the pyramid API's, why not go back to Pylons? – S.Lott Jan 08 '11 at 12:44
  • Does not Pyramid use middleware? Why can't you render JSON based on what the user requests? Doing inside the view directly, is in my book, a faulty solution. If possible, take advantage over the middleware. – Anders Jan 08 '11 at 13:55

4 Answers4

51

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.

Theron Luhn
  • 3,974
  • 4
  • 37
  • 49
andreypopp
  • 6,887
  • 5
  • 26
  • 26
  • 3
    +1 I think this best reflects the "pyramid way" of doing things. It's flexible, decouples the views from the renderers making the views easier to unit test because instead of parsing an html doc you can just look for values in a dictionary. – Tom Willis Jan 08 '11 at 19:28
  • This does seem like a neat way of doing it. How would I go about applying this method to my Handlers methods? I found, in the documentation, that you can decorate functions with `view_config` which will allow you to set the renderer but I couldn't see an equivalent for URL Dispatch. – dave Jan 09 '11 at 03:19
  • Doesn't `add_handler` works like this? I've never worked with handlers, but documentation states, that "Any extra keyword arguments are passed along to add_route." – andreypopp Jan 09 '11 at 07:57
  • 1
    apparently the wsiwyg editor is messing with me here, i can't type a return character without submitting the comment... but see http://plope.com/static/pyr_so.txt – Chris McDonough Jan 11 '11 at 01:15
  • What about if you are using traversal ? – sebpiq Mar 09 '12 at 11:41
  • @sebpiq, add_view accepts same predicates as add_route, so you can use xhr=True or accept=... or whatever predicate you want — http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/api/config.html#pyramid.config.Configurator.add_view – andreypopp Mar 11 '12 at 05:08
  • @andreypopp if you're using traversal it won't work. The thing is that traversal is matching a resource name. In other words, if someone type "/home/friends/test.json" it should point to "/home/friends/test" and it kind of mean that "/home.json/friends/test.html" could be valid – Loïc Faure-Lacroix Jul 13 '12 at 05:34
  • @LoïcFaure-Lacroix yep, this is because I've used ``add_route`` instead of ``add_view``. – andreypopp Jul 23 '12 at 11:54
6

Is this what you're looking for? Pylons and Pyramid have different API's. So they will be different. You can make them a little more similar, but you can't make them identical.

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

    if extension == 'json':
        return Response( json.dumps(variables) )

    return Response( render('mypackage:templates/blah.pt', variables, request=request) )
S.Lott
  • 384,516
  • 81
  • 508
  • 779
2

Pyramid's URL Dispatch is very powerful and flexible mechanism. First of all, we'll write correct url pattern. In route pattern syntax we can use regular expressions for replacement markers.

'/test{ext:\\..*}'

Here we can see that url path should contain . (period) and then any symbols. All symbols including . (period) will be under the key ext in request.matchdict.

Of course, we can complicate the regular expression in order to specify what extensions there may be:

'/test{ext:\\.(html|json)}'

Then we adding route with our pattern:

config.add_route('test',
                 pattern='/test{ext:\\.(html|json)}')

Want to add, that we can specify the set of extensions using custom predicates.

In order to specify the default extension we can use simple pregenerator.

def default_extension(ext):
    def pregenerator(request, elements, kw):
        if 'ext' not in kw:
            kw['ext'] = ext

        return elements, kw

    return pregenerator

config.add_route('test',
                 pattern='/test{ext:\\.(html|json)}',
                 pregenerator=default_extension('.html'))

request.route_path('test')
# '/test.html'
request.route_path('test', ext='.json')
# '/test.json'

After that we'll Traversal to help us switch between html and json output:

config.add_route('test',
                 '/test{ext:\\.(html|json)}',
                 pregenerator=default_extension('.html'),
                 traverse='{ext}')

With the traverse argument in add_route we force our application to be hybrid. And we should understand that the factory which will provide context for our views must not contain the keys matching our extensions. The default root factory doesn't.

views.py:

from pyramid.view import view_config, view_defaults


@view_defaults(route_name='test')
class Test(object):
    def __init__(self, request):
        self.request = request
        self.variables = {
            'name': 'blah',
            'asd': 'sdf'
        }

    @view_config(name='.html', renderer='mypackage:templates/blah.pt')
    def html(request):
        return {
            'request': request,
            'variables': self.variables
        }

    @view_config(name='.json', renderer='json')
    def json(request):
        return {
            'request': request,
            'variables': self.variables
        }

Here we've created class Test and specify route name for it. And then we've separated methods by names of our extensions.

scraplesh
  • 1,326
  • 12
  • 20
  • I like the use of traversal for differentiating behavior on the different extensions. The view config can be dramatically simpler though. It could be a single function that accepts a request decorated with 2 `@view_config` lines, both with the `route_name`, with the different `name`s and `renderer`s. It makes the example significantly less complex (just 7 lines of code). – Elmer Jan 26 '14 at 15:20
0

Try this way:

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

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

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

This is the most similar to your pylons example. Also it shows some more friendly way to render a template or some JSON to a response.

Dobes Vandermeer
  • 8,463
  • 5
  • 43
  • 46