Skip to content

Django Plugin for Team Coordination & Shift Scheduling Technical Roadmap #6

Description

@MukundC25

Summary

eventyay-teamshifts is a standalone Django plugin for eventyay that brings native team and shift management into the platform. It covers the entire team lifecycle: organiser opens a Call for Team Members / Volunteers / Staff, members apply and select roles, organiser reviews and accepts applications, shifts are built on a drag-and-drop grid editor, accepted team members are assigned to shifts, and everyone gets email notifications and a personal calendar view. The plugin is modelled on Engelsystem (the PHP volunteer coordination tool used at large open-source events) but built natively into eventyay using its existing Django, DRF, and Vue 3 architecture.


Background and Problem Statement

Large events run entirely on coordinated teams. Today eventyay has no built-in team management. Organisers manage team/volunteers through Engelsystem (a separate PHP server that must be self-hosted), Google Sheets, or plain email. This creates:

  • A broken workflow: ticket sales in eventyay, team coordination outside it
  • No data connection between attendees/speakers and their team shifts
  • No per-event scope: Engelsystem is a separate instance, not tied to a specific event's data model

What eventyay has today:

Feature Status
Call for Papers Available
Schedule editor Available
Ticket sales and orders Available
Video rooms Available
Team / Volunteer management Not available

This plugin fills that gap entirely within eventyay's existing architecture, with no external dependencies.


Table of Contents


Feature Overview

The plugin delivers the following end-to-end features:

Organiser-side (control panel):

  • Configure a Call for Volunteers / Team / Staff: open/close toggle, deadline, description
  • Define team roles (e.g. AV Tech, Registration Desk, Room Monitor)
  • Review team/staff member applications: filter by status and role, accept or reject
  • Build shift schedule using a drag-and-drop visual grid (Vue app)
  • Assign accepted team members/staff to shifts from a table-based panel
  • Designate a shift moderator (team lead) per shift
  • View team member stats: hours contributed per member/staff

Team member / staff-side (public pages, login required):

  • Browse open roles and submit a member/volunteer application
  • View personal shift schedule ("My Shifts") as a day-by-day calendar
  • Download personal schedule as an iCal file (stretch goal)

Automated (background):

  • Email confirmation when assigned to a shift
  • Email notification when an application is rejected
  • Automated 24-hour shift reminder via Celery Beat

Architecture Overview

Plugin Registration

eventyay discovers plugins by reading the pretix.plugin entry point group at startup (settings.py uses importlib_metadata.entry_points(group='pretix.plugin') and adds each ep.module to INSTALLED_APPS). This plugin registers itself as:

[project.entry-points."pretix.plugin"]
teamshifts = "teamshifts"

Django then loads teamshifts.apps.TeamShiftsApp (which subclasses eventyay.base.plugins.PluginConfig and declares EventyayPluginMeta) and the plugin appears in the eventyay plugin list.

Tech Stack

Layer Technology
Language Python 3.12
Web framework Django 5.2+
Database PostgreSQL
Task queue Celery + Redis
REST API Django REST Framework
Frontend Vue 3 + TypeScript + Vite (forked from schedule-editor)
Multi-tenancy django-scopes
i18n fields django-i18nfield
Testing pytest-django, DRF APIClient
CI GitHub Actions

Data Models

Event (from eventyay core)
  |
  +-- CallForVolunteers/Team/Staff          one per event; active flag, deadline, description
  |         |
  |         +-- TeamMemberApplication   per user per role; status: pending/accepted/rejected
  |
  +-- TeamRole              many per event; name, description, required_count
  |         |
  |         +-- Shift                  time slot; location, start_time, end_time, capacity
  |                   |
  |                   +-- ShiftAssignment    member <-> shift; is_moderator, notified,
  |                                          reminder_sent, assigned_by, assigned_at
  |
  +-- User (from eventyay core)
            |
            +-- TeamMemberApplication (as applicant)
            +-- ShiftAssignment (as team member or assigned_by)

All models use ScopedManager(event='event') for multi-tenant ORM isolation.

Key Architecture Rules

