1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
|
"""
This module contains all functions which are used to improve the documentation of classes.
"""
from __future__ import annotations
from django import forms
from django.db import models
from sphinx.application import Sphinx
from sphinx.pycode import ModuleAnalyzer
from .field_utils import get_field_type, get_field_verbose_name
def improve_class_docstring(app: Sphinx, cls: type, lines: list[str]) -> None:
"""
Improve the documentation of a class if it's a Django model or form
:param app: The Sphinx application object
:param cls: The instance of the class to document
:param lines: The docstring lines
"""
if issubclass(cls, models.Model):
improve_model_docstring(app, cls, lines)
elif issubclass(cls, forms.BaseForm):
improve_form_docstring(cls, lines)
def improve_model_docstring(app: Sphinx, model: models.Model, lines: list[str]) -> None:
"""
Improve the documentation of a Django :class:`~django.db.models.Model` subclass.
This adds all model fields as parameters to the ``__init__()`` method.
:param app: The Sphinx application object
:param model: The instance of the model to document
:param lines: The docstring lines
"""
# Add database table name
if app.config.django_show_db_tables:
add_db_table_name(app, model, lines)
# Get predefined params to exclude them from the automatically inserted params
param_offset = len(":param ")
predefined_params = [
line[param_offset : line.find(":", param_offset)]
for line in lines
if line.startswith(":param ") and ":" in line[param_offset:]
]
# Get all fields of this model which are not already explicitly included in the docstring
all_fields = [
field
for field in model._meta.get_fields(include_parents=True)
if field.name not in predefined_params
]
# Get all related fields (ForeignKey, OneToOneField, ManyToManyField)
related_fields = [
field
for field in all_fields
if isinstance(field, models.fields.related.RelatedField)
]
# Get all reverse relationships
reverse_related_fields = [
field
for field in all_fields
if isinstance(field, models.fields.reverse_related.ForeignObjectRel)
]
# All fields which are neither related nor reverse related
non_related_fields = [
field
for field in all_fields
if field not in related_fields + reverse_related_fields
]
# Analyze model to get inline field docstrings
analyzer = ModuleAnalyzer.for_module(model.__module__)
analyzer.analyze()
field_docs = {
field_name: field_docstring
for (_, field_name), field_docstring in analyzer.attr_docs.items()
}
# Add the normal fields to the docstring
add_model_parameters(non_related_fields, lines, field_docs)
# Add the related fields
if related_fields:
lines.append("")
lines.append("Relationship fields:")
lines.append("")
add_model_parameters(related_fields, lines, field_docs)
# Add the reverse related fields
if reverse_related_fields:
lines.append("")
lines.append("Reverse relationships:")
lines.append("")
add_model_parameters(reverse_related_fields, lines, field_docs)
# Add the inheritance diagram
if (
"sphinx.ext.inheritance_diagram" in app.extensions
and "sphinx.ext.graphviz" in app.extensions
and not any("inheritance-diagram::" in line for line in lines)
):
lines.append("")
lines.append(f".. inheritance-diagram:: {model.__module__}.{model.__name__}")
lines.append("")
def add_db_table_name(app: Sphinx, model: models.Model, lines: list[str]) -> None:
"""
Format and add table name by extension configuration.
:param app: The Sphinx application object
:param model: The instance of the model to document
:param lines: The docstring lines
"""
if model._meta.abstract and not app.config.django_show_db_tables_abstract:
return
table_name = None if model._meta.abstract else model._meta.db_table
lines.insert(0, "")
lines.insert(0, f"**Database table:** ``{table_name}``")
def add_model_parameters(
fields: list[models.Field], lines: list[str], field_docs: dict
) -> None:
"""
Add the given fields as model parameter with the ``:param:`` directive
:param fields: The list of fields
:param lines: The list of current docstring lines
:param field_docs: The attribute docstrings of the model
"""
for field in fields:
# Add docstrings if they are found
docstring_lines = field_docs.get(field.name, [])
# Add param doc line
param = f":param {field.name}: "
lines.append(param + get_field_verbose_name(field))
if docstring_lines:
# Separate from verbose name
lines.append("")
# Add and indent existing docstring lines
lines.extend([(" " * len(param)) + line for line in docstring_lines])
# Add type
lines.append(f":type {field.name}: {get_field_type(field, include_role=False)}")
def improve_form_docstring(form: forms.Form, lines: list[str]) -> None:
"""
Improve the documentation of a Django :class:`~django.forms.Form` class.
This highlights the available fields in the form.
:param form: The form object
:param lines: The list of existing docstring lines
"""
lines.append("**Form fields:**")
lines.append("")
for name, field in form.base_fields.items():
field_type = f"{field.__class__.__module__}.{field.__class__.__name__}"
label = field.label or name.replace("_", " ").title()
lines.append(f"* ``{name}``: {label} (:class:`~{field_type}`)")
|