347 lines
12 KiB
Python
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)
|