CherryPy Project Download

Routes URL generation

For those who use Routes and RoutesDispatcher?, making use of the url generation capabilities can be a great timesaver.

To make Routes generate a url, call routes.url_for with keyword parameters corresponding to the Routes rules. To account for urls hidden behind mod_proxy or mod_rewrite it's also good to pass that url through cherrypy.url.

Say we have this setup:

# -*- encoding: UTF-8 -*-
import os

import cherrypy

class MainController:
    
    def index(self):
        return "This is the main page"

class BlogController:

    def index(self, entry_id):
        return "This is blog entry no. %d" % int(entry_id)

    def edit(self, entry_id):
        return "This is an edit form for blog entry no. %d" % int(entry_id)


dispatcher = None

def setup_routes():
    d = cherrypy.dispatch.RoutesDispatcher()
    d.connect('blog', 'myblog/:entry_id/:action', controller=BlogController())
    d.connect('main', ':action', controller=MainController())
    dispatcher = d
    return dispatcher

conf = {
    '/': {
        'request.dispatch': setup_routes()
    },
    '/static': {
        'tools.staticdir.on': True,
        'tools.staticdir.root': os.path.dirname(os.path.abspath(__file__)),
        'tools.staticdir.dir': 'static'
    }
}

def start(config=None):
    if config:
        cherrypy.config.update(config)
    app = cherrypy.tree.mount(None, config=conf)
    cherrypy.quickstart(app)

if __name__ == '__main__':
    start(os.path.join(os.path.dirname(__file__), 'app.conf'))

To make use of Routes url-generation, we can for example call cherrypy.url(routes.url_for(controller="blog", action="edit", entry_id="123")) - which generates an url that goes straight to the edit form of blog entry 123. It might be something like http://www.example.org/my_cp_app/myblog/123/edit.

Calling cherrypy.url(routes.url_for(...)) is tedious, especially for a template designer, so let's make things easier:

My goal here is to provide one function - url - which transparently handles both the role of cherrypy.url and routes.url_for. In it's simplest form it looks like this:

import routes
import cherrypy

def url(*args, **kwargs):
    if len(args) == 1 and len(kwargs) == 0 and type(args[0]) in (str, unicode):
        return cherrypy.url(args[0])
    else:
        return cherrypy.url(routes.url_for(*args, **kwargs))

This makes it possible to do things like this

>>> url('/static/css/main.css')
'http://www.example.org/path_to_webapp/static/css/main.css'
>>> url(controller="main")
'http://www.example.org/path_to_webapp'
>>> url(controller="blog", action="edit", entry_id=123)
'http://www.example.org/path_to_webapp/myblog/123/edit'

I find the most common use is the last one, specify a controller, an action and some parameters. In most cases, a controller is a class instance and the action is a method. This makes me want to write something like this:

>>> url.blog.edit(entry_id=123)}
'http://www.example.org/path_to_webapp/myblog/123/edit'

Of course, this is easy in Python, with the following definition of url

import routes
import cherrypy

class _ctrlchain(object):
 
    def __init__(self, name, head=None):
        if head is None:
            self.chain = list()
        else:
            self.chain = head[:]
        self.chain.append(name)
 
    def __getattr__(self, attr):
        return _ctrlchain(attr, self.chain)
 
    def __call__(self, *args, **kwargs):
        if len(self.chain) > 3:
            raise Exception("Don't know what to do with over 3 chain elements")
        if len(self.chain) > 2:
            kwargs["action"] = self.chain[2]
        if len(self.chain) > 1:
            kwargs["controller"] = self.chain[1]
 
        if len(args) == 1 and len(kwargs) == 0 and type(args[0]) in (str, unicode):
            return cherrypy.url(args[0])
        else:
            return cherrypy.url(routes.url_for(*args, **kwargs))
 
url = _ctrlchain('urlgen')

This correctly handles all the use cases above. For good measure, here are some more:

>>> url.main()
'http://www.example.org/path_to_webapp'
>>> url.blog(entry_id=123)
'http://www.example.org/path_to_webapp/myblog/123'

Of course, this becomes most useful when this function is made available in whatever template language you are using:

<li py:for="entry in blog.select()">
    <a href="${url.blog(entry_id=entry.id)}" py:content="entry.title">Placeholder for entry title</a>
</li>

For more information on Routes url generation check the Routes documentation: http://routes.groovie.org/module-routes.html#url_for

-- Arnar (arnarbi at gmail)

For more information about what kind of urls can be matched with Routes and how to do so, take a look at RoutesUrlMatching

Hosted by WebFaction

Log in as guest/cherrypy to create/edit wiki pages