Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

newforms-admin: Fixed #5731 -- Implemented ModelAdmin.radio_fields to…

… match trunk's radio_admin. Removed legacy code and added tests. Thanks Karen Tracey for the initial work.

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@7626 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit f914b0a71b8059166208175f994f61e4984eab1d 1 parent 4a6965b
@brosner brosner authored
View
2  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
View
19 django/contrib/admin/options.py
@@ -17,6 +17,10 @@
from django.utils.encoding import force_unicode
import sets
+HORIZONTAL, VERTICAL = 1, 2
+# returns the <ul> 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 @@ def formfield_for_dbfield(self, db_field, **kwargs):
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 @@ def formfield_for_dbfield(self, db_field, **kwargs):
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)
View
13 django/contrib/admin/widgets.py
@@ -3,6 +3,8 @@
"""
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 @@ def format_output(self, rendered_widgets):
return mark_safe(u'<p class="datetime">%s %s<br />%s %s</p>' % \
(_('Date:'), rendered_widgets[0], _('Time:'), rendered_widgets[1]))
+class AdminRadioFieldRenderer(RadioFieldRenderer):
+ def render(self):
+ """Outputs a <ul> for this set of radio fields."""
+ 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 AdminRadioSelect(forms.RadioSelect):
+ renderer = AdminRadioFieldRenderer
+
class AdminFileWidget(forms.FileInput):
"""
A FileField Widget that shows its current value if it has one.
View
3  django/contrib/redirects/models.py
@@ -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 @@ def __unicode__(self):
class RedirectAdmin(admin.ModelAdmin):
list_filter = ('site',)
search_fields = ('old_path', 'new_path')
+ radio_fields = {'site': admin.VERTICAL}
admin.site.register(Redirect, RedirectAdmin)
View
21 django/db/models/fields/__init__.py
@@ -26,8 +26,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")]
@@ -35,9 +33,6 @@ class NOT_PROVIDED:
# 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
@@ -87,8 +82,8 @@ def __init__(self, verbose_name=None, name=None, primary_key=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,
- 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 @@ def __init__(self, verbose_name=None, name=None, primary_key=False,
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 @@ def prepare_field_objs_and_params(self, manipulator, name_prefix):
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 @@ def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH):
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:
View
22 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 @@ def get_validator_unique_lookup_type(self):
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 @@ def flatten_data(self, follow, obj=None):
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)
View
17 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 (<select>) for
+fields that are ``ForeignKey`` or have ``choices`` set. If a field is present
+in ``radio_fields``, Django will use a radio-button interface instead.
+Assuming ``group`` is a ``ForeignKey`` on the ``Person`` model::
+
+ class PersonAdmin(admin.ModelAdmin):
+ radio_fields = {"group": admin.VERTICAL}
+
+You have the choice of using ``HORIZONTAL`` or ``VERTICAL`` from the
+``django.contrib.admin`` module.
+
+Don't include a field in ``radio_fields`` unless it's a ``ForeignKey`` or has
+``choices`` set.
+
``save_as``
-----------
View
1  docs/custom_model_fields.txt
@@ -204,7 +204,6 @@ order:
* ``unique_for_year``
* ``validator_list``
* ``choices``
- * ``radio_admin``
* ``help_text``
* ``db_column``
* ``db_tablespace``: Currently only used with the Oracle backend and only
View
10 docs/model-api.txt
@@ -663,16 +663,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``
~~~~~~~~~~
View
96 tests/regressiontests/modeladmin/models.py
@@ -1,15 +1,30 @@
# coding: utf-8
from django.db import models
+from datetime import date
class Band(models.Model):
name = models.CharField(max_length=100)
bio = models.TextField()
sign_date = models.DateField()
+
+ def __unicode__(self):
+ return self.name
+
+class Concert(models.Model):
+ main_band = models.ForeignKey(Band, related_name='main_concerts')
+ opening_band = models.ForeignKey(Band, related_name='opening_concerts',
+ 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,7 +32,9 @@ class Band(models.Model):
>>> request = None
->>> band = Band(name='The Doors', bio='')
+# the sign_date is not 100 percent accurate ;)
+>>> band = Band(name='The Doors', bio='', sign_date=date(1965, 1, 1))
+>>> 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
@@ -105,5 +122,80 @@ class and an AdminSite instance, so let's just go ahead and do that manually
>>> type(ma.get_form(request).base_fields['sign_date'].widget)
<class 'django.contrib.admin.widgets.AdminDateWidget'>
+# radio_fields behavior ################################################
+
+First, without any radio_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.
+
+>>> cma = ModelAdmin(Concert, site)
+>>> cmafa = cma.get_form(request)
+
+>>> type(cmafa.base_fields['main_band'].widget)
+<class 'django.newforms.widgets.Select'>
+>>> list(cmafa.base_fields['main_band'].widget.choices)
+[(u'', u'---------'), (1, u'The Doors')]
+
+>>> type(cmafa.base_fields['opening_band'].widget)
+<class 'django.newforms.widgets.Select'>
+>>> list(cmafa.base_fields['opening_band'].widget.choices)
+[(u'', u'---------'), (1, u'The Doors')]
+
+>>> type(cmafa.base_fields['day'].widget)
+<class 'django.newforms.widgets.Select'>
+>>> list(cmafa.base_fields['day'].widget.choices)
+[('', '---------'), (1, 'Fri'), (2, 'Sat')]
+
+>>> type(cmafa.base_fields['transport'].widget)
+<class 'django.newforms.widgets.Select'>
+>>> list(cmafa.base_fields['transport'].widget.choices)
+[('', '---------'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')]
+
+Now specify all the fields as radio_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 ConcertAdmin(ModelAdmin):
+... radio_fields = {
+... 'main_band': HORIZONTAL,
+... 'opening_band': VERTICAL,
+... 'day': VERTICAL,
+... 'transport': HORIZONTAL,
+... }
+
+>>> cma = ConcertAdmin(Concert, site)
+>>> cmafa = cma.get_form(request)
+
+>>> type(cmafa.base_fields['main_band'].widget)
+<class 'django.contrib.admin.widgets.AdminRadioSelect'>
+>>> cmafa.base_fields['main_band'].widget.attrs
+{'class': 'radiolist inline'}
+>>> list(cmafa.base_fields['main_band'].widget.choices)
+[(1, u'The Doors')]
+
+>>> type(cmafa.base_fields['opening_band'].widget)
+<class 'django.contrib.admin.widgets.AdminRadioSelect'>
+>>> cmafa.base_fields['opening_band'].widget.attrs
+{'class': 'radiolist'}
+>>> list(cmafa.base_fields['opening_band'].widget.choices)
+[(u'', u'None'), (1, u'The Doors')]
+
+>>> type(cmafa.base_fields['day'].widget)
+<class 'django.contrib.admin.widgets.AdminRadioSelect'>
+>>> cmafa.base_fields['day'].widget.attrs
+{'class': 'radiolist'}
+>>> list(cmafa.base_fields['day'].widget.choices)
+[(1, 'Fri'), (2, 'Sat')]
+
+>>> type(cmafa.base_fields['transport'].widget)
+<class 'django.contrib.admin.widgets.AdminRadioSelect'>
+>>> cmafa.base_fields['transport'].widget.attrs
+{'class': 'radiolist inline'}
+>>> list(cmafa.base_fields['transport'].widget.choices)
+[('', u'None'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')]
+
+>>> band.delete()
+
"""
}
Please sign in to comment.
Something went wrong with that request. Please try again.