Re: FormEncode form validators

Felix Schwarz <felix.schwarz-S0/[email protected]>
Newsgroups gmane.comp.python.formencode
Message-ID <[email protected]>
Ian Bicking write:
> Felix Schwarz wrote:
>> Ian Bicking wrote:
>>> Do people have classes or recipes they've written that are general 
>>> enough to include directly in FormEncode?  If so, please contribute! 
>>> Realistic examples in the docstrings will be very helpful.
>>
>> I have some validators which might be useful:
>> * RestrictedInteger (min, max, may be marked as optional)
> 
> The optional part can just be given with if_missing, no?  (Or 
> if_empty/if_invalid)

You are right.

> validators.Int already has min/max.

If it has, it doesn't work. See the attached patch.

>> * PartialDate - FormValidator which takes three fields as day/month/year
>>   and ensures that these fields form are valid date while some date
>>   parts may be missing (e.g. year is missing so only day+month can be
>>   validated).
> 
> That could be useful, as long the options don't get overwhelming. Though 
> I've generally stayed away from compound date validators, because they 
> seem generally unnecessary to me if you have good string field parsing.
> 
> Also, for dates if you use variabledecode you can name your fields 
> things like date.year, date.month, date.day, and do:
> 
> class MySchema(Schema):
>     date = DateValidator()
> 
> And the validator should get a dictionary with year/month/day keys.

See the attached implementation.

> 
>> * a custom unicode string validator which works as I expected the 
>> FormEncode one
>>   initially.
> 
> How is it different from the current UnicodeString?  Can the 
> functionality be merged in there directly?

See the attached patch, basically it works ok now but empty does not return a 
unicode string.

fs

-------------------------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc.
Still grepping through log files to find problems?  Stop.
Now Search log events and configuration files using AJAX and a browser.
Download your FREE copy of Splunk now >> http://get.splunk.com/

_______________________________________________
FormEncode-discuss mailing list
FormEncode-discuss-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org
https://lists.sourceforge.net/lists/listinfo/formencode-discuss
formencode_int.patch (text/x-patch, 3.4 KB)
Index: formencode/validators.py
===================================================================
--- formencode/validators.py	(Revision 3092)
+++ formencode/validators.py	(Arbeitskopie)
@@ -915,8 +915,20 @@
 
     messages = {
         'integer': _("Please enter an integer value"),
+        'tooLow': _("Please enter an integer above %(min)i"),
+        'tooHigh': _("Please enter an integer below %(max)i"),
         }
 
+    min = None
+    max = None
+    
+    def __initargs__(self, args):
+        if self.min != None:
+            self.min = int(self.min)
+        if self.max != None:
+            self.max = int(self.max)
+
+
     def _to_python(self, value, state):
         try:
             return int(value)
@@ -924,6 +936,14 @@
             raise Invalid(self.message('integer', state),
                           value, state)
 
+    def validate_python(self, value, state):
+        if self.min != None and value < self.min:
+            msg = self.message("tooLow", state, min=self.min)
+            raise Invalid(msg, value, state)
+        if self.max != None and value > self.max:
+            msg = self.message("tooHigh", state, max=self.max)
+            raise Invalid(msg, value, state)
+
     _from_python = _to_python
 
 class Number(FancyValidator):
@@ -1115,6 +1135,9 @@
             value = value.encode(self.outputEncoding)
         return value
 
