Login

Auto-documenting Django Models with Sphinx

Author:
Voightkampff
Posted:
August 25, 2011
Language:
Python
Version:
1.3
Tags:
sphinx introspection documentation docs autodoc
Score:
5 (after 5 ratings)

In my sphinx documentation I really wanted a nice clean list of the fields on my Django models. The most obvious way of doing this is to add ":param blah:" tags to the docstring, but this takes a long time to implement and violates DRY principles when you already have lots of nice help text in the model definition. Another way is to use "#:" comments on attributes, but this approach suffers from the same issues as the previous method with the additional problem of Sphinx crapping out on file and custom fields.

So anyway, this is my solution. It uses the Sphinx docstring processing callback to intercept any objects that inherit from django.models.Model and creates a nice param list of all the fields on that model. Param descriptions come from the field's help text or verbose name if no help text is defined.

To use this, just add it to the end of your source/conf.py, filling out the project path as appropriate. You may be able to skip the Django environment setup lines if you're adding this to a Sphinx doc that already has Django models set up.

 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
THIS_DIR = os.path.dirname(__file__)
PROJECT_DIR = os.path.join(THIS_DIR, 'relative/path/to/your/project/')
sys.path.append(PROJECT_DIR)

import inspect
import settings
from django.core.management import setup_environ
from django.utils.html import strip_tags
from django.utils,encoding import force_unicode

setup_environ(settings)


def process_docstring(app, what, name, obj, options, lines):
    # This causes import errors if left outside the function
    from django.db import models
    
    # Only look at objects that inherit from Django's base model class
    if inspect.isclass(obj) and issubclass(obj, models.Model):
        # Grab the field list from the meta class
        fields = obj._meta._fields()
    
        for field in fields:
            # Decode and strip any html out of the field's help text
            help_text = strip_tags(force_unicode(field.help_text))
            
            # Decode and capitalize the verbose name, for use if there isn't
            # any help text
            verbose_name = force_unicode(field.verbose_name).capitalize()
            
            if help_text:
                # Add the model field to the end of the docstring as a param
                # using the help text as the description
                lines.append(u':param %s: %s' % (field.attname, help_text))
            else:
                # Add the model field to the end of the docstring as a param
                # using the verbose name as the description
                lines.append(u':param %s: %s' % (field.attname, verbose_name))
                
            # Add the field's type to the docstring
            lines.append(u':type %s: %s' % (field.attname, type(field).__name__))
    
    # Return the extended docstring
    return lines  
  
def setup(app):
    # Register the docstring processor with sphinx
    app.connect('autodoc-process-docstring', process_docstring)  

More like this

  1. Sphinx Search ORM / Revised by ludo 7 years, 9 months ago
  2. Django forms add placeholder from help text template tag by Ahmad.Dukhan 1 year ago
  3. SnippySnip by youell 6 years, 10 months ago
  4. Tuned IPAddressField with IPv4 & IPv6 support using Postgres Network Field type by illsci 6 years, 1 month ago
  5. django-mptt enabled FilteredSelectMultiple m2m widget by anentropic 5 years, 6 months ago

Comments

akaihola (on October 7, 2011):

This will fail if the help_text= kwargs for model fields are Unicode objects with non-ASCII characters, e.g.:

price = models.DecimalField(help_text=u'Price (in €)')

To fix this, add this import at the top:

from django.utils.encoding import force_unicode

and replace line 24 with:

help_text = strip_tags(force_unicode(field.help_text))

Also change the bytestrings on lines 33, 37 and 40 to Unicode literals, e.g. lines.append(u':param %s %s' ....

#

Voightkampff (on October 9, 2011):

Thanks for that! I've fixed the snippet.

#

vdboor (on July 9, 2012):

Thanks for the snippet! This works really sweet.

One addition to link to model classes to:

# Add the field's type to the docstring
if isinstance(field, models.ForeignKey):
    to = field.rel.to
    lines.append(u':type %s: %s to :class:`~%s.%s`' % (field.attname, type(field).__name__, to.__module__, to.__name__))
else:
    lines.append(u':type %s: %s' % (field.attname, type(field).__name__))

Secondly, it makes sense to keep the setup() in conf.py only, and place the rest in a docs/_ext folder. The Django docs do this, and it makes it really clean to implement.

#

vojtek.nowak@gmail.com (on November 4, 2014):

in django>=1.6

_meta._fields() has been removed please change it to _meta.fields as an attribute

#

cgaspoz (on January 27, 2015):

If you try to use it with python 3 and django 1.7, you need to replace force_unicode() by force_text()

from django.utils.encoding import force_text
... 
help_text = strip_tags(force_text(field.help_text))
...
verbose_name = force_text(field.verbose_name).capitalize()'''

Thanks for the snippet

#

Please login first before commenting.