by FROWNINGdev
Provides a live sidebar, instant ER diagrams, a powerful CLI, and an MCP server that statically parse Django models to visualize the schema, enforce lint rules, generate factories, and answer AI agents without needing a running Django project or database.
Django ORM Lens delivers schema‑intelligence for any Django codebase. By walking the source tree and parsing models.py (including split models/ packages), it builds a complete model graph that can be viewed in the VS Code sidebar, rendered as a live Mermaid ER diagram, queried via a command‑line interface, or accessed through MCP tools for AI agents.
pip install django-orm-lens (or uvx django-orm-lens scan). Run sub‑commands such as django-orm-lens er, django-orm-lens diff, django-orm-lens nplusone, or django-orm-lens migration-risk.mcp tag (pip install "django-orm-lens[mcp]"). Start the server via django-orm-lens-mcp or django-orm-lens mcp. Configure DJANGO_ORM_LENS_ROOT (or workspace_root argument) to point at the Django project.docker run --rm -v "$PWD:/workspace" ghcr.io/frowningdev/django-orm-lens scan --path .factory_boy factories with Faker providers.runserver, works on broken venvs.Does the tool send my code anywhere? No. All parsing and rendering happen locally; no telemetry or external calls are made.
Can it work without a virtual environment or a database? Yes. The parser reads source files directly and never imports Django, so it works on any checkout, even with missing dependencies.
Will it handle models split across multiple files?
Yes, support for models/ packages has been available since v0.2.0.
What Python versions are supported? Python 3.9 and newer; Django 4.0‑5.2 are fully supported.
How do I configure the MCP server for an AI agent?
Install the extra mcp package, run django-orm-lens-mcp, and set DJANGO_ORM_LENS_ROOT (or pass workspace_root in the call). The server then listens for MCP requests.
How can I export the ER diagram in other formats?
Use django-orm-lens er -f dbml, -f d2, -f plantuml, or -f dot to generate DBML, D2, PlantUML, or Graphviz DOT files.
Can the extension be used in other Code‑derived editors? Yes. The VSIX works in VS Code, VSCodium, Code‑Server, Gitpod, and any Open VSX‑compatible fork.
What are the quick‑fix rules called?
They use codes DOL001 … DOL032. Rules can be disabled inline (# django-orm-lens-disable-next-line DOL007) or globally via VS Code settings.
How does the schema diff handle renames? It uses Levenshtein distance plus field‑shape Jaccard similarity to treat a rename as a distinct event rather than an add‑and‑drop.
Is there a JetBrains/PyCharm plugin? Not currently; the tool focuses on VS Code and terminal/AI workflows.
English · Русский · Español · 中文
Your entire model graph — live in your editor sidebar, gating your CI, and answering your AI agent over MCP. All from static parsing: no database, no runserver, no working venv.
Replaces: graph_models + django-schema-graph + hand-drawn ER diagrams + grep archaeology.
uvx django-orm-lens scan # or: pipx run django-orm-lens scan
Cold clone, broken venv, no settings module — you still get every app, model, field, and relation of the project in your terminal.
Then pick your surface — three distributions, one parser core:
| You are | Install | You get |
|---|---|---|
| Editor user — VS Code / Cursor / Windsurf / VSCodium | code --install-extension frowningdev.django-orm-lens |
Sidebar tree, live ER diagram, hover cards, 16 QuickFix rules |
| Terminal / CI user | pip install django-orm-lens |
13 subcommands, SARIF + PR annotations, pre-commit hooks, a GitHub Action |
| AI-agent user — Cursor / Claude Code / Aider / Zed / Continue | pip install "django-orm-lens[mcp]" |
10 read-only MCP tools answering schema questions from ground truth |
MCP setup is one JSON block — see Integrations. Point DJANGO_ORM_LENS_ROOT at your Django project's absolute path.
If the tool saves you a
grepnext time you touch a strange Django project — a star helps others find it.
VS Code / Cursor / Windsurf (VS Code Marketplace):
code --install-extension frowningdev.django-orm-lens
VSCodium / code-server / Gitpod / any OSS Code fork (Open VSX):
codium --install-extension frowningdev.django-orm-lens
Or search Django ORM Lens in the Extensions view — same publisher frowningdev on both registries.
Terminal & AI coding agents:
pip install django-orm-lens # CLI only
pip install "django-orm-lens[mcp]" # + MCP server for AI agents
Requires Python 3.9+. Zero runtime dependencies for the CLI.
Docker (v0.6+):
docker run --rm -v "$PWD:/workspace" ghcr.io/frowningdev/django-orm-lens scan --path .
Multi-arch (amd64 + arm64). No Python required on the host. Good for CI and one-off audits.
Works offline. Works on a broken venv. Works on someone else's laptop. Works in CI.
You open a Django project. It has 20 apps. You need to answer a simple question:
"Which app owns the
Ordermodel, and how is it connected toUser?"
Today, that means: Ctrl+P, "models", scroll through 30 hits, open five files, Ctrl+F for class Order, read through 400 lines of ForeignKey('otherapp.Something') strings, try to remember what you learned two files ago.
Half a day gone. Every time. On every project.
Every app → every model → every field → every Meta option. Grouped by application, sorted alphabetically, expandable.
Icons distinguish CharField from ForeignKey from ManyToManyField at a glance.
One command opens a Mermaid entity-relationship diagram of your entire schema. Watch it redraw as you edit. Export to SVG.
ForeignKey, OneToOneField, and ManyToManyField become proper cardinality arrows.
Hover over ForeignKey('app.Model') in any Python file → a card pops up with the target model's fields, relations, and a "Jump to" link. No Ctrl+F, no file dialog.
Click any field in the tree → cursor lands on the exact line. Filter the tree by app or model name. Split models/ packages are fully supported.
No DJANGO_SETTINGS_MODULE. No runserver. Parses models.py statically. Works with a broken venv, a missing dependency, or on someone else's laptop.
Dark theme. Light theme. Your theme. Follows your icon theme, your font, your key bindings. Nothing garish, nothing branded.
Static analysis over .py files with Ruff-style codes (DOL001..DOL032), Clippy-style Applicability, and per-rule severity overrides. .count() > 0 → .exists(), null=True on CharField, missing on_delete, datetime.now() → timezone.now() and a dozen more.
Suppress inline with # django-orm-lens-disable-next-line DOL007.
Right-click any model → factory_boy DjangoModelFactory scaffold with Faker providers keyed by field type. CharField(max_length) scales word-count buckets, DecimalField(N,D) computes left_digits=N-D, choices= maps to Iterator, M2M gets @post_generation. FK chains pull related factories transitively.
Also available as CodeLens above each model class.
Pick a models.py, pick two commits, get a typed diff as PR-ready markdown. AddModel / DropModel / RenameModel / ModifyModel events with confidence-scored rename detection (Levenshtein + field-shape Jaccard).
Renames are first-class events, never Add + Drop. Blob-SHA LRU cache — commits that don't touch models.py share their parsed snapshot.
"What breaks if I remove this field?" — right-click a field or model → workspace-wide scan grouped by Django layer (models, serializers, forms, admin, views, urls, templates, tests, migrations).
Findings carry a Certain / Likely / Possibly confidence tag. Handles ORM string refs (order_by("-author")), kwarg lookups (filter(author__id=1)), Meta.fields tuples, and template variables.
Right-click a field or model → pick a template → snippet inserted at cursor (with tab-stops) or in a fresh untitled buffer.
.filter(field=?) on an FK auto-appends .select_related(...), .annotate(post_count=Count('post_set')) honours related_name, .prefetch_related for M2M, .values('field').distinct(), .only('field').
Stable TreeItem.id — refresh no longer collapses the tree. Rich MarkdownString tooltips with command: deep-links. Activity-bar badge counts DOL### issues.
FileDecorationProvider badges: red ! on FK-without-on_delete, yellow ~ on null=True string fields (bubbles up to the parent Model row, Git-style).
Live sample — real django-orm-lens er output, rendered by GitHub right here:
Also included in the extension:
CASCADE, through Model, as related_name), theme-aware, one-click SVG exportForeignKey('app.Model') or ManyToManyField(...), with a one-click jump linkclass Model line: field count, relation count, and an Open ER diagram actionauto / default / dark / forest / neutral for the diagram webviewThe same parser that powers the VS Code extension ships as a standalone Python package — with an optional MCP (Model Context Protocol) server so any MCP-compatible AI agent can navigate your Django schema without importing Django or booting your app.
django-orm-lens scan -f json # every app, every model, every field
django-orm-lens describe blog.Post # one model in Markdown
django-orm-lens list | fzf # flat app.Model — pipes anywhere
django-orm-lens er > schema.mmd # ER diagram — Mermaid (default)
django-orm-lens er -f dbml > schema.dbml # …or DBML: paste into dbdiagram.io
django-orm-lens er -f d2 > schema.d2 # …or D2 / plantuml / dot
django-orm-lens diff before.json after.json # what a PR changes structurally
django-orm-lens nplusone --format github # N+1 findings as PR annotations
django-orm-lens migration-risk -f sarif # SARIF for GitHub Code Scanning
django-orm-lens suggest-indexes blog.Post # Meta.indexes proposals from usage
django-orm-lens signals # sender→signal→handler graph
django-orm-lens migration-deps blog -f mermaid # per-app migration DAG
django-orm-lens cascade blog.Author # what one delete() takes down
Every command accepts --path <dir> and --exclude <glob>. nplusone / migration-risk / diff exit code 1 on findings — drop them into CI to block PRs on regressions.
Register it once with your agent and it exposes ten read-only tools:
| Tool | Purpose |
|---|---|
list_apps |
Every Django app in the workspace with model counts |
list_models |
Flat app.Model list, optional app filter |
describe_model |
Full field / relation / Meta detail for one model |
find_relations |
Inbound + outbound relations for one model |
cascade_preview |
Blast radius of one delete(), grouped by on_delete |
er_diagram |
ER diagram — mermaid / dbml / d2 / plantuml / dot |
describe_migration_dependency |
Per-app migration DAG: roots, leaves, cross-app deps |
suggest_indexes |
Meta.indexes proposals from observed QuerySet usage |
signal_graph |
Sender→signal→handler graph from @receiver decorators |
nplusone_scan |
Static N+1 findings for the whole workspace |
# Start it directly
django-orm-lens-mcp
# Or via the CLI subcommand
django-orm-lens mcp
Workspace resolution (py-1.3.0+). Every tool accepts an optional
workspace_root argument on the call. Resolution priority: explicit arg →
$DJANGO_ORM_LENS_ROOT → current working directory. Invalid or non-Django
paths return a structured envelope
({"error": "WORKSPACE_NOT_DJANGO", "hint": "…"}) instead of empty results,
so the agent can self-correct. Optional sandbox via
DJANGO_ORM_LENS_ALLOWED_ROOTS (;-separated on Windows, : elsewhere).
Schema regressions are cheapest to catch the moment they enter a PR. Three zero-config ways to block them:
pre-commit — two hooks, nothing to install locally:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/FROWNINGdev/django-orm-lens
rev: py-v1.6.0
hooks:
- id: django-orm-lens-nplusone
- id: django-orm-lens-migration-risk
GitHub Action — findings appear as PR annotations with zero extra permissions:
- uses: FROWNINGdev/django-orm-lens@action-v1
with:
command: migration-risk # or: nplusone
format: github # ::error / ::warning annotations on the diff
SARIF → Code Scanning — findings land in the repo Security tab:
- run: |
pip install django-orm-lens
django-orm-lens migration-risk --format sarif --exit-zero > lens.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: lens.sarif
Exit codes are CI-native: diff and nplusone exit 1 on findings, migration-risk exits 1 on critical findings. Add --exit-zero for report-only mode.
| Client | How to enable | Status |
|---|---|---|
| VS Code | code --install-extension frowningdev.django-orm-lens |
✅ |
| Cursor | same VSIX + optional MCP entry in ~/.cursor/mcp.json |
✅ |
| Windsurf / VSCodium / any Code fork | install the VSIX from the Marketplace or GitHub Releases | ✅ |
| Aider | add django-orm-lens-mcp to your mcp.json |
✅ (via MCP) |
| Continue.dev | register the MCP server in ~/.continue/config.json |
✅ (via MCP) |
| Zed | register the MCP server in Zed settings | ✅ (via MCP) |
| Any MCP-compatible client | point command at django-orm-lens-mcp, set DJANGO_ORM_LENS_ROOT |
✅ |
| pre-commit | repo: https://github.com/FROWNINGdev/django-orm-lens + two hook ids |
✅ |
| GitHub Actions | uses: FROWNINGdev/django-orm-lens@action-v1 — annotations or SARIF |
✅ |
| Discoverable via MCP Registry | official Model Context Protocol server directory | ✅ |
| Plain terminal / CI | pip install django-orm-lens && django-orm-lens scan |
✅ |
{
"mcpServers": {
"django-orm-lens": {
"command": "django-orm-lens-mcp",
"env": { "DJANGO_ORM_LENS_ROOT": "/abs/path/to/your/project" }
}
}
}
The regression suite parses the vendored model graphs of Zulip, Saleor, Wagtail, django CMS, and Mezzanine — 59 models across 13,478 lines of real-world models.py — in about 20 ms end-to-end on a laptop (21 ms best-of-3 on the repo's golden-fixture corpus; a <2 s guard runs in CI on every matrix cell).
Reproduce it yourself:
git clone https://github.com/FROWNINGdev/django-orm-lens && cd django-orm-lens/cli
pip install -e . && python -m pytest tests/test_golden_fixtures.py tests/test_golden_snapshots.py -q
models.py sprawl.related_name?") without importing the project.runserver, no manage.py migrate, still works.Django ORM Lens sits at the intersection of editor tooling and AI-agent tooling — a slot no existing package covers:
| Segment | Existing option | What it costs you |
|---|---|---|
| Boot-and-graph | django-extensions graph_models |
Requires Graphviz + Django settings + a working DB URL |
| Web-based viewer | django-schema-graph |
Requires a running Django server; hosts one more thing to break |
| Admin panel | Django Admin | Requires runserver + auth + database — great for data, not for architecture |
| Editor plugin | PyCharm's Django Structure | Locked to PyCharm; no CLI, no AI-agent story |
| MCP server | (none until now) | AI agents guess your schema from source, imperfectly |
Django ORM Lens is the only tool that ships three surfaces from one parser: a VS Code extension (any Code fork), a zero-dep CLI (terminals + CI), and an MCP server (AI agents). All static. All free. All MIT.
| Django ORM Lens | django-extensions graph_models |
django-schema-graph |
Django Admin | PyCharm Django Structure | |
|---|---|---|---|---|---|
| Works without a bootable Django project | ✅ | ❌ | ❌ | ❌ | ⚠️ |
| Zero-install (no graphviz, no server) | ✅ | ❌ | ❌ | ❌ | ❌ (needs PyCharm) |
| Works in VS Code / Cursor / any Code fork | ✅ | ❌ | ❌ | ❌ | ❌ |
| Sidebar tree inside the editor | ✅ | ❌ | ❌ | ❌ | ✅ |
| Live ER diagram | ✅ | ✅ | ✅ | ❌ | ❌ |
Hover cards on ForeignKey |
✅ | ❌ | ❌ | ❌ | ⚠️ |
| CodeLens on model classes | ✅ | ❌ | ❌ | ❌ | ❌ |
Split models/ package support |
✅ | ⚠️ | ⚠️ | ✅ | ✅ |
| CLI for terminal / CI | ✅ | ⚠️ | ❌ | ❌ | ❌ |
| MCP server for AI agents | ✅ | ❌ | ❌ | ❌ | ❌ |
| Discoverable in the MCP Registry | ✅ | ❌ | ❌ | ❌ | ❌ |
| Free & open-source (MIT) | ✅ | ✅ | ✅ | ✅ | ❌ (paid IDE) |
| Django version support | 4.0 – 5.2 | latest | 3.2 – 4.1 (stale since 2023) | latest | latest |
django-schema-graphhas not been updated since 2023-05 and does not test Django 5.x.
Honest boundaries: profiling a live request → django-debug-toolbar. Historical request profiling → django-silk. Query-count assertions inside a test suite → django-perf-rec. Production APM on real traffic → Scout / Sentry. Django ORM Lens deliberately stays static — it's the layer that works before the app can even boot, and the only one your CI and your AI agent can use on any checkout.
The defaults are opinionated and sensible. If you need to tweak:
// .vscode/settings.json
{
"djangoOrmLens.excludeGlobs": [
"**/migrations/**",
"**/node_modules/**",
"**/venv/**",
"**/.venv/**",
"**/env/**"
],
"djangoOrmLens.autoRefresh": true
}
| Setting | Type | Default | What it does |
|---|---|---|---|
djangoOrmLens.excludeGlobs |
string[] |
See above | Glob patterns to skip when scanning |
djangoOrmLens.autoRefresh |
boolean |
true |
Rescan on models.py changes |
djangoOrmLens.codeFixes.enabled |
boolean |
true |
Master switch for the DOL### diagnostics + QuickFixes |
djangoOrmLens.rules |
object |
{} |
Per-rule severity: { "DOL007": "off", "DOL013": "error" } |
djangoOrmLens.rulesSelect |
string[] |
[] |
Ruff-style select. ["DOL0"] runs only queryset+model rules |
djangoOrmLens.rulesIgnore |
string[] |
[] |
Ruff-style ignore. ["DOL03"] silences form/view rules |
Sixteen editor-side checks (DOL001–DOL032) with Ruff-style codes, per-rule severity, and Clippy-style applicability — plus fifteen CLI-side migration-risk rules and the static N+1 analyzer. Every rule now has its own documentation page.
| Category | Rules | Examples |
|---|---|---|
| Queryset | DOL001–DOL007 |
.count() > 0 → .exists(), FK access in loops (N+1) |
| Model definition | DOL011–DOL015 |
ForeignKey without on_delete, null=True on string fields |
| Datetime | DOL021–DOL022 |
datetime.now() → timezone.now() |
| Forms / views | DOL031–DOL032 |
locals() in render(), Meta.fields = '__all__' |
| Migration risks | 16 rules | NOT NULL add without default, table-locking index builds, irreversible data migrations |
| Static N+1 | 1 analyzer | FK/M2M access in loops without select_related / prefetch_related |
→ Full rule reference — every code with bad/good examples, QuickFix behaviour, and suppression syntax.
# django-orm-lens-disable-next-line DOL007
for user in User.objects.all():
print(user.profile) # not flagged
qs.count() > 0 # django-orm-lens-disable-line DOL001
# django-orm-lens-disable DOL011 ← on its own line, kills DOL011 for the rest of the file
Applicability follows Rust's Clippy: safe fixes can be applied automatically ("Fix All"), suggestion fixes are offered as a QuickFix but reviewed, unsafe findings never auto-apply. Fixes are separated from analyzers (Roslyn-style), so one rule can grow multiple fixers over time without touching detection logic.
Open the command palette (Ctrl+Shift+P / Cmd+Shift+P) and type "Django ORM Lens":
| Command | What it does |
|---|---|
Django ORM Lens: Refresh |
Force-rescan the workspace |
Django ORM Lens: Show ER Diagram |
Open the Mermaid ER diagram side-by-side |
Django ORM Lens: Filter Models |
Filter the tree by app / model / field name |
Django ORM Lens: Clear Filter |
Restore the full tree |
Django ORM Lens: Jump to Model |
Programmatic — triggered by tree clicks and hover cards |
Django ORM Lens: Find Reverse References |
Right-click a model — QuickPick of every FK pointing at it |
Django ORM Lens: Generate factory_boy Factory |
Right-click a model or use CodeLens — scaffold a DjangoModelFactory |
Django ORM Lens: Schema Diff (Time-Travel) |
Pick two commits — get a typed diff as a markdown buffer |
Django ORM Lens: Find Impact (What Uses This?) |
Right-click a field or model — workspace-wide reference scan |
Django ORM Lens: Build Query (Insert Snippet) |
Right-click a field or model — pick an ORM template |
Shipped
ForeignKey('app.Model')models/ package supportN fields · N relations · Open ER diagram)CASCADE, SET_NULL, PROTECT, related_name)auto / default / dark / forest / neutral)through_model on M2M edges (contributed by @kingrubic)nplusone — static N+1 detector (FK/M2M access inside loops without select_related/prefetch_related)migration-risk — flags risky operations in migrations/*.py (15 rules today)diff — compare two schema JSON dumps for PR reviewdocker run ghcr.io/frowningdev/django-orm-lenssettings.AUTH_USER_MODEL resolves everywhere: n+1 reverse-relations, signal senders, Mermaid ER, VS Code webview, inbound-relation panel, React ERForeignKey(on_delete=CASCADE, to='User') resolves regardless of kwarg order (Python + TS parity)find_user_model, resolve_related_tail, find_model, iter_workspace_py_files (Python) + findUserModel, resolveRelatedTail (TS)--verbose no longer walks the tree twice; WorkspaceIndex.scanned_files carries the countjti: CharField[str] = models.CharField(...)) now parse — reported by @jsabater (#25) with a clean Django Ninja 1.6 reproclass Container[T](models.Model): now parsesfrom django.db import models as m) and third-party field packages (jsonfield.JSONField) now detectedDOL001..DOL032) with per-rule severity + Ruff-style select/ignore + inline # django-orm-lens-disable-next-linefactory_boy scaffold from any model with Faker providers keyed by field type.select_related, related_name honoured)TreeItem.id, MarkdownString tooltips with command: deep-links, FileDecorationProvider badges, TreeView.badge on the activity bar, three when-gated viewsWelcome statesv1.5.0 — the "one core, three surfaces" wave
--format github PR annotations for nplusone and migration-risksuggest-indexes, signals, migration-deps, cascadeer --format dbml | d2 | plantuml | dot — community-standard diagram exports (dbdiagram.io, D2, PlantUML, Graphviz — dot contributed by @JJordan0C)runpython_no_reverse, alter_unique_together_lock, alter_index_together_deprecated — 15 totaldjango-orm-lens-nplusone, django-orm-lens-migration-risk) + composite GitHub Actiondocs/rules/ — a documentation page for every rule (19 pages)migration-deps (text / json / mermaid)Next
.filter() / .exclude() / .annotate() (#3)Later
django-mptt, django-taggit, django-model-utils)Vote by 👍-ing the corresponding issue.
models.py snippet)MIT © FROWNINGdev
Made for developers who care about their codebase.
Marketplace · PyPI · GitHub · Issues · Discussions · Sponsor
Please log in to share your review and rating for this MCP.
Explore related MCPs that share similar capabilities and solve comparable challenges
by modelcontextprotocol
A Model Context Protocol server for Git repository interaction and automation.
by zed-industries
A high‑performance, multiplayer code editor designed for speed and collaboration.
by modelcontextprotocol
Model Context Protocol Servers
by modelcontextprotocol
A Model Context Protocol server that provides time and timezone conversion capabilities.
by cline
An autonomous coding assistant that can create and edit files, execute terminal commands, and interact with a browser directly from your IDE, operating step‑by‑step with explicit user permission.
by upstash
Provides up-to-date, version‑specific library documentation and code examples directly inside LLM prompts, eliminating outdated information and hallucinated APIs.
by daytonaio
Provides a secure, elastic infrastructure that creates isolated sandboxes for running AI‑generated code with sub‑90 ms startup, unlimited persistence, and OCI/Docker compatibility.
by continuedev
Enables faster shipping of code by integrating continuous AI agents across IDEs, terminals, and CI pipelines, offering chat, edit, autocomplete, and customizable agent workflows.
by github
Connects AI tools directly to GitHub, enabling natural‑language interactions for repository browsing, issue and pull‑request management, CI/CD monitoring, code‑security analysis, and team collaboration.