1
0
mirror of https://github.com/django/django.git synced 2025-07-04 01:39:20 +00:00

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
This commit is contained in:
Brian Rosner 2008-06-12 20:13:27 +00:00
parent 4a6965b308
commit f914b0a71b
10 changed files with 157 additions and 47 deletions

View File

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

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

View File

@ -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'<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 File

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

View File

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

View File

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

View File

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

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

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

@ -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 @@ we'll just pass in None.
>>> 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 @@ properly. This won't, however, break any of the admin widgets or media.
>>> 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()
"""
}