- 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
- 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
- [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))
| 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
- 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
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)
- 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):)
- 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
# -*- 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!
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
# 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:
- 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.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
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()
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)
- 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)
- The functions from webhelpers.rails are imported automatically
- Controllers and templates can access the webhelpers module by the 'h' (helper) name
- Links:
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'].
- IRC: #pylons @ irc.freenode.net
- 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)
...
|
|