:mod:`formalchemy.validators` -- Validation stuff ================================================= .. Commented imports >>> from formalchemy.tests import * .. automodule:: formalchemy.validators To validate data, you must bind it to your :class:`~formalchemy.forms.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 :class:`~formalchemy.forms.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: .. sourcecode:: html
Please enter a value
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:: 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. .. >>> field = fs.name .. autofunction:: integer >>> integer('1', field) 1 >>> integer('1.2', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Value is not an integer .. autofunction:: float_ >>> float_('1', field) 1.0 >>> float_('1.2', field) 1.2 >>> float_('asdf', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Value is not a number .. autofunction:: currency >>> currency('asdf', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Value is not a number >>> currency('1', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Please specify full currency value, including cents (e.g., 12.34) >>> currency('1.0', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Please specify full currency value, including cents (e.g., 12.34) >>> currency('1.00', field) .. autofunction:: required >>> required('asdf', field) >>> required('', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Please enter a value .. autofunction:: email >>> email('a+formalchemy@gmail.com', field) >>> email('a+."<>"@gmail.com', field) >>> email('a+."<>"."[]"@gmail.com', field) >>> email('a+."<>@gmail.com', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Unterminated quoted section in recipient >>> email('a+."<>""[]"@gmail.com', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Quoted section must be followed by '@' or '.' >>> email('<>@gmail.com', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Reserved character present in recipient >>> email(chr(0) + '@gmail.com', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Control characters present >>> email(chr(129) + '@gmail.com', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Non-ASCII characters present >>> email('', field) >>> email('asdf', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Missing @ sign >>> email('@', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Recipient must be non-empty >>> email('a@', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Domain must be non-empty >>> email('a@gmail.com.', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Domain must not end with '.' >>> email('a@gmail..com', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Domain must not contain '..' >>> email('a@gmail>com', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Reserved character present in domain Function generators ******************* .. autofunction:: length >>> length(min=2, max=5)('a', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Value must be at least 2 characters long >>> length(min=2, max=5)('abcdef', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Value must be no more than 5 characters long >>> length(min=2, max=5)('abcde', field) .. autofunction:: minlength >>> minlength(0)('a', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValueError: Invalid minimum length >>> minlength(2)('a', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Value must be at least 2 characters long >>> minlength(2)('ab', field) .. autofunction:: maxlength >>> maxlength(0)('a', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValueError: Invalid maximum length >>> maxlength(1)('a', field) >>> maxlength(1)('ab', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ValidationError: Value must be no more than 1 characters long .. autofunction:: regex >>> regex('[A-Z]+$')('ASDF', field) >>> regex('[A-Z]+$')('abc', field) # doctest: +ELLIPSIS,+IGNORE_EXCEPTION_DETAIL 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 # doctest: +IGNORE_UNICODE {Field(number): [literal('Value must be less than 0')]} >>> fs.number.errors # doctest: +IGNORE_UNICODE [literal('Value must be less than 0')]