Jarvis/jarvis/tools/models.py

235 lines
7.0 KiB
Python

"""Ensemble des classes d'utilité publique :-)"""
import re
import markdown
import pendulum
from django.db import models
from django.utils import timezone
from .date_week_transition import (
get_number_of_weeks_between,
from_date_to_week_number,
)
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):
"""Classe abstraite définissant des champs commun à tout ce qui est relatif à une saison."""
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
return value_2
if value_1 is not None:
return value_1
if value_2 is not None:
return value_2
return 0