Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Made BoundFields iterable, so that you can iterate over individual ra…

…dio buttons of a RadioSelect in a template

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17173 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit fc90c09efdfbc029d43e6d4c0952ed6b49f6f34d 1 parent 0519adb
Adrian Holovaty authored December 07, 2011
10  django/forms/forms.py
@@ -410,6 +410,16 @@ def __unicode__(self):
410 410
             return self.as_widget() + self.as_hidden(only_initial=True)
411 411
         return self.as_widget()
412 412
 
  413
+    def __iter__(self):
  414
+        """
  415
+        Yields rendered strings that comprise all widgets in this BoundField.
  416
+
  417
+        This really is only useful for RadioSelect widgets, so that you can
  418
+        iterate over individual radio buttons in a template.
  419
+        """
  420
+        for subwidget in self.field.widget.subwidgets(self.html_name, self.value()):
  421
+            yield self.as_widget(subwidget)
  422
+
413 423
     def _errors(self):
414 424
         """
415 425
         Returns an ErrorList for this field. Returns an empty ErrorList
19  django/forms/widgets.py
@@ -156,6 +156,15 @@ def __deepcopy__(self, memo):
156 156
         memo[id(self)] = obj
157 157
         return obj
158 158
 
  159
+    def subwidgets(self, name, value, attrs=None, choices=()):
  160
+        """
  161
+        Yields all "subwidgets" of this widget. Used only by RadioSelect to
  162
+        allow template access to individual <input type="radio"> buttons.
  163
+
  164
+        Arguments are the same as for render().
  165
+        """
  166
+        yield self
  167
+
159 168
     def render(self, name, value, attrs=None):
160 169
         """
161 170
         Returns this Widget rendered as HTML, as a Unicode string.
@@ -628,6 +637,12 @@ def __init__(self, name, value, attrs, choice, index):
628 637
         self.index = index
629 638
 
630 639
     def __unicode__(self):
  640
+        return self.render()
  641
+
  642
+    def render(self, name=None, value=None, attrs=None, choices=()):
  643
+        name = name or self.name
  644
+        value = value or self.value
  645
+        attrs = attrs or self.attrs
631 646
         if 'id' in self.attrs:
632 647
             label_for = ' for="%s_%s"' % (self.attrs['id'], self.index)
633 648
         else:
@@ -681,6 +696,10 @@ def __init__(self, *args, **kwargs):
681 696
             self.renderer = renderer
682 697
         super(RadioSelect, self).__init__(*args, **kwargs)
683 698
 
  699
+    def subwidgets(self, name, value, attrs=None, choices=()):
  700
+        for widget in self.get_renderer(name, value, attrs, choices):
  701
+            yield widget
  702
+
684 703
     def get_renderer(self, name, value, attrs=None, choices=()):
685 704
         """Returns an instance of the renderer."""
686 705
         if value is None: value = ''
37  docs/ref/forms/widgets.txt
@@ -345,7 +345,8 @@ commonly used groups of widgets:
345 345
 
346 346
 .. class:: RadioSelect
347 347
 
348  
-    Similar to :class:`Select`, but rendered as a list of radio buttons:
  348
+    Similar to :class:`Select`, but rendered as a list of radio buttons within
  349
+    ``<li>`` tags:
349 350
 
350 351
     .. code-block:: html
351 352
 
@@ -354,6 +355,40 @@ commonly used groups of widgets:
354 355
           ...
355 356
         </ul>
356 357
 
  358
+    .. versionadded:: 1.4
  359
+
  360
+    For more granular control over the generated markup, you can loop over the
  361
+    radio buttons in the template. Assuming a form ``myform`` with a field
  362
+    ``beatles`` that uses a ``RadioSelect`` as its widget:
  363
+
  364
+    .. code-block:: html+django
  365
+
  366
+        {% for radio in myform.beatles %}
  367
+        <div class="myradio">
  368
+            {{ radio }}
  369
+        </div>
  370
+        {% endfor %}
  371
+
  372
+    This would generate the following HTML:
  373
+
  374
+    .. code-block:: html
  375
+
  376
+        <div class="myradio">
  377
+            <label><input type="radio" name="beatles" value="john" /> John</label>
  378
+        </div>
  379
+        <div class="myradio">
  380
+            <label><input type="radio" name="beatles" value="paul" /> Paul</label>
  381
+        </div>
  382
+        <div class="myradio">
  383
+            <label><input type="radio" name="beatles" value="george" /> George</label>
  384
+        </div>
  385
+        <div class="myradio">
  386
+            <label><input type="radio" name="beatles" value="ringo" /> Ringo</label>
  387
+        </div>
  388
+
  389
+    If you decide not to loop over the radio buttons, they'll be output in a
  390
+    ``<ul>`` with ``<li>`` tags, as above.
  391
+
357 392
 ``CheckboxSelectMultiple``
358 393
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
359 394
 
22  tests/regressiontests/forms/tests/forms.py
@@ -434,6 +434,28 @@ class FrameworkForm(Form):
434 434
 <li><label for="id_language_1"><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
435 435
 </ul></p>""")
436 436
 
  437
+    def test_form_with_iterable_boundfield(self):
  438
+        class BeatleForm(Form):
  439
+            name = ChoiceField(choices=[('john', 'John'), ('paul', 'Paul'), ('george', 'George'), ('ringo', 'Ringo')], widget=RadioSelect)
  440
+
  441
+        f = BeatleForm(auto_id=False)
  442
+        self.assertEqual('\n'.join(list(f['name'])), """<label><input type="radio" name="name" value="john" /> John</label>
  443
+<label><input type="radio" name="name" value="paul" /> Paul</label>
  444
+<label><input type="radio" name="name" value="george" /> George</label>
  445
+<label><input type="radio" name="name" value="ringo" /> Ringo</label>""")
  446
+        self.assertEqual('\n'.join(['<div>%s</div>' % bf for bf in f['name']]), """<div><label><input type="radio" name="name" value="john" /> John</label></div>
  447
+<div><label><input type="radio" name="name" value="paul" /> Paul</label></div>
  448
+<div><label><input type="radio" name="name" value="george" /> George</label></div>
  449
+<div><label><input type="radio" name="name" value="ringo" /> Ringo</label></div>""")
  450
+
  451
+    def test_form_with_noniterable_boundfield(self):
  452
+        # You can iterate over any BoundField, not just those with widget=RadioSelect.
  453
+        class BeatleForm(Form):
  454
+            name = CharField()
  455
+
  456
+        f = BeatleForm(auto_id=False)
  457
+        self.assertEqual('\n'.join(list(f['name'])), u'<input type="text" name="name" />')
  458
+
437 459
     def test_forms_wit_hmultiple_choice(self):
438 460
         # MultipleChoiceField is a special case, as its data is required to be a list:
439 461
         class SongForm(Form):

0 notes on commit fc90c09

Please sign in to comment.
Something went wrong with that request. Please try again.