diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index 05f42f475b..9f66ce8545 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -16,6 +16,7 @@ from django.http import Http404 from django.template.engine import Engine from django.urls import get_mod_func, get_resolver, get_urlconf from django.utils.decorators import method_decorator +from django.utils.functional import cached_property from django.utils.inspect import ( func_accepts_kwargs, func_accepts_var_args, get_func_full_args, method_has_no_args, @@ -250,7 +251,7 @@ class ModelDetailView(BaseAdminDocsView): methods = [] # Gather model methods. for func_name, func in model.__dict__.items(): - if inspect.isfunction(func) or isinstance(func, property): + if inspect.isfunction(func) or isinstance(func, (cached_property, property)): try: for exclude in MODEL_METHODS_EXCLUDE: if func_name.startswith(exclude): @@ -261,9 +262,10 @@ class ModelDetailView(BaseAdminDocsView): verbose = verbose and ( utils.parse_rst(cleandoc(verbose), 'model', _('model:') + opts.model_name) ) - # Show properties and methods without arguments as fields. - # Otherwise, show as a 'method with arguments'. - if isinstance(func, property): + # Show properties, cached_properties, and methods without + # arguments as fields. Otherwise, show as a 'method with + # arguments'. + if isinstance(func, (cached_property, property)): fields.append({ 'name': func_name, 'data_type': get_return_data_type(func_name), diff --git a/docs/ref/contrib/admin/admindocs.txt b/docs/ref/contrib/admin/admindocs.txt index 5a95e101ed..38c67ea6bd 100644 --- a/docs/ref/contrib/admin/admindocs.txt +++ b/docs/ref/contrib/admin/admindocs.txt @@ -55,6 +55,10 @@ system along with all the fields, properties, and methods available on it. Relationships to other models appear as hyperlinks. Descriptions are pulled from ``help_text`` attributes on fields or from docstrings on model methods. +.. versionchanged:: 4.0 + + Older versions don't display model cached properties. + A model with useful documentation might look like this:: class BlogEntry(models.Model): diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt index 88a73369ef..9295f086b2 100644 --- a/docs/releases/4.0.txt +++ b/docs/releases/4.0.txt @@ -43,6 +43,8 @@ Minor features * The admindocs now allows esoteric setups where :setting:`ROOT_URLCONF` is not a string. +* The model section of the ``admindocs`` now shows cached properties. + :mod:`django.contrib.auth` ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/admin_docs/models.py b/tests/admin_docs/models.py index 02bf1efa9f..06569d5c4c 100644 --- a/tests/admin_docs/models.py +++ b/tests/admin_docs/models.py @@ -3,6 +3,7 @@ Models for testing various aspects of the djang.contrib.admindocs app """ from django.db import models +from django.utils.functional import cached_property class Company(models.Model): @@ -56,6 +57,10 @@ class Person(models.Model): def a_property(self): return 'a_property' + @cached_property + def a_cached_property(self): + return 'a_cached_property' + def suffix_company_name(self, suffix='ltd'): return self.company.name + suffix diff --git a/tests/admin_docs/test_views.py b/tests/admin_docs/test_views.py index 266ee98bf8..8e09c4cfec 100644 --- a/tests/admin_docs/test_views.py +++ b/tests/admin_docs/test_views.py @@ -232,6 +232,10 @@ class TestModelDetailView(TestDataMixin, AdminDocsTestCase): """Model properties are displayed as fields.""" self.assertContains(self.response, 'a_property') + def test_instance_of_cached_property_methods_are_displayed(self): + """Model cached properties are displayed as fields.""" + self.assertContains(self.response, 'a_cached_property') + def test_method_data_types(self): company = Company.objects.create(name="Django") person = Person.objects.create(first_name="Human", last_name="User", company=company)