diff --git a/django/contrib/admin/__init__.py b/django/contrib/admin/__init__.py
index 9e8b4fb8f4..6a459ec22a 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 8ba543d4a7..0d2af3d524 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -17,6 +17,10 @@ from django.utils.translation import ugettext as _
from django.utils.encoding import force_unicode
import sets
+HORIZONTAL, VERTICAL = 1, 2
+# returns the
class for a given radio_admin field
+get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
+
class IncorrectLookupParameters(Exception):
pass
@@ -133,6 +137,7 @@ class BaseModelAdmin(object):
form = forms.ModelForm
filter_vertical = ()
filter_horizontal = ()
+ radio_fields = {}
prepopulated_fields = {}
def __init__(self):
@@ -173,6 +178,11 @@ 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_fields:
+ kwargs['widget'] = widgets.AdminRadioSelect(attrs={
+ 'class': get_ul_class(self.radio_fields[db_field.name]),
+ })
+ kwargs['empty_label'] = db_field.blank and _('None') or None
else:
if isinstance(db_field, models.ManyToManyField):
if db_field.name in self.raw_id_fields:
@@ -187,6 +197,15 @@ class BaseModelAdmin(object):
if not db_field.name in self.raw_id_fields:
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_fields:
+ kwargs['widget'] = widgets.AdminRadioSelect(
+ choices=db_field.get_choices(include_blank=db_field.blank,
+ blank_choice=[('', _('None'))]),
+ attrs={
+ 'class': get_ul_class(self.radio_fields[db_field.name]),
+ }
+ )
# For any other type of field, just call its formfield() method.
return db_field.formfield(**kwargs)
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index 16b17071d2..e7ea4aa129 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -3,6 +3,8 @@ Form Widget classes specific to the Django admin site.
"""
from django import newforms as forms
+from django.newforms.widgets import RadioFieldRenderer
+from django.newforms.util import flatatt
from django.utils.datastructures import MultiValueDict
from django.utils.text import capfirst, truncate_words
from django.utils.translation import ugettext as _
@@ -62,6 +64,17 @@ class AdminSplitDateTime(forms.SplitDateTimeWidget):
return mark_safe(u'
for this set of radio fields."""
+ return mark_safe(u'
\n%s\n
' % (
+ flatatt(self.attrs),
+ u'\n'.join([u'
%s
' % force_unicode(w) for w in self]))
+ )
+
+class AdminRadioSelect(forms.RadioSelect):
+ renderer = AdminRadioFieldRenderer
+
class AdminFileWidget(forms.FileInput):
"""
A FileField Widget that shows its current value if it has one.
diff --git a/django/contrib/redirects/models.py b/django/contrib/redirects/models.py
index 68f4afeb49..a35c95f1a8 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_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 a05f65c443..e71f41cb85 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -26,8 +26,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")]
@@ -35,9 +33,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
class for a given radio_admin value
-get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
-
class FieldDoesNotExist(Exception):
pass
@@ -87,8 +82,8 @@ class Field(object):
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,
- db_tablespace=None, auto_created=False):
+ choices=None, help_text='', db_column=None, db_tablespace=None,
+ auto_created=False):
self.name = name
self.verbose_name = verbose_name
self.primary_key = primary_key
@@ -105,7 +100,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
@@ -285,11 +279,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:
@@ -377,10 +367,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 49d41ff3d9..c82574081c 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, FieldDoesNotExist
+from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist
from django.db.models.related import RelatedObject
from django.db.models.query_utils import QueryWrapper
from django.utils.text import capfirst
@@ -628,14 +628,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
@@ -660,15 +656,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)
diff --git a/docs/admin.txt b/docs/admin.txt
index 7466d41ee3..0d3cb811ee 100644
--- a/docs/admin.txt
+++ b/docs/admin.txt
@@ -344,6 +344,23 @@ ordered. This should be a list or tuple in the same format as a model's
If this isn't provided, the Django admin will use the model's default ordering.
+``radio_fields``
+----------------
+
+By default, Django's admin uses a select-box interface (