mirror of https://github.com/django/django.git
Fixed #7503 -- Allow callables in list_display. This also does a lookup on the ModelAdmin for the method if the value is a string before looking on the model. Refs #8054. Thanks qmanic and Daniel Pope for tickets and patches.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@8352 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
9e423b51e3
commit
b2ec6473c0
|
@ -84,14 +84,30 @@ def result_headers(cl):
|
||||||
elif field_name == '__str__':
|
elif field_name == '__str__':
|
||||||
header = smart_str(lookup_opts.verbose_name)
|
header = smart_str(lookup_opts.verbose_name)
|
||||||
else:
|
else:
|
||||||
attr = getattr(cl.model, field_name) # Let AttributeErrors propagate.
|
if callable(field_name):
|
||||||
|
attr = field_name # field_name can be a callable
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
attr = getattr(cl.model_admin, field_name)
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
attr = getattr(cl.model, field_name)
|
||||||
|
except AttributeError:
|
||||||
|
raise AttributeError, \
|
||||||
|
"'%s' model or '%s' objects have no attribute '%s'" % \
|
||||||
|
(lookup_opts.object_name, cl.model_admin.__class__, field_name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
header = attr.short_description
|
header = attr.short_description
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
header = field_name.replace('_', ' ')
|
if callable(field_name):
|
||||||
|
header = field_name.__name__
|
||||||
|
else:
|
||||||
|
header = field_name
|
||||||
|
header = header.replace('_', ' ')
|
||||||
|
|
||||||
# It is a non-field, but perhaps one that is sortable
|
# It is a non-field, but perhaps one that is sortable
|
||||||
admin_order_field = getattr(getattr(cl.model, field_name), "admin_order_field", None)
|
admin_order_field = getattr(attr, "admin_order_field", None)
|
||||||
if not admin_order_field:
|
if not admin_order_field:
|
||||||
yield {"text": header}
|
yield {"text": header}
|
||||||
continue
|
continue
|
||||||
|
@ -128,19 +144,28 @@ def items_for_result(cl, result):
|
||||||
try:
|
try:
|
||||||
f = cl.lookup_opts.get_field(field_name)
|
f = cl.lookup_opts.get_field(field_name)
|
||||||
except models.FieldDoesNotExist:
|
except models.FieldDoesNotExist:
|
||||||
# For non-field list_display values, the value is either a method
|
# For non-field list_display values, the value is either a method,
|
||||||
# or a property.
|
# property or returned via a callable.
|
||||||
try:
|
try:
|
||||||
|
if callable(field_name):
|
||||||
|
attr = field_name
|
||||||
|
value = attr(result)
|
||||||
|
elif hasattr(cl.model_admin, field_name):
|
||||||
|
attr = getattr(cl.model_admin, field_name)
|
||||||
|
value = attr(result)
|
||||||
|
else:
|
||||||
attr = getattr(result, field_name)
|
attr = getattr(result, field_name)
|
||||||
|
if callable(attr):
|
||||||
|
value = attr()
|
||||||
|
else:
|
||||||
|
value = attr
|
||||||
allow_tags = getattr(attr, 'allow_tags', False)
|
allow_tags = getattr(attr, 'allow_tags', False)
|
||||||
boolean = getattr(attr, 'boolean', False)
|
boolean = getattr(attr, 'boolean', False)
|
||||||
if callable(attr):
|
|
||||||
attr = attr()
|
|
||||||
if boolean:
|
if boolean:
|
||||||
allow_tags = True
|
allow_tags = True
|
||||||
result_repr = _boolean_icon(attr)
|
result_repr = _boolean_icon(value)
|
||||||
else:
|
else:
|
||||||
result_repr = smart_unicode(attr)
|
result_repr = smart_unicode(value)
|
||||||
except (AttributeError, ObjectDoesNotExist):
|
except (AttributeError, ObjectDoesNotExist):
|
||||||
result_repr = EMPTY_CHANGELIST_VALUE
|
result_repr = EMPTY_CHANGELIST_VALUE
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -35,6 +35,15 @@ def validate(cls, model):
|
||||||
if hasattr(cls, 'list_display'):
|
if hasattr(cls, 'list_display'):
|
||||||
_check_istuplew('list_display', cls.list_display)
|
_check_istuplew('list_display', cls.list_display)
|
||||||
for idx, field in enumerate(cls.list_display):
|
for idx, field in enumerate(cls.list_display):
|
||||||
|
if not callable(field):
|
||||||
|
if not hasattr(cls, field):
|
||||||
|
if not hasattr(model, field):
|
||||||
|
try:
|
||||||
|
return opts.get_field(field)
|
||||||
|
except models.FieldDoesNotExist:
|
||||||
|
raise ImproperlyConfigured("%s.list_display[%d], %r is "
|
||||||
|
"not a callable or an attribute of %r or found in the model %r."
|
||||||
|
% (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
|
||||||
f = _check_attr_existsw("list_display[%d]" % idx, field)
|
f = _check_attr_existsw("list_display[%d]" % idx, field)
|
||||||
if isinstance(f, models.ManyToManyField):
|
if isinstance(f, models.ManyToManyField):
|
||||||
raise ImproperlyConfigured("`%s.list_display[%d]`, `%s` is a "
|
raise ImproperlyConfigured("`%s.list_display[%d]`, `%s` is a "
|
||||||
|
|
|
@ -201,6 +201,48 @@ Example::
|
||||||
If you don't set ``list_display``, the admin site will display a single column
|
If you don't set ``list_display``, the admin site will display a single column
|
||||||
that displays the ``__unicode__()`` representation of each object.
|
that displays the ``__unicode__()`` representation of each object.
|
||||||
|
|
||||||
|
You have four possible values that can be used in ``list_display``:
|
||||||
|
|
||||||
|
* A field of the model. For example::
|
||||||
|
|
||||||
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('first_name', 'last_name')
|
||||||
|
|
||||||
|
* A callable that accepts one parameter for the model instance. For
|
||||||
|
example::
|
||||||
|
|
||||||
|
def upper_case_name(obj):
|
||||||
|
return "%s %s" % (obj.first_name, obj.last_name).upper()
|
||||||
|
upper_case_name.short_description = 'Name'
|
||||||
|
|
||||||
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (upper_case_name,)
|
||||||
|
|
||||||
|
* A string representating an attribute on the ``ModelAdmin``. This behaves
|
||||||
|
the same as the callable. For example::
|
||||||
|
|
||||||
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('upper_case_name',)
|
||||||
|
|
||||||
|
def upper_case_name(self, obj):
|
||||||
|
return "%s %s" % (obj.first_name, obj.last_name).upper()
|
||||||
|
upper_case_name.short_description = 'Name'
|
||||||
|
|
||||||
|
* A string representating an attribute on the model. This behaves almost
|
||||||
|
the same as the callable, but ``self`` in this context is the model
|
||||||
|
instance. Here's a full model example::
|
||||||
|
|
||||||
|
class Person(models.Model):
|
||||||
|
name = models.CharField(max_length=50)
|
||||||
|
birthday = models.DateField()
|
||||||
|
|
||||||
|
def decade_born_in(self):
|
||||||
|
return self.birthday.strftime('%Y')[:3] + "0's"
|
||||||
|
decade_born_in.short_description = 'Birth decade'
|
||||||
|
|
||||||
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'decade_born_in')
|
||||||
|
|
||||||
A few special cases to note about ``list_display``:
|
A few special cases to note about ``list_display``:
|
||||||
|
|
||||||
* If the field is a ``ForeignKey``, Django will display the
|
* If the field is a ``ForeignKey``, Django will display the
|
||||||
|
@ -215,26 +257,10 @@ A few special cases to note about ``list_display``:
|
||||||
* If the field is a ``BooleanField`` or ``NullBooleanField``, Django will
|
* If the field is a ``BooleanField`` or ``NullBooleanField``, Django will
|
||||||
display a pretty "on" or "off" icon instead of ``True`` or ``False``.
|
display a pretty "on" or "off" icon instead of ``True`` or ``False``.
|
||||||
|
|
||||||
* If the string given is a method of the model, Django will call it and
|
* If the string given is a method of the model, ``ModelAdmin`` or a
|
||||||
display the output. This method should have a ``short_description``
|
callable, Django will HTML-escape the output by default. If you'd rather
|
||||||
function attribute, for use as the header for the field.
|
not escape the output of the method, give the method an ``allow_tags``
|
||||||
|
attribute whose value is ``True``.
|
||||||
Here's a full example model::
|
|
||||||
|
|
||||||
class Person(models.Model):
|
|
||||||
name = models.CharField(max_length=50)
|
|
||||||
birthday = models.DateField()
|
|
||||||
|
|
||||||
def decade_born_in(self):
|
|
||||||
return self.birthday.strftime('%Y')[:3] + "0's"
|
|
||||||
decade_born_in.short_description = 'Birth decade'
|
|
||||||
|
|
||||||
class PersonAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('name', 'decade_born_in')
|
|
||||||
|
|
||||||
* If the string given is a method of the model, Django will HTML-escape the
|
|
||||||
output by default. If you'd rather not escape the output of the method,
|
|
||||||
give the method an ``allow_tags`` attribute whose value is ``True``.
|
|
||||||
|
|
||||||
Here's a full example model::
|
Here's a full example model::
|
||||||
|
|
||||||
|
@ -250,9 +276,10 @@ A few special cases to note about ``list_display``:
|
||||||
class PersonAdmin(admin.ModelAdmin):
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
list_display = ('first_name', 'last_name', 'colored_name')
|
list_display = ('first_name', 'last_name', 'colored_name')
|
||||||
|
|
||||||
* If the string given is a method of the model that returns True or False
|
* If the string given is a method of the model, ``ModelAdmin`` or a
|
||||||
Django will display a pretty "on" or "off" icon if you give the method a
|
callable that returns True or False Django will display a pretty "on" or
|
||||||
``boolean`` attribute whose value is ``True``.
|
"off" icon if you give the method a ``boolean`` attribute whose value is
|
||||||
|
``True``.
|
||||||
|
|
||||||
Here's a full example model::
|
Here's a full example model::
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue