Alternative controller searching method
 | Warning
This article is in DRAFT status. It needs rewording and style improvement.
Documentation masters, please help!  |
 | Be Careful
This article is for advanced users! |
 | Be Careful
This article is for Pylons 0.9.6!
In older versions the configuration layout is different, but it is still possible to implement this, I think. |
Intro
Sometimes the default controller finding method is not very handy. For example if you want controllers to be loaded from other package, or you are implementing some kind of plugin system where plugins can add their controllers to the routing.
Here's some advices on how can you implement it.
First, Routes
define and use a controller_scan method, that scans modules in a specified directory by default, but we can override it. Then, when Routes match a route to a controller name, PylonsBaseWSGIApp's find_controller method is used to get a class from the controller name. We can subclass the the PylonsBaseWSGIApp and override that method.
For example, we want to have a dictionary of controller keys and classes, like this:
1
2
3
4
5 | from otherpackage.controllers import hello
from mypackage.controllers import blog
from mypackage.controllers.page import PageController
controllers = {'hello': hello.Controller, 'blog': blog.BlogController, 'page': PageController}
|
Implementation
Now we need to setup our application to search controllers using this variable. Create a test application for this tutorial and a "Hello, world!" controller:
1
2
3 | $ paster create -t pylons testapp
$ cd testapp/
$ paster controller hello
|
Now go to testapp/testapp/config directory and open the environment.py file. The list of controllers is a part of application environment so it's sane to create it here. And for global use, we'll save it in the config object. So in the load_environment function, just after this line:
1
2 | config.init_app(global_conf, app_conf, package='testapp',
template_engine='mako', paths=paths)
|
add something like the following:
1
2
3
4
5
6
7 | from testapp.controllers.error import ErrorController
from testapp.controllers.template import TemplateController
from testapp.controllers.hello import HelloController
config['pylons.controllers'] = {'error': ErrorController,
'hello': HelloController,
'template': TemplateController}
|
So, now we have a mapping of controller names to actual controller classes. We will make Pylons use it instead of directory lookup method provided by default.
Open the routing.py file and at the top of make_map function, locate this call:
1
2 | map = Mapper(directory=config['pylons.paths']['controllers'],
always_scan=config['debug'])
|
This means that the Mapper will try to get the available controller list by listing files in our project's controllers directory. But we can specify own function to get the available controller list. Change the line above to this:
1
2 | map = Mapper(controller_scan=lambda: config['pylons.controllers'].keys(),
always_scan=config['debug'])
|
Okay, now Routes will use our dictionary keys for matching controller names. But still, Pylons will load controllers using the old method (the controller module <package_name>.controllers.<controller_name> is loaded and the class with name <controller_name>.title()+'Controller' is loaded from it). To change this, we need to override the find_controller method in Pylon's base WSGI application.
Open the middleware.py file and find these lines:
1
2 | # The Pylons WSGI app
app = PylonsApp()
|
Add the following ABOVE:
1
2
3
4
5 | from pylons.wsgiapp import PylonsBaseWSGIApp
class MyBaseWSGIApp(PylonsBaseWSGIApp):
def find_controller(self, controller):
return config['pylons.controllers'][controller]
|
Now, change the the app = PylonsApp() line, mentioned above to this:
1
2 | # The Pylons WSGI app using custom controller find method
app = PylonsApp(base_wsgi_app=MyBaseWSGIApp)
|
That's all. Now the default controller finding method is overriden and Pylons will use the mapping you specified in the config['pylons.controllers'], you can map URL schemas against its keys and their values will be used as controller classes.
Using this technique, you can load controller classes from other packages and/or implement a configurable controller mapping system.
Hope it helps...