CaseNote
Django helpdesk for a portfolio demo. The tenant is the fictional retailer Northwind Outfitters. Shoppers, tickets, and staff accounts are generated by reset_demo. Nothing here is a client engagement, a review, or a measured outcome.
What it shows
- Django 4.2 admin with custom columns, a SLA filter, and actions.
- Groups
viewer,agent, andlead, plus object checks: a lead closes and reassigns; an agent changes tickets assigned to them; a viewer cannot write. - Ticket list with search, filters, and pagination. Detail page with a timeline. SLA breaches are highlighted.
- Dashboard counts come from
COUNT/GROUP BYon the ticket table. - CSV export of the current filter.
- REST API under
/api/tickets/and/api/customers/with token auth, django-filter, and pagination. - OpenAPI schema at
/api/schema/and Swagger UI at/api/docs/(assets served by the app, not a CDN).
Demo logins
| Username | Password | Role |
|---|---|---|
| demo | demo | viewer, read-only, writes return 403 |
| agent | agent | agent, own tickets only |
| lead | lead | lead, can reassign and close |
The login page shows the same three rows. API tokens for these accounts are created by reset_demo and shown on the dashboard after login.
Run locally
Python 3.9 or newer. Django stays on 4.2 so the app can run on a 3.9 host.
python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txt
export SECRET_KEY=casenote-demo-key-change-before-any-real-use
.venv/bin/python manage.py migrate
.venv/bin/python manage.py reset_demo
.venv/bin/pytest -q
./run.sh creates .venv if needed, migrates, loads the seed the first time the sqlite file is missing, and starts gunicorn with 2 workers. The default bind is 127.0.0.1:18808. Open http://127.0.0.1:18808/portfolio/casenote/.
reset_demo deletes the seeded desk and writes it again. A second run does not duplicate tickets.
Subpath
FORCE_SCRIPT_NAME defaults to /portfolio/casenote. STATIC_URL defaults to /portfolio/casenote/static/. Generated links include that prefix. Requests that already include the prefix, and requests whose prefix was stripped by a reverse proxy, both resolve. Static files are served by WhiteNoise.
Settings that stay off the defaults
DEBUGisFalse.SECRET_KEYis read from the environment. The process refuses to start without it.ALLOWED_HOSTSandCSRF_TRUSTED_ORIGINScome from the environment.SECURE_PROXY_SSL_HEADERisX-Forwarded-Proto.- SQLite is the demo database (
DATA_DIR/casenote.sqlite3). Apostgres://orpostgresql://DATABASE_URLswitches the engine. Installpsycopgyourself before using that URL. This demo does not ship a Postgres service.
Process
deploy/casenote.service runs gunicorn with 2 workers, MemoryMax=200M, and the same hardening lines as the other portfolio units (NoNewPrivileges, PrivateTmp, ProtectSystem=strict, ProtectHome, ReadWritePaths on the data directory). The unit does not install itself.
SLA windows are clock hours from created_at: urgent 4, high 8, normal 24, low 72. Closed tickets are not marked breached.