Index: django/newforms/widgets.py
===================================================================
--- django/newforms/widgets.py	(revision 5620)
+++ django/newforms/widgets.py	(working copy)
@@ -215,8 +215,10 @@
             return data.getlist(name)
         return data.get(name, None)
 
-class RadioInput(StrAndUnicode):
-    "An object used by RadioFieldRenderer that represents a single <input type='radio'>."
+class ChoiceInput(StrAndUnicode):
+    input_type = None # Subclasses must define this.
+
+    "An object used by ChoiceFieldRenderer that represents a single radio or checkbox input."
     def __init__(self, name, value, attrs, choice, index):
         self.name, self.value = name, value
         self.attrs = attrs
@@ -233,36 +235,46 @@
     def tag(self):
         if 'id' in self.attrs:
             self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
-        final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
+        final_attrs = dict(self.attrs, type=self.input_type, name=self.name, value=self.choice_value)
         if self.is_checked():
             final_attrs['checked'] = 'checked'
         return u'<input%s />' % flatatt(final_attrs)
 
-class RadioFieldRenderer(StrAndUnicode):
-    "An object used by RadioSelect to enable customization of radio widgets."
-    def __init__(self, name, value, attrs, choices):
+class RadioChoiceInput(ChoiceInput):
+    input_type = 'radio'
+
+class CheckboxChoiceInput(ChoiceInput):
+    input_type = 'checkbox'
+
+    def is_checked(self):
+        return self.choice_value in self.value
+
+class ChoiceFieldRenderer(StrAndUnicode):
+    "An object used to enable customization of radio and checkbox widgets."
+    def __init__(self, name, value, attrs, choices, choice_widget=ChoiceInput):
         self.name, self.value, self.attrs = name, value, attrs
         self.choices = choices
+        self.choice_widget = choice_widget
 
     def __iter__(self):
         for i, choice in enumerate(self.choices):
-            yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i)
+            yield self.choice_widget(self.name, self.value, self.attrs.copy(), choice, i)
 
     def __getitem__(self, idx):
         choice = self.choices[idx] # Let the IndexError propogate
-        return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
+        return self.choice_widget(self.name, self.value, self.attrs.copy(), choice, idx)
 
     def __unicode__(self):
-        "Outputs a <ul> for this set of radio fields."
+        "Outputs a <ul> for this set of choice fields."
         return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % force_unicode(w) for w in self])
 
 class RadioSelect(Select):
     def render(self, name, value, attrs=None, choices=()):
-        "Returns a RadioFieldRenderer instance rather than a Unicode string."
+        "Returns a ChoiceFieldRenderer instance rather than a Unicode string."
         if value is None: value = ''
         str_value = force_unicode(value) # Normalize to string.
         final_attrs = self.build_attrs(attrs)
-        return RadioFieldRenderer(name, str_value, final_attrs, list(chain(self.choices, choices)))
+        return ChoiceFieldRenderer(name, str_value, final_attrs, list(chain(self.choices, choices)), choice_widget=RadioChoiceInput)
 
     def id_for_label(self, id_):
         # RadioSelect is represented by multiple <input type="radio"> fields,
@@ -276,22 +288,11 @@
 
 class CheckboxSelectMultiple(SelectMultiple):
     def render(self, name, value, attrs=None, choices=()):
+        "Returns a ChoiceFieldRenderer instance rather than a Unicode string."
         if value is None: value = []
-        has_id = attrs and 'id' in attrs
+        str_values = set([force_unicode(v) for v in value]) # Normalize to strings.
         final_attrs = self.build_attrs(attrs, name=name)
-        output = [u'<ul>']
-        str_values = set([force_unicode(v) for v in value]) # Normalize to strings.
-        for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
-            # If an ID attribute was given, add a numeric index as a suffix,
-            # so that the checkboxes don't all have the same ID attribute.
-            if has_id:
-                final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
-            cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
-            option_value = force_unicode(option_value)
-            rendered_cb = cb.render(name, option_value)
-            output.append(u'<li><label>%s %s</label></li>' % (rendered_cb, escape(force_unicode(option_label))))
-        output.append(u'</ul>')
-        return u'\n'.join(output)
+        return ChoiceFieldRenderer(name, str_values, final_attrs, list(chain(self.choices, choices)), choice_widget=CheckboxChoiceInput)
 
     def id_for_label(self, id_):
         # See the comment for RadioSelect.id_for_label()
