from datetime import datetime, date, time, timedelta from django.contrib.auth import get_user_model from django.db import models from django.utils import timezone from location.models import Place import pendulum from tools.models import Markdownizable from people.models import Gymnast from location.models import Place # User = get_user_model() def get_number_of_weeks_between(start, stop): """ Renvoie le nombre de semaines entre deux dates. Par extension, cela permet de connaitre le nombre d'occurence d'un évènement (entraînement, par exemple) hebdromadaire entre deux dates et ainsi pouvoir plannifier. :param start: date de début de la période :type start: datetime.date :param stop: date de fin de la période :type stop: datetime.date :return: Le nombre de semaines entre les deux dates. Remarks: Proposition d'utiliser isocalendar() sur une date. L'indice 1 de la valeur de retour donne la semaine correspondant. Eg. >>> from datetime import date >>> d = date(2020, 9, 27) >>> d.isocalendar() (2020, 39, 7) -> Est-ce qu'il ne suffirait pas de faire la différence ? """ tmp = stop - start number_of_days = abs(tmp.days) number_of_week = int((number_of_days + 1) / 7) if ((number_of_days + 1) % 7) > 0: number_of_week += 1 if tmp.days < 0: number_of_week *= -1 return number_of_week 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(datebegin__gte=timezone.now()).order_by("datebegin")[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(dateend__lte=timezone.now()).order_by("-dateend")[0:limit] # def get(self, date_string): # """ # """ # try: # selected_object = self.get(datebegin__lte=date_string, dateend__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 datebegin = models.DateTimeField(verbose_name="Début") dateend = models.DateTimeField(blank=True, verbose_name="Fin") 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.datebegin.date(), self.dateend.date()) def get_number_of_occurence_to_event(self, the_date): """ Renvoie le nombre semaines entre une date choisie et le début (datebegin) 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.datebegin.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.dateend.date()) else: return get_number_of_weeks_between(self.datebegin.date(), the_date) class EventType(models.Model): """ Classe représentant les types d'évènements. C'est un dictionnaire fini : - compétiton qualificative, - compétition finale, - démonstration, - … """ class Meta: verbose_name = "Event Type" verbose_name_plural = "Event Types" name = models.CharField(max_length=255, verbose_name="Nom") acronym = models.CharField(max_length=15, verbose_name="Acronyme") def __str__(self): return "%s (%s)" % (self.name, self.acronym) class Event(Markdownizable, Temporizable): """Classe représentant les évènements. Un évènement est caractérisé par : * un nom, * un lieu (place), * un type (compétition, démonstration, …), * des gymnastes (participation prévue). """ class Meta: verbose_name = "Event" verbose_name_plural = "Event" place = models.ForeignKey( Place, on_delete=models.CASCADE, default=None ) eventtype = models.ForeignKey( EventType, verbose_name="Type", on_delete=models.CASCADE, default=None ) name = models.CharField(max_length=255, verbose_name="Nom") gymnasts = models.ManyToManyField( Gymnast, through="Event_Participation", related_name="participate_to", verbose_name="Participants", ) def __str__(self): return "%s (à %s)" % (self.name, self.place) def save(self, *args, **kwargs): self.checkdates() super().save(*args, **kwargs) def checkdates(self): """ Fonction assignant la date de fin d'un évènement à la date de début, si la date de fin n'est pas définie, l'heure de fin est par défaut 18h00. """ if self.dateend is None and self.datebegin is not None: self.dateend = datetime.combine(self.datebegin.date(), time(18, 0)) @property def number_of_week_from_today(self): today = pendulum.now().date() return get_number_of_weeks_between(today, self.datebegin.date()) class Event_Participation(models.Model): """ """ class Meta: verbose_name = "Event Participation" event = models.ForeignKey(Event, on_delete=models.CASCADE) gymnast = models.ForeignKey(Gymnast, on_delete=models.CASCADE) rank = models.PositiveSmallIntegerField(default=0)