khana/khana/people/models.py

347 lines
12 KiB
Python

"""Modélisation des gymnastes, accidents et relations à faire/faites.
Notes:
Est-ce qu'il ne faudrait pas refactoriser les GSM père/mère ?
Avec une table d'association, et un champ qui indique la qualité du contact ?
Du coup, cela permettrait se débarasser des champs phone, gsm, gsm_father et gsm_mother.
Comment sont gérées les évolutions ? Changement de clubs, de fédération,
A quoi correspond le champ `year_of_practice` ?
Comment se comportera-t-il dans un an ? Dans deux ans ?
Est-ce qu'il ne faudrait pas une date de début, plutôt ?
Idem pour la méthode `actual_year_of_pratice`.
Comme elle se base sur le champ `created_at`, il suffit que l'enregistrement ne soit pas
réalisé correctement pour que la méthode retourne une mauvaise information.
Que signifie qu'un gymnaste soit actif ou inactif ? Est-ce que cela ne devrait pas plutôt
être géré au niveau des utilisateurs ?
Au niveau des utilisateurs, comme un User Django a déjà les champs lastname/firstname
pourquoi ne pas les réutiliser ? On garde la clé OneToOne, mais on déplace dans l'autre
classe les champs qui peuvent l'être. `Email` s'y retrouve également.
Les relations `cando`, `haveToDo` et `have_routine` ne sont pas correctement nommées.
Si tu as une instance de `Gymnast`, tu devrais faire ceci :
>>> gregg = Gymnast.objects.first()
>>> gregg.have_routine <-- pas bien
>>> gregg.routines <-- ok
Idéalement, cela pourrait même être une indirection.
>>> gregg.routines <-- retourne la relation de type `have_routine`
>>> gregg.educatives.can_do <-- retourne les éducatifs qu'il **peut** faire
>>> gregg.educatives.must_do <-- retourne les éducatifs qu'il **doit** faire
(j'avoue ne pas tout à fait comprendre la nuance entre les deux)
<!> Tu as des fonctions qui ne sont pas du tout utilisées et qui pourrissent un peu le fichier.
>>> next_age() ? N'est appelé nulle part ailleurs.
>>> known_skills() <-- peut être récupéré directement via l'attribut `cando`
(du coup, tu as sans doute une piste de renommage ;-))
"""
from datetime import date
from django.contrib.auth.models import User
from django.db import models
import pendulum
from khana.base.models import Markdownizable
class Gymnast(Markdownizable):
"""Représente un gymnaste.
En plus de sa signalétique (nom, prénom, date de naissance, ...),
un gymnaste aura une photo et une orientation (de vrille).
Un gymnaste peut être actif ou inactif.
"""
class Meta:
verbose_name = "Gymnast"
verbose_name_plural = "Gymnasts"
ordering = ["user__last_name", "user__first_name"]
GENDER_CHOICES = ((0, "Male"), (1, "Female"))
ORIENTATION_CHOICES = ((None, "Unknown"), (0, "Left"), (1, "Right"))
birthdate = models.DateField(verbose_name="Date de naissance")
gender = models.PositiveSmallIntegerField(
choices=GENDER_CHOICES, verbose_name="Sexe"
)
niss = models.CharField(max_length=11, null=True, blank=True)
address = models.CharField(max_length=255, null=True, blank=True)
postal = models.CharField(max_length=6, null=True, blank=True)
city = models.CharField(max_length=150, null=True, blank=True)
phone = models.CharField(max_length=9, null=True, blank=True, verbose_name="Phond")
gsm = models.CharField(max_length=10, null=True, blank=True)
federation_id = models.CharField(
max_length=10, null=True, blank=True, verbose_name="Federation ID"
)
gsm_main_responsible = models.CharField(
max_length=10, null=True, blank=True, verbose_name="GSM mère"
)
email_main_responsible = models.EmailField(max_length=255, null=True, blank=True)
gsm_second_responsible = models.CharField(
max_length=10, null=True, blank=True, verbose_name="GSM père"
)
email_second_responsible = models.EmailField(max_length=255, null=True, blank=True)
orientation = models.PositiveSmallIntegerField(
choices=ORIENTATION_CHOICES, null=True, blank=True, verbose_name="Twist side",
)
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="gymnast")
trainer = models.ForeignKey(
User, null=True, on_delete=models.SET_NULL, related_name="gymnasts"
)
club = models.ForeignKey(
"location.Club", null=True, on_delete=models.SET_NULL, related_name="gymnasts"
)
can_do = models.ManyToManyField(
"objective.Educative",
through="CanDoRelation",
related_name="isdoneby", # related_name to change
)
have_to_do = models.ManyToManyField(
"objective.Educative",
through="ToDoRelation",
related_name="mustBeDoneBy", # related_name to change
)
routine = models.ManyToManyField(
"objective.Routine",
through="GymnastHasRoutine",
related_name="doneBy", # related_name to change
)
picture = models.ImageField(upload_to="gymnasts", null=True, blank=True)
year_of_practice = models.PositiveSmallIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return u"%s, %s" % (self.user.last_name, self.user.first_name)
@property
def next_birthday(self):
"""Définit la prochaine date (de fête) d'anniversaire pour cette personne.
Returns:
Soit le jour/mois pour cette année
Soit le jour/mois pour l'année prochaine.
Examples: en supposant qu'on soit le 23/05/2019
>>> from datetime import date
>>> gregg = People(name='Tru', firstname='Gregg', birthdate=date(1982, 2, 5)
>>> gregg.next_birthday()
Date(2020, 2, 5)
"""
now = pendulum.now()
this_year_birthday = pendulum.date(
now.year, self.birthdate.month, self.birthdate.day
)
if this_year_birthday.is_past():
return pendulum.date(now.year + 1, self.birthdate.month, self.birthdate.day)
return this_year_birthday
@property
def next_birthday_in_days(self):
now = pendulum.now()
return self.next_birthday.diff(now).in_days()
@property
def age(self):
""" Renvoie l'âge d'un gymnaste. """
today = date.today()
return (
today.year
- self.birthdate.year
- ((today.month, today.day) < (self.birthdate.month, self.birthdate.day))
)
@property
def next_age(self):
""" Renvoie l'âge prochain du gymnaste. """
return (self.age) + 1
@property
def known_skills(self):
""" Renvoie la liste des objectifs qu'un gymnaste sait faire. """
return CanDoRelation.objects.filter(gymnast=self.id)
@property
def actual_year_of_pratice(self):
"""
Renvoie le nombre d'année de pratique du gymnaste en se basant sur le
nombre d'année qu'il avait déjà lors de son introduction dans le
système + le nombre d'année qu'il est dans le système.
"""
period = pendulum.now() - pendulum.instance(self.created_at, "Europe/Brussels")
return int(self.year_of_practice + period.in_years())
class Accident(Markdownizable):
"""La classe `Accident` permet d'indiquer qu'un gymnaste est tombé durant un saut.
"""
class Meta:
verbose_name = "Accident"
verbose_name_plural = "Accidents"
# ordering = ["date", "gymnast"]
gymnast = models.ForeignKey(
Gymnast,
verbose_name="Gymnast",
related_name="accident",
on_delete=models.CASCADE,
)
educative = models.ForeignKey(
"objective.Skill",
verbose_name="Skill",
related_name="accident",
on_delete=models.CASCADE,
)
date = models.DateField(verbose_name="Date")
def __str__(self):
return "%s, %s (%s)" % (
self.gymnast.lastname,
self.gymnast.firstname,
self.date,
)
class CanDoRelation(models.Model):
"""
Représente les objectifs qu'un gymnaste sait faire (à partir d'une date donnée).
"""
class Meta:
verbose_name = "CanDo"
verbose_name_plural = "CanDos"
ordering = ["date", "educative", "gymnast"]
unique_together = ("gymnast", "educative")
# TO_FRED : puis-je avoir ton avis là-dessus ?
#
# JE VAIS INDIQUER QUE DES ELEVES SAVENT FAIRE DES FIGURES (SKILL)
#
#
# QUESTIONS
# ---------
#
# 1) Cela ne dervait pas être deux relations ManyToMany dans chacune des tables GYMNAST et EDUCATIVE (ou SKILL) ?
# Un élève saura faire plusieurs figures.
# Et une figure pourra savoir être faite par plusieurs élèves... Donc, ?
#
# 2) Dans un premier temps, même si je l'appelle EDUCATIVE mon champs, pour être pérenne,
# je n'associerai QUE des figures à un élève. Je voudrais donc, dans l'admin, que mon champs
# ne soit populer qu'avec les éléments de la table SKILL et non pas tous les éléments de la
# table EDUCATIVE.
gymnast = models.ForeignKey(
Gymnast,
verbose_name="Gymnast",
related_name="toObjective",
on_delete=models.CASCADE,
)
educative = models.ForeignKey(
"objective.Educative",
verbose_name="Skill",
related_name="cando",
on_delete=models.CASCADE,
)
date = models.DateField(default=date.today, verbose_name="Date")
def __str__(self):
return "%s, %s - %s" % (
self.gymnast.user.last_name,
self.gymnast.user.first_name,
self.educative.shortLabel,
)
@staticmethod
def create(gymnast, skill, linkdate):
c = CanDoRelation()
c.gymnast = gymnast
c.educative = skill
c.date = linkdate
c.save()
return c
class ToDoRelation(models.Model):
"""
Classe représentant les objectifs qu'une gymnaste devra savoir faire pour une date donnée.
"""
class Meta:
verbose_name = "ToDo"
verbose_name_plural = "ToDos"
ordering = ["date", "educative", "gymnast"]
unique_together = ("gymnast", "educative")
gymnast = models.ForeignKey(
Gymnast, verbose_name="Gymnast", related_name="todo", on_delete=models.CASCADE
)
educative = models.ForeignKey(
"objective.Educative",
verbose_name="Skill",
related_name="isInToDo",
on_delete=models.CASCADE,
)
date = models.DateField(default=date.today, verbose_name="Date")
def __str__(self):
return "%s, %s - %s" % (
self.gymnast.lastname,
self.gymnast.firstname,
self.educative.shortLabel,
)
class GymnastHasRoutine(models.Model):
"""
Classe représentant le lien entre les gymnastes et leurs séries.
"""
class Meta:
verbose_name = "Gymnast Has Routine"
verbose_name_plural = "Gymnast Has Routines"
ROUTINETYPE_CHOICE = (
(1, "L1"),
(2, "L2"),
(3, "L3"),
(4, "L4"),
(5, "L1S"),
(6, "L2S"),
(7, "L3S"),
(8, "L4S"),
)
gymnast = models.ForeignKey(
Gymnast,
verbose_name="Gymnast",
related_name="has_routine",
on_delete=models.CASCADE,
)
routine = models.ForeignKey(
"objective.Routine",
verbose_name="Routine",
related_name="used_by_gymnast",
on_delete=models.CASCADE,
)
routine_type = models.PositiveSmallIntegerField(
choices=ROUTINETYPE_CHOICE, verbose_name="Type", default="1"
)
datebegin = models.DateField(default=date.today, verbose_name="Date begin")
dateend = models.DateField(
default=date.today, verbose_name="Date end", null=True, blank=True
)
def __str__(self):
return "%s - %s : %s" % (self.gymnast, self.routine_type, self.routine)