Celery Tasks¶
The rota system uses Celery with Redis for asynchronous task processing. Long-running operations -- email delivery, database backups, notifications -- are offloaded from the request/response cycle to background workers.
Configuration¶
All Celery settings live in rota/settings/base.py under the CELERY_ namespace. The Celery app is created in tasks/celery_app.py.
| Setting | Value | Notes |
|---|---|---|
CELERY_BROKER_URL |
Built from REDIS_HOST, REDIS_PORT, etc. or CELERY_BROKER_URL env var |
Redis with optional auth |
CELERY_RESULT_BACKEND |
Same as broker URL by default | Stores task results |
CELERY_ACCEPT_CONTENT |
['json'] |
JSON only |
CELERY_TASK_SERIALIZER |
'json' |
-- |
CELERY_RESULT_SERIALIZER |
'json' |
-- |
CELERY_TIMEZONE |
'UTC' |
-- |
CELERY_TASK_TRACK_STARTED |
True |
Records when tasks begin |
CELERY_TASK_TIME_LIMIT |
1800 (30 min) |
Hard kill after 30 minutes |
CELERY_WORKER_PREFETCH_MULTIPLIER |
1 |
One task per worker at a time |
CELERY_WORKER_MAX_TASKS_PER_CHILD |
1000 |
Restarts worker process after 1000 tasks |
Environment overrides (no Celery-specific overrides in dev.py or prod.py, but logging levels differ):
- dev.py:
celerylogger atDEBUG. - prod.py:
celerylogger atWARNING.
App Bootstrap¶
The Celery app is instantiated in tasks/celery_app.py:
rota/celery.py re-exports the app so the standard CLI works:
Django is bootstrapped in the main process so AppConfig.ready() fires and registers @shared_task decorators. A worker_process_init signal re-initializes Django in each forked worker so the app registry survives the fork.
Task Registration¶
Tasks are registered through TasksConfig.ready() in tasks/apps.py, which explicitly imports the task modules:
tasks.backup_taskstasks.notification_taskstasks.rota_tasks
After Django setup, app.autodiscover_tasks() catches any remaining @shared_task definitions in other installed apps (e.g. tasks.email_tasks, tasks.shift_swap_tasks).
Task Modules¶
tasks/rota_tasks.py¶
Purpose: Rota generation entry points. Both tasks are deprecated -- the phase-based generation system in rota_generation/tasks is used instead.
| Task | Status | Notes |
|---|---|---|
generate_rota_task |
Deprecated | Raises NotImplementedError. Use phase tasks or manage.py generate_rota --phase. |
rebalance_rota_task |
Deprecated | Raises NotImplementedError. Depended on the old RotaGenerator class. |
tasks/email_tasks.py¶
Purpose: General-purpose email sending with exponential backoff retry. All tasks use bind=True, autoretry_for=(Exception,), retry_backoff=True, retry_backoff_max=600, retry_jitter=True, and max_retries=5 (unless noted otherwise).
| Task | Recipients | Description |
|---|---|---|
send_email_with_retry |
Arbitrary | Generic email send. Takes subject, message, from_email, recipient_list, html_message. |
send_html_email |
Arbitrary | Renders a Django template to HTML and sends. Takes template_name and context dict. |
send_admin_email |
Admin users (filtered) | Sends to Dashboard Admins group. Optional email_type filter (system_failures, admin_actions, operational) respects per-admin email preferences. |
send_password_reset_email |
Single user | Password reset email using allauth template. Takes user_email, reset_url, user_name. |
send_task_failure_email |
Admins with system_failures pref |
Alerts admins when a Celery task fails. |
send_rota_generation_failure_email |
Admins with system_failures pref |
Alerts admins when rota generation fails. max_retries=3. |
send_invitation_email |
Single invitee | Sends signup invitation with secure token link. Takes invitation_id and clinician_name. |
Retry behavior: first retry ~1 s, then 2 s, 4 s, 8 s, up to a maximum of 600 s (10 min). Jitter randomises delays to prevent thundering herds.
tasks/notification_tasks.py¶
Purpose: Leave and shift change notifications. Tasks in this module do not use bind=True or auto-retry -- they use fail_silently=True and return bool instead.
| Task | Trigger | Description |
|---|---|---|
send_leave_request_submitted_admin_notification |
Clinician submits leave request | Notifies admins with admin_actions preference. |
send_leave_request_confirmation_email |
Clinician submits leave request | Acknowledges receipt to the clinician. |
send_leave_approval_email |
Admin approves leave | Notifies clinician of approval. Delegates to send_email_with_retry. |
send_leave_denial_email |
Admin denies leave | Notifies clinician with denial reason. Delegates to send_email_with_retry. |
send_shift_change_notification |
Rota updated for clinician | Plain-text shift change notification. Delegates to send_email_with_retry. |
send_batch_notifications |
Bulk operation | Iterates a list of notification dicts (type: leave_approval, leave_denial, or shift_change) and dispatches each. Returns {success, failure, total} counts. |
tasks/shift_swap_tasks.py¶
Purpose: Shift swap workflow emails. All tasks use bind=True, autoretry_for=(Exception,), exponential backoff, and max_retries=5 (unless noted otherwise).
| Task | Trigger | Description |
|---|---|---|
send_shift_swap_request_email |
Clinician initiates swap | Notifies the target clinician about the incoming request. |
send_shift_swap_accepted_email |
Target accepts swap | Notifies the requester that the target accepted; notes admin approval is still needed. |
send_shift_swap_rejected_email |
Target declines swap | Notifies the requester with the rejection reason. |
send_shift_swap_admin_notification_email |
Target accepts swap | Notifies admins with admin_actions preference that a swap needs approval. max_retries=3. |
send_shift_swap_outcome_email |
Admin approves/rejects swap | Notifies both users of the final admin decision. Takes approved: bool. |
Each task looks up the ShiftSwapRequest by swap_id with select_related to avoid extra queries. If the target or requester has no user account or email, the task returns True (not an error condition).
tasks/backup_tasks.py¶
Purpose: Scheduled database backup and cleanup. Uses bind=True, autoretry_for=(Exception,), exponential backoff, max_retries=3.
| Task | Schedule | Description |
|---|---|---|
daily_pg_dump_backup |
3 AM daily (beat) | Calls manage.py pg_dump_backup. On failure, dispatches send_backup_failure_email then retries with manual countdown (60 s, 120 s, 240 s). |
send_backup_failure_email |
On backup failure | Delegates to send_admin_email with email_type='system_failures'. |
cleanup_old_backups |
Not scheduled (utility) | Simple age-based cleanup with configurable retention_months (default 6). |
cleanup_old_backups_task |
4 AM daily (beat) | Tiered retention: pg_dump (daily 30 d, weekly 12 w, monthly 12 m) plus JSON backups (6 months). |
Periodic Tasks (Beat Schedule)¶
Defined in CELERY_BEAT_SCHEDULE in rota/settings/base.py:
| Name | Task | Schedule |
|---|---|---|
daily-pg-dump-backup |
tasks.backup_tasks.daily_pg_dump_backup |
crontab(hour=3, minute=0) |
cleanup-old-backups |
tasks.backup_tasks.cleanup_old_backups_task |
crontab(hour=4, minute=0) |
Management Command¶
manage.py run_celery wraps the Celery worker CLI for convenience.
| Option | Default | Description |
|---|---|---|
--loglevel, -l |
info |
Logging level (debug, info, warning, error, critical) |
--concurrency, -c |
CPU count | Number of worker processes |
--pool, -P |
prefork |
Pool implementation (prefork, gevent, eventlet, solo, threads) |
--queue, -Q |
celery |
Queue(s) to consume from |
--purge |
off | Purge all pending messages before starting |
--beat, -B |
off | Run the beat scheduler in the same process |