Skip to content

Instantly share code, notes, and snippets.

@codingjoe
Last active July 24, 2022 23:16
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save codingjoe/314bda5a07ff3b41f247 to your computer and use it in GitHub Desktop.
Save codingjoe/314bda5a07ff3b41f247 to your computer and use it in GitHub Desktop.
Build Django docs like a pro!

Build Django docs like a pro!

Sphinx config

docs/conf.py

import importlib
import inspect
import os
import sys

sys.path.insert(0, os.path.abspath('..'))

# setup Django using django-configurations
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "my_app.settings")
os.environ.setdefault('DJANGO_CONFIGURATION', 'Dev')

# Fix Django's FileFields
from django.db.models.fields.files import FileDescriptor
FileDescriptor.__get__ = lambda self, *args, **kwargs: self
from django.db.models.manager import AbstractManagerDescriptor, ManagerDescriptor
ManagerDescriptor.__get__ = lambda self, *args, **kwargs: self.manager
AbstractManagerDescriptor.__get__ = lambda self, *args, **kwargs: None

# Stop Django from executing DB queries
from django.db.models.query import QuerySet
QuerySet.__repr__ = lambda self: self.__class__.__name__


try:
    import enchant  # NoQA
except ImportError:
    enchant = None
    
GITHUB_USER = ''  # Name of your Github user or organisation
GITHUB_PROJECT = ''  # Name of your Github repository


def process_django_models(app, what, name, obj, options, lines):
    """Append params from fields to model documentation."""
    from django.utils.encoding import force_text
    from django.utils.html import strip_tags
    from django.db import models

    spelling_white_list = ['', '.. spelling::']

    if inspect.isclass(obj) and issubclass(obj, models.Model):
        for field in obj._meta.fields:
            help_text = strip_tags(force_text(field.help_text))
            verbose_name = force_text(field.verbose_name).capitalize()

            if help_text:
                lines.append(':param %s: %s - %s' % (field.attname, verbose_name,  help_text))
            else:
                lines.append(':param %s: %s' % (field.attname, verbose_name))

            if enchant is not None:
                from enchant.tokenize import basic_tokenize

                words = verbose_name.replace('-', '.').replace('_', '.').split('.')
                words = [s for s in words if s != '']
                for word in words:
                    spelling_white_list += ["    %s" % ''.join(i for i in word if not i.isdigit())]
                    spelling_white_list += ["    %s" % w[0] for w in basic_tokenize(word)]

            field_type = type(field)
            module = field_type.__module__
            if 'django.db.models' in module:
                # scope with django.db.models * imports
                module = 'django.db.models'
            lines.append(':type %s: %s.%s' % (field.attname, module, field_type.__name__))
        if enchant is not None:
            lines += spelling_white_list
    return lines


def process_modules(app, what, name, obj, options, lines):
    """Add module names to spelling white list."""
    if what != 'module':
        return lines
    from enchant.tokenize import basic_tokenize

    spelling_white_list = ['', '.. spelling::']
    words = name.replace('-', '.').replace('_', '.').split('.')
    words = [s for s in words if s != '']
    for word in words:
        spelling_white_list += ["    %s" % ''.join(i for i in word if not i.isdigit())]
        spelling_white_list += ["    %s" % w[0] for w in basic_tokenize(word)]
    lines += spelling_white_list
    return lines


def skip_queryset(app, what, name, obj, skip, options):
    """Skip queryset subclasses to avoid database queries."""
    from django.db import models
    if isinstance(obj, (models.QuerySet, models.manager.BaseManager)) or name.endswith('objects'):
        return True
    return skip


def setup(app):
    # Register the docstring processor with sphinx
    app.connect('autodoc-process-docstring', process_django_models)
    app.connect('autodoc-skip-member', skip_queryset)
    if enchant is not None:
        app.connect('autodoc-process-docstring', process_modules)

extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.graphviz',
    'sphinx.ext.napoleon',
    'sphinx.ext.linkcode',
    'sphinx.ext.inheritance_diagram',
    'sphinx.ext.intersphinx',
    'configurations',
]

if enchant is not None:
    extensions.append('sphinxcontrib.spelling')


intersphinx_mapping = {
    'python': ('https://docs.python.org/3.5', None),
    'sphinx': ('http://sphinx.pocoo.org/', None),
    'django': ('https://docs.djangoproject.com/en/dev/', 'https://docs.djangoproject.com/en/dev/_objects/'),
    'djangoextensions': ('https://django-extensions.readthedocs.org/en/latest/', None),
    'geoposition': ('https://django-geoposition.readthedocs.org/en/latest/', None),
    'braces': ('https://django-braces.readthedocs.org/en/latest/', None),
    'select2': ('https://django-select2.readthedocs.org/en/latest/', None),
    'celery': ('https://celery.readthedocs.org/en/latest/', None),
}


def linkcode_resolve(domain, info):
    """Link source code to GitHub."""
    project = GITHUB_PROJECT
    github_user = GITHUB_USER
    head = 'master'

    if domain != 'py' or not info['module']:
        return None
    filename = info['module'].replace('.', '/')
    mod = importlib.import_module(info['module'])
    basename = os.path.splitext(mod.__file__)[0]
    if basename.endswith('__init__'):
        filename += '/__init__'
    item = mod
    lineno = ''
    for piece in info['fullname'].split('.'):
        item = getattr(item, piece)
        try:
            lineno = '#L%d' % inspect.getsourcelines(item)[1]
        except (TypeError, IOError):
            pass
    return ("https://github.com/%s/%s/blob/%s/%s.py%s" %
            (github_user, project, head, filename, lineno))

autodoc_default_flags = ['members']

# spell checking
spelling_lang = 'en_US'
spelling_word_list_filename = 'spelling_wordlist.txt'
spelling_show_suggestions = True
spelling_ignore_pypi_package_names = True


# Here goes all your other config.

Testing docs

To test your docs, simply run the following test command:

(cd docs; make apidoc spelling)

To make fetch all the warnings in your test, simply add the -W arguement in your docs Makefile.

spelling:
	$(SPHINXBUILD) -b spelling -W $(ALLSPHINXOPTS) $(BUILDDIR)/spelling

Travis-CI

To make all this work on travis-ci you'll need to add the enchant binaries to the build. You won't need them on the deploy tho, since enchant is an optional import.

language: python
sudo: false
cache:
  apt: true
  pip: true
python:
- '3.5.1'
addons:
  apt:
    packages:
    - python3-enchant
    - graphviz
@linevych
Copy link

What is the license of this snippet?
Can I partly reuse this in article licensed under CC BY-NC 2.0?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment