""" 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") >>> gymnast.nb_known_skill_by_type("rank") """ 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