diff --git a/django/contrib/admin/__init__.py b/django/contrib/admin/__init__.py
index 9e8b4fb..6a459ec 100644
--- a/django/contrib/admin/__init__.py
+++ b/django/contrib/admin/__init__.py
@@ -1,3 +1,3 @@
-from django.contrib.admin.options import ModelAdmin
+from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL
 from django.contrib.admin.options import StackedInline, TabularInline
 from django.contrib.admin.sites import AdminSite, site
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index c3289ea..5da2489 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -15,6 +15,10 @@ from django.utils.translation import ugettext as _
 from django.utils.encoding import force_unicode
 import sets
 
+HORIZONTAL, VERTICAL = 1, 2
+# returns the <ul> class for a given radio_admin value
+get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
+
 class IncorrectLookupParameters(Exception):
     pass
 
@@ -126,6 +130,7 @@ class AdminField(object):
 class BaseModelAdmin(object):
     """Functionality common to both ModelAdmin and InlineAdmin."""
     raw_id_fields = ()
+    radio_admin_fields = {}
     fields = None
     fieldsets = None
     filter_vertical = ()
@@ -175,6 +180,9 @@ class BaseModelAdmin(object):
         if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
             if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields:
                 kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel)
+            elif isinstance(db_field, models.ForeignKey) and db_field.name in self.radio_admin_fields:
+                kwargs['widget'] = forms.RadioSelect(attrs={'class':get_ul_class(self.radio_admin_fields[db_field.name])})
+                kwargs['empty_label'] = db_field.blank and _('None') or None
             else:
                 if isinstance(db_field, models.ManyToManyField) and db_field.name in self.raw_id_fields:
                     kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
@@ -187,6 +195,12 @@ class BaseModelAdmin(object):
                 formfield.widget.render = widgets.RelatedFieldWidgetWrapper(formfield.widget.render, db_field.rel, self.admin_site)
             return formfield
 
+        if db_field.choices and db_field.name in self.radio_admin_fields:
+            kwargs['widget'] = forms.RadioSelect(choices=db_field.get_choices(include_blank=db_field.blank,
+                                                                              blank_choice=[("", _('None'))]),
+                                                 attrs={'class':get_ul_class(self.radio_admin_fields[db_field.name])})
+            return db_field.formfield(**kwargs)
+
         # For any other type of field, just call its formfield() method.
         return db_field.formfield(**kwargs)
 
diff --git a/django/contrib/redirects/models.py b/django/contrib/redirects/models.py
index 68f4afe..1d1e5c9 100644
--- a/django/contrib/redirects/models.py
+++ b/django/contrib/redirects/models.py
@@ -3,7 +3,7 @@ from django.contrib.sites.models import Site
 from django.utils.translation import ugettext_lazy as _
 
 class Redirect(models.Model):
-    site = models.ForeignKey(Site, radio_admin=models.VERTICAL)
+    site = models.ForeignKey(Site)
     old_path = models.CharField(_('redirect from'), max_length=200, db_index=True,
         help_text=_("This should be an absolute path, excluding the domain name. Example: '/events/search/'."))
     new_path = models.CharField(_('redirect to'), max_length=200, blank=True,
@@ -28,6 +28,7 @@ from django.contrib import admin
 class RedirectAdmin(admin.ModelAdmin):
     list_filter = ('site',)
     search_fields = ('old_path', 'new_path')
+    radio_admin_fields = { 'site': admin.VERTICAL }
 
 admin.site.register(Redirect, RedirectAdmin)
 
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 0ea4eae..40bed55 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -24,8 +24,6 @@ from django.utils.maxlength import LegacyMaxlength
 class NOT_PROVIDED:
     pass
 
-HORIZONTAL, VERTICAL = 1, 2
-
 # The values to use for "blank" in SelectFields. Will be appended to the start of most "choices" lists.
 BLANK_CHOICE_DASH = [("", "---------")]
 BLANK_CHOICE_NONE = [("", "None")]
@@ -33,9 +31,6 @@ BLANK_CHOICE_NONE = [("", "None")]
 # prepares a value for use in a LIKE query
 prep_for_like_query = lambda x: smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
 
-# returns the <ul> class for a given radio_admin value
-get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
-
 class FieldDoesNotExist(Exception):
     pass
 
@@ -81,7 +76,7 @@ class Field(object):
         max_length=None, unique=False, blank=False, null=False, db_index=False,
         core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True,
         unique_for_date=None, unique_for_month=None, unique_for_year=None,
-        validator_list=None, choices=None, radio_admin=None, help_text='', db_column=None,
+        validator_list=None, choices=None, help_text='', db_column=None,
         db_tablespace=None):
         self.name = name
         self.verbose_name = verbose_name
