formencode.htmlfill_schemabuilder
Covered: 78 lines
Missed: 18 lines
Skipped 17 lines
Percent: 81 %
  1
"""
  2
Extension to ``htmlfill`` that can parse out schema-defining
  3
statements.
  5
You can either pass ``SchemaBuilder`` to ``htmlfill.render`` (the
  6
``listen`` argument), or call ``parse_schema`` to just parse out a
  7
``Schema`` object.
  8
"""
 10
import validators, schema, compound
 12
__all__ = ['parse_schema', 'SchemaBuilder']
 14
def parse_schema(form):
 15
    """
 16
    Given an HTML form, parse out the schema defined in it and return
 17
    that schema.
 18
    """
 19
    listener = htmlfill_schemabuilder.SchemaBuilder()
 20
    p = htmlfill.FillingParser(
 21
        defaults={}, listener=listener)
 22
    p.feed(self.form)
 23
    p.close()
 24
    return listener.schema()
 26
default_validators = dict(
 27
    [(name.lower(), getattr(validators, name))
 28
     for name in dir(validators)])
 30
def get_messages(cls, message):
 31
    if not message:
 32
        return {}
 33
    else:
 34
        return dict([(k, message) for k in cls._messages.keys()])
 36
def to_bool(value):
 37
    value = value.strip().lower()
 38
    if value in ('true', 't', 'yes', 'y', 'on', '1'):
 39
        return True
 40
    elif value in ('false', 'f', 'no', 'n', 'off', '0'):
 41
        return False
 42
    else:
 43
        raise ValueError("Not a boolean value: %r (use 'true'/'false')")
 45
def force_list(v):
 46
    """
 47
    Force single items into a list. This is useful for checkboxes.
 48
    """
 49
    if isinstance(v, list):
 50
        return v
 51
    elif isinstance(v, tuple):
 52
        return list(v)
 53
    else:
 54
        return [v]
 56
class SchemaBuilder(object):
 58
    def __init__(self, validators=default_validators):
 59
        self.validators = validators
 60
        self._schema = None
 62
    def reset(self):
 63
        self._schema = schema.Schema()
 65
    def schema(self):
 66
        return self._schema
 68
    def listen_input(self, parser, tag, attrs):
 69
        get_attr = parser.get_attr
 70
        name = get_attr(attrs, 'name')
 71
        if not name:
 73
            return
 74
        v = compound.All(validators.Identity())
 75
        add_to_end = None
 77
        if tag.lower() == "input":
 78
            type_attr = get_attr(attrs, "type").lower().strip()
 79
            if type_attr == "submit":
 80
                v.validators.append(validators.Bool())
 81
            elif type_attr == "checkbox":
 82
                v.validators.append(validators.Wrapper(to_python = force_list))
 83
            elif type_attr == "file":
 84
                add_to_end = validators.FieldStorageUploadConverter()
 85
        message = get_attr(attrs, 'form:message')
 86
        required = to_bool(get_attr(attrs, 'form:required', 'false'))
 87
        if required:
 88
            v.validators.append(
 89
                validators.NotEmpty(
 90
                messages=get_messages(validators.NotEmpty, message)))
 91
        else:
 92
            v.validators[0].if_missing = False
 93
        if add_to_end:
 94
            v.validators.append(add_to_end)
 95
        v_type = get_attr(attrs, 'form:validate', None)
 96
        if v_type:
 97
            pos = v_type.find(':')
 98
            if pos != -1:
100
                args = (v_type[pos+1:],)
101
                v_type = v_type[:pos]
102
            else:
103
                args = ()
104
            v_type = v_type.lower()
105
            v_class = self.validators.get(v_type)
106
            if not v_class:
107
                raise ValueError("Invalid validation type: %r" % v_type)
108
            kw_args={'messages': get_messages(v_class, message)}
109
            v_inst = v_class(
110
                *args, **kw_args)
111
            v.validators.append(v_inst)
112
        self._schema.add_field(name, v)