mirror of
https://github.com/django/django.git
synced 2025-11-07 07:15:35 +00:00
Fixed #10743 -- Allowed lookups for related fields in ModelAdmin.list_display.
Co-authored-by: Alex Garcia <me@alexoteiza.com> Co-authored-by: Natalia <124304+nessita@users.noreply.github.com> Co-authored-by: Nina Menezes <https://github.com/nmenezes0>
This commit is contained in:
@@ -915,21 +915,19 @@ class ModelAdminChecks(BaseModelAdminChecks):
|
||||
try:
|
||||
field = getattr(obj.model, item)
|
||||
except AttributeError:
|
||||
return [
|
||||
checks.Error(
|
||||
"The value of '%s' refers to '%s', which is not a "
|
||||
"callable, an attribute of '%s', or an attribute or "
|
||||
"method on '%s'."
|
||||
% (
|
||||
label,
|
||||
item,
|
||||
obj.__class__.__name__,
|
||||
obj.model._meta.label,
|
||||
),
|
||||
obj=obj.__class__,
|
||||
id="admin.E108",
|
||||
)
|
||||
]
|
||||
try:
|
||||
field = get_fields_from_path(obj.model, item)[-1]
|
||||
except (FieldDoesNotExist, NotRelationField):
|
||||
return [
|
||||
checks.Error(
|
||||
f"The value of '{label}' refers to '{item}', which is not "
|
||||
f"a callable or attribute of '{obj.__class__.__name__}', "
|
||||
"or an attribute, method, or field on "
|
||||
f"'{obj.model._meta.label}'.",
|
||||
obj=obj.__class__,
|
||||
id="admin.E108",
|
||||
)
|
||||
]
|
||||
if (
|
||||
getattr(field, "is_relation", False)
|
||||
and (field.many_to_many or field.one_to_many)
|
||||
|
||||
@@ -289,8 +289,8 @@ def lookup_field(name, obj, model_admin=None):
|
||||
try:
|
||||
f = _get_non_gfk_field(opts, name)
|
||||
except (FieldDoesNotExist, FieldIsAForeignKeyColumnName):
|
||||
# For non-field values, the value is either a method, property or
|
||||
# returned via a callable.
|
||||
# For non-regular field values, the value is either a method,
|
||||
# property, related field, or returned via a callable.
|
||||
if callable(name):
|
||||
attr = name
|
||||
value = attr(obj)
|
||||
@@ -298,10 +298,17 @@ def lookup_field(name, obj, model_admin=None):
|
||||
attr = getattr(model_admin, name)
|
||||
value = attr(obj)
|
||||
else:
|
||||
attr = getattr(obj, name)
|
||||
sentinel = object()
|
||||
attr = getattr(obj, name, sentinel)
|
||||
if callable(attr):
|
||||
value = attr()
|
||||
else:
|
||||
if attr is sentinel:
|
||||
attr = obj
|
||||
for part in name.split(LOOKUP_SEP):
|
||||
attr = getattr(attr, part, sentinel)
|
||||
if attr is sentinel:
|
||||
return None, None, None
|
||||
value = attr
|
||||
if hasattr(model_admin, "model") and hasattr(model_admin.model, name):
|
||||
attr = getattr(model_admin.model, name)
|
||||
@@ -345,9 +352,10 @@ def label_for_field(name, model, model_admin=None, return_attr=False, form=None)
|
||||
"""
|
||||
Return 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 model field. If return_attr is True, also
|
||||
return the resolved attribute (which could be a callable). This will be
|
||||
None if (and only if) the name refers to a field.
|
||||
object's attribute, as well as a model field, including across related
|
||||
objects. If return_attr is True, also return the resolved attribute
|
||||
(which could be a callable). This will be None if (and only if) the name
|
||||
refers to a field.
|
||||
"""
|
||||
attr = None
|
||||
try:
|
||||
@@ -371,15 +379,15 @@ def label_for_field(name, model, model_admin=None, return_attr=False, form=None)
|
||||
elif form and name in form.fields:
|
||||
attr = form.fields[name]
|
||||
else:
|
||||
message = "Unable to lookup '%s' on %s" % (
|
||||
name,
|
||||
model._meta.object_name,
|
||||
)
|
||||
if model_admin:
|
||||
message += " or %s" % model_admin.__class__.__name__
|
||||
if form:
|
||||
message += " or %s" % form.__class__.__name__
|
||||
raise AttributeError(message)
|
||||
try:
|
||||
attr = get_fields_from_path(model, name)[-1]
|
||||
except (FieldDoesNotExist, NotRelationField):
|
||||
message = f"Unable to lookup '{name}' on {model._meta.object_name}"
|
||||
if model_admin:
|
||||
message += f" or {model_admin.__class__.__name__}"
|
||||
if form:
|
||||
message += f" or {form.__class__.__name__}"
|
||||
raise AttributeError(message)
|
||||
|
||||
if hasattr(attr, "short_description"):
|
||||
label = attr.short_description
|
||||
|
||||
@@ -30,6 +30,7 @@ from django.core.exceptions import (
|
||||
)
|
||||
from django.core.paginator import InvalidPage
|
||||
from django.db.models import F, Field, ManyToOneRel, OrderBy
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.expressions import Combinable
|
||||
from django.urls import reverse
|
||||
from django.utils.deprecation import RemovedInDjango60Warning
|
||||
@@ -356,9 +357,9 @@ class ChangeList:
|
||||
"""
|
||||
Return the proper model field name corresponding to the given
|
||||
field_name to use for ordering. field_name may either be the name of a
|
||||
proper model field or the name of a method (on the admin or model) or a
|
||||
callable with the 'admin_order_field' attribute. Return None if no
|
||||
proper model field name can be matched.
|
||||
proper model field, possibly across relations, or the name of a method
|
||||
(on the admin or model) or a callable with the 'admin_order_field'
|
||||
attribute. Return None if no proper model field name can be matched.
|
||||
"""
|
||||
try:
|
||||
field = self.lookup_opts.get_field(field_name)
|
||||
@@ -371,7 +372,12 @@ class ChangeList:
|
||||
elif hasattr(self.model_admin, field_name):
|
||||
attr = getattr(self.model_admin, field_name)
|
||||
else:
|
||||
attr = getattr(self.model, field_name)
|
||||
try:
|
||||
attr = getattr(self.model, field_name)
|
||||
except AttributeError:
|
||||
if LOOKUP_SEP in field_name:
|
||||
return field_name
|
||||
raise
|
||||
if isinstance(attr, property) and hasattr(attr, "fget"):
|
||||
attr = attr.fget
|
||||
return getattr(attr, "admin_order_field", None)
|
||||
|
||||
Reference in New Issue
Block a user