All code in this plugin must comply with eventyay's non-negotiable architectural rules:

  1. Multi-tenancy — every ORM query wrapped with django_scopes.scope(event=event). Queries without a scope will raise ScopeError by default.
  2. Permissions — all organiser views use EventPermissionRequiredMixin + permission_required = 'can_change_event_settings'.
  3. ORM efficiencyselect_related and prefetch_related required on all list queries to prevent N+1.
  4. Importseventyay.* namespace only; no new pretix.* imports in plugin code.
  5. JavaScript — no jQuery, no inline <script> blocks; all JS via external ES modules with data- attribute config passing.
  6. Atomic capacity enforcementselect_for_update() inside transaction.atomic() for all assignment writes.

URL and View Structure

All plugin URLs are auto-discovered by eventyay's maindomain_urlconf.py and registered under the plugins:teamshifts namespace. The plugin defines its own urls.py with top-level paths.

# Organiser views (event-scoped, requires can_change_event_settings)
/teamshifts/event/<org>/<event>/                            Dashboard
/teamshifts/event/<org>/<event>/settings/                   CFV settings
/teamshifts/event/<org>/<event>/roles/                      Team roles CRUD
/teamshifts/event/<org>/<event>/applications/               Application review panel
/teamshifts/event/<org>/<event>/assignments/                Shift assignment panel
/teamshifts/event/<org>/<event>/editor/                     Shift editor (Vue app)

# User views (login required, not event-scoped)
/teamshifts/my-shifts/                                      Personal shift calendar
/teamshifts/my-shifts/calendar.ics                          iCal export (stretch)

# Public views (event-scoped, login required for submission)
/teamshifts/event/<org>/<event>/apply/                      Public team member sign-up form
/teamshifts/event/<org>/<event>/shifts/                     Public shift board (stretch)

# Moderator views (event-scoped, assigned moderators only)
/teamshifts/event/<org>/<event>/moderator/                  Moderator dashboard

# REST API (event-scoped, organiser-only, DRF)
/teamshifts/event/<org>/<event>/api/shifts/                 GET, POST shift list/create
/teamshifts/event/<org>/<event>/api/shifts/<id>/            PATCH, DELETE shift detail
/teamshifts/event/<org>/<event>/api/shifts/<id>/assign/     POST assign team member
/teamshifts/event/<org>/<event>/api/shifts/<id>/assign/<staff_id>/  DELETE unassign
/teamshifts/event/<org>/<event>/api/teams/                  GET teams list
/teamshifts/event/<org>/<event>/api/applications/           GET applications list

Routing notes:

  • PermissionMiddleware in eventyay core is patched to recognise /teamshifts/ paths for auth and event resolution.
  • control/context.py allows /teamshifts/ for organiser sidebar context.
  • The plugin uses its own teamshifts/base.html template with an isolated sidebar (not inheriting the Tickets sidebar).
  • Component panel visibility is driven by event_dashboard_components signal (EventPluginSignal) — no context variables needed.

Phase Breakdown

Phase 1 — Plugin Scaffold and Core Models

Goal: Working plugin that installs, is auto-discovered, and has all five models migrated and accessible in admin.

Issues in this phase:

Deliverable: Plugin appears in eventyay plugin list; dashboard panel visible; manage.py migrate succeeds; models visible in /django-admin/.

Acceptance Criteria:

  • Plugin is auto-discovered by eventyay on startup and listed in the organiser plugin panel
  • TeamShifts panel appears as 4th box on event dashboard via event_dashboard_components signal
  • event_dashboard_widgets numwidget appears when plugin is enabled
  • Both disappear when plugin is disabled (signal dispatch gating)
  • Top-level /teamshifts/event/<org>/<event>/ URL returns 200 for authorised users
  • manage.py check passes with no errors
  • manage.py migrate applies initial migration cleanly from a blank database
  • EventTeam and EventStaff models are visible and usable in Django admin
  • Editable install (uv pip install -e .) works from a clean environment

Phase 2 — Call for Volunteers / Team / Staff

Goal: Complete CFV workflow — organiser configures roles and opens the CFV, members apply, organiser reviews and accepts/rejects.

Issues in this phase:

Deliverable: End-to-end CFV flow working — member submits, organiser accepts, application status updated.

