We’ve already seen how we can do this in the Configure Rum to create an interface for your model section of the tutorial.
The basic steps are:
PasteDeploy can be used to do this easily since Rum implements a paste.app_factory entry point. A sample paste deploy configuration file:
[DEFAULT]
debug = true
[server:main]
use = egg:Paste#http
host = 127.0.0.1
port = 5000
[app:main]
use = egg:rum
full_stack = True
config = %(here)s/development.cfg
The development.cfg file it refers to, which configures rum.RumApp in the same way as a config dictionary would, is this one:
[DEFAULT]
debug = True
[rum.repositoryfactory]
use = 'sqlalchemy'
# These will be imported. Notation is package.subpackage.module:class
models = ['model:Person', 'model:Genre', 'model:Actor', 'model:Director', 'model:Movie', 'model:Rental']
sqlalchemy.url = 'sqlite:///movie.db'
session.autocommit = False
[rum.viewfactory]
use = 'toscawidgets'
Notice that the configuration passed in this file mirrors the one we used in the Configure Rum to create an interface for your model section of the tutorial.
Rum applications are self-contained, they store no state outside the graph of objects reachable from rum.RumApp so several Rum applications can coexist in the same process each of them configured in a different way.
One of the things this design enables is to run Rum inside another framework that suports delegating the response process from a controller to another WSGI app. AFAIK, Pylons (and by extension, TurboGears 2) is the only popular Python web-framework that supports this.
Why would we want to do this?
The simplest way to embed a Rum app inside a TG2 app is by using the helpers provided by TgRum.
First install TgRum:
$ easy_install TgRum
Then in your RootController living in yourapp.controllers.root:
from repoze.what import predicates
from tg import config
from tgrum import RumAlchemyController
from yourapp.lib.base import BaseController
from yourapp import model
# This is the predicate that we'll use to secure the Rum app
is_manager = predicates.has_permission(
'manage',
msg=_('Only for people with the "manage" permission')
)
class RootController(BaseController):
admin = RumAlchemyController(
model,
is_manager,
template_path=config['paths']['templates'][0],
# Since this TG's master template will render it
render_flash=False,
)
Note that we initialize RumAlchemyController with the same template path used by your TG app. This allows Rum to xinclude the master.html template used by your app so Rum integrates nicely with the look and feel of the rest of uour site. More about this in the Customizing Rum’s chrome section.
There is a sample TG application which embeds rum called TgRumDemo which you can download to see a living example.
Mounting a Rum application inside a Pylons application is no different than mounting any other WSGI. However, you need to first make sure that class:pylons.middleware.StatusCodeRedirect is configured not to handle 400 Bad Request HTTP status codes since Rum uses them to signal errors when validation fails. To do this modigy yourapp.config.middleware the block where pylons.middleware.StatusCodeRedirect is instantiated and stacked looks like this:
# Display error documents for 401, 403, 404 status codes (and
# 500 when debug is disabled)
if asbool(config['debug']):
app = StatusCodeRedirect(app, [401, 403, 404])
else:
app = StatusCodeRedirect(app, [401, 403, 404, 500])
Notice how we explicitly pass the list of HTTP status codes the middleware should handle so it doesn’t include the 400 status code it handles by default.
Once this is done you should instantiate a Rum application and keep a reference to it. I usually do this in yourapp.lib.app_globals.Globals but you can instantiate it anywhere you want as long as it is done after yourapp.model.init_model is called. This is because RumAlchemy won’t recognize your models if they haven’t been mapped by SQLAlhemy’s ORM. For example:
from pylons import config
from paste.deploy.converters import asbool
from yourapp.model import meta
from rum import RumApp
class Globals(object):
"""Globals acts as a container for objects available throughout the
life of the application
"""
def __init__(self):
"""One instance of Globals is created during application
initialization and is available during requests via the
'app_globals' variable
"""
self.admin_app = self._make_admin_app()
def _make_admin_app(self):
# Path to where you are keeping rum-specific templates.
# You can imit this if you're not overriding them.
search_path = [os.path.join(
config['paths']['templates'][0],
'admin'
)]
app = RumApp({
'debug': asbool(config.get('debug')),
'full_stack': True,
'rum.repositoryfactory': {
'use': 'sqlalchemy',
'session_factory': meta.Session,
'models': [
# list the model classes you want Rum to handle here
]
},
'rum.viewfactory': {
'use': 'toscawidgets',
},
'rum.translator': {
'use': 'default',
'locales': ['es', 'en'],
},
'templating': {
'search_path': search_path,
}
})
return app
Once you have a RumApp instance somwhere you just need to delegate to it the request from within a controller action like described here. For example, assuming that the default routes are in place, just drop a file named admin.py in yourapp/controllers containing this code:
from pylons import g
from webob import Request
from pylons import g
def AdminController(environ, start_response):
return g.admin_app(environ, start_response)
Registering the routes can be done in the following way:
map.connect("/admin", controller="admin", path_info="/")
map.connect('/admin/{path_info:.*}', controller='admin')
The admin application should now be accesible at http://localhost:5000/admin/
Warning
Make sure you modify the code shown above to control access tho Rum or else anyone will be able to modify your data! The easiest way is to peek into environ['REMOTE_USER'] or into pylons.session depending on how you’re handling auth/authz in your app.