809 lines
29 KiB
Python
809 lines
29 KiB
Python
from django.db import models
|
||
from django.db.models import Q
|
||
|
||
import re
|
||
from jarvis.core.global_vars import ROUTINE_TYPE_CHOICE
|
||
from jarvis.tools.models import (
|
||
Markdownizable,
|
||
Seasonisable,
|
||
max_even_if_none,
|
||
)
|
||
|
||
# from jarvis.followup.models import ROUTINE_TYPE_CHOICE
|
||
|
||
|
||
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=None):
|
||
"""
|
||
Renvoie le breadcrumb pour l'édutatif courant.
|
||
Exemple :
|
||
>>> s = Skill.objects.get(pk=44)
|
||
>>> s.breadcrumb()
|
||
"""
|
||
if path is None:
|
||
path = []
|
||
|
||
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})""" # pylint: disable=line-too-long
|
||
|
||
|
||
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 combinaison de plusieurs figures. Elle hérite de la classe
|
||
`Educative`. Cette classe permet donc de représenter toute combinaison de figures.
|
||
Il existe 4 types de combinaison :
|
||
- les combinaisons :
|
||
* tout ensemble de plus d'un <Skill>
|
||
* <Skill> liés par des "&"
|
||
- les enchaînements :
|
||
* ce sont des combinaisons particulière : les saut doivent être enchaînés.
|
||
* Peu importe le nombre de <Skill>.
|
||
* <Skill> liés par des "-"
|
||
* is_routine = True
|
||
- les enchaînements de compétition (série de compétition) :
|
||
* ce sont des enchaûnements particuliers :
|
||
* les sauts doivent être enchaînés,
|
||
* au nombre de 10 (pas plus, pas moins),
|
||
* composé que de <Skill> autorisés en compétition,
|
||
* etc.
|
||
* is_routine = True
|
||
* is_competitive = True
|
||
- les éducatifs (enchainements courts en vue d'apprendre une figure précise)
|
||
"""
|
||
|
||
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 save(self, *args, **kwargs):
|
||
# """Calcule les informations de qualité de l'intensité de entraînement et sauve les informations."""
|
||
# super().save(*args, **kwargs)
|
||
|
||
def __str__(self):
|
||
return f"{self.long_label} ({self.short_label})"
|
||
|
||
def inline_str(self):
|
||
tmp_string = ""
|
||
for skill in self.jumps.all():
|
||
if self.is_routine:
|
||
tmp_string = tmp_string + " - " + str(skill)
|
||
else:
|
||
tmp_string = tmp_string + " & " + str(skill)
|
||
return tmp_string[3:]
|
||
|
||
def compute_informations(self):
|
||
"""Cette fonction a pour but d'assurer la cohérence des informations d'une combinaison.
|
||
La fonction vérifie :
|
||
- les âges pour les filles et garçons,
|
||
- le rang,
|
||
- le niveau,
|
||
- la difficulté
|
||
- si c'est une routine (série)
|
||
- si c'est une routine (série) de compétition
|
||
A chaque fois qu'on le peut, on garde les informations entrées par les utilisateurs
|
||
néanmoins on vérifie qu'il n'a pas encodé n'importe quoi.
|
||
"""
|
||
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_routine = False
|
||
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}"
|
||
|
||
|
||
class Passe(Markdownizable):
|
||
"""Classe représentant les passages (à faire pendant un entraînement)."""
|
||
|
||
class Meta:
|
||
ordering = ["label"]
|
||
|
||
label = models.CharField(max_length=30)
|
||
educatives = models.ManyToManyField(Educative, blank=True)
|
||
regexp = models.CharField(max_length=50, null=True, blank=True)
|
||
number_of_skill = models.PositiveSmallIntegerField(default=0)
|
||
difficulty = models.DecimalField(max_digits=4, decimal_places=1, default=0.0)
|
||
# TODO: number_of_skill doit être calculé correctement dans tous les cas.
|
||
|
||
def save(self, *args, **kwargs):
|
||
"""Sauve les informations de la personne et initialise les champs nettoyés.
|
||
On part du principe que self.regexp est correct.
|
||
"""
|
||
self.difficulty = 0
|
||
self.number_of_skill = 0
|
||
super().save(*args, **kwargs)
|
||
# print("Dans le save")
|
||
|
||
if self.educatives.count() == 0:
|
||
# print("educative is none")
|
||
present = False
|
||
operation_list = self.regexp.split(" ")
|
||
for item in ROUTINE_TYPE_CHOICE:
|
||
if item[1] == operation_list[0]:
|
||
present = True
|
||
break
|
||
|
||
if present and len(operation_list) == 2:
|
||
# print("present")
|
||
content = operation_list[1].replace("[", "").replace("]", "")
|
||
ranks = content.split("-")
|
||
|
||
if ranks[0] == "":
|
||
self.number_of_skill += int(ranks[1])
|
||
elif ranks[1] == "":
|
||
self.number_of_skill += (10 - int(ranks[0])) + 1
|
||
else:
|
||
self.number_of_skill += (int(ranks[1]) - int(ranks[0])) + 1
|
||
else:
|
||
self.number_of_skill += 10
|
||
|
||
else:
|
||
for educative in self.educatives.all():
|
||
is_skill = False
|
||
try:
|
||
educative = Routine.objects.get(pk=educative)
|
||
except Routine.DoesNotExist:
|
||
educative = Skill.objects.get(pk=educative)
|
||
is_skill = True
|
||
|
||
if is_skill:
|
||
self.difficulty += educative.difficulty
|
||
self.number_of_skill += 1
|
||
else:
|
||
if self.regexp is not None:
|
||
regexp = self.regexp.replace("[", "").replace("]", "")
|
||
position = regexp.find("-")
|
||
|
||
start = regexp[:position]
|
||
if start == "":
|
||
start = 0
|
||
else:
|
||
start = int(start)
|
||
|
||
end = regexp[position + 1 :]
|
||
if end == "":
|
||
end = educative.jumps.all().count()
|
||
else:
|
||
end = int(end)
|
||
|
||
self.number_of_skill += end - (start - 1)
|
||
list_of_skill = educative.skill_links.filter(
|
||
rank__gte=start, rank__lte=end
|
||
)
|
||
# .aggregate(total=Sum("value"))
|
||
tmp_difficulty = 0
|
||
for routine_skill in list_of_skill:
|
||
tmp_difficulty += routine_skill.skill.difficulty
|
||
self.difficulty += tmp_difficulty
|
||
|
||
else:
|
||
self.number_of_skill += educative.jumps.all().count()
|
||
self.difficulty += educative.difficulty
|
||
|
||
super().save(*args, **kwargs)
|
||
|
||
def __str__(self):
|
||
return f"{self.label} ({self.number_of_skill} | {self.difficulty})"
|
||
|
||
@staticmethod
|
||
def is_valid_regexp_one_arg(arg):
|
||
"""Vérifie une regexp avec un paramètre."""
|
||
if arg == "WC":
|
||
return True
|
||
|
||
if re.match(r"[1-9]+\|", arg):
|
||
return True
|
||
|
||
if not Passe.is_valid_dot(arg):
|
||
return False
|
||
|
||
value = arg.replace(".", "")
|
||
is_valid_routine = Passe.is_valid_routine_type(value)
|
||
|
||
if is_valid_routine:
|
||
return True
|
||
|
||
return Passe.is_valid_subset(arg)
|
||
|
||
@staticmethod
|
||
def is_valid_regexp_two_args(arg1, arg2):
|
||
"""Vérifie une regexp avec deux paramètres."""
|
||
if not Passe.is_valid_dot(arg1):
|
||
return False
|
||
|
||
value = arg1.replace(".", "")
|
||
is_valid_routine = Passe.is_valid_routine_type(value)
|
||
|
||
if is_valid_routine:
|
||
return Passe.is_valid_subset(arg2)
|
||
|
||
return False
|
||
|
||
@staticmethod
|
||
def is_valid_dot(pattern):
|
||
"""Reçoit une chaine de caratère et vérifie que si elle contient un point (.), il se trouve
|
||
soit à la première position soit à la dernière position.
|
||
"""
|
||
last_place = len(pattern) - 1
|
||
if re.search("\.", pattern):
|
||
if pattern[0] != "." and pattern[last_place] != ".":
|
||
return False
|
||
|
||
return True
|
||
|
||
@staticmethod
|
||
def is_valid_routine_type(routine_type):
|
||
"""Recoit une chaine de caractère et vérifie si elle est présente dans une liste."""
|
||
is_valid = False
|
||
for item in ROUTINE_TYPE_CHOICE:
|
||
if item[1] == routine_type:
|
||
is_valid = True
|
||
break
|
||
|
||
return is_valid
|
||
|
||
@staticmethod
|
||
def is_valid_subset(subset):
|
||
"""Reçoit la description d'un subset sous forme de string et vérifie qu'elle est conforme.
|
||
|
||
Format attendu : [X-Y]
|
||
X ou Y peuvent être vide mais pas en même temps.
|
||
X est un entier >= 2
|
||
Y est un entier >= 2 OU Y > X si X est non vide
|
||
|
||
Exemples :
|
||
- [2-8] True
|
||
- [-5] True
|
||
- [3-] True
|
||
- [8-2] False
|
||
- [-] False
|
||
- [1-] False
|
||
- [-1] False
|
||
- [4] False
|
||
- [6-6] False
|
||
"""
|
||
if re.match(r"^\[[2-9]*\-[2-9]*\]$", subset):
|
||
value = subset.replace("[", "").replace("]", "")
|
||
if len(value) > 1:
|
||
ranks = value.split("-")
|
||
|
||
if ranks[0] == "" or ranks[1] == "":
|
||
return True
|
||
|
||
if int(ranks[0]) < int(ranks[1]):
|
||
return True
|
||
|
||
return False
|
||
|
||
@staticmethod
|
||
def is_valid_regexp(regexp):
|
||
"""Vérifie le champ regexp
|
||
|
||
Type de string pris en compte :
|
||
- Toutes les valeurs de ROUTINE_TYPE_CHOICE (Educative vide !)
|
||
- avec [x-y] (avec X ou Y vide mais pas les deux en même temps)
|
||
- [x-y] (avec X ou Y vide mais pas les deux en même temps) (EDUCATIVE non vide !!!)
|
||
- WC
|
||
- x| (x entier)
|
||
|
||
Exemples :
|
||
- Q1R1 True
|
||
- Q1R2 [2-8] True
|
||
- Q2R1 [-5] True
|
||
- SF [6-] True
|
||
- FS [3-7] True
|
||
- Q1R1. True
|
||
- .Q1R2 True
|
||
- Q1R1. [-4] True
|
||
- .Q1R2 [4-] True
|
||
- .FS [3-7] True
|
||
- [2-8] True
|
||
- [-5] True
|
||
- WC True
|
||
- 1| True
|
||
"""
|
||
argument_list = regexp.split(" ")
|
||
|
||
if len(argument_list) >= 3:
|
||
return False
|
||
|
||
if len(argument_list) == 2:
|
||
return Passe.is_valid_regexp_two_args(argument_list[0], argument_list[1])
|
||
else:
|
||
return Passe.is_valid_regexp_one_arg(argument_list[0])
|
||
|
||
return False
|
||
|
||
@staticmethod
|
||
def is_valid_regexp_extended(regexp, label, educatives_list):
|
||
"""Vérifie le champ regexp
|
||
|
||
Type de string pris en compte :
|
||
- Toutes les valeurs de ROUTINE_TYPE_CHOICE (Educative vide !)
|
||
- avec [x-y] (avec X ou Y vide mais pas les deux en même temps)
|
||
- [x-y] (avec X ou Y vide mais pas les deux en même temps) (EDUCATIVE non vide !!!)
|
||
- WC
|
||
- x| (x entier)
|
||
|
||
Exemples :
|
||
- Q1R1 True (si educatives.count() vide)
|
||
- Q1R2 [2-8] True (si educatives.count() vide)
|
||
- Q1R1 [8-2] False
|
||
- Q1R1 [-] False
|
||
- Q2R1 [-5] True (si educatives.count() vide)
|
||
- SF [6-] True (si educatives.count() vide)
|
||
- FS [3-7] True (si educatives.count() vide)
|
||
- Q1R1. True (si educatives.count() vide)
|
||
- .Q1R2 True (si educatives.count() vide)
|
||
- Q1R1. [-4] True (si educatives.count() vide)
|
||
- .Q1R2 [4-] True (si educatives.count() vide)
|
||
- .FS [3-7] True (si educatives.count() vide)
|
||
- [2-8] True (si educative.count() == 1 et educative est une routine >= 8 sauts)
|
||
- [8-2] False
|
||
- [-] False
|
||
- [-5] True (si educative.count() == 1 et educative est une routine >= 8 sauts)
|
||
- WC True (si educatives.count() >= 2)
|
||
- 1| True (si educatives.count() >= 1)
|
||
"""
|
||
# devrait être en dehors de cette fonction -->
|
||
if label is None and educatives_list is None and regexp is None:
|
||
return False
|
||
# <--
|
||
|
||
argument_list = regexp.split(" ")
|
||
|
||
if len(argument_list) >= 3:
|
||
return False
|
||
|
||
if len(argument_list) == 2 and educatives_list.count() == 1:
|
||
return Passe.is_valid_regexp_two_args(argument_list[0], argument_list[1])
|
||
else:
|
||
if (
|
||
argument_list[0] == "WC"
|
||
and educatives_list is not None
|
||
and len(educatives_list) == 2
|
||
):
|
||
return True
|
||
|
||
if re.match(r"[1-9]+\|", argument_list[0]) and len(educatives_list) >= 1:
|
||
return True
|
||
|
||
if not Passe.is_valid_dot(argument_list[0]):
|
||
return False
|
||
|
||
value = argument_list[0].replace(".", "")
|
||
is_valid_routine = Passe.is_valid_routine_type(value)
|
||
|
||
if is_valid_routine:
|
||
return True
|
||
|
||
return Passe.is_valid_subset(argument_list[0])
|
||
|
||
return False
|
||
|
||
|
||
class TrainingProgram(Seasonisable, Markdownizable):
|
||
"""Classe représentant un entraînement (ensemble de passage).
|
||
|
||
TODO:
|
||
- renommer (supprimer/remettre) TrainingProgram en TrainingPasse
|
||
- supprimer Seasonisable
|
||
- supprimer Markdownizable
|
||
- supprimer le champ Gymnast
|
||
- supprimer score
|
||
- supprimer rank ??? (--> dans la M2M)
|
||
- supprimer difficulty ??? (--> dans la M2M)
|
||
"""
|
||
|
||
class Meta:
|
||
verbose_name = "Training Program"
|
||
verbose_name_plural = "Trainings Programs"
|
||
ordering = [
|
||
"rank",
|
||
]
|
||
unique_together = ["date", "gymnast", "rank"]
|
||
|
||
gymnast = models.ForeignKey("people.Gymnast", on_delete=models.CASCADE) # TO DELETE
|
||
passe = models.ForeignKey(Passe, on_delete=models.CASCADE)
|
||
repetition = models.PositiveSmallIntegerField(default=1)
|
||
number_of_skill = models.PositiveSmallIntegerField(default=0)
|
||
difficulty = models.DecimalField(max_digits=4, decimal_places=1, default=0.0)
|
||
rank = models.PositiveSmallIntegerField(default=1) # TO DELETE
|
||
score = models.PositiveSmallIntegerField(blank=True, null=True) # TO DELETE
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
def __str__(self):
|
||
return (
|
||
f"{self.gymnast} {self.date} - {self.rank} : {self.passe} {self.repetition}"
|
||
)
|
||
|
||
def save(self, *args, **kwargs):
|
||
"""Sauve les informations de la personne et initialise les champs nettoyés."""
|
||
super().save(*args, **kwargs)
|
||
self.difficulty = self.passe.difficulty * self.repetition
|
||
self.number_of_skill = self.passe.number_of_skill * self.repetition
|
||
super().save(*args, **kwargs)
|
||
|
||
|
||
# class Training(Seasonisable, Markdownizable):
|
||
# """Classe représentant un entraînement."""
|
||
|
||
# gymnast = models.ForeignKey("people.Gymnast", on_delete=models.CASCADE)
|
||
# difficulty = models.DecimalField(max_digits=4, decimal_places=1, default=0.0)
|
||
# number_of_skill = models.PositiveSmallIntegerField(default=0)
|
||
# score = models.PositiveSmallIntegerField(default=1)
|