aboutsummaryrefslogtreecommitdiffstats
path: root/jm2l/ExtWtforms.py
blob: e84418a43976dd92c25ae84a5539b2d92cb4fa77 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
try:
    from html import escape
except ImportError:
    from cgi import escape

#from wtforms import widgets
from wtforms.widgets import HTMLString, html_params
from wtforms.fields.core import Field
from wtforms.compat import text_type, izip

class MySelect(object):
    """
    Renders a select field.

    If `multiple` is True, then the `size` property should be specified on
    rendering to make the field useful.

    The field must provide an `iter_choices()` method which the widget will
    call on rendering; this method must yield tuples of
    `(value, label, selected)`.
    """
    def __init__(self, multiple=False):
        self.multiple = multiple

    def __call__(self, field, **kwargs):
        kwargs.setdefault('id', field.id)
        if self.multiple:
            kwargs['multiple'] = True
        html = ['<select %s>' % html_params(name=field.name, **kwargs)]
        last_group = None
        for group, val, label, selected in field.iter_choices():
            if group is None:
                html.append(self.render_option(val, label, selected))
            elif last_group != group:
                html.append(self.render_optgroup(last_group, group))
                html.append(self.render_option(val, label, selected))
                last_group=group
            else:
                html.append(self.render_option(val, label, selected))
        if last_group:
            html.append(self.render_optgroup(last_group, None))
        html.append('</select>')
        return HTMLString(''.join(html))

    @classmethod
    def render_option(cls, value, label, selected, **kwargs):
        if value is True:
            # Handle the special case of a 'True' value.
            value = text_type(value)

        options = dict(kwargs, value=value)
        if selected:
            options['selected'] = True
        return HTMLString('<option %s>%s</option>' % (html_params(**options), escape(text_type(label), quote=False)))

    @classmethod
    def render_optgroup(cls, previous_label, label, **kwargs):
        options = dict(kwargs)
        if previous_label is None:
            return HTMLString('<optgroup %s label="%s">' % (html_params(**options), escape(text_type(label), quote=False)))
        elif label is None:
            return HTMLString('</optgroup>')
        else:
            return HTMLString('</optgroup><optgroup %s label="%s">' % (html_params(**options), escape(text_type(label), quote=False)))


class MyOption(object):
    """
    Renders the individual option from a select field.

    This is just a convenience for various custom rendering situations, and an
    option by itself does not constitute an entire field.
    """
    def __call__(self, field, **kwargs):
        return MySelect.render_option(field._value(), field.label.text, field.checked, **kwargs)


class MySelectFieldBase(Field):
    #option_widget = widgets.Option()
    option_widget = MyOption()

    """
    Base class for fields which can be iterated to produce options.

    This isn't a field, but an abstract base class for fields which want to
    provide this functionality.
    """
    def __init__(self, label=None, validators=None, option_widget=None, **kwargs):
        super(MySelectFieldBase, self).__init__(label, validators, **kwargs)

        if option_widget is not None:
            self.option_widget = option_widget

    def iter_choices(self):
        """
        Provides data for choice widget rendering. Must return a sequence or
        iterable of (value, label, selected) tuples.
        """
        raise NotImplementedError()

    def __iter__(self):
        opts = dict(widget=self.option_widget, _name=self.name, _form=None, _meta=self.meta)
        for i, (value, label, checked) in enumerate(self.iter_choices()):
            opt = self._Option(label=label, id='%s-%d' % (self.id, i), **opts)
            opt.process(None, value)
            opt.checked = checked
            yield opt

    class _Option(Field):
        checked = False

        def _value(self):
            return text_type(self.data)


class MySelectField(MySelectFieldBase):
    #widget = widgets.Select()
    widget = MySelect()

    def __init__(self, label=None, validators=None, coerce=text_type, choices=None, **kwargs):
        super(MySelectField, self).__init__(label, validators, **kwargs)
        self.coerce = coerce
        self.choices = choices

    def iter_choices(self):
        for choiceA, choiceB in self.choices:
            if isinstance(choiceB, (tuple, list)):
                # We should consider choiceA as an optgroup label
                group_label = choiceA
                for value, label in choiceB:
                    yield (group_label, value, label, self.coerce(value) == self.data)
            else:
                value, label = choiceA, choiceB
                # Not an optgroup, let's fallback to classic usage
                yield (None, value, label, self.coerce(value) == self.data)

    def process_data(self, value):    
        try:
            self.data = self.coerce(value)           
        except (ValueError, TypeError):
            self.data = None

    def process_formdata(self, valuelist):
        if valuelist:
            try:
                self.data = self.coerce(valuelist[0])
            except ValueError:
                raise ValueError(self.gettext('Invalid Choice: could not coerce'))

    def pre_validate(self, form):
        for choiceA, choiceB in self.choices:
            if isinstance(choiceB, (tuple, list)):        
                for value, label in choiceB:
                    if self.data == value:
                        break
                if self.data == value:
                    break
            else:
                value, label = choiceA, choiceB
                if self.data == value:
                    break
        else:
            raise ValueError(self.gettext('Not a valid choice'))