LAX Book

Authors: Nicolas Chauvat
Sylvain Thénault
Adrien Di Mascio
Date: 2008-07-12
Version: 0.3
organisation:Logilab
Copyright: © 2008 Logilab
Contact: contact@logilab.fr

Contents

1   Introduction

This book uses version 0.4.0 of LAX.

1.1   What is LAX ?

LAX stands for Logilab Appengine eXtension. It is a web application framework based on Google AppEngine.

LAX is a port of the web framework Logilab has been developping since 2001. This framework originally published data queried from different sources including SQL databases, LDAP directories and concurrent versionning systems (like subversion). In April/May 2008, it was adapted to run also on top of Google AppEngine's datastore.

Google AppEngine is provided with a partial port of the Django framework, but Google stated at Google IO 2008 that it would not support a specific Python web framework and that all community-supported frameworks would be more than welcome[1]_.

LAX competes with other Python web application frameworks to get developers' attention and support. It originates from Logilab and is the result of about ten years of experience in developing large-scale web applications.

Distinctive features include a data-model driven engine, a full-blown query language, a selection/view mechanism for HTML/XML/text generation, reuseable components, etc. It all sums up to very fast and efficient development.

If you like Python and its standard library, chances are you will like LAX for it comes with batteries included thanks to its standard component library.

Compare LAX with other frameworks and see for yourself what is your best option.

[1]for more on this matter, read our blog at http://www.logilab.org/blog/5216

1.2   Essentials

Schema

The schema defines the data model of an application as entities and relationships. It is the core of an application. Entities and relationships are modeled with a comprehensive language made of Python classes.

Query language

A full-blown query language named RQL is used to formulate requests to the datastore.

Result set

A resultset encapsulates the results of a request sent to the datastore and informations about this request.

Views

A view is applied to a result set to present it as HTML, XML, JSON, CSV, etc. Views are implemented as Python classes. There is no templating language.

Generated user interface

A user interface is generated on-the-fly from the schema definition: entities can be created, displayed, updated and deleted. As display views are not very fancy, it is usually necessary to develop your own. Any generated view can be overridden by defining a new one with the same identifier.

Components

Pieces of schema and sets of views can be combined into components. Larger applications can be built faster by importing components, adding entities and relationships and overriding the views that need to display or edit informations not provided by components.

2   Installation

2.1   Download the source

After unpacking the archive lax-0.4.0.tar.gz, you get a lax-0.4.0 directory that contains a skel directory:

lax-0.4.0/
 |-- doc/
 |-- README
 `-- skel/
      |-- app.conf
      |-- app.yaml
      |-- bin/
      |    `-- laxctl
      |-- custom.py
      |-- data/
      |-- dateutil/
      |-- docutils/
      |-- fckeditor/
      |-- ginco/
      |-- ginco-apps/
      |    |-- eaddressbook/
      |    ..
      |    |-- ecomment
      |    ..
      |    `-- ezone/
      |-- i18n/
      |-- index.yaml
      |-- loader.py
      |-- logilab/
      |-- main.py
      |-- migration.py
      |-- mx/
      |-- roman.py
      |-- rql/
      |-- schema.py
      |-- simplejson/
      |-- tools/
      |-- views.py
      |-- vobject/
      |-- yams/
      `-- yapps/

This skeleton directory is a working AppEngine application. You will recognize the files app.yaml and main.py. All the rest is the LAX framework and its third-party libraries. You will notice that the directory ginco-apps is a library of reusable components.

2.2   Setup

To make sure you do not change the original skeleton, first copy lax/skel/ to a new directory that will be your new application.

On Mac OS X and Unix:

$ cp -r lax/skel myapp

For the laxctl command to work, full/path/to/google_appengine and full/path/to/myapp have to be in the PYTHONPATH.

2.2.1   Generating translation files

LAX is fully internationalized. Translation catalogs are found in myapp/i18n. To compile the translation files, use the gettext tools or the laxctl command

$ python myapp/bin/laxctl i18nupdate
$ python myapp/bin/laxctl i18ncompile

Ignore the errors that print "No translation file found for domain 'erudi'". They disappear after the first run of i18ncompile.

2.2.2   Generating the data directory

