Index: django/db/models/fields/__init__.py
===================================================================
--- django/db/models/fields/__init__.py	(revision 6483)
+++ django/db/models/fields/__init__.py	(working copy)
@@ -24,8 +24,6 @@
 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 @@
 # 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 @@
         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 @@
         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
@@ -253,12 +247,7 @@
             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()
@@ -345,10 +334,7 @@
         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:
Index: django/db/models/fields/related.py
===================================================================
--- django/db/models/fields/related.py	(revision 6483)
+++ django/db/models/fields/related.py	(working copy)
@@ -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 @@

     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 @@
         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 @@
     # 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

Index: django/contrib/redirects/models.py
===================================================================
--- django/contrib/redirects/models.py	(revision 6483)
+++ django/contrib/redirects/models.py	(working copy)
@@ -3,7 +3,7 @@
 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 @@
 class RedirectAdmin(admin.ModelAdmin):
     list_filter = ('site',)
     search_fields = ('old_path', 'new_path')
+    radio_admin_fields = { 'site': admin.VERTICAL }

 admin.site.register(Redirect, RedirectAdmin)

Index: django/contrib/admin/options.py
===================================================================
--- django/contrib/admin/options.py	(revision 6483)
+++ django/contrib/admin/options.py	(working copy)
@@ -14,6 +14,10 @@
 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

@@ -125,6 +129,7 @@
 class BaseModelAdmin(object):
     """Functionality common to both ModelAdmin and InlineAdmin."""
     raw_id_fields = ()
+    radio_admin_fields = {}
     fields = None
     fieldsets = None
     filter_vertical = ()
@@ -174,6 +179,9 @@
         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)
@@ -186,6 +194,12 @@
                 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)

Index: django/contrib/admin/__init__.py
===================================================================
--- django/contrib/admin/__init__.py	(revision 6483)
+++ django/contrib/admin/__init__.py	(working copy)
@@ -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
Index: tests/regressiontests/modeladmin/models.py
===================================================================
--- tests/regressiontests/modeladmin/models.py	(revision 6483)
+++ tests/regressiontests/modeladmin/models.py	(working copy)
@@ -4,11 +4,19 @@
 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 @@
 >>> 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 @@
 >>> 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()
+
 """
 }
Index: docs/model-api.txt
===================================================================
--- docs/model-api.txt	(revision 6483)
+++ docs/model-api.txt	(working copy)
@@ -663,16 +663,6 @@
 ``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``
 ~~~~~~~~~~

Index: docs/admin.txt
===================================================================
--- docs/admin.txt	(revision 6483)
+++ docs/admin.txt	(working copy)
@@ -35,6 +35,22 @@
 ``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
 =====================

