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