PylonsHQ.

Layout: Fixed-width

Validation without the validate decorator

Skip to end of metadata
Go to start of metadata

Validation done manually (aka "die, @validate, die")

For my taste (and that of others) the @validate decorator is not flexible enough. So after playing around with various ways the simplest I came up was a little helper class that I'm using in my applications.

Schema definition

Imagine a typical Formencode schema you would use to validate a form. The only thing you would do differently is add "MyValidate" to the class definition:

1
2
3
4
class ValidatePerson(formencode.Schema, MyValidate):
    last_name = formencode.validators.UnicodeString(min=2, not_empty=True)
    given_name = formencode.validators.UnicodeString(min=2, not_empty=True)
    phone = formencode.validators.UnicodeString(min=5, not_empty=True)

Validation

This "MyValidate" class adds a little magic and makes validating a form in a controller pretty simple:

1
2
3
form = ValidateEdit()
    if not form.is_valid():
        return form.complain(...)

This creates a "form" object from a Formencode schema and with .is_valid() it checks if the form input was valid. If there was a validation error then .complain() will re-display the last input form and add error messages using Formencode's "htmlfill" method. You need to replace the "..." by something that re-creates the HTML of the current form. If you are in the "save" action from saving the form input to the database then you will probably want to send the user back to the "edit" form like: return form.complain(self.edit(id)).

The helper class

You need to import this helper class anywhere in your project. I prefer the lib/ directory for such modules:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import formencode
import pylons
class MyForm(object):
    def is_valid(self, state=None):
        """Validate the form using FormEncode"""

        # See if the form validates okay through Formencode
        try:
            self.fields = self.to_python(pylons.request.params, state)
            self.formencode_exception = None
            return True
        except formencode.Invalid, e:
            self.formencode_exception = e
            return False

    def complain(self, html):
        """Add validation error messages to the 'html'"""
        if self.formencode_exception:
            errors=self.formencode_exception.unpack_errors()
        else:
            errors=None

        return formencode.htmlfill.render(
            form=html,
            defaults=pylons.request.params,
            errors=errors,
            encoding=pylons.response.determine_charset()
        )

State

(This is just a general hint on what the "state" is. It is not necessary to use my class.)

A "state" object as outlined in the Formencode documentation can be passed via the .is_valid() method. It can be used to tell Formencode something about the current state of your application. Like who is logged in or which phone book entry is supposed to be edited. The state object is nothing special - just a Python object with attributes. Example class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class State(object):
    """Trivial class to be used as State objects to transport information to formencode validators"""
    def __init__(self, **kw):
        for key in kw:
            setattr(self, key, kw[key])

    def __repr__(self):
        atts = []
        for key in self.__dict__:
            atts.append( (key, getattr(self, key)) )

        return self.__class__.__name__ + '(' + ', '.join(x[0] + '=' + repr(x[1]) for x in atts) + ')'

Feedback

I'm using this code in production and it does what I expect. Let me know if you have improvements or ideas.

Christoph

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.

Powered by Pylons - Contact Administrators