Configuration¶
All runtime settings are controlled through environment variables. There is no separate
settings_local.py:
event_talks/settings.py
reads each value from the environment (via django-environ) and applies a sensible default when the
variable is unset.
How settings are loaded¶
The project follows the 12-factor convention: operating system environment variables always win. On top of that, the app can optionally read variables from a file.
DJANGO_READ_VARS_FILE(defaultFalse). WhenTrue, settings are also read fromdjango-vars.envin the project root. OS environment variables still take precedence over file values.scripts/dev-setup.shsets this automatically:Truewhendjango-vars.envis present,Falseotherwise. So a normal local checkout reads from the committed file.
READ_VARS_FILE = env.bool("DJANGO_READ_VARS_FILE", default=False)
if READ_VARS_FILE:
environ.Env.read_env(BASE_DIR / "django-vars.env")
Keep django-vars.env and docker/.env in sync
There are two environment files. django-vars.env drives local development; docker/.env drives
the Docker Compose production stack. When you add, rename, or remove a variable in one, you must
make the matching change in the other in the same commit. They are intentionally kept aligned so a
setting that exists in dev is not silently missing in production.
Never commit real secrets
django-vars.env and docker/.env are tracked, so they may only contain placeholders or safe local
defaults. The committed DJANGO_SECRET_KEY is an obvious dev-only placeholder and must be replaced
in production. Real tokens (Pretalx, Vimeo, Mailgun, Discord) and the real secret key belong only in
the deployment environment, never in a commit.
In the tables below, "Default" is the value settings.py falls back to when the variable is unset.
Where the committed django-vars.env differs (because it targets local development), that is noted.
Core Django¶
| Variable | Default | Purpose |
|---|---|---|
DJANGO_DEBUG |
False |
Debug mode. django-vars.env sets True for local development. |
DJANGO_SECRET_KEY |
unsafe-secret-key |
Cryptographic signing key. Must be replaced in production. |
DJANGO_ALLOWED_HOSTS |
empty | Comma-separated hosts Django will serve. Dev file sets localhost,.... |
SITE_ID |
1 |
django.contrib.sites site id. |
LANGUAGE_CODE |
en |
Default/source language. One of settings.LANGUAGES (en, pt-br, de, es). |
TIME_ZONE |
Europe/Berlin |
Project time zone. |
USE_I18N |
True |
Enable Django translation machinery. |
USE_TZ |
True |
Store datetimes as timezone-aware UTC. |
LANGUAGE_COOKIE_SECURE |
True |
Mark the django_language cookie Secure. Set False for local dev (no HTTPS). |
DJANGO_ADMIN_URL |
admin/ |
Path prefix for the Django admin site. |
ADMIN_NAMES |
Admin |
Comma-separated admin display names (zipped with ADMIN_EMAILS). |
ADMIN_EMAILS |
admin@example.com |
Comma-separated admin emails. Used as ADMINS / MANAGERS. |
Static and media files¶
| Variable | Default | Purpose |
|---|---|---|
DJANGO_STATIC_URL |
static/ |
URL prefix for static assets. |
DJANGO_STATIC_ROOT |
<project>/staticfiles |
Where collectstatic writes files. |
DJANGO_STATICFILES_DIRS |
<project>/static |
Extra source directories for static files. |
DJANGO_STATICFILES_STORAGE |
StaticFilesStorage |
Set to ManifestStaticFilesStorage in prod for hashed names. |
DJANGO_DEFAULT_FILE_STORAGE |
FileSystemStorage |
Default file storage backend. |
DJANGO_MEDIA_URL |
/media/ |
URL prefix for uploaded/generated media. |
DJANGO_MEDIA_ROOT |
media |
Directory for media files (resolved under the project root). |
DJANGO_SERVE_STATIC_LOCALLY |
False |
Let Django serve static/media when running without Nginx. |
Database¶
The database is configured from a single connection URL.
| Variable | Default | Purpose |
|---|---|---|
DATABASE_URL |
sqlite:///db.sqlite3 |
Connection string. Use a postgres://... URL in production. |
DJANGO_CONN_MAX_AGE |
60 |
Seconds to keep a persistent connection open. Ignored when the pool is enabled. |
DJANGO_CONN_HEALTH_CHECKS |
True |
Detect and replace stale persistent connections. |
DJANGO_DB_POOL |
False |
Postgres + psycopg3 native connection pool. Leave off on SQLite and under ASGI. |
Connection pool vs CONN_MAX_AGE
These are mutually exclusive. With DJANGO_DB_POOL=True (Postgres only), Django manages a pool and
DJANGO_CONN_MAX_AGE is ignored. The default keeps a single connection warm per worker for up to
CONN_MAX_AGE seconds, which works on any backend including SQLite.
Email¶
Login codes and admin notifications are sent by email. In development this points at Mailpit; in production it uses Mailgun via django-anymail.
| Variable | Default | Purpose |
|---|---|---|
DJANGO_EMAIL_BACKEND |
django.core.mail.backends.smtp.EmailBackend |
Email backend. Set to the Mailgun Anymail backend in prod. |
EMAIL_HOST |
localhost |
SMTP host (Mailpit in dev). |
EMAIL_PORT |
1025 |
SMTP port. The setup script points dev at Mailpit on 1026. |
EMAIL_TIMEOUT |
10 |
SMTP socket timeout in seconds. |
DEFAULT_FROM_EMAIL |
webmaster@localhost |
Default From address. |
SERVER_EMAIL |
root@localhost |
From address for error mail to admins. |
MAILGUN_API_KEY |
empty | Mailgun API key (required when the Anymail backend is used). |
MAILGUN_API_URL |
https://api.eu.mailgun.net/v3 |
Mailgun API base URL. |
MAILGUN_SENDER_DOMAIN |
empty | Mailgun sending domain (required with the Anymail backend). |
Mailgun settings are only read when the Anymail backend is active
The MAILGUN_* variables are only consulted when DJANGO_EMAIL_BACKEND is set to
anymail.backends.mailgun.EmailBackend. With the default SMTP backend they are ignored.
Authentication and accounts¶
These tune the passwordless email-code login. See Authentication for the full flow.
| Variable | Default | Purpose |
|---|---|---|
LOGIN_REDIRECT_URL |
home |
Where to send users after a successful login. |
ACCOUNT_LOGIN_BY_CODE_TIMEOUT |
300 |
Seconds a login code stays valid. |
ACCOUNT_EMAIL_SUBJECT_PREFIX |
empty | Prefix added to account email subjects. |
ALLAUTH_TRUSTED_PROXY_COUNT |
0 |
Number of trusted reverse proxies. Set to 1 behind the bundled Nginx. |
Set the proxy count correctly behind a proxy
ALLAUTH_TRUSTED_PROXY_COUNT must reflect how many trusted proxy hops sit in front of the app. If
it stays 0 behind Nginx, every request looks like 127.0.0.1, so allauth's per-IP rate limits
collapse into one global bucket and a single attacker can lock everyone out. The Docker/Nginx
deployment sets it to 1.
Email validation API¶
A new attendee's email is checked against an external validation API to confirm a ticket purchase
before an account is created. An event can define its own validation_api_url; otherwise the
fallback below is used.
| Variable | Default | Purpose |
|---|---|---|
EMAIL_VALIDATION_API_URL_FALLBACK |
empty | Validation API URL used when an event does not define its own. |
EMAIL_VALIDATION_API_TIMEOUT |
10 |
Timeout in seconds for validation API calls. |
AUTHORIZED_EMAILS_WHITELIST |
ADMIN_EMAILS |
Emails that bypass API validation entirely. |
EMAIL_VALIDATION_API_OAUTH2_CLIENT_ID |
empty | OAuth2 client id for authenticated validation calls. |
EMAIL_VALIDATION_API_OAUTH2_CLIENT_SECRET |
empty | OAuth2 client secret. |
EMAIL_VALIDATION_API_OAUTH2_TOKEN_URL |
empty | OAuth2 token endpoint. All three OAuth2 values are required to enable. |
Discord OAuth¶
Optional login with Discord, gated by guild membership and role. See Authentication for how roles map to permissions.
| Variable | Default | Purpose |
|---|---|---|
DISCORD_CLIENT_ID |
empty | Discord OAuth application client id. |
DISCORD_CLIENT_SECRET |
empty | Discord OAuth application client secret. |
DISCORD_GUILD_ID |
empty | Numeric id of the Discord server checked for membership. |
DISCORD_API_TIMEOUT |
5 |
Timeout in seconds for Discord API calls. |
DISCORD_ROLES |
{} |
JSON map of role name to role id (e.g. {"attendee":"BBBBB"}). |
DISCORD_ALLOWED_ROLES |
empty | Role names permitted to log in. Empty means no Discord access. |
DISCORD_ADMIN_ROLES |
empty | Role names that grant is_superuser and is_staff. |
DISCORD_STAFF_ROLES |
empty | Role names that grant is_staff only. |
An empty DISCORD_ALLOWED_ROLES rejects all Discord logins
Discord access is opt-in. With no allowed roles configured, every Discord login is rejected. Roles
in DISCORD_ROLES must use the same names referenced by the allowed/admin/staff lists.
Pretalx sync¶
Talk import from the Pretalx REST API. See Pretalx sync for the importer modes and workflow.
| Variable | Default | Purpose |
|---|---|---|
PRETALX_API_TOKEN |
empty | API token used by import_pretalx_talks. |
PICKLE_PRETALX_TALKS |
False |
Cache fetched Pretalx data to disk to speed up repeated imports. |
PRETALX_DIGEST_RECIPIENTS |
falls back to ADMINS |
Recipients of the detect-only summary email. Set to - to disable the digest. |
Livestreams and video¶
| Variable | Default | Purpose |
|---|---|---|
LIVESTREAMS_SHEET_ID |
empty | Google Sheet id used by import_livestream_urls. |
LIVESTREAMS_WORKSHEET_NAME |
Livestreams |
Worksheet name within that sheet. |
VIMEO_ACCESS_TOKEN |
empty | Vimeo API token used by update_video_links. |
VIMEO_PROJECT_IDS |
empty | Comma-separated Vimeo project ids to scan for recordings. |
Social cards¶
Talk social cards are rendered with a configurable font.
| Variable | Default | Purpose |
|---|---|---|
TALK_CARD_FONT |
assets/fonts/NotoSans.ttf |
Path to the TrueType font used for card text. |
TALK_CARD_FONT_NAME |
Noto Sans |
Font family name reported in card metadata. |
Logging¶
| Variable | Default | Purpose |
|---|---|---|
LOG_LEVEL |
INFO |
Level for the event_talks, users, and talks loggers. |
AUTH_LOG_LEVEL |
INFO |
Level for the dedicated auth logger (writes to auth.log). |
DJANGO_DATABASE_LOG_LEVEL |
ERROR |
Level for django.db.backends. |
DJANGO_LOGS_DIR |
<project>/logs |
Directory for rotating JSON log files. |
LOG_EMAIL_HASH |
True |
Hash email addresses in logs instead of writing them in the clear. |
Email privacy in logs
With LOG_EMAIL_HASH=True, emails are recorded as a SHA-256 hash rather than plaintext, so log
files do not leak attendee addresses. This is described further in
Authentication.
Error tracking (Sentry)¶
Sentry is only initialized when a DSN is set, so dev and test stay quiet.
| Variable | Default | Purpose |
|---|---|---|
SENTRY_DSN |
empty | Sentry project DSN. Leave blank to disable Sentry. |
SENTRY_ENVIRONMENT |
production (or development if debug) |
Environment tag reported to Sentry. |
SENTRY_TRACES_SAMPLE_RATE |
0.0 |
Performance trace sampling rate (errors are always captured). |
SENTRY_SEND_DEFAULT_PII |
False |
Whether to send user identifiers. Off by default for privacy. |
Security and cookies¶
These default to safe production values, so the local django-vars.env relaxes them for HTTP
development.
| Variable | Default | Purpose |
|---|---|---|
SESSION_COOKIE_SECURE |
True |
Send the session cookie over HTTPS only. Dev file sets False. |
CSRF_COOKIE_SECURE |
True |
Send the CSRF cookie over HTTPS only. Dev file sets False. |
DJANGO_CSRF_TRUSTED_ORIGINS |
http://localhost,http://127.0.0.1 |
Origins trusted for CSRF. |
SECURE_HSTS_SECONDS |
31536000 |
HSTS max-age (1 year). Dev file sets 0 to disable HSTS. |
SECURE_HSTS_INCLUDE_SUBDOMAINS |
True |
Apply HSTS to subdomains. |
SECURE_HSTS_PRELOAD |
False |
Opt in to the HSTS preload list. Off by default. |
DATA_UPLOAD_MAX_NUMBER_FIELDS |
3000 |
Max form fields per request (raised to manage many users at once). |
Other security headers (X-Frame-Options: DENY, content-type nosniff, referrer policy, the
cross-origin opener policy, and the SECURE_PROXY_SSL_HEADER) are set in code and are not
environment-configurable.
Feature flags and event behaviour¶
| Variable | Default | Purpose |
|---|---|---|
DEFAULT_EVENT |
empty | Slug of the event pre-selected on the login page and linked on signup. |
SHOW_UPCOMING_TALKS_LINKS |
False |
Show links to upcoming talks. Dev file sets True. |
IMPORT_TALKS_WITHOUT_SPEAKERS |
True |
Allow Pretalx import of talks that have no speakers yet. |
CHAIR_ROOM_TRANSITION_MINUTES |
5 |
Warn session chairs about room transitions tighter than this. |
Multi-event behaviour¶
This instance can host several conferences at once. Each Event has its own slug, branding, talks,
and validation API URL, and users are linked to the events they have access to.
DEFAULT_EVENTis the slug shown pre-selected on the login page. The current default ispyconde-pydata-2026. New users authorizing via Discord are linked to this event, and the login view offers it as the default selection.- A regular user only sees the active events they are linked to. Superusers see all active events.
- The committed dev defaults set
DEFAULT_EVENT=pyconde-pydata-2026, anddev-setup.shcreates thepyconde-pydata-2025,pyconde-pydata-2026, andpydata-berlin-2026sample events.
For how events relate to talks, users, and the rest of the data model, see Architecture.