test_multiwidget.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import copy
  2. from datetime import datetime
  3. from django.forms import (
  4. CharField, FileInput, MultipleChoiceField, MultiValueField, MultiWidget,
  5. RadioSelect, SelectMultiple, SplitDateTimeField, SplitDateTimeWidget,
  6. TextInput,
  7. )
  8. from .base import WidgetTest
  9. class MyMultiWidget(MultiWidget):
  10. def decompress(self, value):
  11. if value:
  12. return value.split('__')
  13. return ['', '']
  14. class ComplexMultiWidget(MultiWidget):
  15. def __init__(self, attrs=None):
  16. widgets = (
  17. TextInput(),
  18. SelectMultiple(choices=WidgetTest.beatles),
  19. SplitDateTimeWidget(),
  20. )
  21. super().__init__(widgets, attrs)
  22. def decompress(self, value):
  23. if value:
  24. data = value.split(',')
  25. return [
  26. data[0], list(data[1]), datetime.strptime(data[2], "%Y-%m-%d %H:%M:%S")
  27. ]
  28. return [None, None, None]
  29. class ComplexField(MultiValueField):
  30. def __init__(self, required=True, widget=None, label=None, initial=None):
  31. fields = (
  32. CharField(),
  33. MultipleChoiceField(choices=WidgetTest.beatles),
  34. SplitDateTimeField(),
  35. )
  36. super().__init__(fields, required, widget, label, initial)
  37. def compress(self, data_list):
  38. if data_list:
  39. return '%s,%s,%s' % (
  40. data_list[0], ''.join(data_list[1]), data_list[2],
  41. )
  42. return None
  43. class DeepCopyWidget(MultiWidget):
  44. """
  45. Used to test MultiWidget.__deepcopy__().
  46. """
  47. def __init__(self, choices=[]):
  48. widgets = [
  49. RadioSelect(choices=choices),
  50. TextInput,
  51. ]
  52. super().__init__(widgets)
  53. def _set_choices(self, choices):
  54. """
  55. When choices are set for this widget, we want to pass those along to
  56. the Select widget.
  57. """
  58. self.widgets[0].choices = choices
  59. def _get_choices(self):
  60. """
  61. The choices for this widget are the Select widget's choices.
  62. """
  63. return self.widgets[0].choices
  64. choices = property(_get_choices, _set_choices)
  65. class MultiWidgetTest(WidgetTest):
  66. def test_text_inputs(self):
  67. widget = MyMultiWidget(
  68. widgets=(
  69. TextInput(attrs={'class': 'big'}),
  70. TextInput(attrs={'class': 'small'}),
  71. )
  72. )
  73. self.check_html(widget, 'name', ['john', 'lennon'], html=(
  74. '<input type="text" class="big" value="john" name="name_0">'
  75. '<input type="text" class="small" value="lennon" name="name_1">'
  76. ))
  77. self.check_html(widget, 'name', 'john__lennon', html=(
  78. '<input type="text" class="big" value="john" name="name_0">'
  79. '<input type="text" class="small" value="lennon" name="name_1">'
  80. ))
  81. self.check_html(widget, 'name', 'john__lennon', attrs={'id': 'foo'}, html=(
  82. '<input id="foo_0" type="text" class="big" value="john" name="name_0">'
  83. '<input id="foo_1" type="text" class="small" value="lennon" name="name_1">'
  84. ))
  85. def test_constructor_attrs(self):
  86. widget = MyMultiWidget(
  87. widgets=(
  88. TextInput(attrs={'class': 'big'}),
  89. TextInput(attrs={'class': 'small'}),
  90. ),
  91. attrs={'id': 'bar'},
  92. )
  93. self.check_html(widget, 'name', ['john', 'lennon'], html=(
  94. '<input id="bar_0" type="text" class="big" value="john" name="name_0">'
  95. '<input id="bar_1" type="text" class="small" value="lennon" name="name_1">'
  96. ))
  97. def test_constructor_attrs_with_type(self):
  98. attrs = {'type': 'number'}
  99. widget = MyMultiWidget(widgets=(TextInput, TextInput()), attrs=attrs)
  100. self.check_html(widget, 'code', ['1', '2'], html=(
  101. '<input type="number" value="1" name="code_0">'
  102. '<input type="number" value="2" name="code_1">'
  103. ))
  104. widget = MyMultiWidget(widgets=(TextInput(attrs), TextInput(attrs)), attrs={'class': 'bar'})
  105. self.check_html(widget, 'code', ['1', '2'], html=(
  106. '<input type="number" value="1" name="code_0" class="bar">'
  107. '<input type="number" value="2" name="code_1" class="bar">'
  108. ))
  109. def test_value_omitted_from_data(self):
  110. widget = MyMultiWidget(widgets=(TextInput(), TextInput()))
  111. self.assertIs(widget.value_omitted_from_data({}, {}, 'field'), True)
  112. self.assertIs(widget.value_omitted_from_data({'field_0': 'x'}, {}, 'field'), False)
  113. self.assertIs(widget.value_omitted_from_data({'field_1': 'y'}, {}, 'field'), False)
  114. self.assertIs(widget.value_omitted_from_data({'field_0': 'x', 'field_1': 'y'}, {}, 'field'), False)
  115. def test_needs_multipart_true(self):
  116. """
  117. needs_multipart_form should be True if any widgets need it.
  118. """
  119. widget = MyMultiWidget(widgets=(TextInput(), FileInput()))
  120. self.assertTrue(widget.needs_multipart_form)
  121. def test_needs_multipart_false(self):
  122. """
  123. needs_multipart_form should be False if no widgets need it.
  124. """
  125. widget = MyMultiWidget(widgets=(TextInput(), TextInput()))
  126. self.assertFalse(widget.needs_multipart_form)
  127. def test_nested_multiwidget(self):
  128. """
  129. MultiWidgets can be composed of other MultiWidgets.
  130. """
  131. widget = ComplexMultiWidget()
  132. self.check_html(widget, 'name', 'some text,JP,2007-04-25 06:24:00', html=(
  133. """
  134. <input type="text" name="name_0" value="some text">
  135. <select multiple name="name_1">
  136. <option value="J" selected>John</option>
  137. <option value="P" selected>Paul</option>
  138. <option value="G">George</option>
  139. <option value="R">Ringo</option>
  140. </select>
  141. <input type="text" name="name_2_0" value="2007-04-25">
  142. <input type="text" name="name_2_1" value="06:24:00">
  143. """
  144. ))
  145. def test_no_whitespace_between_widgets(self):
  146. widget = MyMultiWidget(widgets=(TextInput, TextInput()))
  147. self.check_html(widget, 'code', None, html=(
  148. '<input type="text" name="code_0">'
  149. '<input type="text" name="code_1">'
  150. ), strict=True)
  151. def test_deepcopy(self):
  152. """
  153. MultiWidget should define __deepcopy__() (#12048).
  154. """
  155. w1 = DeepCopyWidget(choices=[1, 2, 3])
  156. w2 = copy.deepcopy(w1)
  157. w2.choices = [4, 5, 6]
  158. # w2 ought to be independent of w1, since MultiWidget ought
  159. # to make a copy of its sub-widgets when it is copied.
  160. self.assertEqual(w1.choices, [1, 2, 3])