formalchemy.validators – Validation stuff

To validate data, you must bind it to your FieldSet along with the SQLAlchemy model. Normally, you will retrieve data from a dict:

>>> from formalchemy.tests import User, bill
>>> from formalchemy.forms import FieldSet
>>> fs = FieldSet(User)
>>> fs.configure(include=[fs.name]) # we only use the name field here
>>> fs.rebind(bill, data={'User-1-name': 'Sam'})

Validation is performed simply by invoking fs.validate(), which returns True if validation was successful, and False otherwise. Validation functions are added with the validate method, described above.

If validation fails, fs.errors will be populated. errors is a dictionary of validation failures, and is always empty before validate() is run. Dictionary keys are attributes; values are lists of messages given to ValidationException. Global errors (not specific to a single attribute) are under the key None.

Rendering a FieldSet with errors will result in error messages being displayed inline. Here’s what this looks like for a required field that was not supplied with a value:

<div>
 <label class="field_req" for="foo">
  Foo
 </label>
 <input id="foo" name="foo" type="text" value="" />
 <span class="field_error">
  Please enter a value
 </span>
</div>

If validation succeeds, you can have FormAlchemy put the submitted data back into the bound model object with fs.sync. (If you bound to a class instead of an object, the class will be instantiated for you.) The object will be placed into the current session, if one exists:

>>> if fs.validate(): fs.sync()
>>> print bill.name
Sam

Exception

All validators raise a ValidationError if the validation failed.

exception formalchemy.validators.ValidationError

Validators

formalchemy.validators contains two types of functions: validation functions that can be used directly, and validation function _generators_ that _return_ a validation function satisfying some conditon. E.g., validators.maxlength(30) will return a validation function that can then be passed to validate.

>>> from formalchemy.validators import *

Validation Functions

A validation function is simply a function that, given a value, raises a ValidationError if it is invalid.

formalchemy.validators.integer(value, field=None)

Successful if value is an int

>>> integer('1', field)
1
>>> integer('1.2', field)
Traceback (most recent call last):
...
ValidationError: Value is not an integer
formalchemy.validators.float_(value, field=None)

Successful if value is a float

>>> float_('1', field)
1.0
>>> float_('1.2', field)
1.2
>>> float_('asdf', field)
Traceback (most recent call last):
...
ValidationError: Value is not a number
formalchemy.validators.currency(value, field=None)

Successful if value looks like a currency amount (has exactly two digits after a decimal point)

>>> currency('asdf', field)
Traceback (most recent call last):
...
ValidationError: Value is not a number
>>> currency('1', field)
Traceback (most recent call last):
...
ValidationError: Please specify full currency value, including cents (e.g., 12.34)
>>> currency('1.0', field)
Traceback (most recent call last):
...
ValidationError: Please specify full currency value, including cents (e.g., 12.34)
>>> currency('1.00', field)
formalchemy.validators.required(value, field=None)

Successful if value is neither None nor the empty string (yes, including empty lists)

>>> required('asdf', field)
>>> required('', field)
Traceback (most recent call last):
...
ValidationError: Please enter a value
formalchemy.validators.email(value, field=None)

Successful if value is a valid RFC 822 email address. Ignores the more subtle intricacies of what is legal inside a quoted region, and thus may accept some technically invalid addresses, but will never reject a valid address (which is a much worse problem).

>>> email('a+formalchemy@gmail.com', field)
>>> email('a+."<>"@gmail.com', field)
>>> email('a+."<>"."[]"@gmail.com', field)
>>> email('a+."<>@gmail.com', field)
Traceback (most recent call last):
...
ValidationError: Unterminated quoted section in recipient
>>> email('a+."<>""[]"@gmail.com', field)
Traceback (most recent call last):
...
ValidationError: Quoted section must be followed by '@' or '.'
>>> email('<>@gmail.com', field)
Traceback (most recent call last):
...
ValidationError: Reserved character present in recipient
>>> email(chr(0) + '@gmail.com', field)
Traceback (most recent call last):
...
ValidationError: Control characters present
>>> email(chr(129) + '@gmail.com', field)
Traceback (most recent call last):
...
ValidationError: Non-ASCII characters present
>>> email('', field)
>>> email('asdf', field)
Traceback (most recent call last):
...
ValidationError: Missing @ sign
>>> email('@', field)
Traceback (most recent call last):
...
ValidationError: Recipient must be non-empty
>>> email('a@', field)
Traceback (most recent call last):
...
ValidationError: Domain must be non-empty
>>> email('a@gmail.com.', field)
Traceback (most recent call last):
...
ValidationError: Domain must not end with '.'
>>> email('a@gmail..com', field)
Traceback (most recent call last):
...
ValidationError: Domain must not contain '..'
>>> email('a@gmail>com', field)
Traceback (most recent call last):
...
ValidationError: Reserved character present in domain

Function generators

formalchemy.validators.length(min=0, max=None)

Returns a validator that is successful if the input’s length is between min and max.

>>> length(min=2, max=5)('a', field)
Traceback (most recent call last):
...
ValidationError: Value must be at least 2 characters long
>>> length(min=2, max=5)('abcdef', field)
Traceback (most recent call last):
...
ValidationError: Value must be no more than 5 characters long
>>> length(min=2, max=5)('abcde', field)
formalchemy.validators.minlength(min)

Returns a validator that is successful if the input’s length is at least the given one.

>>> minlength(0)('a', field)
Traceback (most recent call last):
...
ValueError: Invalid minimum length
>>> minlength(2)('a', field)
Traceback (most recent call last):
...
ValidationError: Value must be at least 2 characters long
>>> minlength(2)('ab', field)
formalchemy.validators.maxlength(max)

Returns a validator that is successful if the input’s length is at most the given one.

>>> maxlength(0)('a', field)
Traceback (most recent call last):
...
ValueError: Invalid maximum length
>>> maxlength(1)('a', field)
>>> maxlength(1)('ab', field)
Traceback (most recent call last):
...
ValidationError: Value must be no more than 1 characters long
formalchemy.validators.regex(exp, errormsg=u'Invalid input')

Returns a validator that is successful if the input matches (that is, fulfils the semantics of re.match) the given expression. Expressions may be either a string or a Pattern object of the sort returned by re.compile.

>>> regex('[A-Z]+$')('ASDF', field)
>>> regex('[A-Z]+$')('abc', field)
Traceback (most recent call last):
...
ValidationError: Invalid input
>>> import re
>>> pattern = re.compile('[A-Z]+$', re.I)
>>> regex(pattern)('abc')

Write your own validator

You can write your own validator, with the following function signature. The field parameter will be the Field object being validated (and though its .parent attribute, the FieldSet:

>>> def negative(value, field):
...     if not (isinstance(value, int) and value < 0):
...         raise ValidationError('Value must be less than 0')

Then bind it to a field:

>>> from formalchemy import types
>>> fs = FieldSet(One)
>>> fs.append(Field('number', type=types.Integer))
>>> fs.configure(include=[fs.number.validate(negative)])

Then it should work:

>>> fs.rebind(One, data={'One--number': '-2'})
>>> fs.validate()
True

>>> fs.rebind(One, data={'One--number': '2'})
>>> fs.validate()
False

You can also use the field positional argument to compare with some other fields in the same FieldSet if you know this will be contained in a FieldSet, for example:

>>> def passwd2_validator(value, field):
...     if field.parent.passwd1.value != value:
...         raise validators.ValidationError('Password do not match')

The FieldSet.errors and Field.errors attributes contain your custom error message:

>>> fs.errors
{AttributeField(number): ['Value must be less than 0']}

>>> fs.number.errors
['Value must be less than 0']