A few weeks ago, I contributed to Django with a couple of patches. One of them consisted of an enhancement to Django’s auto generated admin documentation for admin sites. I got to know how this feature really works.
Read on if you want to get a clear picture of how you can make more effective use of it.
Django's Automated Generator
In this post, I'll assume that you are working with Python 3.
This past week I had a chance to learn more about the internals of Django's automated admin documentation generator.
Among a plethora of cool stuff, the automated generation of admin sites and the documentation for your project’s code stands out when comparing Django to similar web frameworks.
I'll walk you through some of Django’s internals to understand where the magic for automated docs generation happens.
Setting Up Your Environment
Create the directory where your project will live and move there (e.g., cd).
Create a virtual environment by executing the following command,
$ python3 -m venv yourvenvname
To activate the environment and install Django, execute the following commands,
$ source yourvenvname/bin/activate
$ pip install django
Writing Your Application
To start a Django project, execute the following command from within the root dir for your project,
$ django-admin startproject yourprojectname
Move into the directory created by the previous command. Create an app named blog
.
You will be working under this context.
Now, run the following command to create the app,
$ python manage.py startapp blog
NOTE: for an explanation of what is considered a project and what is considered an app, go to the official Django Docs.
Writing a Small Django App
Now, it is time to write some code,
- Create the models for the
blog
app - Configure the urls to which the app will respond
- Set up the database (i.e., we will be using the default configuration with SQLite)
- Set up the admin site and docs.
I will not be talking about views, as this is not within the scope of this post.
Start by going to the blog/models.py
file and write,
import datetime
from django.db import models
# Create your models here.
class Author(models.Model):
name = models.CharField(max_length=100)
# This defines how the object will be represented.
def __str__(self):
return self.name
class BlogPost(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
post_title = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
# This defines how the object will be represented.
def __str__(self):
return self.post_title
# States if the blog post was published less than a day ago.
def published_today(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
Create two models for your blog
app: a BlogPost
and Author
.
The Author
model has only one attribute for its name. The BlogPost
model has several: a post title, publishing date, an author (which is an Author
instance), and a method that will tell if the blog post was published today.
Don’t worry about adding the routes for the blog
app as you will not be working with them for this post.
Setting Up the Database
Before running the migrations to set up the database, add the blog
app to the INSTALLED_APPS
list in the settings file. Go to the file settings.py
in your project’s root dir and add the following item to the INSTALLED_APPS
list,
'blog.apps.BlogConfig',
Run the following command to create the appropriate migrations for your Blog
app, and run them to setup the database,
$ python manage.py createmigrations blog
$ python manage.py migrate
Your database is now set and ready.
Building An Admin Panel
Add the proper configuration to build the admin site. Go to the urls.py
file in the project’s root directory. Make sure the following line is included in your urlpatterns
list,
path('admin/', admin.site.url),
You can now navigate to the admin site, but first, create an admin user. To do it, run the following command (i.e., it will prompt you for the appropriate credentials),
$ python manage.py createsuperuser
Run the development server,
$ python manage.py runserver
You’re all set to visit the admin site and log in with the credentials you just provided. You will be logged into the home admin site.
Adding App Documentation to Admin Site
Let's see how to add the documentation for your app to your admin site.
First you will have to install the docutils
Python module. Navigate to this link for how to install it.
After the installation, add the following two lines of code,
- In
settings.py
, in theINSTALLED_APPS
list, adddjango.contrib.admindocs
. - In your
urls.py
, addpath('admin/doc/',include('django.contrib.admindocs.urls'))
to yoururlpatterns
list. Make sure to add it before the‘admin/’
route.
Now you can visit the documentation section and you’ll see there is a section for models.
Navigate to the models section and select the BlogPost
model to see its details.
How to Improve the Description of Fields and Methods
It’s as easy as adding docstrings to your methods and human readable alternative strings and help text to your model fields.
Try it by adding or removing comments enclosed by double-quotes at the beginning of your methods, and by adding a help_text
attribute to your field definitions.
How Does Django Generate its Model Documentation?
Let's go to the Django source.
In django.contrib.admindocs.views.py
you will find there are a couple of classes named ModelIndexView
and ModelDetailView
. These are the ones in charge of collecting and packing your model's info in order to pass it to the views.
As you can see in the following code, the ModelIndexView
retrieves models metadata to create a list that will be passed on to the view.
class ModelIndexView(BaseAdminDocsView):
template_name = 'admin_doc/model_index.html'
def get_context_data(self, **kwargs):
m_list = [m._meta for m in apps.get_models()]
return super().get_context_data(**{**kwargs, 'models': m_list})
In ModelDetailView
you can see that the model fields (attributes) and methods are packed in lists.
For attributes, a dictionary for each one is created with the following keys: name
, data_type
, verbose
. The last key is for human readable or extended descriptions.
For methods, same as fields, a dictionary is created with the keys name
, arguments
, and verbose
.
Something to point out is the fact that this class will consider any method without arguments as a Field.
# Gather fields/field descriptions.
fields = []
for field in opts.fields:
# ForeignKey is a special case since the field will actually be a
# descriptor that returns the other object
if isinstance(field, models.ForeignKey):
data_type = field.remote_field.model.__name__
app_label = field.remote_field.model._meta.app_label
verbose = utils.parse_rst(
(_("the related `%(app_label)s.%(data_type)s` object") % {
'app_label': app_label, 'data_type': data_type,
}),
'model',
_('model:') + data_type,
)
else:
data_type = get_readable_field_data_type(field)
verbose = field.verbose_name
fields.append({
'name': field.name,
'data_type': data_type,
'verbose': verbose or '',
'help_text': field.help_text,
})
# Gather model methods.
for func_name, func in model.__dict__.items():
if inspect.isfunction(func):
try:
for exclude in MODEL_METHODS_EXCLUDE:
if func_name.startswith(exclude):
raise StopIteration
except StopIteration:
continue
verbose = func.__doc__
verbose = verbose and (
utils.parse_rst(utils.trim_docstring(verbose), 'model', _('model:') + opts.model_name)
)
# If a method has no arguments, show it as a 'field', otherwise
# as a 'method with arguments'.
if func_has_no_args(func) and not func_accepts_kwargs(func) and not func_accepts_var_args(func):
fields.append({
'name': func_name,
'data_type': get_return_data_type(func_name),
'verbose': verbose or '',
})
Finally, all this packed info regarding model methods and fields is passed to view templates through a method inherited by these classes from one of the classes in their lineage. You can see this in action in the django.contrib.admindocs.urls.py
.
urlpatterns = [
...
path(
'models/',
views.ModelIndexView.as_view(),
name='django-admindocs-models-index',
),
re_path(
r'^models/(?P[^\.]+)\.(?P[^/]+)/
Finally…
I hope this post has helped shed some light on the internals of Django’s admin docs to help you better understand how it is generated.
Also, the official documentation is a great place to visit if you want to know more about what you can do to improve your admin docs.
Have fun with Django and make the most out of it.
For further questions you can contact me at htellechea@nearsoft.com.
,
views.ModelDetailView.as_view(),
name='django-admindocs-models-detail',
),
...
]
Finally…
I hope this post has helped shed some light on the internals of Django’s admin docs to help you better understand how it is generated.
Also, the official documentation is a great place to visit if you want to know more about what you can do to improve your admin docs.
Have fun with Django and make the most out of it.
For further questions, you can contact us.