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 DomainTenant, 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.