Grok ORM with Storm

Author:Christian Klinger (goschtl)
Version:unkown

RDBMS/ORM and Zope/Grok doesn’t fit together?
With this tutorial I will show you how easy it is,
to make a simple CRUD Application with Grok and Storm.

Prerequisities

First we setup our database. As a lightweight solution I use sqlite. So please make sure that sqlite is installed in your system. I think sqlite should be included in every *nix distribution. After installing sqlite we set up our database for our little application.

Here are the commands for an initial setup of our database:

note: here we create the database in /tmp/contact.db

chrissi$ sqlite3 /tmp/contact.db
SQLite version 3.1.3
Enter ".help" for instructions
sqlite> CREATE TABLE Contacts (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(200), city VARCHAR(200));
sqlite> .exit

After setting up the database let’s build a new project with grokproject.:

chrissi$ bin/grokproject contacts

Fill out the asked questions from grokproject. I’m sure you have done this before a dozen times. So it should not be a problem.

After setting up our grokproject environment we have to install a stormcontainer, which acts like a normal grok.Container. The main difference is of course that stormcontainer gets its data from an RDBMS via the Storm ORM API. Unfortunately I don’t have a release on pypi so we have to install it manually. Let’s install the stromcontainer:

First change to our grokproject package contacts:

chrissi$ cd contacts/

In this directory we have to checkout the stromcontainer:

chrissi$ svn checkout http://stormcontainer.googlecode.com/svn/trunk/ stormcontainer
A    stormcontainer/bootstrap.py
A    stormcontainer/buildout.cfg
A    stormcontainer/setup.py
A    stormcontainer/src
A    stormcontainer/src/stormcontainer
A    stormcontainer/src/stormcontainer/tests
A    stormcontainer/src/stormcontainer/tests/stormcontainer.txt
A    stormcontainer/src/stormcontainer/tests/__init__.py
A    stormcontainer/src/stormcontainer/tests/test_unittests.py
A    stormcontainer/src/stormcontainer/tests/test_doctests.py
A    stormcontainer/src/stormcontainer/__init__.py
A    stormcontainer/src/stormcontainer/utils.py
A    stormcontainer/src/stormcontainer/interfaces.py
A    stormcontainer/src/stormcontainer/components.py
A    stormcontainer/.installed.cfg

After checkout we have to install the package stormcontainer to our python’s site-packages directory. This works with the command:

python2.4 setup.py develop

OK congratulations. Now we can start to developing our contacts application.

Step by step

Let’s change back to our contacts directory to define the interface for our contact application.:

../src/contacts/

I will walk through the package structure of contacts to give you an impression of what is required to let Grok work with Storm.:

configure.zcml

<configure xmlns="http://namespaces.zope.org/zope"
           xmlns:grok="http://namespaces.zope.org/grok">
  <include package="grok" />
  <grok:grok package="." />

  <!-- Include the storm.zope in our application -->
  <include package="storm.zope"/>
  <include package="storm.zope" file="meta.zcml"/>

  <!-- Here is our Store this is a utility which holds the connection to DB -->
  <store name="contact" uri="sqlite:/tmp/contact.db"/>

</configure>

We have to include the package storm.zope. And to configure a store which is responsible for connecting the database. Take a look at the uri in the store, this points to our contact database which we have created.:

interfaces.py

from zope.interface import Interface
from zope.schema import Int, TextLine

class IContact(Interface):
    """ Interface for Contacts """

    id = Int(title=u"id",
                  description=u"The id of our contact",
                  readonly=True)

    name = TextLine(title=u"Name",
                    description=u"The Name of our contact",
                    required=True)

    city = TextLine(title=u"City",
                    description=u"The City of our contact",
                    required=True)

It’s a normal interface. No ORM related dependencies.:

contact.py

import grok
from storm.locals import *
from interfaces import IContact

class Contact(grok.Model, Storm):
    grok.implements(IContact)
    __storm_table__ = "Contacts"  # This is the corresponding table in our DB

    id = Int(primary=True) # Here we give our attributes an "Storm" Datatype
    name = Unicode()
    city = Unicode()

class Edit(grok.EditForm):
   grok.context(Contact)
   form_fields = grok.AutoFields(IContact)

There is also no big deal in our model. We have to add a __storm_table__ attribute to our grok.Model this attribute reflects the corresponding table in our database. We have to give our class attributes storm datatypes.:

app.py

import grok
from interfaces import IContact
from stormcontainer import StormContainer
from contact import Contact

class contacts(StormContainer, grok.Application, grok.Container):
    """ this application inherits from StormContainer too """
    def __init__(self):
        super(contacts, self).__init__()
        self.setClassName('contacts.contact.Contact')
        self.setStoreUtilityName('contact')

class Index(grok.View):

    def getContacts(self):
        """ Return all Contacts of our StormContainer.
            We can use the normal container API (items, keys ...)"""
        rc=[]
        for obj in self.context.items():
            d={'uid': obj[0], 'id': obj[1].id, 'city': obj[1].city, 'name': obj[1].name}
            rc.append(d)
        return rc

class CreateContact(grok.AddForm):
    form_fields = grok.AutoFields(IContact)

    @grok.action('Create')
    def create(self, **kw):
        context = self.context
        c = Contact()
        self.applyData(c, **kw)
        context['id']= c
        self.redirect(self.url(self.context))

Here are the greatest changes to a normal grok application. Our application has to inherit from StormContainer, grok.Application and grok.Container. In the __init__ method of our application we set up two import parameters of our application:

self.setClassName('contacts.contact.Contact') --> This is our model. --> contact.py
self.setStoreUtilityName('contact') --> This is the store utility name.  --> configure.zcml

The method getContacts use the normal container API to get the results out of the database through the Storm API. The CreateContact grok.AddForm should also be standard.:

app_templates/index.pt

<html>
<head>
</head>
<body>
  <h1>Congratulations!</h1>

  <a href="createcontact"> Add new Contact </a>

  <table>
   <tr>
    <th> id </th>
    <th> name </th>
    <th> city </th>
    <th>  </th>
   </tr>
   <tr tal:repeat="person view/getContacts">
     <td tal:content="person/id"></td>
     <td tal:content="person/name"></td>
     <td tal:content="person/city"></td>
     <td> <a href="#" tal:attributes="href string: ${person/uid}/edit"> edit </a></td>
   </tr>
  </table>

</body>
</html>

No big deal here we display the results in a nice table.

Now we can start our application:

zopectl fg

Now place your browser to localhost:8080 add an contact app.

Sometimes i got this error after adding my app:

ValueError: database parameter must be string or APSW Connection object

Then we have to patch this file:

lib/python2.4/site-packages/storm-0.11-py2.4.egg/storm/databases/sqlite.py - on line 173

Just make self._filename a string:

raw_connection = sqlite.connect(str(self._filename),

Now play a bit with adding and editing an contact. You can always look with sqlite in your contact database to see what happens.

Further information

Visit the storm page to get more information about storm.