Acceptance Criteria:

  • Organiser can open and close the CFV, set a deadline, and save a description — persisted correctly in the database
  • Duplicate role name for the same event is rejected with a validation error
  • Deleting a role that has existing applications or shifts is blocked
  • Unauthenticated and non-organiser users receive 403 on all organiser views
  • Member can submit an application with one or more role selections and availability notes
  • Re-submission by the same user for the same event shows existing status instead of a blank form
  • CFV closed state shows a "not accepting applications" page to members
  • Organiser can filter applications by status and role, and search by email or name
  • Individual and bulk accept/reject actions update application.status correctly

Phase 3 — Shift Scheduling REST API

Goal: Complete DRF REST API that the Vue shift editor and assignment panel will consume.

Issues in this phase:

  • 7 — DRF serializers for shifts, roles and applications (area: api): ShiftSerializer (with filled_count and is_full computed fields), VolunteerRoleSerializer, ShiftAssignmentSerializer, VolunteerApplicationSerializer. Read/write split on nested fields.
  • 8 — Shift CRUD API endpoints (area: api): GET, POST, PATCH, DELETE for /api/shifts/ and GET for /api/roles/. Event-scoped, organiser-only, paginated, with ?role= and ?date= filters.
  • 9 — Volunteer assignment API and capacity enforcement (area: api): POST /api/shifts/<id>/assign/ with acceptance check and select_for_update() capacity enforcement; DELETE /api/shifts/<id>/assign/<volunteer_id>/; GET /api/applications/?status=accepted.

Deliverable: Full REST API verified via APIClient tests and Postman/httpie manual testing.

Acceptance Criteria:

  • All four serializers exist with correct fields; filled_count and is_full return accurate values
  • GET /api/shifts/ returns only shifts belonging to the requested event (no cross-event leakage)
  • POST, PATCH, DELETE on /api/shifts/ succeed and persist correctly
  • ?role= and ?date= filters on the shifts list work correctly
  • POST /api/shifts/<id>/assign/ returns 400 if volunteer is not accepted, 409 if shift is at capacity
  • select_for_update() prevents concurrent over-assignment (tested with simultaneous requests)
  • Unauthenticated requests return 401; non-organiser requests return 403
  • GET /api/applications/?status=accepted returns only accepted applications for the event
  • API test coverage reaches at least 80% at this stage

Phase 4 — Shift Editor Vue App

Goal: Working drag-and-drop shift editor adapted from the existing schedule-editor Vue app, wired to the TeamShifts API.

Issues in this phase:

  • 10 — Reuse schedule-editor and Vite build setup (area: vue): Copy webapp/schedule-editor/ to static/teamshifts/shift-editor/, update package.json and vite.config.ts, verify npm run build produces dist/, add CI step.
  • 11 — Shift grid and shift card Vue components (area: vue): Adapt GridSchedule.vueShiftGrid.vue (columns = shift locations, rows = timeslots); adapt Session.vueShiftCard.vue (role name, filled/capacity badge, capacity colour coding); update TypeScript Shift type in schemas.ts.
  • 12 — Vue app API integration and Django template wiring (area: vue, area: views): Update api.ts to call TeamShifts endpoints; pass apiBase and csrfToken via data- attributes on mount element; wire built assets into editor.html via {% static %}.

Deliverable: Shift editor loads in browser, renders shifts from API, drag-and-drop repositioning persists via PATCH.

Acceptance Criteria:

  • npm install and npm run build complete without errors; CI build step passes
  • Shift editor loads in the browser at /control/.../teamshifts/editor/ with no console errors
  • Shift grid renders all existing shifts from the API with correct role, location, and time
  • Drag-and-drop reordering calls PATCH /api/shifts/<id>/ and the change persists after page reload
  • Creating a shift from the unassigned panel calls POST /api/shifts/ and the new shift appears in the grid
  • Deleting a shift card calls DELETE /api/shifts/<id>/
  • No hardcoded schedule-editor API URLs remain in api.ts
  • CSRF token is correctly sent on all mutating requests (no 403 from CSRF middleware)
  • Static assets load without 404 errors in the browser network tab

Phase 5 — Volunteer Assignment and Moderators

Goal: Organiser assigns volunteers to shifts from a table panel; moderator roles managed; capacity fully enforced.

