Zine

open source content publishing system


source: zine/utils/forms.py @ 1299:fb540c42162b

Revision 1299:fb540c42162b, 61.1 KB checked in by mitsuhiko, 2 years ago (diff)

No longer possible to commit empty comments.

Line 
1# -*- coding: utf-8 -*-
2"""
3    zine.utils.forms
4    ~~~~~~~~~~~~~~~~
5
6    This module implements a sophisticated form validation and rendering
7    system that is based on diva with concepts from django newforms and
8    wtforms incorporated.
9
10    It can validate nested structures and works in both ways.  It can also
11    handle intelligent backredirects (via :mod:`zine.utils.http`) and supports
12    basic CSRF protection.
13
14    For usage information, see :class:`Form`.
15
16    :copyright: (c) 2010 by the Zine Team, see AUTHORS for more details.
17    :license: BSD, see LICENSE for more details.
18"""
19from datetime import datetime
20from itertools import chain
21from threading import Lock
22try:
23    from hashlib import sha1
24except ImportError:
25    from sha import new as sha1
26
27from werkzeug import html, escape, MultiDict
28
29from zine.application import get_request, url_for
30from zine.database import db
31from zine.i18n import _, ngettext, lazy_gettext, parse_datetime, \
32     format_system_datetime
33from zine.utils.http import get_redirect_target, _redirect, redirect_to
34from zine.utils.validators import ValidationError
35from zine.utils.datastructures import OrderedDict, missing
36
37
38_last_position_hint = -1
39_position_hint_lock = Lock()
40
41
42def fill_dict(_dict, **kwargs):
43    """A helper to fill the dict passed with the items passed as keyword
44    arguments if they are not yet in the dict.  If the dict passed was
45    `None` a new dict is created and returned.
46
47    This can be used to prepopulate initial dicts in overriden constructors:
48
49        class MyForm(forms.Form):
50            foo = forms.TextField()
51            bar = forms.TextField()
52
53            def __init__(self, initial=None):
54                forms.Form.__init__(self, forms.fill_dict(initial,
55                    foo="nothing",
56                    bar="nothing"
57                ))
58    """
59    if _dict is None:
60        return kwargs
61    for key, value in kwargs.iteritems():
62        if key not in _dict:
63            _dict[key] = value
64    return _dict
65
66
67def set_fields(obj, data, *fields):
68    """Set all the fields on obj with data if changed."""
69    for field in fields:
70        value = data[field]
71        if getattr(obj, field) != value:
72            setattr(obj, field, value)
73
74
75def _next_position_hint():
76    """Return the next position hint."""
77    global _last_position_hint
78    _position_hint_lock.acquire()
79    try:
80        _last_position_hint += 1
81        return _last_position_hint
82    finally:
83        _position_hint_lock.release()
84
85
86def _decode(data):
87    """Decodes the flat dictionary d into a nested structure.
88
89    >>> _decode({'foo': 'bar'})
90    {'foo': 'bar'}
91    >>> _decode({'foo.0': 'bar', 'foo.1': 'baz'})
92    {'foo': ['bar', 'baz']}
93    >>> data = _decode({'foo.bar': '1', 'foo.baz': '2'})
94    >>> data == {'foo': {'bar': '1', 'baz': '2'}}
95    True
96
97    More complex mappings work too:
98
99    >>> _decode({'foo.bar.0': 'baz', 'foo.bar.1': 'buzz'})
100    {'foo': {'bar': ['baz', 'buzz']}}
101    >>> _decode({'foo.0.bar': '23', 'foo.1.baz': '42'})
102    {'foo': [{'bar': '23'}, {'baz': '42'}]}
103    >>> _decode({'foo.0.0': '23', 'foo.0.1': '42'})
104    {'foo': [['23', '42']]}
105    >>> _decode({'foo': ['23', '42']})
106    {'foo': ['23', '42']}
107
108    Missing items in lists are ignored for convenience reasons:
109
110    >>> _decode({'foo.42': 'a', 'foo.82': 'b'})
111    {'foo': ['a', 'b']}
112
113    This can be used for help client side DOM processing (inserting and
114    deleting rows in dynamic forms).
115
116    It also supports werkzeug's multi dicts:
117
118    >>> _decode(MultiDict({"foo": ['1', '2']}))
119    {'foo': ['1', '2']}
120    >>> _decode(MultiDict({"foo.0": '1', "foo.1": '2'}))
121    {'foo': ['1', '2']}
122
123    Those two submission ways can also be used combined:
124
125    >>> _decode(MultiDict({"foo": ['1'], "foo.0": '2', "foo.1": '3'}))
126    {'foo': ['1', '2', '3']}
127
128    This function will never raise exceptions except for argument errors
129    but the recovery behavior for invalid form data is undefined.
130    """
131    list_marker = object()
132    value_marker = object()
133
134    if isinstance(data, MultiDict):
135        listiter = data.iterlists()
136    else:
137        listiter = ((k, [v]) for k, v in data.iteritems())
138
139    def _split_key(name):
140        result = name.split('.')
141        for idx, part in enumerate(result):
142            if part.isdigit():
143                result[idx] = int(part)
144        return result
145
146    def _enter_container(container, key):
147        if key not in container:
148            return container.setdefault(key, {list_marker: False})
149        return container[key]
150
151    def _convert(container):
152        if value_marker in container:
153            force_list = False
154            values = container.pop(value_marker)
155            if container.pop(list_marker):
156                force_list = True
157                values.extend(_convert(x[1]) for x in
158                              sorted(container.items()))
159            if not force_list and len(values) == 1:
160                values = values[0]
161            return values
162        elif container.pop(list_marker):
163            return [_convert(x[1]) for x in sorted(container.items())]
164        return dict((k, _convert(v)) for k, v in container.iteritems())
165
166    result = {list_marker: False}
167    for key, values in listiter:
168        parts = _split_key(key)
169        if not parts:
170            continue
171        container = result
172        for part in parts:
173            last_container = container
174            container = _enter_container(container, part)
175            last_container[list_marker] = isinstance(part, (int, long))
176        container[value_marker] = values[:]
177
178    return _convert(result)
179
180
181def _bind(obj, form, memo):
182    """Helper for the field binding.  This is inspired by the way `deepcopy`
183    is implemented.
184    """
185    if memo is None:
186        memo = {}
187    obj_id = id(obj)
188    if obj_id in memo:
189        return memo[obj_id]
190    rv = obj._bind(form, memo)
191    memo[obj_id] = rv
192    return rv
193
194
195def _force_dict(value):
196    """If the value is not a dict, raise an exception."""
197    if value is None or not isinstance(value, dict):
198        return {}
199    return value
200
201
202def _force_list(value):
203    """If the value is not a list, make it one."""
204    if value is None:
205        return []
206    try:
207        if isinstance(value, basestring):
208            raise TypeError()
209        return list(value)
210    except TypeError:
211        return [value]
212
213
214def _make_widget(field, name, value, errors):
215    """Shortcut for widget creation."""
216    return field.widget(field, name, value, errors)
217
218
219def _make_name(parent, child):
220    """Joins a name."""
221    if parent is None:
222        result = child
223    else:
224        result = '%s.%s' % (parent, child)
225
226    # try to return a ascii only bytestring if possible
227    try:
228        return str(result)
229    except UnicodeError:
230        return unicode(result)
231
232
233def _to_string(value):
234    """Convert a value to unicode, None means empty string."""
235    if value is None:
236        return u''
237    return unicode(value)
238
239
240def _to_list(value):
241    """Similar to `_force_list` but always succeeds and never drops data."""
242    if value is None:
243        return []
244    if isinstance(value, basestring):
245        return [value]
246    try:
247        return list(value)
248    except TypeError:
249        return [value]
250
251
252def _value_matches_choice(value, choice):
253    """Checks if a given value matches a choice."""
254    # this algorithm is also implemented in `MultiChoiceField.convert`
255    # for better scaling with multiple items.  If it's changed here, it
256    # must be changed for the multi choice field too.
257    return choice == value or _to_string(choice) == _to_string(value)
258
259
260def _iter_choices(choices):
261    """Iterate over choices."""
262    if choices is not None:
263        for choice in choices:
264            if not isinstance(choice, tuple):
265                choice = (choice, choice)
266            yield choice
267
268
269def _is_choice_selected(field, value, choice):
270    """Checks if a choice is selected.  If the field is a multi select
271    field it's checked if the choice is in the passed iterable of values,
272    otherwise it's checked if the value matches the choice.
273    """
274    if field.multiple_choices:
275        for value in value:
276            if _value_matches_choice(value, choice):
277                return True
278        return False
279    return _value_matches_choice(value, choice)
280
281
282class _Renderable(object):
283    """Mixin for renderable HTML objects."""
284
285    def render(self):
286        return u''
287
288    def __call__(self, *args, **kwargs):
289        return self.render(*args, **kwargs)
290
291
292class Widget(_Renderable):
293    """Baseclass for all widgets.  All widgets share a common interface
294    that can be used from within templates.
295
296    Take this form as an example:
297
298    >>> class LoginForm(Form):
299    ...     username = TextField(required=True)
300    ...     password = TextField(widget=PasswordInput)
301    ...     flags = MultiChoiceField(choices=[1, 2, 3])
302    ...
303    >>> form = LoginForm()
304    >>> form.validate({'username': '', 'password': '',
305    ...                'flags': [1, 3]})
306    False
307    >>> widget = form.as_widget()
308
309    You can get the subwidgets by using the normal indexing operators:
310
311    >>> username = widget['username']
312    >>> password = widget['password']
313
314    To render a widget you can usually invoke the `render()` method.  All
315    keyword parameters are used as HTML attribute in the resulting tag.
316    You can also call the widget itself (``username()`` instead of
317    ``username.render()``) which does the same if there are no errors for
318    the field but adds the default error list after the widget if there
319    are errors.
320
321    Widgets have some public attributes:
322
323    `errors`
324
325        gives the list of errors:
326
327        >>> username.errors
328        [u'This field is required.']
329
330        This error list is printable:
331
332        >>> print username.errors()
333        <ul class="errors"><li>This field is required.</li></ul>
334
335        Like any other sequence that yields list items it provides
336        `as_ul` and `as_ol` methods:
337
338        >>> print username.errors.as_ul()
339        <ul><li>This field is required.</li></ul>
340
341        Keep in mind that ``widget.errors()`` is equivalent to
342        ``widget.errors.as_ul(class_='errors', hide_empty=True)``.
343
344    `value`
345
346        returns the value of the widget as primitive.  For basic
347        widgets this is always a string, for widgets with subwidgets or
348        widgets with multiple values a dict or a list:
349
350        >>> username.value
351        u''
352        >>> widget['flags'].value
353        [u'1', u'3']
354
355    `name` gives you the name of the field for form submissions:
356
357        >>> username.name
358        'username'
359
360        Please keep in mind that the name is not always that obvious.  Zine
361        supports nested form fields so it's a good idea to always use the
362        name attribute.
363
364    `id`
365
366        gives you the default domain for the widget.  This is either none
367        if there is no idea for the field or `f_` + the field name with
368        underscores instead of dots:
369
370        >>> username.id
371        'f_username'
372
373    `all_errors`
374
375        like `errors` but also contains the errors of child
376        widgets.
377    """
378
379    disable_dt = False
380
381    def __init__(self, field, name, value, all_errors):
382        self._field = field
383        self._value = value
384        self._all_errors = all_errors
385        self.name = name
386
387    def hidden(self):
388        """Return one or multiple hidden fields for the current value.  This
389        also handles subwidgets.  This is useful for transparent form data
390        passing.
391        """
392        fields = []
393
394        def _add_field(name, value):
395            fields.append(html.input(type='hidden', name=name, value=value))
396
397        def _to_hidden(value, name):
398            if isinstance(value, list):
399                for idx, value in enumerate(value):
400                    _to_hidden(value, _make_name(name, idx))
401            elif isinstance(value, dict):
402                for key, value in value.iteritems():
403                    _to_hidden(value, _make_name(name, key))
404            else:
405                _add_field(name, value)
406
407        _to_hidden(self.value, self.name)
408        return u'\n'.join(fields)
409
410    @property
411    def localname(self):
412        """The local name of the field."""
413        return self.name.rsplit('.', 1)[-1]
414
415    @property
416    def id(self):
417        """The proposed id for this widget."""
418        if self.name is not None:
419            return 'f_' + self.name.replace('.', '__')
420
421    @property
422    def value(self):
423        """The primitive value for this widget."""
424        return self._field.to_primitive(self._value)
425
426    @property
427    def label(self):
428        """The label for the widget."""
429        if self._field.label is not None:
430            return Label(unicode(self._field.label), self.id)
431
432    @property
433    def help_text(self):
434        """The help text of the widget."""
435        if self._field.help_text is not None:
436            return unicode(self._field.help_text)
437
438    @property
439    def errors(self):
440        """The direct errors of this widget."""
441        if self.name in self._all_errors:
442            return self._all_errors[self.name]
443        return ErrorList()
444
445    @property
446    def all_errors(self):
447        """The current errors and the errors of all child widgets."""
448        items = sorted(self._all_errors.items())
449        if self.name is None:
450            return ErrorList(chain(*(item[1] for item in items)))
451        result = ErrorList()
452        for key, value in items:
453            if key == self.name or key.startswith(self.name + '.'):
454                result.extend(value)
455        return result
456
457    def as_dd(self, **attrs):
458        """Return a dt/dd item."""
459        rv = []
460        if not self.disable_dt:
461            label = self.label
462            if label:
463                rv.append(html.dt(label()))
464        rv.append(html.dd(self(**attrs)))
465        if self.help_text:
466            rv.append(html.dd(self.help_text, class_='explanation'))
467        return u''.join(rv)
468
469    def _attr_setdefault(self, attrs):
470        """Add an ID to the attrs if there is none."""
471        if 'id' not in attrs and self.id is not None:
472            attrs['id'] = self.id
473
474    def __call__(self, **attrs):
475        """The default display is the form + error list as ul if needed."""
476        return self.render(**attrs) + self.errors()
477
478
479class Label(_Renderable):
480    """Holds a label."""
481
482    def __init__(self, text, linked_to=None):
483        self.text = text
484        self.linked_to = linked_to
485
486    def render(self, **attrs):
487        attrs.setdefault('for', self.linked_to)
488        return html.label(escape(self.text), **attrs)
489
490
491class InternalWidget(Widget):
492    """Special widgets are widgets that can't be used on arbitrary
493    form fields but belong to others.
494    """
495
496    def __init__(self, parent):
497        self._parent = parent
498
499    value = name = None
500    errors = all_errors = property(lambda x: ErrorList())
501
502
503class Input(Widget):
504    """A widget that is a HTML input field."""
505    hide_value = False
506    type = None
507
508    def render(self, **attrs):
509        self._attr_setdefault(attrs)
510        value = self.value
511        if self.hide_value:
512            value = u''
513        return html.input(name=self.name, value=value, type=self.type,
514                          **attrs)
515
516
517class TextInput(Input):
518    """A widget that holds text."""
519    type = 'text'
520
521
522class PasswordInput(TextInput):
523    """A widget that holds a password."""
524    type = 'password'
525    hide_value = True
526
527
528class HiddenInput(Input):
529    """A hidden input field for text."""
530    type = 'hidden'
531
532
533class Textarea(Widget):
534    """Displays a textarea."""
535
536    def _attr_setdefault(self, attrs):
537        Widget._attr_setdefault(self, attrs)
538        attrs.setdefault('rows', 8)
539        attrs.setdefault('cols', 40)
540
541    def render(self, **attrs):
542        self._attr_setdefault(attrs)
543        return html.textarea(self.value, name=self.name, **attrs)
544
545
546class Checkbox(Widget):
547    """A simple checkbox."""
548
549    @property
550    def checked(self):
551        return self.value != u'False'
552
553    def with_help_text(self, **attrs):
554        """Render the checkbox with help text."""
555        data = self(**attrs)
556        if self.help_text:
557            data += u' ' + html.label(self.help_text, class_='explanation',
558                                      for_=self.id)
559        return data
560
561    def as_dd(self, **attrs):
562        """Return a dt/dd item."""
563        rv = []
564        label = self.label
565        if label:
566            rv.append(html.dt(label()))
567        rv.append(html.dd(self.with_help_text()))
568        return u''.join(rv)
569
570    def as_li(self, **attrs):
571        """Return a li item."""
572        rv = [self.render(**attrs)]
573        if self.label:
574            rv.append(u' ' + self.label())
575        if self.help_text:
576            rv.append(html.div(self.help_text, class_='explanation'))
577        rv.append(self.errors())
578        return html.li(u''.join(rv))
579
580    def render(self, **attrs):
581        self._attr_setdefault(attrs)
582        return html.input(name=self.name, type='checkbox',
583                          checked=self.checked, **attrs)
584
585
586class SelectBox(Widget):
587    """A select box."""
588
589    def _attr_setdefault(self, attrs):
590        Widget._attr_setdefault(self, attrs)
591        attrs.setdefault('multiple', self._field.multiple_choices)
592
593    def render(self, **attrs):
594        self._attr_setdefault(attrs)
595        items = []
596        for choice in self._field.choices:
597            if isinstance(choice, tuple):
598                key, value = choice
599            else:
600                key = value = choice
601            selected = _is_choice_selected(self._field, self.value, key)
602            items.append(html.option(unicode(value), value=unicode(key),
603                                     selected=selected))
604        return html.select(name=self.name, *items, **attrs)
605
606
607class _InputGroupMember(InternalWidget):
608    """A widget that is a single radio button."""
609
610    # override the label descriptor
611    label = None
612    inline_label = True
613
614    def __init__(self, parent, value, label):
615        InternalWidget.__init__(self, parent)
616        self.value = unicode(value)
617        self.label = Label(label, self.id)
618
619    @property
620    def name(self):
621        return self._parent.name
622
623    @property
624    def id(self):
625        return 'f_%s_%s' % (self._parent.name, self.value)
626
627    @property
628    def checked(self):
629        return _is_choice_selected(self._parent._field, self._parent.value,
630                                   self.value)
631
632    def render(self, **attrs):
633        self._attr_setdefault(attrs)
634        return html.input(type=self.type, name=self.name, value=self.value,
635                          checked=self.checked, **attrs)
636
637
638class RadioButton(_InputGroupMember):
639    """A radio button in an input group."""
640    type = 'radio'
641
642
643class GroupCheckbox(_InputGroupMember):
644    """A checkbox in an input group."""
645    type = 'checkbox'
646
647
648class _InputGroup(Widget):
649
650    def __init__(self, field, name, value, all_errors):
651        Widget.__init__(self, field, name, value, all_errors)
652        self.choices = []
653        self._subwidgets = {}
654        for value, label in _iter_choices(self._field.choices):
655            widget = self.subwidget(self, value, label)
656            self.choices.append(widget)
657            self._subwidgets[value] = widget
658
659    def __getitem__(self, value):
660        """Return a subwidget."""
661        return self._subwidgets[value]
662
663    def _as_list(self, list_type, attrs):
664        if attrs.pop('hide_empty', False) and not self.choices:
665            return u''
666        self._attr_setdefault(attrs)
667        empty_msg = attrs.pop('empty_msg', None)
668        label = not attrs.pop('nolabel', False)
669        class_ = attrs.pop('class_', attrs.pop('class', None))
670        if class_ is None:
671            class_ = 'choicegroup'
672        attrs['class'] = class_
673        choices = [u'<li>%s %s</li>' % (
674            choice(),
675            label and choice.label() or u''
676        ) for choice in self.choices]
677        if not choices:
678            if empty_msg is None:
679                empty_msg = _('No choices.')
680            choices.append(u'<li>%s</li>' % _(empty_msg))
681        return list_type(*choices, **attrs)
682
683    def as_ul(self, **attrs):
684        """Render the radio buttons widget as <ul>"""
685        return self._as_list(html.ul, attrs)
686
687    def as_ol(self, **attrs):
688        """Render the radio buttons widget as <ol>"""
689        return self._as_list(html.ol, attrs)
690
691    def render(self, **attrs):
692        return self.as_ul(**attrs)
693
694
695class RadioButtonGroup(_InputGroup):
696    """A group of radio buttons."""
697    subwidget = RadioButton
698
699
700class CheckboxGroup(_InputGroup):
701    """A group of checkboxes."""
702    subwidget = GroupCheckbox
703
704
705class MappingWidget(Widget):
706    """Special widget for dict-like fields."""
707
708    def __init__(self, field, name, value, all_errors):
709        Widget.__init__(self, field, name, _force_dict(value), all_errors)
710        self._subwidgets = {}
711
712    def __getitem__(self, name):
713        subwidget = self._subwidgets.get(name)
714        if subwidget is None:
715            # this could raise a KeyError we pass through
716            subwidget = _make_widget(self._field.fields[name],
717                                     _make_name(self.name, name),
718                                     self._value.get(name),
719                                     self._all_errors)
720            self._subwidgets[name] = subwidget
721        return subwidget
722
723    def as_dl(self, **attrs):
724        return html.dl(*[x.as_dd() for x in self], **attrs)
725
726    def __call__(self, *args, **kwargs):
727        return self.as_dl(*args, **kwargs)
728
729    def __iter__(self):
730        for key in self._field.fields:
731            yield self[key]
732
733
734class FormWidget(MappingWidget):
735    """A widget for forms."""
736
737    def get_hidden_fields(self):
738        """This method is called by the `hidden_fields` property to return
739        a list of (key, value) pairs for the special hidden fields.
740        """
741        fields = []
742        if self._field.form.request is not None:
743            if self._field.form.csrf_protected:
744                fields.append(('_csrf_token', self.csrf_token))
745            if self._field.form.redirect_tracking:
746                target = self.redirect_target
747                if target is not None:
748                    fields.append(('_redirect_target', target))
749        return fields
750
751    @property
752    def hidden_fields(self):
753        """The hidden fields as string."""
754        return u''.join(html.input(type='hidden', name=name, value=value)
755                        for name, value in self.get_hidden_fields())
756
757    @property
758    def csrf_token(self):
759        """Forward the CSRF check token for templates."""
760        return self._field.form.csrf_token
761
762    @property
763    def redirect_target(self):
764        """The redirect target for this form."""
765        return self._field.form.redirect_target
766
767    def default_actions(self, **attrs):
768        """Returns a default action div with a submit button."""
769        label = attrs.pop('label', None)
770        if label is None:
771            label = _('Submit')
772        attrs.setdefault('class', 'actions')
773        return html.div(html.input(type='submit', value=label), **attrs)
774
775    def render(self, action='', method='post', **attrs):
776        self._attr_setdefault(attrs)
777        with_errors = attrs.pop('with_errors', False)
778
779        # support jinja's caller
780        caller = attrs.pop('caller', None)
781        if caller is not None:
782            body = caller()
783        else:
784            body = self.as_dl() + self.default_actions()
785
786        hidden = self.hidden_fields
787        if hidden:
788            # if there are hidden fields we put an invisible div around
789            # it.  the HTML standard doesn't allow input fields as direct
790            # childs of a <form> tag...
791            body = '<div style="display: none">%s</div>%s' % (hidden, body)
792
793        if with_errors:
794            body = self.errors() + body
795        return html.form(body, action=action, method=method, **attrs)
796
797    def __call__(self, *args, **attrs):
798        attrs.setdefault('with_errors', True)
799        return self.render(*args, **attrs)
800
801
802class ListWidget(Widget):
803    """Special widget for list-like fields."""
804
805    def __init__(self, field, name, value, all_errors):
806        Widget.__init__(self, field, name, _force_list(value), all_errors)
807        self._subwidgets = {}
808
809    def as_ul(self, **attrs):
810        return self._as_list(html.ul, attrs)
811
812    def as_ol(self, **attrs):
813        return self._as_list(html.ol, attrs)
814
815    def _as_list(self, factory, attrs):
816        if attrs.pop('hide_empty', False) and not self:
817            return u''
818        items = []
819        for index in xrange(len(self) + attrs.pop('extra_rows', 1)):
820            items.append(html.li(self[index]()) for item in self)
821        # add an invisible item for the validator
822        if not items:
823            items.append(html.li(style='display: none'))
824        return factory(*items, **attrs)
825
826    def __getitem__(self, index):
827        if not isinstance(index, (int, long)):
828            raise TypeError('list widget indices must be integers')
829        subwidget = self._subwidgets.get(index)
830        if subwidget is None:
831            try:
832                value = self._value[index]
833            except IndexError:
834                # return an widget without value if we try
835                # to access a field not in the list
836                value = None
837            subwidget = _make_widget(self._field.field,
838                                     _make_name(self.name, index), value,
839                                     self._all_errors)
840            self._subwidgets[index] = subwidget
841        return subwidget
842
843    def __iter__(self):
844        for index in xrange(len(self)):
845            yield self[index]
846
847    def __len__(self):
848        return len(self._value)
849
850    def __call__(self, *args, **kwargs):
851        return self.as_ul(*args, **kwargs)
852
853
854class ErrorList(_Renderable, list):
855    """The class that is used to display the errors."""
856
857    def render(self, **attrs):
858        return self.as_ul(**attrs)
859
860    def as_ul(self, **attrs):
861        return self._as_list(html.ul, attrs)
862
863    def as_ol(self, **attrs):
864        return self._as_list(html.ol, attrs)
865
866    def _as_list(self, factory, attrs):
867        if attrs.pop('hide_empty', False) and not self:
868            return u''
869        return factory(*(html.li(item) for item in self), **attrs)
870
871    def __call__(self, **attrs):
872        attrs.setdefault('class', attrs.pop('class_', 'errors'))
873        attrs.setdefault('hide_empty', True)
874        return self.render(**attrs)
875
876
877class MultipleValidationErrors(ValidationError):
878    """A validation error subclass for multiple errors raised by
879    subfields.  This is used by the mapping and list fields.
880    """
881
882    def __init__(self, errors):
883        ValidationError.__init__(self, '%d error%s' % (
884            len(errors), len(errors) != 1 and 's' or ''
885        ))
886        self.errors = errors
887
888    def __unicode__(self):
889        return ', '.join(map(unicode, self.errors.itervalues()))
890
891    def unpack(self, key=None):
892        rv = {}
893        for name, error in self.errors.iteritems():
894            rv.update(error.unpack(_make_name(key, name)))
895        return rv
896
897
898class FieldMeta(type):
899
900    def __new__(cls, name, bases, d):
901        messages = {}
902        for base in reversed(bases):
903            if hasattr(base, 'messages'):
904                messages.update(base.messages)
905        if 'messages' in d:
906            messages.update(d['messages'])
907        d['messages'] = messages
908        return type.__new__(cls, name, bases, d)
909
910
911class Field(object):
912    """Abstract field base class."""
913
914    __metaclass__ = FieldMeta
915    messages = dict(required=lazy_gettext('This field is required.'))
916    form = None
917    widget = TextInput
918
919    # these attributes are used by the widgets to get an idea what
920    # choices to display.  Not every field will also validate them.
921    multiple_choices = False
922    choices = ()
923
924    # fields that have this attribute set get special treatment on
925    # validation.  It means that even though a value was not in the
926    # submitted data it's validated against a default value.
927    validate_on_omission = False
928
929    def __init__(self, label=None, help_text=None, validators=None,
930                 widget=None, messages=None, default=missing):
931        self._position_hint = _next_position_hint()
932        self.label = label
933        self.help_text = help_text
934        if validators is None:
935            validators = []
936        self.validators = validators
937        self.custom_converter = None
938        if widget is not None:
939            self.widget = widget
940        if messages:
941            self.messages = self.messages.copy()
942            self.messages.update(messages)
943        self._default = default
944        assert not issubclass(self.widget, InternalWidget), \
945            'can\'t use internal widgets as widgets for fields'
946
947    def __call__(self, value):
948        value = self.convert(value)
949        self.apply_validators(value)
950        return value
951
952    def __copy__(self):
953        return _bind(self, None, None)
954
955    def apply_validators(self, value):
956        """Applies all validators on the value."""
957        if self.should_validate(value):
958            for validate in self.validators:
959                validate(self.form, value)
960
961    def should_validate(self, value):
962        """Per default validate if the value is not None.  This method is
963        called before the custom validators are applied to not perform
964        validation if the field is empty and not required.
965
966        For example a validator like `is_valid_ip` is never called if the
967        value is an empty string and the field hasn't raised a validation
968        error when checking if the field is required.
969        """
970        return value is not None
971
972    def convert(self, value):
973        """This can be overridden by subclasses and performs the value
974        conversion.
975        """
976        return unicode(value)
977
978    def to_primitive(self, value):
979        """Convert a value into a primitve (string or a list/dict of lists,
980        dicts or strings).
981
982        This method must never fail!
983        """
984        return _to_string(value)
985
986    def get_default(self):
987        if callable(self._default):
988            return self._default()
989        return self._default
990
991    def _bind(self, form, memo):
992        """Method that binds a field to a form. If `form` is None, a copy of
993        the field is returned.
994        """
995        if form is not None and self.bound:
996            raise TypeError('%r already bound' % type(self).__name__)
997        rv = object.__new__(self.__class__)
998        rv.__dict__.update(self.__dict__)
999        rv.validators = self.validators[:]
1000        rv.messages = self.messages.copy()
1001        if form is not None:
1002            rv.form = form
1003        return rv
1004
1005    @property
1006    def bound(self):
1007        """True if the form is bound."""
1008        return 'form' in self.__dict__
1009
1010    def __repr__(self):
1011        rv = object.__repr__(self)
1012        if self.bound:
1013            rv = rv[:-1] + ' [bound]>'
1014        return rv
1015
1016
1017class Mapping(Field):
1018    """Apply a set of fields to a dictionary of values.
1019
1020    >>> field = Mapping(name=TextField(), age=IntegerField())
1021    >>> field({'name': u'John Doe', 'age': u'42'})
1022    {'age': 42, 'name': u'John Doe'}
1023
1024    Although it's possible to reassign the widget after field construction
1025    it's not recommended because the `MappingWidget` is the only builtin
1026    widget that is able to handle mapping structures.
1027    """
1028
1029    widget = MappingWidget
1030
1031    def __init__(self, *args, **fields):
1032        Field.__init__(self)
1033        if len(args) == 1:
1034            if fields:
1035                raise TypeError('keyword arguments and dict given')
1036            self.fields = OrderedDict(args[0])
1037        else:
1038            if args:
1039                raise TypeError('no positional arguments allowed if keyword '
1040                                'arguments provided.')
1041            self.fields = OrderedDict(fields)
1042        self.fields.sort(key=lambda i: i[1]._position_hint)
1043
1044    def convert(self, value):
1045        value = _force_dict(value)
1046        errors = {}
1047        result = {}
1048        for name, field in self.fields.iteritems():
1049            try:
1050                result[name] = field(value.get(name))
1051            except ValidationError, e:
1052                errors[name] = e
1053        if errors:
1054            raise MultipleValidationErrors(errors)
1055        return result
1056
1057    def to_primitive(self, value):
1058        value = _force_dict(value)
1059        result = {}
1060        for key, field in self.fields.iteritems():
1061            result[key] = field.to_primitive(value.get(key))
1062        return result
1063
1064    def _bind(self, form, memo):
1065        rv = Field._bind(self, form, memo)
1066        rv.fields = OrderedDict()
1067        for key, field in self.fields.iteritems():
1068            rv.fields[key] = _bind(field, form, memo)
1069        return rv
1070
1071
1072class FormMapping(Mapping):
1073    """Like a mapping but does csrf protection and stuff."""
1074
1075    widget = FormWidget
1076
1077    def convert(self, value):
1078        if self.form is None:
1079            raise TypeError('form mapping without form passed is unable '
1080                            'to convert data')
1081        if self.form.csrf_protected and self.form.request is not None:
1082            token = self.form.request.values.get('_csrf_token')
1083            if token != self.form.csrf_token:
1084                raise ValidationError(_(u'Invalid security token submitted.'))
1085        return Mapping.convert(self, value)
1086
1087
1088class FormAsField(Mapping):
1089    """If a form is converted into a field the returned field object is an
1090    instance of this class.  The behavior is mostly equivalent to a normal
1091    :class:`Mapping` field with the difference that it as an attribute called
1092    :attr:`form_class` that points to the form class it was created from.
1093    """
1094
1095    def __init__(self):
1096        raise TypeError('can\'t create %r instances' %
1097                        self.__class__.__name__)
1098
1099
1100class Multiple(Field):
1101    """Apply a single field to a sequence of values.
1102
1103    >>> field = Multiple(IntegerField())
1104    >>> field([u'1', u'2', u'3'])
1105    [1, 2, 3]
1106
1107    Recommended widgets:
1108
1109    -   `ListWidget` -- the default one and useful if multiple complex
1110        fields are in use.
1111    -   `CheckboxGroup` -- useful in combination with choices
1112    -   `SelectBoxWidget` -- useful in combination with choices
1113    """
1114
1115    widget = ListWidget
1116    messages = dict(too_small=None, too_big=None)
1117    validate_on_omission = True
1118
1119    def __init__(self, field, label=None, help_text=None, min_size=None,
1120                 max_size=None, validators=None, widget=None, messages=None,
1121                 default=missing):
1122        Field.__init__(self, label, help_text, validators, widget, messages,
1123                       default)
1124        self.field = field
1125        self.min_size = min_size
1126        self.max_size = max_size
1127
1128    @property
1129    def multiple_choices(self):
1130        return self.max_size is None or self.max_size > 1
1131
1132    def convert(self, value):
1133        value = _force_list(value)
1134        if self.min_size is not None and len(value) < self.min_size:
1135            message = self.messages['too_small']
1136            if message is None:
1137                message = ngettext(u'Please provide at least %d item.',
1138                                   u'Please provide at least %d items.',
1139                                   self.min_size) % self.min_size
1140            raise ValidationError(message)
1141        if self.max_size is not None and len(value) > self.max_size:
1142            message = self.messages['too_big']
1143            if message is None:
1144                message = ngettext(u'Please provide no more than %d item.',
1145                                   u'Please provide no more than %d items.',
1146                                   self.min_size) % self.min_size
1147            raise ValidationError(message)
1148        result = []
1149        errors = {}
1150        for idx, item in enumerate(value):
1151            try:
1152                result.append(self.field(item))
1153            except ValidationError, e:
1154                errors[idx] = e
1155        if errors:
1156            raise MultipleValidationErrors(errors)
1157        return result
1158
1159    def to_primitive(self, value):
1160        return map(self.field.to_primitive, _force_list(value))
1161
1162    def _bind(self, form, memo):
1163        rv = Field._bind(self, form, memo)
1164        rv.field = _bind(self.field, form, memo)
1165        return rv
1166
1167
1168class CommaSeparated(Multiple):
1169    """Works like the multiple field but for comma separated values:
1170
1171    >>> field = CommaSeparated(IntegerField())
1172    >>> field(u'1, 2, 3')
1173    [1, 2, 3]
1174
1175    The default widget is a `TextInput` but `Textarea` would be a possible
1176    choices as well.
1177    """
1178
1179    widget = TextInput
1180
1181    def __init__(self, field, label=None, help_text=None, min_size=None,
1182                 max_size=None, sep=u',', validators=None, widget=None,
1183                 messages=None, default=missing):
1184        Multiple.__init__(self, field, label, help_text, min_size,
1185                          max_size, validators, widget, messages,
1186                          default)
1187        self.sep = sep
1188
1189    def convert(self, value):
1190        if isinstance(value, basestring):
1191            value = filter(None, [x.strip() for x in value.split(self.sep)])
1192        return Multiple.convert(self, value)
1193
1194    def to_primitive(self, value):
1195        if value is None:
1196            return u''
1197        if isinstance(value, basestring):
1198            return value
1199        return (self.sep + u' ').join(map(self.field.to_primitive, value))
1200
1201
1202class LineSeparated(CommaSeparated):
1203    r"""Works like `CommaSeparated` but uses multiple lines:
1204
1205    >>> field = LineSeparated(IntegerField())
1206    >>> field(u'1\n2\n3')
1207    [1, 2, 3]
1208
1209    The default widget is a `Textarea` and taht is pretty much the only thing
1210    that makes sense for this widget.
1211    """
1212    widget = Textarea
1213
1214    def convert(self, value):
1215        if isinstance(value, basestring):
1216            value = filter(None, [x.strip() for x in value.splitlines()])
1217        return Multiple.convert(self, value)
1218
1219    def to_primitive(self, value):
1220        if value is None:
1221            return u''
1222        if isinstance(value, basestring):
1223            return value
1224        return u'\n'.join(map(self.field.to_primitive, value))
1225
1226
1227class TextField(Field):
1228    """Field for strings.
1229
1230    >>> field = TextField(required=True, min_length=6)
1231    >>> field('foo bar')
1232    u'foo bar'
1233    >>> field('')
1234    Traceback (most recent call last):
1235      ...
1236    ValidationError: This field is required.
1237    """
1238
1239    messages = dict(too_short=None, too_long=None)
1240
1241    def __init__(self, label=None, help_text=None, required=False,
1242                 min_length=None, max_length=None, validators=None,
1243                 widget=None, messages=None, default=missing):
1244        Field.__init__(self, label, help_text, validators, widget, messages,
1245                       default)
1246        self.required = required
1247        self.min_length = min_length
1248        self.max_length = max_length
1249
1250    def convert(self, value):
1251        value = _to_string(value)
1252        if self.required and not value:
1253            raise ValidationError(self.messages['required'])
1254        if value:
1255            if self.min_length is not None and len(value) < self.min_length:
1256                message = self.messages['too_short']
1257                if message is None:
1258                    message = ngettext(u'Please enter at least %d character.',
1259                                       u'Please enter at least %d characters.',
1260                                       self.min_length) % self.min_length
1261                raise ValidationError(message)
1262            if self.max_length is not None and len(value) > self.max_length:
1263                message = self.messages['too_long']
1264                if message is None:
1265                    message = ngettext(u'Please enter no more than %d character.',
1266                                       u'Please enter no more than %d characters.',
1267                                       self.max_length) % self.max_length
1268                raise ValidationError(message)
1269        return value
1270
1271    def should_validate(self, value):
1272        """Validate if the string is not empty."""
1273        return bool(value)
1274
1275
1276class DateTimeField(Field):
1277    """Field for datetime objects.
1278
1279    >>> field = DateTimeField()
1280    >>> field('1970-01-12 00:00')
1281    datetime.datetime(1970, 1, 12, 0, 0)
1282
1283    >>> field('foo')
1284    Traceback (most recent call last):
1285      ...
1286    ValidationError: Please enter a valid date.
1287    """
1288
1289    messages = dict(invalid_date=lazy_gettext('Please enter a valid date.'))
1290
1291    def __init__(self, label=None, help_text=None, required=False,
1292                 rebase=True, validators=None, widget=None, messages=None,
1293                 default=missing):
1294        Field.__init__(self, label, help_text, validators, widget, messages,
1295                       default)
1296        self.required = required
1297        self.rebase = rebase
1298
1299    def convert(self, value):
1300        if isinstance(value, datetime):
1301            return value
1302        value = _to_string(value)
1303        if not value:
1304            if self.required:
1305                raise ValidationError(self.messages['required'])
1306            return None
1307        try:
1308            return parse_datetime(value, rebase=self.rebase)
1309        except ValueError:
1310            raise ValidationError(self.messages['invalid_date'])
1311
1312    def to_primitive(self, value):
1313        if isinstance(value, datetime):
1314            value = format_system_datetime(value, rebase=self.rebase)
1315        return value
1316
1317
1318class ModelField(Field):
1319    """A field that queries for a model.
1320
1321    The first argument is the name of the model, the second the named
1322    argument for `filter_by` (eg: `User` and ``'username'``).  If the
1323    key is not given (None) the primary key is assumed.
1324    """
1325    messages = dict(not_found=lazy_gettext(u'“%(value)s” does not exist'))
1326
1327    def __init__(self, model, key=None, label=None, help_text=None,
1328                 required=False, message=None, validators=None, widget=None,
1329                 messages=None, default=missing, on_not_found=None):
1330        Field.__init__(self, label, help_text, validators, widget, messages,
1331                       default)
1332        self.model = model
1333        self.key = key
1334        self.required = required
1335        self.message = message
1336        self.on_not_found = on_not_found
1337
1338    def convert(self, value):
1339        if isinstance(value, self.model):
1340            return value
1341        if not value:
1342            if self.required:
1343                raise ValidationError(self.messages['required'])
1344            return None
1345        value = self._coerce_value(value)
1346
1347        if self.key is None:
1348            rv = self.model.query.get(value)
1349        else:
1350            rv = self.model.query.filter_by(**{self.key: value}).first()
1351
1352        if rv is None:
1353            if self.on_not_found is not None:
1354                self.on_not_found(value)
1355            raise ValidationError(self.messages['not_found'] %
1356                                  {'value': value})
1357        return rv
1358
1359    def _coerce_value(self, value):
1360        return value
1361
1362    def to_primitive(self, value):
1363        if value is None:
1364            return u''
1365        elif isinstance(value, self.model):
1366            if self.key is None:
1367                value = db.class_mapper(self.model) \
1368                          .primary_key_from_instance(value)[0]
1369            else:
1370                value = getattr(value, self.key)
1371        return unicode(value)
1372
1373
1374class HiddenModelField(ModelField):
1375    """A hidden field that points to a model identified by primary key.
1376    Can be used to pass models through a form.
1377    """
1378    widget = HiddenInput
1379
1380    # these messages should never show up unless ...
1381    #   ... the user tempered with the form data
1382    #   ... or the object went away in the meantime.
1383    messages = dict(
1384        invalid=lazy_gettext('Invalid value.'),
1385        not_found=lazy_gettext('Key does not exist.')
1386    )
1387
1388    def __init__(self, model, key=None, required=False, message=None,
1389                 validators=None, widget=None, messages=None,
1390                 default=missing):
1391        ModelField.__init__(self, model, key, None, None, required,
1392                            message, validators, widget, messages,
1393                            default)
1394
1395    def _coerce_value(self, value):
1396        try:
1397            return int(value)
1398        except (TypeError, ValueError):
1399            raise ValidationError(self.messages['invalid'])
1400
1401
1402class ChoiceField(Field):
1403    """A field that lets a user select one out of many choices.
1404
1405    A choice field accepts some choices that are valid values for it.
1406    Values are compared after converting to unicode which means that
1407    ``1 == "1"``:
1408
1409    >>> field = ChoiceField(choices=[1, 2, 3])
1410    >>> field('1')
1411    1
1412    >>> field('42')
1413    Traceback (most recent call last):
1414      ...
1415    ValidationError: Please enter a valid choice.
1416
1417    Two values `a` and `b` are considered equal if either ``a == b`` or
1418    ``primitive(a) == primitive(b)`` where `primitive` is the primitive
1419    of the value.  Primitives are created with the following algorithm:
1420
1421        1.  if the object is `None` the primitive is the empty string
1422        2.  otherwise the primitive is the string value of the object
1423
1424    A choice field also accepts lists of tuples as argument where the
1425    first item is used for comparing and the second for displaying
1426    (which is used by the `SelectBoxWidget`):
1427
1428    >>> field = ChoiceField(choices=[(0, 'inactive'), (1, 'active')])
1429    >>> field('0')
1430    0
1431
1432    Because all fields are bound to the form before validation it's
1433    possible to assign the choices later:
1434
1435    >>> class MyForm(Form):
1436    ...     status = ChoiceField()
1437    ...
1438    >>> form = MyForm()
1439    >>> form.status.choices = [(0, 'inactive', 1, 'active')]
1440    >>> form.validate({'status': '0'})
1441    True
1442    >>> form.data
1443    {'status': 0}
1444
1445    If a choice field is set to "not required" and a `SelectBox` is used
1446    as widget you have to provide an empty choice or the field cannot be
1447    left blank.
1448
1449    >>> field = ChoiceField(required=False, choices=[('', _('Nothing')),
1450    ...                                              ('1', _('Something'))])
1451    """
1452
1453    widget = SelectBox
1454    messages = dict(
1455        invalid_choice=lazy_gettext('Please enter a valid choice.')
1456    )
1457
1458    def __init__(self, label=None, help_text=None, required=True,
1459                 choices=None, validators=None, widget=None, messages=None,
1460                 default=missing):
1461        Field.__init__(self, label, help_text, validators, widget, messages,
1462                       default)
1463        self.required = required
1464        self.choices = choices
1465
1466    def convert(self, value):
1467        if not value and not self.required:
1468            return
1469        if self.choices:
1470            for choice in self.choices:
1471                if isinstance(choice, tuple):
1472                    choice = choice[0]
1473                if _value_matches_choice(value, choice):
1474                    return choice
1475        raise ValidationError(self.messages['invalid_choice'])
1476
1477    def _bind(self, form, memo):
1478        rv = Field._bind(self, form, memo)
1479        if self.choices is not None:
1480            rv.choices = list(self.choices)
1481        return rv
1482
1483
1484class MultiChoiceField(ChoiceField):
1485    """A field that lets a user select multiple choices."""
1486
1487    multiple_choices = True
1488    messages = dict(too_small=None, too_big=None)
1489    validate_on_omission = True
1490
1491    def __init__(self, label=None, help_text=None, choices=None,
1492                 min_size=None, max_size=None, validators=None,
1493                 widget=None, messages=None, default=missing):
1494        ChoiceField.__init__(self, label, help_text, min_size > 0, choices,
1495                             validators, widget, messages, default)
1496        self.min_size = min_size
1497        self.max_size = max_size
1498
1499    def convert(self, value):
1500        result = []
1501        known_choices = {}
1502        for choice in self.choices:
1503            if isinstance(choice, tuple):
1504                choice = choice[0]
1505            known_choices[choice] = choice
1506            known_choices.setdefault(_to_string(choice), choice)
1507
1508        for value in _to_list(value):
1509            for version in value, _to_string(value):
1510                if version in known_choices:
1511                    result.append(known_choices[version])
1512                    break
1513            else:
1514                raise ValidationError(_(u'“%s” is not a valid choice') %
1515                                      value)
1516
1517        if self.min_size is not None and len(result) < self.min_size:
1518            message = self.messages['too_small']
1519            if message is None:
1520                message = ngettext(u'Please provide at least %d item.',
1521                                   u'Please provide at least %d items.',
1522                                   self.min_size) % self.min_size
1523            raise ValidationError(message)
1524        if self.max_size is not None and len(result) > self.max_size:
1525            message = self.messages['too_big']
1526            if message is None:
1527                message = ngettext(u'Please provide no more than %d item.',
1528                                   u'Please provide no more than %d items.',
1529                                   self.min_size) % self.min_size
1530            raise ValidationError(message)
1531
1532        return result
1533
1534    def to_primitive(self, value):
1535        return map(unicode, _force_list(value))
1536
1537
1538class IntegerField(Field):
1539    """Field for integers.
1540
1541    >>> field = IntegerField(min_value=0, max_value=99)
1542    >>> field('13')
1543    13
1544
1545    >>> field('thirteen')
1546    Traceback (most recent call last):
1547      ...
1548    ValidationError: Please enter a whole number.
1549
1550    >>> field('193')
1551    Traceback (most recent call last):
1552      ...
1553    ValidationError: Ensure this value is less than or equal to 99.
1554    """
1555
1556    messages = dict(
1557        too_small=None,
1558        too_big=None,
1559        no_integer=lazy_gettext('Please enter a whole number.')
1560    )
1561
1562    def __init__(self, label=None, help_text=None, required=False,
1563                 min_value=None, max_value=None, validators=None,
1564                 widget=None, messages=None, default=missing):
1565        Field.__init__(self, label, help_text, validators, widget, messages,
1566                       default)
1567        self.required = required
1568        self.min_value = min_value
1569        self.max_value = max_value
1570
1571    def convert(self, value):
1572        value = _to_string(value)
1573        if not value:
1574            if self.required:
1575                raise ValidationError(self.messages['required'])
1576            return None
1577        try:
1578            value = int(value)
1579        except ValueError:
1580            raise ValidationError(self.messages['no_integer'])
1581
1582        if self.min_value is not None and value < self.min_value:
1583            message = self.messages['too_small']
1584            if message is None:
1585                message = _(u'Ensure this value is greater than or '
1586                            u'equal to %s.') % self.min_value
1587            raise ValidationError(message)
1588        if self.max_value is not None and value > self.max_value:
1589            message = self.messages['too_big']
1590            if message is None:
1591                message = _(u'Ensure this value is less than or '
1592                            u'equal to %s.') % self.max_value
1593            raise ValidationError(message)
1594
1595        return int(value)
1596
1597
1598class BooleanField(Field):
1599    """Field for boolean values.
1600
1601    >>> field = BooleanField()
1602    >>> field('1')
1603    True
1604
1605    >>> field = BooleanField()
1606    >>> field('')
1607    False
1608    """
1609
1610    widget = Checkbox
1611    validate_on_omission = True
1612    choices = [
1613        (u'True', lazy_gettext(u'True')),
1614        (u'False', lazy_gettext(u'False'))
1615    ]
1616
1617    def convert(self, value):
1618        return value != u'False' and bool(value)
1619
1620    def to_primitive(self, value):
1621        if self.convert(value):
1622            return u'True'
1623        return u'False'
1624
1625
1626class FormMeta(type):
1627    """Meta class for forms.  Handles form inheritance and registers
1628    validator functions.
1629    """
1630
1631    def __new__(cls, name, bases, d):
1632        fields = {}
1633        validator_functions = {}
1634        root_validator_functions = []
1635
1636        for base in reversed(bases):
1637            if hasattr(base, '_root_field'):
1638                # base._root_field is always a FormMapping field
1639                fields.update(base._root_field.fields)
1640                root_validator_functions.extend(base._root_field.validators)
1641
1642        for key, value in d.iteritems():
1643            if key.startswith('validate_') and callable(value):
1644                validator_functions[key[9:]] = value
1645            elif isinstance(value, Field):
1646                fields[key] = value
1647                d[key] = FieldDescriptor(key)
1648
1649        for field_name, func in validator_functions.iteritems():
1650            if field_name in fields:
1651                fields[field_name].validators.append(func)
1652
1653        d['_root_field'] = root = FormMapping(**fields)
1654        context_validate = d.get('context_validate')
1655        root.validators.extend(root_validator_functions)
1656        if context_validate is not None:
1657            root.validators.append(context_validate)
1658
1659        return type.__new__(cls, name, bases, d)
1660
1661    def as_field(cls):
1662        """Returns a field object for this form.  The field object returned
1663        is independent of the form and can be modified in the same manner as
1664        a bound field.
1665        """
1666        field = object.__new__(FormAsField)
1667        field.__dict__.update(cls._root_field.__dict__)
1668        field.form_class = cls
1669        field.validators = cls._root_field.validators[:]
1670        field.fields = cls._root_field.fields.copy()
1671        return field
1672
1673    @property
1674    def validators(cls):
1675        return cls._root_field.validators
1676
1677    @property
1678    def fields(cls):
1679        return cls._root_field.fields
1680
1681
1682class FieldDescriptor(object):
1683
1684    def __init__(self, name):
1685        self.name = name
1686
1687    def __get__(self, obj, type=None):
1688        try:
1689            return (obj or type).fields[self.name]
1690        except KeyError:
1691            raise AttributeError(self.name)
1692
1693    def __set__(self, obj, value):
1694        obj.fields[self.name] = value
1695
1696    def __delete__(self, obj):
1697        if self.name not in obj.fields:
1698            raise AttributeError('%r has no attribute %r' %
1699                                 (type(obj).__name__, self.name))
1700        del obj.fields[self.name]
1701
1702
1703class Form(object):
1704    """Form base class.
1705
1706    >>> class PersonForm(Form):
1707    ...     name = TextField(required=True)
1708    ...     age = IntegerField()
1709
1710    >>> form = PersonForm()
1711    >>> form.validate({'name': 'johnny', 'age': '42'})
1712    True
1713    >>> form.data['name']
1714    u'johnny'
1715    >>> form.data['age']
1716    42
1717
1718    Let's cause a simple validation error:
1719
1720    >>> form = PersonForm()
1721    >>> form.validate({'name': '', 'age': 'fourty-two'})
1722    False
1723    >>> print form.errors['age'][0]
1724    Please enter a whole number.
1725    >>> print form.errors['name'][0]
1726    This field is required.
1727
1728    You can also add custom validation routines for fields by adding methods
1729    that start with the prefix ``validate_`` and the field name that take the
1730    value as argument. For example:
1731
1732    >>> class PersonForm(Form):
1733    ...     name = TextField(required=True)
1734    ...     age = IntegerField()
1735    ...
1736    ...     def validate_name(self, value):
1737    ...         if not value.isalpha():
1738    ...             raise ValidationError(u'The value must only contain letters')
1739
1740    >>> form = PersonForm()
1741    >>> form.validate({'name': 'mr.t', 'age': '42'})
1742    False
1743    >>> form.errors
1744    {'name': [u'The value must only contain letters']}
1745
1746    You can also validate multiple fields in the context of other fields.
1747    That validation is performed after all other validations.  Just add a
1748    method called ``context_validate`` that is passed the dict of all fields::
1749
1750    >>> class RegisterForm(Form):
1751    ...     username = TextField(required=True)
1752    ...     password = TextField(required=True)
1753    ...     password_again = TextField(required=True)
1754    ...
1755    ...     def context_validate(self, data):
1756    ...         if data['password'] != data['password_again']:
1757    ...             raise ValidationError(u'The two passwords must be the same')
1758
1759    >>> form = RegisterForm()
1760    >>> form.validate({'username': 'admin', 'password': 'blah',
1761    ...                'password_again': 'blag'})
1762    ...
1763    False
1764    >>> form.errors
1765    {None: [u'The two passwords must be the same']}
1766
1767    Forms can be used as fields for other forms.  To create a form field of
1768    a form you can call the `as_field` class method::
1769
1770    >>> field = RegisterForm.as_field()
1771
1772    This field can be used like any other field class.  What's important about
1773    forms as fields is that validators don't get an instance of `RegisterForm`
1774    passed as `form` / `self` but the form where it's used in if the field is
1775    used from a form.
1776
1777    Form fields are bound to the form on form instanciation.  This makes it
1778    possible to modify a particular instance of the form.  For example you
1779    can create an instance of it and drop some fiels by using
1780    ``del form.fields['name']`` or reassign choices of choice fields.  It's
1781    however not easily possible to add new fields to an instance because newly
1782    added fields wouldn't be bound.  The fields that are stored directly on
1783    the form can also be accessed with their name like a regular attribute.
1784
1785    Example usage:
1786
1787    >>> class StatusForm(Form):
1788    ...     status = ChoiceField()
1789    ...
1790    >>> StatusForm.status.bound
1791    False
1792    >>> form = StatusForm()
1793    >>> form.status.bound
1794    True
1795    >>> form.status.choices = [u'happy', u'unhappy']
1796    >>> form.validate({'status': u'happy'})
1797    True
1798    >>> form['status']
1799    u'happy'
1800
1801    Fields support default values.  These however are not as useful as you
1802    might think.  These defaults are just annotations for external handling.
1803    The form validation system does not respect those values.
1804
1805    They are for example used in the configuration system.
1806
1807    Example:
1808
1809    >>> field = TextField(default=u'foo')
1810    """
1811    __metaclass__ = FormMeta
1812
1813    csrf_protected = True
1814    redirect_tracking = True
1815
1816    def __init__(self, initial=None):
1817        self.request = get_request()
1818        if initial is None:
1819            initial = {}
1820        self.initial = initial
1821        self.invalid_redirect_targets = set()
1822
1823        self._root_field = _bind(self.__class__._root_field, self, {})
1824        self.reset()
1825
1826    def __getitem__(self, key):
1827        return self.data[key]
1828
1829    def __contains__(self, key):
1830        return key in self.data
1831
1832    def as_widget(self):
1833        """Return the form as widget."""
1834        # if there is submitted data, use that for the widget
1835        if self.raw_data is not None:
1836            data = self.raw_data
1837        # otherwise go with the data from the source (eg: database)
1838        else:
1839            data = self.data
1840        return _make_widget(self._root_field, None, data, self.errors)
1841
1842    def add_invalid_redirect_target(self, *args, **kwargs):
1843        """Add an invalid target. Invalid targets are URLs we don't want to
1844        visit again. For example if a post is deleted from the post edit page
1845        it's a bad idea to redirect back to the edit page because in that
1846        situation the edit page would return a page not found.
1847
1848        This function accepts the same parameters as `url_for`.
1849        """
1850        self.invalid_redirect_targets.add(url_for(*args, **kwargs))
1851
1852    @property
1853    def redirect_target(self):
1854        """The back-redirect target for this form."""
1855        return get_redirect_target(self.invalid_redirect_targets,
1856                                   self.request)
1857
1858    def redirect(self, *args, **kwargs):
1859        """Redirects to the url rule given or back to the URL where we are
1860        comming from if `redirect_tracking` is enabled.
1861        """
1862        target = None
1863        if self.redirect_tracking:
1864            target = self.redirect_target
1865        if target is None:
1866            return redirect_to(*args, **kwargs)
1867        return _redirect(target)
1868
1869    @property
1870    def csrf_token(self):
1871        """The unique CSRF security token for this form."""
1872        if self.request is None:
1873            raise AttributeError('no csrf token because form not bound '
1874                                 'to request')
1875        path = self.request.path
1876        user_id = -1
1877        if self.request.user.is_somebody:
1878            user_id = self.request.user.id
1879        login_time = self.request.session.get('lt', -1)
1880        key = self.request.app.cfg['secret_key']
1881        return sha1(('%s|%s|%s|%s' % (path, login_time, user_id, key))
1882                     .encode('utf-8')).hexdigest()
1883
1884    @property
1885    def is_valid(self):
1886        """True if the form is valid."""
1887        return not self.errors
1888
1889    @property
1890    def has_changed(self):
1891        """True if the form has changed."""
1892        return self._root_field.to_primitive(self.initial) != \
1893               self._root_field.to_primitive(self.data)
1894
1895    @property
1896    def fields(self):
1897        return self._root_field.fields
1898
1899    @property
1900    def validators(self):
1901        return self._root_field.validators
1902
1903    def reset(self):
1904        """Resets the form."""
1905        self.data = self.initial.copy()
1906        self.errors = {}
1907        self.raw_data = None
1908
1909    def validate(self, data):
1910        """Validate the form against the data passed."""
1911        self.raw_data = _decode(data)
1912
1913        # for each field in the root that requires validation on value
1914        # omission we add `None` into the raw data dict.  Because the
1915        # implicit switch between initial data and user submitted data
1916        # only happens on the "root level" for obvious reasons we only
1917        # have to hook the data in here.
1918        for name, field in self._root_field.fields.iteritems():
1919            if field.validate_on_omission and name not in self.raw_data:
1920                self.raw_data.setdefault(name)
1921
1922        d = self.data.copy()
1923        d.update(self.raw_data)
1924        errors = {}
1925        try:
1926            data = self._root_field(d)
1927        except ValidationError, e:
1928            errors = e.unpack()
1929        self.errors = errors
1930        if errors:
1931            return False
1932
1933        self.data.update(data)
1934        return True
Note: See TracBrowser for help on using the repository browser.