= Using Storm with Pylons and Repoze.tm2 = This example shows how to integrate Storm with [[http://repoze.org/tmdemo.html|Repoze.tm2]] in a Pylons application. Repoze.tm2 is a transaction manager which uses Zope's transaction package. The example project will just have a simple controller with two actions. The first action will add a person to a sqldb database and the second action will list all persons in the database. You need to have the following packages: * transaction * zope.interface * repoze.tm2 The following dependencies are required when using Storm 0.12 or lower. They are optional when using the following [[https://lists.ubuntu.com/archives/storm/2008-July/000673.html|patch]] or until the [[https://code.launchpad.net/~jamesh/storm/require-less-zope|require-less-zope]] branch is merged. * zope.security * zope.testing Create the project using paster: {{{ $ paster create -t pylons RepozeStorm }}} As explained in [[http://blog.repoze.org/repoze.tm_with_pylons-20071218.html|this blog]] you need to add repoze.tm2 to the pipeline of your Pylons application. This will enable every request to be a transaction. You should add the following pipeline and rename the {{{app:main}}} section to {{{app:repozestorm}}} in {{{development.ini}}}. {{{ [app:repozestorm] use = egg:RepozeStorm full_stack = false storm.database.persons.uri = sqlite:///%(here)s/repozestorm/data/persons.db [pipeline:main] pipeline = egg:Paste#cgitb egg:Paste#httpexceptions egg:repoze.tm2#tm repozestorm }}} Next define the model for persons {{{repozestorm/model/person.py}}}. {{{#!python from storm.locals import * class Person(object): __storm_table__ = "Persons" id = Int(primary=True) firstname = Unicode() lastname = Unicode() }}} Person will be stored in a sql table called {{{persons}}}. Create that table by using setup-app. But for that to work make {{{repozestorm/websetup.py}}}: {{{#!python """Setup the RepozeStorm application""" import logging from paste.deploy import appconfig from pylons import config from storm.zope.zstorm import global_zstorm from repozestorm.config.environment import load_environment log = logging.getLogger(__name__) def setup_config(command, filename, section, vars): """Place any commands to setup repozestorm here""" conf = appconfig('config:' + filename, name=section.split(':')[1]) load_environment(conf.global_conf, conf.local_conf) store = global_zstorm.get('persondb', config['storm.database.persons.uri']) try: store.execute("create table persons " "(id integer primary key, " "firstname varchar, lastname varchar)") store.commit() finally: store.close() }}} Create directory to hold the database and run setup-app: {{{ $ mkdir -p repozestorm/data $ paster setup-app development.ini#repozestorm Running setup_config() from repozestorm.websetup }}} Create a controller for the application {{{repozestorm/controllers/person.py}}}: {{{#!python import logging from pylons import config from storm.zope.zstorm import global_zstorm from repozestorm.lib.base import * from repozestorm.model.person import Person log = logging.getLogger(__name__) class PersonController(BaseController): def index(self): # Return a rendered template # return render('/some/template.mako') # or, Return a response return 'Hello World' def new(self): store = global_zstorm.get('persondb', config['storm.database.persons.uri']) p = Person() p.firstname = u"Boo" p.lastname = u"Foo" store.add(p) return "Done" #raise Exception('Haha') def list(self): store = global_zstorm.get('persondb', config['storm.database.persons.uri']) c.persons = list(store.find(Person)) return render('/list_persons.mako') }}} global_zstorm contains all Storm stores by key. In this example the uri associated with that key is stored in {{{development.ini}}}. You can also set a default uri on global_zstorm. Because the application runs inside a Repoze.tm2 pipeline, there is no need to call {{{store.commit()}}}. Every request is a transaction and global_zstorm will register every store to the current transaction. If an exception is raised the transaction is rolled back or otherwise committed. The list controller action calls a mako template {{{repozestorm/templates/list_persons.mako}}} to display a list of persons: {{{ # -*- coding: utf-8 -*- List Persons

List of persons

}}} And finally run the example: {{{ $ paster serve --reload development.ini Starting subprocess with file monitor Starting server in PID 1234. serving on 0.0.0.0:5000 view at http://127.0.0.1:5000 }}} Start off by showing an (initially) empty list of persons by going to [[http://localhost:5000/person/list]]. Add a person by visiting [[http://localhost:5000/person/new]] and check the result on [[http://localhost:5000/person/list]] again. = Abort on GET, commit on POST = Repoze.tm2 has commit_veto support which can be specified in {{{development.ini}}} (this requires repoze.tm2 >= 1.0a3). {{{#!ini [filter:tm2] use = egg:repoze.tm2#tm commit_veto = repozestorm.lib.commit_veto:veto_on_get [pipeline:main] pipeline = egg:Paste#cgitb egg:Paste#httpexceptions tm2 repozestorm }}} And define {{{veto_on_get}}} in {{{repozestorm/lib/commit_veto.py}}} as follows: {{{#!python def veto_on_get(environ, status, headers): if environ['REQUEST_METHOD'] == 'GET': return True return False }}} Different policies might include checking for a logged in user, or whatever policy you want by examining the environment, status or headers. You can test the policy by visiting [[http://localhost:5000/person/new]]. The new user should not be added, check on [[http://localhost:5000/person/list]]. Commits will only happen on POST requests, which means the new controller should use a form.