Fixed #30543 -- Fixed checks of ModelAdmin.list_display for fields accessible only via instance.

Co-Authored-By: Andrew Simons <andrewsimons@bubblegroup.com>
This commit is contained in:
Hasan Ramezani 2019-07-10 00:27:06 +02:00 committed by Mariusz Felisiak
parent 7991111af1
commit ed668796f6
2 changed files with 41 additions and 22 deletions

View File

@ -720,33 +720,33 @@ class ModelAdminChecks(BaseModelAdminChecks):
return [] return []
elif hasattr(obj, item): elif hasattr(obj, item):
return [] return []
elif hasattr(obj.model, item): try:
field = obj.model._meta.get_field(item)
except FieldDoesNotExist:
try: try:
field = obj.model._meta.get_field(item) field = getattr(obj.model, item)
except FieldDoesNotExist: except AttributeError:
return [] return [
else: checks.Error(
if isinstance(field, models.ManyToManyField): "The value of '%s' refers to '%s', which is not a "
return [ "callable, an attribute of '%s', or an attribute or "
checks.Error( "method on '%s.%s'." % (
"The value of '%s' must not be a ManyToManyField." % label, label, item, obj.__class__.__name__,
obj=obj.__class__, obj.model._meta.app_label, obj.model._meta.object_name,
id='admin.E109', ),
) obj=obj.__class__,
] id='admin.E108',
return [] )
else: ]
if isinstance(field, models.ManyToManyField):
return [ return [
checks.Error( checks.Error(
"The value of '%s' refers to '%s', which is not a callable, " "The value of '%s' must not be a ManyToManyField." % label,
"an attribute of '%s', or an attribute or method on '%s.%s'." % (
label, item, obj.__class__.__name__,
obj.model._meta.app_label, obj.model._meta.object_name,
),
obj=obj.__class__, obj=obj.__class__,
id='admin.E108', id='admin.E109',
) )
] ]
return []
def _check_list_display_links(self, obj): def _check_list_display_links(self, obj):
""" Check that list_display_links is a unique subset of list_display. """ Check that list_display_links is a unique subset of list_display.

View File

@ -3,7 +3,7 @@ from django.contrib.admin import BooleanFieldListFilter, SimpleListFilter
from django.contrib.admin.options import VERTICAL, ModelAdmin, TabularInline from django.contrib.admin.options import VERTICAL, ModelAdmin, TabularInline
from django.contrib.admin.sites import AdminSite from django.contrib.admin.sites import AdminSite
from django.core.checks import Error from django.core.checks import Error
from django.db.models import F from django.db.models import F, Field, Model
from django.db.models.functions import Upper from django.db.models.functions import Upper
from django.forms.models import BaseModelFormSet from django.forms.models import BaseModelFormSet
from django.test import SimpleTestCase from django.test import SimpleTestCase
@ -509,6 +509,25 @@ class ListDisplayTests(CheckTestCase):
self.assertIsValid(TestModelAdmin, ValidationTestModel) self.assertIsValid(TestModelAdmin, ValidationTestModel)
def test_valid_field_accessible_via_instance(self):
class PositionField(Field):
"""Custom field accessible only via instance."""
def contribute_to_class(self, cls, name):
super().contribute_to_class(cls, name)
setattr(cls, self.name, self)
def __get__(self, instance, owner):
if instance is None:
raise AttributeError()
class TestModel(Model):
field = PositionField()
class TestModelAdmin(ModelAdmin):
list_display = ('field',)
self.assertIsValid(TestModelAdmin, TestModel)
class ListDisplayLinksCheckTests(CheckTestCase): class ListDisplayLinksCheckTests(CheckTestCase):