Skip to content

django-test-doctor

PyPI Downloads Downloads / month License: MIT Python 3.10+ Django 4.2+

360° health check for Django projects. One command, fifteen layers of analysis, one verdict.

pip install django-test-doctor
./manage.py doctor

Distribution vs import path

Installed as django-test-doctor on PyPI, but the Python import path is django_doctor. pip install django-test-doctor, then from django_doctor import Check, Finding.

Why

./manage.py check validates runtime configuration. djlint validates template syntax. pytest validates whatever you remembered to test. Nothing validates the whole project at once. Doctor does.

Three real bugs doctor surfaced in the past week on a production codebase (Django 6 / DRF / PostGIS, ~175 models, ~150 forms, 202 serializers):

  • forms check caught a ModelForm.__init__ that touched self.fields['fonction'].choices where fonction was a bare CharField with no choices → AttributeError → HTTP 500 on every GET.
  • post_smoke check caught a post_save signal creating a ConfigurationTVA without its NOT-NULL regime FK → IntegrityError → HTTP 500 on every /mandats/nouveau/ POST.
  • forms_meta check caught an OperationTVAForm.clean() that assigned to cleaned_data["montant_ttc"] while montant_ttc wasn't in Meta.fields → Django silently dropped the value → IntegrityError on save.

Every one of these passed ./manage.py check, passed their unit tests, and still reached production.

Layers

Layer What it does Status
system Wraps Django's built-in check framework ✅ 0.1
urls Probes every URL as anon / user / staff / superuser, fails on 5xx ✅ 0.1
forms Instantiates every Form / ModelForm blank + with empty data ✅ 0.1
migrations Byte-for-byte parity with makemigrations --check --dry-run ✅ 0.1
admin list_display / list_filter / search_fields / ordering fields actually exist ✅ 0.2
models __str__ defined, Meta.ordering, no SET_NULL on NOT NULL FK ✅ 0.2
security check --deploy + weak SECRET_KEY + DEBUG=True + empty ALLOWED_HOSTS ✅ 0.2
drf Serializer init + Meta.fieldsMeta.model coherence ✅ 0.3
post_smoke POST every CreateView / UpdateView with auto fixture, fail on 5xx ✅ 0.4
forms_meta cleaned_data[x] = … where x is NOT NULL but not in Meta.fields ✅ 0.5
templates Broken {% url %} / {% static %} / template syntax ✅ 0.6
views First-party views with no LoginRequired / @login_required / DRF permission ✅ 0.6
autogen Unsafe int(x.split('-')[-1]) in auto-gen without try/except ✅ 0.7
update_fields save(update_fields=…) dropping fields recomputed by save() ✅ 0.8
fk_redundant Column duplicating a value reachable via a ForeignKey on the same model ✅ 0.10

Plus in 0.6: --html <path> (standalone HTML report), --diff <ref> (only show findings on files changed since a git ref), and URL-kwargs fixtures that unlock urls / post_smoke on detail and edit routes.

Roadmap: static, i18n, --fix, --watch.

Next steps

Status

Alpha. Battle-tested against a production Django 6 / DRF / PostGIS / pgvector codebase. Fifteen checks, 72 regression tests, Trusted Publishing on PyPI. Still expect the occasional rough edge — report issues on GitHub.