Permissions

Midsummer layers several permission concepts. This page is the mental model you need before gating any view.

The layers

  1. Django per-app permissions — standard auth permissions per app (<app>.<action>_<model>).

  2. Event admin roles — six scoped roles that grant module-level admin powers within an event: admin_event, admin_schedule, admin_staff, admin_registrar, admin_vendors, admin_shop, plus admin_my.

  3. Tenant rolesMembership.isTenantSuperuser and developer flags (per-tenant, on the shared User).

  4. Permissive department leadership — a staff member who is leadership of a department that owns a module (ModuleOwnership) gains elevated access to that module’s department-scoped data.

The resolver: check_permission(...)

Views gate on check_permission(request, '<module>'), which returns a level string — commonly something like 'none', 'staff', 'admin', 'superadmin'. A typical guard:

if check_permission(request, 'register') not in ('admin', 'superadmin'):
    return Response(..., status=403)

This helper lives in midsummer/common/permissions.py. It folds together the event admin role, the tenant superuser flag, and the leadership model into one answer, so most views only need a single check.

The “permissive” leadership model

Staff permissions are deliberately permissive: if you are leadership of a department that owns a module, you get that module’s elevated access even without an explicit event-admin role. This means membership type (staff, volunteer, deputy, leadership) and department ownership both matter.

  • staff__check_permissions is a diagnostic command to see exactly what a given user can do, including a --hr simulation that excludes deputies/staff/ volunteers from leadership-level permissions.

  • staff__check_menu simulates what menu items a user would see.

Known pitfall: isTenantAdmin

Warning

The staff roster code path references a Membership.isTenantAdmin field that does not exist on the model. It’s masked by a short-circuit today, so it hasn’t blown up, but it is a latent bug. For any new tenant-admin gating, use the real Membership.isTenantSuperuser field.

UI gating

The Python-side permission check is the source of truth. Frontend pages are served through angular_page / angular_page_event_protected; per-module page routes (e.g. system/audit-log/) are permission-gated in Python before the SPA shell is served, so a hidden menu link can’t bypass the backend check.

The menu itself is built from midsummer/common/ui.py (submenus, trigger values, menu definitions) using the same permission resolution, so what a user sees matches what they can access.

Where to look

  • Resolver: midsummer/common/permissions.py (check_permission)

  • Menu/UI gating: midsummer/common/ui.py, midsummer/common/decorators.py

  • Staff department model: staff/models.py (Department, ModuleOwnership)

  • Diagnostics: staff/management/commands/staff__check_permissions.py, staff/management/commands/staff__check_menu.py