Index: tests/regressiontests/forms/tests.py
===================================================================
--- tests/regressiontests/forms/tests.py	(revision 5620)
+++ tests/regressiontests/forms/tests.py	(working copy)
@@ -614,7 +614,7 @@
 <li><label><input type="radio" name="num" value="5" /> 5</label></li>
 </ul>
 
-The render() method returns a RadioFieldRenderer object, whose str() is a <ul>.
+The render() method returns a ChoiceFieldRenderer object, whose str() is a <ul>.
 You can manipulate that object directly to customize the way the RadioSelect
 is rendered.
 >>> w = RadioSelect()
@@ -644,7 +644,7 @@
 beatle J G George False
 beatle J R Ringo False
 
-A RadioFieldRenderer object also allows index access to individual RadioInput
+A ChoiceFieldRenderer object also allows index access to individual RadioInput
 objects.
 >>> w = RadioSelect()
 >>> r = w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
@@ -792,7 +792,56 @@
 <li><label><input type="checkbox" name="nums" value="5" /> 5</label></li>
 </ul>
 
->>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
+The render() method returns a ChoiceFieldRenderer object, whose str() is a <ul>.
+You can manipulate that object directly to customize the way the RadioSelect
+is rendered.
+>>> w2 = CheckboxSelectMultiple()
+>>> r = w2.render('beatle', ['J', 'G'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+>>> for inp in r:
+...     print inp
+<label><input checked="checked" type="checkbox" name="beatle" value="J" /> John</label>
+<label><input type="checkbox" name="beatle" value="P" /> Paul</label>
+<label><input checked="checked" type="checkbox" name="beatle" value="G" /> George</label>
+<label><input type="checkbox" name="beatle" value="R" /> Ringo</label>
+>>> for inp in r:
+...     print '%s<br />' % inp
+<label><input checked="checked" type="checkbox" name="beatle" value="J" /> John</label><br />
+<label><input type="checkbox" name="beatle" value="P" /> Paul</label><br />
+<label><input checked="checked" type="checkbox" name="beatle" value="G" /> George</label><br />
+<label><input type="checkbox" name="beatle" value="R" /> Ringo</label><br />
+>>> for inp in r:
+...     print '<p>%s %s</p>' % (inp.tag(), inp.choice_label)
+<p><input checked="checked" type="checkbox" name="beatle" value="J" /> John</p>
+<p><input type="checkbox" name="beatle" value="P" /> Paul</p>
+<p><input checked="checked" type="checkbox" name="beatle" value="G" /> George</p>
+<p><input type="checkbox" name="beatle" value="R" /> Ringo</p>
+>>> for inp in r:
+...     print '%s %s %s %s %s' % (inp.name, inp.value, inp.choice_value, inp.choice_label, inp.is_checked())
+beatle set([u'J', u'G']) J John True
+beatle set([u'J', u'G']) P Paul False
+beatle set([u'J', u'G']) G George True
+beatle set([u'J', u'G']) R Ringo False
+
+A ChoiceFieldRenderer object also allows index access to individual RadioInput
+objects.
+>>> w2 = CheckboxSelectMultiple()
+>>> r = w2.render('beatle', ['J'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+>>> print r[1]
+<label><input type="checkbox" name="beatle" value="P" /> Paul</label>
+>>> print r[0]
+<label><input checked="checked" type="checkbox" name="beatle" value="J" /> John</label>
+>>> r[0].is_checked()
+True
+>>> r[1].is_checked()
+False
+>>> r[1].name, r[1].value, r[1].choice_value, r[1].choice_label
+('beatle', set([u'J']), u'P', u'Paul')
+>>> r[10]
+Traceback (most recent call last):
+...
+IndexError: list index out of range
+
+>>> unicode(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>'
 
 # MultiWidget #################################################################
