Generate seasons per club instead of a hardcoded Aug-May window
Club now carries its own season_start and season_duration_months, editable via controlpanel; generate_seasons chains each new season off the day after the club's last one ends (or its configured start, for a club with none yet) instead of assuming every club runs Aug 1 - May 31. Adds --resync to the generate_seasons command to clean up seasons left over from the old hardcoded rule -- removing any that don't match a club's current settings and aren't still referenced by real data.
This commit is contained in:
57
club/management/commands/generate_seasons.py
Normal file
57
club/management/commands/generate_seasons.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""Generate seasons ahead of time for every active club.
|
||||
|
||||
Meant to run on a schedule (cron): safe to call repeatedly, since generate_seasons
|
||||
skips whatever already exists. No --dry-run/--commit gate on generation itself --
|
||||
unlike archiving a club or billing it, creating a future season row is additive
|
||||
and idempotent, same reasoning as extend_event_series (materialising occurrences).
|
||||
|
||||
--resync is different: it can delete rows (any season that doesn't match a
|
||||
club's *current* season_start/season_duration_months, and isn't referenced by a
|
||||
membership), so it defaults to reporting only -- pass --commit alongside it to
|
||||
actually remove anything.
|
||||
"""
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from django.utils import timezone
|
||||
|
||||
from club.models import Club
|
||||
from club.services.seasons import generate_seasons, resync_seasons
|
||||
from features.commands import MaintenanceAwareCommand
|
||||
|
||||
|
||||
class Command(MaintenanceAwareCommand):
|
||||
help = "Generate seasons up to N years ahead for every active club (default 2)."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("--years", type=int, default=2, help="How many years ahead to generate seasons for (default 2).")
|
||||
parser.add_argument("--resync", action="store_true", help="Before generating, remove any existing seasons that don't match the club's current settings (skips any still referenced by a membership).")
|
||||
parser.add_argument("--commit", action="store_true", help="With --resync, actually delete the seasons found to be wrong. Without it, --resync only reports what it would remove.")
|
||||
|
||||
def handle(self, *args, **options):
|
||||
until = timezone.localdate() + relativedelta(years=options["years"])
|
||||
clubs = Club.objects.active()
|
||||
|
||||
if options["resync"]:
|
||||
total_removed, total_kept = 0, 0
|
||||
for club in clubs:
|
||||
removed, kept = resync_seasons(club, until, commit=options["commit"])
|
||||
total_removed += len(removed)
|
||||
total_kept += len(kept)
|
||||
if removed:
|
||||
verb = "Removed" if options["commit"] else "Would remove"
|
||||
self.stdout.write(f"{club}: {verb} {len(removed)} season(s) that no longer match its settings.")
|
||||
if kept:
|
||||
self.stdout.write(self.style.WARNING(f"{club}: {len(kept)} season(s) don't match its settings but are still in use, left alone."))
|
||||
|
||||
if not options["commit"] and total_removed:
|
||||
self.stdout.write(self.style.WARNING("Dry run -- pass --commit to actually delete these."))
|
||||
self.stdout.write(self.style.SUCCESS(f"Resync: {total_removed} season(s) {'removed' if options['commit'] else 'to remove'}, {total_kept} kept (in use)."))
|
||||
|
||||
total = 0
|
||||
for club in clubs:
|
||||
created = generate_seasons(club, until)
|
||||
total += len(created)
|
||||
if created:
|
||||
self.stdout.write(f"{club}: generated {len(created)} season(s).")
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Done. Generated {total} season(s) across {clubs.count()} club(s)."))
|
||||
Reference in New Issue
Block a user