@@ -99,7 +94,6 @@ class Field(object):
         self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month
         self.unique_for_year = unique_for_year
         self._choices = choices or []
-        self.radio_admin = radio_admin
         self.help_text = help_text
         self.db_column = db_column
         self.db_tablespace = db_tablespace or settings.DEFAULT_INDEX_TABLESPACE
@@ -255,12 +249,7 @@ class Field(object):
             params['max_length'] = self.max_length
 
         if self.choices:
-            if self.radio_admin:
-                field_objs = [oldforms.RadioSelectField]
-                params['ul_class'] = get_ul_class(self.radio_admin)
-            else:
-                field_objs = [oldforms.SelectField]
-
+            field_objs = [oldforms.SelectField]
             params['choices'] = self.get_choices_default()
         else:
             field_objs = self.get_manipulator_field_objs()
@@ -347,10 +336,7 @@ class Field(object):
         return first_choice + lst
 
     def get_choices_default(self):
-        if self.radio_admin:
-            return self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE)
-        else:
-            return self.get_choices()
+        return self.get_choices()
 
     def _get_val_from_obj(self, obj):
         if obj:
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 2bd31c9..7146311 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -1,6 +1,6 @@
 from django.db import connection, transaction
 from django.db.models import signals, get_model
-from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, get_ul_class
+from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField
 from django.db.models.related import RelatedObject
 from django.utils.text import capfirst
 from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _
@@ -496,14 +496,10 @@ class ForeignKey(RelatedField, Field):
 
     def prepare_field_objs_and_params(self, manipulator, name_prefix):
         params = {'validator_list': self.validator_list[:], 'member_name': name_prefix + self.attname}
-        if self.radio_admin:
-            field_objs = [oldforms.RadioSelectField]
-            params['ul_class'] = get_ul_class(self.radio_admin)
+        if self.null:
+            field_objs = [oldforms.NullSelectField]
         else:
-            if self.null:
-                field_objs = [oldforms.NullSelectField]
-            else:
-                field_objs = [oldforms.SelectField]
+            field_objs = [oldforms.SelectField]
         params['choices'] = self.get_choices_default()
         return field_objs, params
 
@@ -521,15 +517,11 @@ class ForeignKey(RelatedField, Field):
         if not obj:
             # In required many-to-one fields with only one available choice,
             # select that one available choice. Note: For SelectFields
-            # (radio_admin=False), we have to check that the length of choices
-            # is *2*, not 1, because SelectFields always have an initial
-            # "blank" value. Otherwise (radio_admin=True), we check that the
-            # length is 1.
+            # we have to check that the length of choices is *2*, not 1,
+            # because SelectFields always have an initial "blank" value.
             if not self.blank and self.choices:
                 choice_list = self.get_choices_default()
-                if self.radio_admin and len(choice_list) == 1:
-                    return {self.attname: choice_list[0][0]}
-                if not self.radio_admin and len(choice_list) == 2:
+                if len(choice_list) == 2:
                     return {self.attname: choice_list[1][0]}
         return Field.flatten_data(self, follow, obj)
 
@@ -591,14 +583,10 @@ class OneToOneField(RelatedField, IntegerField):
     # ManyToManyField. This works for now.
     def prepare_field_objs_and_params(self, manipulator, name_prefix):
         params = {'validator_list': self.validator_list[:], 'member_name': name_prefix + self.attname}
