Pylons 0.9.6 Cheat Sheet

This cheat sheet is outdated because there is a newer Pylons version available.

Links

Installation

  • Debian/Ubuntu
    • Install: aptitude install python-pylons (available in lenny/testing or sid/unstable but not Etch!)
    • Update: aptitude update && aptitude install python-pylons
  • Easy-Install:
    • Install: easy_install Pylons
    • Update: easy_install -U Pylons
  • virtualenv

Project (Paste)

  • Create new project
    • paster create -t pylons myapplicationsname
    • Remove the public/index.html welcome page
    • Customize the routing in config/routing.py (e.g. set '' to a start controller)
  • Update project to new Pylons version: paster create -t pylons myapplicationsname (from above the directory where your development.ini is located)
  • Serve application via HTTP: paster serve --reload development.ini (application runs at http://localhost:5000)
  • Interactive shell: paster shell (install ipython for additional convenience)
  • Gory details of Pylons execution

development.ini

  • [server:main] (Paste)
    • use: points to egg of Paste's web server
    • host: the IP address the web server listens on
    • port: the TCP port the web server listens on
  • [app:main] (Pylons)
    • use: points to your application
    • full_stack: enables (by default) the error and exeption handling
    • cache_dir: directory where cached HTML templates and cookie-sessions are saved

(%(here)s refers the project's root directory (where the development.ini lives))

  • Accessing settings from the [app:main] section:

    from pylons import config
    my_setting = config['foo.bar']

Directory structure

data/sessions
Cookie-based sessions are saved into files in this directory.

data/templates
Cached HTML files that are rendered from your template files are stored here.

development.ini
The startup configuration for Paste.

myapplication/config/
Global configuration files of your project. Used to define routes for URL dispatching, middleware (like for adding authentication) or the template system you prefer.

myapplication/controllers/
The location of the classes that contain your application logic. This code controls your application, renders templates, and queries the database.

myapplication/docs/
Put any documentation on the application here. Preferably in rest (restructured text) format.

myapplication/i18n/
Files that deal with localized message strings of your project. (i18n = internationalization)

myapplication/lib/
Contains files that set up a number of global variables and objects that you can use in your controllers. For example everything in lib/base.py is made available in every controller.

myapplication/model/
Here go your database models. They define the database schema and sets up ORM mappers.

myapplication/public/
Static files like images, CSS style sheets or Javascript files may go here.

myapplication/templates/
Your templates that render the actual HTML output are saved here.

myapplication/tests/
Every controller you create gets a counterpart to implement automated tests here.

README.txt
Some basic instructions that you can give to the admininstrator who is supposed to install your application.

ez_setup, myapplication.egg-info, setup.cfg, setup.py
Administrative files that are used to create an egg from your project that can be deployed on a web server then.

test.ini
Similar to the development.ini. This file is used when you want to run automated tests on your project.

Controllers

  • Create new controller: paster controller name-of-new-controller
  • Controller mycontroller is located in controllers/mycontroller.py as class MycontrollerController
  • The index method is called when no action is specified
  • All symbols from lib/base.py are imported
  • Routes parameters can be accepted as arguments:

    def index(self, id):
    return 'Your ID is ' + id

  • Other parameters are available through request.params['my_paramter']
  • Return values
    • A (unicode) string
    • Rending a Mako template: render('/mytemplate')
    • Sending a HTTP error code: abort(404)
    • An iterator (StreamingController example)
    • Redirecting to another URL: redirect_to(controller='start', action='about') or redirect_to('/start/about')
  • Modifying the reponse object (to be done before the action's return statement:

    response.headers['content-type'] = 'text/xml; charset=utf-8'
    response.set_cookie('sitelang', 'uk')
    response.status_code = 201

Global objects
Configuration data is available through the config object

  • Variables from the app:main section of the development.ini: config['app_conf'] dictionary
  • Variables from the [DEFAULT] section of the development.ini: config['global_conf'] dictionary
  • Absolute path to the ini file: config['__file__']
  • Name of the application: config['package'] or config['pylons.package']
  • Path to the current project: config['here']
  • The 'h' (webhelpers) object: config['pylons.h'] (set up in config/environment.py)
  • The 'g' (globals) object: config['pylons.g'] (set up in config/environment.py)
  • Path configuration (where controllers, templates and static files are located): config['pylons.paths'] (set up in config/environment.py)

Routes

  • Links
  • Defined in config/routing.py
  • Start controller (for path /) can be specified as map.connect('', controller='start')
  • map.connect('newstoday', controller='news') -> calls the ''class NewsController'' in the controllers/news.py file.
  • Additional arguments are passed to the controller's method as named arguments (e.g. def __index__(self, id, name, city):)

Mako (Templates)

  • Unicode
    • Start all template with: # -*- coding: utf-8 -*-
  • Comments start with '##' - those lines are not rendered
  • Commands (do not forget the trailing ':')
    • % for / % endfor
    • % if / % elif / % else / % endif
  • Printing variables
    • ${ c.variablename }
    • The closing bracket must not be used alone on a line

Sample base template (to be inherited)

# -*- coding: utf-8 -*-
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>...........</title>
${ h.stylesheet_link_tag( '/style1.css', '/style2.css') }
${ h.javascript_include_tag( 'jquery.js', 'jquery.debug.js') }
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
</head>
<body>
<h1>My application</h1>
${ next.body() }
</body>
</html>

Inheriting from base template

# -*- coding: utf-8 -*-
<%inherit file="master.mako"/>
<p>Hello World</p>

Database access

  • Documentation links
  • pylons.database and SAContext are deprecated!
  • See Using SQLAlchemy with Pylons.
  • development.ini:

    sqlalchemy.url = sqlite:///%(here)s/phonebook.db
    sqlalchemy.url = mysql://username:password@host:3306/database
    sqlalchemy.url = postgres://username:password@host:5432/database
    sqlalchemy.url = oracle://username:password@host:1521/sidname

  • Options
  • Defining tables and ORM-mapping in model/__init__.py
    • Simple mapping:

      import sqlalchemy as sql
      import sqlalchemy.orm as orm

      metadata = MetaData()

      # Define table
      my_table = sql.Table(
      'tablename', metadata,
      sql.Column('id', sql.Integer, primary_key=True), # gets a sequence/serial assigned
      sql.Column('othertable_id', sql.Integer, sql.ForeignKey("othertable.id")),
      sql.Column('name', sql.Unicode(80), nullable=False, unique=True)
      )

      # Define object class for ORM-mapping (every object matches one row)
      class MyModel(object):
      def __repr__(self):
      return "MyModel (%s)" % self.name

      orm.mapper(MyModel, my_table)

  • Test access to the mapped models comfortably using paster shell
  • Creating queries and getting results
    • Get one row with certain field content (raises an exception if there are more rows returned): model.Session.query(model.MyModel).filter_by(name='John').one()
    • Get an iterator of items matching certain criteria: model.Session.query(model.MyModel).filter(model.MyModel.name=='John')
    • Get an iterator of items having certain field content: model.Session.query(model.MyModel).filter_by(name='John')
    • Get an iterator of items selected by a custom SQL condition: model.Session.query(model.MyModel).filter_by("id<:value and name=:name").params(value=224, name='fred')
    • Get one row with primary key 42: model.Session.query(model.MyModel).get(42)
    • Get all rows: model.Session.query(model.MyModel).all()
    • Get the first row: model.Session.query(model.MyModel).first()
    • Get the single row (raises an exception if there are more rows returned): model.Session.query(model.MyModel).one()
    • Get a limited number of rows (as a list)
      • model.Session.query(model.MyModel).offset(50).limit(10).all()
      • model.Session.query(model.MyModel)[10:50].all()
    • Order rows
      • model.Session.query(model.MyModel).order_by(model.MyModel.age).all()
      • model.Session.query(model.MyModel).order_by(model.sql.desc(model.MyModel.age)).all()
      • model.Session.query(model.MyModel).order_by([model.MyModel.age, model.MyModel.city]).all()
    • Count rows in result: model.Session.query(model.MyModel).count()
    • Joins (beware of cartesian products): model.Session.query(model.Model1, model.Model2).all()
    • Use filter and filter_by for generative queries:

      query = model.Session.query(model.MyModel)
      query = query.filter_by(name='John', type=189)
      query = query.filter_by(city='Hamburg')
      results = query.all()

    • Logical operators
      • AND: and_(condition1, condition2, ...)
      • AND: (condition1 & condition2 & ...)
      • OR: or_(condition1, condition2, ...)
      • OR: (condition1 | condition2 | ...)
      • NOT: not_(condition)
      • NOT: ~(condition)
  • Query operators:
    • ==
    • <
      • >
      • <=
      • >=
      • startswith('foo')
      • endswith('.com')
      • like('%jean')
      • between(100,500)
      • in('A', 'CNAME', 'MX')
      • + (concatenation of strings)
      • op('...') (custom operator)
      • ==None (NULL comparison)
  • Query functions:
    •       Gen  eral syntax: func.FUNCTIONNAME(...)
    • e.g. func.count() or func.now()
  • Creating new objects:

    new_object = model.MyModel()
    new_object.name = 'Jane'
    new_object.city = 'Tokio'
    model.Session.save(new_object)
    model.Session.flush() # not needed if set autoflush=True
    model.Session.commit() # needed because SQLAlchemy 0.4 uses transactions everywhere

  • Altering objects:

    old_object = model.Session.query(model.MyModel).get(42)
    old_object.city = 'Paris'
    model.Session.flush() # not needed if set autoflush=True
    model.Session.commit() # needed because SQLAlchemy 0.4 uses transactions everywhere

  • Deleting objects (do not use del(...)):

    old_object = model.sac.query(model.MyModel).get(42)
    model.Session.delete(old_object)
    model.Session.flush() # not needed if set autoflush=True
    model.Session.commit() # needed because SQLAlchemy 0.4 uses transactions everywhere

  • Arbitrary selects (return what you query for instead of complete rows):

    query = model.sql.select([model.MyModel.some_column])
    result = model.Session.execute(query).fetchall() # or .fetchmany() or .fetchone()

  • Types
    • String(length=None) [better use Unicode instead]
    • Integer
    • SmallInteger
    • Numeric(precision=10, length=2)
    • Float(precision=10)
    • DateTime [corresponds to datetime.datetime]
    • Date [corresponds to datetime.date]
    • Time [corresponds to datetime.time]
    • Binary(length=None)
    • Boolean
    • Unicode(length=None)
    • PickleType

Cookie-based Sessions

  • Beaker home page
  • development.ini
    • beaker.session.key = myproject_session (name of the session cookie being sent to the browser)
    • beaker.session.secret = somesecret (random string that the cookie string sent to the user is signed with)
    • beaker.session.cookie_expires = True (whether the cookie is a session cookie that expires when the browser is closed)
    • beaker.session.timeout = ... (time in seconds until the session times out)
    • beaker.session.data_dir = ... (path where the 'sessions' directory is located storing the session data)
    • beaker.session.type = ... (Storage type for session information.)
      • dbm stores sessions in files on disk
      • file stores sessions in files on disk
      • memory stores sessions in RAM
      • ext:memcached stores sessions on memcached servers. Memcache servers are configured as beaker.session.url = server1, server2, ..
  • The session variable comes from pylons.session and is imported in your lib/base.py. It behaves like a dictionary.
  • Loading a value from a session:

    value = session['whatever']

  • Saving a value into the session:

    session['whatever'] = value
    session.save()

  • Deleting a key from the session:

    del session['whatever']
    session.save()

  • Clearing the session:

    session.clear() session.save()

Formencode

  • Documentation
  • Using formencode:
    • Define a validation schema
    • Decorate your controller's method/action (@formencode.validate(MySchema))
    • The validated parameter will be available in the self.form_result dictionary. Note: If the schema changes values (like if specifying if_empty) then this will not be visible in the request.params dictionary.
    • If the form that the user submitted does not validate then formencode will re-run the current action (or run the action that you specify as form) as in @formencode.validate(MySchema, form='anotheraction') and display error messages right above the input fields with a CSS class of '.error-message'
  • Example validation class:

    # My form schema
    class MySchema(formencode.Schema):
    allow_extra_fields = True
    filter_extra_fields = True
    days = formencode.validators.IntRange(min=0, max=365, not_empty=True)
    name = formencode.validators.MaxLength(50)

    # MySchema schema without the 'days' field
    class MySchema2(MySchema):
    days = None # (removes days field from the superclass)

    # MySchema schema with additional 'comment' field
    class MySchema3(MySchema):
    comment = String(min=20)

  • Subclass variables:
    • allow_extra_fields = False (whether to display an error if fields are found in the POST request that are not mentioned in this subclass)
    • filter_extra_fields = False (whether to remove fields that are not mentioned in this subclass) are missing in the POST request but defined in this subclass)
    • ignore_key_missing = False (whether to display an error if fields are missing in the POST request but defined in this subclass)
    • if_key_missing (fields that are missing in the POST request will get this value assigned)
  • (Fancy)Validator variables:
    • if_empty = False (default value in case of an empty field)
    • not_empty = False (whether the field is allowed to be left empty)
    • strip = False (whether the field will be stripped from whitespace)
    • if_invalid = ... (return this value if the user entered something invalid)
    • if_missing = ... (return this value if the field is missing from the POST request)

Javascript libraries

Webhelpers

  • The functions from webhelpers.rails are imported automatically
  • Controllers and templates can access the webhelpers module by the 'h' (helper) name
  • Links:

Internationalisation (i18n)

  • Install Babel:
    • Debian/Ubuntu: aptitude install python-pybabel (formerly accidentally named "python-babel" but was renamed later)
    • Other operatings systems: easy_install Babel
  • Wiki article
  • Edit your setup.py and enable the message_extractors section to parse gettext strings in templates.
  • Use the "_" function (an alias to the gettext function) everywhere you would use normal strings.
    • Controllers: _('Hello World')
    • Mako templates: ${ _('Hello World') }
  • First time:
    • Create an english pot template file in the i18n directory: python setup.py extract_messages
    • Create a po file for every language (here the language is 'es'): python setup.py init_catalog -l es
    • Edit the po file in i18n/es/LC_MESSAGES/*.po and add translated strings in the msgstr fields
    • Compile the po files into mo files: python setup.py compile_catalog
  • Update:
    • Overwrite the english pot template:: python setup.py extract_messages
    • Update the po files with new strings (does not overwrite the already translated strings): python setup.py update_catalog -l es
    • Compile the po files into mo files: python setup.py compile_catalog
  • Hint: paster serve --reload does not detect changes in i18n. You will have to restart the web server.
  • To set the gettext language matching the browser language add this to your lib/base.py:

    def __before__(self):
    user_agent_language = request.languages[0][0:2]
    set_lang(user_agent_language)

    request.languages is an array of preferred languages that the users sets in the browser. I.e. ['de', 'en', 'en-us'].

Unit Tests (Nose)

Community

  • IRC: #pylons @ irc.freenode.net