Section 4.1. Building a Simple Bookmark Collection Site


4.1. Building a Simple Bookmark Collection Site

We'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:

Table 4.1. Model Classes SQLObject Column Types

SQLObject Column Types

Description

BLOBCol

Blob of binary datajpeg's, word files, etc. (Works with MySql, Postgres, and SQLlite only)

BoolCol

Stores true/false data, works (differently) on all back ends

CurrencyCol

Equivalent to DecimalCol(size=10, precision=2)

DateCol

Stores date returned as datetime

DateTimeCol

Stores date and timereturned as datetime

DecimalCol

Stores a decimal (size=(Number of digits), precision=(after the decimal)

FloatCol

Stores a floating-point number

PickleCol

Can store any Python object (It is an extension of BLOBCol, which uses the pickle module from the Python standard library to store serialized objects.)

StringCol

Stores a string (If length is defined, that's the length; if not, a TEXT col is used.)

UnicodeCol

Special string column that encodes in UTF-8 by default

Model Classes SQLObject Column Parameters

dbName

Name of Column in database (If none is specified, Pythonic name is converted from MixedCase to underscore_separated; for example, UserName becomes user_name.)

default

Defines the default value for the column (If not set, the column is required.)

alternateID

If true, 1) makes column value unique, 2) creates a "byUsername like method" to your class

unique

If true, makes column value unique

notNone

If true, null not allowed for the column


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:

[View full width]

$>tg-admin shell >>> Bookmark(name="Google", link="http://www.google.com",description="The one linkto rule them all.") <Bookmark 1 name='Google' link="'http://www.googl...'"> >>> from turbogears import database >>> database.commit_all() >>> Bookmark(name="Compound Thinking",link="http://compoundthinking.com", description="Ablog.") <Bookmark 2 name='Compound Thinking' link="'http://compoundt...'" >>>from turbogears import database >>> hub.commit()


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:

1.

Decided to expose this using the bookmark/templates/list.kid template (using Python dotted notation to locate the template within our project)

2.

Pulled in an iterator with every Bookmark object (that is, every row from our bookmark table)

3.

Passed that iterator over to our view in a dictionary as a bookmark

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.

Why Do Controllers Return a Dictionary?

Something that often jumps out at new TurboGears users is how controller methods return a dictionary. In other frameworks, you either return a string representing the output to send to the user, write the output via a response object, or possibly return some object that can directly render the output.

By returning a dictionary rather than rendered output, you're able to leave the decision of how to render the output to TurboGears. The first advantage to this is that it saves you some code. If you're using CherryPy alone, you have to call your template engine to render out your template and then return the string that you get back. This gives you complete control over the template engine used, but it also makes you do all the work.

The next advantage to the TurboGears approach is that it enables you to return multiple forms of output from a single method. This makes it possible to conveniently use one set of logic to generate output that can be used in different ways. One example of this that comes up often is the ability to generate an HTML page if a user comes directly to that URL, or to generate JavaScript Object Notation (JSON-formatted data for Ajax requests.

But, you're not limited to HTML and JSON. You can also output XML or plain text from the same method. We talk more about how to configure all these options in Chapter 19, "The TurboGears Toolbox and Other Tools."

A third advantage to returning a dictionary rather than the direct output is in testing. TurboGears provides a way for your test code to call your controller methods and just get the dictionary back, instead of rendering it out to HTML. This gives you an easy way to test your controller's logic without worrying about how the view is working. It's also a lot easier to inspect the values in a dictionary than it is to inspect generated HTML.

If for some reason the dictionary return values can't properly express what you need the method to return, you can always return a string, just like you can with plain CherryPy.


Table 4.2 provides a quick view of each major Kid construct you'll use in your template.

Table 4.2. Kid Template Basics

Kid Construct

Description

<?python
x = 0
<?

Embed larger chunks of Python in Kid with <?python ?> syntax.

${Variable}

Replaces this expression with the value of Variable.

py:for

Repeats this tag once per item in the set provided.

py:if

This element (and all its descendants) should be rendered only if the py:if=value is true.

py:content

Replaces the contents of an XHTML element with the value of the py:content tag.

py:replace

Replaces the entire attribute with the value of the py:replace tag.

py:strip

If the expression is true, eliminate the tag but keep everything under the tag.


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.




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

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net