Tutorial
BFG + Storm = CRUD
Author: tseaverDate: 06/18/09 at 16:03:48
External URL: http://svn.repoze.org/playground/tseaver/stormy
()
I have just finished a prototype of a traverseal-based RDBMS application using the Storm object-relational mapper.
The models here are a Person class (corresponding to a single
row in the persons table, as defined in the
Storm tutorial),
and a traversable container class, Persons. The
Persons class defines __getitem__ (making it
traversable with the primary key), as well as __iter__ and
__delitem__.
The root Persons object holds onto a "store" object, managed using
the storm.zope.zstorm:ZStorm class to hand out per-thread copies;
this class also arranges for the stores to be associated with the Zope
transaction model.
The app expects to use repoze.tm2 to manage per-request transactions, and registers a "commit veto" hook which prevents error responses from committing a transaction.
The views are straightforward BFG views: note that they do not do any explicit transaction management. They demonstrate using a "main template" which holds the common look-and-feel for the application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | #------------------------------------------------------------------------------
# stormy/models.py
#------------------------------------------------------------------------------
from storm.locals import create_database
from storm.locals import Int
from storm.locals import Store
from storm.locals import Unicode
from storm.zope.zstorm import global_zstorm
class Person(object):
__storm_table__ = 'persons'
id = Int(primary=True)
name = Unicode()
class Persons(object):
__parent__ = None
__name__ = None
def __init__(self, store):
self.store = store
def __getitem__(self, id):
try:
id = int(id)
except (TypeError, ValueError):
raise KeyError(id)
person = self.store.get(Person, id)
if person is None:
raise KeyError(id)
person.__parent__ = self
person.__name__ = str(id)
return person
def __delitem__(self, id):
try:
id = int(id)
except (TypeError, ValueError):
raise KeyError(id)
found = self.store.find(Person, id=id)
if found.count() == 0:
raise KeyError(id)
assert found.count() == 1
self.store.remove(found.any())
def __iter__(self):
for person in self.store.find(Person).order_by(Person.name):
person.__parent__ = self
person.__name__ = str(person.id)
yield person
DEFAULT_URI = 'sqlite:stormy.db'
def _make_store(name, db_uri=DEFAULT_URI):
store = global_zstorm.get(name, db_uri)
found = store.execute('select * from sqlite_master '
'where type = "table" and name = "persons"'
).get_all()
if not found:
store.execute('create table persons'
'( id integer primary key'
', name varchar(1024) unique'
')', noresult=True)
store.commit()
return store
def get_root(environ, name='main', db_uri=DEFAULT_URI):
store = environ.get('storm.store') # middlestorm uses this key
if store is None:
store = _make_store(name, db_uri)
return Persons(store)
#------------------------------------------------------------------------------
# stormy/views.py
#------------------------------------------------------------------------------
from repoze.bfg.chameleon_zpt import get_template
from repoze.bfg.chameleon_zpt import render_template_to_response
from repoze.bfg.url import model_url
from repoze.bfg.view import static
from webob.exc import HTTPFound
from stormy.models import Person
static_view = static('templates/static')
def view_root(context, request):
persons = [{'id': x.id, 'name': x.name} for x in context]
return render_template_to_response(
'templates/view_root.pt',
main_template = get_template('templates/main_template.pt'),
persons = persons,
status = request.GET.get('status'),
here = model_url(context, request),
)
def search_persons(context, request):
name = request.GET.get('name')
if name is None or name.strip() == '':
return HTTPFound(location = model_url(context, request,
query={'status': 'No search term'}))
persons = context.store.find(Person, Person.name.like(u'%%%s%%' % name))
if persons.count() == 0:
status = 'No persons matched "%s"' % name
else:
status = request.GET.get('status')
return render_template_to_response(
'templates/search_persons.pt',
main_template = get_template('templates/main_template.pt'),
persons = persons,
name = name,
status = status,
here = model_url(context, request),
)
def add_person(context, request):
if 'form.submitted' in request.POST:
person = Person()
name = person.name = request.POST['name']
context.store.add(person)
return HTTPFound(
location = model_url(context, request,
query={'status': 'Person added: %s' % name}))
return render_template_to_response(
'templates/add_person.pt',
main_template = get_template('templates/main_template.pt'),
here = model_url(context, request),
status = request.GET.get('status'),
save_url = model_url(context, request, request.view_name),
)
def delete_person(context, request):
if 'confirm' in request.params:
id = request.params['id']
name = context[id].name
del context[id]
return HTTPFound(
location = model_url(context, request,
query={'status': 'Person deleted: %s' % name}))
child = context[request.params['id']]
return render_template_to_response(
'templates/delete_person.pt',
main_template = get_template('templates/main_template.pt'),
name = child.name,
status = request.GET.get('status'),
here = model_url(context, request),
confirm_url = model_url(context, request, request.view_name,
query={'id': child.__name__,
'confirm': 1}),
)
def view_person(context, request):
return render_template_to_response(
'templates/view_person.pt',
main_template = get_template('templates/main_template.pt'),
name = context.name,
status = request.GET.get('status'),
here = model_url(context.__parent__, request),
)
def commit_veto(environ, status, headers):
# Hook for repoze.tm2: veto commit of error responses.
return status.startswith('4') or status.startswith('5')
#------------------------------------------------------------------------------
# stormy/configure.zcml
#------------------------------------------------------------------------------
<configure xmlns="http://namespaces.repoze.org/bfg">
<!-- this must be included for the view declarations to work -->
<include package="repoze.bfg.includes" />
<view
for=".models.Persons"
view=".views.view_root"
/>
<view
for=".models.Persons"
view=".views.search_persons"
name="search.html"
/>
<view
for=".models.Persons"
view=".views.add_person"
name="add.html"
/>
<view
for=".models.Persons"
view=".views.delete_person"
name="delete.html"
/>
<view
for=".models.Persons"
view=".views.static_view"
name="static"
/>
<view
for=".models.Person"
view=".views.view_person"
/>
</configure>
|