diff --git a/django/contrib/admin/checks.py b/django/contrib/admin/checks.py
index 830a190..6c4beda 100644
--- a/django/contrib/admin/checks.py
+++ b/django/contrib/admin/checks.py
@@ -587,54 +587,37 @@ class ModelAdminChecks(BaseModelAdminChecks):
         elif hasattr(obj, item):
             return []
         elif hasattr(model, item):
-            # getattr(model, item) could be an X_RelatedObjectsDescriptor
+            # Because hasattr(model, item) succeeded, we know that
+            # getattr(model, item) will succeed, and can thus be displayed in
+            # the listview, since the former is implemented by calling the
+            # latter and testing for errors.  In this branch, we therefore only
+            # need to test for ManyToManyFields.
             try:
                 field = model._meta.get_field(item)
             except FieldDoesNotExist:
-                try:
-                    field = getattr(model, item)
-                except AttributeError:
-                    field = None
-
-            if field is None:
-                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.%s'." % (
-                            label, item, obj.__class__.__name__, model._meta.app_label, model._meta.object_name
-                        ),
-                        obj=obj.__class__,
-                        id='admin.E108',
-                    )
-                ]
-            elif isinstance(field, models.ManyToManyField):
-                return [
-                    checks.Error(
-                        "The value of '%s' must not be a ManyToManyField." % label,
-                        obj=obj.__class__,
-                        id='admin.E109',
-                    )
-                ]
-            else:
                 return []
-        else:
-            try:
-                model._meta.get_field(item)
-            except FieldDoesNotExist:
-                return [
-                    # This is a deliberate repeat of E108; there's more than one path
-                    # required to test this condition.
-                    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.%s'." % (
-                            label, item, obj.__class__.__name__, model._meta.app_label, model._meta.object_name
-                        ),
-                        obj=obj.__class__,
-                        id='admin.E108',
-                    )
-                ]
             else:
-                return []
+                if isinstance(field, models.ManyToManyField):
+                    return [
+                        checks.Error(
+                            "The value of '%s' must not be a ManyToManyField." % label,
+                            obj=obj.__class__,
+                            id='admin.E109',
+                        )
+                    ]
+                else:
+                    return []
+        else:
+            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.%s'." % (
+                        label, item, obj.__class__.__name__, model._meta.app_label, model._meta.object_name
+                    ),
+                    obj=obj.__class__,
+                    id='admin.E108',
+                )
+            ]
 
     def _check_list_display_links(self, obj):
         """ Check that list_display_links is a unique subset of list_display.
diff --git a/tests/modeladmin/models.py b/tests/modeladmin/models.py
index 861a2db..845b119 100644
--- a/tests/modeladmin/models.py
+++ b/tests/modeladmin/models.py
@@ -25,6 +25,16 @@ class Concert(models.Model):
     ), blank=True)
 
 
+class DescriptorThatReturnsNone(object):
+    def __get__(self, obj, objtype):
+        # In general, Descriptors should return `self` when `obj is None`, but
+        # let's test the case where they don't.
+        return None
+
+    def __set__(self, obj, value):
+        pass
+
+
 class ValidationTestModel(models.Model):
     name = models.CharField(max_length=100)
     slug = models.SlugField()
@@ -36,6 +46,7 @@ class ValidationTestModel(models.Model):
     best_friend = models.OneToOneField(User, models.CASCADE, related_name='best_friend')
     # This field is intentionally 2 characters long (#16080).
     no = models.IntegerField(verbose_name="Number", blank=True, null=True)
+    none_descriptor = DescriptorThatReturnsNone()
 
     def decade_published_in(self):
         return self.pub_date.strftime('%Y')[:3] + "0's"
diff --git a/tests/modeladmin/test_checks.py b/tests/modeladmin/test_checks.py
index acca6b1..5d3528a 100644
--- a/tests/modeladmin/test_checks.py
+++ b/tests/modeladmin/test_checks.py
@@ -479,7 +479,7 @@ class ListDisplayTests(CheckTestCase):
         class TestModelAdmin(ModelAdmin):
             def a_method(self, obj):
                 pass
-            list_display = ('name', 'decade_published_in', 'a_method', a_callable)
+            list_display = ('name', 'decade_published_in', 'a_method', a_callable, 'none_descriptor')
 
         self.assertIsValid(TestModelAdmin, ValidationTestModel)
 
