Skip to main content

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_query wraps 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 Commandpython manage.py querylens_report for 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: colorama for 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'] is True
  • settings.DEBUG is True (never in production)
  • Response Content-Type is text/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_MS threshold (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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_querylens-0.1.0.tar.gz (44.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_querylens-0.1.0-py3-none-any.whl (36.3 kB view details)

Uploaded Python 3

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

Hashes for django_querylens-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c003201bf351c97fcb311a042e016fb9fc73d235f4d8a2f5d79e3cc120068ac3
MD5 b83cef1bd323d040957cd3614e54e7d9
BLAKE2b-256 593919fc802b0acb8b2f9ebdfaf227a7d377add5fdb24f2d9b92b2c5c02119a5

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_querylens-0.1.0.tar.gz:

Publisher: publish.yml on Reyretee/django-querylens

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_querylens-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_querylens-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 387a15bdd5d9383603dec1af33880bac7602a699575b87bc3f8fda4dbbae7db0
MD5 1a4ea0abdbb84f690eea07c8b198142b
BLAKE2b-256 a2bae9af86f9df0db75e379a83f2db199e8f606e32c7801f3257c3e4c65cdf70

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_querylens-0.1.0-py3-none-any.whl:

Publisher: publish.yml on Reyretee/django-querylens

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page