Large uncommitted body of work accumulated across sessions on this branch -- committing as a checkpoint so it's tracked and future worktree-isolated agents see the real codebase instead of a stale ancestor commit. Covers the management app's dedicated Tailwind theme and templates, the club onboarding requirement/signup workflow (club/services/onboarding.py, requirement/status models, sign-up dashboard), fee/status auto-activation decoupling, referee management, and the new events calendar grid service layer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
69 lines
2.7 KiB
Python
69 lines
2.7 KiB
Python
"""Keep waffle's flag cache honest when club targeting changes, and keep a JobRun history
|
|
of the scheduled platform jobs (see features/jobs.py).
|
|
|
|
waffle caches a flag's M2M ids and only flushes on ``save()``. Editing an M2M
|
|
does not call ``save()``, so adding or removing a club would otherwise leave a
|
|
stale cached set and the flag would keep answering with the old value.
|
|
"""
|
|
|
|
from celery.signals import task_failure, task_postrun, task_prerun
|
|
from django.db.models.signals import m2m_changed
|
|
from django.dispatch import receiver
|
|
from django.utils import timezone
|
|
|
|
from .jobs import JOB_REGISTRY
|
|
from .models import Flag, JobRun
|
|
|
|
FLUSH_ACTIONS = {"post_add", "post_remove", "post_clear"}
|
|
|
|
|
|
@receiver(m2m_changed, sender=Flag.clubs.through)
|
|
def flush_flag_club_cache(sender, instance, action, reverse, pk_set, **kwargs):
|
|
if action not in FLUSH_ACTIONS:
|
|
return
|
|
|
|
if isinstance(instance, Flag):
|
|
instance.flush()
|
|
else:
|
|
# Reverse edit (club.flags.add(flag)): flush each flag touched.
|
|
for flag in Flag.objects.filter(pk__in=pk_set or []):
|
|
flag.flush()
|
|
|
|
|
|
@task_prerun.connect
|
|
def record_job_start(sender=None, task_id=None, task=None, **kwargs):
|
|
"""One JobRun row per task execution, for the jobs in JOB_REGISTRY only -- an
|
|
unregistered Celery task (should one ever be added without a job to show for it)
|
|
is not the control panel Jobs tab's business."""
|
|
if task is None or task.name not in JOB_REGISTRY:
|
|
return
|
|
|
|
JobRun.objects.create(task_id=task_id, name=task.name, status=JobRun.Status.STARTED, started_at=timezone.now())
|
|
|
|
|
|
@task_postrun.connect
|
|
def record_job_finish(sender=None, task_id=None, task=None, retval=None, state=None, **kwargs):
|
|
"""Closes the row record_job_start opened. Fires whether the task succeeded or raised --
|
|
on success, ``retval`` is whatever the task returned (see billing/tasks.py, club/tasks.py,
|
|
events/tasks.py: each returns a short human summary for this) and becomes JobRun.detail.
|
|
On failure ``retval`` is not reliably the exception, so record_job_failure below (driven
|
|
by the dedicated task_failure signal instead) fills in JobRun.error."""
|
|
if task is None or task.name not in JOB_REGISTRY:
|
|
return
|
|
|
|
succeeded = state == "SUCCESS"
|
|
JobRun.objects.filter(task_id=task_id).update(
|
|
status=JobRun.Status.SUCCESS if succeeded else JobRun.Status.FAILURE,
|
|
finished_at=timezone.now(),
|
|
detail=str(retval)[:4000] if succeeded else "",
|
|
)
|
|
|
|
|
|
@task_failure.connect
|
|
def record_job_failure(sender=None, task_id=None, exception=None, **kwargs):
|
|
task_name = getattr(sender, "name", None)
|
|
if task_name not in JOB_REGISTRY:
|
|
return
|
|
|
|
JobRun.objects.filter(task_id=task_id).update(error=str(exception)[:4000])
|