Using ToscaWidgets with repoze.bfg and Storm
by Christian Scholz on September 2, 2008
Recently I tried to use Storm and ToscaWidgets with repoze.bfg to create a simple usermanagement application. Here is a little rundown of what I did.
First I installed repoze.bfg inside a virtualenv:
mkdir users cd users virtualenv --no-site-packages . source bin/activate easy_install repoze.bfg
Some problems might occur on a Mac with Python 2.5 resulting in the need to install PasteDeploy and zope.proxy separately:
easy_install PasteDeploy
easy_install zope.proxy
Then you can create a project:
paster create -t bfg usermanagement
Inside usermanagement you find files like models.py, views.py and so on which is pretty good explained in the bfg documentation.
We now also need to develop this egg:
cd usermanagement
python setup.py develop
The basic idea now is to have a UserFolder model which contains BaseUser objects. This led to changing models.py to look like this:
from zope.interface import Interface
from zope.interface import implements
class IUserFolder(Interface):
pass
class UserFolder(object):
implements(IUserFolder)
class IBaseUser(Interface):
pass
class BaseUser(object):
implements(IBaseUser)
root = UserFolder()
def get_root(environ):
return root
I also changed configure.zcml to reflect that and both use the same default view:
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:bfg="http://namespaces.repoze.org/bfg"
i18n_domain="repoze.bfg">
<!-- this must be included for the view declarations to work -->
<include package="repoze.bfg" />
<bfg:view
for=".models.IBaseUser"
view=".views.my_view"
/>
<bfg:view
for=".models.IUserFolder"
view=".views.my_view"
/>
</configure>
Adding an addform
Now for adding an addform I wanted to use ToscaWidgets. I installed it manually for now (later this will go into setup.py):
easy_install ToscaWidgets
easy_install tw.forms
easy_install genshi
The latter is needed because otherwise the internally used template engine is not found.
Then I added another function and some imports to views.py:
from tw.forms import *
from tw.forms.validators import *
from tw.api import WidgetsList
class UserAddForm(TableForm):
# This WidgetsList is just a container
class fields(WidgetsList):
id = HiddenField(default="I'm hidden!")
username = TextField()
fullname = TextField()
email = TextField(
validator = Email()
)
password = PasswordField(
validator = String(not_empty=True),
max_size = 10
)
password_confirm = PasswordField(
validator = String(not_empty=True),
max_size=10
)
def addform_view(context, request):
"""show the addform for adding a new user"""
form = UserAddForm('form')
form_output = form.display(context)
return render_template_to_response('templates/addform.pt',
form=form_output)
and wired this as “addform” into configure.zcml by adding:
<bfg:view
for=".models.IUserFolder"
view=".views.addform_view"
name="addform"
/>
I started the server by typing
paster serve usermanagement.ini
inside the project directory and accessed at http://127.0.0.1:5432/addform
We get the following error:
TypeError: No object (name: ToscaWidgets per-request storage) has been registered for this thread
Looking the example it becomes clear that some WSGI middleware is missing.
Guessing from examples for ToscaWidgets and reading some code I figured out I had to add something to run.py so that it looks like this:
def make_app(global_config, **kw):
# paster app config callback
from repoze.bfg import make_app
import tw.api
import usermanagement
from usermanagement.models import get_root
app = make_app(get_root, usermanagement)
app = tw.api.make_middleware(app, {
'toscawidgets.framework' : 'wsgi',
'toscawidgets.middleware.inject_resources' : True,
})
return app
if __name__ == '__main__':
from paste import httpserver
app = make_app(None)
httpserver.serve(app, host='0.0.0.0', port='5432')
I assume that you can also add stuff like this to usermanagement.ini and this probably is the preferred way of doing it but back then I wasn’t that much used to all the WSGI stuff.
What the middleware here does is also not completely clear to me (and I haven’t yet looked at the code). One part is apparently to make the above error go away which means that it probably injects something into the request. The other part is to inject CSS and JS links into the header. This is similar to the Plone ResourceRegistries the resource stuff in Zope3 ZCML in that each package (widgets mostly in this case) can register their JS and CSS snippets to be included in the document when being rendered.
I also wonder if it’s possible to get around adding this to the middleware stack.
When we now try the app again we notice that addform.pt is missing so we add it to templates:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal">
<head></head>
<body>
<h1>Add new user</h1>
${form}
</body>
</html>
And voila, we have our first form rendered:
Submitting the form
A form is nice but it’s nicer if you actually also store and validate the data. ToscaWidgets keeps the action attribute of the form empty (although I guess you can put something in there somehow) so it will submit to the same URL again.
The example shows how to handle it by checking for the POST method and I do the same just with WebOb which bfg uses. I changed my view to this:
from formencode import Invalid
def addform_view(context, request):
"""show the addform for adding a new user"""
form = UserAddForm('form')
if request.method=="POST":
try:
values = form.validate(request.POST)
return render_template_to_response('templates/addform_success.pt',
values = values)
except Invalid, error:
pass
form_output = form.display(context)
return render_template_to_response('templates/addform.pt',
form=form_output)
What’s happening here is basically a check if the request was a POST, then passing the values of the POST to the validate() method of the form and checking for an exception. If there was an exception we simply render the form again like before. If no exception was raised we display the addform_success.pt template:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal">
<head></head>
<body>
<h1>Added new user ${values.username}</h1>
</body>
</html>
Note how this is a little different from how Page Template syntax usually looks like. It’s different in so far in that z3c.pt also allows Expression interpolation inside HTML and not only attributes. See the z3c.pt docs (http://pypi.python.org/pypi/z3c.pt) for more information on what’s different.
Now if you pass some wrong data in you should see something like this:
If everything succeeded you can see confirmation we defined above.
So far this works but we are not finished yet. We of course need to store the data somewhere. In production we maybe also want to do some redirect or other means of preventing the form to be submitted twice by accident and e.g.
hitting the reload button on the browser. But that’s for later.
Storage
This is where Storm comes into play, a object relational database mapper which
is developed and used by Canonical. We install it by doing
easy_install storm
Now we need a database which basically uses our user schema, namely username,
email, password and fullname. For this we create an sqlite database (which btw
you should have installed):
sqlite3 users.db
sqlite> CREATE TABLE users (id INTEGER PRIMARY KEY, username VARCHAR, password VARCHAR, fullname VARCHAR, email VARCHAR);
.quit
Now that we have that table we can add some stuff to models.py to wire it to
the table. I replaced the BaseUser class in models.py with this one:
class BaseUser(object):
implements(IBaseUser)
__storm_table__ = "users"
id = Int(primary=True)
username = Unicode()
password = Unicode()
fullname = Unicode()
email = Unicode()
and imported some storm stuff on top:
from storm.locals import *
We also need to create a database. I did this on module level directly after the import:
database = create_database("sqlite:users.db")
Now the class should know about our database schema (btw, while I like the
fact that it does not use a metaclass I still would like to have the actual
database schema uncoupled from the actual class to have more flexibility.
Something a la ZPatterns (only old people like me know what this is) might be nice.
Now we need to add some code to views.py. The new view now looks like this:
from storm.locals import *
import models
def addform_view(context, request):
"""show the addform for adding a new user"""
form = UserAddForm('form')
if request.method=="POST":
try:
values = form.validate(request.POST)
# open the store
store = Store(models.database)
# create a new user and store the data inside
user = models.BaseUser()
user.username = unicode(values.get("username",""))
user.email = unicode(values.get("email",""))
user.password = unicode(values.get("password",""))
user.fullname = unicode(values.get("fullname",""))
# add it to the store
store.add(user)
# commit the transaction and flush the store
store.commit()
store.flush()
# now close it again so we are thread safe
store.close()
# this is like before
return render_template_to_response('templates/addform_success.pt',
values = values)
except Invalid, error:
print error
pass
form_output = form.display(context)
return render_template_to_response('templates/addform.pt',
form=form_output)
I had some trouble with sqlite and threading as it didn’t work when I defined
the store in models.py and just used it in the view. Thus I now open and close
the store in each transaction. This limitation might not apply to other
database backends though. The question nevertheless is for what database
backend you code so maybe this is safer but maybe also a litte less performant
(but I don’t know what the store compared to the database object actually
does).
If you now start the server again and add some users you should finally see them again inside the database:
sqlite3 users.db
sqlite> select * from users
1|hansi|hdbjdb|Dummy|dummy@dummydummy123.com
2|hansi2|hdbjdb|Dummy|dummy@dummydummy123.com
3|foobar|foobar111|Dummy|dummy@dummydummy123.com
Listing users
The userfolder view looks rather boring right now, so why not making this a list of all users?
Let’s define the template first as templates/userfolder.pt:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal">
<head></head>
<body>
<h1>User Listing</h1>
<ul>
<tal:block repeat="user users">
<li>
<a href="/${user.id}">${user.username}</a>
</li>
</tal:block>
</ul>
</body>
</html>
We also wire it inside configure.zcml by changing the default
view for the folder:
<bfg:view
for=".models.IUserFolder"
view=".views.userlisting"
/>
(make sure you replace this and not just add it to the file)
And we implement userlisting in views.py as follows:
def userlisting(context, request):
"""show a userlisting"""
store = Store(models.database)
users = list(store.find(models.BaseUser).order_by(models.BaseUser.username))
return render_template_to_response('templates/userfolder.pt',
users=users)
If you restart you will see the userlisting at
http://localhost:5432/ (as the userfolder is our root object at
the moment). The only problem is if you click on a user because there is no
traversal yet defined for this and bfg produces a 404.
Let’s change this by adding a __getitem__() to the BaseUser class:
def __getitem__(self, id):
store = Store(database)
user = store.get(BaseUser, int(id))
return user
After a restart we can already click on the users but the template does not
say that much about them. So we need a new one and also need to create a
separate view for them. I added the following to views.py:
def userdetails(context, request):
return render_template_to_response('templates/userdetails.pt',
context = context)
And we need to create some template for it as templates/userdetails.pt:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal">
<head></head>
<body>
<h1>User Details for ${context.username}</h1>
<table border="0" cellspacing="5" cellpadding="5">
<tr>
<td>Username:</td>
<td tal:content="context.username"></td>
</tr>
<tr>
<td>Fullname:</td>
<td tal:content="context.fullname"></td>
</tr>
<tr>
<td>E-Mail:</td>
<td tal:content="context.email"></td>
</tr>
</table>
</body>
</html>
Now we just need to wire this view to the model, which means changing it in configure.zcml:
<bfg:view
for=".models.IBaseUser"
view=".views.userdetails"
/>
And that’s all there is to it. Much is of course still missing, such as deleting. Next steps could be to abstract things a bit more to have some sort of schema which handles storage and form generation all in one.


4 comments
Hi Christian,
interesting article as I'm checked out repoze.bfg recently too. It's like a breath of fresh air, isn't it? What is you experience with storm related to sqlalchemy?
Kai
by Kai Diefenbach on 2.9.2008 at 22:32. #
It's not the first MVC framework I use as I used Django as well but you are right it's sort of a breath of fresh air :-)
As for Storm vs. sqlalchemy I cannot really tell as I only quickly looked at Storm (which I think looks sane) and even shorter at SQLAlchemy (which looks more complicated but probably has more features). What I used was the Django ORM but I am not sure I am a big fan of them anyway esp. when it comes to writing queries with some python objects instead of SQL. I think I still mostly like the way ZPatterns approached this. There you know at least what's happening beneath your code.
by Christian Scholz on 2.9.2008 at 23:19. #
I'm having problem of get the result of the userdetails, whenever I click on the link I get 404 not found error. I've checked every where couldn't find a clue
by georgehu on 18.2.2009 at 11:48. #
I finally found the issue, the "Let’s change this by adding a __getitem__() to the BaseUser class:" is not correct, the __getitem__ should be in the UserFolder class.
by georgehu on 18.2.2009 at 12:11. #