The Problem
You may have noticed that routes don’t work as you might expect them to in TurboGears 2.0. That is to say, that they don’t work the way the docs suggest they should.
If you just extend the TGController, it will try to do object dispatch routing, after your groovie routes have been applied.
If you just extend the DecoratedController, it will choke with an error like this:
File '/wherever/you/keep/shared/libraries/python-myapp/lib/python2.5/site-packages/TurboGears2-2.0b5.1-py2.5.egg/tg/controllers.py', line 109 in _perform_call
controller.decoration.run_hooks('before_validate', remainder,
AttributeError: 'function' object has no attribute 'decoration'That’s because the DecoratedController expects all decorated methods to have a decoration attribute added to them by the @expose decorator. What DecoratedController doesn’t understand is that its dispatch also involves adding __before__ and __after__ methods and that those support methods haven’t been decorated.
The Solution
TGController takes care of this by overloading certain methods, after it inherits from DecoratedController. My solution here is essentially the same. We’re going to create a RoutingController that inherits only from DecoratedController, but understands about these __before__ and __after__ methods in the dispatch, and handles them correctly.
class RoutingController(DecoratedController): def _perform_call(self, func, args): if not args: args = {} try: aname = str(args.get('action', 'lookup')) controller = getattr(self, aname) # If these are the __before__ or __after__ methods, they will have no decoration property # This will make the default DecoratedController._perform_call() method choke # We'll handle them just like TGController handles them. func_name = func.__name__ if func_name == '__before__' or func_name == '__after__': if func_name == '__before__' and hasattr(controller.im_class, '__before__'): return controller.im_self.__before__(*args) if func_name == '__after__' and hasattr(controller.im_class, '__after__'): return controller.im_self.__after__(*args) return else: controller = func params = args remainder = '' result = DecoratedController._perform_call( self, controller, params, remainder=remainder) except HTTPException, httpe: result = httpe # 304 Not Modified's shouldn't have a content-type set if result.status_int == 304: result.headers.pop('Content-Type', None) result._exception = True return result
Any controllers that you want to use routes for, just make them extend this RoutingController class and routing will work just as described in the docs: override the setup_routes() method of the base_config object in app_cfg.py.
RoutingController will work properly with regular object dispatch, as well as with routes, so long as your root controller inherits from TGController (the controller responsible for object dispatch) and the following line is kept at the end of the setup_routes() method:
map.connect('*url', controller='root', action='routes_placeholder')
Check out the links below for more discussion of the issue, and some examples of how to setup routes.
Update 12/06/2009
To clarify, if all you want to use routes for is to specify the route to a controller that will then perform object dispatch on the URL, the default TGController class will do just fine.
If you want to specify the action, or any keyword args inside your routes, however, you’ll need to extend DecoratedController as detailed above.
References:
- TG2 and custom routes on the TG mailing list.
- Validation inconsistencies and custom routes on the TG Mailing list.
- The TG2.0 Routes Integration docs.
Jorge Vargas Says:
This is really interesting, have you proposed it as a patch to TG itself? I haven’t really used Routes like this but it “should work” with the stuff in the docs. Have you tried TG2.1? after the rewrite of dispatch DecoratedController will behave better and I believe the new one will respect before and after. Again I don’t know for sure as this is code I don’t need/use.
Jorge Vargas Says:
OK ignore my previous comment when I say suggest a patch I just found http://groups.google.com/group/turbogears/browse_thread/thread/16d446e0aa796778/ I’m very interested in getting this working will you mind dropping by #turbogears on freenode talk to elpargo or percious. We’ll get your fix into the tip/trunk