from django.db import models from django.db.models import Q, Count from jarvis.tools.models import Markdownizable, max_even_if_none class Educative(Markdownizable): """ Classe `mère` educative. En trampoline tout est un éducatif : un saut, un enchainement, une série de compétition, …. Level (skill) : Toutes les figures appartiennent à un niveau. Un niveau peut contenir plusieurs figures. Par défaut, le niveau d'une figure est son coéfficient de difficulté (exprimé en 10ème pour avoir des nombres entiers) auquel on ajoute 1 pour les positions tendue. Exemples : - saut groupé, saut carpé joint et saut écart ==> niveau 0 - salto avant groupé, salto arrière groupé ==> niveau 5 - salto avant carpé, barani groupé, salto arrière carpé ==> niveau 6 - salto avant tendu, salto arrière tendu, barani tendu ==> niveau 7 En plus de cela, il y a une limite minimum : le niveau d’une figure ne peut pas être plus petit que le niveau maximum de ses prérequis. Le niveau, avec le rang, ont pour but d’aider les coaches à planifier l’évolution et l’ apprentissage des figures les unes par rapport aux autres. Level (routine) : Toutes les séries ont également un niveau. Par défaut le niveau d'une série est le niveau maximum des figures qui composent la série. Rank (skill) : Le rang permet, en plus du `level` (niveau), de classer les figures entre elles, de leur donner un ordre (informatif). Le rang d’une figure est calculé par rapport aux prérequis et au niveau : par défaut le rang d’une figure est le maximum entre le niveau maximum de ses prérequis plus un et le niveau de la figure. Le rang, avec le niveau, ont pour but d’aider les coaches à planifier l’évolution et l’apprentissage des figures les unes par rapport aux autres. """ AGE_CHOICES = ( (6, "6-7"), (7, "7-8"), (8, "8-9"), (9, "9-10"), (10, "10-11"), (11, "11-12"), (12, "12-13"), (13, "13-14"), (14, "14-15"), (15, "15-16"), (16, "16-17"), (17, "17+"), ) class Meta: verbose_name = "Educatif" verbose_name_plural = "Educatifs" ordering = ["long_label", "short_label"] # 'level', long_label = models.CharField(max_length=255, verbose_name="Long Name") short_label = models.CharField(max_length=255, verbose_name="Short Name") difficulty = models.DecimalField( max_digits=3, decimal_places=1, verbose_name="Difficulty", default=0.000 ) level = models.PositiveSmallIntegerField(verbose_name="Level", default=0) rank = models.PositiveSmallIntegerField(verbose_name="Rank", default=0) educatives = models.ManyToManyField( "self", related_name="educatives_of", blank=True, symmetrical=False ) prerequisites = models.ManyToManyField( "self", related_name="prerequisite_of", blank=True, symmetrical=False ) age_boy_with_help = models.PositiveSmallIntegerField( choices=AGE_CHOICES, verbose_name="Boy's age with help", default=6 ) age_boy_without_help = models.PositiveSmallIntegerField( choices=AGE_CHOICES, verbose_name="Boy's age without help", default=6 ) age_boy_chained = models.PositiveSmallIntegerField( choices=AGE_CHOICES, verbose_name="Boy's age chained", default=6 ) age_boy_masterised = models.PositiveSmallIntegerField( choices=AGE_CHOICES, verbose_name="Boy's age masterised", default=6 ) age_girl_with_help = models.PositiveSmallIntegerField( choices=AGE_CHOICES, verbose_name="Girl's age with help", default=6 ) age_girl_without_help = models.PositiveSmallIntegerField( choices=AGE_CHOICES, verbose_name="Girl's age without help", default=6 ) age_girl_chained = models.PositiveSmallIntegerField( choices=AGE_CHOICES, verbose_name="Girl's age chained", default=6 ) age_girl_masterised = models.PositiveSmallIntegerField( choices=AGE_CHOICES, verbose_name="Girl's age masterised", default=6 ) # is_competitive = models.BooleanField(default=False) def __str__(self): return f"{self.long_label} ({self.short_label} - {self.difficulty})" def breadcrumb(self, path=[]): """ Renvoie le breadcrumb pour l'édutatif courant. Exemple : >>> s = Skill.objects.get(pk=44) >>> s.breadcrumb() """ path = [self] + path if self.prerequisites.all().count() == 0: return [path] path_list = [] for prerequisite in self.prerequisites.all(): if prerequisite.id == self.id: return [self] new_paths = prerequisite.breadcrumb(path) for new_path in new_paths: path_list.append(new_path) return path_list class PrerequisiteClosure(models.Model): """ Closure table de prérequis """ class Meta: unique_together = ("descendant", "ancestor", "level", "path") descendant = models.ForeignKey( Educative, on_delete=models.CASCADE, related_name="ancestor" ) ancestor = models.ForeignKey( Educative, on_delete=models.CASCADE, related_name="descendants" ) level = models.PositiveIntegerField() path = models.PositiveIntegerField() def __str__(self): return f"{self.ancestor.long_label} -> {self.descendant.long_label} ({self.level}|{self.path})" class TouchPosition(models.Model): """ Classe représentant les différentes position d'arrivée/départ (landing position) en trampoline. """ class Meta: verbose_name = "Landing" verbose_name_plural = "Landings" ordering = ["long_label", "short_label", "is_default", "allowed_in_competition"] long_label = models.CharField(max_length=30, verbose_name="Long label") short_label = models.CharField(max_length=15, verbose_name="Short label") allowed_in_competition = models.BooleanField( verbose_name="Allowed in competition", default=True ) is_default = models.BooleanField(verbose_name="Défault ?", default=False) def __str__(self): return f"{self.long_label}" def get_default_position(): """ Renvoie la position d'arrivée/départ par définie par défaut si elle existe. Sinon, renvoie None. """ try: return TouchPosition.objects.get(is_default=True).id except TouchPosition.DoesNotExist: return None class Skill(Educative): """ Classe représentant une figure (un mouvement, un saut acrobatique). Elle hérite de la classe `Educative`. """ # SELECT * FROM `objective_skill` # WHERE educative_ptr_id NOT IN ( # SELECT DISTINCT(from_educative_id) FROM `objective_educative_prerequisite` # ) # # SELECT * FROM `objective_skill`, `objective_educative` # WHERE `objective_educative`.id = `objective_skill`.educative_ptr_id class Meta: verbose_name = "Skill" verbose_name_plural = "Skills" POSITION_CHOICES = ( ("0", "none"), ("o", "tuck"), ("c", "puck"), ("<", "pike"), ("L", "half pike"), ("/", "straight"), ("//", "straddle"), ) ROTATION_CHOICES = ( (0, "none"), (1, "frontward"), (2, "backward"), ) position = models.CharField(max_length=2, choices=POSITION_CHOICES) departure = models.ForeignKey( TouchPosition, related_name="depart_of", default=get_default_position, verbose_name="Take-off position", on_delete=models.CASCADE, ) landing = models.ForeignKey( TouchPosition, related_name="landing_of", default=get_default_position, verbose_name="Landing position", on_delete=models.CASCADE, ) rotation_type = models.PositiveSmallIntegerField( choices=ROTATION_CHOICES, verbose_name="Type de rotation" ) rotation = models.PositiveSmallIntegerField(verbose_name="¼ de rotation") twist = models.PositiveSmallIntegerField(verbose_name="½ Vrille") notation = models.CharField(max_length=10) simplified_notation = models.CharField( max_length=10, verbose_name="Notation simplifiée" ) is_competitive = models.BooleanField(default=False) # importance = models.PositiveSmallIntegerField(default = 1) def __str__(self): return f"{self.long_label} ({self.notation})" class Routine(Educative): """ Classe représentant une série (enchainement de plusieurs figures). Elle hérite de la classe `Educative`. """ class Meta: verbose_name = "Routine" verbose_name_plural = "Routines" jumps = models.ManyToManyField( Skill, through="RoutineSkill", verbose_name="routine" ) is_active = models.BooleanField(default=True) is_routine = models.BooleanField(default=False) is_competitive = models.BooleanField(default=False) def __str__(self): return f"{self.long_label} ({self.short_label})" def compute_informations(self): """Calcule les informations (rank, niveau, ages, …) d'une routine.""" rank = 0 level = 0 age_boy_with_help = 0 age_girl_with_help = 0 age_boy_without_help = 0 age_girl_without_help = 0 age_boy_chained = 0 age_girl_chained = 0 age_boy_masterised = 0 age_girl_masterised = 0 difficulty = 0 is_competitive = True for skill_link in self.skill_links.all(): skill = skill_link.skill difficulty += skill.difficulty level = max(skill.level, level) rank = max(skill.rank + 1, rank) if not skill.is_competitive: is_competitive = False # Age boy computing age_boy_with_help = max_even_if_none( skill.age_boy_with_help, age_boy_with_help ) age_boy_without_help = max_even_if_none( skill.age_boy_without_help, age_boy_without_help ) age_boy_chained = max_even_if_none(skill.age_boy_chained, age_boy_chained) age_boy_masterised = max_even_if_none( skill.age_boy_masterised, age_boy_masterised ) # Age girl computing age_girl_with_help = max_even_if_none( skill.age_girl_with_help, age_girl_with_help ) age_girl_without_help = max_even_if_none( skill.age_girl_without_help, age_girl_without_help ) age_girl_chained = max_even_if_none( skill.age_girl_chained, age_girl_chained ) age_girl_masterised = max_even_if_none( skill.age_girl_masterised, age_girl_masterised ) if self.skill_links.all().count() != 10: is_competitive = False self.is_competitive = is_competitive self.difficulty = difficulty self.level = max(self.level, level) self.rank = max(self.rank, rank) self.age_boy_with_help = max(self.age_boy_with_help, age_boy_with_help) self.age_boy_without_help = max(self.age_boy_without_help, age_boy_without_help) self.age_boy_chained = max(self.age_boy_chained, age_boy_chained) self.age_boy_masterised = max(self.age_boy_masterised, age_boy_masterised) self.age_girl_with_help = max(self.age_girl_with_help, age_girl_with_help) self.age_girl_without_help = max( self.age_girl_without_help, age_girl_without_help ) self.age_girl_chained = max(self.age_girl_chained, age_girl_chained) self.age_girl_masterised = max(self.age_girl_masterised, age_girl_masterised) self.save() def contains_basic_jumps(self): """ Renvoie True si la série contient au moins un saut de base, False sinon. """ return self.skill_links.filter(skill__notation__in=["//", "<", "o"]).exists() def contains_basic_fall(self): """ Renvoie True si la série contient au moins un tomber de base, False sinon. """ return self.skill_links.filter( skill__landing__long_label__in=["Assis", "Dos", "Ventre"] ).exists() def contains_basic_salto(self): """ Renvoie True si la série contient au moins un salto/barani de base, False sinon. """ return self.skill_links.filter( Q(skill__notation__icontains=".41") | Q(skill__notation__icontains="4.-") ).exists() def contains_basic_three_quarters(self): """ Renvoie True si la série contient au moins un 3/4 de salto, False sinon. """ return self.skill_links.filter( Q(skill__notation__icontains=".3") | Q(skill__notation__icontains="3.") ).exists() def contains_basic_twist(self): """ Renvoie True si la série contient au moins une vrille de base, False sinon. """ return self.skill_links.filter( Q(skill__notation__icontains=".43") | Q(skill__notation__icontains="4.2") ).exists() def contains_double(self): """ Renvoie True si la série contient au moins un double, False sinon. """ return self.skill_links.filter( Q(skill__notation__icontains=".8") | Q(skill__notation__icontains="8.") ).exists() def contains_triple(self): """ Renvoie True si la série contient au moins un triple, False sinon. """ return self.skill_links.filter( Q(skill__notation__icontains=".12") | Q(skill__notation__icontains="12.") ).exists() class RoutineSkill(models.Model): """ Classe de liaison permettant de liée une figure à une série. (relation n-n) """ class Meta: ordering = ("rank",) routine = models.ForeignKey( Routine, on_delete=models.CASCADE, default=None, related_name="skill_links" ) skill = models.ForeignKey( Skill, on_delete=models.CASCADE, default=None, related_name="routine_links" ) rank = models.PositiveSmallIntegerField() def __str__(self): return f"{self.rank} - {self.routine.short_label} : {self.skill.short_label}"