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