A close relative of web.py maintained by Branko Vukelic (bg [dot] branko [at] gmail [dot] com). Compared to web.py, it has very little different features, but it does have fixes for various bugs not released in the main project.
Each release is versioned like this:
${web.py's version}.${foxbunny's patchset}fox
For example, first patchset over version 0.34 would be 0.34.1fox.
Because of the need to commit patches to upstream, the master branch in Foxbunny's web.py fork is a clean copy of the upstream project. Other branches branch from the master branch to introduce fixes for various launchpad tickets.
The fork itself is maintained in the foxbunny branch.
- Bugfixes to
@ajax
decorator handling in sweet class. - Validators module (
web.contrib.validators
)
- Fixed a critical bug in sweet class that prevented usage of
@accepts
decorator with actions.
- Added documentation for the sweet class
Patches for following tickets are included:
- Bug #542568: Templetor's _find_file method should be available to contributed template support code
- Bug #252232: Cannot use Mako templates not ending in '.html'
- Bug #130440: Radio button behavior unlike dropdown
- Bug #542963: Utility to force conversion to integer
- Bug #525260: File upload should assign cgi.FieldStorage to form.File field's value attribute
Feature not found in upstream version:
- seweet class: abstraction layer over regular web.py controllers (docs are included in the source code (web/contrib/sweet.py) and in this README.
sweet class allows you to handle requests marked as a named action. Naming an
action is performed by using the action
request parameter. At this moment,
this cannot be customized, although it is planned for future releases.
Once the request with named action is received, sweet class matches the action name to one of its custom methods. The method must be named using the same name as the action. The custom method does what is usually expected of regular POST and GET methods in usual web.py controllers.
To use the sweet class import it into your application and subclass it.
from web.contrib.sweet import sweet
class my_controller(sweet):
pass
The above simplistic controller is only capable of raising the
NotImplementedError
exception on every request and nothing else. To make
this controller do anything remotely useful, you need to define at least the
default
method. This method is a catch-all method that is called whenever
no action
parameter is received, or a matching method is not found.
class my_controller(sweet):
def default(self):
# do something useful here
You might have noticed that the default
method is not explicitly marked as
accepting either POST or GET response. It actually accepts both. If you need to
differentiate between different HTTP verbs, you can test the value of
self._method
attribute:
class my_controller(sweet):
def default(self):
if self._method == 'GET':
# respond to GET
elif self._method == 'POST':
# respond to POST
elif self._method == 'PUT':
# respond to PUT
If you want to make different responses to AJAX and non-AJAX methods, you can
test if the request was made via an AJAX call by looking at the value of
self._is_ajax
attribute. It contains None
if the request was non-AJAX,
and it contains the contents of the 'HTTP_X_REQUESTED_WITH' header if it was.
Assumption here is that only AJAX method set any content in this header. This
behavior might change in future versions of the sweet class to whitelisting of
header contents, in which case the _is_ajax
attribute would contain True
or False
.
The point of a sweet class is to define methods that respond to a particular action. To do this, simply define a method that matches the name of an action you wish to respond to:
class my_controller(sweet):
def default(self):
# default action
def create(self):
# respond only to ``create`` action
In the setup above, if you pass an action parameter with the value of
create
, the create
method is called instead of the default
. You
can pass the parameter either using the URL parameter ?action=create
or
via a web form in a POST request, like in this example:
<form action='/my/controller' method='post'>
<input type=text name='myfield' /><br />
<input type='submit' name='action' value='create' />
</form>
Any arguments that are contained in URL definitions are accepted by custom methods.
urls = ('/posts/(title)', 'my_controller')
class my_controller(sweet):
def show_post(self, title):
# do something
Although this applies to the default
method as well, we suggest you only
restrict custom methods, and allow the default
method to respond to any
request, and handle any unforeseen situation yourself.
To restrict a custom method, you can use the decorators provided by the sweet module.
from web.contrib.sweet import sweet, accepts, ajax
class my_controller(sweet):
def default(self):
# default action
@accepts('POST')
@ajax
def custom(self):
# custom method
# accepts only POST request made via AJAX
@accepts('GET')
def list(self)
# accepts only GET requests, AJAX or otherwise
@accepts('GET')
@ajax(False)
def archive(self):
# accepts only GET, non-AJAX requests
# AJAX requests are not responded to
@accepts(['POST', 'PUT'])
def update(self):
# accepts both POST and PUT requests, AJAX or otherwise
def beep(self):
# accepts all HTTP verbs, AJAX or otherwise
In case you didn't catch it, the ajax
decorator restricts both ways. If
it is invoked with True
or no argument, the method will only accept
AJAX requests and bounce non-AJAX requests. If it is invoked with a False
argument, it will bounce AJAX requests. To accept both, do not use this
decorator.
The accepts
decorator accepts either string or list argument. A list
argument can contain multiple HTTP verbs the method will respond to. Currently
sweet supports the following verbs out-of-box: GET, HEAD, POST, PUT, DELETE.
To support a custom verb, you need to follow the instructions in the custom
verb section below (see Setting up the sweet subclass for custom verbs).
To simplify hadnling of URL parameters in GET requests, sweet class can extract
the values from web.input
every time an instance is initialized. To do that
specify a property that returns a dictionary of URL parameters and their default
values. There are two ways to do this:
# Method A
class my_controller(sweet):
urlparams = {'page': 1, 'per_page': 10}
# Method B
class my_controller(sweet):
@property
def urlparams(self):
# do some calculations
return result # <- ``result`` is a dict
Once the instance is initialized, you can access the values of the URL
parameters using the self._q
attribute. In the above example, the page
parameter can be accessed as self._q.page
or self._q['page']
.
You can limit the entire sweet subclass to a subset (or superset) of HTTP verbs
it will allow. To do this, define the allowed_methods
property. This can be
done the same way as with URL parameters above, but the result is a list, not
dictionary. By default, sweet class is restricted to GET and POST verbs.
While sweet subclass is instantiated, it goes through three phases. It first
calls the subclass' __init__
, then it does the instance configuration,
and finally calls the subclass' _init
.
It is important to note that __init__
is called before configuration step,
and _init
is called after it. In the configuration step, the subclass is
assigned the _a
, _q
, _i
, _method
, and _is_ajax
attributes.
These attributes cannot be accessed during the __init__
phase, and therefore
if you need to do any custom configuration that involve these attributes, you
should override the _init
method and do the configuration there.
Here are the descriptions of the sweet class attribute mentioned in the previous section.
_a
(action): Contains the name of the action. Defaults todefault
_i
(input): Contains all of the request parameters (same asweb.input
)_q
(query): Contains the URL params specified byurlparams
_method
: Contains the HTTP verb used in the request._is_ajax
: Contains the contents of 'HTTP_X_REQUESTED_WITH' header (or None if this header was not set by the client)
Because of the way web.py works, custom HTTP verbs cannot be added dynamically.
To set up the sweet subclass to accept custom verbs, you have to define a method
with the name of the verb and (optionally) hand the controll over to _handle
method. Here is an example using the custom COPY verb:
class my_controller(sweet):
allowed_methods = ['GET', 'POST', 'COPY']
def COPY(self, *args):
self._method = 'COPY'
self.handle(*args)
@accepts('COPY')
def copy_this(self, *args):
# copy something
If you don't want the custom method to handle any actions, simply omit the
self._method = ...
and self.handle(...)
lines, and write your own
controller code.
Foxbunny's web.py ships with a set of predefined validators for use with the
web.form library. These currently include two groups of validators:
parameterized generic validators, and regexp-based specific ones. To use the
validators, you must import the web.contrib.validators
module. Here is an
example:
>>> from web import form
>>> from web.contrib inport validators as v
>>> f = form.Form(
>>> form.Textbox('text', v.required)
>>> )
The above will create a form that has a single text field which is required (i.e., you must enter a non-null value).
Parameterized validators require you to specify a value for the validator.
These are usually some limits, just as a maximum value, or minimum length.
All parameterized validators take an optional parameter msg
that allows you
to customize the error message (this is not the case with non-parametric
validators, but it will change in near future). Following is a list of such
validators currently included in the validators module:
max_len
(length[, msg]): Ensures that fields contain less than or equal tolength
characters.min_len
(length[, msg]): Ensures that fields contain a minimum oflength
characters.ex_len
(length[, msg]): Ensures that fields contain exactlylength
characters.max_val
(value[, msg]): Ensures that the fields' value is less than or equal to value.min_val
(value[, msg]): Ensures that fields' value is equal to or more than thevalue
.enum
(list[, msg]): Ensures that the contents of fields match one of the elements in the specifiedlist
.dropdown
(ddlist[, msg]): Given a list of 2-tuples such as those used forform.Dropdown
fields, it ensures that fields' contain one of the allwed values.
Example:
>>> f = form.Form(
>>> form.Textbox('commission:', v.max_val(80), v.min_val(10))
Specific validators match field values with a predefined regexp. The regexps were taken from Django's validator library. You may see the original source code in Django's SVN repository.
Here is a list of available validators:
alphanum
: Ensures that the field contains only alpha-numeric values.alphanum_path
: Same as alphanum, but allows slashes and underscores.ansi_date
: ANSI/ISO date.ansi_time
: ANSI/ISO time.ansi_datetime
: ANSI/ISO timestamp.email
: Canonical e-mail address (yes, it handles all the weird ones, too).integer
: Integer values.ip4
: IPv4 address.phone
: Phone number (NNN-NNN-NNNN).slug
: Slug URL (alphanum with dashes)url
: URL's that begin withhttp:
andhttps:
zip_code
: United States 5-digit ZIP codes.zip4_code
: US ZIP+4 code (NNNNN-NNNN).required
: Non-empty value enforcement.
It has to be noted that none of the specific validators (except required
,
of course) actually enforce non-empty values. The validators will only work if
the field contains any data at all. To ensure that there is a non-empty
requirement, use the required
validator in conjunction with the others.
Here's an example:
>>> intf = form.Textbox('number', v.integer)
>>> intf.validate('A') # This is invalid
>>> intf.validate('') # This is valid
>>> intf = form.Textbox('numer', v.integer, v.required)
>>> intf.validate('A') # This is invalid
>>> intf.validate('') # Now this is also invalid
All the default messages from all validators are run through gettext and marked for translation.
The value of the action
parameter is case-sensitive. This will be solved in
future versions, but for now keep in mind the case-sensitivity of this parameter
and name your methods accordingly. Another workaround is to use hidden fields in
forms rather than controls whose values are user-facing (e.g, submit buttons).
The action
parameter cannot contain characters that are not allowed in
Python method names. For example, it cannot contain spaces, or slashes, or dots,
etc. This will be fixed in future versions with a predictable algorhythm for
normalization of action
parameter. You can use the same workaround as the
second one in the case-sensitivity issue to work around this issue.