"""Ensemble des classes d'utilité publique :-)""" from django.db import models from django.utils import timezone from datetime import date from .date_week_transition import ( get_number_of_weeks_between, from_date_to_week_number, ) import markdown import pendulum import re class Season: """Class pour représenter une saison""" def __init__(self, label=None): self.label = label if self.label is None or not self.is_valid(): the_date = pendulum.today().date() if the_date.month >= 9: # nouvelle saison self.label = str(the_date.year) + "-" + str(the_date.year + 1) else: self.label = str(the_date.year - 1) + "-" + str(the_date.year) def is_valid(self): """Test si une chaine de caractère correspond bien à une saison Args: season string saison sous la forme "xxxx-xxxy" Returns: bool vrai si la chaine de caractère correspond à une saison. Examples: >>> from jarvis.tools.date_week_transition import is_valid_season >>> is_valid_season("2022-2023") >>> True >>> is_valid_season("2022-2024") >>> False >>> is_valid_season("2024-2023") >>> False >>> is_valid_season("1358-5682") >>> False >>> is_valid_season("fgkrs-gkml") >>> False >>> is_valid_season("drgnldsjgklfdtngl") >>> False """ dash_position = self.label.find("-") if dash_position < 0: return False pattern = "^[0-9]{4}-[0-9]{4}$" if not re.search(pattern, self.label): return False first_year = int(self.label[:dash_position]) second_year = int(self.label[dash_position + 1 :]) if first_year != second_year - 1: return False return True def __str__(self): return f"{self.label}" def get_default_date(): return pendulum.now().date() class Seasonisable(models.Model): """ """ class Meta: abstract = True date = models.DateField(default=get_default_date, verbose_name="Date") season = models.CharField(max_length=9, editable=False) week_number = models.PositiveSmallIntegerField(editable=False) def save(self, *args, **kwargs): """Calcule les valeurs `season` et `week_number` sur base d'une date lors de l' enregistrement d'un object enfant. """ if self.date is None: self.date = get_default_date() self.season, self.week_number = from_date_to_week_number(self.date) super().save(*args, **kwargs) @property def is_past(self): return pendulum.now().date() > self.date class TemporizableQuerySet(models.QuerySet): """ Classe permettant de spécifier le `QuerySet` de la classe `Temporizable`. """ def next(self, limit): """ Renvoie la liste des prochains "temporizable" (par rapport à la date du jour). :param limit: nombre d'éléments désirés. :type limit: int :return: une liste de `limit` éléments temporizables. """ return self.filter(date_begin__gte=timezone.now()).order_by("date_begin")[ 0:limit ] def last(self, limit): """ Renvoie la liste des derniers "temporizable" (par rapport à la date du jour). :param limit: nombre d'éléments désirés. :type limit: int :return: une liste de `limit` éléments temporizables """ return self.filter(date_end__lte=timezone.now()).order_by("-date_end")[0:limit] # def get(self, date_string): # """ # """ # try: # selected_object = self.get(date_begin__lte=date_string, date_end__gte=date_string) # except self.DoesNotExist: # return None # except self.MultipleObjectsReturned: # return None # return selected_object class Temporizable(models.Model): """ Classe abstraite définissant une période comprise entre deux dates. """ class Meta: abstract = True date_begin = models.DateTimeField(verbose_name="Begin", default=timezone.now) date_end = models.DateTimeField(verbose_name="End", default=timezone.now) objects = models.Manager.from_queryset(TemporizableQuerySet)() def get_total_occurence(self): """ Renvoie le nombre de semaines entre les deux dates d'une instance de la classe `Temporizable`. :return: nombre de semaines. """ return get_number_of_weeks_between(self.date_begin.date(), self.date_end.date()) def get_number_of_occurence_to_event(self, the_date): """ Renvoie le nombre semaines entre une date choisie et le début (date_begin) d'une instance de la classe `Temporizable`. :param the_date: date par rapport à laquelle le calcul sera fait. :type the_date: datetime.date :return: nombre de semaines. """ return get_number_of_weeks_between(the_date, self.date_begin.date()) def get_number_of_occurence_inbetween(self, the_date, rest=True): """ Renvoie le nombre semaines entre une date choisie et une instance de la classe `Temporizable`. Le calcul peut se faire soit entre la date choisie et le date de fin d'une occurence de la classe, soit entre la date de début d'une occurence de la classe et la date choisie. :param the_date: date par rapport à laquelle le calcul sera fait. :type the_date: datetime.date :param rest: paramètre définissant s'il faut calculer le reste des occurences à venir (depuis `the_date` jusqu'à la date de fin) ou les occurences déjà passées (depuis la date de début jusqu'à `the_date`) :type rest: booléen :return: nombre de semaines. """ if rest: return get_number_of_weeks_between(the_date, self.date_end.date()) return get_number_of_weeks_between(self.date_begin.date(), the_date) class Markdownizable(models.Model): """ Classe abstraite ajoutant un champ `informations`, convertible de .md -> .html. """ class Meta: abstract = True informations = models.TextField( null=True, blank=True, verbose_name="Comments", help_text="Only MarkDown is authorized", ) def to_markdown(self): """Convertit le champ `informations` en (Github-flavored) Markdown.""" return markdown.markdown(self.informations) def max_even_if_none(value_1, value_2): """ Renvoie le maximum de deux valeurs même si l'une des deux vaut None. """ if value_1 is not None and value_2 is not None: if value_1 > value_2: return value_1 else: return value_2 elif value_1 is not None: return value_1 elif value_2 is not None: return value_2 else: return 0