PK1X=5 formencode/fieldstorage.py## FormEncode, a Form processor ## Copyright (C) 2003, Ian Bicking ## ## This library is free software; you can redistribute it and/or ## modify it under the terms of the GNU Lesser General Public ## License as published by the Free Software Foundation; either ## version 2.1 of the License, or (at your option) any later version. ## ## This library is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## Lesser General Public License for more details. ## ## You should have received a copy of the GNU Lesser General Public ## License along with this library; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## ## NOTE: In the context of the Python environment, I interpret "dynamic ## linking" as importing -- thus the LGPL applies to the contents of ## the modules, but make no requirements on code importing these ## modules. """ Wrapper class for use with cgi.FieldStorage types for file uploads """ import cgi def convert_fieldstorage(fs): if fs.filename: return fs else: return None PK1X=5@D% % formencode/htmlform.py""" Class to encapsulate an HTML form, using htmlfill and htmlfill_schemabuilder (deprecated). .. note:: This is deprecated, as it's not that helpful. Usage:: html = '
...
' class FormSchema(schema.Schema): f1 = ... form = HTMLForm(html, FormSchema()) errors = {} if form_submitted: form_result, errors = form.validate(request_dict) if not errors: do_action(form_result) return defaults = form.schema.from_python(get_defaults_from_model()) defaults.update(request_dict) write(form.render(defaults, errors) You can also embed the schema in the form, using form:required, etc., tags. """ import htmlfill import htmlfill_schemabuilder from api import Invalid import warnings class HTMLForm(object): def __init__(self, form, schema=None, auto_insert_errors=True): warnings.warn( 'HTMLForm has been deprecated; use the htmlfill and ' 'htmlfill_schemabuilder modules directly.', DeprecationWarning, stacklevel=1) self.form = form self._schema = schema self.auto_insert_errors = auto_insert_errors def schema__get(self): if self._schema is not None: return self._schema self._schema = self.parse_schema() def schema__set(self, value): self._schema = value def schema__del(self): self._schema = None schema = property(schema__get, schema__set, schema__del) def parse_schema(self): listener = htmlfill_schemabuilder.SchemaBuilder() p = htmlfill.FillingParser( defaults={}, listener=listener) p.feed(self.form) p.close() return listener.schema() def render(self, defaults={}, errors={}, use_all_keys=False, text_as_default=False): if self.auto_insert_errors: auto_error_formatter = htmlfill.default_formatter else: auto_error_formatter = None p = htmlfill.FillingParser( defaults=defaults, errors=errors, use_all_keys=use_all_keys, auto_error_formatter=auto_error_formatter, text_as_default=text_as_default) p.feed(self.form) p.close() return p.text() def validate(self, request_dict, state=None): schema = self.schema try: result = schema.to_python(request_dict, state=state) return result, None except Invalid, e: return None, e.unpack_errors() PK1X=5. !formencode/doctest_xml_compare.pytry: import doctest doctest.OutputChecker except AttributeError: import util.doctest24 as doctest import elementtree.ElementTree as et from xml.parsers.expat import ExpatError as XMLParseError RealOutputChecker = doctest.OutputChecker def debug(*msg): import sys print >> sys.stderr, ' '.join(map(str, msg)) class HTMLOutputChecker(RealOutputChecker): def check_output(self, want, got, optionflags): normal = RealOutputChecker.check_output(self, want, got, optionflags) if normal or not got: return normal try: want_xml = make_xml(want) except XMLParseError, e: pass else: try: got_xml = make_xml(got) except XMLParseError: pass else: if xml_compare(want_xml, got_xml): return True return False def output_difference(self, example, got, optionflags): actual = RealOutputChecker.output_difference( self, example, got, optionflags) want_xml = got_xml = None try: want_xml = make_xml(example.want) want_norm = make_string(want_xml) except XMLParseError, e: if example.want.startswith('<'): want_norm = '(bad XML: %s)' % e # '%s' % example.want else: return actual try: got_xml = make_xml(got) got_norm = make_string(got_xml) except XMLParseError, e: if example.want.startswith('<'): got_norm = '(bad XML: %s)' % e else: return actual s = '%s\nXML Wanted: %s\nXML Got : %s\n' % ( actual, want_norm, got_norm) if got_xml and want_xml: result = [] xml_compare(want_xml, got_xml, result.append) s += 'Difference report:\n%s\n' % '\n'.join(result) return s def xml_compare(x1, x2, reporter=None): if x1.tag != x2.tag: if reporter: reporter('Tags do not match: %s and %s' % (x1.tag, x2.tag)) return False for name, value in x1.attrib.items(): if x2.attrib.get(name) != value: if reporter: reporter('Attributes do not match: %s=%r, %s=%r' % (name, value, name, x2.attrib.get(name))) return False for name in x2.attrib.keys(): if not x1.attrib.has_key(name): if reporter: reporter('x2 has an attribute x1 is missing: %s' % name) return False if not text_compare(x1.text, x2.text): if reporter: reporter('text: %r != %r' % (x1.text, x2.text)) return False if not text_compare(x1.tail, x2.tail): if reporter: reporter('tail: %r != %r' % (x1.tail, x2.tail)) return False cl1 = x1.getchildren() cl2 = x2.getchildren() if len(cl1) != len(cl2): if reporter: reporter('children length differs, %i != %i' % (len(cl1), len(cl2))) return False i = 0 for c1, c2 in zip(cl1, cl2): i += 1 if not xml_compare(c1, c2, reporter=reporter): if reporter: reporter('children %i do not match: %s' % (i, c1.tag)) return False return True def text_compare(t1, t2): if not t1 and not t2: return True if t1 == '*' or t2 == '*': return True return (t1 or '').strip() == (t2 or '').strip() def make_xml(s): return et.XML('%s' % s) def make_string(xml): if isinstance(xml, (str, unicode)): xml = make_xml(xml) s = et.tostring(xml) if s == '': return '' assert s.startswith('') and s.endswith(''), repr(s) return s[5:-6] def install(): doctest.OutputChecker = HTMLOutputChecker PK1X=5@?formencode/variabledecode.py""" Takes GET/POST variable dictionary, as might be returned by ``cgi``, and turns them into lists and dictionaries. Keys (variable names) can have subkeys, with a ``.`` and can be numbered with ``-``, like ``a.b-3=something`` means that the value ``a`` is a dictionary with a key ``b``, and ``b`` is a list, the third(-ish) element with the value ``something``. Numbers are used to sort, missing numbers are ignored. This doesn't deal with multiple keys, like in a query string of ``id=10&id=20``, which returns something like ``{'id': ['10', '20']}``. That's left to someplace else to interpret. If you want to represent lists in this model, you use indexes, and the lists are explicitly ordered. If you want to change the character that determines when to split for a dict or list, both variable_decode and variable_encode take dict_char and list_char keyword args. For example, to have the GET/POST variables, ``a_1=something`` as a list, you would use a list_char='_'. """ import api __all__ = ['variable_decode', 'variable_encode', 'NestedVariables'] def variable_decode(d, dict_char='.', list_char='-'): """ Decodes the flat dictionary d into a nested structure. """ result = {} dicts_to_sort = {} known_lengths = {} for key, value in d.items(): keys = key.split(dict_char) new_keys = [] was_repetition_count = False for key in keys: if key.endswith('--repetitions'): key = key[:-len('--repetitions')] new_keys.append(key) known_lengths[tuple(new_keys)] = int(value) was_repetition_count = True break elif list_char in key: key, index = key.split(list_char) new_keys.append(key) dicts_to_sort[tuple(new_keys)] = 1 new_keys.append(int(index)) else: new_keys.append(key) if was_repetition_count: continue place = result for i in range(len(new_keys)-1): try: if not isinstance(place[new_keys[i]], dict): place[new_keys[i]] = {None: place[new_keys[i]]} place = place[new_keys[i]] except KeyError: place[new_keys[i]] = {} place = place[new_keys[i]] if place.has_key(new_keys[-1]): if isinstance(place[new_keys[-1]], dict): place[new_keys[-1]][None] = value elif isinstance(place[new_keys[-1]], list): if isinstance(value, list): place[new_keys[-1]].extend(value) else: place[new_keys[-1]].append(value) else: if isinstance(value, list): place[new_keys[-1]] = [place[new_keys[-1]]] place[new_keys[-1]].extend(value) else: place[new_keys[-1]] = [place[new_keys[-1]], value] else: place[new_keys[-1]] = value to_sort_keys = dicts_to_sort.keys() to_sort_keys.sort(lambda a, b: -cmp(len(a), len(b))) for key in to_sort_keys: to_sort = result source = None last_key = None for sub_key in key: source = to_sort last_key = sub_key to_sort = to_sort[sub_key] if to_sort.has_key(None): noneVals = [(0, x) for x in to_sort[None]] del to_sort[None] noneVals.extend(to_sort.items()) to_sort = noneVals else: to_sort = to_sort.items() to_sort.sort() to_sort = [v for k, v in to_sort] if known_lengths.has_key(key): if len(to_sort) < known_lengths[key]: to_sort.extend(['']*(known_lengths[key] - len(to_sort))) source[last_key] = to_sort return result def variable_encode(d, prepend='', result=None, add_repetitions=True, dict_char='.', list_char='-'): """ Encodes a nested structure into a flat dictionary. """ if result is None: result = {} if isinstance(d, dict): for key, value in d.items(): if key is None: name = prepend elif not prepend: name = key else: name = "%s%s%s" % (prepend, dict_char, key) variable_encode(value, name, result, add_repetitions, dict_char=dict_char, list_char=list_char) elif isinstance(d, list): for i in range(len(d)): variable_encode(d[i], "%s%s%i" % (prepend, list_char, i), result, add_repetitions, dict_char=dict_char, list_char=list_char) if add_repetitions: if prepend: repName = '%s--repetitions' % prepend else: repName = '__repetitions__' result[repName] = str(len(d)) else: result[prepend] = d return result class NestedVariables(api.FancyValidator): def _to_python(self, value, state): return variable_decode(value) def _from_python(self, value, state): return variable_encode(value) PK1X=59d `formencode/formgen.py""" Experimental extensible form generation """ # @@: This is experimental import fields import pkg_resources pkg_resources.require('RuleDispatch') import dispatch @dispatch.generic() def makeform(obj, context): """ Return ``(field_obj, Schema)``. Return a field or field container used to edit ``obj`` given the context. Also return a Schema object (or None for no Schema) that will be applied before other validation. """ raise NotImplementedError PK1X=5formencode/htmlgen.py""" Kind of like htmlgen, only much simpler. The only important symbol that is exported is ``html``. This builds ElementTree nodes, but with some extra useful methods. (Open issue: should it use ``ElementTree`` more, and the raw ``Element`` stuff less?) You create tags with attribute access. I.e., the ``A`` anchor tag is ``html.a``. The attributes of the HTML tag are done with keyword arguments. The contents of the tag are the non-keyword arguments (concatenated). You can also use the special ``c`` keyword, passing a list, tuple, or single tag, and it will make up the contents (this is useful because keywords have to come after all non-keyword arguments, which is non-intuitive). Or you can chain them, adding the keywords with one call, then the body with a second call, like:: >>> print html.a(href='http://yahoo.com')('') <Yahoo> Note that strings will be quoted; only tags given explicitly will remain unquoted. If the value of an attribute is None, then no attribute will be inserted. So:: >>> print html.a(href='http://www.yahoo.com', name=None, ... c='Click Here') Click Here If the value is None, then the empty string is used. Otherwise str() is called on the value. ``html`` can also be called, and it will produce a special list from its arguments, which adds a ``__str__`` method that does ``html.str`` (which handles quoting, flattening these lists recursively, and using '' for ``None``). ``html.comment`` will generate an HTML comment, like ``html.comment('comment text')`` -- note that it cannot take keyword arguments (because they wouldn't mean anything). Examples:: >>> print html.html( ... html.head(html.title(\"Page Title\")), ... html.body( ... bgcolor='#000066', ... text='#ffffff', ... c=[html.h1('Page Title'), ... html.p('Hello world!')], ... )) Page Title

Page Title

Hello world!

>>> print html.a(href='#top')('return to top') return to top """ from __future__ import generators from cgi import escape import elementtree.ElementTree as et default_encoding = 'utf-8' class _HTML: def __getattr__(self, attr): if attr.startswith('_'): raise AttributeError attr = attr.lower() if attr.endswith('_'): attr = attr[:-1] if attr.find('__') != -1: attr = attr.replace('__', ':') if attr == 'comment': return Element(et.Comment, {}) else: return Element(attr, {}) def __call__(self, *args): return ElementList(args) def quote(self, arg): if arg is None: return '' return escape(unicode(arg).encode(default_encoding), 1) def str(self, arg, encoding=None): if isinstance(arg, str): return arg elif arg is None: return '' elif isinstance(arg, unicode): return arg.encode(default_encoding) elif isinstance(arg, (list, tuple)): return ''.join(map(self.str, arg)) elif isinstance(arg, Element): return str(arg) else: return unicode(arg).encode(default_encoding) html = _HTML() class Element(et._ElementInterface): def __call__(self, *args, **kw): el = self.__class__(self.tag, self.attrib) if kw.has_key('c'): if args: raise ValueError( "You may either provide positional arguments or a " "'c' keyword argument, but not both") args = kw['c'] del kw['c'] if not isinstance(args, (list, tuple)): args = (args,) for name, value in kw.items(): if value is None: del kw[name] continue kw[name] = unicode(value) if name.endswith('_'): kw[name[:-1]] = value del kw[name] if name.find('__') != -1: new_name = name.replace('__', ':') kw[new_name] = value del kw[name] el.attrib.update(kw) el.text = self.text last = None for item in self.getchildren(): last = item el.append(item) for arg in flatten(args): if arg is None: continue if not et.iselement(arg): if last is None: if el.text is None: el.text = unicode(arg) else: el.text += unicode(arg) else: if last.tail is None: last.tail = unicode(arg) else: last.tail += unicode(arg) else: last = arg el.append(last) return el def __str__(self): return et.tostring(self, default_encoding) def __unicode__(self): # This is lame! return str(self).decode(default_encoding) def __repr__(self): content = str(self) if len(content) > 25: content = repr(content[:25]) + '...' else: content = repr(content) return '' % content class ElementList(list): def __str__(self): return html.str(self) def __repr__(self): return 'ElementList(%s)' % list.__repr__(self) def flatten(items): for item in items: if isinstance(item, (list, tuple)): for sub in flatten(item): yield sub else: yield item __all__ = ['html'] PK1X=5m/Kformencode/__init__.pyfrom api import * # @@ ianb 2005-05: should these be lazily loaded? Especially validators? from schema import * from compound import * from foreach import * import validators from variabledecode import NestedVariables PK1X=5mwoeG2G2formencode/schema.pyfrom interfaces import * from api import * import declarative __all__ = ['Schema'] class Schema(FancyValidator): """ A schema validates a dictionary of values, applying different validators (be key) to the different values. If allow_extra_fields=True, keys without validators will be allowed; otherwise they will raise Invalid. If filter_extra_fields is set to true, then extra fields are not passed back in the results. Validators are associated with keys either with a class syntax, or as keyword arguments (class syntax is usually easier). Something like:: class MySchema(Schema): name = Validators.PlainText() phone = Validators.PhoneNumber() These will not be available as actual instance variables, but will be collected in a dictionary. To remove a validator in a subclass that is present in a superclass, set it to None, like:: class MySubSchema(MySchema): name = None """ # These validators will be applied before this schema: pre_validators = [] # These validators will be applied after this schema: chained_validators = [] # If true, then it is not an error when keys that aren't # associated with a validator are present: allow_extra_fields = False # If true, then keys that aren't associated with a validator # are removed: filter_extra_fields = False # If this is given, then any keys that aren't available but # are expected will be replaced with this value (and then # validated!) This does not override a present .if_missing # attribute on validators: if_key_missing = NoDefault # If true, then missing keys will be missing in the result, # if the validator doesn't have if_missing on it already: ignore_key_missing = False compound = True fields = {} order = [] messages = { 'notExpected': 'The input field %(name)s was not expected.', 'missingValue': "Missing value", } __mutableattributes__ = ('fields', 'chained_validators', 'pre_validators') def __classinit__(cls, new_attrs): FancyValidator.__classinit__(cls, new_attrs) # Don't bother doing anything if this is the most parent # Schema class (which is the only class with just # FancyValidator as a superclass): if cls.__bases__ == (FancyValidator,): return cls # Scan through the class variables we've defined *just* # for this subclass, looking for validators (both classes # and instances): for key, value in new_attrs.items(): if key in ('pre_validators', 'chained_validators', 'view'): continue if is_validator(value): cls.fields[key] = value delattr(cls, key) # This last case means we're overwriting a validator # from a superclass: elif cls.fields.has_key(key): del cls.fields[key] for name, value in cls.fields.items(): cls.add_field(name, value) def __initargs__(self, new_attrs): for key, value in new_attrs.items(): if key in ('pre_validators', 'chained_validators', 'view'): continue if is_validator(value): self.fields[key] = value delattr(self, key) # This last case means we're overwriting a validator # from a superclass: elif self.fields.has_key(key): del self.fields[key] for name, value in self.fields.items(): self.add_field(name, value) def _to_python(self, value_dict, state): if not value_dict and self.if_empty is not NoDefault: return self.if_empty for validator in self.pre_validators: value_dict = validator.to_python(value_dict, state) new = {} errors = {} unused = self.fields.keys() if state is not None: previous_key = getattr(state, 'key', None) previous_full_dict = getattr(state, 'full_dict', None) state.full_dict = value_dict try: for name, value in value_dict.items(): try: unused.remove(name) except ValueError: if not self.allow_extra_fields: raise Invalid( self.message('notExpected', state, name=repr(name)), value_dict, state) else: if not self.filter_extra_fields: new[name] = value continue validator = self.fields[name] try: new[name] = validator.to_python(value, state) except Invalid, e: errors[name] = e for name in unused: validator = self.fields[name] try: if_missing = validator.if_missing except AttributeError: if_missing = NoDefault if if_missing is NoDefault: if self.ignore_key_missing: continue if self.if_key_missing is NoDefault: errors[name] = Invalid( self.message('missingValue', state), None, state) else: try: new[name] = validator.to_python(self.if_key_missing, state) except Invalid, e: errors[name] = e else: new[name] = validator.if_missing if errors: for validator in self.chained_validators: if (not hasattr(validator, 'validate_partial') or not getattr(validator, 'validate_partial_form', False)): continue try: validator.validate_partial(value_dict, state) except Invalid, e: sub_errors = e.unpack_errors() if not isinstance(sub_errors, dict): # Can't do anything here continue merge_dicts(errors, sub_errors) if errors: raise Invalid( format_compound_error(errors), value_dict, state, error_dict=errors) for validator in self.chained_validators: new = validator.to_python(new, state) return new finally: if state is not None: state.key = previous_key state.full_dict = previous_full_dict def _from_python(self, value_dict, state): chained = self.chained_validators[:] chained.reverse() finished = [] for validator in chained: __traceback_info__ = 'for_python chained_validator %s (finished %s)' % (validator, ', '.join(map(repr, finished)) or 'none') finished.append(validator) value_dict = validator.from_python(value_dict, state) new = {} errors = {} unused = self.fields.keys() if state is not None: previous_key = getattr(state, 'key', None) previous_full_dict = getattr(state, 'full_dict', None) state.full_dict = value_dict try: __traceback_info__ = None for name, value in value_dict.items(): __traceback_info__ = 'for_python in %s' % name try: unused.remove(name) except ValueError: if not self.allow_extra_fields: raise Invalid( self.message('notExpected', state, name=repr(name)), value_dict, state) if not self.filter_extra_fields: new[name] = value else: try: new[name] = self.fields[name].from_python(value, state) except Invalid, e: errors[name] = e del __traceback_info__ for name in unused: validator = self.fields[name] try: new[name] = validator.from_python(None, state) except Invalid, e: errors[name] = e if errors: raise Invalid( format_compound_error(errors), value_dict, state, error_dict=errors) pre = self.pre_validators[:] pre.reverse() for validator in pre: __traceback_info__ = 'for_python pre_validator %s' % validator new = validator.from_python(new, state) return new finally: if state is not None: state.key = previous_key state.full_dict = previous_full_dict def add_chained_validator(self, cls, validator): if self is not None: if self.chained_validators is cls.chained_validators: self.chained_validators = cls.chained_validators[:] self.chained_validators.append(validator) else: cls.chained_validators.append(validator) add_chained_validator = declarative.classinstancemethod( add_chained_validator) def add_field(self, cls, name, validator): if self is not None: if self.fields is cls.fields: self.fields = cls.fields.copy() self.fields[name] = validator else: cls.fields[name] = validator add_field = declarative.classinstancemethod(add_field) def add_pre_validator(self, cls, validator): if self is not None: if self.pre_validators is cls.pre_validators: self.pre_validators = cls.pre_validators[:] self.pre_validators.append(validator) else: cls.pre_validators.append(validator) add_pre_validator = declarative.classinstancemethod(add_pre_validator) def subvalidators(self): result = [] result.extend(self.pre_validators) result.extend(self.chained_validators) result.extend(self.fields.values()) return result def format_compound_error(v, indent=0): if isinstance(v, Exception): try: return str(v) except UnicodeDecodeError: # There doesn't seem to be a better way to get a str() # version if possible, and unicode() if necessary, because # testing for the presence of a __unicode__ method isn't # enough return unicode(v) elif isinstance(v, dict): l = v.items() l.sort() return ('%s\n' % (' '*indent)).join( ["%s: %s" % (k, format_compound_error(value, indent=len(k)+2)) for k, value in l if value is not None]) elif isinstance(v, list): return ('%s\n' % (' '*indent)).join( ['%s' % (format_compound_error(value, indent=indent)) for value in v if value is not None]) elif isinstance(v, str): return v else: assert 0, "I didn't expect something like %s" % repr(v) def merge_dicts(d1, d2): for key in d2: if key in d1: d1[key] = merge_values(d1[key], d2[key]) else: d1[key] = d2[key] return d1 def merge_values(v1, v2): if (isinstance(v1, (str, unicode)) and isinstance(v2, (str, unicode))): return v1 + '\n' + v2 elif (isinstance(v1, (list, tuple)) and isinstance(v2, (list, tuple))): return merge_lists(v1, v2) elif isinstance(v1, dict) and isinstance(v2, dict): return merge_dicts(v1, v2) else: # @@: Should we just ignore errors? Seems we do... return v1 def merge_lists(l1, l2): if len(l1) < len(l2): l1 = l1 + [None]*(len(l2)-len(l1)) elif len(l2) < len(l1): l2 = l2 + [None]*(len(l1)-len(l2)) result = [] for l1item, l2item in zip(l1, l2): item = None if l1item is None: item = l2item elif l2item is None: item = l1item else: item = merge_values(l1item, l2item) result.append(item) return result PK1X=5K븃formencode/fields.py## FunFormKit, a Webware Form processor ## Copyright (C) 2001, Ian Bicking ## ## This library is free software; you can redistribute it and/or ## modify it under the terms of the GNU Lesser General Public ## License as published by the Free Software Foundation; either ## version 2.1 of the License, or (at your option) any later version. ## ## This library is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## Lesser General Public License for more details. ## ## You should have received a copy of the GNU Lesser General Public ## License along with this library; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## ## NOTE: In the context of the Python environment, I interpret "dynamic ## linking" as importing -- thus the LGPL applies to the contents of ## the modules, but make no requirements on code importing these ## modules. """ Fields for use with Forms. The Field class gives the basic interface, and then there's bunches of classes for the specific kinds of fields. """ import urllib PILImage = None import os from htmlgen import html True, False = (1==1), (0==1) from declarative import Declarative class NoDefault: pass class none_dict(dict): def __getattr__(self, attr): if attr.startswith('_'): raise AttributeError return self.get(attr) class Context(object): def __init__(self, name_prefix='', id_prefix='', defaults=None, **kw): self.name_prefix = name_prefix self.id_prefix = id_prefix self.defaults = defaults for name, value in kw.items(): setattr(self, name, value) self.options = none_dict(kw) def name(self, field, adding=None): if not field.name: assert self.name_prefix, ( "Field has not name, and context has no name_prefix") name = self.name_prefix elif self.name_prefix: name = self.name_prefix + field.name else: name = field.name if adding: return name + '.' + adding else: return name def id(self, field): return self.id_prefix + field.name def default(self, field): if self.defaults: name = self.name(field) return self.defaults.get(name) else: return None def push_attr(self, **kw): if 'add_name' in kw: if self.name_prefix: kw['name_prefix'] = self.name_prefix+'.'+kw.pop('add_name') else: kw['name_prefix'] = kw.pop('add_name') restore = {} for name, value in kw.items(): restore[name] = getattr(self, name, PopValue.no_value) setattr(self, name, value) return PopValue(self, restore) class PopValue(object): no_value = [] def __init__(self, object, restore_values): self.object = object self.restore_values = restore_values def pop_attr(self): for name, value in self.restore_values.items(): if value is self.no_value: delattr(self.object, name) else: setattr(self.object, name, value) class Field(Declarative): description = None id = None static = False hidden = False requires_label = True default = None name = None width = None enctype = None def __init__(self, *args, **kw): if args: context = args[0] args = args[1:] kw['name'] = context.name_prefix + kw.get('name', '') super(Field, self).__init__(*args, **kw) def render(self, context): if self.hidden: return self.html_hidden(context) elif self.static: return self.html_static(context) else: return self.html(context) def html_hidden(self, context): """The HTML for a hidden input ()""" return html.input( type='hidden', id=context.id(self), name=context.name(self), value=context.default(self)) def html_static(self, context): return html( self.html_hidden(context), context.default(self)) def html(self, context): """The HTML input code""" raise NotImplementedError def style_width(self): if self.width: if isinstance(self.width, int): return 'width: %s%%' % self.width else: return 'width: %s' % self.width else: return None style_width = property(style_width) def load_javascript(self, filename): f = open(os.path.join(os.path.dirname(__file__), 'javascript', filename)) c = f.read() f.close() return c class Form(Declarative): action = None id = None method = "POST" fields = [] form_name = None enctype = None def __init__(self, *args, **kw): Declarative.__init__(self, *args, **kw) def render(self, context): assert self.action, "You must provide an action" contents = html( [f.render(context) for f in self.fields]) enctype = self.enctype for field in self.fields: if field.enctype: if enctype is None or enctype == field.enctype: enctype = field.enctype else: raise ValueError( "Conflicting enctypes; need %r, field %r wants %r" % (enctype, field, field.enctype)) return html.form( action=self.action, method=self.method, name=context.name(self), id=context.id(self), enctype=enctype, c=contents) class Layout(Field): """ Represents a set of fields. Keyword arguments or attributes that are of type ``Field`` will be collected in the ``fields`` list:: >>> class MyForm(Layout): ... name = Text() ... address = Textarea() >>> [f.name for f in MyForm.fields] ['name', 'address'] >>> MyForm.name.name 'name' >>> another = MyForm(city=Text()) >>> [f.name for f in another.fields] ['name', 'address', 'city'] """ append_to_label = ':' use_fieldset = False legend = None requires_label = False fieldset_class = 'formfieldset' fields = [] __mutableattributes__ = ('fields',) def __classinit__(cls, new_attrs): Field.__classinit__(cls, new_attrs) found = [] for name, value in new_attrs.items(): # @@: Should we capture the name somehow? if isinstance(value, Field): if not value.name: value.name = name else: value.name = name + '.' + value.name found.append(value) found.sort(lambda a, b: cmp(a.declarative_count, b.declarative_count)) cls.fields.extend(found) def __init__(self, *args, **kw): self.fields = self.fields[:] found = [] for name, value in kw.items(): if isinstance(value, Field): found.append(value) if not value.name: value.name = name else: value.name = name + '.' + value.name del kw[name] found.sort(lambda a, b: cmp(a.declarative_count, b.declarative_count)) self.fields.extend(found) super(Field, self).__init__(*args, **kw) def render(self, context): if self.name: restore = context.push_attr(add_name=self.name+'.') else: restore = None try: if self.hidden: return html([f.html_hidden(context) for f in self.fields]) normal = [] hidden = [] for field in self.fields: if field.hidden: hidden.append(field.render(context)) else: normal.append(field) return html(self.wrap(hidden, normal, context)) finally: if restore: restore.pop_attr() def wrap(self, hidden, normal, context): hidden.append(self.wrap_fields( [self.wrap_field(field, context) for field in normal], context)) return hidden def wrap_field(self, field, context): return html(self.format_label(field, context), field.render(context), html.br) def format_label(self, field, context): label = '' if self.requires_label: label = field.description if label: label = label + self.append_to_label return label def wrap_fields(self, rendered_fields, context): if not self.use_fieldset: return rendered_fields legend = self.legend if legend: legend = html.legend(legend) else: legend = '' return html.fieldset(legend, rendered_fields, class_=self.fieldset_class) class TableLayout(Layout): width = None label_class = 'formlabel' field_class = 'formfield' label_align = None table_class = 'formtable' def wrap_field(self, field, context): return html.tr( html.td(self.format_label(field, context), align=self.label_align, class_=self.label_class), html.td(field.render(context), class_=self.field_class)) def wrap_fields(self, rendered_fields, context): return html.table(rendered_fields, width=self.width, class_=self.table_class) class FormTableLayout(Layout): layout = None append_to_label = '' def wrap(self, hidden, normal, context): fields = {} for field in normal: fields[field.name] = field layout = self.layout assert layout, "You must provide a layout for %s" % self output = [] for line in layout: if isinstance(line, (str, unicode)): line = [line] output.append(self.html_line(line, fields, context)) hidden.append(self.wrap_fields(output, context)) return hidden def html_line(self, line, fields, context): """ Formats lines: '=text' means a literal of 'text', 'name' means the named field, ':name' means the named field, but without a label. """ cells = [] for item in line: if item.startswith('='): cells.append(html.td(item)) continue if item.startswith(':'): field = fields[item[1:]] label = '' else: field = fields[item] label = self.format_label(field, context) if label: label = html(label, html.br) cells.append(html.td('\n', label, field.render(context), valign="bottom")) cells.append('\n') return html.table(html.tr(cells)) class SubmitButton(Field): """ Not really a field, but a widget of sorts. methodToInvoke is the name (string) of the servlet method that should be called when this button is hit. You can use suppressValidation for large-form navigation (wizards), when you want to save the partially-entered and perhaps invalid data (e.g., for the back button on a wizard). You can load that data back in by passing the fields to FormRequest/From as httpRequest. The confirm option will use JavaScript to confirm that the user really wants to submit the form. Useful for buttons that delete things. Examples:: >>> prfield(SubmitButton(description='submit')) >>> prfield(SubmitButton(confirm='Really?')) """ confirm = None default_description = "Submit" description = '' requires_label = False def html(self, context): if self.confirm: query = ('return window.confirm(\'%s\')' % javascript_quote(self.confirm)) else: query = None description = ((self.description) or self.default_description) return html.input( type='submit', name=context.name(self), value=description, onclick=query) def html_hidden(self, context): if context.default(self): return html.input.hidden( name=context.name(self), value=context.default(self)) else: return '' class ImageSubmit(SubmitButton): """ Like SubmitButton, but with an image. Examples:: >>> prfield(ImageSubmit(img_src='test.gif')) """ img_height = None img_width = None border = 0 def html(self, context): return html.input( type='image', name=context.name(self), value=self.description, src=self.img_src, height=self.img_height, width=self.img_width, border=self.border, alt=self.description) class Hidden(Field): """ Hidden field. Set the value using form defaults. Since you'll always get string back, you are expected to only pass strings in (unless you use a converter like AsInt). Examples:: >>> prfield(Hidden(), defaults={'f': 'a&value'}) """ requires_label = False hidden = True def html(self, context): return self.html_hidden(context) class Text(Field): """ Basic text field. Examples:: >>> t = Text() >>> prfield(t) >>> prfield(t, defaults={'f': "&whatever&"}) >>> prfield(t(maxlength=20, size=10)) """ size = None maxlength = None width = None def html(self, context): return html.input( type='text', name=context.name(self), value=context.default(self), maxlength=self.maxlength, size=self.size, style=self.style_width) class Textarea(Field): """ Basic textarea field. Examples:: >>> prfield(Textarea(), defaults={'f': ''}) """ rows = 10 cols = 60 wrap = "SOFT" width = None def html(self, context): return html.textarea( name=context.name(self), rows=self.rows, cols=self.cols, wrap=self.wrap or None, style=self.style_width, c=context.default(self)) class Password(Text): """ Basic password field. Examples:: >>> prfield(Password(maxlength=10), defaults={'f': 'pass'}) """ def html(self, context): return html.input( type='password', name=context.name(self), value=context.default(self), maxlength=self.maxlength, size=self.size, style=self.style_width) class Select(Field): """ Creates a select field, based on a list of value/description pairs. The values do not need to be strings. If nullInput is given, this will be the default value for an unselected box. This would be the "Select One" selection. If you want to give an error if they do not select one, then use the NotEmpty() validator. They will not get this selection if the form is being asked for a second time after they already gave a selection (i.e., they can't go back to the null selection if they've made a selection and submitted it, but are presented the form again). If you always want a null selection available, put that directly in the selections. Examples:: >>> prfield(Select(selections=[(1, 'One'), (2, 'Two')]), defaults=dict(f='2')) >>> prfield(Select(selections=[(1, 'One')], null_input='Choose')) """ selections = [] null_input = None size = None def html(self, context, subsel=None): selections = self.selections null_input = self.null_input if not context.default(self) and null_input: # @@: list()? selections = [('', null_input)] + selections if subsel: return subsel(selections, context) else: return self.selection_html(selections, context) def selection_html(self, selections, context): return html.select( name=context.name(self), size=self.size, c=[html.option(desc, value=value, selected=self.selected(value, context.default(self))) for (value, desc) in selections]) def selected(self, key, default): if str(key) == str(default): return 'selected' else: return None class Ordering(Select): """ Rendered as a select field, this allows the user to reorder items. The result is a list of the items in the new order. Examples:: >>> o = Ordering(selections=[('a', 'A'), ('b', 'B')]) >>> prfield(o, chop=('')))
""" confirm_on_delete = None def buttons(self, context): buttons = Ordering.buttons(self, context) confirm_on_delete = self.confirm_on_delete if confirm_on_delete: delete_button = ( 'delete', 'window.confirm(\'%s\') ? delete_entry(this) : false' % javascript_quote(confirm_on_delete)) else: delete_button = ('delete', 'delete_entry(this)') new_buttons = [] for button in buttons: if button[1] == 'reset_entries(this)': new_buttons.append(delete_button) delete_button = None new_buttons.append(button) if delete_button: new_buttons.append(delete_button) return new_buttons def javascript(self, context): js = Ordering.javascript(self, context) return js + (''' function deleteEntry(formElement) { var select; select = getSelect(formElement); select.options[select.selectedIndex] = null; saveValue(select); } ''') class Radio(Select): """ Radio selection; very similar to a select, but with a radio. Example:: >>> prfield(Radio(selections=[('a', 'A'), ('b', 'B')]), ... defaults=dict(f='b'))