-        if self.radio_admin:
-            field_objs = [oldforms.RadioSelectField]
-            params['ul_class'] = get_ul_class(self.radio_admin)
+        if self.null:
+            field_objs = [oldforms.NullSelectField]
         else:
-            if self.null:
-                field_objs = [oldforms.NullSelectField]
-            else:
-                field_objs = [oldforms.SelectField]
+            field_objs = [oldforms.SelectField]
         params['choices'] = self.get_choices_default()
         return field_objs, params
 
diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py
index 4f65ff6..9c72883 100644
--- a/django/newforms/widgets.py
+++ b/django/newforms/widgets.py
@@ -438,8 +438,9 @@ class RadioFieldRenderer(StrAndUnicode):
 
     def render(self):
         """Outputs a <ul> for this set of radio fields."""
-        return mark_safe(u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>'
-                % force_unicode(w) for w in self]))
+        return mark_safe(u'<ul%s>\n%s\n</ul>' % 
+                         (flatatt(self.attrs), 
+                         u'\n'.join([u'<li>%s</li>' % force_unicode(w) for w in self])))
 
 class RadioSelect(Select):
     renderer = RadioFieldRenderer
diff --git a/docs/admin.txt b/docs/admin.txt
index 5bcc94d..c949a4d 100644
--- a/docs/admin.txt
+++ b/docs/admin.txt
@@ -35,6 +35,22 @@ There are four steps in activating the Django admin site:
 ``ModelAdmin`` objects
 ======================
 
+
+``radio_admin_fields``
+~~~~~~~~~~~~~~~~~~~~~~
+
+By default, Django's admin uses a select-box interface (<select>) for
+fields that are ``ForeignKey`` or have ``choices`` set. If a field is
+present in ``radio_admin_fields``, Django will use a radio-button interface
+instead. ``radio_admin_fields`` is a dictionary, keyed by field names.
+Values should be set to either django.contrib.admin.HORIZONTAL for a
+horizontal radio-button list, or django.contrib.admin.VERTICAL for a
+vertical presentation.
+
+Don't include a field in ``radio_admin_fields`` unless it's a ``ForeignKey``
+or has ``choices`` set.
+
+
 ``AdminSite`` objects
 =====================
 
diff --git a/docs/model-api.txt b/docs/model-api.txt
index 3f908ec..748a149 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -664,16 +664,6 @@ unless you want to override the default primary-key behavior.
 ``primary_key=True`` implies ``blank=False``, ``null=False`` and
 ``unique=True``. Only one primary key is allowed on an object.
 
-``radio_admin``
-~~~~~~~~~~~~~~~
-
-By default, Django's admin uses a select-box interface (<select>) for
-fields that are ``ForeignKey`` or have ``choices`` set. If ``radio_admin``
-is set to ``True``, Django will use a radio-button interface instead.
-
-Don't use this for a field unless it's a ``ForeignKey`` or has ``choices``
-set.
-
 ``unique``
 ~~~~~~~~~~
 
diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py
index 7c0cf8a..6205ca6 100644
--- a/tests/regressiontests/forms/forms.py
+++ b/tests/regressiontests/forms/forms.py
@@ -423,7 +423,7 @@ gets a distinct ID, formed by appending an underscore plus the button's
 zero-based index.
 >>> f = FrameworkForm(auto_id='id_%s')
 >>> print f['language']
-<ul>
+<ul id="id_language">
 <li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
 <li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
 </ul>
@@ -433,19 +433,19 @@ either as_table() or as_ul(), the label for the RadioSelect will point to the
 ID of the *first* radio button.
 >>> print f
 <tr><th><label for="id_name">Name:</label></th><td><input type="text" name="name" id="id_name" /></td></tr>
-<tr><th><label for="id_language_0">Language:</label></th><td><ul>
+<tr><th><label for="id_language_0">Language:</label></th><td><ul id="id_language">
 <li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
 <li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
 </ul></td></tr>
 >>> print f.as_ul()
 <li><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></li>
