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
-
MySQL closes inactive connections automatically. You would need:
sqlalchemy.pool_recycle = 3600
-
Print all queries to the console (you can as well use the 'logging' module as described in the SQLAlchemy documentation):
sqlalchemy.echo = True
- Complete list of supported options
-
MySQL closes inactive connections automatically. You would need:
-
Defining tables and ORM-mapping in model/__init__.py
-
Simple mapping:
import sqlalchemy as sql
import sqlalchemy.orm as ormmetadata = 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.nameorm.mapper(MyModel, my_table)
-
Simple mapping:
- 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
- Formencode web site
- Forms are validated through subclasses of formencode.schema
- Custom validators are usually subclasses of formencode.FancyValidator
- Writing custom validators
-
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
- jQuery (small, simple Javascript utility library with lots of plugins)
- Prototype (Javascript utility library)
- script.aculo.us (Javascript effects library built on top of Prototype)
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
- Tags:
Recent comments
9 hours 30 min ago
11 hours 6 min ago
11 hours 35 min ago
15 hours 15 min ago
16 hours 45 min ago
1 day 13 hours ago
1 day 17 hours ago
1 day 22 hours ago
1 day 23 hours ago
2 days 8 hours ago