Files
TeamForge/teams/models.py

85 lines
3.4 KiB
Python

import calendar
import datetime
import re
from constance import config
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from rules import is_superuser
from rules.contrib.models import RulesModel
class Season(RulesModel):
start_date = models.DateField(_("start date"))
end_date = models.DateField(_("end date"))
class Meta:
verbose_name = _("season")
verbose_name_plural = _("seasons")
ordering = ["start_date"]
rules_permissions = {"add": is_superuser, "view": is_superuser, "change": is_superuser, "delete": is_superuser}
constraints = [
models.UniqueConstraint(fields=["start_date", "end_date"], name="season_start_date_end_date_unique", violation_error_message=_("Start and end date for a given season must be unique")),
models.CheckConstraint(condition=models.Q(start_date__lt=models.F("end_date")), name="season_start_date_lt_end_date", violation_error_message=_("Start date must be before end date.")),
]
def __str__(self):
return _("Season '{start_date} - '{end_date}").format(start_date=self.start_date.strftime("%y"), end_date=self.end_date.strftime("%y"))
@property
def is_current(self) -> bool:
return self.start_date <= timezone.now().date() <= self.end_date
@property
def date_range(self) -> tuple[datetime.date, datetime.date]:
return self.start_date, self.end_date
@classmethod
def for_date(cls, current_date: datetime.date | None = None, values_only: bool = False) -> "tuple[datetime.date, datetime.date] | Season":
if current_date is None:
current_date = timezone.now().date()
season = cls.objects.get(start_date__lte=current_date, end_date__gte=current_date)
if values_only:
return season.date_range
return season
@classmethod
def generate_default(cls, day: int | None = None, month: int | None = None, duration: str | None = None) -> "Season":
if day is None:
day = config.TF_DEFAULT_SEASON_DAY
if month is None:
month = config.TF_DEFAULT_SEASON_MONTH
if duration is None:
duration = config.TF_DEFAULT_SEASON_DURATION
current_year = timezone.now().date().year
start_date = datetime.date(current_year, month, day)
match = re.fullmatch(r"(\d+)([ym])", duration.strip().lower())
if match is None:
raise ValueError('Duration must be specified as "<number>y" or "<number>m", for example "1y", "1m", or "3m".')
duration_value = int(match.group(1))
duration_unit = match.group(2)
if duration_unit == "y":
end_date = cls._add_months(start_date, duration_value * 12) - datetime.timedelta(days=1)
elif duration_unit == "m":
end_date = cls._add_months(start_date, duration_value) - datetime.timedelta(days=1)
else:
raise ValueError('Duration unit must be "y" or "m".')
return cls.objects.create(start_date=start_date, end_date=end_date)
@staticmethod
def _add_months(date_value: datetime.date, months: int) -> datetime.date:
month_index = date_value.month - 1 + months
year = date_value.year + month_index // 12
month = month_index % 12 + 1
day = min(date_value.day, calendar.monthrange(year, month)[1])
return date_value.replace(year=year, month=month, day=day)