Many TurboGears applications are small and quite simple like a small wiki or blog. For these projects, the default project layout as generated by tg-admin quickstart is sufficient. But sometimes you need more: multiple databases, several controllers and dozens of tables. Fortunately, TurboGears provides means to adapt your project layout to bigger projects.
The first thing you can do to re-structure a controller class that is getting too big is to swap out methods for handling complete URL sub-trees into separate sub-controller classes. Let’s look at a simple example:
from turbogears import controllers, expose from turbogears import redirect # sub controller class class SubController(controllers.Controller): @expose() def index(self): return "<h2>Hello TurboGears</h2>" @expose() def default(self, *args, **kw): # redirect to front page redirect("/") # main controller class class Root(controllers.RootController): @expose() def index(self): return "<h1>Hello World</h1>" @expose() def default(self, *args, **kw): return "This page is not ready" sub = SubController()
You see that in addition to the Root controller class, there is another controller class named SubController (inheriting from controllers.Controller), which has its own exposed method. This class is instantiated and “attached” to the root controller in the last line:
sub = SubController()
This is called “mounting” a sub-controller, in CherryPy-speak.
With this setup, your application will invoke the exposed methods of the SubController class whenever a URL below /sub is requested. In the example, when you access a URL like
They will all return “<h2>Hello TurboGears</h2>” since they will all be mapped to the index() method of the SubController instance referenced by the sub attribute of the Root class.
But if you access e.g. http://localhost:8080/sub/oops, you’ll be redirected to the front page (http://localhost:8080/), since there is no dedicated method handling this URL and it is therefore handled by the default method of SubController.
If you wish, you can think of this technique as if you were creating a folder named “sub” under your web space, but since controller methods are neither directories nor files, the proper way to describe this is: you name the SubController object as sub, and thereby mount it on the Root object under the URL /sub.
Also, we imported and used another helpful utility, turbogears.redirect here to redirect to the front-page from the default method in the SubController class.
In a small application, one module each for all controller and model classes is all you need. In a larger application, you will likely want to break up your application into separate packages, e.g. you will want to have controllers and a model sub-package below your application’s main package.
Fortunately, Python provides a way to organize modules into packages under a common name-space to handle this situation. Let’s see how we can apply this to the controller and the model.
If you know from the beginning that you will have several big controller classes and your project will need more structure, you can use the “tgbig” template when you create your project by issuing the following command:
tg-admin quickstart -t tgbig
After that you will have a directory structure where all controllers are located in a controllers sub-package directory and you can add more controller modules easily (see below).
When you didn’t set up your project with the tgbig template from the start, you can still easily turn the controllers module into a package by following these steps:
Create a directory named controllers in your application’s main package directory.
Move the controllers.py module into this directory.
Rename controllers.py to root.py.
Create a file controllers/__init__.py with the following content:
from root import Root
You can then add additional modules to the controllers package by creating new .py files in the controllers directory and placing your sub-controller classes to them. You’ll want to use divisions that make sense for you. You can still import the controllers from your various packages into your main controllers module, if you like them to be all available in one place. For example, if you have the following package structure:
controllers/__init__.py controllers/root.py controllers/documents.py controllers/registration.py
Then your <yourpackage>/controller/__init__.py could look like this:
from root import Root from documents import DocumentsController from registration import RegistrationController
Of course you have to mount the sub-controller classes from your controller modules to your root controller, so that they are callable through URLs. This is how your root controller class might look like for the package structure shown above:
from turbogears import controllers, expose from documents import DocumentsController from registration import RegistrationController class Root(controllers.Rootcontroller): documents = DocumentsController() registration = RegistrationController() @expose(...) def index(self): #...
The same technique shown above to create a controllers package can be applied to distribute the model across multiple module files.
Internally, TurboGears expects that there is a loadable module in <yourpackage>.model. It doesn’t care whether this is a single-file module or a multi-file package, as long as it can import your model classes directly from the <yourpackage>.model namespace.
So here are the steps again to create a package, adapted for the model module:
Create a directory named model in your application’s main package directory.
Move the model.py module into this directory.
Rename model.py to base.py.
Create a file model/__init__.py with the following content:
from base import *
Now you can add more modules with additional model classes. Say, for example, your application’s main package is called blog and you have two modules, public.py and internal.py, which contain additional model classes. Then you have to put these two files in the model directory and your model/__init__.py should contain something like this:
from blog.model.base import * from blog.model.public import * from blog.model.internal import *
If you are using TurboGears’ identity functionality than you have to update the following parameters:
visit.saprovider.model identity.saprovider.model.visit identity.saprovider.model.user identity.saprovider.model.group identity.saprovider.model.permission
in the config/app.cfg file.
If you’re using SQLObject, then in order to use the admin tools, you will also have to add all the modules to the db_module setting in <yourpackage>.egg-inf/sqlobject.txt, each separated by a comma. I.e. for this example, the setting should be changed to:
Now you are done! To see if everything worked correctly, type tg-admin sql list in the root directory of your TurboGears project. If everything worked, all classes in your model files should be listed here.
When you have many templates you might want to organize them into sub-directories below the <youpackage>/templates directory, grouping together, for example, templates for each controller into a separate sub-directory.
Templates are looked up by the template engines via the normal Python package mechanisms and the templates directory is therefore just a Python package and templates are the “modules” in this package. Therefore, sub-directories have to be sub-packages and you can achieve that by placing an __init__.py file in them. The __init__.py can contain Python code, but an empty file will do just as well.
Example directory layout:
<yourpackage> <yourpackage>/templates <yourpackage>/templates/__init__.py <yourpackage>/templates/index.html <yourpackage>/templates/master.html <yourpackage>/templates/subpackage <yourpackage>/templates/subpackage/__init__.py <yourpackage>/templates/subpackage/index.html <yourpackage>/templates/subpackage/foo.html
In your controller methods you then refer to the templates in "subpackage" via the normal dotted-path notation:
class MyController(controllers.Controller): @expose('<yourpackage>.templates.subpackage.index') def index(self): # ...
The default templates in a quickstarted project inherit from the master.html template in the same directory. When you want templates in sub-directories to inherit from the same master.html, you have to adapt the path to the master template in the xi:include tag directly below the html tag. For example:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/" xmlns:xi="http://www.w3.org/2001/XInclude"> <xi:include href="../master.html" />