Multi-tenancy

Midsummer is multi-tenant: a single deployment serves many independent convention organizations, fully isolated from one another. The isolation is schema-based, provided by django-tenants.

The model

  • One PostgreSQL database.

  • A public schema holds shared data: Tenant, Domain, CustomDomain, User, Membership, and Django’s own core apps (auth, admin, contenttypes, oauth2_provider, …).

  • One schema per tenant holds the tenant’s event data: every tenant app (event, register, staff, vendors, schedule, shop, front, kioskos, utility) is provisioned into each tenant’s schema.

public schema                tenant:furrydelphia schema   tenant:uproar schema
  Tenant, Domain,              Event, Registration,        Event, Registration,
  CustomDomain, User,          Vendor, Staff, …            Vendor, Staff, …
  Membership, auth core

A User is shared (one identity across all tenants). Their per-tenant role lives in Membership (isTenantSuperuser, developer flags).

How a request lands in the right schema

TenantMainMiddleware (top of the middleware stack) reads the request host, looks up the matching Domain → Tenant, and switches the DB connection to that tenant’s schema for the duration of the request. It also sets the URLconf: the tenant URLconf for tenant domains, the public URLconf for the public domain. See Request resolution for the full walk.

Implications for development

Migrations are schema-aware

  • Shared-table migrations run with migrate_schemas --shared.

  • Tenant-table migrations are applied to every tenant’s schema. django-tenants provides migrate_schemas and tenant_command / all_tenants_command to run a management command across all tenant schemas.

python manage.py migrate_schemas --shared        # shared/public schema
python manage.py migrate_schemas                 # all tenant schemas
python manage.py all_tenants_command event__generate_properties

Commands run inside a tenant context

When you run a management command that touches tenant data, it executes against one tenant’s schema (typically the configured/default tenant locally). Some commands derive behavior from connection.schema_name, so they must be run from inside the owning tenant’s context. Example: register__export_reg_timeline writes per-event data to <dir>/<schema_name>/<event_slug>/ and errors if the schema is public.

Shared vs. tenant app lists

Defined in midsummer/settings.py (SHARED_APPS / TENANT_APPS). When you add a new app, decide which list it belongs in:

  • Shared if its data is identical for every tenant (identity, auth, tenant org metadata).

  • Tenant if it holds event/operational data that differs per tenant.

One event per tenant (today)

In practice each tenant runs one active event. Events are “swapped in and out” — true concurrent multi-event access is a documented future goal. Locally you usually have a single defaultdev tenant with one event.

Where to look

  • Settings: midsummer/settings.py (SHARED_APPS, TENANT_APPS, PUBLIC_SCHEMA_URLCONF, TENANT_URLCONF).

  • Middleware: midsummer/middleware.py (TenantMainMiddleware).

  • Tenant model: tenant/models.py.