/tests/regressiontests/forms/tests/widgets.py
Python | 1158 lines | 1145 code | 5 blank | 8 comment | 1 complexity | 6a791d43e857d509e116bb2f1471044d MD5 | raw file
Possible License(s): BSD-3-Clause
1# -*- coding: utf-8 -*-
2import datetime
3from decimal import Decimal
4import re
5import time
6from django.conf import settings
7from django.core.files.uploadedfile import SimpleUploadedFile
8from django.forms import *
9from django.forms.widgets import RadioFieldRenderer
10from django.utils import copycompat as copy
11from django.utils import formats
12from django.utils.safestring import mark_safe
13from django.utils.translation import activate, deactivate
14from django.utils.unittest import TestCase
15
16
17
18class FormsWidgetTestCase(TestCase):
19 # Each Widget class corresponds to an HTML form widget. A Widget knows how to
20 # render itself, given a field name and some data. Widgets don't perform
21 # validation.
22 def test_textinput(self):
23 w = TextInput()
24 self.assertEqual(w.render('email', ''), u'<input type="text" name="email" />')
25 self.assertEqual(w.render('email', None), u'<input type="text" name="email" />')
26 self.assertEqual(w.render('email', 'test@example.com'), u'<input type="text" name="email" value="test@example.com" />')
27 self.assertEqual(w.render('email', 'some "quoted" & ampersanded value'), u'<input type="text" name="email" value="some "quoted" & ampersanded value" />')
28 self.assertEqual(w.render('email', 'test@example.com', attrs={'class': 'fun'}), u'<input type="text" name="email" value="test@example.com" class="fun" />')
29
30 # Note that doctest in Python 2.4 (and maybe 2.5?) doesn't support non-ascii
31 # characters in output, so we're displaying the repr() here.
32 self.assertEqual(w.render('email', '????', attrs={'class': 'fun'}), u'<input type="text" name="email" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" class="fun" />')
33
34 # You can also pass 'attrs' to the constructor:
35 w = TextInput(attrs={'class': 'fun'})
36 self.assertEqual(w.render('email', ''), u'<input type="text" class="fun" name="email" />')
37 self.assertEqual(w.render('email', 'foo@example.com'), u'<input type="text" class="fun" value="foo@example.com" name="email" />')
38
39 # 'attrs' passed to render() get precedence over those passed to the constructor:
40 w = TextInput(attrs={'class': 'pretty'})
41 self.assertEqual(w.render('email', '', attrs={'class': 'special'}), u'<input type="text" class="special" name="email" />')
42
43 # 'attrs' can be safe-strings if needed)
44 w = TextInput(attrs={'onBlur': mark_safe("function('foo')")})
45 self.assertEqual(w.render('email', ''), u'<input onBlur="function(\'foo\')" type="text" name="email" />')
46
47 def test_passwordinput(self):
48 w = PasswordInput()
49 self.assertEqual(w.render('email', ''), u'<input type="password" name="email" />')
50 self.assertEqual(w.render('email', None), u'<input type="password" name="email" />')
51 self.assertEqual(w.render('email', 'secret'), u'<input type="password" name="email" />')
52
53 # The render_value argument lets you specify whether the widget should render
54 # its value. For security reasons, this is off by default.
55 w = PasswordInput(render_value=True)
56 self.assertEqual(w.render('email', ''), u'<input type="password" name="email" />')
57 self.assertEqual(w.render('email', None), u'<input type="password" name="email" />')
58 self.assertEqual(w.render('email', 'test@example.com'), u'<input type="password" name="email" value="test@example.com" />')
59 self.assertEqual(w.render('email', 'some "quoted" & ampersanded value'), u'<input type="password" name="email" value="some "quoted" & ampersanded value" />')
60 self.assertEqual(w.render('email', 'test@example.com', attrs={'class': 'fun'}), u'<input type="password" name="email" value="test@example.com" class="fun" />')
61
62 # You can also pass 'attrs' to the constructor:
63 w = PasswordInput(attrs={'class': 'fun'}, render_value=True)
64 self.assertEqual(w.render('email', ''), u'<input type="password" class="fun" name="email" />')
65 self.assertEqual(w.render('email', 'foo@example.com'), u'<input type="password" class="fun" value="foo@example.com" name="email" />')
66
67 # 'attrs' passed to render() get precedence over those passed to the constructor:
68 w = PasswordInput(attrs={'class': 'pretty'}, render_value=True)
69 self.assertEqual(w.render('email', '', attrs={'class': 'special'}), u'<input type="password" class="special" name="email" />')
70
71 self.assertEqual(w.render('email', '????', attrs={'class': 'fun'}), u'<input type="password" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />')
72
73 def test_hiddeninput(self):
74 w = HiddenInput()
75 self.assertEqual(w.render('email', ''), u'<input type="hidden" name="email" />')
76 self.assertEqual(w.render('email', None), u'<input type="hidden" name="email" />')
77 self.assertEqual(w.render('email', 'test@example.com'), u'<input type="hidden" name="email" value="test@example.com" />')
78 self.assertEqual(w.render('email', 'some "quoted" & ampersanded value'), u'<input type="hidden" name="email" value="some "quoted" & ampersanded value" />')
79 self.assertEqual(w.render('email', 'test@example.com', attrs={'class': 'fun'}), u'<input type="hidden" name="email" value="test@example.com" class="fun" />')
80
81 # You can also pass 'attrs' to the constructor:
82 w = HiddenInput(attrs={'class': 'fun'})
83 self.assertEqual(w.render('email', ''), u'<input type="hidden" class="fun" name="email" />')
84 self.assertEqual(w.render('email', 'foo@example.com'), u'<input type="hidden" class="fun" value="foo@example.com" name="email" />')
85
86 # 'attrs' passed to render() get precedence over those passed to the constructor:
87 w = HiddenInput(attrs={'class': 'pretty'})
88 self.assertEqual(w.render('email', '', attrs={'class': 'special'}), u'<input type="hidden" class="special" name="email" />')
89
90 self.assertEqual(w.render('email', '????', attrs={'class': 'fun'}), u'<input type="hidden" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />')
91
92 # 'attrs' passed to render() get precedence over those passed to the constructor:
93 w = HiddenInput(attrs={'class': 'pretty'})
94 self.assertEqual(w.render('email', '', attrs={'class': 'special'}), u'<input type="hidden" class="special" name="email" />')
95
96 # Boolean values are rendered to their string forms ("True" and "False").
97 w = HiddenInput()
98 self.assertEqual(w.render('get_spam', False), u'<input type="hidden" name="get_spam" value="False" />')
99 self.assertEqual(w.render('get_spam', True), u'<input type="hidden" name="get_spam" value="True" />')
100
101 def test_multiplehiddeninput(self):
102 w = MultipleHiddenInput()
103 self.assertEqual(w.render('email', []), u'')
104 self.assertEqual(w.render('email', None), u'')
105 self.assertEqual(w.render('email', ['test@example.com']), u'<input type="hidden" name="email" value="test@example.com" />')
106 self.assertEqual(w.render('email', ['some "quoted" & ampersanded value']), u'<input type="hidden" name="email" value="some "quoted" & ampersanded value" />')
107 self.assertEqual(w.render('email', ['test@example.com', 'foo@example.com']), u'<input type="hidden" name="email" value="test@example.com" />\n<input type="hidden" name="email" value="foo@example.com" />')
108 self.assertEqual(w.render('email', ['test@example.com'], attrs={'class': 'fun'}), u'<input type="hidden" name="email" value="test@example.com" class="fun" />')
109 self.assertEqual(w.render('email', ['test@example.com', 'foo@example.com'], attrs={'class': 'fun'}), u'<input type="hidden" name="email" value="test@example.com" class="fun" />\n<input type="hidden" name="email" value="foo@example.com" class="fun" />')
110
111 # You can also pass 'attrs' to the constructor:
112 w = MultipleHiddenInput(attrs={'class': 'fun'})
113 self.assertEqual(w.render('email', []), u'')
114 self.assertEqual(w.render('email', ['foo@example.com']), u'<input type="hidden" class="fun" value="foo@example.com" name="email" />')
115 self.assertEqual(w.render('email', ['foo@example.com', 'test@example.com']), u'<input type="hidden" class="fun" value="foo@example.com" name="email" />\n<input type="hidden" class="fun" value="test@example.com" name="email" />')
116
117 # 'attrs' passed to render() get precedence over those passed to the constructor:
118 w = MultipleHiddenInput(attrs={'class': 'pretty'})
119 self.assertEqual(w.render('email', ['foo@example.com'], attrs={'class': 'special'}), u'<input type="hidden" class="special" value="foo@example.com" name="email" />')
120
121 self.assertEqual(w.render('email', ['????'], attrs={'class': 'fun'}), u'<input type="hidden" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />')
122
123 # 'attrs' passed to render() get precedence over those passed to the constructor:
124 w = MultipleHiddenInput(attrs={'class': 'pretty'})
125 self.assertEqual(w.render('email', ['foo@example.com'], attrs={'class': 'special'}), u'<input type="hidden" class="special" value="foo@example.com" name="email" />')
126
127 # Each input gets a separate ID.
128 w = MultipleHiddenInput()
129 self.assertEqual(w.render('letters', list('abc'), attrs={'id': 'hideme'}), u'<input type="hidden" name="letters" value="a" id="hideme_0" />\n<input type="hidden" name="letters" value="b" id="hideme_1" />\n<input type="hidden" name="letters" value="c" id="hideme_2" />')
130
131 def test_fileinput(self):
132 # FileInput widgets don't ever show the value, because the old value is of no use
133 # if you are updating the form or if the provided file generated an error.
134 w = FileInput()
135 self.assertEqual(w.render('email', ''), u'<input type="file" name="email" />')
136 self.assertEqual(w.render('email', None), u'<input type="file" name="email" />')
137 self.assertEqual(w.render('email', 'test@example.com'), u'<input type="file" name="email" />')
138 self.assertEqual(w.render('email', 'some "quoted" & ampersanded value'), u'<input type="file" name="email" />')
139 self.assertEqual(w.render('email', 'test@example.com', attrs={'class': 'fun'}), u'<input type="file" name="email" class="fun" />')
140
141 # You can also pass 'attrs' to the constructor:
142 w = FileInput(attrs={'class': 'fun'})
143 self.assertEqual(w.render('email', ''), u'<input type="file" class="fun" name="email" />')
144 self.assertEqual(w.render('email', 'foo@example.com'), u'<input type="file" class="fun" name="email" />')
145
146 self.assertEqual(w.render('email', '????', attrs={'class': 'fun'}), u'<input type="file" class="fun" name="email" />')
147
148 # Test for the behavior of _has_changed for FileInput. The value of data will
149 # more than likely come from request.FILES. The value of initial data will
150 # likely be a filename stored in the database. Since its value is of no use to
151 # a FileInput it is ignored.
152 w = FileInput()
153
154 # No file was uploaded and no initial data.
155 self.assertFalse(w._has_changed(u'', None))
156
157 # A file was uploaded and no initial data.
158 self.assertTrue(w._has_changed(u'', {'filename': 'resume.txt', 'content': 'My resume'}))
159
160 # A file was not uploaded, but there is initial data
161 self.assertFalse(w._has_changed(u'resume.txt', None))
162
163 # A file was uploaded and there is initial data (file identity is not dealt
164 # with here)
165 self.assertTrue(w._has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'}))
166
167 def test_textarea(self):
168 w = Textarea()
169 self.assertEqual(w.render('msg', ''), u'<textarea rows="10" cols="40" name="msg"></textarea>')
170 self.assertEqual(w.render('msg', None), u'<textarea rows="10" cols="40" name="msg"></textarea>')
171 self.assertEqual(w.render('msg', 'value'), u'<textarea rows="10" cols="40" name="msg">value</textarea>')
172 self.assertEqual(w.render('msg', 'some "quoted" & ampersanded value'), u'<textarea rows="10" cols="40" name="msg">some "quoted" & ampersanded value</textarea>')
173 self.assertEqual(w.render('msg', mark_safe('pre "quoted" value')), u'<textarea rows="10" cols="40" name="msg">pre "quoted" value</textarea>')
174 self.assertEqual(w.render('msg', 'value', attrs={'class': 'pretty', 'rows': 20}), u'<textarea class="pretty" rows="20" cols="40" name="msg">value</textarea>')
175
176 # You can also pass 'attrs' to the constructor:
177 w = Textarea(attrs={'class': 'pretty'})
178 self.assertEqual(w.render('msg', ''), u'<textarea rows="10" cols="40" name="msg" class="pretty"></textarea>')
179 self.assertEqual(w.render('msg', 'example'), u'<textarea rows="10" cols="40" name="msg" class="pretty">example</textarea>')
180
181 # 'attrs' passed to render() get precedence over those passed to the constructor:
182 w = Textarea(attrs={'class': 'pretty'})
183 self.assertEqual(w.render('msg', '', attrs={'class': 'special'}), u'<textarea rows="10" cols="40" name="msg" class="special"></textarea>')
184
185 self.assertEqual(w.render('msg', '????', attrs={'class': 'fun'}), u'<textarea rows="10" cols="40" name="msg" class="fun">\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111</textarea>')
186
187 def test_checkboxinput(self):
188 w = CheckboxInput()
189 self.assertEqual(w.render('is_cool', ''), u'<input type="checkbox" name="is_cool" />')
190 self.assertEqual(w.render('is_cool', None), u'<input type="checkbox" name="is_cool" />')
191 self.assertEqual(w.render('is_cool', False), u'<input type="checkbox" name="is_cool" />')
192 self.assertEqual(w.render('is_cool', True), u'<input checked="checked" type="checkbox" name="is_cool" />')
193
194 # Using any value that's not in ('', None, False, True) will check the checkbox
195 # and set the 'value' attribute.
196 self.assertEqual(w.render('is_cool', 'foo'), u'<input checked="checked" type="checkbox" name="is_cool" value="foo" />')
197
198 self.assertEqual(w.render('is_cool', False, attrs={'class': 'pretty'}), u'<input type="checkbox" name="is_cool" class="pretty" />')
199
200 # You can also pass 'attrs' to the constructor:
201 w = CheckboxInput(attrs={'class': 'pretty'})
202 self.assertEqual(w.render('is_cool', ''), u'<input type="checkbox" class="pretty" name="is_cool" />')
203
204 # 'attrs' passed to render() get precedence over those passed to the constructor:
205 w = CheckboxInput(attrs={'class': 'pretty'})
206 self.assertEqual(w.render('is_cool', '', attrs={'class': 'special'}), u'<input type="checkbox" class="special" name="is_cool" />')
207
208 # You can pass 'check_test' to the constructor. This is a callable that takes the
209 # value and returns True if the box should be checked.
210 w = CheckboxInput(check_test=lambda value: value.startswith('hello'))
211 self.assertEqual(w.render('greeting', ''), u'<input type="checkbox" name="greeting" />')
212 self.assertEqual(w.render('greeting', 'hello'), u'<input checked="checked" type="checkbox" name="greeting" value="hello" />')
213 self.assertEqual(w.render('greeting', 'hello there'), u'<input checked="checked" type="checkbox" name="greeting" value="hello there" />')
214 self.assertEqual(w.render('greeting', 'hello & goodbye'), u'<input checked="checked" type="checkbox" name="greeting" value="hello & goodbye" />')
215
216 # A subtlety: If the 'check_test' argument cannot handle a value and raises any
217 # exception during its __call__, then the exception will be swallowed and the box
218 # will not be checked. In this example, the 'check_test' assumes the value has a
219 # startswith() method, which fails for the values True, False and None.
220 self.assertEqual(w.render('greeting', True), u'<input type="checkbox" name="greeting" />')
221 self.assertEqual(w.render('greeting', False), u'<input type="checkbox" name="greeting" />')
222 self.assertEqual(w.render('greeting', None), u'<input type="checkbox" name="greeting" />')
223
224 # The CheckboxInput widget will return False if the key is not found in the data
225 # dictionary (because HTML form submission doesn't send any result for unchecked
226 # checkboxes).
227 self.assertFalse(w.value_from_datadict({}, {}, 'testing'))
228
229 self.assertFalse(w._has_changed(None, None))
230 self.assertFalse(w._has_changed(None, u''))
231 self.assertFalse(w._has_changed(u'', None))
232 self.assertFalse(w._has_changed(u'', u''))
233 self.assertTrue(w._has_changed(False, u'on'))
234 self.assertFalse(w._has_changed(True, u'on'))
235 self.assertTrue(w._has_changed(True, u''))
236
237 def test_select(self):
238 w = Select()
239 self.assertEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select name="beatle">
240<option value="J" selected="selected">John</option>
241<option value="P">Paul</option>
242<option value="G">George</option>
243<option value="R">Ringo</option>
244</select>""")
245
246 # If the value is None, none of the options are selected:
247 self.assertEqual(w.render('beatle', None, choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select name="beatle">
248<option value="J">John</option>
249<option value="P">Paul</option>
250<option value="G">George</option>
251<option value="R">Ringo</option>
252</select>""")
253
254 # If the value corresponds to a label (but not to an option value), none of the options are selected:
255 self.assertEqual(w.render('beatle', 'John', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select name="beatle">
256<option value="J">John</option>
257<option value="P">Paul</option>
258<option value="G">George</option>
259<option value="R">Ringo</option>
260</select>""")
261
262 # The value is compared to its str():
263 self.assertEqual(w.render('num', 2, choices=[('1', '1'), ('2', '2'), ('3', '3')]), """<select name="num">
264<option value="1">1</option>
265<option value="2" selected="selected">2</option>
266<option value="3">3</option>
267</select>""")
268 self.assertEqual(w.render('num', '2', choices=[(1, 1), (2, 2), (3, 3)]), """<select name="num">
269<option value="1">1</option>
270<option value="2" selected="selected">2</option>
271<option value="3">3</option>
272</select>""")
273 self.assertEqual(w.render('num', 2, choices=[(1, 1), (2, 2), (3, 3)]), """<select name="num">
274<option value="1">1</option>
275<option value="2" selected="selected">2</option>
276<option value="3">3</option>
277</select>""")
278
279 # The 'choices' argument can be any iterable:
280 from itertools import chain
281 def get_choices():
282 for i in range(5):
283 yield (i, i)
284 self.assertEqual(w.render('num', 2, choices=get_choices()), """<select name="num">
285<option value="0">0</option>
286<option value="1">1</option>
287<option value="2" selected="selected">2</option>
288<option value="3">3</option>
289<option value="4">4</option>
290</select>""")
291 things = ({'id': 1, 'name': 'And Boom'}, {'id': 2, 'name': 'One More Thing!'})
292 class SomeForm(Form):
293 somechoice = ChoiceField(choices=chain((('', '-'*9),), [(thing['id'], thing['name']) for thing in things]))
294 f = SomeForm()
295 self.assertEqual(f.as_table(), u'<tr><th><label for="id_somechoice">Somechoice:</label></th><td><select name="somechoice" id="id_somechoice">\n<option value="" selected="selected">---------</option>\n<option value="1">And Boom</option>\n<option value="2">One More Thing!</option>\n</select></td></tr>')
296 self.assertEqual(f.as_table(), u'<tr><th><label for="id_somechoice">Somechoice:</label></th><td><select name="somechoice" id="id_somechoice">\n<option value="" selected="selected">---------</option>\n<option value="1">And Boom</option>\n<option value="2">One More Thing!</option>\n</select></td></tr>')
297 f = SomeForm({'somechoice': 2})
298 self.assertEqual(f.as_table(), u'<tr><th><label for="id_somechoice">Somechoice:</label></th><td><select name="somechoice" id="id_somechoice">\n<option value="">---------</option>\n<option value="1">And Boom</option>\n<option value="2" selected="selected">One More Thing!</option>\n</select></td></tr>')
299
300 # You can also pass 'choices' to the constructor:
301 w = Select(choices=[(1, 1), (2, 2), (3, 3)])
302 self.assertEqual(w.render('num', 2), """<select name="num">
303<option value="1">1</option>
304<option value="2" selected="selected">2</option>
305<option value="3">3</option>
306</select>""")
307
308 # If 'choices' is passed to both the constructor and render(), then they'll both be in the output:
309 self.assertEqual(w.render('num', 2, choices=[(4, 4), (5, 5)]), """<select name="num">
310<option value="1">1</option>
311<option value="2" selected="selected">2</option>
312<option value="3">3</option>
313<option value="4">4</option>
314<option value="5">5</option>
315</select>""")
316
317 # Choices are escaped correctly
318 self.assertEqual(w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you > me')))), """<select name="escape">
319<option value="1">1</option>
320<option value="2">2</option>
321<option value="3">3</option>
322<option value="bad">you & me</option>
323<option value="good">you > me</option>
324</select>""")
325
326 # Unicode choices are correctly rendered as HTML
327 self.assertEqual(w.render('email', '????', choices=[('????', '?abc???'), ('??', 'abc??')]), u'<select name="email">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" selected="selected">\u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</option>\n<option value="\u0107\u017e\u0161\u0111">abc\u0107\u017e\u0161\u0111</option>\n</select>')
328
329 # If choices is passed to the constructor and is a generator, it can be iterated
330 # over multiple times without getting consumed:
331 w = Select(choices=get_choices())
332 self.assertEqual(w.render('num', 2), """<select name="num">
333<option value="0">0</option>
334<option value="1">1</option>
335<option value="2" selected="selected">2</option>
336<option value="3">3</option>
337<option value="4">4</option>
338</select>""")
339 self.assertEqual(w.render('num', 3), """<select name="num">
340<option value="0">0</option>
341<option value="1">1</option>
342<option value="2">2</option>
343<option value="3" selected="selected">3</option>
344<option value="4">4</option>
345</select>""")
346
347 # Choices can be nested one level in order to create HTML optgroups:
348 w.choices=(('outer1', 'Outer 1'), ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2'))))
349 self.assertEqual(w.render('nestchoice', None), """<select name="nestchoice">
350<option value="outer1">Outer 1</option>
351<optgroup label="Group "1"">
352<option value="inner1">Inner 1</option>
353<option value="inner2">Inner 2</option>
354</optgroup>
355</select>""")
356
357 self.assertEqual(w.render('nestchoice', 'outer1'), """<select name="nestchoice">
358<option value="outer1" selected="selected">Outer 1</option>
359<optgroup label="Group "1"">
360<option value="inner1">Inner 1</option>
361<option value="inner2">Inner 2</option>
362</optgroup>
363</select>""")
364
365 self.assertEqual(w.render('nestchoice', 'inner1'), """<select name="nestchoice">
366<option value="outer1">Outer 1</option>
367<optgroup label="Group "1"">
368<option value="inner1" selected="selected">Inner 1</option>
369<option value="inner2">Inner 2</option>
370</optgroup>
371</select>""")
372
373 def test_nullbooleanselect(self):
374 w = NullBooleanSelect()
375 self.assertTrue(w.render('is_cool', True), """<select name="is_cool">
376<option value="1">Unknown</option>
377<option value="2" selected="selected">Yes</option>
378<option value="3">No</option>
379</select>""")
380 self.assertEqual(w.render('is_cool', False), """<select name="is_cool">
381<option value="1">Unknown</option>
382<option value="2">Yes</option>
383<option value="3" selected="selected">No</option>
384</select>""")
385 self.assertEqual(w.render('is_cool', None), """<select name="is_cool">
386<option value="1" selected="selected">Unknown</option>
387<option value="2">Yes</option>
388<option value="3">No</option>
389</select>""")
390 self.assertEqual(w.render('is_cool', '2'), """<select name="is_cool">
391<option value="1">Unknown</option>
392<option value="2" selected="selected">Yes</option>
393<option value="3">No</option>
394</select>""")
395 self.assertEqual(w.render('is_cool', '3'), """<select name="is_cool">
396<option value="1">Unknown</option>
397<option value="2">Yes</option>
398<option value="3" selected="selected">No</option>
399</select>""")
400 self.assertTrue(w._has_changed(False, None))
401 self.assertTrue(w._has_changed(None, False))
402 self.assertFalse(w._has_changed(None, None))
403 self.assertFalse(w._has_changed(False, False))
404 self.assertTrue(w._has_changed(True, False))
405 self.assertTrue(w._has_changed(True, None))
406 self.assertTrue(w._has_changed(True, False))
407
408 def test_selectmultiple(self):
409 w = SelectMultiple()
410 self.assertEqual(w.render('beatles', ['J'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select multiple="multiple" name="beatles">
411<option value="J" selected="selected">John</option>
412<option value="P">Paul</option>
413<option value="G">George</option>
414<option value="R">Ringo</option>
415</select>""")
416 self.assertEqual(w.render('beatles', ['J', 'P'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select multiple="multiple" name="beatles">
417<option value="J" selected="selected">John</option>
418<option value="P" selected="selected">Paul</option>
419<option value="G">George</option>
420<option value="R">Ringo</option>
421</select>""")
422 self.assertEqual(w.render('beatles', ['J', 'P', 'R'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select multiple="multiple" name="beatles">
423<option value="J" selected="selected">John</option>
424<option value="P" selected="selected">Paul</option>
425<option value="G">George</option>
426<option value="R" selected="selected">Ringo</option>
427</select>""")
428
429 # If the value is None, none of the options are selected:
430 self.assertEqual(w.render('beatles', None, choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select multiple="multiple" name="beatles">
431<option value="J">John</option>
432<option value="P">Paul</option>
433<option value="G">George</option>
434<option value="R">Ringo</option>
435</select>""")
436
437 # If the value corresponds to a label (but not to an option value), none of the options are selected:
438 self.assertEqual(w.render('beatles', ['John'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select multiple="multiple" name="beatles">
439<option value="J">John</option>
440<option value="P">Paul</option>
441<option value="G">George</option>
442<option value="R">Ringo</option>
443</select>""")
444
445 # If multiple values are given, but some of them are not valid, the valid ones are selected:
446 self.assertEqual(w.render('beatles', ['J', 'G', 'foo'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select multiple="multiple" name="beatles">
447<option value="J" selected="selected">John</option>
448<option value="P">Paul</option>
449<option value="G" selected="selected">George</option>
450<option value="R">Ringo</option>
451</select>""")
452
453 # The value is compared to its str():
454 self.assertEqual(w.render('nums', [2], choices=[('1', '1'), ('2', '2'), ('3', '3')]), """<select multiple="multiple" name="nums">
455<option value="1">1</option>
456<option value="2" selected="selected">2</option>
457<option value="3">3</option>
458</select>""")
459 self.assertEqual(w.render('nums', ['2'], choices=[(1, 1), (2, 2), (3, 3)]), """<select multiple="multiple" name="nums">
460<option value="1">1</option>
461<option value="2" selected="selected">2</option>
462<option value="3">3</option>
463</select>""")
464 self.assertEqual(w.render('nums', [2], choices=[(1, 1), (2, 2), (3, 3)]), """<select multiple="multiple" name="nums">
465<option value="1">1</option>
466<option value="2" selected="selected">2</option>
467<option value="3">3</option>
468</select>""")
469
470 # The 'choices' argument can be any iterable:
471 def get_choices():
472 for i in range(5):
473 yield (i, i)
474 self.assertEqual(w.render('nums', [2], choices=get_choices()), """<select multiple="multiple" name="nums">
475<option value="0">0</option>
476<option value="1">1</option>
477<option value="2" selected="selected">2</option>
478<option value="3">3</option>
479<option value="4">4</option>
480</select>""")
481
482 # You can also pass 'choices' to the constructor:
483 w = SelectMultiple(choices=[(1, 1), (2, 2), (3, 3)])
484 self.assertEqual(w.render('nums', [2]), """<select multiple="multiple" name="nums">
485<option value="1">1</option>
486<option value="2" selected="selected">2</option>
487<option value="3">3</option>
488</select>""")
489
490 # If 'choices' is passed to both the constructor and render(), then they'll both be in the output:
491 self.assertEqual(w.render('nums', [2], choices=[(4, 4), (5, 5)]), """<select multiple="multiple" name="nums">
492<option value="1">1</option>
493<option value="2" selected="selected">2</option>
494<option value="3">3</option>
495<option value="4">4</option>
496<option value="5">5</option>
497</select>""")
498
499 # Choices are escaped correctly
500 self.assertEqual(w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you > me')))), """<select multiple="multiple" name="escape">
501<option value="1">1</option>
502<option value="2">2</option>
503<option value="3">3</option>
504<option value="bad">you & me</option>
505<option value="good">you > me</option>
506</select>""")
507
508 # Unicode choices are correctly rendered as HTML
509 self.assertEqual(w.render('nums', ['????'], choices=[('????', '?abc???'), ('??', 'abc??')]), u'<select multiple="multiple" name="nums">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" selected="selected">\u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</option>\n<option value="\u0107\u017e\u0161\u0111">abc\u0107\u017e\u0161\u0111</option>\n</select>')
510
511 # Test the usage of _has_changed
512 self.assertFalse(w._has_changed(None, None))
513 self.assertFalse(w._has_changed([], None))
514 self.assertTrue(w._has_changed(None, [u'1']))
515 self.assertFalse(w._has_changed([1, 2], [u'1', u'2']))
516 self.assertTrue(w._has_changed([1, 2], [u'1']))
517 self.assertTrue(w._has_changed([1, 2], [u'1', u'3']))
518
519 # Choices can be nested one level in order to create HTML optgroups:
520 w.choices = (('outer1', 'Outer 1'), ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2'))))
521 self.assertEqual(w.render('nestchoice', None), """<select multiple="multiple" name="nestchoice">
522<option value="outer1">Outer 1</option>
523<optgroup label="Group "1"">
524<option value="inner1">Inner 1</option>
525<option value="inner2">Inner 2</option>
526</optgroup>
527</select>""")
528
529 self.assertEqual(w.render('nestchoice', ['outer1']), """<select multiple="multiple" name="nestchoice">
530<option value="outer1" selected="selected">Outer 1</option>
531<optgroup label="Group "1"">
532<option value="inner1">Inner 1</option>
533<option value="inner2">Inner 2</option>
534</optgroup>
535</select>""")
536
537 self.assertEqual(w.render('nestchoice', ['inner1']), """<select multiple="multiple" name="nestchoice">
538<option value="outer1">Outer 1</option>
539<optgroup label="Group "1"">
540<option value="inner1" selected="selected">Inner 1</option>
541<option value="inner2">Inner 2</option>
542</optgroup>
543</select>""")
544
545 self.assertEqual(w.render('nestchoice', ['outer1', 'inner2']), """<select multiple="multiple" name="nestchoice">
546<option value="outer1" selected="selected">Outer 1</option>
547<optgroup label="Group "1"">
548<option value="inner1">Inner 1</option>
549<option value="inner2" selected="selected">Inner 2</option>
550</optgroup>
551</select>""")
552
553 def test_radioselect(self):
554 w = RadioSelect()
555 self.assertEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
556<li><label><input checked="checked" type="radio" name="beatle" value="J" /> John</label></li>
557<li><label><input type="radio" name="beatle" value="P" /> Paul</label></li>
558<li><label><input type="radio" name="beatle" value="G" /> George</label></li>
559<li><label><input type="radio" name="beatle" value="R" /> Ringo</label></li>
560</ul>""")
561
562 # If the value is None, none of the options are checked:
563 self.assertEqual(w.render('beatle', None, choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
564<li><label><input type="radio" name="beatle" value="J" /> John</label></li>
565<li><label><input type="radio" name="beatle" value="P" /> Paul</label></li>
566<li><label><input type="radio" name="beatle" value="G" /> George</label></li>
567<li><label><input type="radio" name="beatle" value="R" /> Ringo</label></li>
568</ul>""")
569
570 # If the value corresponds to a label (but not to an option value), none of the options are checked:
571 self.assertEqual(w.render('beatle', 'John', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
572<li><label><input type="radio" name="beatle" value="J" /> John</label></li>
573<li><label><input type="radio" name="beatle" value="P" /> Paul</label></li>
574<li><label><input type="radio" name="beatle" value="G" /> George</label></li>
575<li><label><input type="radio" name="beatle" value="R" /> Ringo</label></li>
576</ul>""")
577
578 # The value is compared to its str():
579 self.assertEqual(w.render('num', 2, choices=[('1', '1'), ('2', '2'), ('3', '3')]), """<ul>
580<li><label><input type="radio" name="num" value="1" /> 1</label></li>
581<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
582<li><label><input type="radio" name="num" value="3" /> 3</label></li>
583</ul>""")
584 self.assertEqual(w.render('num', '2', choices=[(1, 1), (2, 2), (3, 3)]), """<ul>
585<li><label><input type="radio" name="num" value="1" /> 1</label></li>
586<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
587<li><label><input type="radio" name="num" value="3" /> 3</label></li>
588</ul>""")
589 self.assertEqual(w.render('num', 2, choices=[(1, 1), (2, 2), (3, 3)]), """<ul>
590<li><label><input type="radio" name="num" value="1" /> 1</label></li>
591<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
592<li><label><input type="radio" name="num" value="3" /> 3</label></li>
593</ul>""")
594
595 # The 'choices' argument can be any iterable:
596 def get_choices():
597 for i in range(5):
598 yield (i, i)
599 self.assertEqual(w.render('num', 2, choices=get_choices()), """<ul>
600<li><label><input type="radio" name="num" value="0" /> 0</label></li>
601<li><label><input type="radio" name="num" value="1" /> 1</label></li>
602<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
603<li><label><input type="radio" name="num" value="3" /> 3</label></li>
604<li><label><input type="radio" name="num" value="4" /> 4</label></li>
605</ul>""")
606
607 # You can also pass 'choices' to the constructor:
608 w = RadioSelect(choices=[(1, 1), (2, 2), (3, 3)])
609 self.assertEqual(w.render('num', 2), """<ul>
610<li><label><input type="radio" name="num" value="1" /> 1</label></li>
611<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
612<li><label><input type="radio" name="num" value="3" /> 3</label></li>
613</ul>""")
614
615 # If 'choices' is passed to both the constructor and render(), then they'll both be in the output:
616 self.assertEqual(w.render('num', 2, choices=[(4, 4), (5, 5)]), """<ul>
617<li><label><input type="radio" name="num" value="1" /> 1</label></li>
618<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
619<li><label><input type="radio" name="num" value="3" /> 3</label></li>
620<li><label><input type="radio" name="num" value="4" /> 4</label></li>
621<li><label><input type="radio" name="num" value="5" /> 5</label></li>
622</ul>""")
623
624 # RadioSelect uses a RadioFieldRenderer to render the individual radio inputs.
625 # You can manipulate that object directly to customize the way the RadioSelect
626 # is rendered.
627 w = RadioSelect()
628 r = w.get_renderer('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
629 inp_set1 = []
630 inp_set2 = []
631 inp_set3 = []
632 inp_set4 = []
633
634 for inp in r:
635 inp_set1.append(str(inp))
636 inp_set2.append('%s<br />' % inp)
637 inp_set3.append('<p>%s %s</p>' % (inp.tag(), inp.choice_label))
638 inp_set4.append('%s %s %s %s %s' % (inp.name, inp.value, inp.choice_value, inp.choice_label, inp.is_checked()))
639
640 self.assertEqual('\n'.join(inp_set1), """<label><input checked="checked" type="radio" name="beatle" value="J" /> John</label>
641<label><input type="radio" name="beatle" value="P" /> Paul</label>
642<label><input type="radio" name="beatle" value="G" /> George</label>
643<label><input type="radio" name="beatle" value="R" /> Ringo</label>""")
644 self.assertEqual('\n'.join(inp_set2), """<label><input checked="checked" type="radio" name="beatle" value="J" /> John</label><br />
645<label><input type="radio" name="beatle" value="P" /> Paul</label><br />
646<label><input type="radio" name="beatle" value="G" /> George</label><br />
647<label><input type="radio" name="beatle" value="R" /> Ringo</label><br />""")
648 self.assertEqual('\n'.join(inp_set3), """<p><input checked="checked" type="radio" name="beatle" value="J" /> John</p>
649<p><input type="radio" name="beatle" value="P" /> Paul</p>
650<p><input type="radio" name="beatle" value="G" /> George</p>
651<p><input type="radio" name="beatle" value="R" /> Ringo</p>""")
652 self.assertEqual('\n'.join(inp_set4), """beatle J J John True
653beatle J P Paul False
654beatle J G George False
655beatle J R Ringo False""")
656
657 # You can create your own custom renderers for RadioSelect to use.
658 class MyRenderer(RadioFieldRenderer):
659 def render(self):
660 return u'<br />\n'.join([unicode(choice) for choice in self])
661 w = RadioSelect(renderer=MyRenderer)
662 self.assertEqual(w.render('beatle', 'G', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<label><input type="radio" name="beatle" value="J" /> John</label><br />
663<label><input type="radio" name="beatle" value="P" /> Paul</label><br />
664<label><input checked="checked" type="radio" name="beatle" value="G" /> George</label><br />
665<label><input type="radio" name="beatle" value="R" /> Ringo</label>""")
666
667 # Or you can use custom RadioSelect fields that use your custom renderer.
668 class CustomRadioSelect(RadioSelect):
669 renderer = MyRenderer
670 w = CustomRadioSelect()
671 self.assertEqual(w.render('beatle', 'G', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<label><input type="radio" name="beatle" value="J" /> John</label><br />
672<label><input type="radio" name="beatle" value="P" /> Paul</label><br />
673<label><input checked="checked" type="radio" name="beatle" value="G" /> George</label><br />
674<label><input type="radio" name="beatle" value="R" /> Ringo</label>""")
675
676 # A RadioFieldRenderer object also allows index access to individual RadioInput
677 w = RadioSelect()
678 r = w.get_renderer('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
679 self.assertEqual(str(r[1]), '<label><input type="radio" name="beatle" value="P" /> Paul</label>')
680 self.assertEqual(str(r[0]), '<label><input checked="checked" type="radio" name="beatle" value="J" /> John</label>')
681 self.assertTrue(r[0].is_checked())
682 self.assertFalse(r[1].is_checked())
683 self.assertEqual((r[1].name, r[1].value, r[1].choice_value, r[1].choice_label), ('beatle', u'J', u'P', u'Paul'))
684
685 try:
686 r[10]
687 self.fail("This offset should not exist.")
688 except IndexError:
689 pass
690
691 # Choices are escaped correctly
692 w = RadioSelect()
693 self.assertEqual(w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you > me')))), """<ul>
694<li><label><input type="radio" name="escape" value="bad" /> you & me</label></li>
695<li><label><input type="radio" name="escape" value="good" /> you > me</label></li>
696</ul>""")
697
698 # Unicode choices are correctly rendered as HTML
699 w = RadioSelect()
700 self.assertEqual(unicode(w.render('email', '????', choices=[('????', '?abc???'), ('??', 'abc??')])), u'<ul>\n<li><label><input checked="checked" type="radio" name="email" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /> \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</label></li>\n<li><label><input type="radio" name="email" value="\u0107\u017e\u0161\u0111" /> abc\u0107\u017e\u0161\u0111</label></li>\n</ul>')
701
702 # Attributes provided at instantiation are passed to the constituent inputs
703 w = RadioSelect(attrs={'id':'foo'})
704 self.assertEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
705<li><label for="foo_0"><input checked="checked" type="radio" id="foo_0" value="J" name="beatle" /> John</label></li>
706<li><label for="foo_1"><input type="radio" id="foo_1" value="P" name="beatle" /> Paul</label></li>
707<li><label for="foo_2"><input type="radio" id="foo_2" value="G" name="beatle" /> George</label></li>
708<li><label for="foo_3"><input type="radio" id="foo_3" value="R" name="beatle" /> Ringo</label></li>
709</ul>""")
710
711 # Attributes provided at render-time are passed to the constituent inputs
712 w = RadioSelect()
713 self.assertEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')), attrs={'id':'bar'}), """<ul>
714<li><label for="bar_0"><input checked="checked" type="radio" id="bar_0" value="J" name="beatle" /> John</label></li>
715<li><label for="bar_1"><input type="radio" id="bar_1" value="P" name="beatle" /> Paul</label></li>
716<li><label for="bar_2"><input type="radio" id="bar_2" value="G" name="beatle" /> George</label></li>
717<li><label for="bar_3"><input type="radio" id="bar_3" value="R" name="beatle" /> Ringo</label></li>
718</ul>""")
719
720 def test_checkboxselectmultiple(self):
721 w = CheckboxSelectMultiple()
722 self.assertEqual(w.render('beatles', ['J'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
723<li><label><input checked="checked" type="checkbox" name="beatles" value="J" /> John</label></li>
724<li><label><input type="checkbox" name="beatles" value="P" /> Paul</label></li>
725<li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
726<li><label><input type="checkbox" name="beatles" value="R" /> Ringo</label></li>
727</ul>""")
728 self.assertEqual(w.render('beatles', ['J', 'P'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
729<li><label><input checked="checked" type="checkbox" name="beatles" value="J" /> John</label></li>
730<li><label><input checked="checked" type="checkbox" name="beatles" value="P" /> Paul</label></li>
731<li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
732<li><label><input type="checkbox" name="beatles" value="R" /> Ringo</label></li>
733</ul>""")
734 self.assertEqual(w.render('beatles', ['J', 'P', 'R'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
735<li><label><input checked="checked" type="checkbox" name="beatles" value="J" /> John</label></li>
736<li><label><input checked="checked" type="checkbox" name="beatles" value="P" /> Paul</label></li>
737<li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
738<li><label><input checked="checked" type="checkbox" name="beatles" value="R" /> Ringo</label></li>
739</ul>""")
740
741 # If the value is None, none of the options are selected:
742 self.assertEqual(w.render('beatles', None, choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
743<li><label><input type="checkbox" name="beatles" value="J" /> John</label></li>
744<li><label><input type="checkbox" name="beatles" value="P" /> Paul</label></li>
745<li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
746<li><label><input type="checkbox" name="beatles" value="R" /> Ringo</label></li>
747</ul>""")
748
749 # If the value corresponds to a label (but not to an option value), none of the options are selected:
750 self.assertEqual(w.render('beatles', ['John'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
751<li><label><input type="checkbox" name="beatles" value="J" /> John</label></li>
752<li><label><input type="checkbox" name="beatles" value="P" /> Paul</label></li>
753<li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
754<li><label><input type="checkbox" name="beatles" value="R" /> Ringo</label></li>
755</ul>""")
756
757 # If multiple values are given, but some of them are not valid, the valid ones are selected:
758 self.assertEqual(w.render('beatles', ['J', 'G', 'foo'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
759<li><label><input checked="checked" type="checkbox" name="beatles" value="J" /> John</label></li>
760<li><label><input type="checkbox" name="beatles" value="P" /> Paul</label></li>
761<li><label><input checked="checked" type="checkbox" name="beatles" value="G" /> George</label></li>
762<li><label><input type="checkbox" name="beatles" value="R" /> Ringo</label></li>
763</ul>""")
764
765 # The value is compared to its str():
766 self.assertEqual(w.render('nums', [2], choices=[('1', '1'), ('2', '2'), ('3', '3')]), """<ul>
767<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
768<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
769<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
770</ul>""")
771 self.assertEqual(w.render('nums', ['2'], choices=[(1, 1), (2, 2), (3, 3)]), """<ul>
772<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
773<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
774<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
775</ul>""")
776 self.assertEqual(w.render('nums', [2], choices=[(1, 1), (2, 2), (3, 3)]), """<ul>
777<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
778<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
779<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
780</ul>""")
781
782 # The 'choices' argument can be any iterable:
783 def get_choices():
784 for i in range(5):
785 yield (i, i)
786 self.assertEqual(w.render('nums', [2], choices=get_choices()), """<ul>
787<li><label><input type="checkbox" name="nums" value="0" /> 0</label></li>
788<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
789<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
790<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
791<li><label><input type="checkbox" name="nums" value="4" /> 4</label></li>
792</ul>""")
793
794 # You can also pass 'choices' to the constructor:
795 w = CheckboxSelectMultiple(choices=[(1, 1), (2, 2), (3, 3)])
796 self.assertEqual(w.render('nums', [2]), """<ul>
797<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
798<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
799<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
800</ul>""")
801
802 # If 'choices' is passed to both the constructor and render(), then they'll both be in the output:
803 self.assertEqual(w.render('nums', [2], choices=[(4, 4), (5, 5)]), """<ul>
804<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
805<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
806<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
807<li><label><input type="checkbox" name="nums" value="4" /> 4</label></li>
808<li><label><input type="checkbox" name="nums" value="5" /> 5</label></li>
809</ul>""")
810
811 # Choices are escaped correctly
812 self.assertEqual(w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you > me')))), """<ul>
813<li><label><input type="checkbox" name="escape" value="1" /> 1</label></li>
814<li><label><input type="checkbox" name="escape" value="2" /> 2</label></li>
815<li><label><input type="checkbox" name="escape" value="3" /> 3</label></li>
816<li><label><input type="checkbox" name="escape" value="bad" /> you & me</label></li>
817<li><label><input type="checkbox" name="escape" value="good" /> you > me</label></li>
818</ul>""")
819
820 # Test the usage of _has_changed
821 self.assertFalse(w._has_changed(None, None))
822 self.assertFalse(w._has_changed([], None))
823 self.assertTrue(w._has_changed(None, [u'1']))
824 self.assertFalse(w._has_changed([1, 2], [u'1', u'2']))
825 self.assertTrue(w._has_changed([1, 2], [u'1']))
826 self.assertTrue(w._has_changed([1, 2], [u'1', u'3']))
827 self.assertFalse(w._has_changed([2, 1], [u'1', u'2']))
828
829 # Unicode choices are correctly rendered as HTML
830 self.assertEqual(w.render('nums', ['????'], choices=[('????', '?abc???'), ('??', 'abc??')]), u'<ul>\n<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>\n<li><label><input type="checkbox" name="nums" value="2" /> 2</label></li>\n<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>\n<li><label><input checked="checked" type="checkbox" name="nums" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /> \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</label></li>\n<li><label><input type="checkbox" name="nums" value="\u0107\u017e\u0161\u0111" /> abc\u0107\u017e\u0161\u0111</label></li>\n</ul>')
831
832 # Each input gets a separate ID
833 self.assertEqual(CheckboxSelectMultiple().render('letters', list('ac'), choices=zip(list('abc'), list('ABC')), attrs={'id': 'abc'}), """<ul>
834<li><label for="abc_0"><input checked="checked" type="checkbox" name="letters" value="a" id="abc_0" /> A</label></li>
835<li><label for="abc_1"><input type="checkbox" name="letters" value="b" id="abc_1" /> B</label></li>
836<li><label for="abc_2"><input checked="checked" type="checkbox" name="letters" value="c" id="abc_2" /> C</label></li>
837</ul>""")
838
839 def test_multi(self):
840 class MyMultiWidget(MultiWidget):
841 def decompress(self, value):
842 if value:
843 return value.split('__')
844 return ['', '']
845 def format_output(self, rendered_widgets):
846 return u'<br />'.join(rendered_widgets)
847
848 w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'})))
849 self.assertEqual(w.render('name', ['john', 'lennon']), u'<input type="text" class="big" value="john" name="name_0" /><br /><input type="text" class="small" value="lennon" name="name_1" />')
850 self.assertEqual(w.render('name', 'john__lennon'), u'<input type="text" class="big" value="john" name="name_0" /><br /><input type="text" class="small" value="lennon" name="name_1" />')
851 self.assertEqual(w.render('name', 'john__lennon', attrs={'id':'foo'}), u'<input id="foo_0" type="text" class="big" value="john" name="name_0" /><br /><input id="foo_1" type="text" class="small" value="lennon" name="name_1" />')
852 w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'})), attrs={'id': 'bar'})
853 self.assertEqual(w.render('name', ['john', 'lennon']), u'<input id="bar_0" type="text" class="big" value="john" name="name_0" /><br /><input id="bar_1" type="text" class="small" value="lennon" name="name_1" />')
854
855 w = MyMultiWidget(widgets=(TextInput(), TextInput()))
856
857 # test with no initial data
858 self.assertTrue(w._has_changed(None, [u'john', u'lennon']))
859
860 # test when the data is the same as initial
861 self.assertFalse(w._has_changed(u'john__lennon', [u'john', u'lennon']))
862
863 # test when the first widget's data has changed
864 self.assertTrue(w._has_changed(u'john__lennon', [u'alfred', u'lennon']))
865
866 # test when the last widget's data has changed. this ensures that it is not
867 # short circuiting while testing the widgets.
868 self.assertTrue(w._has_changed(u'john__lennon', [u'john', u'denver']))
869
870 def test_splitdatetime(self):
871 w = SplitDateTimeWidget()
872 self.assertEqual(w.render('date', ''), u'<input type="text" name="date_0" /><input type="text" name="date_1" />')
873 self.assertEqual(w.render('date', None), u'<input type="text" name="date_0" /><input type="text" name="date_1" />')
874 self.assertEqual(w.render('date', datetime.datetime(2006, 1, 10, 7, 30)), u'<input type="text" name="date_0" value="2006-01-10" /><input type="text" name="date_1" value="07:30:00" />')
875 self.assertEqual(w.render('date', [datetime.date(2006, 1, 10), datetime.time(7, 30)]), u'<input type="text" name="date_0" value="2006-01-10" /><input type="text" name="date_1" value="07:30:00" />')
876
877 # You can also pass 'attrs' to the constructor. In this case, the attrs will be
878 w = SplitDateTimeWidget(attrs={'class': 'pretty'})
879 self.assertEqual(w.render('date', datetime.datetime(2006, 1, 10, 7, 30)), u'<input type="text" class="pretty" value="2006-01-10" name="date_0" /><input type="text" class="pretty" value="07:30:00" name="date_1" />')
880
881 # Use 'date_format' and 'time_format' to change the way a value is displayed.
882 w = SplitDateTimeWidget(date_format='%d/%m/%Y', time_format='%H:%M')
883 self.assertEqual(w.render('date', datetime.datetime(2006, 1, 10, 7, 30)), u'<input type="text" name="date_0" value="10/01/2006" /><input type="text" name="date_1" value="07:30" />')
884
885 self.assertTrue(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), [u'2008-05-06', u'12:40:00']))
886 self.assertFalse(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), [u'06/05/2008', u'12:40']))
887 self.assertTrue(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), [u'06/05/2008', u'12:41']))
888
889 def test_datetimeinput(self):
890 w = DateTimeInput()
891 self.assertEqual(w.render('date', None), u'<input type="text" name="date" />')
892 d = datetime.datetime(2007, 9, 17, 12, 51, 34, 482548)
893 self.assertEqual(str(d), '2007-09-17 12:51:34.482548')
894
895 # The microseconds are trimmed on display, by default.
896 self.assertEqual(w.render('date', d), u'<input type="text" name="date" value="2007-09-17 12:51:34" />')
897 self.assertEqual(w.render('date', datetime.datetime(2007, 9, 17, 12, 51, 34)), u'<input type="text" name="date" value="2007-09-17 12:51:34" />')
898 self.assertEqual(w.render('date', datetime.datetime(2007, 9, 17, 12, 51)), u'<input type="text" name="date" value="2007-09-17 12:51:00" />')
899
900 # Use 'format' to change the way a value is displayed.
901 w = DateTimeInput(format='%d/%m/%Y %H:%M')
902 self.assertEqual(w.render('date', d), u'<input type="text" name="date" value="17/09/2007 12:51" />')
903 self.assertFalse(w._has_changed(d, '17/09/2007 12:51'))
904
905 # Make sure a custom format works with _has_changed. The hidden input will use
906 data = datetime.datetime(2010, 3, 6, 12, 0, 0)
907 custom_format = '%d.%m.%Y %H:%M'
908 w = DateTimeInput(format=custom_format)
909 self.assertFalse(w._has_changed(formats.localize_input(data), data.strftime(custom_format)))
910
911 def test_dateinput(self):
912 w = DateInput()
913 self.assertEqual(w.render('date', None), u'<input type="text" name="date" />')
914 d = datetime.date(2007, 9, 17)
915 self.assertEqual(str(d), '2007-09-17')
916
917 self.assertEqual(w.render('date', d), u'<input type="text" name="date" value="2007-09-17" />')
918 self.assertEqual(w.render('date', datetime.date(2007, 9, 17)), u'<input type="text" name="date" value="2007-09-17" />')
919
920 # We should be able to initialize from a unicode value.
921 self.assertEqual(w.render('date', u'2007-09-17'), u'<input type="text" name="date" value="2007-09-17" />')
922
923 # Use 'format' to change the way a value is displayed.
924 w = DateInput(format='%d/%m/%Y')
925 self.assertEqual(w.render('date', d), u'<input type="text" name="date" value="17/09/2007" />')
926 self.assertFalse(w._has_changed(d, '17/09/2007'))
927
928 # Make sure a custom format works with _has_changed. The hidden input will use
929 data = datetime.date(2010, 3, 6)
930 custom_format = '%d.%m.%Y'
931 w = DateInput(format=custom_format)
932 self.assertFalse(w._has_changed(formats.localize_input(data), data.strftime(custom_format)))
933
934 def test_timeinput(self):
935 w = TimeInput()
936 self.assertEqual(w.render('time', None), u'<input type="text" name="time" />')
937 t = datetime.time(12, 51, 34, 482548)
938 self.assertEqual(str(t), '12:51:34.482548')
939
940 # The microseconds are trimmed on display, by default.
941 self.assertEqual(w.render('time', t), u'<input type="text" name="time" value="12:51:34" />')
942 self.assertEqual(w.render('time', datetime.time(12, 51, 34)), u'<input type="text" name="time" value="12:51:34" />')
943 self.assertEqual(w.render('time', datetime.time(12, 51)), u'<input type="text" name="time" value="12:51:00" />')
944
945 # We should be able to initialize from a unicode value.
946 self.assertEqual(w.render('time', u'13:12:11'), u'<input type="text" name="time" value="13:12:11" />')
947
948 # Use 'format' to change the way a value is displayed.
949 w = TimeInput(format='%H:%M')
950 self.assertEqual(w.render('time', t), u'<input type="text" name="time" value="12:51" />')
951 self.assertFalse(w._has_changed(t, '12:51'))
952
953 # Make sure a custom format works with _has_changed. The hidden input will use
954 data = datetime.time(13, 0)
955 custom_format = '%I:%M %p'
956 w = TimeInput(format=custom_format)
957 self.assertFalse(w._has_changed(formats.localize_input(data), data.strftime(custom_format)))
958
959 def test_splithiddendatetime(self):
960 from django.forms.widgets import SplitHiddenDateTimeWidget
961
962 w = SplitHiddenDateTimeWidget()
963 self.assertEqual(w.render('date', ''), u'<input type="hidden" name="date_0" /><input type="hidden" name="date_1" />')
964 d = datetime.datetime(2007, 9, 17, 12, 51, 34, 482548)
965 self.assertEqual(str(d), '2007-09-17 12:51:34.482548')
966 self.assertEqual(w.render('date', d), u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" name="date_1" value="12:51:34" />')
967 self.assertEqual(w.render('date', datetime.datetime(2007, 9, 17, 12, 51, 34)), u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" name="date_1" value="12:51:34" />')
968 self.assertEqual(w.render('date', datetime.datetime(2007, 9, 17, 12, 51)), u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" name="date_1" value="12:51:00" />')
969
970
971class FormsI18NWidgetsTestCase(TestCase):
972 def setUp(self):
973 super(FormsI18NWidgetsTestCase, self).setUp()
974 self.old_use_l10n = getattr(settings, 'USE_L10N', False)
975 settings.USE_L10N = True
976 activate('de-at')
977
978 def tearDown(self):
979 deactivate()
980 settings.USE_L10N = self.old_use_l10n
981 super(FormsI18NWidgetsTestCase, self).tearDown()
982
983 def test_splitdatetime(self):
984 w = SplitDateTimeWidget(date_format='%d/%m/%Y', time_format='%H:%M')
985 self.assertTrue(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), [u'06.05.2008', u'12:41']))
986
987 def test_datetimeinput(self):
988 w = DateTimeInput()
989 d = datetime.datetime(2007, 9, 17, 12, 51, 34, 482548)
990 w.is_localized = True
991 self.assertEqual(w.render('date', d), u'<input type="text" name="date" value="17.09.2007 12:51:34" />')
992
993 def test_dateinput(self):
994 w = DateInput()
995 d = datetime.date(2007, 9, 17)
996 w.is_localized = True
997 self.assertEqual(w.render('date', d), u'<input type="text" name="date" value="17.09.2007" />')
998
999 def test_timeinput(self):
1000 w = TimeInput()
1001 t = datetime.time(12, 51, 34, 482548)
1002 w.is_localized = True
1003 self.assertEqual(w.render('time', t), u'<input type="text" name="time" value="12:51:34" />')
1004
1005 def test_splithiddendatetime(self):
1006 from django.forms.widgets import SplitHiddenDateTimeWidget
1007
1008 w = SplitHiddenDateTimeWidget()
1009 w.is_localized = True
1010 self.assertEqual(w.render('date', datetime.datetime(2007, 9, 17, 12, 51)), u'<input type="hidden" name="date_0" value="17.09.2007" /><input type="hidden" name="date_1" value="12:51:00" />')
1011
1012
1013class SelectAndTextWidget(MultiWidget):
1014 """
1015 MultiWidget subclass
1016 """
1017 def __init__(self, choices=[]):
1018 widgets = [
1019 RadioSelect(choices=choices),
1020 TextInput
1021 ]
1022 super(SelectAndTextWidget, self).__init__(widgets)
1023
1024 def _set_choices(self, choices):
1025 """
1026 When choices are set for this widget, we want to pass those along to the Select widget
1027 """
1028 self.widgets[0].choices = choices
1029 def _get_choices(self):
1030 """
1031 The choices for this widget are the Select widget's choices
1032 """
1033 return self.widgets[0].choices
1034 choices = property(_get_choices, _set_choices)
1035
1036
1037class WidgetTests(TestCase):
1038 def test_12048(self):
1039 # See ticket #12048.
1040 w1 = SelectAndTextWidget(choices=[1,2,3])
1041 w2 = copy.deepcopy(w1)
1042 w2.choices = [4,5,6]
1043 # w2 ought to be independent of w1, since MultiWidget ought
1044 # to make a copy of its sub-widgets when it is copied.
1045 self.assertEqual(w1.choices, [1,2,3])
1046
1047 def test_13390(self):
1048 # See ticket #13390
1049 class SplitDateForm(Form):
1050 field = DateTimeField(widget=SplitDateTimeWidget, required=False)
1051
1052 form = SplitDateForm({'field': ''})
1053 self.assertTrue(form.is_valid())
1054 form = SplitDateForm({'field': ['', '']})
1055 self.assertTrue(form.is_valid())
1056
1057 class SplitDateRequiredForm(Form):
1058 field = DateTimeField(widget=SplitDateTimeWidget, required=True)
1059
1060 form = SplitDateRequiredForm({'field': ''})
1061 self.assertFalse(form.is_valid())
1062 form = SplitDateRequiredForm({'field': ['', '']})
1063 self.assertFalse(form.is_valid())
1064
1065
1066class FakeFieldFile(object):
1067 """
1068 Quacks like a FieldFile (has a .url and unicode representation), but
1069 doesn't require us to care about storages etc.
1070
1071 """
1072 url = 'something'
1073
1074 def __unicode__(self):
1075 return self.url
1076
1077class ClearableFileInputTests(TestCase):
1078 def test_clear_input_renders(self):
1079 """
1080 A ClearableFileInput with is_required False and rendered with
1081 an initial value that is a file renders a clear checkbox.
1082
1083 """
1084 widget = ClearableFileInput()
1085 widget.is_required = False
1086 self.assertEqual(widget.render('myfile', FakeFieldFile()),
1087 u'Currently: <a href="something">something</a> <input type="checkbox" name="myfile-clear" id="myfile-clear_id" /> <label for="myfile-clear_id">Clear</label><br />Change: <input type="file" name="myfile" />')
1088
1089 def test_html_escaped(self):
1090 """
1091 A ClearableFileInput should escape name, filename and URL when
1092 rendering HTML. Refs #15182.
1093 """
1094
1095 class StrangeFieldFile(object):
1096 url = "something?chapter=1§=2©=3&lang=en"
1097
1098 def __unicode__(self):
1099 return u'''something<div onclick="alert('oops')">.jpg'''
1100
1101 widget = ClearableFileInput()
1102 field = StrangeFieldFile()
1103 output = widget.render('my<div>file', field)
1104 self.assertFalse(field.url in output)
1105 self.assertTrue(u'href="something?chapter=1&sect=2&copy=3&lang=en"' in output)
1106 self.assertFalse(unicode(field) in output)
1107 self.assertTrue(u'something<div onclick="alert('oops')">.jpg' in output)
1108 self.assertTrue(u'my<div>file' in output)
1109 self.assertFalse(u'my<div>file' in output)
1110
1111 def test_clear_input_renders_only_if_not_required(self):
1112 """
1113 A ClearableFileInput with is_required=False does not render a clear
1114 checkbox.
1115
1116 """
1117 widget = ClearableFileInput()
1118 widget.is_required = True
1119 self.assertEqual(widget.render('myfile', FakeFieldFile()),
1120 u'Currently: <a href="something">something</a> <br />Change: <input type="file" name="myfile" />')
1121
1122 def test_clear_input_renders_only_if_initial(self):
1123 """
1124 A ClearableFileInput instantiated with no initial value does not render
1125 a clear checkbox.
1126
1127 """
1128 widget = ClearableFileInput()
1129 widget.is_required = False
1130 self.assertEqual(widget.render('myfile', None),
1131 u'<input type="file" name="myfile" />')
1132
1133 def test_clear_input_checked_returns_false(self):
1134 """
1135 ClearableFileInput.value_from_datadict returns False if the clear
1136 checkbox is checked, if not required.
1137
1138 """
1139 widget = ClearableFileInput()
1140 widget.is_required = False
1141 self.assertEqual(widget.value_from_datadict(
1142 data={'myfile-clear': True},
1143 files={},
1144 name='myfile'), False)
1145
1146 def test_clear_input_checked_returns_false_only_if_not_required(self):
1147 """
1148 ClearableFileInput.value_from_datadict never returns False if the field
1149 is required.
1150
1151 """
1152 widget = ClearableFileInput()
1153 widget.is_required = True
1154 f = SimpleUploadedFile('something.txt', 'content')
1155 self.assertEqual(widget.value_from_datadict(
1156 data={'myfile-clear': True},
1157 files={'myfile': f},
1158 name='myfile'), f)