An elegant, real-time debug assistant for Django. Inspired by Laravel Telescope — built from the ground up for Django.
Django Scope gives you a beautiful dashboard to monitor everything happening in your application: requests, database queries, exceptions, model changes, cache operations, Redis commands, log entries, and much more. Unlike traditional debug tools that dump information into your terminal or a toolbar on the page, Django Scope provides a standalone SPA dashboard with real-time WebSocket updates — you see entries appear the instant they happen, no refresh needed.
| Django Debug Toolbar | Django Scope | |
|---|---|---|
| Interface | Panel injected into HTML responses | Standalone dashboard (works with APIs, SPAs, mobile backends) |
| Real-time | No — only shows data for the current page load | Yes — WebSocket-powered live feed |
| API requests | Cannot inspect (no HTML to inject into) | Full request/response capture for all endpoints |
| Background tasks | Not supported | Commands, schedules, batches |
| Persistence | Gone on page refresh | Stored in database, browsable history |
| N+1 detection | Manual inspection of query list | Automatic detection with threshold alerts |
| Cache/Redis | Limited cache panel | Full operation-level cache + raw Redis command tracking |
| Exceptions | Not tracked | Full stack traces with source context |
| Production safe | Not recommended | Authorization gate, pruning, toggle recording |
| Silk | Django Scope | |
|---|---|---|
| Scope | Requests + queries only | 17 watchers (requests, queries, cache, Redis, mail, models, logs, exceptions, events, commands, dumps, HTTP client, views, gates, notifications, schedules, batches) |
| Real-time | No — must refresh to see new data | WebSocket live feed |
| Frontend | Basic server-rendered HTML | Modern Vue.js SPA with dark/light themes and animations |
| Cache/Redis | Not tracked | Full auto-detection, no config needed |
| Model changes | Not tracked | Field-level change tracking on save/delete |
| N+1 detection | Not built-in | Automatic with configurable threshold |
Those are production monitoring and error tracking platforms (and great at what they do). Django Scope is a development and debugging tool — it's local, free, instant, and shows you everything at the code level without sending data to a third party. Use Django Scope during development, use Sentry in production. They complement each other.
- 17 watchers covering every aspect of your Django application
- Real-time WebSocket updates — entries appear instantly in the dashboard
- N+1 query detection — automatically flags repeated query patterns
- Slow query highlighting — configurable threshold (default: 100ms)
- Beautiful Vue.js dashboard with dark and light themes
- Auto-detection — cache and Redis monitoring works automatically, no backend swapping required
- Request-scoped buffering — entries are grouped by request with a single bulk write for minimal performance impact
- Zero config for basics — add the app, add the middleware, done
- Authorization gate — control who can access the dashboard
- Pruning — automatic cleanup of old entries
pip install django-scopeINSTALLED_APPS = [
"daphne", # Required for WebSocket support
# ... your apps
"telescope",
]Note:
daphnemust be listed beforedjango.contrib.staticfiles(if present) so it can serve the ASGI application. If you don't need real-time WebSocket updates, you can skipdaphneand run under WSGI — the dashboard will still work, just without live updates.
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"telescope.middleware.TelescopeMiddleware",
# ... other middleware
]from django.urls import include, path
urlpatterns = [
path("telescope/", include("telescope.urls")),
# ... your urls
]Create or update your asgi.py:
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
django.setup()
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from telescope.routing import websocket_urlpatterns
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": URLRouter(websocket_urlpatterns),
})And in your settings:
ASGI_APPLICATION = "myproject.asgi.application"
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer",
}
}python manage.py migrateStart your server and visit http://localhost:8000/telescope/.
All configuration lives in a single TELESCOPE dictionary in your Django settings:
TELESCOPE = {
# Master switch — disable to completely turn off Django Scope
"ENABLED": True,
# Toggle recording without disabling (can be toggled from the dashboard)
"RECORDING": True,
# URL paths to ignore (regex patterns)
"IGNORE_PATHS": [r"^/telescope/", r"^/static/", r"^/favicon\.ico$"],
# HTTP methods to ignore
"IGNORE_METHODS": [], # e.g. ["OPTIONS", "HEAD"]
# Status codes to ignore
"IGNORE_STATUS_CODES": [], # e.g. [304]
# Queries slower than this are flagged as "slow" (milliseconds)
"SLOW_QUERY_THRESHOLD": 100,
# Flag N+1 when the same query pattern repeats this many times in one request
"N_PLUS_ONE_THRESHOLD": 5,
# Auto-prune entries older than this
"ENTRY_LIFETIME_HOURS": 24,
# Max size per entry payload (KB)
"ENTRY_SIZE_LIMIT": 64,
# Authorization callback — controls who can access the dashboard
# Receives the request, returns True/False. Defaults to settings.DEBUG.
"AUTHORIZATION": lambda request: request.user.is_superuser,
# Enable/disable individual watchers
"WATCHERS": {
"RequestWatcher": {"enabled": True},
"QueryWatcher": {"enabled": True},
"ExceptionWatcher": {"enabled": True},
"ModelWatcher": {"enabled": True},
"LogWatcher": {"enabled": True},
"CacheWatcher": {"enabled": True},
"RedisWatcher": {"enabled": False}, # Opt-in
"MailWatcher": {"enabled": True},
"ViewWatcher": {"enabled": True},
"EventWatcher": {"enabled": True},
"CommandWatcher": {"enabled": True},
"DumpWatcher": {"enabled": True},
"ClientRequestWatcher": {"enabled": False}, # Opt-in
"GateWatcher": {"enabled": True},
"NotificationWatcher": {"enabled": True},
"ScheduleWatcher": {"enabled": False}, # Requires Celery
"BatchWatcher": {"enabled": False}, # Requires Celery
},
}Captures every HTTP request and response — method, path, headers, payload, response body, status code, duration, the resolved view function, and the user/session.
Records every database query with SQL, bindings, execution time, and connection alias. Automatically detects:
- Slow queries — flagged when execution exceeds
SLOW_QUERY_THRESHOLD(default 100ms) - N+1 queries — flagged when the same query pattern repeats
N_PLUS_ONE_THRESHOLDtimes (default 5) in a single request
Works with any Django database backend (SQLite, PostgreSQL, MySQL, etc.) via Django's connection_created signal — every new database connection gets instrumented automatically.
Captures unhandled exceptions with the full stack trace, exception class, message, and the file/line where it occurred. Connects to Django's got_request_exception signal.
Tracks model create, update, and delete operations via Django's pre_save, post_save, and post_delete signals. Records field-level changes on updates (old value vs. new value).
Captures log entries from Python's logging module — all levels (DEBUG through CRITICAL). Installs a custom handler on the root logger so it catches logs from your code and third-party libraries alike.
Monitors all cache operations: get, set, delete, clear, get_many, set_many, delete_many, has_key, incr, decr. Records the key, value, hit/miss status, duration, and backend alias.
Auto-detection — works with any cache backend out of the box. No need to swap your CACHES backend setting. Works with django-redis, memcached, file cache, database cache, local memory cache, and any custom backend.
Captures raw Redis commands (SET, GET, LPUSH, EXPIRE, etc.) with arguments, duration, and connection info. Supports both:
redis-py— patchesStrictRedis.execute_commanddjango-redis— patchesDefaultClientfor Django cache operations that go through Redis
Enable it in settings:
"RedisWatcher": {"enabled": True},Records outgoing emails — recipients, subject, body (HTML and plain text), and attachments. Uses a wrapping email backend.
Tracks template rendering — which templates are rendered, render duration, and the resolved view.
Records Django signal dispatches. Automatically ignores Django's internal signals (pre_save, post_save, request_started, connection_created, etc.) to avoid flooding. Only records your custom signals and third-party signals.
Captures management command execution — command name, arguments, exit code, output, and duration.
A telescope.dump() function you can call from anywhere in your code to inspect values:
import telescope
def my_view(request):
telescope.dump(some_complex_object, label="Debug: user data")
telescope.dump(request.headers, label="Request headers")
# ...Each dump records the value, label, and the exact file/line where dump() was called.
Monitors outgoing HTTP requests made by your application (via requests or httpx). Records URL, method, status code, headers, and duration.
Tracks permission checks — records which user, which permission, and whether it was granted or denied.
Records notifications sent to users — notification class, recipient, and delivery channels.
Tracks Celery Beat scheduled task execution (requires Celery).
Monitors Celery group/chord batch job execution (requires Celery).
# Prune entries older than the configured lifetime (default: 24 hours)
python manage.py telescope_prune
# Prune entries older than 6 hours
python manage.py telescope_prune --hours 6# Clear everything
python manage.py telescope_clear
# Clear only query entries
python manage.py telescope_clear --type query
# Clear only exceptions
python manage.py telescope_clear --type exceptionYou can also clear entries from the dashboard UI — each list page has a clear button.
If you can't run an ASGI server, Django Scope still works. The dashboard will function normally — you just won't get real-time live updates. The WebSocket indicator will show "Disconnected" and stop retrying after a few attempts. Data is still recorded and visible when you refresh or navigate.
Skip daphne from INSTALLED_APPS and the ASGI/channels configuration. Everything else stays the same.
By default, the dashboard is only accessible when DEBUG = True. For more control, set an authorization callback:
TELESCOPE = {
"AUTHORIZATION": lambda request: request.user.is_staff,
}Or a more complex check:
def scope_authorized(request):
if not request.user.is_authenticated:
return False
return request.user.email.endswith("@mycompany.com")
TELESCOPE = {
"AUTHORIZATION": scope_authorized,
}Django Scope creates 3 tables:
telescope_entries— all recorded entries (UUID, type, JSON content, batch ID, timestamps)telescope_entries_tags— searchable tags (e.g.slow,n+1,status:500,cache:hit)telescope_monitoring— selective monitoring configuration
All entry data is stored as JSON, keeping the schema simple and the queries fast.
Django Scope is designed to have minimal impact on your application:
- Request-scoped buffering — entries are collected in memory during a request and written in a single
bulk_createat the end, not one INSERT per event - Re-entrancy guard — Django Scope's own database operations are never recorded (no infinite loops)
- Path filtering — Dashboard routes and static files are excluded by default
- Async-safe — uses
contextvars(notthreading.local) so it works correctly with Django's async views - Configurable pruning — old entries are automatically cleaned up
- Python 3.10+
- Django 4.2+
channels4.0+ (for WebSocket support)daphne(recommended, for ASGI server)
Optional:
redis/django-redis(for Redis watcher)celery(for Schedule and Batch watchers)
MIT