""" def selection_html(self, selections, context): id = 0 result = [] for value, desc in selections: id = id + 1 if self.selected(value, context.default(self)): checked = 'checked' else: checked = None result.append(html.input( type='radio', name=context.name(self), value=value, id="%s_%i" % (context.name(self), id), checked=checked)) result.append(html.label( for_='%s_%i' % (context.name(self), id), c=desc)) result.append(html.br()) return result class MultiSelect(Select): """ Selection that allows multiple items to be selected. A list will always be returned. The size is, by default, the same as the number of selections (so no scrolling by the user is necessary), up to maxSize. Examples:: >>> sel = MultiSelect(selections=[('&a', '&A'), ('&b', '&B'), (1, 1)]) >>> prfield(sel) >>> prfield(sel, defaults=dict(f=['&b', '1'])) """ size = NoDefault max_size = 10 def selection_html(self, selections, context): result = [] size = self.size if size is NoDefault: size = min(len(selections), self.max_size) result.append(html.select( name=context.name(self), size=size, multiple="multiple", c=[html.option(desc, value=value, selected=self.selected(value, context.default(self)) and "selected" or None) for value, desc in selections])) def selected(self, key, default): if not isinstance(default, (tuple, list)): if default is None: return False default = [default] return str(key) in map(str, default) def html_hidden(self, context): default = context.default(self) if not isinstance(default, (tuple, list)): if default is None: default = [] else: default = [default] return html( [html.input.hidden(name=context.name(self), value=value) for value in default]) def selection_html(self, selections, context): result = [] size = self.size if size is NoDefault: size = min(len(selections), self.max_size) result.append(html.select( name=context.name(self), size=size, multiple="multiple", c=[html.option(desc, value=value, selected=self.selected(value, context.default(self)) and "selected" or None) for value, desc in selections])) return result class MultiCheckbox(MultiSelect): """ Like MultiSelect, but with checkboxes. Examples:: >>> sel = MultiCheckbox(selections=[('&a', '&A'), ('&b', '&B'), (1, 1)]) >>> prfield(sel, defaults=dict(f='&a'))