-<li><label for="id_language_0">Language:</label> <ul>
+<li><label for="id_language_0">Language:</label> <ul id="id_language">
 <li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
 <li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
 </ul></li>
 >>> print f.as_p()
 <p><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></p>
-<p><label for="id_language_0">Language:</label> <ul>
+<p><label for="id_language_0">Language:</label> <ul id="id_language">
 <li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
 <li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
 </ul></p>
diff --git a/tests/regressiontests/forms/regressions.py b/tests/regressiontests/forms/regressions.py
index 1bb6f6e..c4f0ac0 100644
--- a/tests/regressiontests/forms/regressions.py
+++ b/tests/regressiontests/forms/regressions.py
@@ -40,7 +40,7 @@ Unicode decoding problems...
 ...     somechoice = ChoiceField(choices=GENDERS, widget=RadioSelect(), label=u'\xc5\xf8\xdf')
 >>> f = SomeForm()
 >>> f.as_p()
-u'<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul>\n<li><label><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>'
+u'<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul id="id_somechoice">\n<li><label><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>'
 
 Testing choice validation with UTF-8 bytestrings as input (these are the
 Russian abbreviations "мес." and "шт.".
@@ -56,7 +56,7 @@ Translated error messages used to be buggy.
 >>> activate('ru')
 >>> f = SomeForm({})
 >>> f.as_p()
-u'<ul class="errorlist"><li>\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435.</li></ul>\n<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul>\n<li><label><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>'
+u'<ul class="errorlist"><li>\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435.</li></ul>\n<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul id="id_somechoice">\n<li><label><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>'
 >>> deactivate()
 
 Deep copying translated text shouldn't raise an error
diff --git a/tests/regressiontests/forms/widgets.py b/tests/regressiontests/forms/widgets.py
index ccfddc9..2a38477 100644
--- a/tests/regressiontests/forms/widgets.py
+++ b/tests/regressiontests/forms/widgets.py
@@ -755,7 +755,7 @@ u'<ul>\n<li><label><input checked="checked" type="radio" name="email" value="\u0
 # Attributes provided at instantiation are passed to the constituent inputs
 >>> w = RadioSelect(attrs={'id':'foo'})
 >>> print w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
-<ul>
+<ul id="foo">
 <li><label><input checked="checked" type="radio" id="foo_0" value="J" name="beatle" /> John</label></li>
 <li><label><input type="radio" id="foo_1" value="P" name="beatle" /> Paul</label></li>
 <li><label><input type="radio" id="foo_2" value="G" name="beatle" /> George</label></li>
@@ -765,7 +765,7 @@ u'<ul>\n<li><label><input checked="checked" type="radio" name="email" value="\u0
 # Attributes provided at render-time are passed to the constituent inputs
 >>> w = RadioSelect()
 >>> print w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')), attrs={'id':'bar'})
-<ul>
+<ul id="bar">
 <li><label><input checked="checked" type="radio" id="bar_0" value="J" name="beatle" /> John</label></li>
 <li><label><input type="radio" id="bar_1" value="P" name="beatle" /> Paul</label></li>
 <li><label><input type="radio" id="bar_2" value="G" name="beatle" /> George</label></li>
diff --git a/tests/regressiontests/modeladmin/models.py b/tests/regressiontests/modeladmin/models.py
index 6275135..155e24b 100644
--- a/tests/regressiontests/modeladmin/models.py
+++ b/tests/regressiontests/modeladmin/models.py
@@ -4,11 +4,19 @@ from django.db import models
 class Band(models.Model):
     name = models.CharField(max_length=100)
     bio = models.TextField()
+    def __unicode__(self):
+        return self.name
+
+class Gig(models.Model):
+    main_band = models.ForeignKey(Band, related_name='main_gigs')
+    opening_band = models.ForeignKey(Band, related_name='opening_gigs', blank=True)
+    day = models.CharField(max_length=3, choices=((1, 'Fri'), (2, 'Sat')))
+    transport = models.CharField(max_length=100, choices=((1, 'Plane'), (2, 'Train'), (3, 'Bus')), blank=True)
 
 
 __test__ = {'API_TESTS': """
 
->>> from django.contrib.admin.options import ModelAdmin
+>>> from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL
 >>> from django.contrib.admin.sites import AdminSite
 
 None of the following tests really depend on the content of the request, so
@@ -17,6 +25,7 @@ we'll just pass in None.
 >>> request = None
 
 >>> band = Band(name='The Doors', bio='')
+>>> band.save()
 
 Under the covers, the admin system will initialize ModelAdmin with a Model
 class and an AdminSite instance, so let's just go ahead and do that manually
@@ -84,8 +93,69 @@ displayed because you forgot to add it to fields/fielsets
 >>> ma.form_change(request, band).base_fields.keys()
 ['name']
 
-
-
+# radio_admin_fields behavior ################################################
+
+First, without any radio_admin_fields specified, the widgets for ForeignKey
+and fields with choices specified ought to be a basic Select widget.
+For Select fields, all of the choices lists have a first entry of dashes.
+
+>>> gma = ModelAdmin(Gig, site)
+>>> gmafa = gma.form_add(request)
+>>> gmafa.base_fields['main_band'].widget
+<django.newforms.widgets.Select object at...
+>>> list(gmafa.base_fields['main_band'].widget.choices)
+[(u'', u'---------'), (1, u'The Doors')]
+>>> gmafa.base_fields['opening_band'].widget
+<django.newforms.widgets.Select object at...
+>>> list(gmafa.base_fields['opening_band'].widget.choices)
+[(u'', u'---------'), (1, u'The Doors')]
+>>> gmafa.base_fields['day'].widget
+<django.newforms.widgets.Select object at...
+>>> list(gmafa.base_fields['day'].widget.choices)
+[('', '---------'), (1, 'Fri'), (2, 'Sat')]
+>>> gmafa.base_fields['transport'].widget
+<django.newforms.widgets.Select object at...
+>>> list(gmafa.base_fields['transport'].widget.choices)
+[('', '---------'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')]
+
+Now specify all the fields as radio_admin_fields.  Widgets should now be
+RadioSelect, and the choices list should have a first entry of 'None' iff
+blank=True for the model field.  Finally, the widget should have the
+'radiolist' attr, and 'inline' as well if the field is specified HORIZONTAL.
+
+>>> class GigAdmin(ModelAdmin):
+...     radio_admin_fields = {'main_band':HORIZONTAL, 'opening_band':VERTICAL, 'day':VERTICAL, 'transport':HORIZONTAL }
+
+>>> gma = GigAdmin(Gig, site)
+>>> gmafa = gma.form_add(request)
+>>> gmafa.base_fields['main_band'].widget
+<django.newforms.widgets.RadioSelect object at...
+>>> gmafa.base_fields['main_band'].widget.attrs
+{'class': 'radiolist inline'}
+>>> list(gmafa.base_fields['main_band'].widget.choices)
+[(1, u'The Doors')]
+>>> gmafa.base_fields['opening_band'].widget
+<django.newforms.widgets.RadioSelect object at...
+>>> gmafa.base_fields['opening_band'].widget.attrs
+{'class': 'radiolist'}
+>>> list(gmafa.base_fields['opening_band'].widget.choices)
+[(u'', u'None'), (1, u'The Doors')]
+>>> gmafa.base_fields['day'].widget
+<django.newforms.widgets.RadioSelect object at...
+>>> gmafa.base_fields['day'].widget.attrs
+{'class': 'radiolist'}
+>>> list(gmafa.base_fields['day'].widget.choices)
+[(1, 'Fri'), (2, 'Sat')]
+>>> gmafa.base_fields['transport'].widget
+<django.newforms.widgets.RadioSelect object at...
+>>> gmafa.base_fields['transport'].widget.attrs
+{'class': 'radiolist inline'}
+>>> list(gmafa.base_fields['transport'].widget.choices)
+[('', u'None'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')]
+
+
+
+band.delete()
 
 """
 }
