diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index 97858e688e..078adbe827 100644 --- a/django/contrib/admin/util.py +++ b/django/contrib/admin/util.py @@ -269,8 +269,9 @@ def lookup_field(name, obj, model_admin=None): def label_for_field(name, model, model_admin=None, return_attr=False): """ - Returns a sensible label for a field name. The name can be a callable or the - name of an object attributes, as well as a genuine fields. If return_attr is + Returns a sensible label for a field name. The name can be a callable, + property (but not created with @property decorator) or the name of an + object's attribute, as well as a genuine fields. If return_attr is True, the resolved attribute (which could be a callable) is also returned. This will be None if (and only if) the name refers to a field. """ @@ -303,6 +304,10 @@ def label_for_field(name, model, model_admin=None, return_attr=False): if hasattr(attr, "short_description"): label = attr.short_description + elif (isinstance(attr, property) and + hasattr(attr, "fget") and + hasattr(attr.fget, "short_description")): + label = attr.fget.short_description elif callable(attr): if attr.__name__ == "": label = "--" @@ -315,6 +320,7 @@ def label_for_field(name, model, model_admin=None, return_attr=False): else: return label + def help_text_for_field(name, model): try: help_text = model._meta.get_field_by_name(name)[0].help_text diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 90570f9576..0aec62f7b9 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -464,7 +464,7 @@ subclass:: list_display = ('upper_case_name',) def upper_case_name(self, obj): - return ("%s %s" % (obj.first_name, obj.last_name)).upper() + return ("%s %s" % (obj.first_name, obj.last_name)).upper() upper_case_name.short_description = 'Name' * A string representing an attribute on the model. This behaves almost @@ -589,6 +589,27 @@ subclass:: The above will tell Django to order by the ``first_name`` field when trying to sort by ``colored_first_name`` in the admin. + * Elements of ``list_display`` can also be properties. Please note however, + that due to the way properties work in Python, setting + ``short_description`` on a property is only possible when using the + ``property()`` function and **not** with the ``@property`` decorator. + + For example:: + + class Person(object): + first_name = models.CharField(max_length=50) + last_name = models.CharField(max_length=50) + + def my_property(self): + return self.first_name + ' ' + self.last_name + my_property.short_description = "Full name of the person" + + full_name = property(my_property) + + class PersonAdmin(admin.ModelAdmin): + list_display = ('full_name',) + + * .. versionadded:: 1.6 The field names in ``list_display`` will also appear as CSS classes in diff --git a/tests/admin_util/tests.py b/tests/admin_util/tests.py index 7898f200b5..35b7681cbb 100644 --- a/tests/admin_util/tests.py +++ b/tests/admin_util/tests.py @@ -236,6 +236,20 @@ class UtilTests(unittest.TestCase): ("not Really the Model", MockModelAdmin.test_from_model) ) + def test_label_for_property(self): + # NOTE: cannot use @property decorator, because of + # AttributeError: 'property' object has no attribute 'short_description' + class MockModelAdmin(object): + def my_property(self): + return "this if from property" + my_property.short_description = 'property short description' + test_from_property = property(my_property) + + self.assertEqual( + label_for_field("test_from_property", Article, model_admin=MockModelAdmin), + 'property short description' + ) + def test_related_name(self): """ Regression test for #13963