Code

Ticket #4592: 4592-2.diff

File 4592-2.diff, 9.2 KB (added by Matt McClanahan <cardinal@…>, 7 years ago)

Tweak

Line 
1Index: django/newforms/widgets.py
2===================================================================
3--- django/newforms/widgets.py  (revision 5582)
4+++ django/newforms/widgets.py  (working copy)
5@@ -215,8 +215,10 @@
6             return data.getlist(name)
7         return data.get(name, None)
8 
9-class RadioInput(StrAndUnicode):
10-    "An object used by RadioFieldRenderer that represents a single <input type='radio'>."
11+class ChoiceInput(StrAndUnicode):
12+    input_type = None # Subclasses must define this.
13+
14+    "An object used by ChoiceFieldRenderer that represents a single radio or checkbox input."
15     def __init__(self, name, value, attrs, choice, index):
16         self.name, self.value = name, value
17         self.attrs = attrs
18@@ -233,36 +235,46 @@
19     def tag(self):
20         if 'id' in self.attrs:
21             self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
22-        final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
23+        final_attrs = dict(self.attrs, type=self.input_type, name=self.name, value=self.choice_value)
24         if self.is_checked():
25             final_attrs['checked'] = 'checked'
26         return u'<input%s />' % flatatt(final_attrs)
27 
28-class RadioFieldRenderer(StrAndUnicode):
29-    "An object used by RadioSelect to enable customization of radio widgets."
30-    def __init__(self, name, value, attrs, choices):
31+class RadioChoiceInput(ChoiceInput):
32+    input_type = 'radio'
33+
34+class CheckboxChoiceInput(ChoiceInput):
35+    input_type = 'checkbox'
36+
37+    def is_checked(self):
38+        return self.choice_value in self.value
39+
40+class ChoiceFieldRenderer(StrAndUnicode):
41+    "An object used to enable customization of radio and checkbox widgets."
42+    def __init__(self, name, value, attrs, choices, choice_widget=ChoiceInput):
43         self.name, self.value, self.attrs = name, value, attrs
44         self.choices = choices
45+        self.choice_widget = choice_widget
46 
47     def __iter__(self):
48         for i, choice in enumerate(self.choices):
49-            yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i)
50+            yield self.choice_widget(self.name, self.value, self.attrs.copy(), choice, i)
51 
52     def __getitem__(self, idx):
53         choice = self.choices[idx] # Let the IndexError propogate
54-        return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
55+        return self.choice_widget(self.name, self.value, self.attrs.copy(), choice, idx)
56 
57     def __unicode__(self):
58-        "Outputs a <ul> for this set of radio fields."
59+        "Outputs a <ul> for this set of choice fields."
60         return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % w for w in self])
61 
62 class RadioSelect(Select):
63     def render(self, name, value, attrs=None, choices=()):
64-        "Returns a RadioFieldRenderer instance rather than a Unicode string."
65+        "Returns a ChoiceFieldRenderer instance rather than a Unicode string."
66         if value is None: value = ''
67         str_value = smart_unicode(value) # Normalize to string.
68         final_attrs = self.build_attrs(attrs)
69-        return RadioFieldRenderer(name, str_value, final_attrs, list(chain(self.choices, choices)))
70+        return ChoiceFieldRenderer(name, str_value, final_attrs, list(chain(self.choices, choices)), choice_widget=RadioChoiceInput)
71 
72     def id_for_label(self, id_):
73         # RadioSelect is represented by multiple <input type="radio"> fields,
74@@ -276,22 +288,11 @@
75 
76 class CheckboxSelectMultiple(SelectMultiple):
77     def render(self, name, value, attrs=None, choices=()):
78-        if value is None: value = []
79-        has_id = attrs and 'id' in attrs
80+        "Returns a ChoiceFieldRenderer instance rather than a Unicode string."
81+        if value is None: value = ''
82+        str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
83         final_attrs = self.build_attrs(attrs, name=name)
84-        output = [u'<ul>']
85-        str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
86-        for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
87-            # If an ID attribute was given, add a numeric index as a suffix,
88-            # so that the checkboxes don't all have the same ID attribute.
89-            if has_id:
90-                final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
91-            cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
92-            option_value = smart_unicode(option_value)
93-            rendered_cb = cb.render(name, option_value)
94-            output.append(u'<li><label>%s %s</label></li>' % (rendered_cb, escape(smart_unicode(option_label))))
95-        output.append(u'</ul>')
96-        return u'\n'.join(output)
97+        return ChoiceFieldRenderer(name, str_values, final_attrs, list(chain(self.choices, choices)), choice_widget=CheckboxChoiceInput)
98 
99     def id_for_label(self, id_):
100         # See the comment for RadioSelect.id_for_label()
101Index: tests/regressiontests/forms/tests.py
102===================================================================
103--- tests/regressiontests/forms/tests.py        (revision 5582)
104+++ tests/regressiontests/forms/tests.py        (working copy)
105@@ -614,7 +614,7 @@
106 <li><label><input type="radio" name="num" value="5" /> 5</label></li>
107 </ul>
108 
109-The render() method returns a RadioFieldRenderer object, whose str() is a <ul>.
110+The render() method returns a ChoiceFieldRenderer object, whose str() is a <ul>.
111 You can manipulate that object directly to customize the way the RadioSelect
112 is rendered.
113 >>> w = RadioSelect()
114@@ -644,7 +644,7 @@
115 beatle J G George False
116 beatle J R Ringo False
117 
118-A RadioFieldRenderer object also allows index access to individual RadioInput
119+A ChoiceFieldRenderer object also allows index access to individual RadioInput
120 objects.
121 >>> w = RadioSelect()
122 >>> r = w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
123@@ -792,7 +792,56 @@
124 <li><label><input type="checkbox" name="nums" value="5" /> 5</label></li>
125 </ul>
126 
127->>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
128+The render() method returns a ChoiceFieldRenderer object, whose str() is a <ul>.
129+You can manipulate that object directly to customize the way the RadioSelect
130+is rendered.
131+>>> w2 = CheckboxSelectMultiple()
132+>>> r = w2.render('beatle', ['J', 'G'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
133+>>> for inp in r:
134+...     print inp
135+<label><input checked="checked" type="checkbox" name="beatle" value="J" /> John</label>
136+<label><input type="checkbox" name="beatle" value="P" /> Paul</label>
137+<label><input checked="checked" type="checkbox" name="beatle" value="G" /> George</label>
138+<label><input type="checkbox" name="beatle" value="R" /> Ringo</label>
139+>>> for inp in r:
140+...     print '%s<br />' % inp
141+<label><input checked="checked" type="checkbox" name="beatle" value="J" /> John</label><br />
142+<label><input type="checkbox" name="beatle" value="P" /> Paul</label><br />
143+<label><input checked="checked" type="checkbox" name="beatle" value="G" /> George</label><br />
144+<label><input type="checkbox" name="beatle" value="R" /> Ringo</label><br />
145+>>> for inp in r:
146+...     print '<p>%s %s</p>' % (inp.tag(), inp.choice_label)
147+<p><input checked="checked" type="checkbox" name="beatle" value="J" /> John</p>
148+<p><input type="checkbox" name="beatle" value="P" /> Paul</p>
149+<p><input checked="checked" type="checkbox" name="beatle" value="G" /> George</p>
150+<p><input type="checkbox" name="beatle" value="R" /> Ringo</p>
151+>>> for inp in r:
152+...     print '%s %s %s %s %s' % (inp.name, inp.value, inp.choice_value, inp.choice_label, inp.is_checked())
153+beatle set([u'J', u'G']) J John True
154+beatle set([u'J', u'G']) P Paul False
155+beatle set([u'J', u'G']) G George True
156+beatle set([u'J', u'G']) R Ringo False
157+
158+A ChoiceFieldRenderer object also allows index access to individual RadioInput
159+objects.
160+>>> w2 = CheckboxSelectMultiple()
161+>>> r = w2.render('beatle', ['J'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
162+>>> print r[1]
163+<label><input type="checkbox" name="beatle" value="P" /> Paul</label>
164+>>> print r[0]
165+<label><input checked="checked" type="checkbox" name="beatle" value="J" /> John</label>
166+>>> r[0].is_checked()
167+True
168+>>> r[1].is_checked()
169+False
170+>>> r[1].name, r[1].value, r[1].choice_value, r[1].choice_label
171+('beatle', set([u'J']), u'P', u'Paul')
172+>>> r[10]
173+Traceback (most recent call last):
174+...
175+IndexError: list index out of range
176+
177+>>> unicode(w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]))
178 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>'
179 
180 # MultiWidget #################################################################