Skip to content

Commit e838fcc

Browse files
authored
Merge pull request #33 from edoburu/bugfix/multi-line-docstring
Fix rendering of nested directives in model parameter documentation
2 parents e5a38d0 + 6869e76 commit e838fcc

File tree

13 files changed

+335
-161
lines changed

13 files changed

+335
-161
lines changed

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
repos:
22
- repo: https://github.com/psf/black
3-
rev: 22.10.0
3+
rev: 23.1.0
44
hooks:
55
- id: black
66
- repo: https://github.com/PyCQA/isort
7-
rev: 5.10.1
7+
rev: 5.12.0
88
hooks:
99
- id: isort
1010
- repo: https://github.com/pycqa/flake8
11-
rev: 5.0.4
11+
rev: 6.0.0
1212
hooks:
1313
- id: flake8
1414
additional_dependencies:

CHANGES.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Changelog
22
=========
33

4+
Unreleased
5+
----------
6+
7+
* [ `#32 <https://github.com/edoburu/sphinxcontrib-django/issues/32>`_ ] Fix rendering of nested directives in model parameter documentation
8+
9+
410
Version 2.0 (2023-01-02)
511
------------------------
612

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"django-phonenumber-field[phonenumbers]",
5353
"psycopg2-binary",
5454
]
55-
test = ["coverage", "pytest", "requests-mock"]
55+
test = ["coverage", "pytest", "pytest-icdiff", "requests-mock"]
5656

5757
[tool.setuptools.dynamic]
5858
version = { attr = "sphinxcontrib_django.__version__" }
@@ -62,13 +62,14 @@
6262

6363
[tool.black]
6464
skip-magic-trailing-comma = true
65+
preview = true
6566

6667
[tool.coverage.run]
6768
command_line = "-m pytest"
6869
source = ["sphinxcontrib_django"]
6970

7071
[tool.pytest.ini_options]
71-
addopts = "-ra -q"
72+
addopts = "-ra -vv --color=yes"
7273
minversion = "6.0"
7374
testpaths = ["tests"]
7475

sphinxcontrib_django/docstrings/__init__.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,16 @@ def setup_django(app, config):
9898
"""
9999
if not config.django_settings:
100100
raise ConfigError(
101-
"Please specify your Django settings in the configuration 'django_settings' in your "
102-
"conf.py"
101+
"Please specify your Django settings in the configuration 'django_settings'"
102+
" in your conf.py"
103103
)
104104
try:
105105
importlib.import_module(config.django_settings)
106106
except ModuleNotFoundError as e:
107107
raise ConfigError(
108-
"The module you specified in the configuration 'django_settings' in your conf.py "
109-
"cannot be imported. Make sure the module path is correct and the source directoy is "
110-
"added to sys.path."
108+
"The module you specified in the configuration 'django_settings' in your"
109+
" conf.py cannot be imported. Make sure the module path is correct and the"
110+
" source directoy is added to sys.path."
111111
) from e
112112
os.environ["DJANGO_SETTINGS_MODULE"] = config.django_settings
113113
django.setup()

sphinxcontrib_django/docstrings/classes.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def improve_model_docstring(app, model, lines):
8686
analyzer = ModuleAnalyzer.for_module(model.__module__)
8787
analyzer.analyze()
8888
field_docs = {
89-
field_name: ". " + " ".join(field_docstring).strip()
89+
field_name: field_docstring
9090
for (_, field_name), field_docstring in analyzer.attr_docs.items()
9191
}
9292

@@ -131,8 +131,15 @@ def add_model_parameters(fields, lines, field_docs):
131131
"""
132132
for field in fields:
133133
# Add docstrings if they are found
134-
docstring = field_docs.get(field.name, "")
135-
lines.append(f":param {field.name}: {get_field_verbose_name(field)}{docstring}")
134+
docstring_lines = field_docs.get(field.name, [])
135+
# Add param doc line
136+
param = f":param {field.name}: "
137+
lines.append(param + get_field_verbose_name(field))
138+
if docstring_lines:
139+
# Separate from verbose name
140+
lines.append("")
141+
# Add and indent existing docstring lines
142+
lines.extend([(" " * len(param)) + line for line in docstring_lines])
136143

137144
# Add type
138145
lines.append(f":type {field.name}: {get_field_type(field, include_role=False)}")

