Development Commands¶
All development tasks are driven through make targets. This page is the complete reference.
Before every commit
Run make check to catch formatting, linting, type errors, migration issues, and file length violations before they hit CI:
Primary Commands¶
These are the commands you will use most often during day-to-day development.
| Command | Description |
|---|---|
make setup | Complete one-time setup: installs deps, copies .env.example, starts Docker, bootstraps database, starts server |
make run | Start the Django development server (also generates test JWTs) |
make run-e2e | Start a prod-like gunicorn server behind PgBouncer for frontend E2E under load (see below) |
make check | Run all code quality checks (see below) |
make test | Run the full pytest suite in parallel (pytest -n auto) with coverage reporting |
make test-linear | Run the full test suite sequentially (single process, useful for debugging) |
make test-failed | Re-run only previously failed tests |
make run-e2e — prod-like server for E2E under load
Parallel frontend E2E runs can open enough concurrent connections to a plain make run backend to exhaust Postgres (FATAL: sorry, too many clients already). make run-e2e reproduces the production runtime instead: it starts the PgBouncer pooler from docker-compose-e2e.yml and runs gunicorn (gthread workers, mirroring infra's command) routed through it on port 6432. This matches where the pressure actually comes from — ATOMIC_REQUESTS pins one connection per in-flight request across every gunicorn worker/thread — and multiplexes those onto a small server pool.
Only this target opts into PgBouncer (via DB_USE_PGBOUNCER=True DB_PORT=6432 set inline); make run and make test stay on a direct 5432 connection. Unlike make run, gunicorn does not serve Django static files, so admin/Swagger pages are unstyled — irrelevant for frontend E2E, which only hits /api/*. Tune load with GUNICORN_WORKERS / GUNICORN_THREADS.
Code Quality and Formatting¶
make check runs all of the following in sequence:
| Step | Command | What It Does |
|---|---|---|
| 1 | make format | Auto-format code with ruff |
| 2 | make lint | Lint with ruff and auto-fix issues |
| 3 | make mypy | Run strict type checking with mypy |
| 4 | make migration-check | Verify no migrations are missing (makemigrations --check) |
| 5 | make i18n-check | Verify the .po catalog is complete (keys extracted + translated) |
| 6 | make file-length | Verify no source file exceeds 1,000 lines |
Why a custom file length check?
Ruff doesn't currently enforce a maximum file length. Rather than pulling in another linter just for this one rule, we use a lightweight shell script (scripts/check-file-length.sh) that fails if any Python file exceeds 1,000 lines.
Ruff handles both formatting and linting
This project uses ruff as the single tool for both code formatting and linting, replacing the older black + flake8 + isort combination.
Internationalization (i18n)¶
| Command | Description |
|---|---|
make makemessages | Extract translatable strings from code and templates, updating .po files (deterministic — no POT-Creation-Date churn) |
make compilemessages | Compile .po files into .mo binaries (not committed; built here for local runs) |
make i18n-check | Verify the .po catalog is complete: every code string extracted, every entry translated (no empty/fuzzy) |
Don't commit compiled translations
.mo binaries are git-ignored and built during the Docker image build (and before tests). Commit only the .po sources. The CI pipeline runs make i18n-check (scripts/check_translations.py) and fails on missing keys or untranslated entries. See ADR-0011.
Database Management¶
| Command | Description |
|---|---|
make migrations | Create new Django migration files for model changes |
make migrate | Apply all pending migrations |
make bootstrap | Full database initialization: runs migrate, creates admin user, loads sample events and test data, generates test JWTs |
make seed | Populate the database with large-scale faker-generated data (useful for load testing) |
make reset-events | Reset event data to bootstrap defaults (requires DEMO_MODE=True) |
Destructive database commands
The following commands will destroy data. Use them only when you need a completely fresh start.
| Command | Description |
|---|---|
make nuke-db | Deletes the database and migration files (preserving special data migrations), regenerates migrations, restarts Docker, and runs migrate |
make restart | Most destructive: Deletes the database, removes all migration files, regenerates them, restarts Docker, and runs bootstrap |
make flush | Flushes all data from the database (keeps schema) |
After running make nuke-db or make restart, run make bootstrap to restore a working database.
Background Services¶
Revel uses Celery for asynchronous task processing. In development, Celery runs in eager mode by default (CELERY_TASK_ALWAYS_EAGER=True when DEBUG=True), meaning tasks execute synchronously in the same process. No separate workers needed.
To test with real async processing, set CELERY_TASK_ALWAYS_EAGER=False in your .env and start the workers:
| Command | Description |
|---|---|
make run-celery | Start a Celery worker to process background tasks |
make run-celery-beat | Start the Celery beat scheduler for periodic tasks |
make run-flower | Start the Flower web UI for monitoring Celery workers and tasks |
make run-telegram | Start the Telegram bot (long-polling mode) |
make run-stripe | Start the Stripe webhook listener (forwards to localhost) |
Running the full stack locally
For a complete local environment with real async processing, open separate terminal tabs:
Testing¶
| Command | Description |
|---|---|
make test | Run the full test suite in parallel (pytest -n auto) with coverage reporting |
make test-linear | Run the full test suite sequentially (single process, useful for debugging) |
make test-failed | Re-run only tests that failed in the previous run |
make test-integration | Run only tests marked with @pytest.mark.integration |
Security Scanning¶
| Command | Description |
|---|---|
make bandit | Run Bandit static security analysis on src/ |
Authentication¶
| Command | Description |
|---|---|
make jwt [email protected] | Generate JWT access and refresh tokens for a given user |
Using JWT tokens
The generated access token can be used in the Swagger UI or with curl:
Documentation¶
| Command | Description |
|---|---|
make serve-docs | Serve the MkDocs documentation site locally at localhost:8800 |
make build-docs | Build the static documentation site for deployment |
Utility Commands¶
| Command | Description |
|---|---|
make shell | Open the Django interactive shell |
make reset-db | Reset the database using the reset_db management command |
make db-diagram | Generate a database entity-relationship diagram (requires django-extensions) |
make bootstrap-tests | Run only the bootstrap_test_events management command (test events for eligibility gates) |
make check-version | Check the currently deployed versions on main and dev servers |
make count-lines | Count non-blank, non-comment Python lines in src/ |
make tree | Print the project directory tree (excludes __pycache__, images, htmlcov) |
make dump-openapi | Dump the OpenAPI schema to a file |
make dump-issues | Export all open GitHub issues to issues.md |
make release | Create a GitHub release for the current version |
Common Workflows¶
Starting fresh after a main rebase¶
make migrate # Apply any new migrations from main
make bootstrap # Re-seed base data if needed
make run # Start the server
Full reset when things go wrong¶
make restart # Nuke Docker + DB, start fresh
make bootstrap # Recreate schema + base data (includes migrate)
make run # Start the server
Ephemeral database
In development, PostgreSQL uses tmpfs (in-memory storage). All database data is lost when the Docker container stops. After any docker compose down, you'll need to run make bootstrap to restore the database.