Using Storm with Pylons and Repoze.tm2
This example shows how to integrate Storm with 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 patch or until the require-less-zope branch is merged.
- zope.security
- zope.testing
Create the project using paster:
$ paster create -t pylons RepozeStorm
As explained in 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.
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:
1 """Setup the RepozeStorm application"""
2 import logging
3
4 from paste.deploy import appconfig
5 from pylons import config
6
7 from storm.zope.zstorm import global_zstorm
8
9 from repozestorm.config.environment import load_environment
10
11 log = logging.getLogger(__name__)
12
13 def setup_config(command, filename, section, vars):
14 """Place any commands to setup repozestorm here"""
15 conf = appconfig('config:' + filename, name=section.split(':')[1])
16 load_environment(conf.global_conf, conf.local_conf)
17
18 store = global_zstorm.get('persondb', config['storm.database.persons.uri'])
19 try:
20 store.execute("create table persons "
21 "(id integer primary key, "
22 "firstname varchar, lastname varchar)")
23 store.commit()
24 finally:
25 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:
1 import logging
2
3 from pylons import config
4
5 from storm.zope.zstorm import global_zstorm
6
7 from repozestorm.lib.base import *
8 from repozestorm.model.person import Person
9
10 log = logging.getLogger(__name__)
11
12 class PersonController(BaseController):
13
14 def index(self):
15 # Return a rendered template
16 # return render('/some/template.mako')
17 # or, Return a response
18 return 'Hello World'
19
20 def new(self):
21 store = global_zstorm.get('persondb', config['storm.database.persons.uri'])
22 p = Person()
23 p.firstname = u"Boo"
24 p.lastname = u"Foo"
25 store.add(p)
26 return "Done"
27 #raise Exception('Haha')
28
29 def list(self):
30 store = global_zstorm.get('persondb', config['storm.database.persons.uri'])
31 c.persons = list(store.find(Person))
32 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 -*- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html> <head> <title>List Persons</title> </head> <body> <div class="content"> <h1 class="main">List of persons</h1> <ul id="persons"> % for person in c.persons: <li>${person.id} ${person.firstname} ${person.lastname}</li> % endfor </ul> </div> </body> </html>
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).
[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:
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.