Summary
I think I'm really starting to get this "event" thing down. I can tell because, at the end of the "Web Frameworks Jam," half the people were saying "when are we going to do this again?"
Advertisement
Admittedly it was a small group—eight plus me—but I had as much fun and learned as much as I think I possibly could have, so it didn't really matter. And the small group was actually so nice that I even thought of limiting the size in the future. The manageability alone was great: we could all go to lunch together, hikes were much easier to coordinate, and everyone fit easily into the house and kitchen during barbueques.
It's very probable that the next event, which I hope to hold before the end of the year, will be a TurboGears Jam, because a few of us learned enough about it to jumpstart everyone else in a group, and because it seems worthwhile to focus on one framework (plus this is the one that my team liked the most!).
You can hear a podcast that we recorded at the end of the workshop . From this, you'll find out that we broke up into three groups. One worked with the Google Web Toolkit (with the help of Jim White, who had experience with it), one worked with Spring and Hibernate (although mostly Spring), and Barry Hawkins, Dianne Marsh and myself worked with TurboGears.
Installation
easy_install rocks, and that's what TurboGears uses. Python finally has a system to rival Perl's CPAN, and it's about time. Philip Eby really put a lot of thought into easy_install, and it should be part of the standard Python library.
Something I'd like to see is all the optional libraries installed as part of the TurboGears easy_install, because those libraries are often used in the tutorials. Tutorials should be created with the attitude that any little setback is going to cause some percentage of people to give up, or postpone it and maybe not come back.
I've been learning a bit more about Python deployment. For example, I started using a GoDaddy account for downloads, and then I began programming on it; it supports Python 2.4. Naturally, GoDaddy controls the Python installation so you cannot just install the necessary TurboGears packages in the site-packages directory. However, the only thing you must actually do is put those files someplace and then provide the proper path information so that Python can import the packages. This appears to be feasible on GoDaddy. I could of course install TurboGears on my own server, but I have a fascination with trying to see how far a cheap service like GoDaddy can be pushed.
What TurboGears Does
The only web programming I've done is with CGI (C++, Python and a little PHP), Java (Servlets, JSPs and JDBC, but only in an educational context and never "in anger," and EJBs just from a theoretical and generally puzzled perspective), a bit of PHP, and a fair amount of Zope. So I, like many at the workshop (and I suspect many people in the business), still don't have that much of a sense of web frameworks in general. I think one would need to work with more of these before the general sense of them would begin to dawn.
I did find, however, that TurboGears gave me a nice perspective on the problem it is intended to solve. I think this is the first time that a web framework's functionality has been so obvious (admittedly, I may have been struggling with the "web problem" long enough that it's time for an epiphany).
I think that the TurboGears philosophy of cherry-picking and assembling the "best of breed" tools is a good one. Of course, what constitutes "best of breed" is determined by Kevin Dangoor and his team, but from what I've seen so far I'm pretty happy to let him do that research and make those decisions.
The experience I often have with libraries and frameworks consists of the "point of disconnect." That is, things make sense for awhile and then at some point an idea is introduced that feels slightly wonky. That's usually the point where the designer's model got stretched too thin, or their domain knowledge was lacking.
So far, everything about TurboGears has fit nicely into my head in a very straightforward fashion, elegantly and what we often refer to as "pythonic." In particular, this is to me the clearest example of where the division into model-view-controller makes sense. The tg-admin quickstart command even produces files called model.py and controllers.py. In fact, the only thing I would call a hiccup in all this is the naming of the @expose decorator. It took us a fair amount of time to figure out what it meant, whereas if it had been named @view instead, it would have been much more obvious. (I'm guessing that expose came from CherryPy, but there's no reason that the TurboGears decorator couldn't be renamed).
You can start every project using tg-admin quickstart. This provides enough of a framework to actually run, so you can get "hello, world" for you application right away, and then begin adding to it. There's even support for unit testing, which Barry looked at and found to be adequate (we often relied on him for opinions, because he's built enterprise systems using other frameworks, notably Java).
The Model, aka Database
The "model" support provided by TurboGears is about easy connection to the database. Of course, the actual model for an application usually comprises more than this, notably business logic. However, in TurboGears that part is considered to vary too significantly between projects and so you write that code yourself (my impression is that EJB was about trying to reuse business objects, and that didn't seem to work out so well).
Once you run tg-admin quickstart, you go into the dev.cfg file and tell it how to connect to your database; this is a single-line URI similar to what you do for JDBC. The tutorial suggests sqlite, but we found MySQL to be more straightforward—although you do need to create the database and establish permissions, this is what you normally have to do with a database anyway.
Although I do not have broad experience with object-relational mapping (ORM) techniques, and Alex Martelli has said that every one that he's seen makes him ill, the one used by TurboGears, called SQLObjects, seems to me to be quite elegant and reasonable. It performs the most common operations without requiring you to write SQL, but if you need to do something special it's easy to drop into SQL. This seems like a good compromise to me.
You create a database model component by writing a class containing static fields and inheriting from SQLObject. Here's an example for a comment collector:
class Comment(SQLObject):
name = UnicodeCol()
email = UnicodeCol()
body = UnicodeCol()
When you run tg-admin sql create, you'll automatically create a table in your database called comment, containing 4 fields: the three you see above, plus an autoincrement id field to uniquely identify each object. You can access id programmatically, and you can create "alternate ID" fields which can also be used to look up objects.
When you create a new Comment object, SQLObject will create a new row in your database. When you assign to one of the fields, SQLObject will update that row (you can turn on batch mode for more efficiency). You can also do things like joins and foreign keys when you define the fields in an object.
SQLObject is not limited to creating its own databases. You can work with an existing database by setting a metadata tag in the class.
It appears that, by the time version 1.0 of TurboGears comes out, they will be using SQLAlchemy because it is significantly faster. However, you will still be able to continue using the syntax of SQLObject, which will be automatically adapted to SQLAlchemy.
The Controller
The structure of the controllers defined in the controllers.py file that tg-admin quickstart generates for you comes from the CherryPy server that TurboGears has selected for the controller system.
For each page in the system, you create controller code as a method in a class, then you indicate to CherryPy (via TurboGears) that you want this method to be called whenever the page is visited. For example, here is the controllers.py file that tg-admin quickstart creates for you:
import turbogears
from turbogears import controllers
class Root(controllers.Root):
@turbogears.expose("test.templates.welcome")
def index(self):
import time
return dict(now=time.ctime())
You can name your controller class anything you'd like, but it must inherit from controllers.Root. The @expose decorator (which, as I previously mentioned, I'd like to be @view instead) tells the method where it should hand off its data and control when it's finished; in this case it's the kid file test/templates/welcome.kid. The name of the method becomes the last part of the URL used to invoke that method.
If you are submitting a form to one of these methods, the form variables appear as named arguments. So in my comment-collector, if the form contains the fields name, email and body, the controller could look like this:
import turbogears
from turbogears import controllers
class CommentCollector(controllers.Root):
@turbogears.expose("commentcollector.templates.store")
def store(self, name, email, body):
Comment(name=name, email=email, body=body)
return dict()
The @expose decorator indicates that "store" is a legal page to call, and that the resulting page will be displayed using commentcollector/templates/store.kid.
When you create an SQLObject class, it automatically makes a constructor for you that allows you to pass in the fields for your class. By creating a Comment object, I automatically insert a new row into the database.
When you return a value from the method, you return it as a dictionary (a Python dict, aka map or associative array). Ordinarily the results will be displayed via a Kid template, in which case the dictionary is transparently available inside that template. You can also redirect to another page. It's even possible to display the results as JSON (a kind of second-generation version of AJAX) by simply annotating with @expose("json"). MochiKit is also integrated into TurboGears.
You can create or import multiple classes in your controllers.py file, which allows you to partition functionality into classes that might be reusable.
The View
You display pages using the Kid templating language. This is a simple syntax added on top of HTML, so learning to use it is not onerous. As mentioned earlier, the dictionary that you return from your controller method is automatically passed in and available inside the Kid template. So if you wanted to create a page displaying the comment, after writing a controller method that returns a dictionary with the appropriate data, you could insert that data like this:
<h3 py:content="name">This is replaced.</h3>
<h3 py:content="email">This is replaced.</h3>
<h3 py:content="body">This is replaced.</h3>
Notice how the Kid syntax is relatively succinct, and makes use of the existing html. You can even view a Kid template directly with your browser, which is often quite helpful.
Kid has the usual set of constructs for doing things like looping to display a list, but these are very Python-like so they tend to be easy to read and remember. For example, a for loop:
<ul>
<li py:for="fruit in fruits">
I like ${fruit}s
</li>
</ul>
Notice how it uses the closing html tag to delimit the loop, and compare this with other systems; JSPs for example.
Kid doesn't limit you—you can embed python scripts within your Kid templates if you really want, although you then run the risk of mixing your MVC components together.
One thing I like a lot about Kid and the way TurboGears uses it is the "inheritance" model, so that you can establish the basic look and feel of all your pages in master.kid and then each new page inherits that look and feel (unless you tell it otherwise). This is almost always what you want to do on a site, and I think they've solved the problem nicely.
The Tutorial
During the workshop, we initially had the ambition that we were going to jump in and start building something new right away, but then we got reasonable and decided to just work through the existing tutorial first (the Spring/Hibernate group also decided this). This was the "20-minute Wiki," which ended up taking us about 2 days to get through (admittedly interrupted by hikes and dinners and the like).
It took so long because we kept hitting snags, which we attributed primarily to the fact that TurboGears is pre-1.0 and moving quite quickly—there were three version changes during the workshop, which fixed a number of our issues (thus, the list below may indicate more errors than there actually are). We could certainly understand how it would be difficult to be maintaining the tutorial at the same time, but it made for a hard out-of-the-box experience. In fact, we all agreed that if we hadn't had the other people in our team to keep us going, we would have given up. Despite that, our general experience with TurboGears was that everything was very polished, so we expect that by the time version 1.0 comes out the tutorial should be polished as well.
Dianne created this list with the idea that, if you want to go through the tutorial before 1.0 comes out, it may help you get over some of the trouble spots that we ran into.
Although the documentation for TurboGears suggests that you run the tutorials before reading the Getting Started guide, we think that it might actually be helpful to do the reading first, mainly because the tutorials aren’t rock solid just yet.
An offline install would be particularly useful in a situation like we were in, where we didn’t have internet connectivity. Availability of the entire documentation tree would be convenient (essential?) as well. This also helps when you have connectivity but not necessarily good response (or when the site is under heavy use).
If you don’t have docutils, install it (separate download). It doesn’t come with the TurboGears easy install.
Tutorial refers to “six” readable lines (in page.kid). Looks like:
“5. Deals with Unicode properly (always a good habit, on line 5).” seems to have been omitted. Another typo in same section “in other workds”.
While the tutorial implies that using sqlite is the easiest path to getting a working app, it’s probably easier to use mysql or postgres if you’re comfortable with those. You will need to edit the dev.cfg file, but the skeletons are there.
If you want to use sqlite, download it. SQLite3 is compatible with pysqlite2. You will need both. Same problem if you’re going to use MySQL. Grab the MySQLDB installer (we used 1.2.1). Assuming a similar issue if you want to use postgres (?).
Changes to kid files do not seem to require restarting turbogears. Changes to python files (model.py, etc.) may require restarting turbogears. Getting Started guide claims that server restarts if it sees any changes to any files, but the tutorials instructs us to restart in certain situations, so it’s unclear when you need to restart and when you don’t.
While the tutorial doc seems to imply that the TurboGears tools will create databases for you (by naming the header “Creating a Database”), that’s only partly true (depends on your choice of databases). tg-admin sql create will create databases and tables for sqlite or postgres (given a user with appropriate permissions). The database must already exist for MySQL (and proper permissions must be set), and sql create will then create the appropriate tables. The tutorial doesn’t give indications regarding these distinctions.
It appears that tg-admin sql create must be run from the directory where dev.cfg and prod.cfg live (otherwise, you will get an error message, politely asking you if you want to fix things, but the autofix will fail).
We had a problem with catwalk, when following the directions specified in the wiki20 tutorial. The tg-admin toolbox version of catwalk will work fine if the page already exists, but catwalk generated an error (from CherryPy) rather than offering an option to “Add Page” when run from the toolbox. Instead, we imported catwalk and instantiated it with our model (as directed in the turbotunes tutorial), we were able to add a page from catwalk. Essentially, the wiki20 tutorial will not work as directed (which purposefully leads you down a path of not having a page in order to demonstrate this functionality of catwalk). Instead, either add the info to the database directly or use the following for the controllers.py content:
import logging
import cherrypy
import turbogears
from turbogears import controllers, expose, redirect
from wiki20 import json
from wiki20.model import page
from docutils.core import publish_parts
from turbogears.toolbox.catwalk import CatWalk;
import model
log = logging.getLogger("wiki20.controllers")
class Root(controllers.RootController):
catwalk = CatWalk(model)
@expose(template="wiki20.templates.page")
def index(self, pagename="FrontPage"):
p=page.byPagename(pagename)
content=publish_parts(p.data,writer_name="html")["html_body"]
return dict(page=p, data=content )
Some of the variable names in the tutorial (and in the quickstart-generated template) make for unnecessary confusion. Examples follow.
In the controllers.py file (as directed by the tutorial):
Trying to do this tutorial using 0.8.9 hit the wall when we got to the CatWalk step (adding pages to database), so we decided to abandon 0.8.9 and upgrade to 0.9 for the one team member.
Trying to install SQLAlchemy (e.g., from Debian) is a bad thing to do. TurboGears tries to take advantage of this and you will be unable to run the quickstart-generated code out of the box.
Method for save in controller doesn’t have enough arguments. CherryPy gives an error:
TypeError: save() got an unexpected keyword argument 'submit'
Bug report review of turbogears revealed the magic fix:
If you follow the instruction to change the HTTPRedirect to turbogears.url(“/%s” %pagename), you get the following error
Page handler: <bound method Root.save of <wiki20.controllers.Root object at 0x015D9A50>>
Traceback (most recent call last):
File "c:\python24\lib\site-packages\CherryPy-2.2.1-py2.4.egg\cherrypy\_cphttptools.py", line 105, in _run
self.main()
File "c:\python24\lib\site-packages\CherryPy-2.2.1-py2.4.egg\cherrypy\_cphttptools.py", line 254, in main
body = page_handler(*virtual_path, **self.params)
File "<string>", line 3, in save
File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\controllers.py", line 210, in expose
output = database.run_with_transaction(func._expose,func, accept, allow_json, allow_json_from_config,*args, **kw)
File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\database.py", line 216, in run_with_transaction
retval = func(*args, **kw)
File "<string>", line 5, in _expose
File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\controllers.py", line 230, in <lambda>
func._expose.when(rule)(lambda _func, accept, allow_json, allow_json_from_config,*args,**kw: _execute_func(
File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\controllers.py", line 252, in _execute_func
assert isinstance(output, basestring) or isinstance(output, dict) \
AssertionError: Method Root.save() returned unexpected output. Output should be of type basestring, dict or generator.
Changing to:
raise turbogears.redirect(“/%s”%pagename) works fine.
Inconsistency in presentation of source code in documentation is disconcerting. Sometimes, code is shown directly (e.g., wikiwords section). Most other times, only command line instructions are presented directly and code is shown using the “syntax highlighter” style. (Also note: “View plain” is really cool but left for the reader to discover independently).
Using 0.9.a5 (but appears to be working on 0.9a7+), ran into problem with JSON section. Adding @expose(“json”) yielded an error message:
The server encountered an unexpected condition which prevented it from fulfilling the request.
@expose("wiki20.templates.pagelist")
@expose("json")
def pagelist(self):
pages = [page.pagename for page in Page.select(orderBy=Page.q.pagename)]
return dict(pages=pages)
Page handler: <bound method Root.pagelist of <wiki20.controllers.Root object at 0x015F3D90>>
Traceback (most recent call last):
File "c:\python24\lib\site-packages\CherryPy-2.2.1-py2.4.egg\cherrypy\_cphttptools.py", line 105, in _run
self.main()
File "c:\python24\lib\site-packages\CherryPy-2.2.1-py2.4.egg\cherrypy\_cphttptools.py", line 254, in main
body = page_handler(*virtual_path, **self.params)
File "<string>", line 3, in pagelist
File "<string>", line 3, in pagelist
File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\controllers.py", line 210, in expose
output = database.run_with_transaction(func._expose,func, accept, allow_json, allow_json_from_config,*args, **kw)
File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\database.py", line 216, in run_with_transaction
retval = func(*args, **kw)
File "<string>", line 5, in _expose
File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\controllers.py", line 230, in <lambda>
func._expose.when(rule)(lambda _func, accept, allow_json, allow_json_from_config,*args,**kw: _execute_func(
File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\controllers.py", line 251, in _execute_func
output = errorhandling.try_call(func, *args, **kw)
File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\errorhandling.py", line 71, in try_call
return func(self, *args, **kw)
TypeError: pagelist() takes exactly 1 argument (2 given)
Fix follows, but then get JSON format WHENEVER pagelist is viewed (even without tg_format=json parameter). Tutorial pretty much broken from this point on. Verified by running a version that Bruce had working (same result).
@expose("wiki20.templates.pagelist")
@expose("json")
def pagelist(self, *args, **kwds):
pages = [page.pagename for page in Page.select(orderBy=Page.q.pagename)]
return dict(pages=pages)
I'm glad you had fun with the Web Frameworks Jam and I'm sorry I couldn't make it. My schedule this month has been insane, and it wasn't even an option for me.
I'm also happy to hear that you enjoyed working with TurboGears. Thanks for sticking it out through the rough edges of the docs (the edges have crept in so much as to almost be a rough middle!). I've been working with Mark Ramm on a large documentation project (aka "book", Rapid Web Applications with TurboGears, coming at the end of October), which has kept me from bringing closure to TG's current documentation state. I'm nearly done with my part and hope to start hacking on TG's docs (and documentation setup) very soon.
These blog posts certainly make it sound like a great time!
By the way... that reply was after just a quick skim of the article. I'll reply with more detail a bit later once I've had a chance to read the article more fully.
I really like your suggestion of @view instead of @expose. In CherryPy, the decorator just flags methods that you want to make available to the web. In TurboGears, the decorator is definitely used for specifying how the view works, so the @view name makes a lot of sense.
It appears that, by the time version 1.0 of TurboGears comes out, they will be using SQLAlchemy because it is significantly faster. However, you will still be able to continue using the syntax of SQLObject, which will be automatically adapted to SQLAlchemy.
The reason for sqlalchemy isn't speed, but rather flexibility. SQLObject works fine for many tasks but it doesn't handle legacy databases very well (or at least that's why I switched to SA). I haven't heard about a SO compatibility layer, but rather a declarative layer built on SA that allows you to declare classes similarly to how SO works.
I really need to get re-involved in turbogears and write some docs.
for instructions on setting up packages with easy_install in places other than site-packages. You should use either the "virtual python" or "traditional PYTHONPATH" approach. Either one will get you set up so that easy_install installs to a local directory in your account.
Anyway, just follow one of those sets of directions first, then you can install easy_install, followed by TurboGears or anything else you might like.
It's even possible to display the results as JSON (a kind of second-generation version of AJAX)...
This is not quite correct. JSON is an alternative syntax for data transfer which is built into JavaScript (JSON = JavaScript Object Notation).
It's an alternative to XML and, in my opinion, works a lot better than XML for use in AJAX... which should really be called AJAJ if you're (a) using JSON and (b) really anal about your acronyms.