Issues in this phase:

  • 13 — Organiser assignment panel (area: views): Table of shifts with filled/capacity, dropdown to select accepted volunteers, assign/unassign actions. Introduces services.py with assign_volunteer() shared by both this view and the API.
  • 14 — Moderator management and moderator view (area: views, area: auth): is_moderator toggle in assignment panel (one per shift enforced); moderator-only view at /moderator/ showing their shifts and team members (read-only).
  • 15 — Capacity enforcement — backend and frontend (area: api, area: vue): Harden select_for_update() atomic block; structured 409 error response; ShiftCard capacity badge colour states; disabled drop targets on full shifts; toast notification on API 409.

Deliverable: Organiser can build a complete volunteer roster; moderators can see their assigned teams; capacity cannot be exceeded by any path.

Acceptance Criteria:

  • Assignment panel lists all shifts with correct filled/capacity counts
  • Only accepted volunteers appear in the assignment dropdown
  • Assigning a volunteer increments the filled count; unassigning decrements it
  • Over-capacity assignment is blocked at the API (HTTP 409) and the UI disables the Assign button
  • Only one volunteer per shift can hold is_moderator=True; toggling a new moderator unsets the previous one
  • Moderator view at /moderator/ shows only shifts where the logged-in user is the moderator
  • Moderator view is read-only — no assignment controls are present
  • Concurrent assignment test: two simultaneous requests on a capacity-1 shift — only one succeeds
  • services.assign_volunteer() is the single code path used by both the API and the assignment panel view

Phase 6 — Calendar View and Email Notifications

Goal: Volunteer personal shift calendar plus Celery-powered transactional and scheduled email notifications.

Issues in this phase:

  • 16 — Volunteer personal shift calendar view (area: views): /my-shifts/ page showing all assignments grouped by day; role name, location, time, moderator badge; mobile-responsive layout; role colour-coding.
  • 17 — Celery email tasks — assignment confirmation and rejection (area: email): Implement send_assignment_confirmation and send_rejection_notification tasks using eventyay.base.services.mail; sets assignment.notified = True; max_retries=3 with backoff; plain-text email templates.
  • 18 — Celery Beat 24-hour shift reminder (area: email): Hourly periodic task finds shifts starting in 23–25 hour window; sends reminder to unnotified volunteers; sets reminder_sent = True; new migration for reminder_sent field.

Deliverable: Volunteers receive emails on assignment and rejection; 24-hour reminders fire automatically; personal calendar shows full shift schedule.

Acceptance Criteria:

  • My Shifts page is accessible at /event/.../teamshifts/my-shifts/ and requires login
  • Assignments are grouped correctly by calendar date
  • Each shift entry shows role name, location, start–end time, and a moderator badge where applicable
  • Layout renders correctly at 375px viewport width (mobile)
  • Only the logged-in user's assignments are shown
  • send_assignment_confirmation sends an email with shift details; assignment.notified set to True
  • send_rejection_notification sends a rejection email when an application is rejected
  • Both tasks handle ObjectDoesNotExist gracefully (log and return, no crash)
  • Both tasks have max_retries=3 with exponential backoff
  • send_shift_reminders sends reminders to volunteers whose shifts start within 24 hours; reminder_sent set to True
  • All three Celery tasks verified with CELERY_TASK_ALWAYS_EAGER = True — emails appear in mail.outbox

Phase 7 — Stretch Goals

Goal: Bonus features if Phases 1–6 are completed ahead of schedule, or a buffer period if any phase slipped.

Issues in this phase:

  • 19 — Public shift board (area: views): Public read-only page showing open volunteer slots grouped by day; links to sign-up form when CFV is active.
  • 20 — iCal export (area: views): /my-shifts/calendar.ics endpoint generating a valid .ics file with one VEVENT per assigned shift, importable into Google Calendar and Apple Calendar.
  • 21 — Volunteer stats widget and shift conflict detection (area: views, area: api): Organiser dashboard widget showing total hours per volunteer; conflict detection in assignment service warns on overlapping shifts (warning, not a hard block).

Acceptance Criteria:

  • Public shift board is accessible without login and shows only shifts with available slots
  • Full shifts are excluded from the public board (or shown with a "Full" indicator and no apply link)
  • .ics file downloads correctly and is importable into Google Calendar without errors
  • Each assigned shift appears as a VEVENT with correct DTSTART, DTEND, SUMMARY, and LOCATION
  • Stats widget displays correct total hours per volunteer (calculated from end_time - start_time sum)
  • Conflict detection fires a warning (not a block) when a volunteer is assigned to overlapping shifts
  • All stretch features pass their associated tests

