Conference management for community events. Multi-organization and multi-year from the start: one deployment serves several organizations, each running events across many years.
The first feature is video review: organizers pull talk videos from a video host, match them to talks, and invite speakers; speakers review their video, correct captions, and approve publication. See plans/video-review.md.
External services are pluggable. The app depends on capabilities (talk_source, ticketing,
video_host), not on Pretalx, Tito, or YouTube. Each event binds a capability to a provider, so
one event can use Pretalx and Tito while another uses something else entirely. See
plans/provider-integrations.md.
Bootstrap (M0). The tenancy foundation and provider abstraction exist; no feature code yet. Concrete provider adapters arrive with M1. Roadmap in plans/README.md.
Requires uv, just, direnv, and Docker.
cp .env.example .env # works as-is for local development
direnv allow
just up # builds images, starts postgres + app, applies migrationsThe app is at http://localhost:8000, the admin at http://localhost:8000/admin/, and captured outbound mail at http://localhost:8026 (mailpit). Nothing sends real email in development.
Host ports are overridable in .env (APP_PORT, POSTGRES_PORT, MAILPIT_UI_PORT,
MAILPIT_SMTP_PORT) for running this stack alongside another project's.
Create a login for yourself:
just createsuperuserDevelopment uses insecure fallbacks for SECRET_KEY and FIELD_ENCRYPTION_KEY so a fresh
checkout runs with no configuration. Both hard-fail startup when DEBUG=False, so a production
deploy cannot accidentally run with them.
just with no arguments lists everything. The ones you will actually use:
just up / down / bounce # stack lifecycle; bounce after dependency changes
just follow # tail logs
just manage <command> # any manage.py command
just migrate / makemigrations
just shell / dbshell
just test # full suite
just test-unit # no database, fast
just test-integration # needs the database
just lint / just format # ruff; `fmt` aliases format
just check # lint + format check + tests, the local gateRecipes work both from the host and from inside the app container.
src/
manage.py
project/ settings, urls, wsgi/asgi, logging — no application logic
common/ BaseModel: UUIDv7 PK, created_at/updated_at (not a Django app)
accounts/ User (email as username, passwordless), LoginToken
events/ Organization, OrganizationMembership, Event
integrations/ ProviderConnection, EventProviderBinding, SyncRun
providers/ capability protocols and per-provider adapters
templates/ single tree, organized by app, partials/ for HTMX fragments
plans/ forward-looking design docs, issues.md, nits.md
Reference docs get a docs/ tree once there is any reference material; right now everything
forward-looking lives in plans/.
Two-level tenancy: Organization → Event, where an Event is one iteration (PyOhio 2026).
Event.series groups iterations for year-over-year reporting.
Credentials for external providers are held per organization on ProviderConnection, encrypted
at rest, and selected per event by EventProviderBinding:
Organization "PyOhio"
ProviderConnection pretalx-pyohio-2026 capability=talk_source provider=pretalx
ProviderConnection tito-pyohio capability=ticketing provider=tito
ProviderConnection youtube-pyohio capability=video_host provider=youtube
Event "PyOhio 2026"
EventProviderBinding talk_source -> pretalx-pyohio-2026 config={"event_id": "pyohio-2026"}
EventProviderBinding video_host -> youtube-pyohio config={"playlist_id": "PL..."}
An organization can hold several connections for the same provider, because some providers issue credentials per event — Pretalx does.
Full specification in plans/data-model.md.
FIELD_ENCRYPTION_KEY encrypts every organization's provider credentials. Losing it means
losing all of them, with no recovery: back it up before going live. Credentials are write-only
through the admin, redacted in logs by a structlog processor, and stored as ciphertext so they
are not readable in a database dump.
- plans/README.md — roadmap and index of design docs
- plans/decisions.md — bootstrap decisions, rationale, and what was deferred
- plans/data-model.md — schema specification
- plans/provider-integrations.md — capability/adapter architecture
- plans/video-review.md — M1
- plans/confdash-port.md — M2, porting the legacy project
- CLAUDE.md — conventions and architecture for working in this repo
This replaces the original confdash (../confdash), which stays running until the port in M2
lands. Nothing here depends on it at runtime.