Static N+1 query detector for Django ORM – fast Rust-powered checker
Project description
django-check
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
Why this exists
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.
Key properties
- 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
What it detects (today)
-
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")
Installation
pip install djch
Or with uv:
uv pip install djch
This installs the djch binary to your PATH.
CLI
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
Editor integration (LSP)
Neovim 0.11+ (native LSP)
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.
VS Code (Extension)
You can install the VSCode extension from, or directly in VSCode Extensions Market Place.
https://marketplace.visualstudio.com/items?itemName=richardhapb.Django-Check
Examples
Problematic code
# N+1 query detected
users = User.objects.all()
profiles = [user.profile in user for users] # N+1
Explanation:
usersis evaluated onceuser.profiletriggers one query per iteration
Corrected code
users = User.objects.select_related("profile").all()
profiles = [user.profile in user for users]
Problematic code
users = User.objects.all()
for user in users:
user.profile.bio # N+1
Explanation:
usersis evaluated onceuser.profiletriggers one query per iteration
Corrected code
users = User.objects.select_related("profile").all()
for user in users:
user.profile.bio
django-check will clear the diagnostic once the relation is prefetched.
How it works (high level)
- 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
Design philosophy
- Zero config
- Just works out of the box
- Editor feedback must be actionable, not noisy
Limitations (work in progress)
- Interprocedural analysis requires type hints on QuerySet parameters
- Limited understanding of:
annotate,aggregate- complex custom managers
Roadmap
- Custom queryset method summaries
- Templates integration
Contributing
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.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file djch-0.1.2-py3-none-win_amd64.whl.
File metadata
- Download URL: djch-0.1.2-py3-none-win_amd64.whl
- Upload date:
- Size: 2.5 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2bd6157752de6c562ef109f186886c7b90942f0e09b97ea6a74526970aed0d02
|
|
| MD5 |
e07e80dc084202278ae45ba5910a341e
|
|
| BLAKE2b-256 |
6fd7bdf41d5acb8cb7683f5921e845f1a28074250f122044a7a97d564cfc669e
|
File details
Details for the file djch-0.1.2-py3-none-musllinux_1_2_aarch64.whl.
File metadata
- Download URL: djch-0.1.2-py3-none-musllinux_1_2_aarch64.whl
- Upload date:
- Size: 2.4 MB
- Tags: Python 3, musllinux: musl 1.2+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
636f2ec0077e051b0d955d959cbbbc7083192bf0f1342d0b8a9bac0407aea245
|
|
| MD5 |
5c63ae0e5042ebdfd2fd4c2ae7ab4de7
|
|
| BLAKE2b-256 |
ecc5d9cf135a9ccb916ce368a83257426a103e8f5bd41c53cf01cb3f87a5796b
|
File details
Details for the file djch-0.1.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl.
File metadata
- Download URL: djch-0.1.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
- Upload date:
- Size: 2.3 MB
- Tags: Python 3, manylinux: glibc 2.17+ ARMv7l
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d8378ed10535b18a50e3f9f60f7dc3fa9cbfdb775c093412165d7fdd47d6731
|
|
| MD5 |
3322cdabed82710340db291f75653539
|
|
| BLAKE2b-256 |
8c5666ae28629c37cd3ed48f3b3ae0046c79e81c2bf37f212d0921c218465748
|
File details
Details for the file djch-0.1.2-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: djch-0.1.2-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.3 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1bdfa67790fff38ffb46f0909c24afe706f4c59eedad26ede820ac7386e4b981
|
|
| MD5 |
a36a8b0593963bdfe2a5573fd6d1500c
|
|
| BLAKE2b-256 |
ff6673a9735f41e9c393f3b6d72c43fa126146c553b9ceefda60ddc8793241ff
|