sphinxcontrib_django/docstrings/field_utils.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ def get_field_type(field, include_role=True):
2828
# This happens with foreign keys of abstract models
2929
to = get_model_from_string(field, to)
3030
return (
31-
f":class:`~{type(field).__module__}.{type(field).__name__}` to "
32-
f":class:`~{to.__module__}.{to.__name__}`"
31+
f":class:`~{type(field).__module__}.{type(field).__name__}` to"
32+
f" :class:`~{to.__module__}.{to.__name__}`"
3333
)
3434
elif isinstance(field, models.fields.reverse_related.ForeignObjectRel):
3535
to = field.remote_field.model
3636
return (
37-
f"Reverse :class:`~{type(field.remote_field).__module__}."
38-
f"{type(field.remote_field).__name__}` from :class:`~{to.__module__}.{to.__name__}`"
37+
"Reverse"
38+
f" :class:`~{type(field.remote_field).__module__}.{type(field.remote_field).__name__}`"
39+
f" from :class:`~{to.__module__}.{to.__name__}`"
3940
)
4041
else:
4142
if include_role:
@@ -92,9 +93,9 @@ def get_field_verbose_name(field):
9293
):
9394
# GenericForeignKey does not inherit from django.db.models.Field and has no verbose_name
9495
return (
95-
f"Generic foreign key to the :class:`~django.contrib.contenttypes.models.ContentType` "
96-
f"specified in "
97-
f":attr:`~{field.model.__module__}.{field.model.__name__}.{field.ct_field}`"
96+
"Generic foreign key to the"
97+
" :class:`~django.contrib.contenttypes.models.ContentType` specified in"
98+
f" :attr:`~{field.model.__module__}.{field.model.__name__}.{field.ct_field}`"
9899
)
99100
else:
100101
# This means the field is either a normal field or a forward related field
@@ -131,8 +132,8 @@ def get_field_verbose_name(field):
131132
# Link to the related field if it's not an abstract model
132133
if not field.model._meta.abstract:
133134
verbose_name += (
134-
" (related name: "
135-
f":attr:`~{to.__module__}.{to.__name__}.{related_name}`)"
135+
" (related name:"
136+
f" :attr:`~{to.__module__}.{to.__name__}.{related_name}`)"
136137
)
137138
return verbose_name
138139

sphinxcontrib_django/docstrings/methods.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,23 @@ def improve_method_docstring(name, lines):
2929
if match is not None:
3030
# Django get_..._display method
3131
lines.append(
32-
f"Shows the label of the :attr:`{match.group('field')}`. "
33-
f"See :meth:`~django.db.models.Model.get_FOO_display` for more information."
32+
f"Shows the label of the :attr:`{match.group('field')}`. See"
33+
" :meth:`~django.db.models.Model.get_FOO_display` for more"
34+
" information."
3435
)
3536
elif ".get_next_by_" in name:
3637
match = RE_GET_NEXT_BY.search(name)
3738
if match is not None:
3839
lines.append(
39-
f"Finds next instance based on :attr:`{match.group('field')}`. "
40-
f"See :meth:`~django.db.models.Model.get_next_by_FOO` for more information."
40+
f"Finds next instance based on :attr:`{match.group('field')}`. See"
41+
" :meth:`~django.db.models.Model.get_next_by_FOO` for more"
42+
" information."
4143
)
4244
elif ".get_previous_by_" in name:
4345
match = RE_GET_PREVIOUS_BY.search(name)
4446
if match is not None:
4547
lines.append(
46-
f"Finds previous instance based on :attr:`{match.group('field')}`. "
47-
f"See :meth:`~django.db.models.Model.get_previous_by_FOO` for more "
48-
f"information."
48+
f"Finds previous instance based on :attr:`{match.group('field')}`."
49+
" See :meth:`~django.db.models.Model.get_previous_by_FOO` for more"
50+
" information."
4951
)

tests/roots/test-docstrings/dummy_django_app/models.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,20 @@ class SimpleModel(models.Model):
3232
"ChildModelA", related_name="simple_model", on_delete=models.CASCADE
3333
)
3434

35-
#: Docstring of many to many field
3635
childrenB = models.ManyToManyField("ChildModelB", related_name="simple_models")
36+
"""
37+
Docstring of many to many field
38+
39+
.. note::
40+
41+
This syntax is also supported.
42+
"""
3743

3844
#: Docstring of char field
45+
#:
46+
#: .. warning::
47+
#:
48+
#: Inline directives should be preserved.
3949
dummy_field = models.CharField(
4050
max_length=3,
4151
help_text="This should help you",

0 commit comments

Comments
 (0)