| Author: | Copyright (C) 2007 Dipl.-Inform. Christoph Haas <email@christoph-haas.de> |
|---|---|
| IRC: | Meet me at #pylons on irc.freenode.net as Signum |
| License: | This document is published under the terms of the MIT license: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software. |
Note
The source of the document is available from a Mercurial repository at http://workaround.org/cgi-bin/hg-pylons-cheatsheet - please send in patches to email@christoph-haas.de although you may be reading the document on wiki.pylonshq.com. Thanks.
(%(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']
| 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. |
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
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
Configuration data is available through the config object
# -*- 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>
# -*- coding: utf-8 -*- <%inherit file="master.mako"/> <p>Hello World</p>
Documentation links
pylons.database and SAContext are deprecated!
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
Defining tables and ORM-mapping in model/__init__.py
Simple mapping:
import sqlalchemy as sql
import sqlalchemy.orm as orm
# 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)
Order rows
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
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:
- General 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.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()
development.ini
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()
Documentation
Using formencode:
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:
(Fancy)Validator variables:
Install Babel:
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'].
- FormAlchemy: Automatically creates HTML forms for SQLAlchemy schemas (mapped classes)
- Paginator: Helps split up large numbers of results into pages and lets the user navigate through the pages
- DBSprockets: Automatically creates Toscawidget forms and Formencode Validators for SQLAlchemy schemas (mapped classes)
...