Static N+1 query detection for Django (LSP-based)
django-check is a static analyzer and Language Server that detects N+1 query patterns in Django code before runtime. It inspects queryset construction and related-field access to warn when relations are accessed without proper prefetching (select_related, prefetch_related).
It works inside the editor or directly from CLI.
Warning
This project is in active development and not yet production-ready.
- APIs are unstable
- Diagnostics may be incomplete or incorrect
- Expect breaking changes without notice
Django’s ORM makes it easy to accidentally introduce N+1 queries that:
- pass tests,
- look correct in code review,
- only show up under load.
Runtime tools (django-silk, nplusone) are focuesd in runtime optimiezation. django-check make static analysis before the runtime.
- Compute the graph of the
Models in the app - Zero runtime overhead
- LSP-based diagnostics at edit time
- No code instrumentation required
- Works with any editor that supports LSP
-
Iteration over QuerySets followed by related-field access
-
Missing
select_related/prefetch_relatedfor:- ForeignKey
- OneToOne
- ManyToMany
- Inheritance
- Reverse relations (with
related_nameexplicit or not) - Complex chained relations like
model__relation__child_relation__depth - Prefetch usage:
Prefetch("relation", queryset=Relation.objects.select_related("child")
pip install djchOr with uv:
uv pip install djchThis installs the djch binary to your PATH.
Usage: djch <COMMAND>
Commands:
server Start as a Language Server (normally handled by the IDE)
check Analyze the current directory tree for N+1 queries
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help (see more with '--help')
-V, --version Print version
Check from CLI using djch check. You will get an output like this:
app/foo/bar/views/tier1.py:210:22: [N+1] rp.ticker_benchmark
Potential N+1 query: accessing `rp.ticker_benchmark` inside loop
app/apps/crawler/tasks.py:48:17: [N+1] ticker.industry
Potential N+1 query: accessing `ticker.industry` inside loop
app/apps/crawler/views.py:62:20: [N+1] stream.streamer
Potential N+1 query: accessing `stream.streamer` inside loop
app/apps/foo/selectors/anointed.py:43:19: [N+1] anointed.pattern
Potential N+1 query: accessing `anointed.pattern` inside loop
Neovim 0.11 ships with a stable built-in LSP client.
Minimal setup:
-- lua/init.lua
vim.lsp.enable("djch")-- lsp/djch.lua
return {
cmd = { "djch", "server" },
filetypes = { "python" },
root_markers = { 'manage.py', 'pyproject.toml', '.git' }
}This registers django-check as a first-class LSP server.
If you are already attaching multiple LSPs to Python buffers (e.g. Pyright), Neovim will merge diagnostics correctly.
You can install the VSCode extension from, or directly in VSCode Extensions Market Place.
https://marketplace.visualstudio.com/items?itemName=richardhapb.Django-Check
# N+1 query detected
users = User.objects.all()
profiles = [user.profile in user for users] # N+1Explanation:
usersis evaluated onceuser.profiletriggers one query per iteration
users = User.objects.select_related("profile").all()
profiles = [user.profile in user for users]users = User.objects.all()
for user in users:
user.profile.bio # N+1Explanation:
usersis evaluated onceuser.profiletriggers one query per iteration
users = User.objects.select_related("profile").all()
for user in users:
user.profile.biodjango-check will clear the diagnostic once the relation is prefetched.
- Parse Python source into an AST
- Identify Django model classes and relationships
- Track QuerySet-producing expressions
- Track iteration boundaries
- Detect attribute access that implies ORM resolution
- Verify whether the required relation is prefetched
- Zero config
- Just works out of the box
- Editor feedback must be actionable, not noisy
- Interprocedural analysis requires type hints on QuerySet parameters
- Limited understanding of:
annotate,aggregate- complex custom managers
- Custom queryset method summaries
- Templates integration
This project lives at the intersection of:
- Python AST
- Django ORM semantics
- LSP protocol design
If you are interested in any of those, contributions are welcome.
Documentation contributions are welcome, the goal is make this tool easy to use.
MIT