pypi django-orm-lensstdioMITupdated 8d ago
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.
What can you do with Django ORM Lens?
English Β· Π ΡΡΡΠΊΠΈΠΉ Β· EspaΓ±ol Β· δΈζ
Django ORM Lens
The schema intelligence layer for Django.
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.
Featured in Django News #347 Β· PyCoder's Weekly #746
β‘ 10 seconds to first insight
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 |
Field autocomplete in .filter(), sidebar tree, live ER diagram, hover cards, 17 QuickFix rules |
| Terminal / CI user | pip install django-orm-lens |
17 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]" |
13 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.
π Paid-tier capabilities, free and MIT
Schema review is a paid category nearly everywhere. A bot that reviews every pull request, analysis that follows a queryset past the function it was built in, a check that catches schema drift, index advice grounded in real table statistics β those normally sit behind a per-seat or per-database subscription.
All of it is here, MIT-licensed, with no tier gate, no seat count, no account, and no telemetry:
| Capability usually sold as a paid tier | Here |
|---|---|
| PR review bot for schema changes β posts once, then updates in place | blast-radius + the Action |
| Analysis that follows a queryset across functions | nplusone |
| Schema drift detection | drift |
| Index proposals from observed QuerySet usage | suggest-indexes |
| Migration risk weighed against real table sizes | blast-radius --stats |
| Blast radius of a destructive migration | blast-radius |
| Cross-layer impact of removing a field | impact |
There is no Pro tier, and none is planned. If the tool saves you an afternoon, a star is the entire ask.
π Traction
If the tool saves you a
grepnext time you touch a strange Django project β a star helps others find it.
π Star growth
β‘ Install
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.
π― The problem
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.
β¨ With Django ORM Lens
π A tree of everything
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.
πΈοΈ A live ER diagram
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 for relations
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.
π§ Jump-to-definition
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.
β‘ Zero configuration
No DJANGO_SETTINGS_MODULE. No runserver. Parses models.py statically. Works with a broken venv, a missing dependency, or on someone else's laptop.
π¨ Native VS Code UI
Dark theme. Light theme. Your theme. Follows your icon theme, your font, your key bindings. Nothing garish, nothing branded.
π Power features
π₯ Blast radius
The review-time question a schema change actually raises: what does this hit? Every destructive migration operation becomes a target carrying its risks, every place in the codebase that still reads it, and β for whole-model operations β the cascade fallout.
migration-risk, impact and cascade each answer a third of that; nobody joins them by hand, so the tool does. --format markdown is a postable PR comment; --stats turns "probably populated" into ~41 000 000 rows, 12.0 GB from a read-only query you run yourself, with no database credential anywhere near CI.
π§ Schema drift
makemigrations --check without booting Django. Each app's migrations are replayed in order into the field set they imply, then compared against what models.py declares.
Django's own check needs a working settings module, an importable app registry and every dependency installed β unavailable on a cold clone or a broken venv, which is exactly when the answer is cheapest to act on. Only the dangerous direction fails the build: a field declared but never migrated means the column will not exist, and the first query touching it errors.
π― Inline QuickFixes (17 rules)
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.
π§ͺ Factory generator
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.
π° Time-Travel Schema Diff
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.
π Impact analysis
"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.
β‘ Interactive query builder
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').
π¨ Sidebar UX overhaul
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).
πΈ What it looks like
Live sample β real django-orm-lens er output, rendered by GitHub right here:
erDiagram
User {
CharField display_name
}
Tag {
CharField name
}
Post {
CharField title
DateTimeField created_at
}
Comment {
TextField body
}
Post }o--|| User : "author [CASCADE, as posts]"
Post }o--o{ Tag : "tags [as posts]"
Comment }o--|| Post : "post [CASCADE, as comments]"
Comment }o--|| User : "author [SET_NULL]"
Also included in the extension:
- πΈοΈ Live ER diagram β Mermaid cardinality arrows, edge labels (
CASCADE,through Model,as related_name), theme-aware, one-click SVG export - π Hover cards β over any
ForeignKey('app.Model')orManyToManyField(...), with a one-click jump link - π§ CodeLens β above every
class Modelline: field count, relation count, and an Open ER diagram action - π¨ Named themes β
auto/default/dark/forest/neutralfor the diagram webview
π€ For terminals and AI coding agents
The 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.
CLI
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
django-orm-lens impact author # what still references a field
django-orm-lens blast-radius -f markdown # risks + who still reads them
django-orm-lens drift # migrations vs models, no boot
django-orm-lens stats-sql # read-only SQL for --stats
impact,blast-radius,driftandstats-sqlship in py-1.7.0 and later.
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.
MCP server
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).
π‘οΈ Gate your CI
Schema regressions are cheapest to catch the moment they enter a PR. Four zero-config ways to block them:
Blast-radius PR bot β the whole schema review as one comment, updated in place on every push instead of a new comment each time:
name: Schema review
on: pull_request
permissions:
contents: read
pull-requests: write # only for `comment: true`
jobs:
blast-radius:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: FROWNINGdev/django-orm-lens@action-v1
with:
command: blast-radius
only-changed: true # scope to migrations this PR touches
comment: true # post once, then update in place
github-token: ${{ github.token }}
The comment goes up before the job fails, so a blocked PR still explains why. only-changed reads the PR's file list from the API rather than git diff, because actions/checkout defaults to fetch-depth: 1 and the base commit is not in the local history. On push events both flags skip with a notice instead of failing, so one workflow covers both triggers.
The Action installs from PyPI, so
blast-radiusanddriftneed py-1.7.0 or later β pin it withversion: 1.7.0if your workflow must not drift. To run an unreleased build instead, addinstall: falseand install the source yourself; this repo's own workflow does exactly that, and is what verifies the Action on every PR.
pre-commit β two hooks, nothing to install locally:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/FROWNINGdev/django-orm-lens
rev: py-v1.8.1
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 and blast-radius exit 1 on critical findings, drift exits 1 when a field is declared but never migrated. Add --exit-zero for report-only mode.
π Integrations
| 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 |
β |
Example: Cursor / any MCP client
{
"mcpServers": {
"django-orm-lens": {
"command": "django-orm-lens-mcp",
"env": { "DJANGO_ORM_LENS_ROOT": "/abs/path/to/your/project" }
}
}
}
β‘ Performance
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
π― Who this is for
- Django developers joining a codebase with 10+ apps and getting lost in
models.pysprawl. - Contract / freelance engineers who need to grasp an unfamiliar Django project in the first hour, not the first week.
- Teams onboarding new hires who want a one-glance schema view without spinning up documentation infrastructure.
- AI-agent power users (Cursor / Aider / Zed / Continue / any MCP-compatible client) who need the agent to answer schema questions accurately β without giving it database credentials or booting Django.
- CI pipelines that verify schema shape (e.g. "did we accidentally break a
related_name?") without importing the project. - Solo indie devs on a broken venv or someone else's laptop β no
runserver, nomanage.py migrate, still works.
πΊοΈ Market position
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.
π€ How is this different?
| 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.
When you want something else
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.
βοΈ Configuration
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 |
π¬ Rule catalogue
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.
Suppress inline
# 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.
π§ Commands
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 |
πΊοΈ Roadmap
Shipped
- Sidebar tree grouped by app
- Live Mermaid ER diagram
- Hover cards over
ForeignKey('app.Model') - Filter tree by name
- Split
models/package support - Export ER diagram as SVG
- Python CLI + MCP server for terminals and AI agents
- Welcome view for empty workspaces
- Path-safe jump-to-definition and sanitized hover markdown
- v0.3.0 β CodeLens above each model class (
N fields Β· N relations Β· Open ER diagram) - v0.3.0 β Edge labels on the diagram (
CASCADE,SET_NULL,PROTECT,related_name) - v0.3.0 β Named color themes (
auto/default/dark/forest/neutral) - v0.3.1 β
through_modelon M2M edges (contributed by @kingrubic) - v0.3.1 β Listed in the official MCP Registry + Glama.ai
- v0.6.0 β CLI
nplusoneβ static N+1 detector (FK/M2M access inside loops withoutselect_related/prefetch_related) - v0.6.0 β CLI
migration-riskβ flags risky operations inmigrations/*.py(15 rules today) - v0.6.0 β CLI
diffβ compare two schema JSON dumps for PR review - v0.6.0 β ER-diagram minimap color-codes nodes by Django app
- v0.6.0 β README translations: π·πΊ Russian, πͺπΈ Spanish, π¨π³ Chinese
- v0.6.0 β Docker image on GHCR:
docker run ghcr.io/frowningdev/django-orm-lens - v0.7.0 β
settings.AUTH_USER_MODELresolves everywhere: n+1 reverse-relations, signal senders, Mermaid ER, VS Code webview, inbound-relation panel, React ER - v0.7.0 β AST-based field parser:
ForeignKey(on_delete=CASCADE, to='User')resolves regardless of kwarg order (Python + TS parity) - v0.7.0 β Public shared helpers:
find_user_model,resolve_related_tail,find_model,iter_workspace_py_files(Python) +findUserModel,resolveRelatedTail(TS) - v0.7.0 β
--verboseno longer walks the tree twice;WorkspaceIndex.scanned_filescarries the count - v0.7.3 β PEP-526 type annotations on fields (
jti: CharField[str] = models.CharField(...)) now parse β reported by @jsabater (#25) with a clean Django Ninja 1.6 repro - v0.7.4 β PEP-695 generic class headers (Python 3.12+):
class Container[T](models.Model):now parses - v0.7.5 β Aliased models module (
from django.db import models as m) and third-party field packages (jsonfield.JSONField) now detected - v0.7.6 β Tab-indented model bodies now parse (editors defaulting to tabs no longer show empty models)
- v0.8.0 β Inline QuickFixes: 16 rules (
DOL001..DOL032) with per-rule severity + Ruff-style select/ignore + inline# django-orm-lens-disable-next-line - v0.8.0 β Factory generator:
factory_boyscaffold from any model with Faker providers keyed by field type - v0.8.0 β Time-Travel Schema Diff: pick two commits β typed markdown diff with first-class rename detection
- v0.8.0 β Impact analysis: workspace-wide field-reference scan across every Django layer with Certain/Likely/Possibly confidence tags
- v0.8.0 β Interactive Query Builder: right-click β template β snippet inserted at cursor, grammar-aware (FK gets
.select_related,related_namehonoured) - v0.8.0 β Sidebar UX overhaul: stable
TreeItem.id,MarkdownStringtooltips withcommand:deep-links,FileDecorationProviderbadges,TreeView.badgeon the activity bar, three when-gatedviewsWelcomestates
v1.5.0 β the "one core, three surfaces" wave
- CI formats: SARIF 2.1.0 +
--format githubPR annotations fornplusoneandmigration-risk - Four analyzers promoted from MCP-only to the CLI:
suggest-indexes,signals,migration-deps,cascade -
er --format dbml | d2 | plantuml | dotβ community-standard diagram exports (dbdiagram.io, D2, PlantUML, Graphviz βdotcontributed by @JJordan0C) - Three new migration-risk rules:
runpython_no_reverse,alter_unique_together_lock,alter_index_together_deprecatedβ 15 total - pre-commit hooks (
django-orm-lens-nplusone,django-orm-lens-migration-risk) + composite GitHub Action -
docs/rules/β a documentation page for every rule (19 pages) - Golden-snapshot regression suite over 59 real-world models (Zulip / Saleor / Wagtail / django CMS / Mezzanine); ruff + mypy now gate CI
- Migration dependency graph β
migration-deps(text / json / mermaid)
py-1.7 β 1.8 β the schema-intelligence wave
-
blast-radiusβ migration risks joined with what still reads the schema they touch, as a PR bot (comment: true, sticky,only-changed) -
driftβmakemigrations --checkwithout booting Django -
impact <name>β what still references a model or field, grouped by Django layer -
blast-radius --stats+stats-sqlβ optional production row counts from read-only SQL you run yourself (the tool never holds a DB credential) -
nplusoneresolves across functions β a queryset returned by a helper is followed into the loop that consumes it -
blast_radius,driftandimpactexposed as MCP tools β thirteen tools for AI agents -
driftdocuments its!!/~marks in the report and in--helpβ reported by @sevdog (#57) -
driftfollows inheritance from abstract bases β an abstract base's fields count as the concrete child's own, as Django treats them β reported by @sevdog (#58) -
suggest-indexrecognises the indexes Django already made β primary key (pkandidare one lookup),db_index,unique, foreign keys,unique_together,UniqueConstraintβ reported by @sevdog (#60), same cause independently found by @RinZ27 (#61) - A sixth golden fixture β Read the Docs joins Zulip, Saleor, Wagtail, django-CMS and Mezzanine, putting the parser under 75 models and 538 fields of real-world Django β contributed by @JJordan0C (#62, closing #51)
- django-taggit
TaggableManageris read as the M2M it is β throughtaggit.TaggedItemtotaggit.Tag,through=overrides honoured, in both the Python and the TypeScript parser β contributed by @Guflly (#63, closing #50) -
DOL021states theUSE_TZdefault correctly βFalsethrough Django 4.2,Truefrom 5.0, with thestartprojecttemplate'sUSE_TZ = Truesince 4.0 called out as the separate thing it is β and no longer claimstimezone.now()is always aware UTC β found by @Justine0211 while translating the page (#52) - The
parity_input.pyfixture carries themodelsimport a realmodels.pywould have β contributed by @RinZ27 (#64)
py-1.9 β 1.12 β the real-checkout wave
Found by running the CLI over actual checkouts of django-oscar, django-guardian, django-allauth and django-cms rather than over fixtures. Every one of these was invisible to a green test suite, and two of them made the tool answer confidently with something false.
- Models declared inside a module-level block parse β the swappable-model idiom (
if not is_model_registered(...):and then an indentedclass) that every pluggable Django framework uses, against class discovery anchored on^class. django-oscar went from 12 models, every one of them from its owntests/directory, to 82. All six golden snapshots stayed byte-identical: a column-0 class parses exactly as before -
abstract_models.pyis read alongsidemodels.pyβ pluggable frameworks keep the abstract base there and leavemodels.pyholding only the concrete subclass, so 72 of django-oscar's 83 models reported zero fields between them. Now 8, and those 8 are correct: they subclass concrete models, where multi-table inheritance leaves the columns on the parent's table -
driftno longer fails a build over two app directories sharing a name β replayed migration state is merged per app name, matching how the declared side is already keyed. On a real django-guardian checkout the blocking count goes 1 β 0 and the contradictory duplicate row disappears, while a genuinely unmigrated field still blocks - django-mptt models are no longer invisible β
MPTTModelis a recognised base, andTreeForeignKey/TreeOneToOneField/TreeManyToManyFieldare reported as the Django fields they subclass, soTreeForeignKey('self', ...)draws exactly the self-edge a plainForeignKey('self', ...)does. Nodjango-mpttdependency is added β the parser keeps working against a broken venv. Saleor'sproduct.Categoryand itschildrenedge now appear in the golden snapshot: 76 added lines, none removed (closing #49) - The MCP server reports its own version β
FastMCPforwards none, so the SDK fell back toimportlib.metadata.version("mcp")and everyinitializeresponse named the wrong project's release number to the client
v0.9 β v0.12.1 β the extension catches up
- v0.9.0 β Partial
UniqueConstrainttracking in Time-Travel Schema Diff:add/drop/change/renameas typed events, withfromConditioncarrying the pre-change predicate so a review comment can showQ(is_primary=True) β Q(is_primary=True, deleted=False), and a rename no longer showing up as a lossyadd + droppair. Multiple unnamed constraints on one model key onto#anon-<index>instead of collapsing into a single event. Prompted by django-extensions #1813, wheresqldiffdrops thecondition=predicate so migration reviewers never see what changed - v0.10.0 β Impact-analysis layer detection runs on the workspace-relative path. It used to match
/tests/and/views.pyanywhere in a file's absolute path, so a project checked out under any directory calledtestsβ or a monorepo withservices/tests/above it β had every file reported as that layer,views.pyas a test,admin.pyas a test. Also: webview messages validated by origin rather than by source, and conflicting leaf migrations detected β two migrations claiming the same parent, which Django only complains about atmigratetime - v0.10.1 β The Marketplace listing names impact analysis, blast radius and schema drift, and says plainly that the tool is free and MIT with no Pro tier. The store page had still described the extension as a sidebar and an ER diagram β what it was two waves earlier β so nobody searching for those features found it. Metadata takes effect only on publish, which is why it needed a release of its own
- v0.11.0 β The TypeScript half of django-mptt support, cut as its own release rather than folded into a later one: between py-1.12.0 shipping and this build, the CLI and the extension disagreed about what a django-mptt schema contains, which is the exact failure the shared golden fixture exists to prevent
- v0.12.0 β The extension asks for a GitHub star, on the third user-initiated ER-diagram open. Not on install: a prompt arriving before the tool has done anything gets dismissed reflexively, and that dismissal is permanent in the user's mind. Sidebar refreshes that re-render an already-open panel are not counted β they are not the user asking for anything. "Later" and "Don't ask again" are stored as separate states, so a deferral re-arms the ask exactly once, twelve opens later; two prompts is the lifetime maximum. The policy is a pure function covered by six tests that need no VS Code host
- v0.12.1 β Export as SVG wrote an unopenable file.
toSvgreturns percent-encoded markup wheretoPngreturns base64; the save path assumed base64 for anything starting withdata:, and base64-decoding percent-encoded text does not fail β the decoder silently drops every character outside its alphabet and returns bytes. A 48-character<svg>document reached disk with the right name, a plausible size and no valid content anywhere in it. The transfer encoding is now read from the data-URL header instead of guessed from the prefix
Next
- ORM query autocomplete inside
.filter()/.exclude()/.annotate()(#3) - App / model toggle checkboxes to declutter huge schemas
- DOL rule engine ported into the Python CLI β one rule catalogue, three surfaces
Later
- Third-party field support β
django-model-utils(django-taggitshipped in py-1.11.0,django-mpttin py-1.12.0 / v0.11.0) - JetBrains / PyCharm plugin (if there is demand)
Vote by π-ing the corresponding issue.
β FAQ
π Support
- π Bug reports β GitHub Issues (please include a minimal
models.pysnippet) - π‘ Feature requests / ideas β GitHub Discussions
- π Marketplace reviews β rate the extension (the fastest signal that keeps this project moving)
- π PyPI page β pypi.org/project/django-orm-lens
- π Sponsor β github.com/sponsors/FROWNINGdev
π License
MIT Β© FROWNINGdev
Made for developers who care about their codebase.
Marketplace Β· PyPI Β· GitHub Β· Issues Β· Discussions Β· Sponsor
Install
Add Django ORM Lens to your client. Pick the one you use.
claude mcp add django-orm-lens -- uvx django-orm-lenscodex mcp add django-orm-lens -- uvx django-orm-lensamp mcp add django-orm-lens -- uvx django-orm-lens{
"mcpServers": {
"django-orm-lens": {
"command": "uvx",
"args": [
"django-orm-lens"
]
}
}
}Add to `claude_desktop_config.json`, then restart Claude Desktop.
{
"mcpServers": {
"django-orm-lens": {
"command": "uvx",
"args": [
"django-orm-lens"
]
}
}
}Add to `~/.cursor/mcp.json`, or `.cursor/mcp.json` for a single project.
code --add-mcp '{"name":"django-orm-lens","command":"uvx","args":["django-orm-lens"]}'Or add the block manually to `.vscode/mcp.json` under `servers`.
{
"mcpServers": {
"django-orm-lens": {
"command": "uvx",
"args": [
"django-orm-lens"
]
}
}
}Add to `~/.codeium/windsurf/mcp_config.json`.
{
"mcpServers": {
"django-orm-lens": {
"command": "uvx",
"args": [
"django-orm-lens"
]
}
}
}Add to `cline_mcp_settings.json` via the MCP Servers panel.
{
"mcpServers": {
"django-orm-lens": {
"command": "uvx",
"args": [
"django-orm-lens"
]
}
}
}Add to `~/.gemini/settings.json`.
{
"mcpServers": {
"django-orm-lens": {
"type": "local",
"command": "uvx",
"args": [
"django-orm-lens"
],
"tools": [
"*"
]
}
}
}Add to `~/.copilot/mcp-config.json`, or run `/mcp add` inside the CLI.
{
"context_servers": {
"django-orm-lens": {
"command": {
"path": "uvx",
"args": [
"django-orm-lens"
]
}
}
}
}Add to your Zed `settings.json`.
uvx django-orm-lensRun `goose configure`, choose **Add Extension β Command-line Extension**, and paste this command.
Score
39 / 100
Incomplete
- Documentation25/25
- Maintenance25/25
- Trust13/20
- Capability0/15
- Install experience12/15
- Documents what it does and how to connect
- Has a resolvable package or endpoint
- Exposes at least one tool, prompt or resource
- README has substantive content
- Includes a code example
- Documents its configuration
- Mentions credentials or security posture
- Last commit 1 days ago
- Has a release history
- Repository is not archived
- Licensed MIT
- Namespace verified in the official MCP registry
- Claimed by its owner
- Published under an organisation
- 0 tool(s) documented
- Provides prompt templates
- Provides resources
- 12 documented install method(s)
- Published to a package registry
- Offers a hosted endpoint β no local install
Version history
| Versions | Published |
|---|---|
| 1.13.0Latest | Aug 20, 2026 |
| 1.12.0 | Aug 12, 2026 |
| 1.11.0 | Jul 31, 2026 |
| 1.10.0 | Jul 30, 2026 |
| 1.9.0 | Jul 30, 2026 |
| 1.8.1 | Jul 29, 2026 |
| 1.8.0 | Jul 29, 2026 |
| 1.7.1 | Jul 29, 2026 |
| 1.7.0 | Jul 28, 2026 |
| 1.6.0 | Jul 28, 2026 |
| 1.5.1 | Jul 28, 2026 |
| 1.5.0 | Jul 27, 2026 |
| 1.4.0 | Jul 25, 2026 |
| 1.3.0 | Jul 24, 2026 |
| 1.2.7 | Jul 24, 2026 |
| 1.0.7 | Jul 13, 2026 |
| 1.0.5 | Jul 13, 2026 |
| 1.0.4 | Jul 13, 2026 |
| 1.0.3 | Jul 12, 2026 |