Jarvis/jarvis/people/models.py

268 lines
8.9 KiB
Python

""" Modelisation des gymnastes """
from datetime import date
from django.contrib.auth import get_user_model
from django.db.models import Count
from django.db import models
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, "Female"), (1, "Male"))
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 not in ("level", "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 not in ("level", "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 not in ("level", "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