Jarvis/jarvis/objective/models.py

809 lines
29 KiB
Python
Raw Normal View History

2023-04-25 17:06:14 +02:00
from django.db import models
2023-05-08 10:12:15 +02:00
from django.db.models import Q
2023-04-25 17:06:14 +02:00
2024-03-06 15:13:09 +01:00
import re
from jarvis.core.global_vars import ROUTINE_TYPE_CHOICE
2024-02-29 16:20:29 +01:00
from jarvis.tools.models import (
Markdownizable,
Seasonisable,
max_even_if_none,
)
2023-04-25 17:06:14 +02:00
2024-03-06 15:13:09 +01:00
# from jarvis.followup.models import ROUTINE_TYPE_CHOICE
2023-04-25 17:06:14 +02:00
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 dune figure ne peut pas être plus
petit que le niveau maximum de ses prérequis.
Le niveau, avec le rang, ont pour but daider 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 dune figure est calculé par rapport aux prérequis et
au niveau : par défaut le rang dune 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 daider les coaches à planifier lévolution et
lapprentissage des figures les unes par rapport aux autres.
"""
2024-03-10 13:20:09 +01:00
AGE_CHOICES = [
2023-04-25 17:06:14 +02:00
(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+"),
2024-03-10 13:20:09 +01:00
]
2023-04-25 17:06:14 +02:00
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):
2023-04-29 15:31:14 +02:00
return f"{self.long_label} ({self.short_label} - {self.difficulty})"
2023-04-25 17:06:14 +02:00
2024-01-14 14:48:31 +01:00
def breadcrumb(self, path=None):
2023-04-25 17:06:14 +02:00
"""
Renvoie le breadcrumb pour l'édutatif courant.
Exemple :
>>> s = Skill.objects.get(pk=44)
>>> s.breadcrumb()
"""
2024-01-14 14:48:31 +01:00
if path is None:
path = []
2023-04-25 17:06:14 +02:00
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):
2024-01-14 14:48:31 +01:00
return f"""{self.ancestor.long_label} -> {self.descendant.long_label} ({self.level}|{self.path})""" # pylint: disable=line-too-long
2023-04-25 17:06:14 +02:00
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):
2023-04-29 15:31:14 +02:00
return f"{self.long_label}"
2023-04-25 17:06:14 +02:00
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):
2023-04-29 15:31:14 +02:00
return f"{self.long_label} ({self.notation})"
2023-04-25 17:06:14 +02:00
class Routine(Educative):
"""
2024-02-24 22:27:20 +01:00
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)
2023-04-25 17:06:14 +02:00
"""
class Meta:
verbose_name = "Routine"
verbose_name_plural = "Routines"
jumps = models.ManyToManyField(
Skill, through="RoutineSkill", verbose_name="routine"
)
is_active = models.BooleanField(default=True)
2023-05-01 17:07:50 +02:00
is_routine = models.BooleanField(default=False)
2023-04-25 17:06:14 +02:00
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)
2023-04-25 17:06:14 +02:00
def __str__(self):
2023-04-29 15:31:14 +02:00
return f"{self.long_label} ({self.short_label})"
2023-04-25 17:06:14 +02:00
2024-02-25 10:28:58 +01:00
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:]
2023-04-25 17:06:14 +02:00
def compute_informations(self):
2023-05-08 10:12:15 +02:00
"""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
2024-01-14 14:48:31 +01:00
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.
2023-05-08 10:12:15 +02:00
"""
2023-04-25 17:06:14 +02:00
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
2023-04-25 17:06:14 +02:00
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
2023-04-25 17:06:14 +02:00
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):
2023-04-29 15:31:14 +02:00
return f"{self.rank} - {self.routine.short_label} : {self.skill.short_label}"
2024-02-11 17:59:00 +01:00
2024-02-25 20:02:20 +01:00
class Passe(Markdownizable):
2024-02-25 10:28:58 +01:00
"""Classe représentant les passages (à faire pendant un entraînement)."""
2024-02-11 17:59:00 +01:00
class Meta:
ordering = ["label"]
label = models.CharField(max_length=30)
2024-03-06 15:13:09 +01:00
educatives = models.ManyToManyField(Educative, blank=True)
2024-02-25 10:28:58 +01:00
regexp = models.CharField(max_length=50, null=True, blank=True)
2024-02-29 16:20:29 +01:00
number_of_skill = models.PositiveSmallIntegerField(default=0)
difficulty = models.DecimalField(max_digits=4, decimal_places=1, default=0.0)
2024-03-09 08:06:21 +01:00
# TODO: number_of_skill doit être calculé correctement dans tous les cas.
2024-02-25 20:02:20 +01:00
def save(self, *args, **kwargs):
2024-03-09 08:06:21 +01:00
"""Sauve les informations de la personne et initialise les champs nettoyés.
On part du principe que self.regexp est correct.
"""
2024-02-29 16:20:29 +01:00
self.difficulty = 0
2024-03-01 13:52:54 +01:00
self.number_of_skill = 0
2024-02-29 16:20:29 +01:00
super().save(*args, **kwargs)
2024-03-10 13:20:09 +01:00
# print("Dans le save")
2024-03-09 08:06:21 +01:00
if self.educatives.count() == 0:
2024-03-10 13:20:09 +01:00
# print("educative is none")
2024-03-09 08:06:21 +01:00
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:
2024-03-10 13:20:09 +01:00
# print("present")
2024-03-09 08:06:21 +01:00
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
2024-02-29 16:20:29 +01:00
else:
2024-03-09 08:06:21 +01:00
self.number_of_skill += 10
2024-02-29 16:20:29 +01:00
2024-03-09 08:06:21 +01:00
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("-")
2024-02-29 16:20:29 +01:00
2024-03-09 08:06:21 +01:00
start = regexp[:position]
if start == "":
start = 0
else:
start = int(start)
2024-02-25 20:02:20 +01:00
2024-03-09 08:06:21 +01:00
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
2024-02-25 20:02:20 +01:00
super().save(*args, **kwargs)
2024-02-11 17:59:00 +01:00
2024-02-25 10:28:58 +01:00
def __str__(self):
return f"{self.label} ({self.number_of_skill} | {self.difficulty})"
2024-02-29 16:20:29 +01:00
2024-03-09 20:20:08 +01:00
@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
2024-03-06 15:13:09 +01:00
@staticmethod
2024-03-09 08:06:21 +01:00
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 :
2024-03-09 20:20:08 +01:00
- 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
2024-03-09 08:06:21 +01:00
"""
2024-03-09 20:20:08 +01:00
argument_list = regexp.split(" ")
2024-03-09 08:06:21 +01:00
2024-03-09 20:20:08 +01:00
if len(argument_list) >= 3:
2024-03-09 08:06:21 +01:00
return False
2024-03-09 20:20:08 +01:00
if len(argument_list) == 2:
return Passe.is_valid_regexp_two_args(argument_list[0], argument_list[1])
2024-03-09 08:06:21 +01:00
else:
2024-03-09 20:20:08 +01:00
return Passe.is_valid_regexp_one_arg(argument_list[0])
2024-03-09 08:06:21 +01:00
return False
@staticmethod
def is_valid_regexp_extended(regexp, label, educatives_list):
2024-03-06 15:13:09 +01:00
"""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
2024-03-09 08:06:21 +01:00
- Q1R1 [-] False
- Q2R1 [-5] True (si educatives.count() vide)
2024-03-06 15:13:09 +01:00
- SF [6-] True (si educatives.count() vide)
- FS [3-7] True (si educatives.count() vide)
2024-03-09 08:06:21 +01:00
- 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)
2024-03-06 15:13:09 +01:00
- WC True (si educatives.count() >= 2)
2024-03-09 08:06:21 +01:00
- 1| True (si educatives.count() >= 1)
2024-03-06 15:13:09 +01:00
"""
2024-03-09 08:06:21 +01:00
# devrait être en dehors de cette fonction -->
2024-03-06 15:13:09 +01:00
if label is None and educatives_list is None and regexp is None:
return False
2024-03-09 08:06:21 +01:00
# <--
2024-03-06 15:13:09 +01:00
2024-03-10 13:20:09 +01:00
argument_list = regexp.split(" ")
2024-03-06 15:13:09 +01:00
2024-03-10 13:20:09 +01:00
if len(argument_list) >= 3:
2024-03-06 15:13:09 +01:00
return False
2024-03-10 13:20:09 +01:00
if len(argument_list) == 2 and educatives_list.count() == 1:
return Passe.is_valid_regexp_two_args(argument_list[0], argument_list[1])
2024-03-06 15:13:09 +01:00
else:
2024-03-09 08:06:21 +01:00
if (
2024-03-10 13:20:09 +01:00
argument_list[0] == "WC"
2024-03-09 08:06:21 +01:00
and educatives_list is not None
and len(educatives_list) == 2
):
2024-03-06 15:13:09 +01:00
return True
2024-03-10 13:20:09 +01:00
if re.match(r"[1-9]+\|", argument_list[0]) and len(educatives_list) >= 1:
2024-03-06 15:13:09 +01:00
return True
2024-03-10 13:20:09 +01:00
if not Passe.is_valid_dot(argument_list[0]):
2024-03-09 08:06:21 +01:00
return False
2024-03-10 13:20:09 +01:00
value = argument_list[0].replace(".", "")
2024-03-09 08:06:21 +01:00
is_valid_routine = Passe.is_valid_routine_type(value)
if is_valid_routine:
return True
2024-03-10 13:20:09 +01:00
return Passe.is_valid_subset(argument_list[0])
2024-03-09 08:06:21 +01:00
2024-03-06 15:13:09 +01:00
return False
2024-02-11 17:59:00 +01:00
class TrainingProgram(Seasonisable, Markdownizable):
2024-03-10 13:20:09 +01:00
"""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)
"""
2024-02-11 17:59:00 +01:00
2024-03-01 13:52:54 +01:00
class Meta:
verbose_name = "Training Program"
verbose_name_plural = "Trainings Programs"
ordering = [
"rank",
]
2024-03-01 14:12:33 +01:00
unique_together = ["date", "gymnast", "rank"]
2024-03-01 13:52:54 +01:00
2024-03-10 13:20:09 +01:00
gymnast = models.ForeignKey("people.Gymnast", on_delete=models.CASCADE) # TO DELETE
2024-02-29 16:20:29 +01:00
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)
2024-03-10 13:20:09 +01:00
rank = models.PositiveSmallIntegerField(default=1) # TO DELETE
score = models.PositiveSmallIntegerField(blank=True, null=True) # TO DELETE
2024-02-29 16:20:29 +01:00
updated_at = models.DateTimeField(auto_now=True)
2024-02-11 17:59:00 +01:00
2024-02-29 16:20:29 +01:00
def __str__(self):
return (
f"{self.gymnast} {self.date} - {self.rank} : {self.passe} {self.repetition}"
)
2024-03-01 13:52:54 +01:00
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)
2024-03-10 13:20:09 +01:00
# 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)