English · Русский · Español · 中文
Power your agent
คำสั่งของผู้เผยแพร่เอง ไม่ใช่เอนด์พอยต์ของ PlugMCP
Host a copy with our keys and logs.
uvx django-orm-lens==1.13.5Claude Code ไม่มีลิงก์ติดตั้งสำหรับคำสั่งแบบโลคัล ให้คัดลอกคำสั่งด้านล่างแล้วรันในเทอร์มินัล
claude mcp add frowningdev-django-orm-lens -- uvx django-orm-lens==1.13.5รันในเทอร์มินัล
{ "mcpServers": { "frowningdev-django-orm-lens": { "command": "uvx", "args": [ "django-orm-lens==1.13.5" ] } }}claude_desktop_config.json
URL แบบรีโมตหรือคำสั่งบนเครื่องของคุณ? การติดตั้งสองแบบต่างกันอย่างไร
รันบน PlugMCP
A new server you create. It is not this listing's official endpoint. Use the publisher install when you want their URL and their credentials.
เปิด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
uvx django-orm-lens scan -f table # every app and model at a glance
uvx django-orm-lens nplusone # N+1 loops, with the select_related to add
uvx django-orm-lens migration-risk # migrations that lock tables or fail on existing rows
Cold clone, broken venv, no settings module — you still see every app and model of the project, then the N+1 loops and risky migrations in it, right in your terminal. (uvx is from uv; pipx run works the same way.)
Then pick your surface — three distributions, one parser core:
MCP setup is one JSON block — see Integrations. Point DJANGO_ORM_LENS_ROOT at your Django project's absolute path.
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:
There is no Pro tier, and none is planned. If the tool saves you an afternoon, a star is the entire ask.
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.
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.
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.
Static analysis over .py files with Ruff-style codes (DOL001..DOL041), Clippy-style Applicability, and per-rule severity overrides. .count() > 0 → .exists(), null=True on CharField, missing on_delete, datetime.now() → timezone.now(), planner-GUC overrides in raw SQL (enable_*, plan_cache_mode, jit*; diagnostic-only), 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:
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:
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
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.
Register it once with your agent and it exposes thirteen read-only tools:
# 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).
The tools tell an agent what is true; the skill tells it what to do with
that. skills/django-orm-lens is a SKILL.md
package with playbooks for migration review, safe renames and removals,
N+1 fixes and cascade checks, a safe rewrite for each of the 16 migration
rules, and audit.py for a one-command verdict (BLOCK / FIX BEFORE DEPLOY /
SHIP). It uses the MCP server when connected and falls back to the CLI via
uvx.
git clone --depth 1 https://github.com/FROWNINGdev/django-orm-lens
cp -r django-orm-lens/skills/django-orm-lens ~/.claude/skills/
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.13.6
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.
{
"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:
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-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
}
Eighteen editor-side checks (DOL001–DOL041) with Ruff-style codes, per-rule severity, and Clippy-style applicability — plus sixteen CLI-side migration-risk rules and the static N+1 analyzer. Every rule now has its own documentation page.
→ 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":
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)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 --check without booting Djangoimpact <name> — what still references a model or field, grouped by Django layerblast-radius --stats + stats-sql — optional production row counts from read-only SQL you run yourself (the tool never holds a DB credential)nplusone resolves across functions — a queryset returned by a helper is followed into the loop that consumes itblast_radius, drift and impact exposed as MCP tools — thirteen tools for AI agentsdrift documents its !! / ~ marks in the report and in --help — reported by @sevdog (#57)drift follows 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-index recognises the indexes Django already made — primary key (pk and id are one lookup), db_index, unique, foreign keys, unique_together, UniqueConstraint — reported by @sevdog (#60), same cause independently found by @RinZ27 (#61)TaggableManager is read as the M2M it is — through taggit.TaggedItem to taggit.Tag, through= overrides honoured, in both the Python and the TypeScript parser — contributed by @Guflly (#63, closing #50)DOL021 states the USE_TZ default correctly — False through Django 4.2, True from 5.0, with the startproject template's USE_TZ = True since 4.0 called out as the separate thing it is — and no longer claims timezone.now() is always aware UTC — found by @Justine0211 while translating the page (#52)parity_input.py fixture carries the models import a real models.py would 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.
if not is_model_registered(...): and then an indented class) that every pluggable Django framework uses, against class discovery anchored on ^class. django-oscar went from 12 models, every one of them from its own tests/ directory, to 82. All six golden snapshots stayed byte-identical: a column-0 class parses exactly as beforeabstract_models.py is read alongside models.py — pluggable frameworks keep the abstract base there and leave models.py holding 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 tabledrift no 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 blocksMPTTModel is a recognised base, and TreeForeignKey / TreeOneToOneField / TreeManyToManyField are reported as the Django fields they subclass, so TreeForeignKey('self', ...) draws exactly the self-edge a plain ForeignKey('self', ...) does. No django-mptt dependency is added — the parser keeps working against a broken venv. Saleor's product.Category and its children edge now appear in the golden snapshot: 76 added lines, none removed (closing #49)FastMCP forwards none, so the SDK fell back to importlib.metadata.version("mcp") and every initialize response named the wrong project's release number to the clientv0.9 → v0.12.1 — the extension catches up
UniqueConstraint tracking in Time-Travel Schema Diff: add / drop / change / rename as typed events, with fromCondition carrying the pre-change predicate so a review comment can show Q(is_primary=True) → Q(is_primary=True, deleted=False), and a rename no longer showing up as a lossy add + drop pair. Multiple unnamed constraints on one model key onto #anon-<index> instead of collapsing into a single event. Prompted by django-extensions #1813, where sqldiff drops the condition= predicate so migration reviewers never see what changed/tests/ and /views.py anywhere in a file's absolute path, so a project checked out under any directory called tests — or a monorepo with services/tests/ above it — had every file reported as that layer, views.py as a test, admin.py as 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 at migrate timetoSvg returns percent-encoded markup where toPng returns base64; the save path assumed base64 for anything starting with data:, 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 prefixNext
.filter() / .exclude() / .get() (#3)Later
django-model-utils (django-taggit shipped in py-1.11.0, django-mptt in py-1.12.0 / v0.11.0)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
Django ER diagrams, N+1 detection, schema drift & migration-risk linting — in VS Code, a CLI and an MCP server. Static analysis: no database, no django.setup(). Free & MIT.
{ "mcpServers": { "frowningdev-django-orm-lens": { "command": "uvx", "args": [ "django-orm-lens==1.13.5" ] } }}เครื่องมือ
ยังไม่มีรายการเครื่องมือ เซิร์ฟเวอร์แบบรีโมตจะได้รายการหลังการตรวจสอบสถานะครั้งแรก
FAQ
คัดลอกคอนฟิก stdio สำหรับ Cursor, Claude Desktop, Claude Code หรือ VS Code ได้ในหน้านี้ คอนฟิกนี้ใช้การติดตั้งของผู้เผยแพร่ ไม่ใช่พร็อกซีของ PlugMCP
ระเบียนนี้ไม่ได้ระบุตัวแปรสภาพแวดล้อมที่จำเป็น
Django Orm Lens ทำงานแบบโลคัลผ่าน stdio
ตรวจสอบล่าสุดเมื่อ 2026-10-28 Django ER diagrams, N+1 detection, schema drift & migration-risk linting — in VS Code, a CLI and an MCP server. Static analysis: no database, no django.setup(). Free & MIT.