web.py fork with a few added extras
Python
Pull request Compare This branch is 97 commits ahead, 187 commits behind webpy:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
experimental
test
tools
web
.gitignore
ChangeLog.txt
LICENSE.txt
README.mkd
setup.py

README.mkd

Foxbunny's web.py

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.

Versioning scheme

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.

Branches

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.

Changelog

0.34.13fox

  • Bugfixes to @ajax decorator handling in sweet class.
  • Validators module (web.contrib.validators)

0.34.12fox

  • Fixed a critical bug in sweet class that prevented usage of @accepts decorator with actions.

0.34.11fox

  • Added documentation for the sweet class

0.34.1fox

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

Overview

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.

Basic setup

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

Differentiating between POST and GET (and other) requests

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

Differentiating between AJAX and non-AJAX requests

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.

Custom action methods

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

Restricting custom methods

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).

URL parameters

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'].

Allowed HTTP verbs

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.

Customizing instance initialization

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.

sweet class attributes

Here are the descriptions of the sweet class attribute mentioned in the previous section.

  • _a (action): Contains the name of the action. Defaults to default
  • _i (input): Contains all of the request parameters (same as web.input)
  • _q (query): Contains the URL params specified by urlparams
  • _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)

Setting up the sweet subclass for custom verbs

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.

Validators

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

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 to length characters.
  • min_len (length[, msg]): Ensures that fields contain a minimum of length characters.
  • ex_len (length[, msg]): Ensures that fields contain exactly length 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 the value.
  • enum (list[, msg]): Ensures that the contents of fields match one of the elements in the specified list.
  • dropdown (ddlist[, msg]): Given a list of 2-tuples such as those used for form.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

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 with http: and https:
  • 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

Translation of validator messages

All the default messages from all validators are run through gettext and marked for translation.

Known issues

action parameter case-sensitivity

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).

action parameter normalization

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.