From 224c4f9e55ec43b0d8787e944db354e97c4faab8 Mon Sep 17 00:00:00 2001 From: Nick Touran Date: Wed, 16 Aug 2023 14:38:35 -0700 Subject: [PATCH] Fix issue with string args in GenericRelation. Fixes #42. The tests intended to capture the issue but actually don't trigger the real issue in the test scenario, so they may not be appropriate. I tried making a 2nd app to see if the deferred issue would show up but it does not. --- .../docstrings/field_utils.py | 10 ++++--- .../dummy_django_app/settings.py | 1 + .../dummy_django_app2/models.py | 7 +++++ tests/test_attribute_docstrings.py | 25 +++++++++++++++++ tests/test_class_docstrings.py | 27 +++++++++++++++++++ tests/test_data_docstrings.py | 6 ++--- 6 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 tests/roots/test-docstrings/dummy_django_app2/models.py diff --git a/sphinxcontrib_django/docstrings/field_utils.py b/sphinxcontrib_django/docstrings/field_utils.py index bb732e2..40cb130 100644 --- a/sphinxcontrib_django/docstrings/field_utils.py +++ b/sphinxcontrib_django/docstrings/field_utils.py @@ -78,10 +78,14 @@ def get_field_verbose_name(field): related_name = ( related_name or field.remote_field.model._meta.verbose_name_plural ) - # If field is a foreign key or a ManyToMany field, use the prefix "All" - verbose_name = ( - f"All {related_name} of this {field.model._meta.verbose_name}" + # handle potential str-type deferred model names + forward_name = ( + field.model + if isinstance(field.model, str) + else field.model._meta.verbose_name ) + # If field is a foreign key or a ManyToMany field, use the prefix "All" + verbose_name = f"All {related_name} of this {forward_name}" # Link to the origin of the reverse related field if it's not from an abstract model if not field.remote_field.model._meta.abstract: verbose_name += ( diff --git a/tests/roots/test-docstrings/dummy_django_app/settings.py b/tests/roots/test-docstrings/dummy_django_app/settings.py index 256e7e2..8809a53 100644 --- a/tests/roots/test-docstrings/dummy_django_app/settings.py +++ b/tests/roots/test-docstrings/dummy_django_app/settings.py @@ -9,6 +9,7 @@ "django.contrib.auth", "django.contrib.contenttypes", "dummy_django_app", + "dummy_django_app2", ] USE_TZ = False diff --git a/tests/roots/test-docstrings/dummy_django_app2/models.py b/tests/roots/test-docstrings/dummy_django_app2/models.py new file mode 100644 index 0000000..f25c05e --- /dev/null +++ b/tests/roots/test-docstrings/dummy_django_app2/models.py @@ -0,0 +1,7 @@ +from django.contrib.contenttypes.fields import GenericRelation +from django.db import models + + +class GenericRelationModel(models.Model): + # specifically test deferred string-type argument + relation_field = GenericRelation("dummy_django_app.TaggedItem") diff --git a/tests/test_attribute_docstrings.py b/tests/test_attribute_docstrings.py index 3b582c3..fb4c297 100644 --- a/tests/test_attribute_docstrings.py +++ b/tests/test_attribute_docstrings.py @@ -447,3 +447,28 @@ def test_phonenumber_field(app, do_autodoc): " Phone number", "", ] + + +@pytest.mark.sphinx("html", testroot="docstrings") +def test_generic_relation_field(app, do_autodoc): + actual = do_autodoc( + app, "attribute", "dummy_django_app2.models.GenericRelationModel.relation_field" + ) + print(actual) + assert list(actual) == [ + "", + ".. py:attribute:: GenericRelationModel.relation_field", + " :module: dummy_django_app2.models", + "", + ( + " Type: Reverse" + " :class:`~django.contrib.contenttypes.fields.GenericRelation` from" + " :class:`~dummy_django_app2.models.GenericRelationModel`" + ), + "", + ( + " All + of this tagged item (related name of" + " :attr:`~dummy_django_app2.models.GenericRelationModel.relation_field`)" + ), + "", + ] diff --git a/tests/test_class_docstrings.py b/tests/test_class_docstrings.py index 9b45bc3..b8bc6b8 100644 --- a/tests/test_class_docstrings.py +++ b/tests/test_class_docstrings.py @@ -367,3 +367,30 @@ def test_form(app, do_autodoc): " * ``test2``: Test2 (:class:`~django.forms.CharField`)", "", ] + + +@pytest.mark.sphinx("html", testroot="docstrings") +def test_relation_model(app, do_autodoc): + actual = do_autodoc(app, "class", "dummy_django_app2.models.GenericRelationModel") + print(actual) + assert list(actual) == [ + "", + ".. py:class:: GenericRelationModel(id)", + " :module: dummy_django_app2.models", + "", + " :param id: Primary key: ID", + " :type id: ~django.db.models.AutoField", + "", + " Relationship fields:", + "", + ( + " :param relation_field: Relation field (related name:" + " :attr:`~dummy_django_app.models.TaggedItem.+`)" + ), + ( + " :type relation_field:" + " :class:`~django.contrib.contenttypes.fields.GenericRelation` to" + " :class:`~dummy_django_app.models.TaggedItem`" + ), + "", + ] diff --git a/tests/test_data_docstrings.py b/tests/test_data_docstrings.py index 08488be..88dc45d 100644 --- a/tests/test_data_docstrings.py +++ b/tests/test_data_docstrings.py @@ -17,7 +17,7 @@ def test_data(app, do_autodoc): " :module: dummy_django_app.settings", ( " :value: ['django.contrib.auth', 'django.contrib.contenttypes'," - " 'dummy_django_app']" + " 'dummy_django_app', 'dummy_django_app2']" ), "", " These are the installed apps", @@ -25,8 +25,8 @@ def test_data(app, do_autodoc): " .. code-block:: JavaScript", "", ( - " ['django.contrib.auth', 'django.contrib.contenttypes'," - " 'dummy_django_app']\n" + " [\n 'django.contrib.auth',\n 'django.contrib.contenttypes',\n" + " 'dummy_django_app',\n 'dummy_django_app2',\n]\n" ), "", ]