""" def selection_html(self, selections, context): result = [] id = 0 for value, desc in selections: id = id + 1 result.append(html.input( type='checkbox', name=context.name(self), id="%s_%i" % (context.name(self), id), value=value, checked=self.selected(value, context.default(self)) and "checked" or None)) result.append(html.label( " " + str(desc), for_="%s_%i" % (context.name(self), id))) result.append(html.br()) return result class Checkbox(Field): """ Simple checkbox. Examples:: >>> prfield(Checkbox(), defaults=dict(f=0)) >>> prfield(Checkbox(), defaults=dict(f=1)) """ def html(self, context): return html.input( type='checkbox', name=context.name(self), checked = context.default(self) and "checked" or None) class File(Field): """ accept is the a list of MIME types to accept. Browsers pay very little attention to this, though. By default it will return a cgi FieldStorage object -- use .value to get the string, .file to get a file object, .filename to get the filename. Maybe other stuff too. If you set returnString=True it will return a string with the contents of the uploaded file. You can't have any validators unless you do returnString. Examples:: >>> prfield(File()) """ accept = None size = None enctype = "multipart/form-data" def html(self, context): accept = self.accept if accept and accept is not None: mime_list = ",".join(accept) else: mime_list = None return html.input( type='file', name=context.name(self), size=self.size, accept=mime_list) class StaticText(Field): """ A static piece of text to be put into the field, useful only for layout purposes. Examples:: >>> prfield(StaticText(text='some HTML')) some HTML >>> prfield(StaticText(text='whatever', hidden=1)) """ text = '' requires_label = False def html(self, context): default = context.default(self) if default is not None: return str(default) else: return str(self.text) def html_hidden(self, context): return '' class ColorPicker(Field): """ This field allows the user to pick a color from a popup window. This window contains a pallete of colors. They can also enter the hex value of the color. A color swatch is updated with their chosen color. Examples:: >>> cp = ColorPicker(color_picker_url='/colorpick.html') >>> prfield(cp, defaults={'f': '#ff0000'})
""" color_picker_url = None def html(self, context): js = self.javascript(context) color_picker_url = self.color_picker_url assert color_picker_url, ( 'You must give a base URL for the color picker') name = context.name(self) color_id = context.name(self, adding='pick') default_color = context.default(self) or '#ffffff' return html.table( cellspacing=0, border=0, c=[html.tr( html.td(width=20, id=color_id, style="background-color: %s; border: thin black solid;" % default_color, c=" "), html.td( html.input(type='text', size=8, onchange="document.getElementById('%s').style.backgroundColor = this.value; return true" % color_id, name=name, value=context.default(self)), html.input(type='button', value="pick", onclick="colorpick(this, '%s', '%s')" % (name, color_id))))]) def javascript(self, context): return """\ function colorpick(element, textFieldName, color_id) { win = window.open('%s?form=' + escape(element.form.attributes.name.value) + '&field=' + escape(textFieldName) + '&colid=' + escape(color_id), '_blank', 'dependent=no,directories=no,width=300,height=130,location=no,menubar=no,status=no,toolbar=no'); } """ % self.color_picker_url ######################################## ## Utility functions ######################################## def javascript_quote(value): """ Quote a Python value as a Javascript literal. I'm depending on the fact that repr falls back on single quote when both single and double quote are there. Also, JavaScript uses the same octal \\ing that Python uses. Examples:: >>> javascript_quote('a') 'a' >>> javascript_quote('\\n') '\\\\n' >>> javascript_quote('\\\\') '\\\\\\\\' """ return repr('"' + str(value))[2:-1] def prfield(field, chop=None, **kw): """ Prints a field, useful for doctests. """ if not kw.has_key('name'): kw['name'] = 'f' name = kw.pop('name') context = Context(**kw) context.form = Form() result = html.str(field(name=name).render(context)) if chop: pos1 = result.find(chop[0]) pos2 = result.find(chop[1]) if pos1 == -1 or pos2 == -1: print 'chop (%s) not found' % repr(chop) else: result = result[:pos1] + result[pos2+len(chop[1]):] print result if __name__ == '__main__': import doctest import doctest_xml_compare doctest_xml_compare.install() doctest.testmod() PK1X=5s{formencode/foreach.py""" Validator for repeating items. """ from api import NoDefault, Invalid from compound import CompoundValidator, to_python, from_python try: from sets import Set except ImportError: # We only use it for type information now: Set = None __all__ = ['ForEach'] class ForEach(CompoundValidator): """ Use this to apply a validator/converter to each item in a list. For instance:: ForEach(AsInt(), InList([1, 2, 3])) Will take a list of values and try to convert each of them to an integer, and then check if each integer is 1, 2, or 3. Using multiple arguments is equivalent to:: ForEach(All(AsInt(), InList([1, 2, 3]))) Use convert_to_list=True if you want to force the input to be a list. This will turn non-lists into one-element lists, and None into the empty list. This tries to detect sequences by iterating over them (except strings, which aren't considered sequences). ForEach will try to convert the entire list, even if errors are encountered. If errors are encountered, they will be collected and a single Invalid exception will be raised at the end (with error_list set). If the incoming value is a Set, then we return a Set. """ convert_to_list = True if_empty = NoDefault if_missing = [] repeating = True def attempt_convert(self, value, state, validate): if self.convert_to_list: value = self._convert_to_list(value) if self.if_empty is not NoDefault and not value: return self.if_empty if self.not_empty and not value: if validate is from_python and self.accept_python: return [] raise Invalid( self.message('empty', state), value, state) new_list = [] errors = [] all_good = True is_set = isinstance(value, Set) if state is not None: previous_index = getattr(state, 'index', NoDefault) previous_full_list = getattr(state, 'full_list', NoDefault) index = 0 state.full_list = value try: for sub_value in value: if state: state.index = index index += 1 good_pass = True for validator in self.validators: try: sub_value = validate(validator, sub_value, state) except Invalid, e: errors.append(e) all_good = False good_pass = False break if good_pass: errors.append(None) new_list.append(sub_value) if all_good: if is_set: new_list = Set(new_list) return new_list else: raise Invalid( 'Errors:\n%s' % '\n'.join([str(e) for e in errors if e]), value, state, error_list=errors) finally: if state is not None: if previous_index is NoDefault: try: del state.index except AttributeError: pass else: state.index = previous_index if previous_full_list is NoDefault: try: del state.full_list except AttributeError: pass else: state.full_list = previous_full_list def _convert_to_list(self, value): if isinstance(value, (str, unicode)): return [value] elif value is None: return [] elif isinstance(value, (list, tuple)): return value try: for n in value: break return value ## @@: Should this catch any other errors?: except TypeError: return [value] PK1X=53r $formencode/htmlfill_schemabuilder.py""" Extension to ``htmlfill`` that can parse out schema-defining statements. You can either pass ``SchemaBuilder`` to ``htmlfill.render`` (the ``listen`` argument), or call ``parse_schema`` to just parse out a ``Schema`` object. """ import validators, schema, compound __all__ = ['parse_schema', 'SchemaBuilder'] def parse_schema(form): """ Given an HTML form, parse out the schema defined in it and return that schema. """ listener = htmlfill_schemabuilder.SchemaBuilder() p = htmlfill.FillingParser( defaults={}, listener=listener) p.feed(self.form) p.close() return listener.schema() default_validators = dict( [(name.lower(), getattr(validators, name)) for name in dir(validators)]) def get_messages(cls, message): if not message: return {} else: return dict([(k, message) for k in cls._messages.keys()]) def to_bool(value): value = value.strip().lower() if value in ('true', 't', 'yes', 'y', 'on', '1'): return True elif value in ('false', 'f', 'no', 'n', 'off', '0'): return False else: raise ValueError("Not a boolean value: %r (use 'true'/'false')") def force_list(v): """ Force single items into a list. This is useful for checkboxes. """ if isinstance(v, list): return v elif isinstance(v, tuple): return list(v) else: return [v] class SchemaBuilder(object): def __init__(self, validators=default_validators): self.validators = validators self._schema = None def reset(self): self._schema = schema.Schema() def schema(self): return self._schema def listen_input(self, parser, tag, attrs): get_attr = parser.get_attr name = get_attr(attrs, 'name') if not name: # @@: should warn if you try to validate unnamed fields return v = compound.All(validators.Identity()) add_to_end = None # for checkboxes, we must set if_missing = False if tag.lower() == "input": type_attr = get_attr(attrs, "type").lower().strip() if type_attr == "submit": v.validators.append(validators.Bool()) elif type_attr == "checkbox": v.validators.append(validators.Wrapper(to_python = force_list)) elif type_attr == "file": add_to_end = validators.FieldStorageUploadConverter() message = get_attr(attrs, 'form:message') required = to_bool(get_attr(attrs, 'form:required', 'false')) if required: v.validators.append( validators.NotEmpty( messages=get_messages(validators.NotEmpty, message))) else: v.validators[0].if_missing = False if add_to_end: v.validators.append(add_to_end) v_type = get_attr(attrs, 'form:validate', None) if v_type: pos = v_type.find(':') if pos != -1: # @@: should parse args args = (v_type[pos+1:],) v_type = v_type[:pos] else: args = () v_type = v_type.lower() v_class = self.validators.get(v_type) if not v_class: raise ValueError("Invalid validation type: %r" % v_type) kw_args={'messages': get_messages(v_class, message)} v_inst = v_class( *args, **kw_args) v.validators.append(v_inst) self._schema.add_field(name, v) PK1X=5CГAAformencode/validators.py## FormEncode, a Form processor ## Copyright (C) 2003, Ian Bicking ## ## This library is free software; you can redistribute it and/or ## modify it under the terms of the GNU Lesser General Public ## License as published by the Free Software Foundation; either ## version 2.1 of the License, or (at your option) any later version. ## ## This library is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## Lesser General Public License for more details. ## ## You should have received a copy of the GNU Lesser General Public ## License along with this library; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## ## NOTE: In the context of the Python environment, I interpret "dynamic ## linking" as importing -- thus the LGPL applies to the contents of ## the modules, but make no requirements on code importing these ## modules. """ Validator/Converters for use with FormEncode. """ import re DateTime = None mxlookup = None httplib = None urlparse = None socket = None DNSError = None from interfaces import * from api import * sha = random = None try: import sets except ImportError: sets = None import cgi import fieldstorage True, False = (1==1), (0==1) ############################################################ ## Utility methods ############################################################ # These all deal with accepting both mxDateTime and datetime # modules and types datetime_module = None mxDateTime_module = None def import_datetime(module_type): global datetime_module, mxDateTime_module if module_type is None: try: if datetime_module is None: import datetime as datetime_module return datetime_module except ImportError: if mxDateTime_module is None: from mx import DateTime as mxDateTime_module return mxDateTime_module module_type = module_type.lower() assert module_type in ('datetime', 'mxdatetime') if module_type == 'datetime': if datetime_module is None: import datetime as datetime_module return datetime_module else: if mxDateTime_module is None: from mx import DateTime as mxDateTime_module return mxDateTime_module def datetime_now(module): if module.__name__ == 'datetime': return module.datetime.now() else: return module.now() def datetime_makedate(module, year, month, day): if module.__name__ == 'datetime': return module.date(year, month, day) else: try: return module.DateTime(year, month, day) except module.RangeError, e: raise ValueError(str(e)) # TODO: Needs being extended to support mx.DateTime as well. def datetime_time(module): if module.__name__ == 'datetime': return module.time # TODO: Needs being extended to support mx.DateTime as well. def datetime_isotime(module): if module.__name__ == 'datetime': return module.time.isoformat ############################################################ ## Wrapper Validators ############################################################ class ConfirmType(FancyValidator): """ Confirms that the input/output is of the proper type. Uses the parameters: subclass: The class or a tuple of classes; the item must be an instance of the class or a subclass. type: A type or tuple of types (or classes); the item must be of the exact class or type. Subclasses are not allowed. Examples:: >>> cint = ConfirmType(subclass=int) >>> cint.to_python(True) True >>> cint.to_python('1') Traceback (most recent call last): ... Invalid: '1' is not a subclass of >>> cintfloat = ConfirmType(subclass=(float, int)) >>> cintfloat.to_python(1.0), cintfloat.from_python(1.0) (1.0, 1.0) >>> cintfloat.to_python(1), cintfloat.from_python(1) (1, 1) >>> cintfloat.to_python(None) Traceback (most recent call last): ... Invalid: None is not a subclass of one of the types , >>> cint2 = ConfirmType(type=int) >>> cint2(accept_python=False).from_python(True) Traceback (most recent call last): ... Invalid: True must be of the type """ subclass = None type = None messages = { 'subclass': "%(object)r is not a subclass of %(subclass)s", 'inSubclass': "%(object)r is not a subclass of one of the types %(subclassList)s", 'inType': "%(object)r must be one of the types %(typeList)s", 'type': "%(object)r must be of the type %(type)s", } def __init__(self, *args, **kw): FancyValidator.__init__(self, *args, **kw) if self.subclass: if isinstance(self.subclass, list): self.subclass = tuple(self.subclass) elif not isinstance(self.subclass, tuple): self.subclass = (self.subclass,) self.validate_python = self.confirm_subclass if self.type: if isinstance(self.type, list): self.type = tuple(self.type) elif not isinstance(self.type, tuple): self.type = (self.type,) self.validate_python = self.confirm_type def confirm_subclass(self, value, state): if not isinstance(value, self.subclass): if len(self.subclass) == 1: msg = self.message('subclass', state, object=value, subclass=self.subclass[0]) else: subclass_list = ', '.join(map(str, self.subclass)) msg = self.message('inSubclass', state, object=value, subclassList=subclass_list) raise Invalid(msg, value, state) def confirm_type(self, value, state): for t in self.type: if type(value) is t: break else: if len(self.type) == 1: msg = self.message('type', state, object=value, type=self.type[0]) else: msg = self.message('inType', state, object=value, typeList=', '.join(map(str, self.type))) raise Invalid(msg, value, state) return value def is_empty(self, value): return False class Wrapper(FancyValidator): """ Used to convert functions to validator/converters. You can give a simple function for `to_python`, `from_python`, `validate_python` or `validate_other`. If that function raises an exception, the value is considered invalid. Whatever value the function returns is considered the converted value. Unlike validators, the `state` argument is not used. Functions like `int` can be used here, that take a single argument. Examples:: >>> def downcase(v): ... return v.lower() >>> wrap = Wrapper(to_python=downcase) >>> wrap.to_python('This') 'this' >>> wrap.from_python('This') 'This' >>> wrap2 = Wrapper(from_python=downcase) >>> wrap2.from_python('This') 'this' >>> wrap2.from_python(1) Traceback (most recent call last): ... Invalid: 'int' object has no attribute 'lower' >>> wrap3 = Wrapper(validate_python=int) >>> wrap3.to_python('1') '1' >>> wrap3.to_python('a') Traceback (most recent call last): ... Invalid: invalid literal for int(): a """ func_to_python = None func_from_python = None func_validate_python = None func_validate_other = None def __init__(self, *args, **kw): for n in ['to_python', 'from_python', 'validate_python', 'validate_other']: if kw.has_key(n): kw['func_%s' % n] = kw[n] del kw[n] FancyValidator.__init__(self, *args, **kw) self._to_python = self.wrap(self.func_to_python) self._from_python = self.wrap(self.func_from_python) self.validate_python = self.wrap(self.func_validate_python) self.validate_other = self.wrap(self.func_validate_other) def wrap(self, func): if not func: return None def result(value, state, func=func): try: return func(value) except Exception, e: raise Invalid(str(e), {}, value, state) return result class Constant(FancyValidator): """ This converter converts everything to the same thing. I.e., you pass in the constant value when initializing, then all values get converted to that constant value. This is only really useful for funny situations, like:: fromEmailValidator = ValidateAny( ValidEmailAddress(), Constant('unknown@localhost')) In this case, the if the email is not valid ``'unknown@localhost'`` will be used instead. Of course, you could use ``if_invalid`` instead. Examples:: >>> Constant('X').to_python('y') 'X' """ __unpackargs__ = ('value',) def _to_python(self, value, state): return self.value _from_python = _to_python ############################################################ ## Normal validators ############################################################ class MaxLength(FancyValidator): """ Invalid if the value is longer than `maxLength`. Uses len(), so it can work for strings, lists, or anything with length. Examples:: >>> max5 = MaxLength(5) >>> max5.to_python('12345') '12345' >>> max5.from_python('12345') '12345' >>> max5.to_python('123456') Traceback (most recent call last): ... Invalid: Enter a value less than 5 characters long >>> max5(accept_python=False).from_python('123456') Traceback (most recent call last): ... Invalid: Enter a value less than 5 characters long >>> max5.to_python([1, 2, 3]) [1, 2, 3] >>> max5.to_python([1, 2, 3, 4, 5, 6]) Traceback (most recent call last): ... Invalid: Enter a value less than 5 characters long >>> max5.to_python(5) Traceback (most recent call last): ... Invalid: Invalid value (value with length expected) """ __unpackargs__ = ('maxLength',) messages = { 'tooLong': "Enter a value less than %(maxLength)i characters long", 'invalid': "Invalid value (value with length expected)", } def validate_python(self, value, state): try: if value and \ len(value) > self.maxLength: raise Invalid(self.message('tooLong', state, maxLength=self.maxLength), value, state) else: return None except TypeError: raise Invalid(self.message('invalid', state), value, state) class MinLength(FancyValidator): """ Invalid if the value is shorter than `minlength`. Uses len(), so it can work for strings, lists, or anything with length. Examples:: >>> min5 = MinLength(5) >>> min5.to_python('12345') '12345' >>> min5.from_python('12345') '12345' >>> min5.to_python('1234') Traceback (most recent call last): ... Invalid: Enter a value at least 5 characters long >>> min5(accept_python=False).from_python('1234') Traceback (most recent call last): ... Invalid: Enter a value at least 5 characters long >>> min5.to_python([1, 2, 3, 4, 5]) [1, 2, 3, 4, 5] >>> min5.to_python([1, 2, 3]) Traceback (most recent call last): ... Invalid: Enter a value at least 5 characters long >>> min5.to_python(5) Traceback (most recent call last): ... Invalid: Invalid value (value with length expected) """ __unpackargs__ = ('minLength',) messages = { 'tooShort': "Enter a value at least %(minLength)i characters long", 'invalid': "Invalid value (value with length expected)", } def validate_python(self, value, state): try: if len(value) < self.minLength: raise Invalid(self.message('tooShort', state, minLength=self.minLength), value, state) except TypeError: raise Invalid(self.message('invalid', state), value, state) class NotEmpty(FancyValidator): """ Invalid if value is empty (empty string, empty list, etc). Generally for objects that Python considers false, except zero which is not considered invalid. Examples:: >>> ne = NotEmpty(messages={'empty': 'enter something'}) >>> ne.to_python('') Traceback (most recent call last): ... Invalid: enter something >>> ne.to_python(0) 0 """ not_empty = True messages = { 'empty': "Please enter a value", } def validate_python(self, value, state): if value == 0: # This isn't "empty" for this definition. return value if not value: raise Invalid(self.message('empty', state), value, state) class Empty(FancyValidator): """ Invalid unless the value is empty. Use cleverly, if at all. Examples:: >>> Empty.to_python(0) Traceback (most recent call last): ... Invalid: You cannot enter a value here """ messages = { 'notEmpty': "You cannot enter a value here", } def validate_python(self, value, state): if value or value == 0: raise Invalid(self.message('notEmpty', state), value, state) class Regex(FancyValidator): """ Invalid if the value doesn't match the regular expression `regex`. The regular expression can be a compiled re object, or a string which will be compiled for you. Use strip=True if you want to strip the value before validation, and as a form of conversion (often useful). Examples:: >>> cap = Regex(r'^[A-Z]+$') >>> cap.to_python('ABC') 'ABC' Note that ``.from_python()`` calls (in general) do not validate the input:: >>> cap.from_python('abc') 'abc' >>> cap(accept_python=False).from_python('abc') Traceback (most recent call last): ... Invalid: The input is not valid >>> cap.to_python(1) Traceback (most recent call last): ... Invalid: The input must be a string (not a : 1) >>> Regex(r'^[A-Z]+$', strip=True).to_python(' ABC ') 'ABC' >>> Regex(r'this', regexOps=('I',)).to_python('THIS') 'THIS' """ regexOps = () strip = False regex = None __unpackargs__ = ('regex',) messages = { 'invalid': "The input is not valid", } def __init__(self, *args, **kw): FancyValidator.__init__(self, *args, **kw) if isinstance(self.regex, str): ops = 0 assert not isinstance(self.regexOps, str), ( "regexOps should be a list of options from the re module " "(names, or actual values)") for op in self.regexOps: if isinstance(op, str): ops |= getattr(re, op) else: ops |= op self.regex = re.compile(self.regex, ops) def validate_python(self, value, state): self.assert_string(value, state) if self.strip and (isinstance(value, str) or isinstance(value, unicode)): value = value.strip() if not self.regex.search(value): raise Invalid(self.message('invalid', state), value, state) def _to_python(self, value, state): if self.strip and \ (isinstance(value, str) or isinstance(value, unicode)): return value.strip() return value class PlainText(Regex): """ Test that the field contains only letters, numbers, underscore, and the hyphen. Subclasses Regex. Examples:: >>> PlainText.to_python('_this9_') '_this9_' >>> PlainText.from_python(' this ') ' this ' >>> PlainText(accept_python=False).from_python(' this ') Traceback (most recent call last): ... Invalid: Enter only letters, numbers, or _ (underscore) >>> PlainText(strip=True).to_python(' this ') 'this' >>> PlainText(strip=True).from_python(' this ') 'this' """ regex = r"^[a-zA-Z_\-0-9]*$" messages = { 'invalid': 'Enter only letters, numbers, or _ (underscore)', } class OneOf(FancyValidator): """ Tests that the value is one of the members of a given list. If ``testValueLists=True``, then if the input value is a list or tuple, all the members of the sequence will be checked (i.e., the input must be a subset of the allowed values). Use ``hideList=True`` to keep the list of valid values out of the error message in exceptions. Examples:: >>> oneof = OneOf([1, 2, 3]) >>> oneof.to_python(1) 1 >>> oneof.to_python(4) Traceback (most recent call last): ... Invalid: Value must be one of: 1; 2; 3 (not 4) >>> oneof(testValueList=True).to_python([2, 3, [1, 2, 3]]) [2, 3, [1, 2, 3]] >>> oneof.to_python([2, 3, [1, 2, 3]]) Traceback (most recent call last): ... Invalid: Value must be one of: 1; 2; 3 (not [2, 3, [1, 2, 3]]) """ list = None testValueList = False hideList = False __unpackargs__ = ('list',) messages = { 'invalid': "Invalid value", 'notIn': "Value must be one of: %(items)s (not %(value)r)", } def validate_python(self, value, state): if self.testValueList and isinstance(value, (list, tuple)): for v in value: self.validate_python(v, state) else: if not value in self.list: if self.hideList: raise Invalid(self.message('invalid', state), value, state) else: items = '; '.join(map(str, self.list)) raise Invalid(self.message('notIn', state, items=items, value=value), value, state) class DictConverter(FancyValidator): """ Converts values based on a dictionary which has values as keys for the resultant values. If ``allowNull`` is passed, it will not balk if a false value (e.g., '' or None) is given (it will return None in these cases). to_python takes keys and gives values, from_python takes values and gives keys. If you give hideDict=True, then the contents of the dictionary will not show up in error messages. Examples:: >>> dc = DictConverter({1: 'one', 2: 'two'}) >>> dc.to_python(1) 'one' >>> dc.from_python('one') 1 >>> dc.to_python(3) Traceback (most recent call last): Invalid: Enter a value from: 1; 2 >>> dc2 = dc(hideDict=True) >>> dc2.hideDict True >>> dc2.dict {1: 'one', 2: 'two'} >>> dc2.to_python(3) Traceback (most recent call last): Invalid: Choose something >>> dc.from_python('three') Traceback (most recent call last): Invalid: Nothing in my dictionary goes by the value 'three'. Choose one of: 'one'; 'two' """ dict = None hideDict = False __unpackargs__ = ('dict',) messages = { 'keyNotFound': "Choose something", 'chooseKey': "Enter a value from: %(items)s", 'valueNotFound': "That value is not known", 'chooseValue': "Nothing in my dictionary goes by the value %(value)s. Choose one of: %(items)s", } def _to_python(self, value, state): try: return self.dict[value] except KeyError: if self.hideDict: raise Invalid(self.message('keyNotFound', state), value, state) else: items = '; '.join(map(repr, self.dict.keys())) raise Invalid(self.message('chooseKey', state, items=items), value, state) def _from_python(self, value, state): for k, v in self.dict.items(): if value == v: return k if self.hideDict: raise Invalid(self.message('valueNotFound', state), value, state) else: items = '; '.join(map(repr, self.dict.values())) raise Invalid(self.message('chooseValue', state, value=repr(value), items=items), value, state) class IndexListConverter(FancyValidator): """ Converts a index (which may be a string like '2') to the value in the given list. Examples:: >>> index = IndexListConverter(['zero', 'one', 'two']) >>> index.to_python(0) 'zero' >>> index.from_python('zero') 0 >>> index.to_python('1') 'one' >>> index.to_python(5) Traceback (most recent call last): Invalid: Index out of range >>> index(not_empty=True).to_python(None) Traceback (most recent call last): Invalid: Please enter a value >>> index.from_python('five') Traceback (most recent call last): Invalid: Item 'five' was not found in the list """ list = None __unpackargs__ = ('list',) messages = { 'integer': "Must be an integer index", 'outOfRange': "Index out of range", 'notFound': "Item %(value)s was not found in the list", } def _to_python(self, value, state): try: value = int(value) except (ValueError, TypeError): raise Invalid(self.message('integer', state), value, state) try: return self.list[value] except IndexError: raise Invalid(self.message('outOfRange', state), value, state) def _from_python(self, value, state): for i in range(len(self.list)): if self.list[i] == value: return i raise Invalid(self.message('notFound', state, value=repr(value)), value, state) class DateValidator(FancyValidator): """ Validates that a date is within the given range. Be sure to call DateConverter first if you aren't expecting mxDateTime input. ``earliest_date`` and ``latest_date`` may be functions; if so, they will be called each time before validating. ``after_now`` means a time after the current timestamp; note that just a few milliseconds before now is invalid! ``today_or_after`` is more permissive, and ignores hours and minutes. Examples:: >>> from datetime import datetime, timedelta >>> d = DateValidator(earliest_date=datetime(2003, 1, 1)) >>> d.to_python(datetime(2004, 1, 1)) datetime.datetime(2004, 1, 1, 0, 0) >>> d.to_python(datetime(2002, 1, 1)) Traceback (most recent call last): ... Invalid: Date must be after Wednesday, 01 January 2003 >>> d.to_python(datetime(2003, 1, 1)) datetime.datetime(2003, 1, 1, 0, 0) >>> d = DateValidator(after_now=True) >>> now = datetime.now() >>> d.to_python(now+timedelta(seconds=5)) == now+timedelta(seconds=5) True >>> d.to_python(now-timedelta(days=1)) Traceback (most recent call last): ... Invalid: The date must be sometime in the future >>> d.to_python(now+timedelta(days=1)) > now True >>> d = DateValidator(today_or_after=True) >>> d.to_python(now) == now True """ earliest_date = None latest_date = None after_now = False # Like after_now, but just after this morning: today_or_after = False # Use 'datetime' to force the Python 2.3+ datetime module, or # 'mxDateTime' to force the mxDateTime module (None means use # datetime, or if not present mxDateTime) datetime_module = None messages = { 'after': "Date must be after %(date)s", 'before': "Date must be before %(date)s", # Double %'s, because this will be substituted twice: 'date_format': "%%A, %%d %%B %%Y", 'future': "The date must be sometime in the future", } def validate_python(self, value, state): if self.earliest_date: if callable(self.earliest_date): earliest_date = self.earliest_date() else: earliest_date = self.earliest_date if value < earliest_date: date_formatted = earliest_date.strftime( self.message('date_format', state)) raise Invalid( self.message('after', state, date=date_formatted), value, state) if self.latest_date: if callable(self.latest_date): latest_date = self.latest_date() else: latest_date = self.latest_date if value > latest_date: date_formatted = latest_date.strftime( self.message('date_format', state)) raise Invalid( self.message('before', state, date=date_formatted), value, state) if self.after_now: dt_mod = import_datetime(self.datetime_module) now = datetime_now(dt_mod) if value < now: date_formatted = now.strftime( self.message('date_format', state)) raise Invalid( self.message('future', state, date=date_formatted), value, state) if self.today_or_after: dt_mod = import_datetime(self.datetime_module) now = datetime_now(dt_mod) today = datetime_makedate(dt_mod, now.year, now.month, now.day) value_as_date = datetime_makedate( dt_mod, value.year, value.month, value.day) if value_as_date < today: date_formatted = now.strftime( self.message('date_format', state)) raise Invalid( self.message('future', state, date=date_formatted), value, state) class Bool(FancyValidator): """ Always Valid, returns True or False based on the value and the existance of the value. If you want to convert strings like ``'true'`` to booleans, then use ``StringBoolean``. Examples:: >>> Bool.to_python(0) False >>> Bool.to_python(1) True >>> Bool.to_python('') False >>> Bool.to_python(None) False """ if_missing = False def _to_python(self, value, state): return bool(value) _from_python = _to_python def empty_value(self, value): return False class Int(FancyValidator): """ Convert a value to an integer. Example:: >>> Int.to_python('10') 10 >>> Int.to_python('ten') Traceback (most recent call last): ... Invalid: Please enter an integer value """ messages = { 'integer': "Please enter an integer value", } def _to_python(self, value, state): try: return int(value) except (ValueError, TypeError): raise Invalid(self.message('integer', state), value, state) _from_python = _to_python class Number(FancyValidator): """ Convert a value to a float or integer. Tries to convert it to an integer if no information is lost. :: >>> Number.to_python('10') 10 >>> Number.to_python('10.5') 10.5 >>> Number.to_python('ten') Traceback (most recent call last): ... Invalid: Please enter a number """ messages = { 'number': "Please enter a number", } def _to_python(self, value, state): try: value = float(value) if value == int(value): return int(value) return value except ValueError: raise Invalid(self.message('number', state), value, state) class String(FancyValidator): """ Converts things to string, but treats empty things as the empty string. Also takes a `max` and `min` argument, and the string length must fall in that range. :: >>> String(min=2).to_python('a') Traceback (most recent call last): ... Invalid: Enter a value 2 characters long or more >>> String(max=10).to_python('xxxxxxxxxxx') Traceback (most recent call last): ... Invalid: Enter a value less than 10 characters long >>> String().from_python(None) '' >>> String().from_python([]) '' >>> String().to_python(None) '' >>> String(min=3).to_python(None) Traceback (most recent call last): ... Invalid: Please enter a value >>> String(min=1).to_python('') Traceback (most recent call last): ... Invalid: Please enter a value """ min = None max = None not_empty = None messages = { 'tooLong': "Enter a value less than %(max)i characters long", 'tooShort': "Enter a value %(min)i characters long or more", } def __initargs__(self, new_attrs): if self.not_empty is None and self.min: self.not_empty = True def validate_python(self, value, state): if (self.max is not None and value is not None and len(value) > self.max): raise Invalid(self.message('tooLong', state, max=self.max), value, state) if (self.min is not None and (not value or len(value) < self.min)): raise Invalid(self.message('tooShort', state, min=self.min), value, state) def _from_python(self, value, state): if value: return str(value) if value == 0: return str(value) return "" def empty_value(self, value): return '' class UnicodeString(String): """ Converts things to unicode string, this is a specialization of the String class. In addition to the String arguments, an encoding argument is also accepted. By default the encoding will be utf-8. All converted strings are returned as Unicode strings. :: >>> UnicodeString().to_python(None) '' >>> UnicodeString().to_python([]) u'' >>> UnicodeString(encoding='utf-7').to_python('Ni Ni Ni') u'Ni Ni Ni' """ encoding = 'utf-8' messages = { 'badEncoding' : "Invalid data or incorrect encoding", } def __init__(self, inputEncoding=None, outputEncoding=None, **kw): String.__init__(self, **kw) self.inputEncoding = inputEncoding or self.encoding self.outputEncoding = outputEncoding or self.encoding def _to_python(self, value, state): if value: if isinstance(value, unicode): return value if hasattr(value, '__unicode__'): return unicode(value) try: return unicode(value, self.inputEncoding) except UnicodeDecodeError: raise Invalid(self.message('badEncoding', state), value, state) return u'' def _from_python(self, value, state): if hasattr(value, '__unicode__'): value = unicode(value) if isinstance(value, unicode): return value.encode(self.outputEncoding) return str(value) class Set(FancyValidator): """ This is for when you think you may return multiple values for a certain field. This way the result will always be a list, even if there's only one result. It's equivalent to ForEach(convertToList=True). If you give ``use_set=True``, then it will return an actual ``sets.Set`` object. :: >>> Set.to_python(None) [] >>> Set.to_python('this') ['this'] >>> Set.to_python(('this', 'that')) ['this', 'that'] >>> s = Set(use_set=True) >>> s.to_python(None) Set([]) >>> s.to_python('this') Set(['this']) >>> s.to_python(('this',)) Set(['this']) """ use_set = False def _to_python(self, value, state): if self.use_set: if isinstance(value, sets.Set): return value elif isinstance(value, (list, tuple)): return sets.Set(value) elif value is None: return sets.Set() else: return sets.Set([value]) else: if isinstance(value, list): return value elif sets and isinstance(value, sets.Set): return list(value) elif isinstance(value, tuple): return list(value) elif value is None: return [] else: return [value] def empty_value(self, value): if self.use_set: return sets.Set([]) else: return [] class Email(FancyValidator): r""" Validate an email address. If you pass ``resolve_domain=True``, then it will try to resolve the domain name to make sure it's valid. This takes longer, of course. You must have the `pyDNS `_ modules installed to look up MX records. :: >>> e = Email() >>> e.to_python(' test@foo.com ') 'test@foo.com' >>> e.to_python('test') Traceback (most recent call last): ... Invalid: An email address must contain a single @ >>> e.to_python('test@foobar.com.5') Traceback (most recent call last): ... Invalid: The domain portion of the email address is invalid (the portion after the @: foobar.com.5) >>> e.to_python('o*reilly@test.com') 'o*reilly@test.com' >>> e = Email(resolve_domain=True) >>> e.resolve_domain True >>> e.to_python('doesnotexist@colorstudy.com') 'doesnotexist@colorstudy.com' >>> e.to_python('test@thisdomaindoesnotexistithinkforsure.com') Traceback (most recent call last): ... Invalid: The domain of the email address does not exist (the portion after the @: thisdomaindoesnotexistithinkforsure.com) """ resolve_domain = False usernameRE = re.compile(r"^[^ \t\n\r@<>()]+$", re.I) domainRE = re.compile(r"^[a-z0-9][a-z0-9\.\-_]*\.[a-z]+$", re.I) messages = { 'empty': 'Please enter an email address', 'noAt': 'An email address must contain a single @', 'badUsername': 'The username portion of the email address is invalid (the portion before the @: %(username)s)', 'socketError': 'An error occured when trying to connect to the server: %(error)s', 'badDomain': 'The domain portion of the email address is invalid (the portion after the @: %(domain)s)', 'domainDoesNotExist': 'The domain of the email address does not exist (the portion after the @: %(domain)s)', } def __init__(self, *args, **kw): global mxlookup FancyValidator.__init__(self, *args, **kw) if self.resolve_domain: if mxlookup is None: try: import DNS.Base DNS.Base.ParseResolvConf() from DNS.lazy import mxlookup except ImportError: import warnings warnings.warn( "pyDNS is not installed on " "your system (or the DNS package cannot be found). " "I cannot resolve domain names in addresses") raise def validate_python(self, value, state): if not value: raise Invalid( self.message('empty', state), value, state) value = value.strip() splitted = value.split('@', 1) if not len(splitted) == 2: raise Invalid( self.message('noAt', state), value, state) if not self.usernameRE.search(splitted[0]): raise Invalid( self.message('badUsername', state, username=splitted[0]), value, state) if not self.domainRE.search(splitted[1]): raise Invalid( self.message('badDomain', state, domain=splitted[1]), value, state) if self.resolve_domain: global socket, DNSError if socket is None: import socket if DNSError is None: from DNS.Base import DNSError try: domains = mxlookup(splitted[1]) except (socket.error, DNSError), e: raise Invalid( self.message('socketError', state, error=e), value, state) if not domains: raise Invalid( self.message('domainDoesNotExist', state, domain=splitted[1]), value, state) def _to_python(self, value, state): return value.strip() class URL(FancyValidator): """ Validate a URL, either http://... or https://. If check_exists is true, then we'll actually make a request for the page. If add_http is true, then if no scheme is present we'll add http:// :: >>> u = URL(add_http=True) >>> u.to_python('foo.com') 'http://foo.com' >>> u.to_python('http://hahaha/bar.html') Traceback (most recent call last): ... Invalid: That is not a valid URL >>> u.to_python('https://test.com') 'https://test.com' >>> u = URL(add_http=False, check_exists=True) >>> u.to_python('http://google.com') 'http://google.com' >>> u.to_python('http://colorstudy.com/doesnotexist.html') Traceback (most recent call last): ... Invalid: The server responded that the page could not be found >>> u.to_python('http://this.domain.does.not.exists.formencode.org/test.html') Traceback (most recent call last): ... Invalid: An error occured when trying to connect to the server: ... """ check_exists = False add_http = True url_re = re.compile(r'^(http|https)://' r'[a-z0-9][a-z0-9\-\._]*\.[a-z]+' r'(?:[0-9]+)?' r'(?:/.*)?$', re.I) scheme_re = re.compile(r'^[a-zA-Z]+:') messages = { 'noScheme': 'You must start your URL with http://, https://, etc', 'badURL': 'That is not a valid URL', 'httpError': 'An error occurred when trying to access the URL: %(error)s', 'socketError': 'An error occured when trying to connect to the server: %(error)s', 'notFound': 'The server responded that the page could not be found', 'status': 'The server responded with a bad status code (%(status)s)', } def _to_python(self, value, state): value = value.strip() if self.add_http: if not self.scheme_re.search(value): value = 'http://' + value match = self.scheme_re.search(value) if not match: raise Invalid( self.message('noScheme', state), value, state) value = match.group(0).lower() + value[len(match.group(0)):] if not self.url_re.search(value): raise Invalid( self.message('badURL', state), value, state) if self.check_exists and (value.startswith('http://') or value.startswith('https://')): self._check_url_exists(value, state) return value def _check_url_exists(self, url, state): global httplib, urlparse, socket if httplib is None: import httplib if urlparse is None: import urlparse if socket is None: import socket scheme, netloc, path, params, query, fragment = urlparse.urlparse( url, 'http') if scheme == 'http': ConnClass = httplib.HTTPConnection else: ConnClass = httplib.HTTPSConnection try: conn = ConnClass(netloc) if params: path += ';' + params if query: path += '?' + query conn.request('HEAD', path) res = conn.getresponse() except httplib.HTTPException, e: raise Invalid( self.message('httpError', state, error=e), state, url) except socket.error, e: raise Invalid( self.message('socketError', state, error=e), state, url) else: if res.status == 404: raise Invalid( self.message('notFound', state), state, url) if (res.status < 200 or res.status >= 500): raise Invalid( self.message('status', state, status=res.status), state, url) class StateProvince(FancyValidator): """ Valid state or province code (two-letter). Well, for now I don't know the province codes, but it does state codes. Give your own `states` list to validate other state-like codes; give `extra_states` to add values without losing the current state values. :: >>> s = StateProvince('XX') >>> s.to_python('IL') 'IL' >>> s.to_python('XX') 'XX' >>> s.to_python('xx') 'XX' >>> s.to_python('YY') Traceback (most recent call last): ... Invalid: That is not a valid state code """ states = ['AK', 'AL', 'AR', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'GA', 'HI', 'IA', 'ID', 'IN', 'IL', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MI', 'MN', 'MO', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VT', 'WA', 'WI', 'WV', 'WY'] extra_states = [] __unpackargs__ = ('extra_states',) messages = { 'empty': 'Please enter a state code', 'wrongLength': 'Please enter a state code with TWO letters', 'invalid': 'That is not a valid state code', } def validate_python(self, value, state): value = str(value).strip().upper() if not value: raise Invalid( self.message('empty', state), value, state) if not value or len(value) != 2: raise Invalid( self.message('wrongLength', state), value, state) if value not in self.states \ and not (self.extra_states and value in self.extra_states): raise Invalid( self.message('invalid', state), value, state) def _to_python(self, value, state): return str(value).strip().upper() class PhoneNumber(FancyValidator): """ Validates, and converts to ###-###-####, optionally with extension (as ext.##...) @@: should add international phone number support :: >>> p = PhoneNumber() >>> p.to_python('333-3333') Traceback (most recent call last): ... Invalid: Please enter a number, with area code, in the form ###-###-####, optionally with "ext.####" >>> p.to_python('555-555-5555') '555-555-5555' >>> p.to_python('1-393-555-3939') '1-393-555-3939' >>> p.to_python('321.555.4949') '321.555.4949' >>> p.to_python('3335550000') '3335550000' """ # for emacs: " _phoneRE = re.compile(r'^\s*(?:1-)?(\d\d\d)[\- \.]?(\d\d\d)[\- \.]?(\d\d\d\d)(?:\s*ext\.?\s*(\d+))?\s*$', re.I) messages = { 'phoneFormat': 'Please enter a number, with area code, in the form ###-###-####, optionally with "ext.####"', } def _to_python(self, value, state): self.assert_string(value, state) match = self._phoneRE.search(value) if not match: raise Invalid( self.message('phoneFormat', state), value, state) return value def _from_python(self, value, state): self.assert_string(value, state) match = self._phoneRE.search(value) if not match: raise Invalid(self.message('phoneFormat', state), value, state) result = '%s-%s-%s' % (match.group(1), match.group(2), match.group(3)) if match.group(4): result = result + " ext.%s" % match.group(4) return result class FieldStorageUploadConverter(FancyValidator): """ Converts a cgi.FieldStorage instance to a value that FormEncode can use for file uploads. """ def _to_python(self, value, state=None): if isinstance(value, cgi.FieldStorage): if value.filename: return value raise Invalid('invalid', value, state) else: return value class FileUploadKeeper(FancyValidator): """ Takes two inputs (a dictionary with keys ``static`` and ``upload``) and converts them into one value on the Python side (a dictionary with ``filename`` and ``content`` keys). The upload takes priority over the static value. The filename may be None if it can't be discovered. Handles uploads of both text and ``cgi.FieldStorage`` upload values. This is basically for use when you have an upload field, and you want to keep the upload around even if the rest of the form submission fails. When converting *back* to the form submission, there may be extra values ``'original_filename'`` and ``'original_content'``, which may want to use in your form to show the user you still have their content around. """ upload_key = 'upload' static_key = 'static' def _to_python(self, value, state): upload = value.get(self.upload_key) static = value.get(self.static_key, '').strip() filename = content = None if isinstance(upload, cgi.FieldStorage): filename = upload.filename content = upload.value elif isinstance(upload, str) and upload: filename = None content = upload if not content and static: filename, content = static.split(None, 1) if filename == '-': filename = '' else: filename = filename.decode('base64') content = content.decode('base64') return {'filename': filename, 'content': content} def _from_python(self, value, state): filename = value.get('filename', '') content = value.get('content', '') if filename or content: result = self.pack_content(filename, content) return {self.upload_key: '', self.static_key: result, 'original_filename': filename, 'original_content': content} else: return {self.upload_key: '', self.static_key: ''} def pack_content(self, filename, content): enc_filename = self.base64encode(filename) or '-' enc_content = (content or '').encode('base64') result = '%s %s' % (enc_filename, enc_content) return result class DateConverter(FancyValidator): """ Validates and converts a textual date, like mm/yy, dd/mm/yy, dd-mm-yy, etc, always assumes month comes second value is the month. Accepts English month names, also abbreviated. Returns value as a datetime object (you can get mx.DateTime objects if you use ``datetime_module='mxDateTime'``). Two year dates are assumed to be within 1950-2020, with dates from 21-49 being ambiguous and signaling an error. Use accept_day=False if you just want a month/year (like for a credit card expiration date). :: >>> d = DateConverter() >>> d.to_python('12/3/09') datetime.date(2009, 12, 3) >>> d.to_python('12/3/2009') datetime.date(2009, 12, 3) >>> d.to_python('2/30/04') Traceback (most recent call last): ... Invalid: That month only has 29 days >>> d.to_python('13/2/05') Traceback (most recent call last): ... Invalid: Please enter a month from 1 to 12 """ ## @@: accepts only US-style dates accept_day = True # also allowed: 'dd/mm/yyyy' month_style = 'mm/dd/yyyy' # Use 'datetime' to force the Python 2.3+ datetime module, or # 'mxDateTime' to force the mxDateTime module (None means use # datetime, or if not present mxDateTime) datetime_module = None _day_date_re = re.compile(r'^\s*(\d\d?)[\-\./\\](\d\d?|jan|january|feb|febuary|mar|march|apr|april|may|jun|june|jul|july|aug|august|sep|sept|september|oct|october|nov|november|dec|december)[\-\./\\](\d\d\d?\d?)\s*$', re.I) _month_date_re = re.compile(r'^\s*(\d\d?|jan|january|feb|febuary|mar|march|apr|april|may|jun|june|jul|july|aug|august|sep|sept|september|oct|october|nov|november|dec|december)[\-\./\\](\d\d\d?\d?)\s*$', re.I) _month_names = { 'jan': 1, 'january': 1, 'feb': 2, 'febuary': 2, 'mar': 3, 'march': 3, 'apr': 4, 'april': 4, 'may': 5, 'jun': 6, 'june': 6, 'jul': 7, 'july': 7, 'aug': 8, 'august': 8, 'sep': 9, 'sept': 9, 'september': 9, 'oct': 10, 'october': 10, 'nov': 11, 'november': 11, 'dec': 12, 'december': 12, } ## @@: Feb. should be leap-year aware (but mxDateTime does catch that) _monthDays = { 1: 31, 2: 29, 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31} messages = { 'badFormat': 'Please enter the date in the form %(format)s', 'monthRange': 'Please enter a month from 1 to 12', 'invalidDay': 'Please enter a valid day', 'dayRange': 'That month only has %(days)i days', 'invalidDate': 'That is not a valid day (%(exception)s)', 'unknownMonthName': "Unknown month name: %(month)s", 'invalidYear': 'Please enter a number for the year', 'fourDigitYear': 'Please enter a four-digit year', 'wrongFormat': 'Please enter the date in the form %(format)s', } def _to_python(self, value, state): if self.accept_day: return self.convert_day(value, state) else: return self.convert_month(value, state) def convert_day(self, value, state): self.assert_string(value, state) match = self._day_date_re.search(value) if not match: raise Invalid(self.message('badFormat', state, format=self.month_style), value, state) day = int(match.group(1)) try: month = int(match.group(2)) except TypeError: month = self.make_month(match.group(2), state) else: if self.month_style == 'mm/dd/yyyy': month, day = day, month year = self.make_year(match.group(3), state) if month > 12 or month < 1: raise Invalid(self.message('monthRange', state), value, state) if day < 1: raise Invalid(self.message('invalidDay', state), value, state) if self._monthDays[month] < day: raise Invalid(self.message('dayRange', state, days=self._monthDays[month]), value, state) dt_mod = import_datetime(self.datetime_module) try: return datetime_makedate(dt_mod, year, month, day) except ValueError, v: raise Invalid(self.message('invalidDate', state, exception=str(v)), value, state) def make_month(self, value, state): try: return int(value) except ValueError: value = value.lower().strip() if self._month_names.has_key(value): return self._month_names[value] else: raise Invalid(self.message('unknownMonthName', state, month=value), value, state) def make_year(self, year, state): try: year = int(year) except ValueError: raise Invalid(self.message('invalidYear', state), year, state) if year <= 20: year = year + 2000 if year >= 50 and year < 100: year = year + 1900 if year > 20 and year < 50: raise Invalid(self.message('fourDigitYear', state), year, state) return year def convert_month(self, value, state): match = self._month_date_re.search(value) if not match: raise Invalid(self.message('wrongFormat', state, format='mm/yyyy'), value, state) month = self.make_month(match.group(1), state) year = self.make_year(match.group(2), state) if month > 12 or month < 1: raise Invalid(self.message('monthRange', state), value, state) dt_mod = import_datetime(self.datetime_module) return datetime_makedate(dt_mod, year, month, 1) def _from_python(self, value, state): if self.if_empty is not NoDefault and not value: return '' if self.accept_day: return self.unconvert_day(value, state) else: return self.unconvert_month(value, state) def unconvert_day(self, value, state): # @@ ib: double-check, improve return value.strftime("%m/%d/%Y") def unconvert_month(self, value, state): # @@ ib: double-check, improve return value.strftime("%m/%Y") class TimeConverter(FancyValidator): """ Converts times in the format HH:MM:SSampm to (h, m, s). Seconds are optional. For ampm, set use_ampm = True. For seconds, use_seconds = True. Use 'optional' for either of these to make them optional. Examples:: >>> tim = TimeConverter() >>> tim.to_python('8:30') (8, 30) >>> tim.to_python('20:30') (20, 30) >>> tim.to_python('30:00') Traceback (most recent call last): ... Invalid: You must enter an hour in the range 0-23 >>> tim.to_python('13:00pm') Traceback (most recent call last): ... Invalid: You must enter an hour in the range 1-12 >>> tim.to_python('12:-1') Traceback (most recent call last): ... Invalid: You must enter a minute in the range 0-59 >>> tim.to_python('12:02pm') (12, 2) >>> tim.to_python('12:02am') (0, 2) >>> tim.to_python('1:00PM') (13, 0) >>> tim.from_python((13, 0)) '13:00:00' >>> tim2 = tim(use_ampm=True, use_seconds=False) >>> tim2.from_python((13, 0)) '1:00pm' >>> tim2.from_python((0, 0)) '12:00am' >>> tim2.from_python((12, 0)) '12:00pm' Examples with ``datetime.time``:: >>> v = TimeConverter(use_datetime=True) >>> a = v.to_python('18:00') >>> a datetime.time(18, 0) >>> b = v.to_python('30:00') Traceback (most recent call last): ... Invalid: You must enter an hour in the range 0-23 >>> v2 = TimeConverter(prefer_ampm=True, use_datetime=True) >>> v2.from_python(a) '6:00:00pm' >>> v3 = TimeConverter(prefer_ampm=True, ... use_seconds=False, use_datetime=True) >>> a = v3.to_python('18:00') >>> a datetime.time(18, 0) >>> v3.from_python(a) '6:00pm' >>> a = v3.to_python('18:00:00') Traceback (most recent call last): ... Invalid: You may not enter seconds """ use_ampm = 'optional' prefer_ampm = False use_seconds = 'optional' use_datetime = False # This can be set to make it prefer mxDateTime: datetime_module = None messages = { 'noAMPM': 'You must indicate AM or PM', 'tooManyColon': 'There are too many :\'s', 'noSeconds': 'You may not enter seconds', 'secondsRequired': 'You must enter seconds', 'minutesRequired': 'You must enter minutes (after a :)', 'badNumber': 'The %(part)s value you gave is not a number: %(number)r', 'badHour': 'You must enter an hour in the range %(range)s', 'badMinute': 'You must enter a minute in the range 0-59', 'badSecond': 'You must enter a second in the range 0-59', } def _to_python(self, value, state): result = self._to_python_tuple(value, state) if self.use_datetime: dt_mod = import_datetime(self.datetime_module) time_class = datetime_time(dt_mod) return time_class(*result) else: return result def _to_python_tuple(self, value, state): time = value.strip() explicit_ampm = False if self.use_ampm: last_two = time[-2:].lower() if last_two not in ('am', 'pm'): if self.use_ampm != 'optional': raise Invalid( self.message('noAMPM', state), value, state) else: offset = 0 else: explicit_ampm = True if last_two == 'pm': offset = 12 else: offset = 0 time = time[:-2] else: offset = 0 parts = time.split(':') if len(parts) > 3: raise Invalid( self.message('tooManyColon', state), value, state) if len(parts) == 3 and not self.use_seconds: raise Invalid( self.message('noSeconds', state), value, state) if (len(parts) == 2 and self.use_seconds and self.use_seconds != 'optional'): raise Invalid( self.message('secondsRequired', state), value, state) if len(parts) == 1: raise Invalid( self.message('minutesRequired', state), value, state) try: hour = int(parts[0]) except ValueError: raise Invalid( self.message('badNumber', state, number=parts[0], part='hour'), value, state) if explicit_ampm: if hour > 12 or hour < 1: raise Invalid( self.message('badHour', state, number=hour, range='1-12'), value, state) if hour == 12 and offset == 12: # 12pm == 12 pass elif hour == 12 and offset == 0: # 12am == 0 hour = 0 else: hour += offset else: if hour > 23 or hour < 0: raise Invalid( self.message('badHour', state, number=hour, range='0-23'), value, state) try: minute = int(parts[1]) except ValueError: raise Invalid( self.message('badNumber', state, number=parts[1], part='minute'), value, state) if minute > 59 or minute < 0: raise Invalid( self.message('badMinute', state, number=minute), value, state) if len(parts) == 3: try: second = int(parts[2]) except ValueError: raise Invalid( self.message('badNumber', state, number=parts[2], part='second')) if second > 59 or second < 0: raise Invalid( self.message('badSecond', state, number=second), value, state) else: second = None if second is None: return (hour, minute) else: return (hour, minute, second) def _from_python(self, value, state): if isinstance(value, (str, unicode)): return value if hasattr(value, 'hour'): hour, minute = value.hour, value.minute second = value.second elif len(value) == 3: hour, minute, second = value elif len(value) == 2: hour, minute = value second = 0 ampm = '' if ((self.use_ampm == 'optional' and self.prefer_ampm) or (self.use_ampm and self.use_ampm != 'optional')): ampm = 'am' if hour > 12: hour -= 12 ampm = 'pm' elif hour == 12: ampm = 'pm' elif hour == 0: hour = 12 if self.use_seconds: return '%i:%02i:%02i%s' % (hour, minute, second, ampm) else: return '%i:%02i%s' % (hour, minute, ampm) class PostalCode(Regex): """ US Postal codes (aka Zip Codes). :: >>> PostalCode.to_python('55555') '55555' >>> PostalCode.to_python('55555-5555') '55555-5555' >>> PostalCode.to_python('5555') Traceback (most recent call last): ... Invalid: Please enter a zip code (5 digits) """ regex = r'^\d\d\d\d\d(?:-\d\d\d\d)?$' strip = True messages = { 'invalid': 'Please enter a zip code (5 digits)', } class StripField(FancyValidator): """ Take a field from a dictionary, removing the key from the dictionary. ``name`` is the key. The field value and a new copy of the dictionary with that field removed are returned. >>> StripField('test').to_python({'a': 1, 'test': 2}) (2, {'a': 1}) >>> StripField('test').to_python({}) Traceback (most recent call last): ... Invalid: The name 'test' is missing """ __unpackargs__ = ('name',) messages = { 'missing': 'The name %(name)s is missing', } def _to_python(self, valueDict, state): v = valueDict.copy() try: field = v[self.name] del v[self.name] except KeyError: raise Invalid(self.message('missing', state, name=repr(self.name)), valueDict, state) return field, v class StringBool(FancyValidator): # Originally from TurboGears """ Converts a string to a boolean. Values like 'true' and 'false' are considered True and False, respectively; anything in ``true_values`` is true, anything in ``false_values`` is false, case-insensitive). The first item of those lists is considered the preferred form. :: >>> s = StringBoolean() >>> s.to_python('yes'), s.to_python('no') (True, False) >>> s.to_python(1), s.to_python('N') (True, False) >>> s.to_python('ye') Traceback (most recent call last): ... Invalid: Value should be 'true' or 'false' """ true_values = ['true', 't', 'yes', 'y', 'on', '1'] false_values = ['false', 'f', 'no', 'n', 'off', '0'] messages = { "string" : "Value should be %(true)r or %(false)r" } def _to_python(self, value, state): if isinstance(value, (str, unicode)): value = value.strip().lower() if value in self.true_values: return True if not value or value in self.false_values: return False raise Invalid(self.message("string", state, true=self.true_values[0], false=self.false_values[0]), value, state) return bool(value) def _from_python(self, value, state): if value: return self.true_values[0] else: return self.false_values[0] # Should deprecate: StringBoolean = StringBool class SignedString(FancyValidator): """ Encodes a string into a signed string, and base64 encodes both the signature string and a random nonce. It is up to you to provide a secret, and to keep the secret handy and consistent. """ messages = { 'malformed': 'Value does not contain a signature', 'badsig': 'Signature is not correct', } secret = None nonce_length = 4 def _to_python(self, value, state): global sha if not sha: import sha assert self.secret is not None, ( "You must give a secret") parts = value.split(None, 1) if not parts or len(parts) == 1: raise Invalid(self.message('malformed', state), value, state) sig, rest = parts sig = sig.decode('base64') rest = rest.decode('base64') nonce = rest[:self.nonce_length] rest = rest[self.nonce_length:] expected = sha.new(str(self.secret)+nonce+rest).digest() if expected != sig: raise Invalid(self.message('badsig', state), value, state) return rest def _from_python(self, value, state): global sha if not sha: import sha nonce = self.make_nonce() value = str(value) digest = sha.new(self.secret+nonce+value).digest() return self.encode(digest)+' '+self.encode(nonce+value) def encode(self, value): return value.encode('base64').strip().replace('\n', '') def make_nonce(self): global random if not random: import random return ''.join([ chr(random.randrange(256)) for i in range(self.nonce_length)]) class FormValidator(FancyValidator): """ A FormValidator is something that can be chained with a Schema. Unlike normal chaining the FormValidator can validate forms that aren't entirely valid. The important method is .validate(), of course. It gets passed a dictionary of the (processed) values from the form. If you have .validate_partial_form set to True, then it will get the incomplete values as well -- use .has_key() to test if the field was able to process any particular field. Anyway, .validate() should return a string or a dictionary. If a string, it's an error message that applies to the whole form. If not, then it should be a dictionary of fieldName: errorMessage. The special key "form" is the error message for the form as a whole (i.e., a string is equivalent to {"form": string}). Return None on no errors. """ validate_partial_form = False validate_partial_python = None validate_partial_other = None class RequireIfMissing(FormValidator): # Field that potentially is required: required = None # If this field is missing, then it is required: missing = None # If this field is present, then it is required: present = None __unpackargs__ = ('required',) def _to_python(self, value_dict, state): is_required = False if self.missing and not value_dict.get(self.missing): is_required = True if self.present and value_dict.get(self.present): is_required = True if is_required and not value_dict.get(self.required): raise Invalid('You must give a value for %s' % self.required, value, state, error_dict={self.required: Invalid(self.message( 'empty', state), value, state)}) return value_dict RequireIfPresent = RequireIfMissing class FieldsMatch(FormValidator): """ Tests that the given fields match, i.e., are identical. Useful for password+confirmation fields. Pass the list of field names in as `field_names`. :: >>> f = FieldsMatch('pass', 'conf') >>> f.to_python({'pass': 'xx', 'conf': 'xx'}) {'conf': 'xx', 'pass': 'xx'} >>> f.to_python({'pass': 'xx', 'conf': 'yy'}) Traceback (most recent call last): ... Invalid: conf: Fields do not match """ show_match = False field_names = None validate_partial_form = True __unpackargs__ = ('*', 'field_names') messages = { 'invalid': "Fields do not match (should be %(match)s)", 'invalidNoMatch': "Fields do not match", } def validate_partial(self, field_dict, state): for name in self.field_names: if not field_dict.has_key(name): return self.validate_python(field_dict, state) def validate_python(self, field_dict, state): ref = field_dict[self.field_names[0]] errors = {} for name in self.field_names[1:]: if field_dict.get(name, '') != ref: if self.show_match: errors[name] = self.message('invalid', state, match=ref) else: errors[name] = self.message('invalidNoMatch', state) if errors: error_list = errors.items() error_list.sort() error_message = '
\n'.join( ['%s: %s' % (name, value) for name, value in error_list]) raise Invalid(error_message, field_dict, state, error_dict=errors) class CreditCardValidator(FormValidator): """ Checks that credit card numbers are valid (if not real). You pass in the name of the field that has the credit card type and the field with the credit card number. The credit card type should be one of "visa", "mastercard", "amex", "dinersclub", "discover", "jcb". You must check the expiration date yourself (there is no relation between CC number/types and expiration dates). :: >>> cc = CreditCardValidator() >>> cc.to_python({'ccType': 'visa', 'ccNumber': '4111111111111111'}) {'ccNumber': '4111111111111111', 'ccType': 'visa'} >>> cc.to_python({'ccType': 'visa', 'ccNumber': '411111111111111'}) Traceback (most recent call last): ... Invalid: ccNumber: You did not enter a valid number of digits >>> cc.to_python({'ccType': 'visa', 'ccNumber': '411111111111112'}) Traceback (most recent call last): ... Invalid: ccNumber: You did not enter a valid number of digits """ validate_partial_form = True cc_type_field = 'ccType' cc_number_field = 'ccNumber' __unpackargs__ = ('cc_type_field', 'cc_number_field') messages = { 'notANumber': "Please enter only the number, no other characters", 'badLength': "You did not enter a valid number of digits", 'invalidNumber': "That number is not valid", } def validate_partial(self, field_dict, state): if not field_dict.get(self.cc_type_field, None) \ or not field_dict.get(self.cc_number_field, None): return None self.validate_python(field_dict, state) def validate_python(self, field_dict, state): errors = self._validateReturn(field_dict, state) if errors: error_list = errors.items() error_list.sort() raise Invalid( '
\n'.join(["%s: %s" % (name, value) for name, value in error_list]), field_dict, state, error_dict=errors) def _validateReturn(self, field_dict, state): ccType = field_dict[self.cc_type_field].lower().strip() number = field_dict[self.cc_number_field].strip() number = number.replace(' ', '') number = number.replace('-', '') try: long(number) except ValueError: return {self.cc_number_field: self.message('notANumber', state)} assert self._cardInfo.has_key(ccType), ( "I can't validate that type of credit card") foundValid = False validLength = False for prefix, length in self._cardInfo[ccType]: if len(number) == length: validLength = True if (len(number) == length and number.startswith(prefix)): foundValid = True break if not validLength: return {self.cc_number_field: self.message('badLength', state)} if not foundValid: return {self.cc_number_field: self.message('invalidNumber', state)} if not self._validateMod10(number): return {self.cc_number_field: self.message('invalidNumber', state)} return None def _validateMod10(self, s): """ This code by Sean Reifschneider, of tummy.com """ double = 0 sum = 0 for i in range(len(s) - 1, -1, -1): for c in str((double + 1) * int(s[i])): sum = sum + int(c) double = (double + 1) % 2 return((sum % 10) == 0) _cardInfo = { "visa": [('4', 16), ('4', 13)], "mastercard": [('51', 16), ('52', 16), ('53', 16), ('54', 16), ('55', 16)], "discover": [('6011', 16)], "amex": [('34', 15), ('37', 15)], "dinersclub": [('300', 14), ('301', 14), ('302', 14), ('303', 14), ('304', 14), ('305', 14), ('36', 14), ('38', 14)], "jcb": [('3', 16), ('2131', 15), ('1800', 15)], } class CreditCardExpires(FormValidator): """ Checks that credit card expiration date is valid relative to the current date. You pass in the name of the field that has the credit card expiration month and the field with the credit card expiration year. :: >>> ed = CreditCardExpires() >>> ed.to_python({'ccExpiresMonth': '11', 'ccExpiresYear': '2250'}) {'ccExpiresYear': '2250', 'ccExpiresMonth': '11'} >>> ed.to_python({'ccExpiresMonth': '10', 'ccExpiresYear': '2005'}) Traceback (most recent call last): ... Invalid: ccExpiresMonth: Invalid Expiration Date
ccExpiresYear: Invalid Expiration Date """ validate_partial_form = True cc_expires_month_field = 'ccExpiresMonth' cc_expires_year_field = 'ccExpiresYear' __unpackargs__ = ('cc_expires_month_field', 'cc_expires_year_field') datetime_module = None messages = { 'notANumber': "Please enter numbers only for month and year", 'invalidNumber': "Invalid Expiration Date", } def validate_partial(self, field_dict, state): if not field_dict.get(self.cc_expires_month_field, None) \ or not field_dict.get(self.cc_expires_year_field, None): return None self.validate_python(field_dict, state) def validate_python(self, field_dict, state): errors = self._validateReturn(field_dict, state) if errors: error_list = errors.items() error_list.sort() raise Invalid( '
\n'.join(["%s: %s" % (name, value) for name, value in error_list]), field_dict, state, error_dict=errors) def _validateReturn(self, field_dict, state): ccExpiresMonth = str(field_dict[self.cc_expires_month_field]).strip() ccExpiresYear = str(field_dict[self.cc_expires_year_field]).strip() try: ccExpiresMonth = int(ccExpiresMonth) ccExpiresYear = int(ccExpiresYear) dt_mod = import_datetime(self.datetime_module) now = datetime_now(dt_mod) today = datetime_makedate(dt_mod, now.year, now.month, now.day) next_month = (ccExpiresMonth % 12) + 1 if next_month == 1: next_month_year = ccExpiresYear + 1 else: next_month_year = ccExpiresYear expires_date = datetime_makedate(dt_mod, next_month_year, next_month, 1) assert expires_date > today except ValueError: return {self.cc_expires_month_field: self.message('notANumber', state), self.cc_expires_year_field: self.message('notANumber', state)} except AssertionError: return {self.cc_expires_month_field: self.message('invalidNumber', state), self.cc_expires_year_field: self.message('invalidNumber', state)} class CreditCardSecurityCode(FormValidator): """ Checks that credit card security code has the correct number of digits for the given credit card type. You pass in the name of the field that has the credit card type and the field with the credit card security code. :: >>> code = CreditCardSecurityCode() >>> code.to_python({'ccType': 'visa', 'ccCode': '111'}) {'ccType': 'visa', 'ccCode': '111'} >>> code.to_python({'ccType': 'visa', 'ccCode': '1111'}) Traceback (most recent call last): ... Invalid: ccCode: Invalid credit card security code length """ validate_partial_form = True cc_type_field = 'ccType' cc_code_field = 'ccCode' __unpackargs__ = ('cc_type_field', 'cc_code_field') messages = { 'notANumber': "Please enter numbers only for credit card security code", 'badLength': "Invalid credit card security code length", } def validate_partial(self, field_dict, state): if not field_dict.get(self.cc_type_field, None) \ or not field_dict.get(self.cc_code_field, None): return None self.validate_python(field_dict, state) def validate_python(self, field_dict, state): errors = self._validateReturn(field_dict, state) if errors: error_list = errors.items() error_list.sort() raise Invalid( '
\n'.join(["%s: %s" % (name, value) for name, value in error_list]), field_dict, state, error_dict=errors) def _validateReturn(self, field_dict, state): ccType = str(field_dict[self.cc_type_field]).strip() ccCode = str(field_dict[self.cc_code_field]).strip() try: int(ccCode) except ValueError: return {self.cc_code_field: self.message('notANumber', state)} length = self._cardInfo[ccType] validLength = False if len(ccCode) == length: validLength = True if not validLength: return {self.cc_code_field: self.message('badLength', state)} # key = credit card type # value = length of security code _cardInfo = { "visa": 3, "mastercard": 3, "discover": 3, "amex": 4, } __all__ = [] for name, value in globals().items(): if isinstance(value, type) and issubclass(value, Validator): __all__.append(name) PK1X=5V44formencode/api.py""" Core classes for validation. """ import declarative import textwrap import re __all__ = ['NoDefault', 'Invalid', 'Validator', 'Identity', 'FancyValidator', 'is_validator'] class NoDefault: pass def is_validator(obj): return (isinstance(obj, Validator) or (isinstance(obj, type) and issubclass(obj, Validator))) class Invalid(Exception): """ This is raised in response to invalid input. It has several public attributes: msg: The message, *without* values substituted. For instance, if you want HTML quoting of values, you can apply that. substituteArgs: The arguments (a dictionary) to go with `msg`. str(self): The message describing the error, with values substituted. value: The offending (invalid) value. state: The state that went with this validator. This is an application-specific object. error_list: If this was a compound validator that takes a repeating value, and sub-validator(s) had errors, then this is a list of those exceptions. The list will be the same length as the number of values -- valid values will have None instead of an exception. error_dict: Like `error_list`, but for dictionary compound validators. """ def __init__(self, msg, value, state, error_list=None, error_dict=None): Exception.__init__(self, msg) self.msg = msg self.value = value self.state = state self.error_list = error_list self.error_dict = error_dict assert (not self.error_list or not self.error_dict), ( "Errors shouldn't have both error dicts and lists " "(error %s has %s and %s)" % (self, self.error_list, self.error_dict)) def __str__(self): val = self.msg #if self.value: # val += " (value: %s)" % repr(self.value) return val def unpack_errors(self, encode_variables=False, dict_char='.', list_char='-'): """ Returns the error as a simple data structure -- lists, dictionaries, and strings. If ``encode_variables`` is true, then this will return a flat dictionary, encoded with variable_encode """ if self.error_list: assert not encode_variables, ( "You can only encode dictionary errors") assert not self.error_dict result = [] for item in self.error_list: if not item: result.append(item) else: result.append(item.unpack_errors()) return result elif self.error_dict: result = {} for name, item in self.error_dict.items(): if isinstance(item, (str, unicode)): result[name] = item else: result[name] = item.unpack_errors() if encode_variables: import variabledecode result = variabledecode.variable_encode(result, add_repetitions=False, dict_char=dict_char, list_char=list_char) return result else: assert not encode_variables, ( "You can only encode dictionary errors") return self.msg ############################################################ ## Base Classes ############################################################ class Validator(declarative.Declarative): """ The base class of most validators. See `IValidator` for more, and `FancyValidator` for the more common (and more featureful) class. """ _messages = {} if_missing = NoDefault repeating = False compound = False __singletonmethods__ = ('to_python', 'from_python') def __classinit__(cls, new_attrs): if new_attrs.has_key('messages'): cls._messages = cls._messages.copy() cls._messages.update(cls.messages) del cls.messages cls._initialize_docstring() def __init__(self, *args, **kw): if kw.has_key('messages'): self._messages = self._messages.copy() self._messages.update(kw['messages']) del kw['messages'] declarative.Declarative.__init__(self, *args, **kw) def to_python(self, value, state=None): return value def from_python(self, value, state=None): return value def message(self, msgName, state, **kw): try: return self._messages[msgName] % kw except KeyError, e: raise KeyError( "Key not found (%s) for %r=%r %% %r (from: %s)" % (e, msgName, self._messages.get(msgName), kw, ', '.join(self._messages.keys()))) def all_messages(self): """ Return a dictionary of all the messages of this validator, and any subvalidators if present. Keys are message names, values may be a message or list of messages. This is really just intended for documentation purposes, to show someone all the messages that a validator or compound validator (like Schemas) can produce. @@: Should this produce a more structured set of messages, so that messages could be unpacked into a rendered form to see the placement of all the messages? Well, probably so. """ msgs = self._messages.copy() for v in self.subvalidators(): inner = v.all_messages() for key, msg in inner: if key in msgs: if msgs[key] == msg: continue if isinstance(msgs[key], list): msgs[key].append(msg) else: msgs[key] = [msgs[key], msg] else: msgs[key] = msg return msgs def subvalidators(self): """ Return any validators that this validator contains. This is not useful for functional, except to inspect what values are available. Specifically the ``.all_messages()`` method uses this to accumulate all possible messages. """ return [] #@classmethod def _initialize_docstring(cls): """ This changes the class's docstring to include information about all the messages this validator uses. """ doc = cls.__doc__ or '' doc = [textwrap.dedent(doc).rstrip()] messages = cls._messages.items() messages.sort() doc.append('\n\n**Messages**\n\n') for name, default in messages: default = re.sub(r'(%\(.*?\)[rsifcx])', r'``\1``', default) doc.append('``'+name+'``:\n') doc.append(' '+default+'\n\n') cls.__doc__ = ''.join(doc) _initialize_docstring = classmethod(_initialize_docstring) class _Identity(Validator): def __repr__(self): return 'validators.Identity' Identity = _Identity() class FancyValidator(Validator): """ FancyValidator is the (abstract) superclass for various validators and converters. A subclass can validate, convert, or do both. There is no formal distinction made here. Validators have two important external methods: * .to_python(value, state): Attempts to convert the value. If there is a problem, or the value is not valid, an Invalid exception is raised. The argument for this exception is the (potentially HTML-formatted) error message to give the user. * .from_python(value, state): Reverses to_python. There are five important methods for subclasses to override, however none of these *have* to be overridden, only the ones that are appropriate for the validator: * __init__(): if the `declarative.Declarative` model doesn't work for this. * .validate_python(value, state): This should raise an error if necessary. The value is a Python object, either the result of to_python, or the input to from_python. * .validate_other(value, state): Validates the source, before to_python, or after from_python. It's more common to use `.validate_python()` however. * ._to_python(value, state): This returns the converted value, or raises an Invalid exception if there is an error. The argument to this exception should be the error message. * ._from_python(value, state): Should undo .to_python() in some reasonable way, returning a string. Validators should have no internal state besides the values given at instantiation. They should be reusable and reentrant. All subclasses can take the arguments/instance variables: * if_empty: If set, then this value will be returned if the input evaluates to false (empty list, empty string, None, etc), but not the 0 or False objects. This only applies to ``.to_python()``. * not_empty: If true, then if an empty value is given raise an error. (Both with ``.to_python()`` and also ``.from_python()`` if ``.validate_python`` is true). * strip: If true and the input is a string, strip it (occurs before empty tests). * if_invalid: If set, then when this validator would raise Invalid during ``.to_python()``, instead return this value. * if_invalid_python: If set, when the Python value (converted with ``.from_python()``) is invalid, this value will be returned. * accept_python: If True (the default), then ``.validate_python()`` and ``.validate_other()`` will not be called when ``.from_python()`` is used. """ if_invalid = NoDefault if_invalid_python = NoDefault if_empty = NoDefault not_empty = False accept_python = True strip = False messages = { 'empty': "Please enter a value", 'badType': "The input must be a string (not a %(type)s: %(value)r)", 'noneType': "The input must be a string (not None)", } def to_python(self, value, state=None): try: if self.strip and isinstance(value, (str, unicode)): value = value.strip() if self.is_empty(value): if self.not_empty: raise Invalid(self.message('empty', state), value, state) else: if self.if_empty is not NoDefault: return self.if_empty else: return self.empty_value(value) vo = self.validate_other if vo and vo is not self._validate_noop: vo(value, state) tp = self._to_python if tp: value = tp(value, state) vp = self.validate_python if vp and vp is not self._validate_noop: vp(value, state) return value except Invalid: if self.if_invalid is NoDefault: raise else: return self.if_invalid def from_python(self, value, state=None): try: if self.strip and isinstance(value, (str, unicode)): value = value.strip() if not self.accept_python: if self.is_empty(value): if self.not_empty: raise Invalid(self.message('empty', state), value, state) else: return self.empty_value(value) vp = self.validate_python if vp and vp is not self._validate_noop: vp(value, state) fp = self._from_python if fp: value = fp(value, state) vo = self.validate_other if vo and co is not self._validate_noop: vo(value, state) return value else: if self.is_empty(value): return self.empty_value(value) fp = self._from_python if fp: value = self._from_python(value, state) return value except Invalid: if self.if_invalid_python is NoDefault: raise else: return self.if_invalid_python def is_empty(self, value): # None and '' are "empty" return value is None or value == '' def empty_value(self, value): return None def assert_string(self, value, state): if not isinstance(value, (str, unicode)): raise Invalid(self.message('badType', state, type=type(value), value=value), value, state) def base64encode(self, value): """ Encode a string in base64, stripping whitespace and removing newlines. """ return value.encode('base64').strip().replace('\n', '') def _validate_noop(self, value, state): """ A validation method that doesn't do anything. """ pass validate_python = validate_other = _validate_noop _to_python = None _from_python = None PK1X=5esLLformencode/htmlfill.py""" Parser for HTML forms, that fills in defaults and errors. See ``render``. """ import HTMLParser import cgi import re __all__ = ['render', 'htmlliteral', 'default_formatter', 'none_formatter', 'escape_formatter', 'FillingParser'] def render(form, defaults=None, errors=None, use_all_keys=False, error_formatters=None, add_attributes=None, auto_insert_errors=True, auto_error_formatter=None, text_as_default=False, listener=None): """ Render the ``form`` (which should be a string) given the defaults and errors. Defaults are the values that go in the input fields (overwriting any values that are there) and errors are displayed inline in the form (and also effect input classes). Returns the rendered string. If ``auto_insert_errors`` is true (the default) then any errors for which ```` tags can't be found will be put just above the associated input field, or at the top of the form if no field can be found. If ``use_all_keys`` is true, if there are any extra fields from defaults or errors that couldn't be used in the form it will be an error. ``error_formatters`` is a dictionary of formatter names to one-argument functions that format an error into HTML. Some default formatters are provided if you don't provide this. ``error_class`` is the class added to input fields when there is an error for that field. ``add_attributes`` is a dictionary of field names to a dictionary of attribute name/values. If the name starts with ``+`` then the value will be appended to any existing attribute (e.g., ``{'+class': ' important'}``). ``auto_error_formatter`` is used to create the HTML that goes above the fields. By default it wraps the error message in a span and adds a ``
``. If ``text_as_default`` is true (default false) then ```` will be treated as text inputs. ``listener`` can be an object that watches fields pass; the only one currently is in ``htmlfill_schemabuilder.SchemaBuilder`` """ if defaults is None: defaults = {} if auto_insert_errors and auto_error_formatter is None: auto_error_formatter = default_formatter p = FillingParser( defaults=defaults, errors=errors, use_all_keys=use_all_keys, error_formatters=error_formatters, add_attributes=add_attributes, auto_error_formatter=auto_error_formatter, text_as_default=text_as_default, listener=listener, ) p.feed(form) p.close() return p.text() class htmlliteral(object): def __init__(self, html, text=None): if text is None: text = re.sub(r'<.*?>', '', html) text = html.replace('>', '>') text = html.replace('<', '<') text = html.replace('"', '"') # @@: Not very complete self.html = html self.text = text def __str__(self): return self.text def __repr__(self): return '<%s html=%r text=%r>' % (self.html, self.text) def __html__(self): return self.html def html_quote(v): if v is None: return '' elif hasattr(v, '__html__'): return v.__html__() elif isinstance(v, basestring): return cgi.escape(v, 1) else: # @@: Should this be unicode(v) or str(v)? return cgi.escape(str(v), 1) def default_formatter(error): """ Formatter that escapes the error, wraps the error in a span with class ``error-message``, and adds a ``
`` """ return '%s
\n' % html_quote(error) def none_formatter(error): """ Formatter that does nothing, no escaping HTML, nothin' """ return error def escape_formatter(error): """ Formatter that escapes HTML, no more. """ return html_quote(error) def escapenl_formatter(error): """ Formatter that escapes HTML, and translates newlines to ``
`` """ error = html_quote(error) error = error.replace('\n', '
\n') return error class FillingParser(HTMLParser.HTMLParser): r""" Fills HTML with default values, as in a form. Examples:: >>> defaults = {'name': 'Bob Jones', ... 'occupation': 'Crazy Cultist', ... 'address': '14 W. Canal\nNew Guinea', ... 'living': 'no', ... 'nice_guy': 0} >>> parser = FillingParser(defaults) >>> parser.feed('\ ... \ ... \ ... ') >>> print parser.text() """ def __init__(self, defaults, errors=None, use_all_keys=False, error_formatters=None, error_class='error', add_attributes=None, listener=None, auto_error_formatter=None, text_as_default=False): HTMLParser.HTMLParser.__init__(self) self._content = [] self.source = None self.lines = None self.source_pos = None self.defaults = defaults self.in_textarea = None self.in_select = None self.skip_next = False self.errors = errors or {} if isinstance(self.errors, (str, unicode)): self.errors = {None: self.errors} self.in_error = None self.skip_error = False self.use_all_keys = use_all_keys self.used_keys = {} self.used_errors = {} if error_formatters is None: self.error_formatters = default_formatter_dict else: self.error_formatters = error_formatters self.error_class = error_class self.add_attributes = add_attributes or {} self.listener = listener self.auto_error_formatter = auto_error_formatter self.text_as_default = text_as_default def feed(self, data): self.source = data self.lines = data.split('\n') self.source_pos = 1, 0 if self.listener: self.listener.reset() HTMLParser.HTMLParser.feed(self, data) def close(self): HTMLParser.HTMLParser.close(self) unused_errors = self.errors.copy() for key in self.used_errors.keys(): if unused_errors.has_key(key): del unused_errors[key] if self.auto_error_formatter: for key, value in unused_errors.items(): error_message = self.auto_error_formatter(value) error_message = '\n%s' % (key, error_message) self.insert_at_marker( key, error_message) unused_errors = {} if self.use_all_keys: unused = self.defaults.copy() for key in self.used_keys.keys(): if unused.has_key(key): del unused[key] assert not unused, ( "These keys from defaults were not used in the form: %s" % unused.keys()) if unused_errors: error_text = [] for key in unused_errors.keys(): error_text.append("%s: %s" % (key, self.errors[key])) assert False, ( "These errors were not used in the form: %s" % ', '.join(error_text)) self._text = ''.join([ t for t in self._content if not isinstance(t, tuple)]) def add_key(self, key): self.used_keys[key] = 1 def handle_starttag(self, tag, attrs, startend=False): self.write_pos() if tag == 'input': self.handle_input(attrs, startend) elif tag == 'textarea': self.handle_textarea(attrs) elif tag == 'select': self.handle_select(attrs) elif tag == 'option': self.handle_option(attrs) return elif tag == 'form:error': self.handle_error(attrs) return elif tag == 'form:iferror': self.handle_iferror(attrs) return else: return if self.listener: self.listener.listen_input(self, tag, attrs) def handle_misc(self, whatever): self.write_pos() handle_charref = handle_misc handle_entityref = handle_misc handle_data = handle_misc handle_comment = handle_misc handle_decl = handle_misc handle_pi = handle_misc unknown_decl = handle_misc def handle_endtag(self, tag): self.write_pos() if tag == 'textarea': self.handle_end_textarea() elif tag == 'select': self.handle_end_select() elif tag == 'form:iferror': self.handle_end_iferror() def handle_startendtag(self, tag, attrs): return self.handle_starttag(tag, attrs, True) def handle_iferror(self, attrs): name = self.get_attr(attrs, 'name') notted = False if name.startswith('not '): notted = True name = name.split(None, 1)[1] assert name, "Name attribute in required (%s)" % self.getpos() self.in_error = name ok = self.errors.get(name) if notted: ok = not ok if not ok: self.skip_error = True self.skip_next = True def handle_end_iferror(self): self.in_error = None self.skip_error = False self.skip_next = True def handle_error(self, attrs): name = self.get_attr(attrs, 'name') formatter = self.get_attr(attrs, 'format') or 'default' if name is None: name = self.in_error assert name is not None, ( "Name attribute in required if not contained in " " (%i:%i)" % self.getpos()) error = self.errors.get(name, '') if error: error = self.error_formatters[formatter](error) self.write_text(error) self.skip_next = True self.used_errors[name] = 1 def handle_input(self, attrs, startend): t = (self.get_attr(attrs, 'type') or 'text').lower() name = self.get_attr(attrs, 'name') self.write_marker(name) value = self.defaults.get(name) if self.add_attributes.has_key(name): for attr_name, attr_value in self.add_attributes[name].items(): if attr_name.startswith('+'): attr_name = attr_name[1:] self.set_attr(attrs, attr_name, self.get_attr(attrs, attr_name, '') + attr_value) else: self.set_attr(attrs, attr_name, attr_value) if (self.error_class and self.errors.get(self.get_attr(attrs, 'name'))): self.add_class(attrs, self.error_class) if t in ('text', 'hidden'): if value is None: value = self.get_attr(attrs, 'value', '') self.set_attr(attrs, 'value', value) self.write_tag('input', attrs, startend) self.skip_next = True self.add_key(name) elif t == 'checkbox': selected = False if not self.get_attr(attrs, 'value'): selected = value elif self.selected_multiple(value, self.get_attr(attrs, 'value')): selected = True if selected: self.set_attr(attrs, 'checked', 'checked') else: self.del_attr(attrs, 'checked') self.write_tag('input', attrs, startend) self.skip_next = True self.add_key(name) elif t == 'radio': if str(value) == self.get_attr(attrs, 'value'): self.set_attr(attrs, 'checked', 'checked') else: self.del_attr(attrs, 'checked') self.write_tag('input', attrs, startend) self.skip_next = True self.add_key(name) elif t == 'file': pass # don't skip next elif t == 'password': self.set_attr(attrs, 'value', value or self.get_attr(attrs, 'value', '')) self.write_tag('input', attrs, startend) self.skip_next = True self.add_key(name) elif t == 'image': self.set_attr(attrs, 'src', value or self.get_attr(attrs, 'src', '')) self.write_tag('input', attrs, startend) self.skip_next = True self.add_key(name) elif t == 'submit' or t == 'reset' or t == 'button': self.set_attr(attrs, 'value', value or self.get_attr(attrs, 'value', '')) self.write_tag('input', attrs, startend) self.skip_next = True self.add_key(name) elif self.text_as_default: if value is None: value = self.get_attr(attrs, 'value', '') self.set_attr(attrs, 'value', value) self.write_tag('input', attrs, startend) self.skip_next = True self.add_key(name) else: assert 0, "I don't know about this kind of : %s (pos: %s)" \ % (t, self.getpos()) def handle_textarea(self, attrs): name = self.get_attr(attrs, 'name') self.write_marker(name) if (self.error_class and self.errors.get(name)): self.add_class(attrs, self.error_class) self.write_tag('textarea', attrs) value = self.defaults.get(name, '') self.write_text(html_quote(value)) self.write_text('') self.in_textarea = True self.add_key(name) def handle_end_textarea(self): self.in_textarea = False self.skip_next = True def handle_select(self, attrs): name = self.get_attr(attrs, 'name', False) if name: self.write_marker(name) if (self.error_class and self.errors.get(name)): self.add_class(attrs, self.error_class) self.in_select = self.get_attr(attrs, 'name', False) self.write_tag('select', attrs) self.skip_next = True self.add_key(self.in_select) def handle_end_select(self): self.in_select = None def handle_option(self, attrs): assert self.in_select is not None, ( "