269 lines
9.0 KiB
Python
269 lines
9.0 KiB
Python
""" Modelisation des gymnastes """
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.db.models import Count
|
|
from django.db import models
|
|
|
|
from datetime import date
|
|
import pendulum
|
|
|
|
from jarvis.objective.models import Skill
|
|
from jarvis.tools.models import Markdownizable
|
|
from jarvis.objective.tools import (
|
|
nb_skill_by_type,
|
|
nb_skill_lte_type,
|
|
compute_completude,
|
|
compute_statistics_by_type,
|
|
)
|
|
|
|
User = get_user_model()
|
|
|
|
GENDER_CHOICES = ((0, "Male"), (1, "Female"))
|
|
|
|
|
|
class Gymnast(Markdownizable):
|
|
"""Représente un gymnaste.
|
|
Un gymnaste peut être actif ou inactif.
|
|
"""
|
|
|
|
class Meta:
|
|
verbose_name = "Gymnast"
|
|
verbose_name_plural = "Gymnasts"
|
|
|
|
ORIENTATION_CHOICES = ((None, "Unknown"), (1, "Left"), (2, "Right"))
|
|
|
|
user = models.OneToOneField(
|
|
User, on_delete=models.SET_NULL, related_name="gymnast", blank=True, null=True
|
|
)
|
|
last_name = models.CharField(max_length=40, null=False, blank=False)
|
|
first_name = models.CharField(max_length=25, null=False, blank=False)
|
|
birthdate = models.DateField(verbose_name="Date de naissance")
|
|
gender = models.PositiveSmallIntegerField(
|
|
choices=GENDER_CHOICES, verbose_name="Sexe"
|
|
)
|
|
is_active = models.BooleanField(default=1, verbose_name="Active")
|
|
year_of_practice = models.PositiveSmallIntegerField(default=0)
|
|
orientation = models.PositiveSmallIntegerField(
|
|
choices=ORIENTATION_CHOICES,
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="Twist side",
|
|
)
|
|
email_trainer = models.EmailField(
|
|
max_length=254, null=True, blank=True, verbose_name="Trainer's email"
|
|
)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return f"{self.first_name} {self.last_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.
|
|
|
|
Example: (en supposant qu'on soit le 23/05/2019)
|
|
>>> from datetime import date
|
|
>>> gymnast = Gymnast(name='Tru', firstname='Gregg', birthdate=date(1982, 2, 5))
|
|
>>> gymnast.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
|
|
|
|
def skill_max_for_type(self, desired_type="level"):
|
|
"""
|
|
Renvoie le niveau/rank maximum des skill que le gymnaste sait faire suivant le type passé
|
|
en paramètre.
|
|
|
|
Args:
|
|
desired_type (string): type ("level" ou "rank") des Skills désirés.
|
|
|
|
Returns:
|
|
int
|
|
|
|
Examples:
|
|
>>> from jarvis.people.models import Gymnast
|
|
>>> gymnast = Gymnast.objects.get(pk=1)
|
|
>>> gymnast.skill_max_for_type("level")
|
|
1
|
|
>>> gymnast.skill_max_for_type("rank")
|
|
19
|
|
"""
|
|
if desired_type != "level" and desired_type != "rank":
|
|
return None
|
|
|
|
tmp = (
|
|
Skill.objects.filter(known_by__gymnast=self.id)
|
|
.order_by("-" + desired_type)
|
|
.values(desired_type)[:1]
|
|
)
|
|
if tmp:
|
|
skill_max = tmp[0][desired_type]
|
|
else:
|
|
skill_max = 0
|
|
|
|
return skill_max
|
|
|
|
def nb_known_skill_by_type(self, desired_type="level"):
|
|
"""
|
|
Renvoie le nombre de Skill qu'un gymnast sait faire par niveau/rang suivant le type passé
|
|
en paramètre
|
|
|
|
Args:
|
|
desired_type (string): type ("level" ou "rank") des Skills désirés.
|
|
|
|
Returns:
|
|
QuerySet
|
|
|
|
Examples:
|
|
>>> from jarvis.people.models import Gymnast
|
|
>>> gymnast = Gymnast.objects.get(pk=1)
|
|
>>> gymnast.nb_known_skill_by_type("level")
|
|
<QuerySet [{'level': 0, 'nb_known_skill': 15}, {'level': 1, 'nb_known_skill': 5}]>
|
|
>>> gymnast.nb_known_skill_by_type("rank")
|
|
<QuerySet [{'rank': 1, 'nb_known_skill': 2}, {'rank': 2, 'nb_known_skill': 4}, …]>
|
|
"""
|
|
if desired_type != "level" and desired_type != "rank":
|
|
return None
|
|
|
|
nb_known_skill_by_type = (
|
|
Skill.objects.values(desired_type)
|
|
.filter(known_by__gymnast=self.id)
|
|
.order_by(desired_type)
|
|
.annotate(nb_known_skill=Count("id"))
|
|
)
|
|
return nb_known_skill_by_type
|
|
|
|
def unknown_skill_lte_level(self, max_level_skill):
|
|
"""
|
|
Liste des Skill que le gymnaste ne sait PAS faire/
|
|
|
|
Ces skills sont classé par niveau.
|
|
"""
|
|
return Skill.objects.filter(level__lte=max_level_skill).exclude(
|
|
known_by__gymnast=self.id
|
|
)
|
|
|
|
def unknown_skill_gt_level(self, max_level_skill):
|
|
"""
|
|
Liste des Skill que le gymnaste ne sait PAS faire,
|
|
classé par niveau (ayant un niveau inférieur ou égal au niveau max du gym)
|
|
"""
|
|
return Skill.objects.filter(level__gt=max_level_skill).exclude(
|
|
known_by__gymnast=self.id
|
|
)
|
|
|
|
def unknown_skill_lte_rank(self, max_rank_skill):
|
|
# Liste des Skill que le gymnaste ne sait PAS faire, classé par niveau
|
|
# (ayant un niveau inférieur ou égal au niveau max du gym)
|
|
return Skill.objects.filter(level__lte=max_rank_skill).exclude(
|
|
known_by__gymnast=self.id
|
|
)
|
|
|
|
def unknown_skill_gt_rank(self, max_rank_skill):
|
|
"""
|
|
Liste des Skill que le gymnaste ne sais PAS faire
|
|
(ayant un niveau plus grand que le niveau max du gym)
|
|
"""
|
|
return Skill.objects.filter(level__gt=max_rank_skill).exclude(
|
|
known_by__gymnast=self.id
|
|
)
|
|
|
|
def get_informations_from_type(self, desired_type="level"):
|
|
"""
|
|
Calcule toutes les statistiques par rapport au niveau/rang suivant le paramètre transmis.
|
|
|
|
1. On va chercher le niveau/rang maximum de skill que le gymnast sait faire
|
|
2. 0n va chercher le nombre de skill par niveau/rang que le gymnast sait faire
|
|
nb_known_skill_by_type = [
|
|
{
|
|
'level': 1,
|
|
'nb_known_skill': 1
|
|
},
|
|
{
|
|
'level': 2,
|
|
'nb_known_skill': 2
|
|
}
|
|
]
|
|
3. Si le niveau/rang maximum est supérieur à 0:
|
|
a. on va chercher le nombre de skill qu'il y a par niveau/rang :
|
|
[
|
|
{
|
|
'level': 1, 'nb_skill': 1
|
|
},
|
|
{
|
|
'level': 2, 'nb_skill': 1
|
|
}
|
|
]
|
|
|
|
b. on va chercher le nombre total des skills dont le niveau/rang est inférieur ou
|
|
égale au niveau max.
|
|
|
|
c. on calcule les statistiques du gymnaste par rapport aux skill
|
|
|
|
SINON:
|
|
on va chercher tous les Skill qui existent et on les compte.
|
|
|
|
"""
|
|
if desired_type != "level" and desired_type != "rank":
|
|
return None
|
|
|
|
context = {}
|
|
skill_max = self.skill_max_for_type(desired_type)
|
|
gymnast_nb_known_skills = self.known_skills.distinct("skill").count()
|
|
|
|
if skill_max > 0:
|
|
cpt_known_skill_by_type = self.nb_known_skill_by_type(
|
|
desired_type
|
|
) # à remonter ???
|
|
cpt_skill_by_type = nb_skill_by_type(skill_max, desired_type)
|
|
context["total_skill"] = nb_skill_lte_type(skill_max, desired_type)
|
|
context["percentages"] = compute_statistics_by_type(
|
|
cpt_skill_by_type, cpt_known_skill_by_type, desired_type
|
|
)
|
|
context["skill_by_level"] = self.unknown_skill_lte_level(skill_max)
|
|
context["unknown_skill"] = self.unknown_skill_gt_level(skill_max)
|
|
else:
|
|
tmp = Skill.objects.all()
|
|
context["total_skill"] = tmp.count()
|
|
context["unknown_skill"] = tmp
|
|
|
|
context["completude"], context["evaluated_level"] = compute_completude(
|
|
context["total_skill"], gymnast_nb_known_skills, skill_max
|
|
)
|
|
|
|
context["max_" + desired_type + "_skill"] = skill_max
|
|
return context
|