1from __future__ import unicode_literals
2
3import sys
4
5from datetime import date, datetime
6from decimal import Decimal, ROUND_UP, ROUND_DOWN
7from unittest import TestCase
8
9from wtforms import validators, widgets, meta
10from wtforms.fields import *
11from wtforms.fields import Label, Field, SelectFieldBase, html5
12from wtforms.form import Form
13from wtforms.compat import text_type
14from wtforms.utils import unset_value
15from tests.common import DummyPostData
16
17PYTHON_VERSION = sys.version_info
18
19
20class AttrDict(object):
21    def __init__(self, *args, **kw):
22        self.__dict__.update(*args, **kw)
23
24
25def make_form(_name='F', **fields):
26    return type(str(_name), (Form, ), fields)
27
28
29class DefaultsTest(TestCase):
30    def test(self):
31        expected = 42
32
33        def default_callable():
34            return expected
35
36        test_value = TextField(default=expected).bind(Form(), 'a')
37        test_value.process(None)
38        self.assertEqual(test_value.data, expected)
39
40        test_callable = TextField(default=default_callable).bind(Form(), 'a')
41        test_callable.process(None)
42        self.assertEqual(test_callable.data, expected)
43
44
45class LabelTest(TestCase):
46    def test(self):
47        expected = """<label for="test">Caption</label>"""
48        label = Label('test', 'Caption')
49        self.assertEqual(label(), expected)
50        self.assertEqual(str(label), expected)
51        self.assertEqual(text_type(label), expected)
52        self.assertEqual(label.__html__(), expected)
53        self.assertEqual(label().__html__(), expected)
54        self.assertEqual(label('hello'), """<label for="test">hello</label>""")
55        self.assertEqual(TextField('hi').bind(Form(), 'a').label.text, 'hi')
56        if PYTHON_VERSION < (3,):
57            self.assertEqual(repr(label), "Label(u'test', u'Caption')")
58        else:
59            self.assertEqual(repr(label), "Label('test', 'Caption')")
60            self.assertEqual(label.__unicode__(), expected)
61
62    def test_auto_label(self):
63        t1 = TextField().bind(Form(), 'foo_bar')
64        self.assertEqual(t1.label.text, 'Foo Bar')
65
66        t2 = TextField('').bind(Form(), 'foo_bar')
67        self.assertEqual(t2.label.text, '')
68
69    def test_override_for(self):
70        label = Label('test', 'Caption')
71        self.assertEqual(label(for_='foo'), """<label for="foo">Caption</label>""")
72        self.assertEqual(label(**{'for': 'bar'}), """<label for="bar">Caption</label>""")
73
74
75class FlagsTest(TestCase):
76    def setUp(self):
77        t = TextField(validators=[validators.Required()]).bind(Form(), 'a')
78        self.flags = t.flags
79
80    def test_existing_values(self):
81        self.assertEqual(self.flags.required, True)
82        self.assertTrue('required' in self.flags)
83        self.assertEqual(self.flags.optional, False)
84        self.assertTrue('optional' not in self.flags)
85
86    def test_assignment(self):
87        self.assertTrue('optional' not in self.flags)
88        self.flags.optional = True
89        self.assertEqual(self.flags.optional, True)
90        self.assertTrue('optional' in self.flags)
91
92    def test_unset(self):
93        self.flags.required = False
94        self.assertEqual(self.flags.required, False)
95        self.assertTrue('required' not in self.flags)
96
97    def test_repr(self):
98        self.assertEqual(repr(self.flags), '<wtforms.fields.Flags: {required}>')
99
100    def test_underscore_property(self):
101        self.assertRaises(AttributeError, getattr, self.flags, '_foo')
102        self.flags._foo = 42
103        self.assertEqual(self.flags._foo, 42)
104
105
106class UnsetValueTest(TestCase):
107    def test(self):
108        self.assertEqual(str(unset_value), '<unset value>')
109        self.assertEqual(repr(unset_value), '<unset value>')
110        self.assertEqual(bool(unset_value), False)
111        assert not unset_value
112        self.assertEqual(unset_value.__nonzero__(), False)
113        self.assertEqual(unset_value.__bool__(), False)
114
115
116class FiltersTest(TestCase):
117    class F(Form):
118        a = TextField(default=' hello', filters=[lambda x: x.strip()])
119        b = TextField(default='42', filters=[int, lambda x: -x])
120
121    def test_working(self):
122        form = self.F()
123        self.assertEqual(form.a.data, 'hello')
124        self.assertEqual(form.b.data, -42)
125        assert form.validate()
126
127    def test_failure(self):
128        form = self.F(DummyPostData(a=['  foo bar  '], b=['hi']))
129        self.assertEqual(form.a.data, 'foo bar')
130        self.assertEqual(form.b.data, 'hi')
131        self.assertEqual(len(form.b.process_errors), 1)
132        assert not form.validate()
133
134
135class FieldTest(TestCase):
136    class F(Form):
137        a = TextField(default='hello', render_kw={'readonly': True, 'foo': u'bar'})
138
139    def setUp(self):
140        self.field = self.F().a
141
142    def test_unbound_field(self):
143        unbound = self.F.a
144        assert unbound.creation_counter != 0
145        assert unbound.field_class is TextField
146        self.assertEqual(unbound.args, ())
147        self.assertEqual(unbound.kwargs, {'default': 'hello', 'render_kw': {'readonly': True, 'foo': u'bar'}})
148        assert repr(unbound).startswith('<UnboundField(TextField')
149
150    def test_htmlstring(self):
151        self.assertTrue(isinstance(self.field.__html__(), widgets.HTMLString))
152
153    def test_str_coerce(self):
154        self.assertTrue(isinstance(str(self.field), str))
155        self.assertEqual(str(self.field), str(self.field()))
156
157    def test_unicode_coerce(self):
158        self.assertEqual(text_type(self.field), self.field())
159
160    def test_process_formdata(self):
161        Field.process_formdata(self.field, [42])
162        self.assertEqual(self.field.data, 42)
163
164    def test_meta_attribute(self):
165        # Can we pass in meta via _form?
166        form = self.F()
167        assert form.a.meta is form.meta
168
169        # Can we pass in meta via _meta?
170        form_meta = meta.DefaultMeta()
171        field = TextField(_name='Foo', _form=None, _meta=form_meta)
172        assert field.meta is form_meta
173
174        # Do we fail if both _meta and _form are None?
175        self.assertRaises(TypeError, TextField, _name='foo', _form=None)
176
177    def test_render_kw(self):
178        form = self.F()
179        self.assertEqual(form.a(), u'<input foo="bar" id="a" name="a" readonly type="text" value="hello">')
180        self.assertEqual(form.a(foo=u'baz'), u'<input foo="baz" id="a" name="a" readonly type="text" value="hello">')
181        self.assertEqual(
182            form.a(foo=u'baz', readonly=False, other='hello'),
183            u'<input foo="baz" id="a" name="a" other="hello" type="text" value="hello">'
184        )
185
186
187class PrePostTestField(TextField):
188    def pre_validate(self, form):
189        if self.data == "stoponly":
190            raise validators.StopValidation()
191        elif self.data.startswith("stop"):
192            raise validators.StopValidation("stop with message")
193
194    def post_validate(self, form, stopped):
195        if self.data == "p":
196            raise ValueError("Post")
197        elif stopped and self.data == "stop-post":
198            raise ValueError("Post-stopped")
199
200
201class PrePostValidationTest(TestCase):
202    class F(Form):
203        a = PrePostTestField(validators=[validators.Length(max=1, message="too long")])
204
205    def _init_field(self, value):
206        form = self.F(a=value)
207        form.validate()
208        return form.a
209
210    def test_pre_stop(self):
211        a = self._init_field("long")
212        self.assertEqual(a.errors, ["too long"])
213
214        stoponly = self._init_field("stoponly")
215        self.assertEqual(stoponly.errors, [])
216
217        stopmessage = self._init_field("stopmessage")
218        self.assertEqual(stopmessage.errors, ["stop with message"])
219
220    def test_post(self):
221        a = self._init_field("p")
222        self.assertEqual(a.errors, ["Post"])
223        stopped = self._init_field("stop-post")
224        self.assertEqual(stopped.errors, ["stop with message", "Post-stopped"])
225
226
227class SelectFieldTest(TestCase):
228    class F(Form):
229        a = SelectField(choices=[('a', 'hello'), ('btest', 'bye')], default='a')
230        b = SelectField(choices=[(1, 'Item 1'), (2, 'Item 2')], coerce=int, option_widget=widgets.TextInput())
231
232    def test_defaults(self):
233        form = self.F()
234        self.assertEqual(form.a.data, 'a')
235        self.assertEqual(form.b.data, None)
236        self.assertEqual(form.validate(), False)
237        self.assertEqual(form.a(), """<select id="a" name="a"><option selected value="a">hello</option><option value="btest">bye</option></select>""")
238        self.assertEqual(form.b(), """<select id="b" name="b"><option value="1">Item 1</option><option value="2">Item 2</option></select>""")
239
240    def test_with_data(self):
241        form = self.F(DummyPostData(a=['btest']))
242        self.assertEqual(form.a.data, 'btest')
243        self.assertEqual(form.a(), """<select id="a" name="a"><option value="a">hello</option><option selected value="btest">bye</option></select>""")
244
245    def test_value_coercion(self):
246        form = self.F(DummyPostData(b=['2']))
247        self.assertEqual(form.b.data, 2)
248        self.assertTrue(form.b.validate(form))
249        form = self.F(DummyPostData(b=['b']))
250        self.assertEqual(form.b.data, None)
251        self.assertFalse(form.b.validate(form))
252
253    def test_iterable_options(self):
254        form = self.F()
255        first_option = list(form.a)[0]
256        self.assertTrue(isinstance(first_option, form.a._Option))
257        self.assertEqual(
258            list(text_type(x) for x in form.a),
259            ['<option selected value="a">hello</option>', '<option value="btest">bye</option>']
260        )
261        self.assertTrue(isinstance(first_option.widget, widgets.Option))
262        self.assertTrue(isinstance(list(form.b)[0].widget, widgets.TextInput))
263        self.assertEqual(first_option(disabled=True), '<option disabled selected value="a">hello</option>')
264
265    def test_default_coerce(self):
266        F = make_form(a=SelectField(choices=[('a', 'Foo')]))
267        form = F(DummyPostData(a=[]))
268        assert not form.validate()
269        self.assertEqual(form.a.data, 'None')
270        self.assertEqual(len(form.a.errors), 1)
271        self.assertEqual(form.a.errors[0], 'Not a valid choice')
272
273
274class SelectMultipleFieldTest(TestCase):
275    class F(Form):
276        a = SelectMultipleField(choices=[('a', 'hello'), ('b', 'bye'), ('c', 'something')], default=('a', ))
277        b = SelectMultipleField(coerce=int, choices=[(1, 'A'), (2, 'B'), (3, 'C')], default=("1", "3"))
278
279    def test_defaults(self):
280        form = self.F()
281        self.assertEqual(form.a.data, ['a'])
282        self.assertEqual(form.b.data, [1, 3])
283        # Test for possible regression with null data
284        form.a.data = None
285        self.assertTrue(form.validate())
286        self.assertEqual(list(form.a.iter_choices()), [(v, l, False) for v, l in form.a.choices])
287
288    def test_with_data(self):
289        form = self.F(DummyPostData(a=['a', 'c']))
290        self.assertEqual(form.a.data, ['a', 'c'])
291        self.assertEqual(list(form.a.iter_choices()), [('a', 'hello', True), ('b', 'bye', False), ('c', 'something', True)])
292        self.assertEqual(form.b.data, [])
293        form = self.F(DummyPostData(b=['1', '2']))
294        self.assertEqual(form.b.data, [1, 2])
295        self.assertTrue(form.validate())
296        form = self.F(DummyPostData(b=['1', '2', '4']))
297        self.assertEqual(form.b.data, [1, 2, 4])
298        self.assertFalse(form.validate())
299
300    def test_coerce_fail(self):
301        form = self.F(b=['a'])
302        assert form.validate()
303        self.assertEqual(form.b.data, None)
304        form = self.F(DummyPostData(b=['fake']))
305        assert not form.validate()
306        self.assertEqual(form.b.data, [1, 3])
307
308
309class RadioFieldTest(TestCase):
310    class F(Form):
311        a = RadioField(choices=[('a', 'hello'), ('b', 'bye')], default='a')
312        b = RadioField(choices=[(1, 'Item 1'), (2, 'Item 2')], coerce=int)
313
314    def test(self):
315        form = self.F()
316        self.assertEqual(form.a.data, 'a')
317        self.assertEqual(form.b.data, None)
318        self.assertEqual(form.validate(), False)
319        self.assertEqual(
320            form.a(),
321            (
322                """<ul id="a">"""
323                """<li><input checked id="a-0" name="a" type="radio" value="a"> <label for="a-0">hello</label></li>"""
324                """<li><input id="a-1" name="a" type="radio" value="b"> <label for="a-1">bye</label></li></ul>"""
325            )
326        )
327        self.assertEqual(
328            form.b(),
329            (
330                """<ul id="b">"""
331                """<li><input id="b-0" name="b" type="radio" value="1"> <label for="b-0">Item 1</label></li>"""
332                """<li><input id="b-1" name="b" type="radio" value="2"> <label for="b-1">Item 2</label></li></ul>"""
333            )
334        )
335        self.assertEqual(
336            [text_type(x) for x in form.a],
337            ['<input checked id="a-0" name="a" type="radio" value="a">', '<input id="a-1" name="a" type="radio" value="b">']
338        )
339
340    def test_text_coercion(self):
341        # Regression test for text coercsion scenarios where the value is a boolean.
342        coerce_func = lambda x: False if x == 'False' else bool(x)
343        F = make_form(a=RadioField(choices=[(True, 'yes'), (False, 'no')], coerce=coerce_func))
344        form = F()
345        self.assertEqual(
346            form.a(),
347            '''<ul id="a">'''
348            '''<li><input id="a-0" name="a" type="radio" value="True"> <label for="a-0">yes</label></li>'''
349            '''<li><input checked id="a-1" name="a" type="radio" value="False"> <label for="a-1">no</label></li></ul>'''
350        )
351
352
353class TextFieldTest(TestCase):
354    class F(Form):
355        a = TextField()
356
357    def test(self):
358        form = self.F()
359        self.assertEqual(form.a.data, None)
360        self.assertEqual(form.a(), """<input id="a" name="a" type="text" value="">""")
361        form = self.F(DummyPostData(a=['hello']))
362        self.assertEqual(form.a.data, 'hello')
363        self.assertEqual(form.a(), """<input id="a" name="a" type="text" value="hello">""")
364        form = self.F(DummyPostData(b=['hello']))
365        self.assertEqual(form.a.data, '')
366
367
368class HiddenFieldTest(TestCase):
369    class F(Form):
370        a = HiddenField(default="LE DEFAULT")
371
372    def test(self):
373        form = self.F()
374        self.assertEqual(form.a(), """<input id="a" name="a" type="hidden" value="LE DEFAULT">""")
375        self.assertTrue(form.a.flags.hidden)
376
377
378class TextAreaFieldTest(TestCase):
379    class F(Form):
380        a = TextAreaField(default="LE DEFAULT")
381
382    def test(self):
383        form = self.F()
384        self.assertEqual(form.a(), """<textarea id="a" name="a">LE DEFAULT</textarea>""")
385
386
387class PasswordFieldTest(TestCase):
388    class F(Form):
389        a = PasswordField(widget=widgets.PasswordInput(hide_value=False), default="LE DEFAULT")
390        b = PasswordField(default="Hai")
391
392    def test(self):
393        form = self.F()
394        self.assertEqual(form.a(), """<input id="a" name="a" type="password" value="LE DEFAULT">""")
395        self.assertEqual(form.b(), """<input id="b" name="b" type="password" value="">""")
396
397
398class FileFieldTest(TestCase):
399    class F(Form):
400        a = FileField(default="LE DEFAULT")
401
402    def test(self):
403        form = self.F()
404        self.assertEqual(form.a(), """<input id="a" name="a" type="file">""")
405
406
407class IntegerFieldTest(TestCase):
408    class F(Form):
409        a = IntegerField()
410        b = IntegerField(default=48)
411
412    def test(self):
413        form = self.F(DummyPostData(a=['v'], b=['-15']))
414        self.assertEqual(form.a.data, None)
415        self.assertEqual(form.a.raw_data, ['v'])
416        self.assertEqual(form.a(), """<input id="a" name="a" type="text" value="v">""")
417        self.assertEqual(form.b.data, -15)
418        self.assertEqual(form.b(), """<input id="b" name="b" type="text" value="-15">""")
419        self.assertTrue(not form.a.validate(form))
420        self.assertTrue(form.b.validate(form))
421        form = self.F(DummyPostData(a=[], b=['']))
422        self.assertEqual(form.a.data, None)
423        self.assertEqual(form.a.raw_data, [])
424        self.assertEqual(form.b.data, None)
425        self.assertEqual(form.b.raw_data, [''])
426        self.assertTrue(not form.validate())
427        self.assertEqual(len(form.b.process_errors), 1)
428        self.assertEqual(len(form.b.errors), 1)
429        form = self.F(b=9)
430        self.assertEqual(form.b.data, 9)
431        self.assertEqual(form.a._value(), '')
432        self.assertEqual(form.b._value(), '9')
433
434
435class DecimalFieldTest(TestCase):
436    def test(self):
437        F = make_form(a=DecimalField())
438        form = F(DummyPostData(a='2.1'))
439        self.assertEqual(form.a.data, Decimal('2.1'))
440        self.assertEqual(form.a._value(), '2.1')
441        form.a.raw_data = None
442        self.assertEqual(form.a._value(), '2.10')
443        self.assertTrue(form.validate())
444        form = F(DummyPostData(a='2,1'), a=Decimal(5))
445        self.assertEqual(form.a.data, None)
446        self.assertEqual(form.a.raw_data, ['2,1'])
447        self.assertFalse(form.validate())
448        form = F(DummyPostData(a='asdf'), a=Decimal('.21'))
449        self.assertEqual(form.a._value(), 'asdf')
450        assert not form.validate()
451
452    def test_quantize(self):
453        F = make_form(a=DecimalField(places=3, rounding=ROUND_UP), b=DecimalField(places=None))
454        form = F(a=Decimal('3.1415926535'))
455        self.assertEqual(form.a._value(), '3.142')
456        form.a.rounding = ROUND_DOWN
457        self.assertEqual(form.a._value(), '3.141')
458        self.assertEqual(form.b._value(), '')
459        form = F(a=3.14159265, b=72)
460        self.assertEqual(form.a._value(), '3.142')
461        self.assertTrue(isinstance(form.a.data, float))
462        self.assertEqual(form.b._value(), '72')
463
464
465class FloatFieldTest(TestCase):
466    class F(Form):
467        a = FloatField()
468        b = FloatField(default=48.0)
469
470    def test(self):
471        form = self.F(DummyPostData(a=['v'], b=['-15.0']))
472        self.assertEqual(form.a.data, None)
473        self.assertEqual(form.a.raw_data, ['v'])
474        self.assertEqual(form.a(), """<input id="a" name="a" type="text" value="v">""")
475        self.assertEqual(form.b.data, -15.0)
476        self.assertEqual(form.b(), """<input id="b" name="b" type="text" value="-15.0">""")
477        self.assertFalse(form.a.validate(form))
478        self.assertTrue(form.b.validate(form))
479        form = self.F(DummyPostData(a=[], b=['']))
480        self.assertEqual(form.a.data, None)
481        self.assertEqual(form.a._value(), '')
482        self.assertEqual(form.b.data, None)
483        self.assertEqual(form.b.raw_data, [''])
484        self.assertFalse(form.validate())
485        self.assertEqual(len(form.b.process_errors), 1)
486        self.assertEqual(len(form.b.errors), 1)
487        form = self.F(b=9.0)
488        self.assertEqual(form.b.data, 9.0)
489        self.assertEqual(form.b._value(), "9.0")
490
491
492class BooleanFieldTest(TestCase):
493    class BoringForm(Form):
494        bool1 = BooleanField()
495        bool2 = BooleanField(default=True, false_values=())
496
497    obj = AttrDict(bool1=None, bool2=True)
498
499    def test_defaults(self):
500        # Test with no post data to make sure defaults work
501        form = self.BoringForm()
502        self.assertEqual(form.bool1.raw_data, None)
503        self.assertEqual(form.bool1.data, False)
504        self.assertEqual(form.bool2.data, True)
505
506    def test_rendering(self):
507        form = self.BoringForm(DummyPostData(bool2="x"))
508        self.assertEqual(form.bool1(), '<input id="bool1" name="bool1" type="checkbox" value="y">')
509        self.assertEqual(form.bool2(), '<input checked id="bool2" name="bool2" type="checkbox" value="x">')
510        self.assertEqual(form.bool2.raw_data, ['x'])
511
512    def test_with_postdata(self):
513        form = self.BoringForm(DummyPostData(bool1=['a']))
514        self.assertEqual(form.bool1.raw_data, ['a'])
515        self.assertEqual(form.bool1.data, True)
516        form = self.BoringForm(DummyPostData(bool1=['false'], bool2=['false']))
517        self.assertEqual(form.bool1.data, False)
518        self.assertEqual(form.bool2.data, True)
519
520    def test_with_model_data(self):
521        form = self.BoringForm(obj=self.obj)
522        self.assertEqual(form.bool1.data, False)
523        self.assertEqual(form.bool1.raw_data, None)
524        self.assertEqual(form.bool2.data, True)
525
526    def test_with_postdata_and_model(self):
527        form = self.BoringForm(DummyPostData(bool1=['y']), obj=self.obj)
528        self.assertEqual(form.bool1.data, True)
529        self.assertEqual(form.bool2.data, False)
530
531
532class DateFieldTest(TestCase):
533    class F(Form):
534        a = DateField()
535        b = DateField(format='%m/%d %Y')
536
537    def test_basic(self):
538        d = date(2008, 5, 7)
539        form = self.F(DummyPostData(a=['2008-05-07'], b=['05/07', '2008']))
540        self.assertEqual(form.a.data, d)
541        self.assertEqual(form.a._value(), '2008-05-07')
542        self.assertEqual(form.b.data, d)
543        self.assertEqual(form.b._value(), '05/07 2008')
544
545    def test_failure(self):
546        form = self.F(DummyPostData(a=['2008-bb-cc'], b=['hi']))
547        assert not form.validate()
548        self.assertEqual(len(form.a.process_errors), 1)
549        self.assertEqual(len(form.a.errors), 1)
550        self.assertEqual(len(form.b.errors), 1)
551        self.assertEqual(form.a.process_errors[0], 'Not a valid date value')
552
553
554class DateTimeFieldTest(TestCase):
555    class F(Form):
556        a = DateTimeField()
557        b = DateTimeField(format='%Y-%m-%d %H:%M')
558
559    def test_basic(self):
560        d = datetime(2008, 5, 5, 4, 30, 0, 0)
561        # Basic test with both inputs
562        form = self.F(DummyPostData(a=['2008-05-05', '04:30:00'], b=['2008-05-05 04:30']))
563        self.assertEqual(form.a.data, d)
564        self.assertEqual(form.a(), """<input id="a" name="a" type="text" value="2008-05-05 04:30:00">""")
565        self.assertEqual(form.b.data, d)
566        self.assertEqual(form.b(), """<input id="b" name="b" type="text" value="2008-05-05 04:30">""")
567        self.assertTrue(form.validate())
568
569        # Test with a missing input
570        form = self.F(DummyPostData(a=['2008-05-05']))
571        self.assertFalse(form.validate())
572        self.assertEqual(form.a.errors[0], 'Not a valid datetime value')
573
574        form = self.F(a=d, b=d)
575        self.assertTrue(form.validate())
576        self.assertEqual(form.a._value(), '2008-05-05 04:30:00')
577
578    def test_microseconds(self):
579        d = datetime(2011, 5, 7, 3, 23, 14, 424200)
580        F = make_form(a=DateTimeField(format='%Y-%m-%d %H:%M:%S.%f'))
581        form = F(DummyPostData(a=['2011-05-07 03:23:14.4242']))
582        self.assertEqual(d, form.a.data)
583
584
585class SubmitFieldTest(TestCase):
586    class F(Form):
587        a = SubmitField('Label')
588
589    def test(self):
590        self.assertEqual(self.F().a(), """<input id="a" name="a" type="submit" value="Label">""")
591
592
593class FormFieldTest(TestCase):
594    def setUp(self):
595        F = make_form(
596            a=TextField(validators=[validators.required()]),
597            b=TextField(),
598        )
599        self.F1 = make_form('F1', a=FormField(F))
600        self.F2 = make_form('F2', a=FormField(F, separator='::'))
601
602    def test_formdata(self):
603        form = self.F1(DummyPostData({'a-a': ['moo']}))
604        self.assertEqual(form.a.form.a.name, 'a-a')
605        self.assertEqual(form.a['a'].data, 'moo')
606        self.assertEqual(form.a['b'].data, '')
607        self.assertTrue(form.validate())
608
609    def test_iteration(self):
610        self.assertEqual([x.name for x in self.F1().a], ['a-a', 'a-b'])
611
612    def test_with_obj(self):
613        obj = AttrDict(a=AttrDict(a='mmm'))
614        form = self.F1(obj=obj)
615        self.assertEqual(form.a.form.a.data, 'mmm')
616        self.assertEqual(form.a.form.b.data, None)
617        obj_inner = AttrDict(a=None, b='rawr')
618        obj2 = AttrDict(a=obj_inner)
619        form.populate_obj(obj2)
620        self.assertTrue(obj2.a is obj_inner)
621        self.assertEqual(obj_inner.a, 'mmm')
622        self.assertEqual(obj_inner.b, None)
623
624    def test_widget(self):
625        self.assertEqual(
626            self.F1().a(),
627            '''<table id="a">'''
628            '''<tr><th><label for="a-a">A</label></th><td><input id="a-a" name="a-a" type="text" value=""></td></tr>'''
629            '''<tr><th><label for="a-b">B</label></th><td><input id="a-b" name="a-b" type="text" value=""></td></tr>'''
630            '''</table>'''
631        )
632
633    def test_separator(self):
634        form = self.F2(DummyPostData({'a-a': 'fake', 'a::a': 'real'}))
635        self.assertEqual(form.a.a.name, 'a::a')
636        self.assertEqual(form.a.a.data, 'real')
637        self.assertTrue(form.validate())
638
639    def test_no_validators_or_filters(self):
640        class A(Form):
641            a = FormField(self.F1, validators=[validators.required()])
642        self.assertRaises(TypeError, A)
643
644        class B(Form):
645            a = FormField(self.F1, filters=[lambda x: x])
646        self.assertRaises(TypeError, B)
647
648        class C(Form):
649            a = FormField(self.F1)
650
651            def validate_a(form, field):
652                pass
653        form = C()
654        self.assertRaises(TypeError, form.validate)
655
656    def test_populate_missing_obj(self):
657        obj = AttrDict(a=None)
658        obj2 = AttrDict(a=AttrDict(a='mmm'))
659        form = self.F1()
660        self.assertRaises(TypeError, form.populate_obj, obj)
661        form.populate_obj(obj2)
662
663
664class FieldListTest(TestCase):
665    t = TextField(validators=[validators.Required()])
666
667    def test_form(self):
668        F = make_form(a=FieldList(self.t))
669        data = ['foo', 'hi', 'rawr']
670        a = F(a=data).a
671        self.assertEqual(a.entries[1].data, 'hi')
672        self.assertEqual(a.entries[1].name, 'a-1')
673        self.assertEqual(a.data, data)
674        self.assertEqual(len(a.entries), 3)
675
676        pdata = DummyPostData({'a-0': ['bleh'], 'a-3': ['yarg'], 'a-4': [''], 'a-7': ['mmm']})
677        form = F(pdata)
678        self.assertEqual(len(form.a.entries), 4)
679        self.assertEqual(form.a.data, ['bleh', 'yarg', '', 'mmm'])
680        self.assertFalse(form.validate())
681
682        form = F(pdata, a=data)
683        self.assertEqual(form.a.data, ['bleh', 'yarg', '', 'mmm'])
684        self.assertFalse(form.validate())
685
686        # Test for formdata precedence
687        pdata = DummyPostData({'a-0': ['a'], 'a-1': ['b']})
688        form = F(pdata, a=data)
689        self.assertEqual(len(form.a.entries), 2)
690        self.assertEqual(form.a.data, ['a', 'b'])
691        self.assertEqual(list(iter(form.a)), list(form.a.entries))
692
693    def test_enclosed_subform(self):
694        make_inner = lambda: AttrDict(a=None)
695        F = make_form(
696            a=FieldList(FormField(make_form('FChild', a=self.t), default=make_inner))
697        )
698        data = [{'a': 'hello'}]
699        form = F(a=data)
700        self.assertEqual(form.a.data, data)
701        self.assertTrue(form.validate())
702        form.a.append_entry()
703        self.assertEqual(form.a.data, data + [{'a': None}])
704        self.assertFalse(form.validate())
705
706        pdata = DummyPostData({'a-0': ['fake'], 'a-0-a': ['foo'], 'a-1-a': ['bar']})
707        form = F(pdata, a=data)
708        self.assertEqual(form.a.data, [{'a': 'foo'}, {'a': 'bar'}])
709
710        inner_obj = make_inner()
711        inner_list = [inner_obj]
712        obj = AttrDict(a=inner_list)
713        form.populate_obj(obj)
714        self.assertTrue(obj.a is not inner_list)
715        self.assertEqual(len(obj.a), 2)
716        self.assertTrue(obj.a[0] is inner_obj)
717        self.assertEqual(obj.a[0].a, 'foo')
718        self.assertEqual(obj.a[1].a, 'bar')
719
720        # Test failure on populate
721        obj2 = AttrDict(a=42)
722        self.assertRaises(TypeError, form.populate_obj, obj2)
723
724    def test_entry_management(self):
725        F = make_form(a=FieldList(self.t))
726        a = F(a=['hello', 'bye']).a
727        self.assertEqual(a.pop_entry().name, 'a-1')
728        self.assertEqual(a.data, ['hello'])
729        a.append_entry('orange')
730        self.assertEqual(a.data, ['hello', 'orange'])
731        self.assertEqual(a[-1].name, 'a-1')
732        self.assertEqual(a.pop_entry().data, 'orange')
733        self.assertEqual(a.pop_entry().name, 'a-0')
734        self.assertRaises(IndexError, a.pop_entry)
735
736    def test_min_max_entries(self):
737        F = make_form(a=FieldList(self.t, min_entries=1, max_entries=3))
738        a = F().a
739        self.assertEqual(len(a), 1)
740        self.assertEqual(a[0].data, None)
741        big_input = ['foo', 'flaf', 'bar', 'baz']
742        self.assertRaises(AssertionError, F, a=big_input)
743        pdata = DummyPostData(('a-%d' % i, v) for i, v in enumerate(big_input))
744        a = F(pdata).a
745        self.assertEqual(a.data, ['foo', 'flaf', 'bar'])
746        self.assertRaises(AssertionError, a.append_entry)
747
748    def test_validators(self):
749        def validator(form, field):
750            if field.data and field.data[0] == 'fail':
751                raise ValueError('fail')
752            elif len(field.data) > 2:
753                raise ValueError('too many')
754
755        F = make_form(a=FieldList(self.t, validators=[validator]))
756
757        # Case 1: length checking validators work as expected.
758        fdata = DummyPostData({'a-0': ['hello'], 'a-1': ['bye'], 'a-2': ['test3']})
759        form = F(fdata)
760        assert not form.validate()
761        self.assertEqual(form.a.errors, ['too many'])
762
763        # Case 2: checking a value within.
764        fdata['a-0'] = ['fail']
765        form = F(fdata)
766        assert not form.validate()
767        self.assertEqual(form.a.errors, ['fail'])
768
769        # Case 3: normal field validator still works
770        form = F(DummyPostData({'a-0': ['']}))
771        assert not form.validate()
772        self.assertEqual(form.a.errors, [['This field is required.']])
773
774    def test_no_filters(self):
775        my_filter = lambda x: x
776        self.assertRaises(TypeError, FieldList, self.t, filters=[my_filter], _form=Form(), _name='foo')
777
778    def test_process_prefilled(self):
779        data = ['foo', 'hi', 'rawr']
780
781        class A(object):
782            def __init__(self, a):
783                self.a = a
784        obj = A(data)
785        F = make_form(a=FieldList(self.t))
786        # fill form
787        form = F(obj=obj)
788        self.assertEqual(len(form.a.entries), 3)
789        # pretend to submit form unchanged
790        pdata = DummyPostData({
791            'a-0': ['foo'],
792            'a-1': ['hi'],
793            'a-2': ['rawr']})
794        form.process(formdata=pdata)
795        # check if data still the same
796        self.assertEqual(len(form.a.entries), 3)
797        self.assertEqual(form.a.data, data)
798
799
800class MyCustomField(TextField):
801    def process_data(self, data):
802        if data == 'fail':
803            raise ValueError('Contrived Failure')
804
805        return super(MyCustomField, self).process_data(data)
806
807
808class CustomFieldQuirksTest(TestCase):
809    class F(Form):
810        a = MyCustomField()
811        b = SelectFieldBase()
812
813    def test_processing_failure(self):
814        form = self.F(a='42')
815        assert form.validate()
816        form = self.F(a='fail')
817        assert not form.validate()
818
819    def test_default_impls(self):
820        f = self.F()
821        self.assertRaises(NotImplementedError, f.b.iter_choices)
822
823
824class HTML5FieldsTest(TestCase):
825    class F(Form):
826        search = html5.SearchField()
827        telephone = html5.TelField()
828        url = html5.URLField()
829        email = html5.EmailField()
830        datetime = html5.DateTimeField()
831        date = html5.DateField()
832        dt_local = html5.DateTimeLocalField()
833        integer = html5.IntegerField()
834        decimal = html5.DecimalField()
835        int_range = html5.IntegerRangeField()
836        decimal_range = html5.DecimalRangeField()
837
838    def _build_value(self, key, form_input, expected_html, data=unset_value):
839        if data is unset_value:
840            data = form_input
841        if expected_html.startswith('type='):
842            expected_html = '<input id="%s" name="%s" %s value="%s">' % (key, key, expected_html, form_input)
843        return {
844            'key': key,
845            'form_input': form_input,
846            'expected_html': expected_html,
847            'data': data
848        }
849
850    def test_simple(self):
851        b = self._build_value
852        VALUES = (
853            b('search', 'search', 'type="search"'),
854            b('telephone', '123456789', 'type="tel"'),
855            b('url', 'http://wtforms.simplecodes.com/', 'type="url"'),
856            b('email', 'foo@bar.com', 'type="email"'),
857            b('datetime', '2013-09-05 00:23:42', 'type="datetime"', datetime(2013, 9, 5, 0, 23, 42)),
858            b('date', '2013-09-05', 'type="date"', date(2013, 9, 5)),
859            b('dt_local', '2013-09-05 00:23:42', 'type="datetime-local"', datetime(2013, 9, 5, 0, 23, 42)),
860            b('integer', '42', '<input id="integer" name="integer" step="1" type="number" value="42">', 42),
861            b('decimal', '43.5', '<input id="decimal" name="decimal" step="any" type="number" value="43.5">', Decimal('43.5')),
862            b('int_range', '4', '<input id="int_range" name="int_range" step="1" type="range" value="4">', 4),
863            b('decimal_range', '58', '<input id="decimal_range" name="decimal_range" step="any" type="range" value="58">', 58),
864        )
865        formdata = DummyPostData()
866        kw = {}
867        for item in VALUES:
868            formdata[item['key']] = item['form_input']
869            kw[item['key']] = item['data']
870
871        form = self.F(formdata)
872        for item in VALUES:
873            field = form[item['key']]
874            render_value = field()
875            if render_value != item['expected_html']:
876                tmpl = 'Field {key} render mismatch: {render_value!r} != {expected_html!r}'
877                raise AssertionError(tmpl.format(render_value=render_value, **item))
878            if field.data != item['data']:
879                tmpl = 'Field {key} data mismatch: {field.data!r} != {data!r}'
880                raise AssertionError(tmpl.format(field=field, **item))
881