Django Model Search Tool

code_annotations django_find_annotations::

Usage: code_annotations django_find_annotations [OPTIONS]

Subcommand for dealing with annotations in Django models.

--config_file FILE

Path to the configuration file

--seed_safelist

Generate an initial safelist file based on the current Django environment. [default: False]

--list_local_models

List all locally defined models (in the current repo) that require annotations. [default: False]

--report_path TEXT

Location to write the report

-v

Verbosity level (-v through -vvv)

--lint

Enable or disable linting checks [default: False]

--report

Enable or disable writing the report [default: False]

--coverage

Enable or disable coverage checks [default: False]

--help

Show this message and exit.

Overview

The Django Model Search Tool, or Django Tool, is written to provide more structured searching and validation in a place where data is often stored. Since all of the models in a package can be enumerated it is possible, though not required, to use this tool to positively assert that all concrete (non-proxy, non-abstract) models in a project are annotated in some way. If you do not need this functionality and simply want to find annotations and create a report, the static search tool is much easier to configure and can search all of your code (instead of just model docstrings).

Important

To use the Django tool you must first set the DJANGO_SETTINGS_MODULE environment variable to point to a valid settings file. The tool will initialize Django and use its introspection to find models. The settings file should have INSTALLED_APPS configured for all Django apps that you wish to have annotated. See the Django Docs for details.

The edX use case which prompted the creation of this tool is evident in many of our tests and code samples. It is to be able to track the storage, use, and retirement of personally identifiable information (PII) across our many projects and repositories. Since the majority of our information is stored via Django models, this tool helps us make sure that at least all of those are annotated to assert whether they contain PII or not.

The tool works by actually running your Django app or project in a development-like environment. It then uses Django’s introspection tools to find all installed apps and enumerate their models. Each model further enumerates its inheritance tree and all model docstrings are checked for annotations. All annotations in all models and their ancestors are added to the list.

The Safelist

In order to assert that all concrete models in a project are annotated, it is also necessary to be able to annotate models that are otherwise installed in the Python virtual environment and are not part of your source tree. Models in your source tree are called “local models”, and ones otherwise installed in the Python environment are “non-local” models. In order to annotate non-local models, which may come from other repositories or PyPI packages, use the “safelist” feature.

“Safe” in safelist doesn’t mean that the models themselves do not require annotation, but rather it gives developers a place to annotate those models and put them in a known state. When setting up a repository to use the Django tool, you should use the --seed_safelist option to generate an initial safelist template that contains empty entries for all non-local models. In order for those models to count as “covered”, you must add annotations to them in the safelist.

An freshly created safelist:

social_django.Association: {}
social_django.Code: {}

And one that has been annotated:

social_django.Association:
  ".. no_pii:": "This model has no PII"
social_django.Code:
  ".. pii:": "Email address"
  ".. pii_types:": other
  ".. pii_retirement:": local_api

Note

Note that each model can only have one annotation for each token type. For example, it would be invalid to add a second .. no_pii: annotation to social_django.Association.

Important

Some types of “local” models are procedurally generated and do not have files in code, e.g. models created by django-simple-history. In those unusual circumstances you can choose to annotate them in the safelist to make sure they are covered.

Coverage

The second unique part of the Django tool is the model coverage report and check. Since we are able to find all models in a project with a reasonable degree of accuracy we can target a percentage of them that must be annotated. When you run the tool with the --coverage option it will compare the percentage of annotated models against the configuration variable coverage_target. If the coverage_target is not met the search will fail and a list of the un-annotated models will be displayed.

Having annotations at any level of a model’s inheritance will result in that model being considered “covered”.

Lint and Report

This tool supports the same --lint and --report options as the Static Search Tool tool, and they are functionally the same. Linting will fail on malformed annotations found in model docstrings, such as bad choices or incomplete groups. Reporting will write out a report file in the same format as the Static Tool, but with some additional information in the extra key such as the model_id, which is a string in the format of “parentApp.ModelClassName”, as Django uses to represent models internally. It also has the full model docstring in full_comment.

If a model inherits from another model that has annotations, those annotations will be included in the report under the child model’s name, as well as any annotations in the model itself.

Local Models

Finally, to help find models in the local source tree that still need to be annotated, the tool has a --list_local_models option. This will output the model id of all models that still need to be annotated.