Source code for bottle_utils.form.fields

from bottle_utils.common import basestring, unicode

try:
    from bottle_utils.i18n import lazy_gettext as _
except ImportError:
    _ = lambda x: x

from .exceptions import ValidationError
from .validators import DateValidator


[docs]class ErrorMixin(object): """ Mixin class used to provide error rendering functionality. """ @property def error(self): """ Human readable error message. This property evaluates to empty string if there are no errors. """ if not self._error: return '' message = self.messages.get(self._error.message) if not message: return self.generic_error if self._error.params: return message.format(**self._error.params) return message
class Ordered(object): #: Counter attribute holding the number of fields found on a form, used #: to assign indexes to individual fields, so their order can be kept _counter = 0 def __init__(self): #: Assign index to the instantiated field and increment the shared #: counter, so the next field will get a higher index self._order = self._counter Ordered._counter += 1
[docs]class DormantField(Ordered): """ Proxy for unbound fields. This class holds the the field constructor arguments until the data can be bound to it. You never need to use this class directly. """ def __init__(self, field_cls, args, kwargs): super(DormantField, self).__init__() # needed to apply ordering self.field_cls = field_cls self.args = args self.kwargs = kwargs def bind(self, name): return self.field_cls(name=name, *self.args, **self.kwargs)
[docs]class Field(Ordered, ErrorMixin): """ Form field base class. This class provides the base functionality for all form fields. The ``label`` argument is used to specify the field's label. The ``validators`` argument is used to specify the validators that will be used on the field data. If any data should be bound to a field, the ``value`` argument can be used to specify it. Value can be a callable, in which case it is called and its return value used as ``value``. The ``name`` argument is used to specify the field name. The ``messages`` argument is used to customize the validation error messages. These override any messages found in the :py:attr:`~messages` attribute. Any extra keyword attributes passed to the constructor are stored as :py:attr:`~options` property on the instance. """ ValidationError = ValidationError #: Field ID attribute prefix (this is prepended to the field name) _id_prefix = '' #: Generic error message to be used when no messages match the validation #: error. # Translators, used as generic error message in form fields, 'value' should # not be translated. generic_error = _('Invalid value for this field') #: Validation error messages. messages = {} #: Field markup type. This is arbitrary and normally used in the templates #: to differentiate between field types. It is up to the template author to #: decide how this should be treated. type = 'text' def __new__(cls, *args, **kwargs): if 'name' in kwargs: return super(Field, cls).__new__(cls) return DormantField(cls, args, kwargs) def __init__(self, label=None, validators=None, value=None, name=None, messages={}, **options): super(Field, self).__init__() # needed to apply ordering #: Field name self.name = name #: Field label self.label = label #: Field validators self.validators = validators or [] #: Raw value of the field self.value = value() if callable(value) else value #: Processed value of the field self.processed_value = None #: Whether value is bound to this field self.is_value_bound = False self._error = None #: Extra keyword argument passed to the constructor self.options = options # Copy the messages so that modifying them does not result in class' # ``messages`` attribute to be altered. self.messages = self.messages.copy() # Collect default messages from all validators into the messages dict for validator in self.validators: self.messages.update(validator.messages) # Update the messages dict with any user-supplied messages self.messages.update(messages)
[docs] def bind_value(self, value): """ Binds a value. This method also sets the :py:attr:`~is_value_bound` property to ``True``. """ self.value = value self.is_value_bound = True
[docs] def is_valid(self): """ Validate form field and return ``True`` is data is valid. If there is an error during Validation, the error object is stored in the :py:attr:`~_error` property. Before validation, the raw value is processed using the :py:meth:`~parse` method, and stored in :py:attr:`~processed_value` attribute. When parsing fails with ``ValueError`` exception, a 'generic' error is stored. The default message for this error is stored in the :py:attr:`~generic_error` property, and can be customized by passing a ``'generic'`` message as part of the ``messages`` constructor argument. """ try: self.processed_value = self.parse(self.processed_value) except ValueError as exc: self._error = ValidationError('generic', {'value': self.processed_value}) return False for validate in self.validators: try: validate(self.processed_value) except ValidationError as exc: self._error = exc return False return True
[docs] def parse(self, value): """ Parse the raw value and convert to Python object. Subclasses should return the value in it's correct type. In case the passed in value cannot be cast into it's correct type, the method should raise a ``ValueError`` exception with an appropriate error message. """ raise NotImplementedError()
[docs]class StringField(Field): """ Field for working with string values. :Python type: str (unicode in Python 2.x) :type: text """ def parse(self, value): if value is None: return '' return unicode(value)
[docs]class PasswordField(StringField): """ Field for working with passwords. :Python type: str (unicode in Python 2.x) :type: password """ type = 'password'
[docs]class HiddenField(StringField): """ Field for working with hidden inputs. :Python type: str (unicode in Python 2.x) :type: hidden """ type = 'hidden'
[docs]class EmailField(StringField): """ Field for working with emails. :Python type: str (unicode in Python 2.x) :type: text """ pass
[docs]class TextAreaField(StringField): """ Field for working with textareas. :Python type: str (unicode in Python 2.x) :type: textarea """ type = 'textarea'
[docs]class DateField(StringField): """ Field for working with dates. This field overloads the base class' constructor to add a :py:class:`~bottle_utils.validators.DateValidator`. :Python type: str (unicode in Python 2.x) :type: text """ def __init__(self, label=None, validators=None, value=None, **options): validators = [DateValidator()] + list(validators or []) super(DateField, self).__init__(label, validators=validators, value=value, **options)
[docs]class FileField(Field): """ Field for working with file uploads. :Python type: raw value :type: file """ type = 'file' def parse(self, value): return value
[docs]class IntegerField(Field): """ Field for working with integers. :Python type: int :type: text """ def parse(self, value): if value is None: return value try: return int(value) except Exception: raise ValueError(_("Invalid value for an integer."))
[docs]class FloatField(Field): """ Field for working with floating-point numbers. :Python type: float :type: text """ def parse(self, value): if value is None: return value try: return float(value) except Exception: raise ValueError(_("Invalid value for a float."))
[docs]class BooleanField(Field): """ Field for working with boolean values. Two additional constructor arguments are added. The ``default`` argument is used to specify the default state of the field, which can be used in the template to, for instance, check or uncheck a checkbox or radio button. The ``expected_value`` is the base value of the field against which the bound value is checked: if they match, the Python value of the field is ``True``. :Python type: bool :type: checkbox """ type = 'checkbox' def __init__(self, label=None, validators=None, value=None, default=False, **options): #: Default state of the field self.default = default #: Base value of the field against which bound value is checked self.expected_value = value super(BooleanField, self).__init__(label, validators=validators, value=value, **options) @property def checked(self): if not self.is_value_bound: return self.default # when value is bound to the field, it must match the expected value return self.parse(self.value) def parse(self, value): if not value or isinstance(value, basestring): return self.expected_value == value # Bool values may be passed to it as initial values if isinstance(value, bool): return value # Check if value is present in collection return self.expected_value in value
[docs]class SelectField(Field): """ Field for dealing with select lists. The ``choices`` argument is used to specify the list of value-label pairs that are used to present the valid choices in the interface. The field value is then checked against the list of value and the parser validates whether the supplied value is among the valid ones. :Python type: str (unicode in Python 2.x) :type: select """ type = 'select' def __init__(self, label=None, validators=None, value=None, choices=None, **options): #: Iterable of value-label pairs of valid choices self.choices = choices or tuple() super(SelectField, self).__init__(label, validators=validators, value=value, **options) def parse(self, value): chosen = unicode(value) for (candidate, label) in self.choices: if unicode(candidate) == chosen: return chosen if value is not None else value raise ValueError(_("This is not a valid choice."))