Ultron/ultron/objective/models.py

405 lines
13 KiB
Python

from django.db import models
from django.db.models import Q, Count
from ultron.tools.models import Markdownizable
class Educative(Markdownizable):
"""
Classe `mère` educative. En trampoline tout est un éducatif : un saut, un enchainement, une
série de compétition, ….
"""
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 "%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 (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 "%s (%s)" % (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_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