In order to generate the myapp/data directory that holds the static files like stylesheets and icons, you need to run the command:

$ python myapp/bin/laxctl populatedata

2.2.3   Generating the schema diagram

There is a view named schema that displays a diagram of the entity-relationship graph defined by the schema. This diagram has to be generated from the command line:

$ python myapp/bin/laxctl genschema

2.3   Quickstart

If you can not wait to read the guide and want to get started right away, just use that myapp/ as an application directory.

On Mac OS X platforms, drag that directory on the GoogleAppEngineLauncher.

On Unix and Windows platforms, run it with the dev_appserver:

python /path/to/google_appengine/dev_appserver.py /path/to/myapp/

Once the local server is started, visit http://MYAPP_URL/_load and sign in as administrator. This will initialize the repository.

Log out by clicking in the menu at the top right corner and restart browsing from http://MYAPP_URL/ as a normal user.

Unless you did something to change it, http://MYAPP_URL should be http://localhost:8080/

3   Creating your first application

This tutorial will guide you step by step to build a blog application and discover the unique features of LAX. It assumes that you followed the installation guidelines and that both the AppEngine SDK and the LAX framework are setup on your computer.

3.1   Creating a new application

When you installed LAX, you saw a directory named skel. Make a copy of this directory and call it BlogDemo.

3.2   Defining a schema

With LAX, the schema/datamodel is the core of the application.

Let us start with something simple and improve on it iteratively.

In schema.py, we define two entities : Blog and BlogEntry.

class Blog(EntityType):
    title = String(maxsize=50, required=True)
    description = String()

class BlogEntry(EntityType):
    title = String(maxsize=100, required=True)
    publish_date = Date(default='TODAY')
    text = String(fulltextindexed=True)
    category = String(vocabulary=('important','business'))
    entry_of = SubjectRelation('Blog', cardinality='?*')

A Blog has a title and a description. The title is a string that is required and must be less than 50 characters. The description is a string that is not constrained.

A BlogEntry has a title, a publish_date and a text. The title is a string that is required and must be less than 100 characters. The publish_date is a Date with a default value of TODAY, meaning that when a BlogEntry is created, its publish_date will be the current day unless it is modified. The text is a string that will be indexed in the full-text index and has no constraint.

A BlogEntry also has a relationship entry_of that link it to a Blog. The cardinality ?* means that a BlogEntry can be part of zero or one Blog (? means zero or one) and that a Blog can have any number of BlogEntry (* means any number including zero). For completeness, remember that + means one or more.

3.3   Using the application

