Configuration¶
django-program reads its settings from a single DJANGO_PROGRAM dictionary in your
Django settings module. Internally this dict gets parsed into frozen dataclasses with
validation, so you get clear error messages if something is wrong.
Django Settings (DJANGO_PROGRAM)¶
Full example¶
DJANGO_PROGRAM = {
# Stripe payment gateway
"stripe": {
"secret_key": "sk_test_...",
"publishable_key": "pk_test_...",
"webhook_secret": "whsec_...",
"api_version": "2024-12-18", # default
"webhook_tolerance": 300, # seconds, default
},
# Pretalx schedule sync
"pretalx": {
"base_url": "https://pretalx.com", # default
"token": "your-api-token",
"schedule_delete_guard_enabled": True, # default
"schedule_delete_guard_min_existing_slots": 5, # default
"schedule_delete_guard_max_fraction_removed": 0.4, # default
},
# PSF sponsor API (PyCon US specific)
"psf_sponsors": {
"api_url": "https://www.python.org/api/v2", # default
"token": "your-psf-token",
"auth_scheme": "Token", # default
"publisher": "pycon", # default
"flight": "sponsors", # default
},
# Feature toggles (all default to True)
"features": {
"registration_enabled": True,
"sponsors_enabled": True,
"travel_grants_enabled": True,
"programs_enabled": True,
"pretalx_sync_enabled": True,
"public_ui_enabled": True,
"manage_ui_enabled": True,
"all_ui_enabled": True,
},
# General
"cart_expiry_minutes": 30, # default
"pending_order_expiry_minutes": 15, # default
"order_reference_prefix": "ORD", # default
"currency": "USD", # default
"currency_symbol": "$", # default
"max_grant_amount": 3000, # default, for travel grants
}
Stripe settings¶
Key |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
Stripe secret API key. Required for payment processing. |
|
|
|
Stripe publishable key for client-side checkout. |
|
|
|
Webhook signing secret for verifying Stripe events. |
|
|
|
Stripe API version to pin against. |
|
|
|
Maximum age (seconds) of a webhook event before it is rejected. |
Pretalx settings¶
Key |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
Base URL of your Pretalx instance. |
|
|
|
Pretalx API token for authenticated requests. |
|
|
|
When |
|
|
|
Minimum existing slots before the guard kicks in. |
|
|
|
Maximum fraction of slots that can be removed in a single sync before the guard aborts. |
The delete guard exists because the Pretalx /talks/ endpoint occasionally returns
404 on some instances. Without the guard, a sync would delete every existing slot in
the database. With the guard enabled (the default), the sync aborts if more than 40%
of existing slots would be removed when at least 5 slots already exist.
PSF sponsor settings¶
These are specific to PyCon US and the Python Software Foundation’s sponsor data API. Most conferences can ignore this section entirely.
Key |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
PSF API base URL. |
|
|
|
PSF API token. |
|
|
|
HTTP auth scheme prefix. |
|
|
|
Publisher identifier for the PSF API. |
|
|
|
Flight identifier for the PSF API. |
General settings¶
Key |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
Minutes before an inactive cart expires and releases its inventory hold. |
|
|
|
Minutes before a pending (unpaid) order expires. |
|
|
|
Prefix for generated order reference codes (e.g. |
|
|
|
ISO 4217 currency code used throughout the system. |
|
|
|
Display symbol for the currency. |
|
|
|
Maximum travel grant amount in the configured currency. |
Feature toggles¶
Feature toggles disable entire modules and UI sections per-conference. Every toggle
defaults to True (enabled). Disable a module by setting its flag to False in
settings, or override it per-conference through the database.
Settings defaults¶
Add a features dict to DJANGO_PROGRAM in your Django settings:
DJANGO_PROGRAM = {
"features": {
"registration_enabled": True,
"sponsors_enabled": True,
"travel_grants_enabled": False, # disable travel grants globally
"programs_enabled": True,
"pretalx_sync_enabled": True,
"public_ui_enabled": True,
"manage_ui_enabled": True,
"all_ui_enabled": True, # master switch for all UI
},
# ... other settings ...
}
Changing these values requires a server restart.
Available toggles¶
Module toggles control backend functionality:
Key |
Default |
Controls |
|---|---|---|
|
|
Ticket types, cart, checkout, orders |
|
|
Sponsor levels, benefits, comp vouchers |
|
|
Travel grant applications and review |
|
|
Activities and signups |
|
|
Speaker/talk/room sync from Pretalx |
UI toggles control interface visibility:
Key |
Default |
Controls |
|---|---|---|
|
|
Public-facing conference pages |
|
|
Organizer management dashboard |
|
|
Master switch – when |
Per-conference database overrides¶
Each conference can override the global defaults through a FeatureFlags row in the
database. These overrides take effect immediately without a server restart.
The FeatureFlags model uses nullable booleans with three states:
None(blank) – use the default fromDJANGO_PROGRAM["features"]True– force the feature on for this conferenceFalse– force the feature off for this conference
Resolution order for each flag:
If the conference has a
FeatureFlagsrow with an explicitTrueorFalse, that value wins.Otherwise, the default from
DJANGO_PROGRAM["features"]is used.For
public_uiandmanage_ui, theall_ui_enabledmaster switch is evaluated first.
Django admin¶
Open the Conference admin page. The Feature flags inline appears below the conference fields, grouped into “Module Toggles” and “UI Toggles”. Each dropdown offers three choices:
Default (enabled) – inherit from settings
Yes – force ON – override to enabled
No – force OFF – override to disabled
Feature flags are also available as a standalone admin model at Conference > Feature flags for a cross-conference overview.
Checking features in Python code¶
Use is_feature_enabled() to check a toggle at runtime:
from django_program.features import is_feature_enabled
# Check against global settings only
if is_feature_enabled("registration"):
...
# Check with per-conference DB override
if is_feature_enabled("sponsors", conference=my_conference):
...
Raise a 404 when a feature is disabled:
from django_program.features import require_feature
def my_view(request):
require_feature("registration", conference=request.conference)
# ... view logic ...
Guard a class-based view with the FeatureRequiredMixin:
from django_program.features import FeatureRequiredMixin
class TicketListView(ConferenceMixin, FeatureRequiredMixin, ListView):
required_feature = ("registration", "public_ui")
The mixin checks all listed features during dispatch(). If the view also uses
ConferenceMixin (placed before FeatureRequiredMixin in the MRO), per-conference
overrides are picked up automatically from self.conference.
Using features in templates¶
Add the context processor to your TEMPLATES setting:
TEMPLATES = [
{
"OPTIONS": {
"context_processors": [
# ... existing processors ...
"django_program.context_processors.program_features",
],
},
},
]
Then use program_features in your templates to conditionally render sections:
{% if program_features.registration_enabled %}
<a href="{% url 'registration:ticket-list' %}">Buy Tickets</a>
{% endif %}
{% if program_features.sponsors_enabled %}
<a href="{% url 'sponsors:sponsor-list' %}">Our Sponsors</a>
{% endif %}
The context processor resolves each flag through is_feature_enabled(), so master
switch logic and per-conference DB overrides are applied. When the request has a
conference attribute (set by middleware or the view), that conference’s FeatureFlags
row is consulted automatically.
Accessing config in code¶
from django_program.settings import get_config
config = get_config()
config.stripe.secret_key # str | None
config.pretalx.base_url # str
config.cart_expiry_minutes # int
config.currency # str
get_config() returns a frozen ProgramConfig dataclass. The result is cached and
the cache automatically clears when Django’s setting_changed signal fires (which
happens inside override_settings in tests).
TOML Bootstrap¶
The bootstrap_conference management command creates a full conference from a single
TOML file. Sections, ticket types, add-ons, and sponsor levels in one shot.
Running the command¶
# Create a new conference
uv run python manage.py bootstrap_conference --config conference.toml
# Update an existing conference (matched by slug)
uv run python manage.py bootstrap_conference --config conference.toml --update
# Preview without touching the database
uv run python manage.py bootstrap_conference --config conference.toml --dry-run
# Create conference + generate demo data (vouchers, users, orders, carts)
uv run python manage.py bootstrap_conference --config conference.toml --seed-demo
The entire operation runs inside a single database transaction. If anything fails, nothing is committed.
[conference] table (required)¶
[conference]
name = "PyCon US 2027" # required
start = 2027-05-14 # required, TOML native date
end = 2027-05-22 # required, TOML native date
timezone = "America/New_York" # required, IANA timezone
slug = "pycon-us-2027" # optional, auto-generated from name
venue = "Convention Center" # optional
pretalx_event_slug = "pycon-us" # optional, for Pretalx sync
website_url = "https://..." # optional
total_capacity = 2500 # optional, 0 = unlimited (default)
total_capacity sets the maximum number of tickets sold across all ticket types for the
entire conference. Add-ons do not count toward this limit. Set to 0 or omit entirely
for unlimited capacity. See Global Ticket Capacity
for enforcement details.
Sections (required, at least one)¶
[[conference.sections]]
name = "Tutorials"
start = 2027-05-14
end = 2027-05-15
slug = "tutorials" # optional, auto-generated from name
Tickets (optional)¶
[[conference.tickets]]
name = "Individual"
price = 100.00 # parsed as Decimal
quantity = 2500 # total_quantity
per_user = 1 # limit_per_user
voucher_required = false # optional, default false
available = { opens = 2025-01-15, closes = 2027-05-13 } # optional
Add-ons (optional)¶
[[conference.addons]]
name = "Tutorial: Intro to Django"
price = 150.00
quantity = 40
requires = ["individual", "corporate"] # ticket type slugs
Sponsor levels (optional)¶
[[conference.sponsor_levels]]
name = "Visionary"
cost = 150_000.00
comp_tickets = 25 # comp vouchers auto-generated per sponsor
Validation¶
The TOML loader validates:
All required fields are present in each table
Slugs are auto-generated from
nameif not providedSlugs are unique within their list (no duplicate section slugs, etc.)
Prices are parsed as
Decimalfor exact arithmeticDates are native TOML dates (not strings)
If validation fails, the loader raises ValueError or TypeError with a message
that tells you exactly which field and index caused the problem.
The --seed-demo flag¶
Passing --seed-demo generates sample transactional data on top of the bootstrapped
conference:
Vouchers – speaker comp, student comp, 20% early bird percentage, $25 fixed discount. Random codes are printed to stdout so you can test with them.
Demo users – Five users (
attendee_alice,attendee_bob,speaker_carol,attendee_dave,attendee_eve), all with passworddemoandis_staff=True.Orders – Alice with a paid individual ticket + t-shirt, Bob with a paid corporate ticket, Carol with a comp speaker ticket.
Carts – An open cart for Bob with an individual ticket and two t-shirts.
Credits – A $25 store credit for Alice.
Activities – Django Sprint, Newcomer Orientation, Open Source Workshop, Lightning Talks.
Travel Grants – One application in each status (submitted, accepted, offered, rejected, withdrawn) with realistic data.
This is for local development and demos only.