4.1. Building a Simple Bookmark Collection SiteWe'll build a small program to save and categorize links. If you want to imagine a user for this, you can think of the tourist association of a small town such as Mars, Pennsylvania, which needs a central place to aggregate links for restaurants, bed-and-breakfasts, and tourist attractions. Before we get started on any project, no matter how small, there are a couple of things we always do. First we set up a version control repository. All the code for this book lives in a Subversion repository. In that repository we already have a sample code folder for each chapter, so all we have to do is check out that folder, cd into it, and add our new project like this: cd code/4/ tg-admin quickstart ... svn ci Everybody makes mistakes, so we're big believers in version control for everything. In fact, we used Subversion to store every revision of every chapter of this book! You can use CVS or Perforce, Bazaar-NG, or whatever version control system you want. You can even skip this step and just start writing code, but we've learned the hard way that something always happens that makes you wish you had used some kind of version control so that you can go back and fix whatever broke. For more information about Subversion, check out the Resources page on our website (www.turbogearsbook.com). Even though we're not going tomention it every time we finish a section, you can bet that we finish every major edit with a quick svn ci. After you've created your bookmarker project, the first thing you'll probably want to dois to check to make sure everything works: python start-bookmarker.py. As before, we test this by browsing to http://localhost:8080/. Just like in Chapter 3, "The Architecture of a TurboGears Application," the first step after generating a new project is to set up our SQLite database connectionURI: If you're running on Linux or Mac OS X and using SQLite, you can edit the dev.cfg file to change the SQLite configuration line to look something likethis: sqlobject.dburi= "sqlite:///var/testdatabases/bookmarker.py" This assumes that you'll want to keep your databases in /var/testdatabases/, but you can easily edit that to keep your database file wherever youwant. If you're on Windows, you edit the same file with a URI that includes the drive letter: sqlobject.dburi = "sqlite:///d|databases/bookmarker.db" This puts your database in a databases folder on your D: drive. At this point in the process, we know we want a list of bookmarks with a name, a URL, and some descriptive text. So we'll just use the SQLObject column definition syntax (Table 4.1) to define a simple model in model.py:
from sqlobject import * from turbogears.database import PackageHub hub = PackageHub("bookmarker") __connection__ = hub class Bookmark (SQLObject): name=UnicodeCol(alternateID=True, length=100) link=UnicodeCol() description=UnicodeCol() After we've got our Bookmark class defined, we can use tg-admin sql create to have SQLObject generate the database. Hoorah! There isn't a need to keep a separate SQL data definition language file synchronized with your application! All three of our database columns contain Unicode strings, but SQLObject provides column classes for most data types; we've listed them all in Table 4.1. Okay, so now we've got a model class and a database back end. The next thing we're going to dois fire up a Python shell where we can play with our model and add some initial data. To do this, type tg-admin shell, and you'll be greeted with a standard >>>Python prompt with your model objects already importedfor you. So we can add new Bookmark objects like this:
Now we've got a couple of bookmark objects defined and mapped to rows in our database. All we need to do is create a controller method that handles a list request and sends a set of all the bookmarks to the template. Remember that CherryPy (and therefore TurboGears) handles all the incoming HTTP/request stuff for you, and calls the first method that matches the URL in yourcontroller's class hierarchy. So, let's open up controllers.py and edit it to look like this: import logging import cherrypy import turbogears from turbogears import controllers, expose, validate, redirect from bookmarker import json log = logging.getLogger("bookmarker.controllers") class Root(controllers.RootController): @expose(template="bookmarker.templates.welcome") def index(self): import time log.debug("Happy TurboGears Controller Responding For Duty") return dict(now=time.ctime()) @expose(template="bookmark.templates.list") def list(self): from model import Bookmark b=Bookmark.select() return dict(bookmark=b) We have now accomplished the following:
Now the only thing left to do before we have running code is to create the list.kid template. Make a copy of welcome.kid and rename it to list.kid.
Table 4.2 provides a quick view of each major Kid construct you'll use in your template.
After you've got a new list.kid file, delete the contents of the body and replace it with the following: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" py:extends="'master.kid'"> <head> <meta content="text/html; charset=UTF-8" http-equiv="content-type" py: replace="''"/> <title>Boomarker</title> </head> <body> <ul> <li py:for="bookmark in bookmarks"> <a href="${bookmark.link}"> <span py:content="bookmark.name">Link to Bookmark</span> </a> </li> </ul> </body> </html> Okay, all done! Now you can browse over to http://localhost:8080/list and see what you've done. Before we move on, let's take a deeper look at template code. There are three different ways that you can put dynamic text into your Kid templates, and we've intentionally used all three in this example. The most conceptually simple is the ${bookmark.link} style, where you put a reference to a Python object that you want to be placed into the final string. If you've used PHP, ASP, or JSP, this will be familiar to you. The ${whatever} is replaced by the contents of the whatever variable, which you probably passed in from the controller that called this template. The main limitation of this style of variable replacment is that it's not particularly designer friendly, or at least is not WYSIWYG tool friendly. When you open up this kind of code directly in a browser (rather than running TurboGears), you see that the ${} content shows up in the output. Because the actual data is likely to be longer than its variable name, this can throw off your whole design. Not only that, but it's easy for a designer to accidentally replace bookmark.name with Bookmark name when running a spell checker (or whatever), and thus break your final output. That's why we have py:content. When you put this inside of a tag, the entire contents of that tag are replaced by the value assigned to the py:content directive. In our example, we're replacing Link to Bookmark with the value of the name attribute of our bookmark object (bookmark.name). The other nice thing about this is that Dreamweaver, Nvu, and other WYSIWYG tools generally ignore XML tag attributes that they don't understand. This makes it less likely that a designer will accidentally break your template code. The third major way to insert text into our template is to use py:replace. This works almost like py:content except that it replaces the entire XHTML tag with the value we assign to it. So in our example, we replace the entire meta content tag with '', because we are extending master.kid and therefore pulling in its header information. But we still need header content so that our page will display correctly when you open it in Firefox, Nvu, or Dreamweaver. There's one other feature of Kid in this example that warrants mentioning. We use the py:for attribute as a processing directive to iterate over all the items in our set of Bookmark. The py:for directive has the same syntax as a regular for loop would in Python. Except instead of relying on indentation to mark the end of the loop, it just iterates over the XHTML tag it is attached to, along with all its subnodes. When you close the tag, you are signaling the end of the loop. We use the ${VariableName} replacement method to put the link information into the anchor tag's href value because this isn't displayed, and the links don't have to work when our designer is playing around with the template. But then when it comes to the name of the bookmark, we used py:replace inside a span tag to replace Link to Bookmark with the name of the bookmark. This means that when we (or a designer) view this page in Firefox we'll see that friendly text rather than the cryptic looking ${bookmark.name} text. We don't use it in this simple example, but py:if works just like Python if statements. Kid also has lots of other tools to help you compose pages from smaller chunks. For example, Kid has tag attributes such as py:match and py:def as well as functions such as xml() and document(). (We get back to these in Chapter 5, "Enhancing Our Bookmark Application.") We can still do a lot to improve this little application. We can add an administrative interface for adding new links and the ability to save categories for our links. We walk through most of these things in the rest of this chapter and in Chapter 5. But before we move on to those projects, let's modify the list.kid template to display the descriptive text that's in the database. Go ahead and try it on your own. As always, this code (including the solution to the earlier exercise) is available on our website at www.turbogearsbook.com/code. |