Jarvis/jarvis/people/models.py

292 lines
10 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.core.global_vars import GENDER_CHOICES, ORIENTATION_CHOICES
from jarvis.objective.models import Skill
# from jarvis.profiles.models import TrainerGymnast
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,
)
from jarvis.tools.clean_name import clean_name
User = get_user_model()
class Gymnast(Markdownizable):
"""Représente un gymnaste.
Un gymnaste peut être actif ou inactif.
"""
class Meta:
verbose_name = "Gymnast"
verbose_name_plural = "Gymnasts"
ordering = ["first_name", "last_name"]
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)
cleaned_last_name = models.CharField(max_length=40, null=False, blank=False)
first_name = models.CharField(max_length=25, null=False, blank=False)
cleaned_first_name = models.CharField(max_length=25, null=False, blank=False)
birthdate = models.DateField(verbose_name="Birth date")
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)
trainers = models.ManyToManyField(
User,
through="profiles.TrainerGymnast",
through_fields=("gymnast", "user"),
related_name="gymnasts",
)
def save(self, *args, **kwargs):
"""Sauve les informations de la personne et initialise les champs nettoyés."""
self.cleaned_last_name = clean_name(self.last_name)
self.cleaned_first_name = clean_name(self.first_name)
super().save(*args, **kwargs)
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."""
# TODO: utiliser pendulum.age : age = pendulum.datetime(1985, 7, 3).age
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 type (niveau ou 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()
context["gymnast_nb_known_skills"] = gymnast_nb_known_skills
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["percentages"] = 0
context["completude"], context["estimated_level"], context["percentage_known_skill"] = compute_completude(
context["total_skill"], gymnast_nb_known_skills, skill_max
)
# print(context["percentage_known_skill"])
# print(context["gymnast_nb_known_skills"])
# print(context["completude"])
# print(context["estimated_level"])
context["max_" + desired_type + "_skill"] = skill_max
return context