All elements support validation. The default, built-in validation logic is simple: if the element is empty, it is invalid. Otherwise it is valid.
If that sounds too simple don’t worry- you can customize validation to suit your needs.
>>> from flatland import String
>>> form = String()
>>> form.is_empty
True
>>> form.valid
Unevaluated
>>> form.validate()
False
>>> form.valid
False
Validation sets the valid attribute of each element it inspects. validate() may be invoked more than once.
>>> form.set('Squiznart')
True
>>> form.is_empty
False
>>> form.validate()
True
>>> form.valid
True
Note that default validation does not set any error messages that might be displayed to an interactive user. Messages are easily added through custom validation.
validate() is recursive by default. Called on a parent node, it will descend through all of its children, validating each. If the parent or any one of its children are invalid, validate returns false. Note that recursion does not stop if it finds an invalid child: all children are evaluated, and each will have its valid attribute updated.
If a field is marked as optional, its elements are exempt from validation when empty. With the default validation strategy, this effectively means that element can never be invalid. With custom validation, optional fields become more useful.
>>> from flatland import Dict, Integer
>>> schema = Dict.of(Integer.named('x'),
... Integer.named('y'),
... Integer.named('z').using(optional=True))
>>> form = schema(dict(x=1))
>>> form.validate()
False
>>> form.valid
True
>>> form['x'].valid
True
>>> form['y'].valid
False
>>> form['z'].valid
True
The flatland.signals.validator_validated signal is emitted each time a validator evaluates an element. The signal’s sender is the validator (or the symbol flatland.validation.NotEmpty for the default validation strategy). The signal also sends the element, the state, and the result of the validation function.
During development, it can be convenient to connect the validator_validated signal to a logging function to aid in debugging.
from flatland.signals import validator_validated
@validator_validated.connect
def monitor_validation(sender, element, state, result):
# print or logging.debug validations as they happen:
print "validation: %s(%s) valid == %r" % (
sender, element.flattened_name(), result)
>>> from flatland import String
>>> form = String(name='surname')
>>> form.validate()
validation: NotEmpty(surname) valid == False
False
The default validation support is useful for some tasks, however in many cases you will want to provide your own validation rules tailored to your schema.
Flatland provides a low level interface for custom validation logic, based on a simple callable. Also provided is a higher level, class-based interface that provides conveniences for messaging, i18n and validator reuse. A library of commonly needed validators is included.
To use custom validation, assign a list of one or more validators to a field’s validators attribute. Each validator will be evaluated in sequence until a validator returns false or the list of validators is exhausted. If the list is exhausted and all have returned true, the element is considered valid.
A validator is a callable of the form:
element is the element being validated, and state is the value passed into validate(), which defaults to None.
A typical validator will examine the value of the element:
def no_shouting(element, state):
"""Disallow ALL CAPS TEXT."""
if element.value.isupper():
return False
else:
return True
# Try out the validator
from flatland import String
form = String(validators=[no_shouting])
form.set('OH HAI')
assert not form.validate()
assert not form.valid
There are two phases when validating an element or container of elements. First, each element is visited once descending down the container, breadth-first. Then each is visited again ascending back up the container.
The simple, scalar types such as String and Integer process their validators on the descent phase. The containers, such as Form and List process validators on the ascent phase.
The upshot of the phased evaluation is that container validators fire after their children, allowing container validation logic that considers the validity and status of child elements.
>>> from flatland import Dict, String
>>> def tattle(element, state):
... print element.name
... return True
...
>>> schema = (Dict.named('outer').
... of(String.named('inner').
... using(validators=[tattle])).
... using(validators=[tattle]))
>>> form = schema()
>>> form.validate()
inner
outer
True
Descent validation can be aborted early by returning SkipAll or SkipAllFalse from a validator. Children will not be validated or have their valid attribute assigned. This capability comes in handy in a web environment when designing rich UIs.
Containers will run any validators in their descent_validators list during the descent phase. Descent validation is the only phase that may be short-circuited.
>>> from flatland import Dict, SkipAll, String
>>> def skip_children(element, state):
... return SkipAll
...
>>> def always_fail(element, state):
... return False
...
>>> schema = Dict.of(String.named('child').using(validators=[always_fail])).\
... using(descent_validators=[skip_children])
>>> form = schema()
>>> form.validate()
True
>>> form['child'].valid
Unevaluated
A form that fails to submit without a clear reason is frustrating. Messages may be stashed in the errors and warnings lists on elements. In your UI or template code, these can be used to flag individual form elements that failed validation and the reason(s) why.
def no_shouting(element, state):
"""Disallow ALL CAPS TEXT."""
if element.value.isupper():
element.errors.append("NO SHOUTING!")
return False
else:
return True
See also add_error(), a wrapper around errors.append that ensures that identical messages aren’t added to an element more than once.
A powerful and i18n-capable interface to validation and messaging is available in the higher level Validation API.
If you want to tweak the element’s value or u string representation, validators are free to assign directly to those attributes. There is no special enforcement of assignment to these attributes, however the convention is to consider them immutable outside of normalizing validators.
validate() accepts an optional state argument. state can be anything you like, such as a dictionary, an object, or a string. Whatever you choose, it will be supplied to each and every validator that’s called.
state can be a convenient way of passing transient information to validators that require additional information to make their decision. For example, in a web environment, one may need to supply the client’s IP address or the logged-in user for some validators to function.
A dictionary is a good place to start if you’re considering passing information in state. None of the validators that ship with flatland access state, so no worries about type conflicts there.
class User(object):
"""A mock website user class."""
def check_password(self, plaintext):
"""Mock comparing a password to one stored in a database."""
return plaintext == 'secret'
def password_validator(element, state):
"""Check that a field matches the user's current password."""
user = state['user']
return user.check_password(element.value)
from flatland import String
form = String(validators=[password_validator])
form.set('WrongPassword')
state = dict(user=User())
assert not form.validate(state)
Element provides a rich API for accessing a form’s members, an element’s parents, children, etc. Writing simple validators such as requiring two fields to match is easy, and complex validations are not much harder.
def passwords_must_match(element, state):
"""Both password fields must match for a password change to succeed."""
if element.value == element.parent.el('password2').value:
return True
element.errors.append("Passwords must match.")
return False
from flatland import Form, String
class ChangePassword(Form):
password = String.using(validators=[passwords_must_match])
password2 = String
new_password = String
form = ChangePassword()
form.set({'password': 'foo', 'password2': 'f00', 'new_password': 'bar'})
assert not form.validate()
assert form['password'].errors
To stop validation of an element & skip any remaining members of flatland.FieldSchema.validators, return flatland.Skip from the validator:
from flatland import Skip
def succeed_early(element, state):
return Skip
def always_fails(element, state):
return False
from flatland import String
form = String(validators=[succeed_early, always_fails])
assert form.validate()
Above, always_fails is never invoked.
To stop validation early with a failure, simply return False.
The Validator class implements the validator callable interface and adds conveniences for messaging, internationalization, and customization.
To use it, subclass Validator and implement validate().
from flatland.validation import Validator
class NoShouting(Validator):
"""Disallow ALL CAPS TEXT."""
has_shouting = "NO SHOUTING in %(label)s, please."
def validate(self, element, state):
if element.value.isupper():
self.note_error(element, state, 'has_shouting')
return False
return True
from flatland import String
schema = String.using(validators=[NoShouting()])
Above is a Validator version of the basic custom validator example. In this version, the flatland.validation.Validator.note_error() method allows the messaging to be separated from the validation logic. note_error has some useful features, including templating and automatic I18N translation.
The base constructor of the Validator class has a twist that makes customizing existing Validators on the fly a breeze. The constructor can be passed keyword arguments matching any class attribute, and they will be overridden on the instance.
schema = String.using(validators=[NoShouting(has_shouting='shh.')])
Subclassing achieves the same effect.
class QuietPlease(NoShouting):
has_shouting = 'shh.'
schema = String.using(validators=[QuietPlease()])
The validators that ship with Flatland place all of their messaging and as much configurable behavior as possible in class attributes to support easy customization.
Messages prepared by Validator.note_error() and Validator.note_warning() may be templated using keywords in the sprintf-style Python string format syntax.
Possible keys are taken from multiple sources. In order of priority:
Flatland supports ngettext-style message pluralization. For this style, messages are specified as a 3-tuple of (singular message, plural message, n-key). n_key is any valid templating keyword, and its value n will be looked up using the same resolution rules. If the value n equals 1, the singular form will be used. Otherwise the plural.
from flatland.validation import Validator
class MinLength(Validator):
min_length = 2
too_short = (
"%(label)s must be at least one character long.",
"%(label)s must be at least %(min_length)s characters long.",
"min_length")
def validate(self, element, state):
if len(element.value) < self.min_length:
self.note_error(element, state, "too_short")
return False
return True
Conditional pluralizaton functions with or without I18N configured.
Messages can be translated using gettext-compatible functions. Translation works in conjunction with message templating features: the message itself is translated, and strings substituted into the message are also translated individually.
Translation uses ugettext and optionally ungettext functions that you provide. You may place these functions in the state, place them on the element or its schema, or place them in Python’s builtins.
An element’s ancestry will be searched for these functions. If you like, you may assign them soley to the top-most element or its schema and they will be used to translate all of its child elements.
If you opt to supply ugettext but not ungettext, Flatland’s built-in pluralization will kick in if a pluralized message is found. Flatland will choose the correct form internally, and the result will be fed through ugettext for translation.
Dynamic generated messages can also take advantage of the templating and internationalization features. There are two options for dynamic messages through Validator.note_error() and Validator.note_warning():
- Supply the message directly to note_error using message="..." instead of a message key.
- Messages looked up by key may also be callables. The callable will be invoked with element and state, and should return either a message string or a 3-tuple as described in pluralization.
Base class for fancy validators.
Apply formatting to a validation message.
Parameters: |
|
---|---|
Returns: | the formatted string |
See Message Templating, Message Pluralization and Message Internationalization for full information on how messages are expanded.
Locate a message-transforming function, such as ugettext.
Returns None or a callable. The callable must return a message. The call signature of the callable is expected to match ugettext or ungettext:
Subclasses may override this method to provide advanced message transformation and translation functionality, on a per-element or per-message granularity if desired.
The default implementation uses the following logic to locate a transformer:
Record a validation error message on an element.
Parameters: |
|
---|---|
Returns: | False |
Either key or message is required. The message will have formatting expanded by expand_message() and be appended to element.errors.
Always returns False. This enables a convenient shorthand when writing validators:
from flatland.validation import Validator
class MyValidator(Validator):
my_message = 'Oh noes!'
def validate(self, element, state):
if not element.value:
return self.note_error(element, state, 'my_message')
else:
return True
Record a validation warning message on an element.
Parameters: |
|
---|---|
Returns: | False |
Either key or message is required. The message will have formatting expanded by expand_message() and be appended to element.warnings.
Always returns False.
Validate an element returning True if valid.
Abstract.
Parameters: |
|
---|---|
Returns: | True if valid |
Validates that an element was converted to a Python value.
Example:
import flatland
from flatland.validation import Converted
not_bogus = Converted(incorrect='Please enter a valid date.')
schema = flatland.DateTime('when', validators=[not_bogus])
Messages
Validates that a value evaluates to false.
Messages
Validates that a value evaluates to true.
Messages
Validates the length of an element’s string value is within bounds.
Example:
import flatland
from flatland.validation import LengthBetween
valid_length = LengthBetween(4, 8)
schema = flatland.String('password', validators=[valid_length])
Attributes
A minimum character length for the u.
This attribute may be supplied as the first positional argument to the constructor.
A maximum character length for the u.
This attribute may be supplied as the second positional argument to the constructor.
Messages
Validates the length of an element’s string value is more than a bound.
Example:
import flatland
from flatland.validation import LongerThan
valid_length = LongerThan(4)
schema = flatland.String('password', validators=[valid_length])
Attributes
A minimum character length for the u.
This attribute may be supplied as the first positional argument to the constructor.
Messages
A general field equality validator.
Validates that two or more fields are equal.
Attributes
Messages
Validates that a value is present.
Messages
Validates the length of an element’s string value is less than a bound.
Example:
import flatland
from flatland.validation import ShorterThan
valid_length = ShorterThan(8)
schema = flatland.String('password', validators=[valid_length])
Attributes
A maximum character length for the u.
This attribute may be supplied as the first positional argument to the constructor.
Messages
Validates that the Unicode values of multiple elements are equal.
A MapEqual that compares the u of each element.
A validator that enforces a minimum value.
Example:
import flatland
from flatland.validation import ValueAtLeast
schema = flatland.Integer('wishes', validators=[ValueAtLeast(minimum=3)])
Attributes
Messages
A validator that enforces a maximum value.
Example:
import flatland
from flatland.validation import ValueAtMost
schema = flatland.Integer('wishes', validators=[ValueAtMost(maximum=3)])
Attributes
Messages
A validator that enforces minimum and maximum values.
Example:
import flatland
from flatland.validation import ValueBetween
schema = flatland.Integer('wishes',
validators=[ValueBetween(minimum=1, maximum=3)])
Attributes
Messages
A validator that ensures that a value is greater than a limit.
Example:
import flatland
from flatland.validation import ValueGreaterThan
schema = flatland.Integer('wishes', validators=[ValueGreaterThan(boundary=4)])
Attributes
Messages
Validates that the value is within a set of possible values.
Example:
import flatland
from flatland.validation import ValueIn
is_yesno = ValueIn(valid_options=['yes', 'no'])
schema = flatland.String('yn', validators=[is_yesno])
Attributes
Messages
A validator that ensures that the value is less than a limit.
Example:
import flatland
from flatland.validation import ValueLessThan
schema = flatland.Integer('wishes', validators=[ValueLessThan(boundary=4)])
Attributes
Messages
Validates that the values of multiple elements are equal.
A MapEqual that compares the value of each element.
Example:
import flatland
from flatland.validation import ValuesEqual
class MyForm(flatland.Form):
password = String
password_again = String
validators = [ValuesEqual('password', 'password_again')]
A sequence validator that ensures a minimum number of members.
May be applied to a sequence type such as a List.
Example:
from flatland import List, String
from flatland.validation import HasAtLeast
schema = List.of(String.named('wish')). using(validators=[HasAtLeast(minimum=3)])
Attributes
Messages
A sequence validator that ensures a maximum number of members.
May be applied to a sequence type such as a List.
Example:
from flatland import List, String
from flatland.validation import HasAtMost
schema = List.of(String.named('wish')). using(validators=[HasAtMost(maximum=3)])
Attributes
Messages
Validates that the number of members of a sequence lies within a range.
May be applied to a sequence type such as a List.
Example:
from flatland import List, String
from flatland.validation import HasBetween
schema = List.of(String.named('wish')). using(validators=[HasBetween(minimum=1, maximum=3)])
Attributes
Messages
A sequence member validator that ensures all sibling values are unique.
Marks the second and any subsequent occurrences of a value as invalid. Only useful on immediate children of sequence fields such as flatland.List.
Example:
import flatland
from flatland.validation import NotDuplicated
validator = NotDuplicated(failure="Please enter each color only once.")
schema = List.of(String.named('favorite_color')). using(validators=[validator])
Attributes
A callable boolean predicate, by default operator.eq. Called positionally with two arguments, element and sibling.
Can be used as a filter, for example ignoring any siblings that have been marked as “deleted” by a checkbox in a web form:
from flatland import Form, List, String, Integer, Boolean
from flatland.validation import NotDuplicated
def live_addrs(element, sibling):
thisval, thatval = element.value, sibling.value
# data marked as deleted is never considered a dupe
if thisval['deleted'] or thatval['deleted']:
return False
# compare elements on 'street' & 'city', ignoring 'id'
return (thisval['street'] == thatval['street'] and
thisval['city'] == thatval['city'])
class Address(Form):
validators = [NotDuplicated(comparator=live_addrs)]
id = Integer.using(optional=True)
deleted = Boolean
street = String
city = String
schema = List.of(Address)
Messages
Bases: flatland.validation.base.Validator
Validates email addresses.
The default behavior takes a very permissive stance on allowed characters in the local-part and a relatively strict stance on the domain. Given local-part@domain:
Attributes
Defaults to a basic domain-validating regular expression with no notion of valid top level domains. Override this to require certain TLDs (or alternately and more simply, add another validator to your chain that checks the endings of the string against your list of TLDs.)
The default pattern rejects the valid but obscure quoted IP-address form ([1.2.3.4]).
Messages
Bases: flatland.validation.base.Validator
A general URL validator.
Validates that a URL is well-formed and may optionally restrict the set of valid schemes and other URL components.
Attributes
Restrict URLs to just this sequence of named schemes, or allow all schemes with (‘*’,). Defaults to all schemes. Example:
allowed_schemes = ('http', 'https', 'ssh')
A sequence of 0 or more part names in urlparse‘s vocabulary:
'scheme', 'netloc', 'path', 'params', 'query', 'fragment'
Defaults to all parts allowed.
Messages
Methods
Construct a validator.
Parameter: | **kw – override any extant class attribute on this instance. |
---|
Bases: flatland.validation.base.Validator
Validates http and https URLs.
Validates that an http-like URL is well-formed and may optionally require and restrict the permissible values of its components.
Attributes
A mapping of part names. If value is True, the part is required. The value may also be a sequence of strings; the value of the part must be present in this collection to validate.
The default requires a scheme of ‘http’ or ‘https’.
A mapping of part names. If value is True, the part is forbidden and validation fails. The value may also be a sequence of strings; the value of the part must not be present in this collection to validate.
The default forbids username and password parts.
Messages
Methods
Construct a validator.
Parameter: | **kw – override any extant class attribute on this instance. |
---|
Bases: flatland.validation.base.Validator
A URL canonicalizing validator.
Given a valid URL, re-writes it with unwanted parts removed. The default implementation drops the #fragment from the URL, if present.
Attributes
A sequence of 0 or more part names in urlparse‘s vocabulary:
'scheme', 'netloc', 'path', 'params', 'query', 'fragment'
Messages
Methods
Construct a validator.
Parameter: | **kw – override any extant class attribute on this instance. |
---|