import dateutil.parser
from bottle_utils.common import basestring
try:
from bottle_utils.i18n import lazy_gettext as _
except ImportError:
_ = lambda x: x
from .exceptions import ValidationError
[docs]class Validator(object):
"""
Base validator class. This calss does not do much on its own. It is used
primarily to build other validators.
The :py:meth:`~validate` method in the subclass performs the validation and
raises a :py:class:`~bottle_utils.form.exceptions.ValidationError`
exception when the data is invalid.
The ``messages`` argument is used to override the error messages for the
validators. This argument should be a dictionary that maps the error names
used by individual validators to the new messages. The error names used by
validators is documented for each class. You can also dynamically obtain
the names by inspecting the ``messages`` property on each of the validator
classes.
"""
#: Mapping between errors and their human-readabile messages
messages = {}
def __init__(self, messages={}):
# Make sure the defaults are copied so updating the messages attribute
# doesn't affect the class' version or any other instances.
self.messages = self.messages.copy()
self.messages.update(messages)
def __call__(self, data):
self.validate(data)
[docs] def validate(self, data):
"""
Perform actual validation over data. Should raise
:py:class:`~bottle_utils.form.exceptions.ValidationError`
if data does not pass the validation.
Two arguments are passed to the validation error, the error name and a
dictionary with extra parameters (usually with ``value`` key that
points to the value).
Error message is constructed based on the arguments, passed to the
exception by looking up the key in the :py:attr:`~messages` property to
obtain the message, and then interpolating any extra parameters into
the message.
This method does not need to return anything.
"""
raise NotImplementedError()
[docs]class Required(Validator):
"""
Validates the presence of data. Technically, this validator fails for any
data that is an empty string, a string that only contains whitespace, or
data that is not a string and evaluates to ``False`` when coerced into
boolean (e.g., 0, empty arrays and dicts, etc).
:error name(s): required
"""
messages = {
'required': _('This field is required'),
}
def validate(self, data):
if not data or isinstance(data, basestring) and not data.strip():
# Translator, represents empty field's value
raise ValidationError('required', {'value': _('(empty)')})
[docs]class DateValidator(Validator):
"""
Validates date fields. This validator attempts to parse the data as date (or
date/time) data. When the data cannot be parsed, it fails.
:error name(s): date
"""
messages = {
'date': _('{value} does not look like a valid date'),
}
def validate(self, value):
try:
dateutil.parser.parse(value)
except (TypeError, ValueError) as exc:
raise ValidationError('date', {'value': value, 'exc': str(exc)})
[docs]class InRangeValidator(Validator):
"""
Validates that value is within a range between two values. This validator
works with any objects that support ``>`` and ``<`` operators.
The ``min_value`` and ``max_value`` arguments are used to set the lower and
upper bounds respectively. The check for those bounds are only done when
the arguments are supplied so, when both arguments are omitted, this
validator is effectively pass-through.
:error name(s): min_val, max_val
"""
messages = {
'min_val': _('{value} is too small'),
'max_val': _('{value} is too large'),
}
def __init__(self, min_value=None, max_value=None, **kwargs):
self.min_value = min_value
self.max_value = max_value
super(InRangeValidator, self).__init__(**kwargs)
def validate(self, value):
if value is None:
return
if self.min_value is not None and self.min_value > value:
raise ValidationError(
'min_val', {'value': value, 'min': self.min_value})
if self.max_value is not None and self.max_value < value:
raise ValidationError(
'max_val', {'value': value, 'max': self.max_value})
[docs]class LengthValidator(Validator):
"""
Validates that value's length is within specified range. This validator
works with any objects that support the ``len()`` function.
The ``min_len`` and ``max_len`` arguments are used to set the lower and
upper bounds respectively. The check for those bounds are only done when
the arguments are supplied so, when both arguments are omitted, this
validator is effectively pass-through.
:error name(s): min_len, max_len
"""
messages = {
'min_len': _('Must be at least {len} long'),
'max_len': _('Must not be longer than {len}'),
}
def __init__(self, min_len=None, max_len=None, **kwargs):
self.min_len = min_len
self.max_len = max_len
super(LengthValidator, self).__init__(**kwargs)
def validate(self, value):
if not value:
return
if self.min_len is not None and len(value) < self.min_len:
raise ValidationError(
'min_len', {'value': value, 'len': self.min_len})
if self.max_len is not None and len(value) > self.max_len:
raise ValidationError(
'max_len', {'value': value, 'len': self.max_len})