khana/khana/objective/models.py

296 lines
9.1 KiB
Python

from datetime import date
from django.contrib.auth import get_user_model
from django.db import models
from django.db.models import Q
from khana.base.models import Markdownizable
User = get_user_model()
class Educative(Markdownizable):
"""
Classe `mère`.
"""
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"
)
level = models.PositiveSmallIntegerField(verbose_name="Level", default=0)
rank = models.PositiveSmallIntegerField(verbose_name="Rank", default=0)
educatives = models.ManyToManyField(
"self", related_name="educativeOf", blank=True, symmetrical=False
)
prerequisites = models.ManyToManyField(
"self", related_name="prerequisiteOf", blank=True, symmetrical=False
)
age_boy = models.PositiveSmallIntegerField(
blank=True, null=True, verbose_name="Boy's age"
)
age_girl = models.PositiveSmallIntegerField(
blank=True, null=True, verbose_name="Girl's age"
)
def __str__(self):
return "%s - %s (level: %s | diff: %s)" % (
self.rank,
self.label,
self.level,
self.difficulty,
)
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 = ["label", "short_label", "is_default", "allowed_in_competition"]
label = models.CharField(max_length=30, verbose_name="Nom long")
short_label = models.CharField(max_length=15, verbose_name="Nom court")
allowed_in_competition = models.BooleanField(verbose_name="Compétition")
is_default = models.BooleanField(verbose_name="Défaut")
def __str__(self):
return "%s" % (self.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(default=True).id
except:
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.short_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"
active = models.BooleanField()
jumps = models.ManyToManyField(
Skill, through="RoutineSkill", verbose_name="routine"
) # ceci n'est pas un vrai champ
is_competitive = models.BooleanField(default=False)
def __str__(self):
return "%s (%s)" % (self.long_label, self.short_label)
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__longLabel__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=".4 1") | 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=".4 3") | 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,
)
class Chrono(models.Model):
"""
Classe représentant le temps réalisé par un élève pour faire une série.
.. todo:: lors de l'affichage, aller chercher les ToF contenu dans les points de compétition (avec traitement adéquat).
"""
ROUTINETYPE_CHOICE = (
(0, "10 |"),
(1, "L1"),
(2, "L2"),
(3, "L3"),
(4, "L4"),
(99, "Other"),
)
routine = models.ForeignKey(Routine, on_delete=models.CASCADE, default=None)
routine_type = models.PositiveSmallIntegerField(choices=ROUTINETYPE_CHOICE)
gymnast = models.ForeignKey(
"people.gymnast", on_delete=models.CASCADE, default=None
)
date = models.DateField(default=date.today, verbose_name="Date")
score = models.DecimalField(max_digits=5, decimal_places=2)
def __str__(self):
return "%s le %s : %s pour %s" % (
self.gymnast,
self.date,
self.routine,
self.score,
)
# class Chrono_Jump(models.Model):
# """
# Permet d'enregistrer un chrono pour un saut
# """
# chrono_routine = models.ForeignKey(Chrono, on_delete=models.CASCADE, default=None)
# rank = models.PositiveSmallIntegerField()
# score = models.DecimalField(max_digits=3, decimal_places=2)
class Evaluation(models.Model):
"""
Classe permettant l'évaluation des éducatifs.
"""
date = models.DateField(default=date.today, verbose_name="Date")
value = models.PositiveSmallIntegerField(default=0)
educative = models.ForeignKey(
Educative,
related_name="depart_of",
verbose_name="Take-off position",
on_delete=models.CASCADE,
)
type_of_evaluator = models.BooleanField(default=0)
evaluator = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)