DjangoPad — Django Shell Notebook for VS CodeA native, Jupyter-like notebook experience for running Django shell code
interactively inside VS Code. Open a
State persists across cells, exactly like a real Django shell session. 1. OverviewDjangoPad registers a custom VS Code notebook type ( 2. Features
3. InstallationFrom the VS Code Marketplace (once published): search for "DjangoPad"
in the Extensions view (
From a
Or press 4. Creating a
|
| Setting | Default | Description |
|---|---|---|
djangoNotebook.pythonPath |
"" |
Explicit interpreter path; overrides auto-detection |
djangoNotebook.projectPath |
"" |
Explicit project root (contains manage.py); overrides auto-detection |
djangoNotebook.envFile |
"" |
Explicit .env file path; overrides auto-detected .env files |
djangoNotebook.autoDetectProject |
true |
Enable/disable project auto-detection |
djangoNotebook.autoDetectEnvironment |
true |
Enable/disable interpreter auto-detection |
djangoNotebook.showEnvironmentOnStart |
true |
Show a notification with the detected environment on kernel start |
djangoNotebook.executionTimeout |
0 |
Max cell execution time in ms (0 = no timeout) |
Explicit configuration always overrides auto-detection.
10. Kernel lifecycle
Each notebook gets its own persistent Python process on first execution
(lazy start). States: starting → idle ⇄ busy → error/restarting/stopped.
Commands (Command Palette, prefixed "Django Notebook: "):
- Run Cell / Run All — standard notebook run controls
- Restart Kernel — stops the current process, starts a fresh one, re-runs Django init, and clears all Python state (cells themselves are untouched)
- Interrupt Kernel — cancels the currently running cell (see below)
- Clear Outputs
- Select Django Project / Select Python Environment
- Show Environment — opens a readable diagnostic summary
- New Notebook
Multiple open .djshell notebooks never share state — users.djshell
and orders.djshell each get their own process and namespace.
11. Troubleshooting
"No Django project (manage.py) could be found for this notebook."
Set djangoNotebook.projectPath, or run Select Django Project.
DjangoPad searches upward from the notebook and through workspace
folders — if your manage.py is somewhere unusual, an explicit path is
the most reliable fix.
"Django could not be found in the selected Python environment."
The message includes the exact interpreter path and a ready-to-run
pip install django command. Run Select Python Environment to switch
interpreters instead, if needed.
A cell seems to hang. Use Interrupt Kernel. Note the known limitation about C-level blocking calls.
State got weird / I want a clean slate. Use Restart Kernel.
I want to see exactly what was detected. Run Show Environment.
12. Supported environments
- OS: Windows, macOS, Linux
- Python: 3.8+ (top-level
awaitrequires 3.8+ forPyCF_ALLOW_TOP_LEVEL_AWAIT; everything else works on any modern CPython 3) - Django: any version installed in the resolved interpreter
- Env managers: venv, Poetry, Pipenv, uv, Conda, system Python
- VS Code: 1.85+
13. Architecture
VS Code Extension (TypeScript)
src/extension.ts - activation, commands
src/notebook/serializer.ts - .djshell <-> vscode.NotebookData
src/notebook/controller.ts - wires cell execution to the kernel
src/notebook/session.ts - one PythonRunner per notebook
src/django/projectDetector.ts - manage.py search (up + down)
src/django/settingsDetector.ts - DJANGO_SETTINGS_MODULE + .env
src/django/parsing.ts - pure regex/.env parsing (unit-testable)
src/django/environment.ts - ties detection together, Quick Picks
src/python/interpreter.ts - interpreter resolution + validation
src/python/runner.ts - spawns & talks to python/runner.py
src/python/protocol.ts - framed IPC protocol (TS side)
src/configuration/config.ts - typed settings accessor
Python Runner (persistent process, one per notebook)
python/runner.py - Django init, persistent namespace,
execution, interrupt, framed IPC (Python side)
IPC protocol
Requests/responses are not newline-delimited JSON (arbitrary user
print() output could corrupt that). Instead, every message — both
directions — is framed as:
b"DJPD" + <8-byte big-endian length> + <UTF-8 JSON payload>
sent over two dedicated file descriptors (fd 3: extension → runner,
fd 4: runner → extension), separate from the child's real stdout/stderr
(fd 1/2), which are kept only as a fallback for output that bypasses
Python's sys.stdout/sys.stderr objects (e.g. some C extensions
writing directly to the OS file descriptor). See
src/python/protocol.ts for the full rationale and the incremental
parser (MessageFramer) that's robust to arbitrary chunk boundaries,
multi-byte UTF-8 splits, and stream desync.
Persistent namespace & result display
Each cell's source is parsed with Python's ast module. If the final
top-level statement is a bare expression, it's compiled and evaluated
separately so its repr() can be captured and shown — exactly like the
interactive interpreter's auto-print, without touching sys.displayhook
(some libraries install their own). Everything else executes in a single
persistent dict namespace that lives for the process's lifetime.
Interrupt
Interrupting a cell does not use OS signals. Each cell runs on a
dedicated worker thread; interrupting uses CPython's
PyThreadState_SetAsyncExc to schedule a KeyboardInterrupt on that
thread. This works identically on Windows/macOS/Linux because it's a
CPython-level mechanism, not an OS one — unlike SIGINT, which has no
clean cross-platform equivalent for targeting an unrelated child process.
14. Development
git clone <this repo>
cd djangopad
npm install
npm run compile
Press F5 in VS Code (with this folder open) to launch an Extension
Development Host with DjangoPad loaded. Open one of the fixtures under
test/fixtures/simple_project/ and create a .djshell file to try it
against a real (test) Django project.
npm run watch recompiles on save.
15. Testing
Three layers:
Pure-logic unit tests (no VS Code needed) —
.envparsing,manage.pyregex extraction, and the IPC framing protocol (including fuzz-style chunking, UTF-8 boundary splits, and garbage-byte recovery):npm run test:unitVS Code integration tests (
@vscode/test-electron, needs network access to download a VS Code test host) — project detection against real fixture directories (nested projects, monorepos, multiplemanage.pyfiles, no-project cases), notebook serializer round-trips, and full end-to-end cell execution through the real Notebook API:npm testStandalone runner smoke tests — drive the actual
python/runner.pyprocess over the real framed protocol, without VS Code, to validate the kernel itself end-to-end:node scripts/smoke_test.js # persistence, stdout, exceptions, interrupt, top-level await node scripts/django_smoke_test.js # same, against a real Django project + SQLite DB + ORM
16. Packaging
npm run compile
npx vsce package
Produces djangopad-<version>.vsix, installable via
code --install-extension djangopad-<version>.vsix.
17. Security
Notebook cells execute arbitrary local Python code using the interpreter you (or auto-detection) selected — this is inherent to what a Django shell notebook is. DjangoPad:
- Never sends notebook code, output, or environment details to any external server
- Adds no telemetry
- Never shells out to execute your Python code — it always invokes the
detected interpreter directly (
spawn(pythonPath, ['-u', 'runner.py']), no shell involved), so notebook code cannot be affected by shell quoting/injection concerns - Runs entirely within your machine's existing permissions — a
.djshellfile is exactly as trusted as a.pyfile you'd run yourself
Treat .djshell files from untrusted sources the same way you'd treat an
untrusted .py script: don't run it.
18. Known limitations
- Interrupting a single long-running C call: a cell blocked inside
one long synchronous C-extension call (e.g. a slow blocking socket read
implemented in C) is only interrupted once that call returns control to
the Python bytecode interpreter. Pure-Python loops (
while True: pass) and normal I/O interrupt immediately. - Top-level
awaitrelies onast.PyCF_ALLOW_TOP_LEVEL_AWAIT(Python 3.8+) and a lazily-createdasyncioevent loop per notebook. Mixing manualasyncio.run()calls with top-levelawaitin the same session can conflict with that loop; prefer one style per notebook. Django's ORM is sync by default (Django 5's async ORM methods, e.g.acount(), work fine under this model). - Multiple
manage.pyat equal distance: DjangoPad ranks by directory proximity; true ties prompt a Quick Pick rather than guessing. uv/Conda detection relies on the corresponding CLI being onPATH; if it isn't, DjangoPad falls back further down the resolution order rather than failing outright.- The
@vscode/test-electronintegration suite requires downloading a VS Code test host fromupdate.code.visualstudio.com, so it cannot run in fully network-isolated environments (it did not run in the sandbox used to build this extension, for that reason — see the pure unit tests and standalone runner smoke tests above for what was verified end-to-end there, including against a real Django project and SQLite database).
Contributing
Issues and pull requests are welcome on GitHub. See CONTRIBUTING.md for the full guidelines, DEVELOPMENT.md for build/debug/test setup, and CODE_OF_CONDUCT.md for community expectations. Found a security issue? See SECURITY.md rather than opening a public issue. Before opening a PR, run the full check locally:
npm install
npm run compile
npm run lint
npx mocha
node scripts/smoke_test.js
node scripts/blocking_notification_test.js
For bug reports, please include your OS, VS Code version, Python version, and — if the kernel misbehaves — the contents of the "Django Notebook" output channel (View → Output → select "Django Notebook").
License
MIT — see LICENSE.