Defining this simple schema is enough to get us started. Make sure you followed the setup steps described in detail in the installation chapter (especially visiting http://localhost:8080/_load as an administrator), then launch the application with the command:

python dev_appserver.py BlogDemo

and point your browser at http://localhost:8080/ (if it is easier for you, use the on-line demo at http://lax.appspot.com/).

login screen

After you log in, you will see the home page of your application. It lists the entity types: Blog and BlogEntry. If these links read blog_plural and blogentry_plural it is because internationalization (i18n) is not working for you yet. Please ignore this for now.

home page

Let us create a few of these entities. Click on the [+] at the right of the link Blog. Call this new Blog Tech-blog and type in everything about technology as the description, then validate the form by clicking on button_ok.

from to create blog

Click on the logo at top left to get back to the home page, then follow the Blog link. You should be seeing a list with a single item Tech-blog.

displaying a list of a single blog

Clicking on this item will get you to its detailed description except that in this case, there is not much to display besides the name and the phrase everything about technology.

displaying the detailed view of a blog

Now get back to the home page by clicking on the top-left logo, then create a new Blog called MyLife and get back to the home page again to follow the Blog link for the second time. The list now has two items.

displaying a list of two blogs

Get back to the home page and click on [+] at the right of the link BlogEntry. Call this new entry Hello World and type in some text before clicking on button_ok. You added a new blog entry without saying to what blog it belongs. There is a box on the left entitled actions, click on the menu item modify. You are back to the form to edit the blog entry you just created, except that the form now has another section with a combobox titled add relation. Chose entry_of in this menu and a second combobox appears where you pick MyLife.

editing a blog entry to add a relation to a blog

Validate the changes by clicking button_ok. The entity BlogEntry that is displayed now includes a link to the entity Blog named MyLife.

displaying the detailed view of a blogentry

Remember that all of this was handled by the framework and that the only input that was provided so far is the schema. To get a graphical view of the schema, run the laxctl genschema BlogDemo command as explained in the installation section and point your browser to the URL http://localhost:8080/schema

graphical view of the schema (aka data-model)

3.4   Conclusion

3.4.1   Exercise

Create new blog entries in Tech-blog.

3.4.2   What we learned

Creating a simple schema was enough to set up a new application that can store blogs and blog entries.

3.4.3   What is next ?

Although the application is fully functionnal, its look is very basic. In the following section we will learn to create views to customize how data is displayed.

4   Developing the user interface with Views

Before moving to this section, make sure you read the Essentials section in the Introduction.

Tip: when modifying views, you do not need to restart the local server. Just save the file in your editor and reload the page in your browser to see the changes.

4.1   The selection/view principle

With LAX, views are defined by Python classes. A view includes :

  • an identifier (all objects in LAX are entered in a registry and this identifier will be used as a key)
  • a filter to select the resulsets it can be applied to

LAX provides a lot of standard views, for a complete list, you will have to read the code in directory ginco/web/views/ (XXX improve doc).

For example, the view named primary is the one used to display a single entity.

If you want to change the way a BlogEntry is displayed, just override the view primary in BlogDemo/views.py

01. from ginco.web.views import baseviews
02.
03. class BlogEntryPrimaryView(baseviews.PrimaryView):
04.
05.     accepts = ('BlogEntry',)
06.
07.     def cell_call(self, row, col):
08.         entity = self.entity(row, col)
09.         self.w(u'<h1>%s</h1>' % entity.title)
10.         self.w(u'<p>published on %s in category %s</p>' % \
11.                (entity.publish_date.strftime('%Y-%m-%d'), entity.category))
12.         self.w(u'<p>%s</p>' % entity.text)

The above source code defines a new primary view (line 03) for BlogEntry (line 05).

Since views are applied to resultsets and resulsets can be tables of data, it is needed to recover the entity from its (row,col) coordinates (line 08). We will get to this in more detail later.

The view has a self.w() method that is used to output data. Here lines 09-12 output HTML tags and values of the entity's attributes.

When displaying same blog entry as before, you will notice that the page is now looking much nicer.

blog entries now look much nicer

Let us now improve the primary view of a blog

01. class BlogPrimaryView(baseviews.PrimaryView):
02.
03.     accepts = ('Blog',)
04.
05.     def cell_call(self, row, col):
06.         entity = self.entity(row, col)
07.         self.w(u'<h1>%s</h1>' % entity.title)
08.         self.w(u'<p>%s</p>' % entity.description)
09.         rset = self.req.execute('Any E WHERE E entry_of B, B eid "%s"' % entity.eid)
10.         self.wview('primary', rset)

In the above source code, lines 01-08 are similar to the previous view we defined.

At line 09, a simple request in made to build a resultset with all the entities linked to the current Blog entity by the relationship entry_of. The part of the framework handling the request knows about the schema and infer that such entities have to be of the BlogEntry kind and retrieves them.

The request returns a selection of data called a resultset. At line 10 the view 'primary' is applied to this resultset to output HTML.

This is to be compared to interfaces and protocols in object-oriented languages. Applying a given view to all the entities of a resultset only requires the availability, for each entity of this resultset, of a view with that name that can accepts the entity.

Assuming we added entries to the blog titled MyLife, displaying it now allows to read its description and all its entries.

a blog and all its entries

Before we move forward, remember that the selection/view principle is at the core of `LAX`. Everywhere in the engine, data is requested using the RQL language, then HTML/XML/text/PNG is output by applying a view to the resultset returned by the query. That is where most of the flexibility comes from.

[WRITE ME]

  • implementing interfaces, calendar for blog entries
  • show that a calendar view can export data to ical

We will implement the ginco.interfaces.ICalendarable interfaces on entities.BloEntry and apply the OneMonthCalendar and iCalendar views to resultsets like "Any E WHERE E is BlogEntry"

  • create view "blogentry table" with title, publish_date, category

We will show that by default the view that displays "Any E,D,C WHERE E publish_date D, E category C" is the table view. Of course, the same can be obtained by calling self.wview('table',rset)

  • in view blog, select blogentries and apply view "blogentry table"
  • demo ajax by filtering blogentry table on category

we did the same with 'primary', but with tables we can turn on filters and show that ajax comes for free.

5   Components

5.1   Standard library

A library of standard components is part of the LAX release (look at lax/skel/ginco-apps). Components provide entities and views. With lax-0.4, you should get:

  • addressbook: PhoneNumber and PostalAddress
  • ebasket: Basket (like a shopping cart)
  • eblog: Blog (a very basic blog)
  • eclassfolder: Folder (to organize things but grouping them in folders)
  • eclasstags: Tag (to tag anything)
  • ecomment: Comment (to attach comment threads to entities)
  • efile: File (to allow users to upload and store binary or text files)
  • elink: Link (to collect links to web resources)
  • emailinglist: MailingList (to reference a mailing-list and the URLs for its archives and its admin interface)
  • eperson: Person (easily mixed with addressbook)
  • etask: Task (something to be done between start and stop date)
  • ezone: Zone (to define places within larger places, for example a city in a state in a country)

5.2   Adding comments to BlogDemo

To import a component in your application just change the line in the app.conf file. For example:

included-yams-components=ecomment

Will make the Comment entity available in your BlogDemo application.

Change the schema to add a relationship between BlogEntry and Comment and you are done. Since the ecomment component defines the comments relationship, adding the line:

comments = ObjectRelation('Comment', cardinality='1*', composite='object')

to the definition of a BlogEntry will be enough.

Clear the datastore and restart.

[WRITE ME]

  • explain the component architecture
  • add comments to the blog by importing the comments component

6   MainTemplate

Look at lax/skel/ginco/web/views/basetemplates.py and you will find the base templates used to generate HTML for your application.

The MainTemplate is a bit complex as it tries to accomodate many different cases. If you want to change the footer for example, look for HTMLPageFooter and override it in your views file as in:

form ginco.web.views.basetemplates import HTMLPageFooter

class MyHTMLPageFooter(HTMLPageFooter):

    def call(self, **kwargs):
        self.w(u'<div class="go_to_css">I was here</div>')

[WRITE ME]

7   RSS Channel

Assuming you have several blog entries, click on the title of the search box in the left column. A larger search box should appear. Enter:

Any X ORDERBY D WHERE X is BlogEntry, X creation_date D

and you get a list of blog entries.

Click on your login at the top right corner. Chose "user preferences", then "boxes", then "possible views box" and check "visible = yes" before validating your changes.

Enter the same query in the search box and you will see the same list, plus a box titled "possible views" in the left column. Click on "entityview", then "RSS".

You just applied the "RSS" view to the RQL selection you requested.

That's it, you have a RSS channel for your blog.

Try again with:

Any X ORDERBY D WHERE X is BlogEntry, X creation_date D,
X entry_of B, B title "MyLife"

Another RSS channel, but a bit more focused.

A last one for the road:

Any C ORDERBY D WHERE C is Comment, C creation_date D LIMIT 15

displayed with the RSS view, that's a channel for the last fifteen comments posted.

[WRITE ME]

8   RQL

Try reading lax/skel/rql/doc. Yes, it is in french but will get translated soon!

[WRITE ME]

9   URL Rewriting

[WRITE ME]

10   Security

[WRITE ME]

11   Frequently Asked Questions

[ADD MORE FAQ]

12   API Reference

12.1   Schema API

12.1.1   Base Types

Base types are defined as a set in yams.BASE_TYPES that includes: String, Int, Float, Boolean, Date, Time, Datetime, Interval, Password, Bytes.

Add link to yams' API documentation.

12.1.2   Constraints

Constraints are defined in yams.constraints and include: UniqueConstraint, SizeConstraint, RegexpConstraint, BoundConstraint, IntervalBoundConstraint, StaticVocabularyConstraint, MultipleStaticVocabularyConstraint.

Add link to yams' API documentation.

12.2   Views API

[WRITE ME]