Phase 8 — Tests and Documentation

Goal: Production-ready, fully tested, fully documented plugin. Coverage target: 85% overall, 90% on API module.

Issues in this phase:

  • 22 — Unit tests — models, views and forms (type: testing): unique_together constraint tests, ScopedManager isolation tests, permission 403 tests on all organiser views, form validation tests (duplicate role name, past deadline, empty fields).
  • 23 — API test coverage at 90% (type: testing): All CRUD endpoints, all error paths (400, 401, 403, 404, 409), event isolation, computed field correctness, capacity concurrency test.
  • 24 — Integration test — full CFV to assignment to email flow (type: testing): Single end-to-end test exercising: CFV open → role created → volunteer applies → organiser accepts → shift created via API → volunteer assigned → email in mail.outboxMy Shifts page shows shift.
  • 25 — README, installation guide and inline docstrings (type: docs): Complete README.md with installation steps, usage walkthrough, screenshots (minimum 5), and Engelsystem comparison table. Docstrings on all model classes, view classes, serializers, Celery tasks, and services.py functions.

Acceptance Criteria:

  • Overall test coverage reported by pytest --cov=teamshifts is 85% or higher
  • API module coverage is 90% or higher
  • All unique_together constraints tested with pytest.raises(IntegrityError)
  • ScopedManager isolation verified across two events in the same test database
  • All organiser views tested for 403 with anonymous and non-organiser users
  • Integration test passes the full lifecycle: CFV open → apply → accept → assign → email → My Shifts page
  • README.md contains installation steps, usage walkthrough, and at least 5 screenshots
  • Engelsystem comparison table included in README
  • All public model, view, serializer, task, and service functions have docstrings
  • All open code review comments from previous PRs are resolved before final PR is merged

Complete Issue Tracking Table

# Issue Title Phase Areas Status
#7 Plugin AppConfig and entry point registration 1 models, ci Finished
#12 Component Registration - event_dashboard_components signal 1 views Finished
#13 Top-Level URL Namespace and Dashboard Widget Signal 1 views, auth Finished
#8 Core data models and initial migrations 1 models Finished
#9 Django admin registration and GitHub Actions CI 1 admin, ci Finished
#10 CFM Settings, Team Roles, Applications, and Public Apply 2 views, forms In progress
#10 Public team member sign-up form 2 views, forms In progress
#10 Organiser application review panel 2 views In progress
#21 Configurable Default Application Fields 2 views, models In progress
#22 Wrap public apply URLs for plugin/live checks 2 bugs In progress
#23 Replace "one option per line" textarea with structured answer-option formset 2 enhancement, forms In progress
7 DRF serializers for shifts, roles and applications 3 api Not Started
8 Shift CRUD API endpoints 3 api Not Started
9 Volunteer assignment API and capacity enforcement 3 api Not Started
10 Fork schedule-editor and Vite build setup 4 vue, ci Not Started
11 Shift grid and shift card Vue components 4 vue Not Started
12 Vue app API integration and Django template wiring 4 vue, views Not Started
13 Organiser volunteer assignment panel 5 views Not Started
14 Moderator management and moderator view 5 views, auth Not Started
15 Capacity enforcement — backend and frontend 5 api, vue Not Started
16 Volunteer personal shift calendar view 6 views Not Started
17 Celery email tasks — assignment confirmation and rejection 6 email Not Started
18 Celery Beat — 24-hour shift reminder 6 email Not Started
19 Public shift board (stretch) 7 views Not Started
20 iCal export (stretch) 7 views Not Started
21 Volunteer stats widget and shift conflict detection (stretch) 7 views, api Not Started
22 Unit tests — models, views and forms 8 testing Not Started
23 API test coverage at 90% 8 testing Not Started
24 Integration test — full CFV to assignment to email flow 8 testing Not Started
25 README, installation guide and inline docstrings 8 docs Not Started

Open Questions


Metadata

Metadata

Assignees

No one assigned

    Labels

    roadmapTop-level tracking issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions