Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>DjangoPad — Django Shell NotebookNew to Visual Studio Code? Get it now.
DjangoPad — Django Shell Notebook

DjangoPad — Django Shell Notebook

AnandShah

| (0) | Free
A native, Jupyter-like notebook experience for running Django shell code interactively in VS Code.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

DjangoPad — Django Shell Notebook for VS Code

License: MIT Issues Last commit Stars

A native, Jupyter-like notebook experience for running Django shell code interactively inside VS Code. Open a .djshell file, write ORM code, run cells — no manual virtualenv activation, no DJANGO_SETTINGS_MODULE juggling, no python manage.py shell.

from users.models import User
User.objects.filter(is_active=True).count()

State persists across cells, exactly like a real Django shell session.

Source & issues on GitHub →


1. Overview

DjangoPad registers a custom VS Code notebook type (.djshell) backed by a persistent Python/Django process — one per open notebook. The extension automatically finds your Django project (manage.py), your Python interpreter/virtualenv, and your DJANGO_SETTINGS_MODULE, then initializes Django once and keeps a live Python namespace around so imports, variables, and querysets defined in one cell are visible in the next.

2. Features

  • Native VS Code Notebook UI (NotebookSerializer + NotebookController, not a webview hack)
  • Automatic Django project detection (nested projects, monorepos, multiple manage.py files)
  • Automatic Python interpreter detection: VS Code's selected interpreter, .venv/venv/env, Poetry, Pipenv, uv, Conda, system Python
  • Automatic DJANGO_SETTINGS_MODULE detection by parsing manage.py
  • .env / .env.local / .env.development / .env.test support
  • One persistent Python process per notebook — no per-cell process spawn, full state persistence (imports, variables, querysets)
  • Real stdout/stderr capture, expression results via repr() (like the interactive interpreter), full tracebacks on exceptions
  • User exceptions never kill the kernel — the next cell still runs
  • Cross-platform interrupt (works on Windows, macOS, and Linux — no reliance on POSIX signals)
  • Restart Kernel, Clear Outputs, Show Environment, Select Project, Select Python Environment commands
  • Best-effort top-level await support
  • No Jupyter dependency — the runner is pure Python standard library

3. Installation

From the VS Code Marketplace (once published): search for "DjangoPad" in the Extensions view (Ctrl+Shift+X / Cmd+Shift+X), or install from the command line:

code --install-extension djangopad.djangopad

From a .vsix (see Packaging), e.g. for a build pulled from a GitHub release:

code --install-extension djangopad-0.1.0.vsix

Or press F5 in this repo to launch an Extension Development Host with the extension loaded (see Development).

4. Creating a .djshell notebook

Run Django Notebook: New Notebook from the Command Palette, or create any file ending in .djshell and open it — VS Code will render it with the DjangoPad notebook UI.

5. Running cells

Use the standard notebook run controls (▷ per cell, or "Run All"). Cells in a run-all execute strictly in order, one at a time, matching a persistent-shell mental model — later cells can depend on earlier ones.

Cell 1:  from users.models import User
         users = User.objects.all()

Cell 2:  users.count()

Cell 3:  User.objects.first()

6. Django auto-detection

DjangoPad searches for manage.py:

  1. Upward from the notebook's directory (it is not assumed to be at the workspace root)
  2. Downward through open workspace folders (for monorepos where the notebook lives outside any single project, e.g. repo/scripts/x.djshell with repo/backend/manage.py)

If exactly one manage.py is found, it's used automatically. If several are found at the same "distance," you're prompted with a Quick Pick; your choice is remembered per notebook. You can always override with djangoNotebook.projectPath, or via Django Notebook: Select Django Project.

DJANGO_SETTINGS_MODULE is parsed directly out of manage.py's os.environ.setdefault("DJANGO_SETTINGS_MODULE", "...") call — nothing is hardcoded, so unusual settings module names/layouts work fine.

7. Python environment detection

Resolution order (first match wins):

  1. djangoNotebook.pythonPath (explicit config)
  2. VS Code's selected Python interpreter (via the ms-python.python extension's API, if installed)
  3. Project-local .venv
  4. Project-local venv / env
  5. Poetry (poetry env info --path)
  6. Pipenv (pipenv --venv)
  7. uv (.venv created by uv, or uv run python -c ... resolution)
  8. Conda (matched by environment name, via conda env list --json)
  9. System Python (python3/python on PATH)

The selected interpreter is always validated — python --version and python -c "import django; print(django.get_version())" — before use. If Django isn't installed there, you get an actionable error with the exact pip install command to run, plus a Select Python button.

8. Environment variables

.env files are searched for in the project root, in this order: .env, .env.local, .env.development, .env.test (first match wins, or override with djangoNotebook.envFile). Values from the .env file never overwrite variables already present in your real environment — they only fill in gaps. DJANGO_SETTINGS_MODULE is set from manage.py unless already present in your environment.

9. Configuration

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 await requires 3.8+ for PyCF_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:

  1. Pure-logic unit tests (no VS Code needed) — .env parsing, manage.py regex extraction, and the IPC framing protocol (including fuzz-style chunking, UTF-8 boundary splits, and garbage-byte recovery):

    npm run test:unit
    
  2. VS 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, multiple manage.py files, no-project cases), notebook serializer round-trips, and full end-to-end cell execution through the real Notebook API:

    npm test
    
  3. Standalone runner smoke tests — drive the actual python/runner.py process 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 .djshell file is exactly as trusted as a .py file 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 await relies on ast.PyCF_ALLOW_TOP_LEVEL_AWAIT (Python 3.8+) and a lazily-created asyncio event loop per notebook. Mixing manual asyncio.run() calls with top-level await in 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.py at 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 on PATH; if it isn't, DjangoPad falls back further down the resolution order rather than failing outright.
  • The @vscode/test-electron integration suite requires downloading a VS Code test host from update.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.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft