Section 5.1. Updating Our Model

5.1. Updating Our Model

Before we start, let's think for a second about the result we want. We're going to have a bunch of links and a bunch of categories; that much is easy, and it's pretty obvious that categories are going to have more than one link, otherwise there's not much point in categories!

But are we ever going to have to have multiple categories for one link? I think the answer is yes. There will be some hotels that are also restaurants, some bed and breakfasts that are also historical landmarks, and so on. With that information in hand, we probably need to bite the bullet and create a join table to manage this many-to-many relationship.

Lucky for us, SQLObject's RelatedJoin functionality makes this amazingly easy:

class Categories(SQLObject):     categoryName   = StringCol(alternateID=True, length=100)     categoryItems  = RelatedJoin('Bookmarks') class Bookmarks(SQLObject):     bookmarkName   = StringCol(alternateID=True, length=100)     link           = StringCol()     description    = StringCol()     categories     = RelatedJoin('Categories')hhh

When SQLObject sees that categories and categoryItems are RelatedJoin column types, it automatically creates a join table for us, as well as a couple of simple methods to help manage the relations contained in that table. In this case, it will create addCategories and deleteCategories methods and add them to the Bookmark class, and an addBookmarks method, which it adds to the Categories class.

We should also be able to get a "list" (actually it's a SQLObject selectResults iterator, but it acts like a list) of all the categories related to that bookmark with, and then get a list of bookmarks per category by accessing its categoryItems property.

But before we get to that, we have a few things to clean up. Just because we changed our model schema doesn't mean that we can start using itbecause we already have an existing database that doesn't match the schema in our database! Right now, the easiest way to get everything back into sync is to delete our bookmarks database and re-create it using tg-admin sql create.

Even after we do that, if we fire up our application now, we'll still get errors. Why? Because I changed the bookmark attribute name to bookmarkName to make reading some of the examples clearer, but that application programming interface (API) change almost certainly broke something.

Luckily, we have some unit tests to help us find what broke, so we can just run nosetests and get some errors. After we've fixed the errors, we can feel comfortable adding new functionality.

Here's the first relevant bit:

====================================================================== ERROR: the list page should contain a link to Google ---------------------------------------------------------------------- Traceback (most recent call last):   File "C:\turbogears\class\bookmarker\bookmarker\tests\", line 14, in test_list_contents     description="A {not so} random link.")   File "c:\turbogears\tg-dev\thirdparty\sqlobject\sqlobject\", line 92, in _wrapper     return_value = fn(self, *args, **kwargs)   File "c:\turbogears\tg-dev\thirdparty\sqlobject\sqlobject\", line 1197, in __init__     self._create(id, **kw)   File "c:\turbogears\tg-dev\thirdparty\sqlobject\sqlobject\", line 1216, in _create     raise TypeError, "%s() did not get expected keyword argument %s" % (self.__ class__.__name__, TypeError: Bookmarks() did not get expected keyword argument bookmarkName

So, it looks like our tests are not passing bookmarkNames when they create new bookmarks. This is easy to fix: We just change .name to bookmark.bookmarkName in our tests and rerun them.

Oops, now we get a different error:

FAIL: If we add a record to the model, it should show up in the final page text ---------------------------------------------------------------------- Traceback (most recent call last):   File "C:\Documents and Settings\Mark\My Documents\book\code\5\bookmarker\book marker\tests\", line 17 , in test_list_contents     assert '<A HREF="">' in cherrypy.response. body[0] AssertionError:

This tells us there is something wrong with our template; it's not returning what we expect. We can then either add

print cherrypy.response.body[0]

to our test_list_contents method right before the failing assert (so that we can see the template output and determine what's wrong), or we can fire up our application and browse to /list and see the offending page ourselves.

Either way, we'll find that TurboGears gives us a long stack trace showing that the rename of name to bookmarkName has created yet another problem. The whole stack trace is there, but the only thing we need to know to find the source of our problem is the last line:

AttributeError: 'Bookmarks' object has no attribute 'name'

It looks like our list template is still looking for a We can fix that quickly by opening up list.kid and changing to bookmark.bookmarkName; and then our test passes!

We could continue to track down places where our code needs to be updated this way. However, the only remaining place where you'll have to update the code is the controller method that handles updating the database. So, go ahead and go over there and change name to bookmarkName:

@expose(template="bookmarker.templates.add") def save_bookmark(self, name, link, description):     b=Bookmarks(bookmarkName=name, link=link, description=description)     raise redirect("/list")

Now we can start adding a page that lists all the bookmarks by category, and perhaps another page that lets us look up the bookmarks in the category we want to see.

So, let's change our list controller to return all the categories from the database, and then we'll iterate over that in the template. Even though we'll continue to test our code, we aren't always going to show all of our tests in this book. Nonetheless, we'll be writing them for our examples, which you can always download from

Rapid Web Applications with TurboGears(c) Using Python to Create Ajax-Powered Sites
Rapid Web Applications with TurboGears: Using Python to Create Ajax-Powered Sites
ISBN: 0132433885
EAN: 2147483647
Year: 2006
Pages: 202

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: