.. include:: ../links.txt .. _deploy: Deploying a Rum application =========================== Stand-alone mode ---------------- We've already seen how we can do this in the :ref:`configure_rum_tutorial` section of the tutorial. The basic steps are: * Initialize a :class:`rum.RumApp` instance * (optionally) wrap it with deployment middleware * Feed the resulting app to a WSGI server. `PasteDeploy`_ can be used to do this easily since Rum implements a ``paste.app_factory`` entry point. A sample paste deploy configuration file: .. literalinclude:: development.ini The ``development.cfg`` file it refers to, which configures :class:`rum.RumApp` in the same way as a ``config`` dictionary would, is this one: .. literalinclude:: development.cfg Notice that the configuration passed in this file mirrors the one we used in the :ref:`configure_rum_tutorial` section of the tutorial. Embedded mode ------------- Rum applications are self-contained, they store no state outside the graph of objects reachable from :class:`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? * Because we can share a common auth/authz framework to provide services to both the hosting app and Rum. * Because the hosting app could mangle the response returned by Rum and theme it with something like `Deliverance`_ * Because we can access information stored in the HTTP session of the underlying application. * Generally speaking: because we can use the hosting framework's services inside controllers/views/repositories we write ourselves. In fact, since Pylons/TG2s/Rum's controllers are all WSGI apps, we can even plug a Pylons/TG2 controller into Rum with a little bit of adaptation code at :func:`__call__` and it will work since the context needed by TG2/Pylons controllers is still there. Running Rum inside TurboGears 2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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``: .. code-block:: python 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 :ref:`customize_chrome` section. There is a sample TG application which embeds rum called `TgRumDemo`_ which you can download to see a living example. Running Rum inside Pylons ~~~~~~~~~~~~~~~~~~~~~~~~~ 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 :mod:`yourapp.config.middleware` the block where :class:`pylons.middleware.StatusCodeRedirect` is instantiated and stacked looks like this: .. code-block:: python # 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 :class:`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: .. code-block:: python 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: .. code-block:: python 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: .. code-block:: python 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.