You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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:
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:
Multi-tenancy — every ORM query wrapped with django_scopes.scope(event=event). Queries without a scope will raise ScopeError by default.
Permissions — all organiser views use EventPermissionRequiredMixin + permission_required = 'can_change_event_settings'.
ORM efficiency — select_related and prefetch_related required on all list queries to prevent N+1.
Imports — eventyay.* namespace only; no new pretix.* imports in plugin code.
JavaScript — no jQuery, no inline <script> blocks; all JS via external ES modules with data- attribute config passing.
Atomic capacity enforcement — select_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.
5 — Core Data Model Design (area: models, area: admin): EventTeam and EventStaff core models with migration and Django admin registration.
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:
[Phase 2] CfM Settings, Team Roles & Application Views (Organiser + Public) #10 — (area: views, area: forms): CFM settings page, Team Roles list+create, Applications review panel with filter/search/accept/reject, public team member sign-up form at /apply/. Dashboard with 4 numwidgets, recent applications table, upcoming shifts placeholder, quick actions panel. Custom component_link.html with no highlighted button. Sidebar with collapsible "Call for Team Members" group.
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)
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.
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.outbox → My 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
Summary
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:
What eventyay has today:
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):
Team member / staff-side (public pages, login required):
Automated (background):
Architecture Overview
Plugin Registration
eventyay discovers plugins by reading the
pretix.pluginentry point group at startup (settings.pyusesimportlib_metadata.entry_points(group='pretix.plugin')and adds eachep.moduletoINSTALLED_APPS). This plugin registers itself as:Django then loads
teamshifts.apps.TeamShiftsApp(which subclasseseventyay.base.plugins.PluginConfigand declaresEventyayPluginMeta) and the plugin appears in the eventyay plugin list.Tech Stack
schedule-editor)django-scopesdjango-i18nfieldData Models
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:
django_scopes.scope(event=event). Queries without a scope will raiseScopeErrorby default.EventPermissionRequiredMixin+permission_required = 'can_change_event_settings'.select_relatedandprefetch_relatedrequired on all list queries to prevent N+1.eventyay.*namespace only; no newpretix.*imports in plugin code.<script>blocks; all JS via external ES modules withdata-attribute config passing.select_for_update()insidetransaction.atomic()for all assignment writes.URL and View Structure
All plugin URLs are auto-discovered by eventyay's
maindomain_urlconf.pyand registered under theplugins:teamshiftsnamespace. The plugin defines its ownurls.pywith top-level paths.Routing notes:
PermissionMiddlewarein eventyay core is patched to recognise/teamshifts/paths for auth and event resolution.control/context.pyallows/teamshifts/for organiser sidebar context.teamshifts/base.htmltemplate with an isolated sidebar (not inheriting the Tickets sidebar).event_dashboard_componentssignal (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:
area: ci,area: auth):apps.pywithEventyayPluginMeta,pyproject.tomlentry point, verified editable install.area: models): All five models —CallForTeamMembers,TeamRole,TeamMemberApplication,Shift,ShiftAssignment— withScopedManager,unique_togetherconstraints,is_moderatorflag,capacityfield, and0001_initialmigration.area: admin,area: ci): All models registered in admin withlist_display/list_filter; CI pipeline runningflake8andpyteston every push.area: views): Addevent_dashboard_componentssignal to eventyay core; plugin implements receiver returning TeamShifts panel HTML; minimal dashboard view and template stub.area: views,area: auth): Top-level/teamshifts/event/<org>/<event>/URL viaPermissionMiddlewareextension;event_dashboard_widgetsnumwidget;event_dashboard_componentspanel receiver.area: models,area: admin):EventTeamandEventStaffcore models with migration and Django admin registration.Deliverable: Plugin appears in eventyay plugin list; dashboard panel visible;
manage.py migratesucceeds; models visible in/django-admin/.Acceptance Criteria:
event_dashboard_componentssignalevent_dashboard_widgetsnumwidget appears when plugin is enabled/teamshifts/event/<org>/<event>/URL returns 200 for authorised usersmanage.py checkpasses with no errorsmanage.py migrateapplies initial migration cleanly from a blank databaseEventTeamandEventStaffmodels are visible and usable in Django adminuv pip install -e .) works from a clean environmentPhase 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:
area: views,area: forms): CFM settings page, Team Roles list+create, Applications review panel with filter/search/accept/reject, public team member sign-up form at/apply/. Dashboard with 4 numwidgets, recent applications table, upcoming shifts placeholder, quick actions panel. Customcomponent_link.htmlwith no highlighted button. Sidebar with collapsible "Call for Team Members" group.area: views,area: forms): Public page at/teamshifts/applywith role selection, availability notes, submission, thank-you page, duplicate-submission guard, and closed-CFV state.area: views): Table view with status badges, filter by status/role, search by email/name, individual and bulk accept/reject actions.Deliverable: End-to-end CFV flow working — member submits, organiser accepts, application status updated.
Acceptance Criteria:
application.statuscorrectlyPhase 3 — Shift Scheduling REST API
Goal: Complete DRF REST API that the Vue shift editor and assignment panel will consume.
Issues in this phase:
area: api):ShiftSerializer(withfilled_countandis_fullcomputed fields),VolunteerRoleSerializer,ShiftAssignmentSerializer,VolunteerApplicationSerializer. Read/write split on nested fields.area: api):GET,POST,PATCH,DELETEfor/api/shifts/andGETfor/api/roles/. Event-scoped, organiser-only, paginated, with?role=and?date=filters.area: api):POST /api/shifts/<id>/assign/with acceptance check andselect_for_update()capacity enforcement;DELETE /api/shifts/<id>/assign/<volunteer_id>/;GET /api/applications/?status=accepted.Deliverable: Full REST API verified via
APIClienttests and Postman/httpie manual testing.Acceptance Criteria:
filled_countandis_fullreturn accurate valuesGET /api/shifts/returns only shifts belonging to the requested event (no cross-event leakage)POST,PATCH,DELETEon/api/shifts/succeed and persist correctly?role=and?date=filters on the shifts list work correctlyPOST /api/shifts/<id>/assign/returns 400 if volunteer is not accepted, 409 if shift is at capacityselect_for_update()prevents concurrent over-assignment (tested with simultaneous requests)GET /api/applications/?status=acceptedreturns only accepted applications for the eventPhase 4 — Shift Editor Vue App
Goal: Working drag-and-drop shift editor adapted from the existing
schedule-editorVue app, wired to the TeamShifts API.Issues in this phase:
area: vue): Copywebapp/schedule-editor/tostatic/teamshifts/shift-editor/, updatepackage.jsonandvite.config.ts, verifynpm run buildproducesdist/, add CI step.area: vue): AdaptGridSchedule.vue→ShiftGrid.vue(columns = shift locations, rows = timeslots); adaptSession.vue→ShiftCard.vue(role name,filled/capacitybadge, capacity colour coding); update TypeScriptShifttype inschemas.ts.area: vue,area: views): Updateapi.tsto call TeamShifts endpoints; passapiBaseandcsrfTokenviadata-attributes on mount element; wire built assets intoeditor.htmlvia{% static %}.Deliverable: Shift editor loads in browser, renders shifts from API, drag-and-drop repositioning persists via
PATCH.Acceptance Criteria:
npm installandnpm run buildcomplete without errors; CI build step passes/control/.../teamshifts/editor/with no console errorsPATCH /api/shifts/<id>/and the change persists after page reloadPOST /api/shifts/and the new shift appears in the gridDELETE /api/shifts/<id>/api.tsPhase 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:
area: views): Table of shifts withfilled/capacity, dropdown to select accepted volunteers, assign/unassign actions. Introducesservices.pywithassign_volunteer()shared by both this view and the API.area: views,area: auth):is_moderatortoggle in assignment panel (one per shift enforced); moderator-only view at/moderator/showing their shifts and team members (read-only).area: api,area: vue): Hardenselect_for_update()atomic block; structured 409 error response;ShiftCardcapacity 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:
filled/capacitycountsis_moderator=True; toggling a new moderator unsets the previous one/moderator/shows only shifts where the logged-in user is the moderatorservices.assign_volunteer()is the single code path used by both the API and the assignment panel viewPhase 6 — Calendar View and Email Notifications
Goal: Volunteer personal shift calendar plus Celery-powered transactional and scheduled email notifications.
Issues in this phase:
area: views):/my-shifts/page showing all assignments grouped by day; role name, location, time, moderator badge; mobile-responsive layout; role colour-coding.area: email): Implementsend_assignment_confirmationandsend_rejection_notificationtasks usingeventyay.base.services.mail; setsassignment.notified = True;max_retries=3with backoff; plain-text email templates.area: email): Hourly periodic task finds shifts starting in 23–25 hour window; sends reminder to unnotified volunteers; setsreminder_sent = True; new migration forreminder_sentfield.Deliverable: Volunteers receive emails on assignment and rejection; 24-hour reminders fire automatically; personal calendar shows full shift schedule.
Acceptance Criteria:
/event/.../teamshifts/my-shifts/and requires loginsend_assignment_confirmationsends an email with shift details;assignment.notifiedset toTruesend_rejection_notificationsends a rejection email when an application is rejectedObjectDoesNotExistgracefully (log and return, no crash)max_retries=3with exponential backoffsend_shift_reminderssends reminders to volunteers whose shifts start within 24 hours;reminder_sentset toTrueCELERY_TASK_ALWAYS_EAGER = True— emails appear inmail.outboxPhase 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:
area: views): Public read-only page showing open volunteer slots grouped by day; links to sign-up form when CFV is active.area: views):/my-shifts/calendar.icsendpoint generating a valid.icsfile with oneVEVENTper assigned shift, importable into Google Calendar and Apple Calendar.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:
.icsfile downloads correctly and is importable into Google Calendar without errorsVEVENTwith correctDTSTART,DTEND,SUMMARY, andLOCATIONend_time - start_timesum)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:
type: testing):unique_togetherconstraint tests,ScopedManagerisolation tests, permission 403 tests on all organiser views, form validation tests (duplicate role name, past deadline, empty fields).type: testing): All CRUD endpoints, all error paths (400, 401, 403, 404, 409), event isolation, computed field correctness, capacity concurrency test.type: testing): Single end-to-end test exercising: CFV open → role created → volunteer applies → organiser accepts → shift created via API → volunteer assigned → email inmail.outbox→My Shiftspage shows shift.type: docs): CompleteREADME.mdwith installation steps, usage walkthrough, screenshots (minimum 5), and Engelsystem comparison table. Docstrings on all model classes, view classes, serializers, Celery tasks, andservices.pyfunctions.Acceptance Criteria:
pytest --cov=teamshiftsis 85% or higherunique_togetherconstraints tested withpytest.raises(IntegrityError)ScopedManagerisolation verified across two events in the same test databaseREADME.mdcontains installation steps, usage walkthrough, and at least 5 screenshotsComplete Issue Tracking Table
Open Questions