Commits

Andriy Kornatskyy committed d371115

Documentation: updated list of validation rules; added model update for list attributes

Comments (0)

Files changed (2)

doc/userguide.rst

 User Guide
 ==========
 
-Users fill the web form with necessary information, however, they often make 
-mistakes. This is where form validation comes into play. The goal of 
-validation is to ensure that the user provided necessary and properly 
+Users fill the web form with necessary information, however, they often make
+mistakes. This is where form validation comes into play. The goal of
+validation is to ensure that the user provided necessary and properly
 formatted information needed to successfully complete an operation.
 
 Validator
 ---------
 
 :py:class:`~wheezy.validation.validator.Validator` is a container of validation
-rules that all together provide object validation. You instantiate 
+rules that all together provide object validation. You instantiate
 :py:class:`~wheezy.validation.validator.Validator` and supply it a map
 between attribute names being validated and list of rules. Here is an example::
 
 
 :py:class:`~wheezy.validation.validator.Validator` in no way tied to object
 and/or class being validated, instead the only requirement is existance of
-attributes being validated. 
+attributes being validated.
 
-:py:class:`~wheezy.validation.validator.Validator` supports 
+:py:class:`~wheezy.validation.validator.Validator` supports
 ``__getitem__`` interface, so is applicable to ``dict`` like objects::
 
     user = {'username': '', 'password': ''}
     errors = {}
     succeed = credential_validator.validate(user, errors)
-    
+
 Method ``validate`` returns ``True`` only in case all validation rules succeed
 otherwise ``False``.
 
 ``errors`` dictionary contains all errors reported during validation. Key
 corresponds to attribute name being checked, while value is a list of errors.
 
-If you need validation check all rules for failed attribute, you need set 
+If you need validation check all rules for failed attribute, you need set
 ``stop`` attribute to ``False`` (default is to stop on first error)::
 
     succeed = credential_validator.validate(user, errors, stop=False)
 
+Nested Validator
+~~~~~~~~~~~~~~~~
+
+:py:class:`~wheezy.validation.validator.Validator` can be nested into the
+other validator so ultimately can form any hierarchically complex structure.
+This can be useful for composite objects, e.g. ``Registration`` model can
+aggregate ``Credential`` model. While each model has own validation,
+registration model can nest validator for credential model::
+
+    class Registration(object):
+        def __init__(self):
+            self.credential = Credential()
+
+    registration_validator = Validator({
+        'credential': credential_validator
+    })
+
 Internationalization
 ~~~~~~~~~~~~~~~~~~~~
 
 to ``validate`` method. Here is an example::
 
     from gettext import NullTranslations
-    
+
     translations = NullTranslations()
     succeed = credential_validator.validate(
-            user, 
-            errors, 
+            user,
+            errors,
             translations=translations)
 
 Thread Safety
 ~~~~~~~~~~~~~
 
-Validator does not alter it state once initialized. It is guaranteed to be 
+Validator does not alter it state once initialized. It is guaranteed to be
 thread safe.
 
 Validation Rules
 ----------------
 
-Validation rules prevent bad data being processed. A validation rule is a 
-criterion used in the process of data validation.
+Validation rules prevent bad data being processed. A validation rule is a
+criterion used in the process of data validation. Rules support simple types
+as well as list types of attributes, e.g. ``iterator`` rule can apply
+a number of other rules to each item in the list.
 
 There are a number of validation rules defined already.
 
   defined by this rule. Supported range attributes include: ``min``, ``max``.
   See :py:class:`~wheezy.validation.rules.LengthRule`.
 * ``compare``. Compares attribute being validated with some other attribute
-  value. Supported comparison operations include: ``equal``. See 
+  value. Supported comparison operations include: ``equal``. See
   :py:class:`~wheezy.validation.rules.CompareRule`.
-* ``regex``. Search for regular expression pattern. Initialized with 
-  ``regex`` as a regular expression pattern or a pre-compiled regular 
+* ``regex``. Search for regular expression pattern. Initialized with
+  ``regex`` as a regular expression pattern or a pre-compiled regular
   expression. See :py:class:`~wheezy.validation.rules.RegexRule`.
-* ``slug``. Ensures only letters, numbers, underscores or hyphens. See 
+* ``slug``. Ensures only letters, numbers, underscores or hyphens. See
   :py:class:`~wheezy.validation.rules.SlugRule`.
-* ``email``. Ensures a valid email. See 
+* ``email``. Ensures a valid email. See
   :py:class:`~wheezy.validation.rules.EmailRule`.
-* ``range``. Ensures value is in range defined by this rule. Works with any 
-  numbers including int, float, decimal, etc. Supported range attributes 
-  include: ``min``, ``max``. See 
+* ``range``. Ensures value is in range defined by this rule. Works with any
+  numbers including int, float, decimal, etc. Supported range attributes
+  include: ``min``, ``max``. See
   :py:class:`~wheezy.validation.rules.RangeRule`.
+* ``and_``. Applies all ``rules`` regardless of validation result. See
+  :py:class:`~wheezy.validation.rules.AndRule`.
+* ``or_``. Succeeds if at least one rule in ``rules`` succeed. Failed rule
+  results are not added unless they all fail. See
+  :py:class:`~wheezy.validation.rules.OrRule`.
+* ``iterator``. Applies ``rules`` to each item in value. Iterates over each
+  rule and checks whenever any item in value fails. Designed to work with
+  iteratable attributes: list, tuple, etc. See
+  :py:class:`~wheezy.validation.rules.IteratorRule`.
+* ``one_of``. Value must match at least one element from ``items``. Checks
+  whenever value belongs to ``items``. See
+  :py:class:`~wheezy.validation.rules.OneOfRule`.
+* ``relative_date``, ``relative_datetime``. Check if value is in relative
+  date/datetime range. See
+  :py:class:`~wheezy.validation.rules.RelativeDateDeltaRule` and
+  :py:class:`~wheezy.validation.rules.RelativeDateTimeDeltaRule`.
 
 Custom Message
 ~~~~~~~~~~~~~~
-  
+
 You are able customize error message by using ``message_template`` argument
 during rule declaration::
 
 
     $ xgettext --join-existing --sort-by-file --omit-header \
 		-o i18n/translations.po src/*.py
-		
+
 Compile po files::
 
     $ msgfmt -v translations.po -o translations.mo
 Custom Rules
 ~~~~~~~~~~~~
 
-It is easy to provide own validation rule. The rule is any callable of the 
+It is easy to provide own validation rule. The rule is any callable of the
 following contract::
 
     def check(self, value, name, model, result, gettext):
 
 Validation Mixin
 ----------------
-:py:class:`~wheezy.validation.mixin.ValidationMixin` provides sort of 
+:py:class:`~wheezy.validation.mixin.ValidationMixin` provides sort of
 contextual integration with third party modules. Specifically this mixin
 requires two attributes: ``errors`` and ``translations``. Once these two
 attributes provided, validation can be simplified. Let review it by example::
     user_validator = Validator({
         'name': [required]
     })
-    
+
 We defined ``user_validator``. Now here is our integration in some service::
-    
+
     class MyService(ValidationMixin):
-    
+
          def __init__(self):
              self.errors = {}
              self.translations = {'validation': None}
-             
+
          def signin(self, user):
              succeed = self.validate(user, user_validator)
              ...
              self.error('Unauthorized')
              return False
-    
-If the ``signin`` operation fails the client can request all validation errors 
+
+If the ``signin`` operation fails the client can request all validation errors
 from  ``errors`` attribute. Note that general error message ('Unauthorized')
-is stored under ``__ERROR__`` key. Thus can be used to display general 
+is stored under ``__ERROR__`` key. Thus can be used to display general
 information to end user.
 
 Model Update
 :py:meth:`~wheezy.validation.model.try_update_model` method is provided to
 try update any given object with values submitted by web form.
 
-The convention used by :py:meth:`~wheezy.validation.model.try_update_model` 
-method is requirement for the model to be properly initialized with default 
+The convention used by :py:meth:`~wheezy.validation.model.try_update_model`
+method is requirement for the model to be properly initialized with default
 values, e.g. integer attributes must default to some integer value, etc.
 
 List of supported ``value_providers``:
 
 .. literalinclude:: ../src/wheezy/validation/model.py
-   :lines: 257-
+   :lines: 328-
 
 Example of domain model initialized with defaults::
 
 Values submitted by web form::
 
     values = {'username': [''], 'password': ['']}
-    
+
 Typical use case as follows::
 
     credential = Credential()
 ~~~~~~~
 
 Number value providers (
-:py:meth:`~wheezy.validation.model.int_value_provider`, 
-:py:meth:`~wheezy.validation.model.decimal_value_provider`, 
+:py:meth:`~wheezy.validation.model.int_value_provider`,
+:py:meth:`~wheezy.validation.model.decimal_value_provider`,
 :py:meth:`~wheezy.validation.model.float_value_provider`) support thousands
 separator as well as decimal separator. Take a look at ``validation.po`` file.
 
 :py:meth:`~wheezy.validation.model.time_value_provider`,
 :py:meth:`~wheezy.validation.model.datetime_value_provider`) support a number
 of formats. Generally there is default format and fallback formats. It tries
-default format and if it fails tries fallback formats. Take a look at 
+default format and if it fails tries fallback formats. Take a look at
 ``validation.po`` file for a list of supported format.
 
 Please note that :py:meth:`~wheezy.validation.model.datetime_value_provider`
 none of its own formats matched. Empty value is converted to minimal value
 for date/time.
 
+Lists
+~~~~~
+
+:py:meth:`~wheezy.validation.model.try_update_model` method supports list
+attributes. Note that existing model list is used (it is not overwritten).
+
+    >>> class User(object):
+    ...     def __init__(self):
+    ...         self.prefs = []
+    ...         self.prefs2 = [0]
+    >>> user = User()
+    >>> values = {'prefs': ['1', '2'], 'prefs2': ['1', '2']}
+    >>> results = {}
+    >>> try_update_model(user, values, results)
+    True
+    >>> user.prefs
+    ['1', '2']
+    >>> user.prefs2
+    [1, 2]
+
+Note that the type of the first element in the list selects value_provider
+for all elements in the list.
+
 Custom Value Providers
 ~~~~~~~~~~~~~~~~~~~~~~
 
 Value provider is any callable of the following contract::
 
-    def my_value_provider(str_value, gettext): 
+    def my_value_provider(str_value, gettext):
         return parsed_value
-        
+
 You can add your value provider to defaults::
 
     from wheezy.validation.model import value_providers
-    
+
     value_providers['my_type'] = my_value_provider

src/wheezy/validation/rules.py

 
 
 class AndRule(object):
-    """ Applies all ``rules`` regardles of validation result.
+    """ Applies all ``rules`` regardless of validation result.
 
         >>> result = []
         >>> r = and_(required, range(1, 5))