Friday, August 6, 2010

Flying with web2py on Google App Engine

Here is the second part of the series about Python frameworks under Google App Engine. Now we will talk about web2py, a simple and fast Python web framework. Like Django, web2py has a great data abstraction layer. Unlike Django, the web2py data abstraction layer (DAL) was designed to manage non-relational databases, including BigTable.

The first step is setup the environment, which is something really easy ;) First, access the web2py official website and in download section, get the source code in a zip file called web2py_src.zip. After download this file, extract it. A directory called web2py will be created, I renamed it to web2py_blog, but it is not relevant. web2py extracted directory is ready to Google App Engine, it contains an app.yaml file with settings of the application, for the application developed here, the following file was used:
application: gaeseries
version: 2
api_version: 1
runtime: python

handlers:

- url: /(?P<a>.+?)/static/(?P<b>.+)
  static_files: applications/\1/static/\2
  upload: applications/(.+?)/static/(.+)
  secure: optional
  expiration: "90d"

- url: /admin-gae/.*
  script: $PYTHON_LIB/google/appengine/ext/admin
  login: admin
 
- url: /_ah/queue/default
  script: gaehandler.py
  login: admin

- url: .*
  script: gaehandler.py  
  secure: optional

skip_files: |
^(.*/)?(
 (app\.yaml)|
 (app\.yml)|
 (index\.yaml)|
 (index\.yml)|
 (#.*#)|
 (.*~)|
 (.*\.py[co])|
 (.*/RCS/.*)|
 (\..*)|
 ((admin|examples|welcome)\.tar)|
 (applications/(admin|examples)/.*)|
 (applications/.*?/databases/.*) |
 (applications/.*?/errors/.*)|
 (applications/.*?/cache/.*)|
 (applications/.*?/sessions/.*)|
 )$
I changed only the two first lines, everything else was provided by web2py. The web2py project contains a subdirectory called applications where the web2py applications are located. There is an application called welcome used as scaffold to build new applications. So, let’s copy this directory and rename it to blog. Now we can walk in the same way that we walked in the django post: we will use two actions on a controller: one protected by login, where we will save posts, and other public action, where we will list all posts.

We need to define our table model using the web2py database abstraction layer. There is a directory called models with a file called db.py inside the application directory (blog). There are a lot of code in this file, and it is already configured to use Google App Engine (web2py is amazing here) and the web2py built-in authentication tool. We will just add our Post model at the end of the file. Here is the code that defines the model:
current_user_id = (auth.user and auth.user.id) or 0

db.define_table('posts', db.Field('title'),
                    db.Field('content', 'text'),
                    db.Field('author', db.auth_user, default=current_user_id, writable=False),
                    db.Field('date', 'datetime', default=request.now, writable=False)
                )

db.posts.title.requires = IS_NOT_EMPTY()
db.posts.content.requires = IS_NOT_EMPTY()
This code looks a little strange, but it is very simple: we define a database table called posts with four fields: title (a varchar – default type), content (a text), author (a foreign key – forget this in BigTable – to the auth_user table) and date (an automatically filled datetime field). On the last two lines, we define two validations to this model: title and content should not be empty.

Now is the time to define a controller with an action to list all posts registered in the database. Another subdirectory of the blog application is the controllers directory, where we put the controllers. web2py controllers are a Python module, and each function of this module is an action, which responds to HTTP requests. web2py has an automatic URL convention for the action: /<application>/<controller>/<action>. In our example, we will have a controller called posts, so it will be a file called posts.py inside the controllers directory.

In the controller posts.py, we will have the action index, in that way, when we access the URL /blog/posts, we will see  the list of the posts. Here is the code of the index action:
def index():
    posts = db().select(db.posts.ALL)
    return response.render('posts/index.html', locals())
As you can see, is just a few of code :) Now we need to make the posts/index.html view. The web2py views system allow the developer to use native Python code on templates, what means that the developer/designer has more power and possibilities. Here is the code of the view posts/index.html (it should be inside the views directory):
{{extend 'layout.html'}}
<h1 id="">Listing all posts</h1>
<dl>
    {{for post in posts:}}
    <dt>{{=post.title}} (written by {{=post.author.first_name}})</dt>
    <dd>{{=post.content}}</dd>
    {{pass}}
</dl>
And now we can run the Google App Engine server locally by typing the following command inside the project root (I have the Google App Engine SDK extracted on my /usr/local/google_appengine):
% /usr/local/google_appengine/dev_appserver.py .
If you check the URL http://localhost:8080/blog/posts, then you will see that we have no posts in the database yet, so let’s create the login protected action that saves a post on the database. Here is the action code:
@auth.requires_login()
def new():
    form = SQLFORM(db.posts, fields=['title','content'])
    if form.accepts(request.vars, session):
        response.flash = 'Post saved.'
        redirect(URL('blog', 'posts', 'index'))
    return response.render('posts/new.html', dict(form=form))
Note that there is a decorator. web2py includes a complete authentication and authorization system, which includes an option for new users registries. So you can access the URL /blog/default/user/register and register yourself to write posts :) Here is the posts/new.html view code, that displays the form:
{{extend 'layout.html'}}

<h1 id="">
Save a new post</h1>
{{=form}}
After it the application is ready to the deploy. The way to do it is running the following command on the project root:
% /usr/local/google_appengine/appcfg.py update .
And see the magic! :) You can check this application live here: http://2.latest.gaeseries.appspot.com/ (you can login with the e-mail demo@demo.com and the password demo, you can also register yourself).

And the code here: https://github.com/fsouza/gaeseries/tree/web2py.