from django.db import models from django.db.models import Q, Count from ultron.tools.models import Markdownizable class Educative(Markdownizable): """ Classe `mère`. """ 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 ) def __str__(self): return "%s (%s - %s)" % ( 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 "%s -> %s (%s|%s)" % ( 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 "%s" % (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 (aka un saut acrobatique). """ # 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="1/4 de rotation") twist = models.PositiveSmallIntegerField(verbose_name="1/2 Vrille") notation = models.CharField(max_length=25) simplified_notation = models.CharField( max_length=25, verbose_name="Notation simplifiée" ) is_competitive = models.BooleanField(default=False) # importance = models.PositiveSmallIntegerField(default = 1) def __str__(self): return "%s (%s)" % (self.long_label, self.notation) class Routine(Educative): """ Classe représentant une série (enchainement de plusieurs figures). """ 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_competitive = models.BooleanField(default=False) def __str__(self): return "%s (%s)" % (self.long_label, self.short_label) def compute_informations(self): """ """ 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.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.is_competitive = is_competitive 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 "%s - %s : %s" % ( self.rank, self.routine.short_label, self.skill.short_label, ) def max_even_if_none(value_1, value_2): """ Renvoie le maximum de deux valeurs même si l'une des deux vaut None. """ if value_1 is not None and value_2 is not None: if value_1 > value_2: return value_1 else: return value_2 elif value_1 is not None: return value_1 elif value_2 is not None: return value_2 else: return 0