+    def empty_value(self, value):
+        return u''
+
 class Set(FancyValidator):
 
     """
Index: tests/test_validators.py
===================================================================
--- tests/test_validators.py	(Revision 3092)
+++ tests/test_validators.py	(Arbeitskopie)
@@ -1,8 +1,8 @@
-from formencode.validators import String, UnicodeString, Invalid
+from formencode.validators import String, UnicodeString, Invalid, Int
 
 def validate(validator, value):
     try:
-        validator.to_python(value)
+        return validator.to_python(value)
         return None
     except Invalid, e:
         return e.unpack_errors()
@@ -38,8 +38,47 @@
     assert sv.from_python(2) == "2"
     assert sv.from_python([]) == ""
 
+
 def test_unicode():
     un = UnicodeString()
     assert un.to_python(12) == u'12'
     assert type(un.to_python(12)) is unicode
-    
+
+def test_unicode_empty():
+    iv = UnicodeString()
+    for input in [None, "", u""]:
+        result = iv.to_python(input)
+        assert u"" == result, result
+        assert isinstance(result, unicode)
+
+
+def test_int_min():
+    messages = Int().message
+    iv = Int(min=5)
+    assert iv.to_python("5") == 5
+    assert validate(iv, "1") == messages('tooLow', None, min=5)
+
+def test_int_max():
+    messages = Int().message
+    iv = Int(max=10)
+    assert iv.to_python("10") == 10
+    assert validate(iv, "15") == messages('tooHigh', None, max=10)
+
+def test_int_minmax_optional():
+    messages = Int().message
+    iv = Int(min=5, max=10, if_empty=None)
+    assert iv.to_python("") == None
+    assert iv.to_python(None) == None
+    assert iv.to_python('7') == 7
+    assert validate(iv, "1") == messages('tooLow', None, min=5)
+    assert validate(iv, "15") == messages('tooHigh', None, max=10)
+
+def test_int_minmax_optional():
+    messages = Int().message
+    iv = Int(min=5, max=10, not_empty=True)
+    assert validate(iv, None) == messages('empty', None)
+    assert validate(iv, "1") == messages('tooLow', None, min=5)
+    assert validate(iv, "15") == messages('tooHigh', None, max=10)
+
+
+
partial_date.py (text/x-python, 3.9 KB)
# -*- coding: UTF-8 -*-
"Validator which only accepts ints in the specified range."

# Copyright (c) 2007, Felix Schwarz <[email protected]>
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without 
# modification, are permitted provided that the following conditions are met:
# 
# * Redistributions of source code must retain the above copyright notice, 
#   this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice, 
#   this list of conditions and the following disclaimer in the documentation 
#   and/or other materials provided with the distribution.
# * Neither the name of the Felix Schwarz nor the names of its contributors 
#   may be used to endorse or promote products derived from this software 
#   without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


from datetime import date

from formencode import Invalid
from formencode.validators import FormValidator

class PartialDate(FormValidator):
    """A FormValidator that checks if the values in the given fields are a 
    valid date. This validator can deal with incomplete dates where some parts
    (e.g. year) are missing and tries to validate the available data. If all
    fields are missing/None the validator just passes unconditionally.
    """
    field_names = None
    __unpackargs__ = ('*', 'field_names')
    
    messages = {
        'bad_day': "'%(day)i' is not a valid day.",
        'bad_month': "'%(month)i' is not a valid month.",
        'bad_year': "'%(year)i' is not a valid year.",
        }
    
    
    def validate_python(self, field_dict, state):
        def get(tupel, index, **kwargs):
            if len(tupel) > index:
                return tupel[index]
            elif 'default' in kwargs:
                return kwargs['default']
            raise IndexError('IndexError: tuple index out of range')
        
        day = field_dict.get(self.field_names[0], None)
        month = field_dict.get(self.field_names[1], None)
        year = field_dict.get(self.field_names[2], None)
        
        if year == None:
            # leap year, so that February has 29 days
            year = 2000
        if month == None:
            # no month has more days than January
            month = 1
        if day == None:
            day = 1
        
        try:
            date(year=year, month=month, day=day)
        except ValueError, e:
            errors = {}
            if str(e) == "day is out of range for month":
                msg = self.message("bad_day", state, day=day)
                errors[self.field_names[0]] = msg
            elif str(e) == "month must be in 1..12":
                msg = self.message("bad_month", state, month=month)
                errors[self.field_names[1]] = msg
            elif str(e) == "year is out of range":
                msg = self.message("bad_year", state, year=year)
                errors[self.field_names[2]] = msg
            else:
                msg = "No exception for %s: %s" % (str(e.__class__), str(e))
                raise NotImplementedError(msg)
            raise Invalid(msg, field_dict, state, error_dict=errors)
test_partial_date.py (text/x-python, 4.5 KB)
# -*- coding: UTF-8 -*-
"Test cases for partial data validator"


import unittest

from formencode import Schema, validators, Invalid

from partial_date import PartialDate

# ------------------------------------------------------------------
# copied from PEP 309: http://www.python.org/dev/peps/pep-0309/
# license: public domain
class partial(object):
    def __init__(*args, **kw):
        self = args[0]
        self.fn, self.args, self.kw = (args[1], args[2:], kw)

    def __call__(self, *args, **kw):
        if kw and self.kw:
            d = self.kw.copy()
            d.update(kw)
        else:
            d = kw or self.kw
        return self.fn(*(self.args + args), **d)
# ------------------------------------------------------------------

def _assert_invalid(value, msg=None, validator=None, error_dict_keys=None):
    try:
        validator.to_python(value, None)
        assert False, "Invalid values passed the valuator: " + str(value)
    except Invalid, exception:
        if msg != None:
            message = "%s != %s" % (msg, exception.msg)
            if msg != exception.msg:
                print message
            assert (msg == exception.msg), message
        if not error_dict_keys in [None, {}]:
            assert exception.error_dict != None
            for key in error_dict_keys:
                assert exception.error_dict[key] != None


class TestPartialDate(unittest.TestCase):
    "Test cases for the PartialDate form validator validator."

    def setUp(self):
        self.init_validator()
    
    def init_validator(self, **kwargs):
        class PartialDateSchema(Schema):
            day = validators.Int()
            month = validators.Int()
            year = validators.Int()
            chained_validators = [PartialDate('day', 'month', 'year')]
        self.schema = PartialDateSchema()
        self.messages = partial(PartialDate().message, state=None)
        self.assert_invalid = partial(_assert_invalid, validator=self.schema)
    
    
    def test_completely_valid_date(self):
        "Test that complete and valid dates are accepted."
        self.schema.to_python({"day": "12", "month": "12", "year": "2001"})
        self.schema.to_python({"day": "29", "month": "02", "year": "2000"})
    
    
    def test_invalid_complete_dates(self):
        "Test that complete but invalid dates are rejected."
        self.assert_invalid({"day": "-1", "month": "12", "year": "2001"},
                            self.messages("bad_day", day=-1),
                            error_dict_keys=['day'])
        self.assert_invalid({"day": "12", "month": "13", "year": "2001"},
                            self.messages("bad_month", month=13),
                            error_dict_keys=['month'])
        self.assert_invalid({"day": "29", "month": "2", "year": "2001"},
                            self.messages("bad_day", day=29))
        self.assert_invalid({"day": "1", "month": "1", "year": "-1"},
                            self.messages("bad_year", year=-1),
                            error_dict_keys=['year'])
    
    
    def test_invalid_types(self):
        "Test that invalid types for some fields are rejected."
        self.assert_invalid({"day": "as", "month": "12", "year": "2001"})
        self.assert_invalid({"day": "12", "month": "as", "year": "2001"})
        self.assert_invalid({"day": "12", "month": "12", "year": "as"})
    
    
    def test_missing_years(self):
        "Test that dates with missing years are validated correctly."
        self.schema.to_python({"day": "12", "month": "12", "year": ""})
        self.schema.to_python({"day": "29", "month": "2", "year": ""})
        self.schema.to_python({"day": "31", "month": "10", "year": ""})
        self.assert_invalid({"day": "31", "month": "11", "year": ""},
                            self.messages("bad_day", day=31))
        self.assert_invalid({"day": "30", "month": "2", "year": ""},
                            self.messages("bad_day", day=30))
    
    
    def test_missing_months(self):
        "Test that dates with missing months are validated correctly."
        self.schema.to_python({"day": "31", "month": "", "year": "2005"})
        self.schema.to_python({"day": "29", "month": "", "year": ""})
    
    
    def test_missing_days(self):
        "Test that dates with missing months are validated correctly."
        self.schema.to_python({"day": "", "month": "2", "year": "2005"})
    
    
    def test_nothing(self):
        "Test that dates without any data (everyhing None) is good too."
        self.schema.to_python({"day": "", "month": "", "year": ""})
smime.p7s (application/x-pkcs7-signature, 3.2 KB) - not displayed
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.