Django ORM query visualizer with N+1 detection and index suggestions
Project description
django-querylens
Django ORM query visualizer with automatic N+1 detection, slow query identification, and terminal/HTML reports.
Features
- N+1 Query Detection — Automatically detects repeated queries to the same table
- Slow Query Detection — Flags queries exceeding a configurable threshold
- View Decorator —
@explain_querywraps views with zero-effort query profiling - Automatic Signal-Based Capture — Per-request analysis via Django signals
- Terminal & HTML Reports — Box-drawn terminal tables or styled HTML output
- Management Command —
python manage.py querylens_reportfor CLI reporting - Live Admin Dashboard — Real-time query history at
/admin/querylens/ - Debug Panel — Collapsible overlay injected into HTML responses
- Production Safe — Configurable sampling rate, zero overhead when disabled
- Thread Safe — All state stored in
threading.local() - Zero Dependencies — Only requires Django (optional:
coloramafor colored output)
Installation
pip install django-querylens
For colored terminal output:
pip install django-querylens[color]
Quick Start
1. Add to INSTALLED_APPS
# settings.py
INSTALLED_APPS = [
...
'django_querylens',
]
2. Configure settings
# settings.py
QUERYLENS = {
'ENABLED': True, # Master switch
'SAMPLE_RATE': 1.0, # 1.0 = all requests, 0.1 = 10% sampling
'N1_THRESHOLD': 3, # Min repeated queries to flag N+1
'SLOW_QUERY_MS': 100, # Slow query threshold in milliseconds
'OUTPUT': 'terminal', # 'terminal' or 'html'
'MAX_STORED_REPORTS': 1000,# Max reports kept in memory for admin dashboard
}
3. Use the decorator on views
from django_querylens import explain_query
@explain_query
def article_list(request):
articles = list(Article.objects.all())
for article in articles:
_ = article.author.name # N+1 detected!
return render(request, 'articles.html', {'articles': articles})
4. Or use the context manager directly
from django_querylens import QueryAnalyzer
analyzer = QueryAnalyzer()
with analyzer.capture() as result:
users = list(User.objects.all())
for user in users:
User.objects.get(pk=user.pk) # N+1!
print(f"Total queries: {result.total_count}")
print(f"N+1 detected: {result.has_n_plus_one}")
for detection in result.n_plus_one_detected:
print(f" Table: {detection.table} ({detection.count}x)")
5. Generate a CLI report
python manage.py querylens_report --top 10
python manage.py querylens_report --format html > report.html
Automatic Per-Request Analysis
When django_querylens is in INSTALLED_APPS and QUERYLENS['ENABLED'] is True, signal handlers automatically capture and log query analysis for every HTTP request (respecting SAMPLE_RATE).
With colorama installed (pip install django-querylens[color]), you get colored box-drawn output in the terminal. Without it, structured plain-text log lines are emitted.
All captured reports are also stored in memory and accessible via the admin dashboard (see below).
Admin Dashboard
django-querylens includes a live query history dashboard accessible at /admin/querylens/. It requires staff access and DEBUG = True.
What it shows
- Dashboard (
/admin/querylens/) — A table of recent requests with color-coded rows:- Red border = N+1 detected
- Orange border = slow queries
- Green border = clean
- Detail (
/admin/querylens/<report_id>/) — Full HTML-formatted analysis for a single request - API (
/admin/querylens/api/reports/) — JSON endpoint for programmatic access or auto-refresh
Features
- Refresh and Clear All buttons
- Query count, total time, N+1 count, and slow query count per request
- Click any row to see the full analysis report
- Reports are stored in a bounded in-memory ring buffer (
MAX_STORED_REPORTS, default 1000)
No extra setup is needed — the dashboard URLs are automatically registered when django.contrib.admin is installed.
Custom Output Function
from django_querylens import explain_query
from django_querylens.analyzer import AnalysisResult
def send_to_monitoring(result: AnalysisResult, view_name: str) -> None:
statsd.gauge('django.queries.count', result.total_count, tags=[view_name])
if result.has_n_plus_one:
statsd.increment('django.queries.n_plus_one', tags=[view_name])
@explain_query(output_fn=send_to_monitoring)
def my_view(request):
...
Formatters
from django_querylens.formatters import get_formatter, TerminalFormatter, HtmlFormatter
# Auto-detect from settings
formatter = get_formatter()
output = formatter.format(result)
# Explicit
terminal_output = TerminalFormatter().format(result)
html_output = HtmlFormatter().format(result)
Debug Panel
django-querylens includes a lightweight debug panel that automatically injects a collapsible overlay at the bottom of every HTML response — similar to Django Debug Toolbar but focused on query analysis.
Setup
# settings.py
MIDDLEWARE = [
...
'django_querylens.middleware.QueryLensMiddleware',
]
QUERYLENS = {
'PANEL': True, # Enable the debug panel (default: False)
...
}
The panel is only injected when all of the following are true:
QUERYLENS['PANEL']isTruesettings.DEBUGisTrue(never in production)- Response
Content-Typeistext/html - Response body contains a
</body>tag - Response is not a streaming response
What it shows
- Bottom bar: Query count and total time with color-coded status (green/orange/red)
- N+1 Detections: Tables with repeated query patterns (red)
- Slow Queries: Queries exceeding
SLOW_QUERY_MSthreshold (orange) - All Queries: Collapsible list of every query with execution time
The panel is self-contained (inline CSS, no external dependencies) with a dark theme and all CSS classes prefixed with querylens- to avoid conflicts.
Production Configuration
For production, use a low sample rate to minimize overhead:
QUERYLENS = {
'ENABLED': True,
'SAMPLE_RATE': 0.01, # Analyze 1% of requests
'N1_THRESHOLD': 5,
'SLOW_QUERY_MS': 200,
'OUTPUT': 'terminal',
'MAX_STORED_REPORTS': 500,
}
To completely disable (zero overhead):
QUERYLENS = {
'ENABLED': False,
}
Compatibility
- Python 3.9+
- Django 3.2, 4.x, 5.x, 6.x
Development
git clone https://github.com/Reyretee/django-querylens.git
cd django-querylens
pip install -e ".[dev]"
pytest
License
MIT License. See LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
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 django_querylens-0.1.0.tar.gz.
File metadata
- Download URL: django_querylens-0.1.0.tar.gz
- Upload date:
- Size: 44.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c003201bf351c97fcb311a042e016fb9fc73d235f4d8a2f5d79e3cc120068ac3
|
|
| MD5 |
b83cef1bd323d040957cd3614e54e7d9
|
|
| BLAKE2b-256 |
593919fc802b0acb8b2f9ebdfaf227a7d377add5fdb24f2d9b92b2c5c02119a5
|
Provenance
The following attestation bundles were made for django_querylens-0.1.0.tar.gz:
Publisher:
publish.yml on Reyretee/django-querylens
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_querylens-0.1.0.tar.gz -
Subject digest:
c003201bf351c97fcb311a042e016fb9fc73d235f4d8a2f5d79e3cc120068ac3 - Sigstore transparency entry: 1019524067
- Sigstore integration time:
-
Permalink:
Reyretee/django-querylens@9b889673be56cef9c14e4ccdd064c36deae21164 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Reyretee
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9b889673be56cef9c14e4ccdd064c36deae21164 -
Trigger Event:
release
-
Statement type:
File details
Details for the file django_querylens-0.1.0-py3-none-any.whl.
File metadata
- Download URL: django_querylens-0.1.0-py3-none-any.whl
- Upload date:
- Size: 36.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
387a15bdd5d9383603dec1af33880bac7602a699575b87bc3f8fda4dbbae7db0
|
|
| MD5 |
1a4ea0abdbb84f690eea07c8b198142b
|
|
| BLAKE2b-256 |
a2bae9af86f9df0db75e379a83f2db199e8f606e32c7801f3257c3e4c65cdf70
|
Provenance
The following attestation bundles were made for django_querylens-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on Reyretee/django-querylens
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_querylens-0.1.0-py3-none-any.whl -
Subject digest:
387a15bdd5d9383603dec1af33880bac7602a699575b87bc3f8fda4dbbae7db0 - Sigstore transparency entry: 1019524077
- Sigstore integration time:
-
Permalink:
Reyretee/django-querylens@9b889673be56cef9c14e4ccdd064c36deae21164 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Reyretee
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9b889673be56cef9c14e4ccdd064c36deae21164 -
Trigger Event:
release
-
Statement type: