Django ORM Lens — read a project's model graph without booting Django

Every time I land on an unfamiliar Django codebase I hit the same wall: to see
how the models actually relate, the project has to run. Settings, a database, a
working venv. On a cold clone that’s an afternoon before I’ve learned anything.

So I wrote a parser that reads models.py directly with the ast module. No
database, no runserver, no venv, no Django import:

uvx django-orm-lens scan

That prints every app, model, field and relation in the project. From the same
parser I built three surfaces:

  • A VS Code sidebar with a live ER diagram (Mermaid, DBML, D2 or PlantUML)
  • A CLI for CI: static N+1 detection, 16 migration-risk rules, SARIF output for
    code scanning, pre-commit hooks, a GitHub Action
  • An MCP server, so a coding agent answers schema questions from the real models
    instead of hallucinating field names

The parsing is the interesting part — it has to survive every way people write a
ForeignKey. The test suite keeps golden snapshots of models vendored from Zulip,
Saleor, Wagtail, django CMS and Mezzanine: 59 models parsed in about 21 ms.

Since I first posted this, three things landed that are worth naming separately:

  • blast-radius joins the migration-risk rules to a workspace-wide reference
    scan. For every destructive operation it reports what still reads the thing
    being dropped, grouped by Django layer, each finding tagged certain / likely /
    possibly. As a GitHub Action it posts one PR comment and updates that same
    comment on later pushes.
  • drift replays each app’s migrations into the field set they imply and diffs
    that against models.py. It is makemigrations --check without a settings
    module or an app registry, so it runs on a cold clone.
  • The N+1 detector now follows a queryset out of the function it was built in,
    so for post in recent(): is analysed. A select_related added either inside
    the helper or by the caller silences it.

MIT, Django 4.2-5.2, Python 3.10-3.12. Everything above is in the free package —
there is no paid tier.

Two things I’d genuinely like input on:

  1. Third-party fields. django-mptt and django-taggit are invisible to the parser
    right now because it matches a whitelist of Django’s own field types.
    TreeForeignKey and TaggableManager are the two I keep hitting — if you
    maintain or use a package that declares its own field types, I’d rather match
    your actual API than guess at it.
  2. The migration-risk rules. I have 16, mostly around table locks and
    irreversible data migrations. If you’ve been burned by a migration in
    production, I’d like to know how — that’s the best source of new rules.