While many people has moved toward Ruby on Rails, I'am more interested in learning Python, and using python-based frameworks. I've played a little with Django, but I was disappointed when I discovered that at present it doesn't support multiple-fields primary keys in your data models. So I looked around a little and i stepped into Turbogears. It seems to me that this framework is undertaking a lot of refactoring, but it still seems to me a good choice because of its modularity and interchangeability (while Django is more monolithic as I can understand).
As I'm learning, I will write some tutorials about Turbogears, hoping next people will get some help with issues I've encountered. I must say that, while I have a strong education in IT and programming languages, I am not an experienced python programmer, but this is surely not a problem (at least, in this tutorial's context :asd: ) . In this tutorial, I will show you a basic project setup: we will use Turbogears framework (126.96.36.199), Elixir (0.5.x) for data modeling & access, and Genshi for templating.
BIG DISCLAIMER: as this is just a "sandbox project", I just want to make things "work" and not stressing out about security and other important issues that every webdeveloper should be aware of. Just take notice of this before reading on.
This tutorial is based on various material I've read around the net and questions I've asked (and that have been kindly replied) in groups. I just tried to "put all the things together" and made them work.
As I'm working on Gentoo Linux, this tutorial will be geared on *NIX systems:
Set up the project
I suppose you have already installed with easy_install (or any other method you choose) the framework and the libraries I'm gonna use (Genshi & Elixir). I suggest you to install gsquickstart as it will help you in quickstarting a turbogears project with Genshi support.
Once you're all ready, you can go in your home directory and issue the following command
tg-admin quickstart -t tggenshi -e testprj
This is the standard Turbogears quickstart command: we tell the framework to create a new project called testprj, using genshi (-t tggenshi) and elixir (-e). Choose not to use Identity when you are asked to (for simplicity's sake).
The application we are going to build is a modification of the Elixir tutorial's one: a Movie Database.
First thing we need to do is choosing which database we are going to use for our application. As I have PostgreSQL, I have created separately a testprj database and a testprj user (with a password, let's say testprj just to be simple). You can simply use SQLite if you want.
Once you are ready, open the testprj/dev.cfg file with your favourite editor and look for the sqlalchemy.dburi line: this is a file which stores static configuration values, and this line holds the details to access database. Since Elixir is simply a declarative layer built on top of SqlAlchemy ORM module, what we are really configuring here is the sqlalchemy database handler which is then used by Turbogears to serve db queries. With my PostgreSQL as above, I will uncomment the line and modify it as shown here:
which tells sqlalchemy to use PostgreSQL server on localhost, with user testprj and password testprj, and database testprj (not much fantasy here).
Setup is all done for now!
Write the model
As our application is going to store a movie database, we need to describe our datamodel.
Open testprj/testprj/model.py file and have a look: it's already set up to use what we have specified in the quickstart command (Elixir and SqlAlchemy). Now we add what we need:
from turbogears.database import bind_meta_data bind_meta_data()
Here we tell sqlalchemy to bind to turbogears database we specified before.
options_defaults['autosetup'] = False options_defaults['autoload'] = False options_defaults['shortnames'] = True
Some configuration options: we turn off the Elixir autosetup and autoloading. Autoloading is useful if you already have a database structure and you want to reflect it in your data objects. This is not our case, as we are defining a new application from scratch and our database is empty. Last options tells Elixir to use class names as table names.
Now we can define our data model
from elixir import options_defaults, using_options has_field, using_table_options class Movie (Entity): has_field('title', Unicode(30),nullable=False) has_field('year', Integer, nullable=False) using_table_options(UniqueConstraint('title', 'year')) def __repr__(self): return '[movie "%s" (%d)]' % (self.title, self.year)
We have defined a Movie class, which has two fields : title (movie's title) and year (movie's year). Our first field is an unicode string (30 chars max) and cannot be null. The second field is an integer and neither can be null.
We also define a constraint: we want each tuple (title, year) to be unique, so we won't have duplicates in our database.
__repr__ method is simply a convenient string representation for our object.
For now, we don't add any more classes in our data model. We will improve it in next chapters of our tutorial :) We just need to tell Elixir that we have finished declaring our model and it can actually do the "dirty work" for us (that is, mapping data objects on database tables and make data ready to use). We just add
from elixir import setup_all [...] # At the end of our file setup_all()
That's it! You can save your models.py and go defining our controllers!
In this tutorial chapter, our goal is to create a page that simply displays all the films in our database, and another page with a form we can use to add films in the database. We start with the latter one, and we will use Turbogears widgets to get this done.
Open your controllers file at testprj/testprj/controllers.py
We need first to define our form widget. Add the missing imports (some of them will be used later) and look at the code
from turbogears import controllers, expose, redirect, validate, widgets, validators, error_handler, flash from turbogears.validators import Int, String class MovieFields(widgets.WidgetsList): title = widgets.TextField(validator=validators.NotEmpty()) year = widgets.TextField(validator=validators.NotEmpty()) addmovie_form = widgets.ListForm( fields=MovieFields(), action="/add" )
Here, we first define a MovieFields, a collection of widgets which includes a title and year text fields. We are also using Turbogears field validation. We simply want our fields not to be empty.
Then, we create a form object that will be used within our templates (addmovie_form). We want it to render as a ListForm: its fields will be MovieFields 's ones, and our form action will point to "/add" resource (we have not yet defined it as one of our controllers).
Before going on, you may check out which widgets are available by launching Turbogears Toolbox
tg-admin toolbox -c ::ffff:127.0.0.1
You can go on the widget browser and have a look at them. Refer to Widgets documentation for further informations on this.
Return to controllers.py , as we need now to define our controllers. We need three controllers:
- A controller which displays a list of films in database
- A controller which displays the "Add Film" form
- A controller which processes the Add Film form
Then we can include a fourth controller, the index one, which will be a sort of homepage of our application. Let's start from this. In our Root class, we can add:
@expose(template='testprj.templates.index',format='xhtml') def index(self): return dict()
@expose decorator simply tells the framework to make this controller available to users, and to render it with template 'index' which should be in XHTML format. We will define it later. Since we don't want to pass anything right now to our template, we return an empty dictionary. We may change this later.
Now we add the list controller:
@expose(template='testprj.templates.list',format='xhtml') def list(self): movies = model.Movie.query.all() return dict(movies=movies)
As above, we expose the function with template 'list' . This time we actually need to get data to pass to our template, the list of the films from the database. We simply call our model.Movie object and query it for all films it has stored. So simple. This time we pass our template a dictionary containing the movies we obtained from the database.
Now let's go on with our Add Movie form page:
@expose() @validate (form=addmovie_form) @error_handler(addform) def add(self,title,year): model.Movie(title=title,year=year) flash('Movie Added!') raise redirect('/') @expose(template='testprj.templates.add',format='xhtml') def addform(self,tg_errors=None): if tg_errors: flash('Something went wrong with form') return dict(form=addmovie_form)
We are defining two things here. The first controller (add) is the one which does the real job of inserting a new movie in our database. It expects to recieve data from our previously defined form (which is actually displayed to the user in the addform controller). Let's look how add function is impemented:
- I've decided, upon successfull addition, to simply redirect user to our homepage (index controller). So expose decorator is left without arguments as we don't need any template to be displayed for this action.
- @validate decorator takes care of input validation. We simply pass our form, as we have already defined our constraints into it
- @error_handler defines which controller takes charge when an error is raised upon validation. We want the user to be redirected on the original form, so addform is the right choice
add action itself takes two arguments (title and year). We then call our model and add a new Movie object. Elixir and SQLAlchemy take care of all the database interactions beyond this, that's cool!
flash function will display a flash message in our next page (index), where we redirect upon successfull insertion.
addform takes a tg_errors argument, which is checked and used in case of validation error (remember that we defined addform as the error handler of add controller, so we must properly treat any error). If any error happens, we display an error message: the widget itself is programmed to highlight specifically each error in the form so we don't need to do anything more.
If there are no errors, it means that a user has just come to fill the form. We pass it to our template for proper displaying.
Dealing with templates
As we are strictly following the MVC paradigm, we have already dealt with model and controllers, but we still miss our views. That's done with Genshi templates. Actually, kid templates are more documented, but as I understand Genshi is going to be the "standard" in next Turbogears releases, so as I'm learning from scratch, why don't use it?
In our controllers we defined three templates: index, add, list
This means we have to create three html files, named in the same way, in the testprj/testprj/templates folder.
As I'm still learning them, I don't want you to present all the nice genshi features. Now I just want to point out to relevant template snippets, so here you are
add.html: this is the simplest way to display our form widget. We use its display method, and we pass relevant parameters (such as the action and the submit text property). Notice that the form is "wrapped" into ET() call. This is because widgets in Turbogears 1.0.x are not "Genshi" ready, so we need this call to properly use them (if you forget the ET() call, you will get an ugly object string pointer reference)
list.html: this is the template logic for our list of movies. I basically made it so that, if there are no movies, a "sorry message" is shown to the user. Otherwise, we show how many movies are in the database, then we iterate over them and create a list. This should be pretty straightforward.
index.html: you just set a welcome page with a couple of links, one poiting to /addform and one pointing to /list, and you re'done.
Time to test our application
Before testing the application, there is still one important thing to do: set up our database and create all the necessary data structures. This can be done by entering root folder of our project (/testprj) and issuing the following command:
tg-admin sql create
This command will look at the model, then it will generate all the necessary SQL commands to create our database table. For example, here is what Turbogears generated to actualize the model we defined above:
CREATE TABLE movie ( id serial NOT NULL, title varchar(30) NOT NULL, "year" int4 NOT NULL, CONSTRAINT movie_pkey PRIMARY KEY (id), CONSTRAINT movie_title_key UNIQUE (title, "year") ) WITH OIDS;
As we can see, it declared a primary key (as I didn't explicitly), and then it added my constraint of uniqueness of pairs (title, year).
Now we are really ready to go. just issue the command
This will launch a test webserver on port 8080. You can reach it at http://localhost:8080. If you don't get any errors, you will be brought to your index page, where you can start adding films and listing them :asd:
Surely this is not a complex application at all, but I've tried to put all things together just to clean up your ideas. We will develop it more in the next chapter! Stay tuned ...