Compare commits

...

5 Commits

Author SHA1 Message Date
Gregory Trullemans 198ceeafc0 Code optimisation 2024-03-22 15:03:44 +01:00
Gregory Trullemans 8e192e1c11 [WIP] Training & more 2024-03-22 09:00:59 +01:00
Gregory Trullemans 6a63776145 Update models 2024-03-22 09:00:59 +01:00
Gregory Trullemans e57e414f27 update dashboard 2024-03-21 17:47:01 +01:00
Gregory Trullemans f5c4ccc68e Update skill tabs 2024-03-21 17:43:43 +01:00
21 changed files with 759 additions and 641 deletions

View File

@ -27,7 +27,7 @@
<h4 class=""><i class="text-primary fal fa-laugh-wink"></i> Hi {{ user.first_name }} !</h4>
</div>
<div class="card-body text-justify pt-0">
<p>Welcome to Jarvi v0.96.1 <span class="text-muted">(last update : 13-2-2024)</span></p>
<p>Welcome to Jarvi v0.96.3 <span class="text-muted">(last update : 21-3-2024)</span></p>
<p>This application is here to help coaches to manage the gymnasts (evolution, evaluation, routines, scores, …). This tool is not perfect so feel free to make improvement proposals, bug reports, … by sending me an <a href="mailto:gregory@flyingacrobaticstrampoline.be">email</a>.</p>
<p>You can find the user manual <a href="{% static "files/Manuel_Utilisateur.pdf" %}" download>here (in french)</a>.</p>
</div>

View File

@ -406,14 +406,14 @@ def heightweight_create_or_update(request, heightweight_id=None, gymnast_id=None
<ul>
<li>Height: {heightweight.height} cm</li>
<li>Weight: {heightweight.weight} kg</li>
<li>BMI: {heightweight.bmi}</li>
<li>BMI: {heightweight.bmi:.2f}</li>
</ul>"""
Email.objects.create(receivers=receivers, title=title, body=body)
send_mail(
title,
f"Un nouveau poids/taille enregistré pour {heightweight.gymnast} ({date}) : {heightweight.height} cm / {heightweight.weight} kg (BMI: {heightweight.bmi}).", # pylint: disable=line-too-long
f"Un nouveau poids/taille enregistré pour {heightweight.gymnast} ({date}) : {heightweight.height} cm / {heightweight.weight} kg (BMI: {heightweight.bmi:.2f}).", # pylint: disable=line-too-long
settings.EMAIL_HOST_USER,
receivers,
fail_silently=False,

View File

@ -7,16 +7,24 @@ from django_admin_listfilter_dropdown.filters import (
)
from .models import (
TouchPosition,
Skill,
Passe,
Routine,
Educative,
RoutineSkill,
TrainingProgram,
TouchPosition,
TrainingRound,
GymnastTraining,
PrerequisiteClosure,
GymnastTrainingRound,
)
# @admin.register(Educative)
# class EducativeAdmin(admin.ModelAdmin):
# model = Educative
# list_display = ("long_label", "short_label", "difficulty", "level", "rank")
@admin.register(TouchPosition)
class TouchPositionAdmin(admin.ModelAdmin):
model = TouchPosition
@ -194,9 +202,9 @@ class PrerequisiteClosureAdmin(admin.ModelAdmin):
)
@admin.register(Passe)
class PasseAdmin(admin.ModelAdmin):
model = Passe
@admin.register(TrainingRound)
class TrainingRoundAdmin(admin.ModelAdmin):
model = TrainingRound
fields = (
"label",
@ -214,6 +222,14 @@ class PasseAdmin(admin.ModelAdmin):
)
filter_horizontal = ("educatives",)
# def save_model(self, request, obj, form, change):
# if obj.educatives:
# form.cleaned_data["educatives"] = obj.educatives.all().values_list(
# "id", flat=True
# )
# super(TrainingRoundAdmin, self).save_model(request, obj, form, change)
class Media:
js = (
"js/core/jquery-3.6.0.min.js",
@ -221,30 +237,59 @@ class PasseAdmin(admin.ModelAdmin):
)
@admin.register(TrainingProgram)
class TrainingProgramAdmin(admin.ModelAdmin):
model = TrainingProgram
@admin.register(GymnastTraining)
class GymnastTrainingAdmin(admin.ModelAdmin):
model = GymnastTraining
fields = (
"gymnast",
"label",
"date",
"rank",
"passe",
"repetition",
"number_of_skill",
"gymnast",
# "trainingrounds",
"difficulty",
"number_of_skill",
"score",
)
list_display = (
"label",
"date",
"gymnast",
# "trainingrounds",
"difficulty",
"number_of_skill",
"score",
)
list_filter = (
("gymnast", RelatedDropdownFilter),
("trainingrounds", RelatedDropdownFilter),
)
@admin.register(GymnastTrainingRound)
class GymnastTrainingRoundAdmin(admin.ModelAdmin):
model = TrainingRound
fields = (
"gymnast_training",
"training_round",
"repetition",
"rank",
"number_of_skill",
"score",
"difficulty",
# "score",
)
list_display = (
"date",
"gymnast",
"passe",
"rank",
"gymnast_training",
"training_round",
"repetition",
"rank",
"number_of_skill",
"score",
"difficulty",
)
list_filter = (
("gymnast", RelatedDropdownFilter),
("date", DropdownFilter),
("gymnast_training", RelatedDropdownFilter),
("training_round", RelatedDropdownFilter),
)

View File

@ -1,17 +1,18 @@
import re
from django import forms
from django.core.exceptions import ValidationError
from django.contrib.admin.widgets import FilteredSelectMultiple
import re
from .models import (
Educative,
Skill,
Routine,
RoutineSkill,
Passe,
TrainingRound,
)
from .validators import is_valid_routine_type, is_valid_subset, is_valid_regexp
class SkillForm(forms.ModelForm):
class Meta:
@ -74,7 +75,7 @@ class CombinationSkillForm(forms.ModelForm):
}
class PasseForm(forms.ModelForm):
class TrainingRoundForm(forms.ModelForm):
educatives = forms.ModelMultipleChoiceField(
required=False,
@ -91,7 +92,7 @@ class PasseForm(forms.ModelForm):
js = ["/admin/jsi18n/"]
class Meta:
model = Passe
model = TrainingRound
fields = ("label", "regexp", "educatives", "informations")
widgets = {
@ -102,9 +103,7 @@ class PasseForm(forms.ModelForm):
"maxlength": 30,
}
),
"regexp": forms.TextInput(
attrs={"class": "form-control", "placeholder": "[2-8]"}
),
"regexp": forms.TextInput(attrs={"class": "form-control"}),
"informations": forms.Textarea(
attrs={
"class": "form-control",
@ -115,8 +114,9 @@ class PasseForm(forms.ModelForm):
def clean_regexp(self):
"""Vérifie que la regexp entrée par l'utilisateur est valide."""
print(self.cleaned_data)
regexp = self.cleaned_data["regexp"]
if not Passe.is_valid_regexp(regexp):
if not is_valid_regexp(regexp):
raise ValidationError("Entered regexp not valid.")
return regexp
@ -131,18 +131,18 @@ class PasseForm(forms.ModelForm):
- x| " il y ait 1! Educatives
"""
cleaned_data = super().clean()
regexp = ["regexp"]
regexp = cleaned_data["regexp"]
if regexp is not None:
if not regexp:
arguments = regexp.split(" ")
educatives = cleaned_data["educatives"]
if Passe.is_valid_routine_type(arguments[0]) and educatives is not None:
if is_valid_routine_type(arguments[0]) and educatives:
raise ValidationError(
"Educatives must be empty with the entered Regexp."
)
if Passe.is_valid_subset(arguments[0]) and educatives is None:
if is_valid_subset(arguments[0]) and educatives is None:
raise ValidationError(
"Educatives can't be empty with the entered Regexp."
)
@ -158,3 +158,13 @@ class PasseForm(forms.ModelForm):
)
return cleaned_data
# def save_model(self, request, obj, form, change):
# obj.save()
# for educative in form.cleaned_data["educatives"]:
# print(educative)
# book = Book.objects.get(pk=bk.id)
# book.quantity -= 1
# if book.quantity == 0:
# book.sold = True;
# book.save()

View File

@ -0,0 +1,31 @@
# Generated by Django 4.2 on 2024-03-13 11:38
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("objective", "0021_alter_passe_educatives"),
]
operations = [
migrations.AlterUniqueTogether(
name="trainingprogram",
unique_together=None,
),
migrations.RemoveField(
model_name="trainingprogram",
name="gymnast",
),
migrations.RemoveField(
model_name="trainingprogram",
name="passe",
),
migrations.DeleteModel(
name="Passe",
),
migrations.DeleteModel(
name="TrainingProgram",
),
]

View File

@ -1,15 +1,18 @@
import re
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 .validators import (
number_of_skill_from_subset,
difficulty_from_regexp,
)
# from jarvis.followup.models import ROUTINE_TYPE_CHOICE
# from .signals import *
class Educative(Markdownizable):
@ -466,7 +469,7 @@ class RoutineSkill(models.Model):
return f"{self.rank} - {self.routine.short_label} : {self.skill.short_label}"
class Passe(Markdownizable):
class TrainingRound(Markdownizable):
"""Classe représentant les passages (à faire pendant un entraînement)."""
class Meta:
@ -476,289 +479,130 @@ class Passe(Markdownizable):
label = models.CharField(max_length=30)
educatives = models.ManyToManyField(
Educative, related_name="passes", blank=True, symmetrical=False
Educative, related_name="trainingrounds", blank=True, symmetrical=False
)
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.
def update_traininground(self):
"""Met à jours les valeurs `number_of_skill` et `difficulty`.
S'il n'y a pas d'educatives, la difficulté ne peut pas être calculée.
"""
self.difficulty = 0
self.number_of_skill = 0
super().save(*args, **kwargs)
# print("Dans le save")
difficulty = 0
number_of_skill = 0
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
if len(operation_list) == 2:
_, _, number_of_skill = number_of_skill_from_subset(operation_list[1])
else:
self.number_of_skill += 10
number_of_skill += 10
else:
for educative in self.educatives.all():
is_skill = False
try:
educative = Routine.objects.get(pk=educative)
educ = Routine.objects.get(pk=educative)
except Routine.DoesNotExist:
educative = Skill.objects.get(pk=educative)
educ = Skill.objects.get(pk=educative)
is_skill = True
if is_skill:
self.difficulty += educative.difficulty
self.number_of_skill += 1
difficulty += educ.difficulty
number_of_skill += 1
elif (
self.regexp is not None
and self.regexp != "WC"
and not re.match(r"[1-9]+\|", self.regexp)
):
(
start,
end,
number_of_skill,
) = number_of_skill_from_subset(self.regexp)
difficulty = difficulty_from_regexp(educ, start, end)
else:
if self.regexp is not None:
regexp = self.regexp.replace("[", "").replace("]", "")
position = regexp.find("-")
number_of_skill += educ.jumps.all().count()
difficulty += educ.difficulty
start = regexp[:position]
if start == "":
start = 0
else:
start = int(start)
self.difficulty = difficulty
self.number_of_skill = number_of_skill
self.save()
end = regexp[position + 1 :]
if end == "":
end = educative.jumps.all().count()
else:
end = int(end)
# 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.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)
# https://stackoverflow.com/questions/13751217/can-the-super-save-method-be-used-several-times-in-a-model
# """
# # print("Object:" + str(self))
# instance = super().save(*args, **kwargs)
# # print("Calling update_fileds on commit transaction.")
# transaction.on_commit(self.update_fields)
# # print("Fields updated.")
# return instance
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.
"""
if len(re.findall("\.", pattern)) > 1:
return False
if re.search("\.", pattern):
last_place = len(pattern) - 1
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 la liste
ROUTINE_TYPE_CHOICE (Educative vide !)
"""
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
- [4--8] False
- [-] False
- [1-] False
- [-1] False
- [4] False
- [6-6] False
"""
if re.match(r"^\[(([2-9]+\-{1})|([2-9]+\-{1}[2-9]+)|(\-{1}[2-9]+))\]$", subset):
value = subset.replace("[", "").replace("]", "")
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
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
def is_regexp_wc(self):
return self.regexp == "WC"
class TrainingProgram(Seasonisable, Markdownizable):
"""Classe représentant ?????
class GymnastTraining(Seasonisable, Markdownizable):
"""Classe représentant un entraînement."""
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)
"""
label = models.CharField()
gymnast = models.ForeignKey(
"people.Gymnast",
on_delete=models.CASCADE,
related_name="trainings",
)
trainingrounds = models.ManyToManyField(
TrainingRound, through="GymnastTrainingRound", verbose_name="trainings"
)
difficulty = models.DecimalField(max_digits=4, decimal_places=1, default=0.0)
number_of_skill = models.PositiveSmallIntegerField(default=0)
score = models.PositiveSmallIntegerField(default=1)
def __str__(self):
return f"Training on {self.date} for {self.gymnast}: {self.number_of_skill} | {self.difficulty}"
class GymnastTrainingRound(Markdownizable):
"""Classe représentant un passage."""
class Meta:
verbose_name = "Training Program"
verbose_name_plural = "Trainings Programs"
verbose_name = "Gymnast Training Round"
verbose_name_plural = "Gymnast Training Rounds"
ordering = [
"rank",
]
unique_together = ["date", "gymnast", "rank"]
unique_together = ["gymnast_training", "rank"]
gymnast = models.ForeignKey("people.Gymnast", on_delete=models.CASCADE) # TO DELETE
passe = models.ForeignKey(Passe, on_delete=models.CASCADE)
gymnast_training = models.ForeignKey(
GymnastTraining,
on_delete=models.CASCADE,
related_name="training_rounds",
)
training_round = models.ForeignKey(TrainingRound, 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
rank = models.PositiveSmallIntegerField(default=1)
updated_at = models.DateTimeField(auto_now=True)
number_of_skill = models.PositiveSmallIntegerField(default=0)
score = models.PositiveSmallIntegerField(blank=True, null=True)
difficulty = models.DecimalField(max_digits=4, decimal_places=1, default=0.0)
def __str__(self):
return (
f"{self.gymnast} {self.date} - {self.rank} : {self.passe} {self.repetition}"
)
return f"{self.gymnast_training} {self.gymnast_training.date} - {self.rank} : {self.training_round} {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
self.difficulty = self.training_round.difficulty * self.repetition
self.number_of_skill = self.training_round.number_of_skill * self.repetition
super().save(*args, **kwargs)
# class TrainingPasseLink(models.Model):
# """Modèle M2M entre Training et TrainingPasse."""
# training = models.ForeignKey(
# Training,
# on_delete=models.CASCADE,
# default=None,
# related_name="program_passe_links",
# )
# program_passe = models.ForeignKey(
# TrainingPasse,
# on_delete=models.CASCADE,
# default=None,
# related_name="training_links",
# )
# rank = models.PositiveSmallIntegerField(default=1)
# number_of_skill = models.PositiveSmallIntegerField(default=0)
# difficulty = models.DecimalField(max_digits=4, decimal_places=1, default=0.0)
# score = models.PositiveSmallIntegerField(blank=True, null=True)
# 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)

View File

@ -0,0 +1,11 @@
# from django.db.models.signals import post_save
# from django.dispatch import receiver
# from .models import TrainingRound
# @receiver(post_save, sender=TrainingRound)
# def compute_traininground_fields(**kwargs):
# print("In the post_save function.")
# # job = kwargs.get("instance")
# # logger.info("POST_SAVE : Job : %s" % job)
# # find people to email based on `job` instance

View File

@ -8,29 +8,29 @@
<div class="col-12 col-sm-12 col-md-8 col-lg-8 col-xl-6">
<div class="card">
<div class="card-body">
{% if trainingprogram_list %}
{% if gymnast_training %}
<table class="table table-striped tablesorter" id="trainingprogram_table">
<thead>
<tr>
<th colspan="5" class="text-center">{% if gymnast %}{{ gymnast }} - {% endif %}{{ date|date:"l j F Y" }}</th>
<th colspan="5" class="text-center">{{ gymnast_training.gymnast }} - {{ gymnast_training.date|date:"l j F Y" }}</th>
</tr>
</thead>
<tbody>
{% for trainingprogram in trainingprogram_list %}
{% for round in training_rounds %}
<tr role="row" class="{% cycle 'odd' 'even' %}">
<td><b>{{ trainingprogram.rank }}</b></td>
<td class="text-center"><a href="{% url 'passe_details' trainingprogram.passe.id gymnast.id date %}">{{ trainingprogram.passe.label }}</a>{% if trainingprogram.repetition != 1 %}&nbsp;&nbsp;&nbsp;{{ trainingprogram.repetition }}{% endif %}</td>
<td class="text-center">{{ trainingprogram.number_of_skill}}</td>
<td class="text-center">{{ trainingprogram.difficulty }}</td>
<td><b>{{ round.rank }}</b></td>
<td class="text-center"><a href="{% url 'traininground_details' round.id %}">{{ round.training_round.label }}</a>{% if round.repetition != 1 %}&nbsp;&nbsp;&nbsp;{{ round.repetition }}{% endif %}</td>
<td class="text-center">{{ round.number_of_skill}}</td>
<td class="text-center">{{ round.difficulty }}</td>
{% if request.user|has_group:"trainer" %}
<td class="text-center p-2">
<a href="#" class="up" data-tp_id="{{ trainingprogram.id }}" data-rank="{{ trainingprogram.rank }}">
<a href="#" class="up" data-tp_id="{{ round.id }}" data-rank="{{ round.rank }}">
<button type="submit" value="" class="btn btn-icon btn-warning mr-2">
<i class="fas fa-chevron-up"></i>
</button>
</a>
<a href="#" class="down" data-tp_id="{{ trainingprogram.id }}" data-rank="{{ trainingprogram.rank }}">
<a href="#" class="down" data-tp_id="{{ round.id }}" data-rank="{{ round.rank }}">
<button type="submit" value="add" class="btn btn-icon btn-warning mr-2">
<i class="fas fa-chevron-down"></i>
</button>
@ -42,8 +42,8 @@
</tbody>
<tr>
<td colspan="2" class="text-right"><b>TOTAL</b></td>
<td class="text-center"><b>{{ number_of_skill }}</b></td>
<td class="text-center"><b>{{ difficulty }}</b></td>
<td class="text-center"><b>{{ gymnast_training.number_of_skill }}</b></td>
<td class="text-center"><b>{{ gymnast_training.difficulty }}</b></td>
{% if request.user|has_group:"trainer" %}
<td></td>
{% endif %}
@ -67,7 +67,7 @@ $(document).ready(function(){
if ($(this).is(".up")) {
$.ajax({
url: "{% url 'switch_trainingprogram_line' %}",
url: "{% url 'switch_traininground' %}",
method: "POST",
data: {
tpid: $(this).data('tp_id'),
@ -85,7 +85,7 @@ $(document).ready(function(){
});
} else {
$.ajax({
url: "{% url 'switch_trainingprogram_line' %}",
url: "{% url 'switch_traininground' %}",
method: "POST",
data: {
tpid: $(this).data('tp_id'),

View File

@ -8,15 +8,15 @@
<h4 class="">{% if passe_id %}Edit{% else %}Add{% endif %} Passe</h4>
</div>
<div class="card-body">
<form action="{% if passe_id %}{% url 'passe_update' passe_id %}{% else %}{% url 'passe_create' %}{% endif %}" method="post" class="form-horizontal" id="formulaire" name="formulaire">
<form action="{% if passe_id %}{% url 'traininground_update' passe_id %}{% else %}{% url 'traininground_create' %}{% endif %}" method="post" class="form-horizontal" id="formulaire" name="formulaire">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
<div class="form-group row {% if form.long_label.errors %}has-error has-feedback{% endif %}">
<label for="id_label" class="col-4 col-sm-2 col-md-2 col-lg-2 col-xl-2 col-form-label">Label</label>
<div class="form-group row {% if form.label.errors %}has-error has-feedback{% endif %}">
<label for="id_label" class="col-4 col-sm-2 col-md-2 col-lg-2 col-xl-2 col-form-label">Label<span class="text-danger"><b>*</b></span></label>
<div class="col-8 col-sm-10 col-md-10 col-lg-10 col-xl-10">
{{ form.label }}
{% if form.label.errors %}&nbsp;<span class="btn btn-sm btn-danger-outline">{% for error in form.label.errors %}{{ error }}{% endfor %}</span>{% endif %}
@ -24,7 +24,7 @@
</div>
<div class="form-group row {% if form.regexp.errors %}has-error has-feedback{% endif %}">
<label for="id_regexp" class="col-4 col-sm-2 col-md-2 col-lg-2 col-xl-2 col-form-label">{{ form.regexp.label }}<span class="text-danger"><b>*</b></span></label>
<label for="id_regexp" class="col-4 col-sm-2 col-md-2 col-lg-2 col-xl-2 col-form-label">{{ form.regexp.label }}</label>
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-3">
{{ form.regexp }}
{% if form.regexp.errors %}&nbsp;<span class="btn btn-sm btn-danger-outline">{% for error in form.regexp.errors %}{{ error }}{% endfor %}</span>{% endif %}
@ -32,7 +32,7 @@
</div>
<div class="form-group row ">
<label for="id_educative" class="col-4 col-sm-2 col-md-2 col-lg-2 col-xl-2 col-form-label">{{ form.educatives.label }} <span class="text-danger"><b>*</b></span></label>
<label for="id_educative" class="col-4 col-sm-2 col-md-2 col-lg-2 col-xl-2 col-form-label">{{ form.educatives.label }}</label>
<div class="col-8 col-sm-8 col-md-6 col-lg-6 col-xl-6 {% if form.educatives.errors %}has-danger{% endif %}">
{{ form.media }}
{{ form.educatives }}

View File

@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load has_group %}
{% block page_title %}Passe details{% endblock %}
{% block page_title %}Training Round details{% endblock %}
{% block content %}
<div class="row justify-content-center">
@ -9,8 +9,8 @@
<div class="card">
<div class="card-header">
<h4 class="mb-0">Passe details</h3>
<h5 class="card-category mb-0">{{ passe.label }} {% if passe.regexp %}{{ passe.regexp }}{% endif %}</a>&nbsp;&nbsp;&nbsp;{{ passe.repetition }}</h4>
<h4 class="mb-0">Training round details</h3>
<h5 class="card-category mb-0">{{ traininground.label }} {% if traininground.regexp and traininground.label != traininground.label %}{{ traininground.regexp }}{% endif %}</a>&nbsp;&nbsp;&nbsp;{{ traininground.repetition }}</h4>
</div>
<div class="card-body">
@ -19,34 +19,48 @@
<div class="form-group row mb-0">
<label for="id_label" class="col-4 col-sm-6 col-md-5 col-lg-4 col-xl-3 col-form-label">Label</label>
<div class="col-8 col-sm-6 col-md-7 col-lg-8 col-xl-9 pt-2">
{{ passe.label }}
{{ traininground.label }}
</div>
</div>
{% if content %}
<div class="form-group row mb-0">
<label for="id_educative" class="col-4 col-sm-6 col-md-5 col-lg-4 col-xl-3 col-form-label">Content</label>
<div class="col-8 col-sm-8 col-md-7 col-lg-8 col-xl-9 pt-2">
<ul class="mb-0 ml-4 pl-0">
{% if routine and not skill_link_list %}
<ul class="mb-0 ml-4 pl-0">
<li><a href="{% url 'educative_details' routine.id %}">{{ routine }}</a></li>
</ul>
{% elif routine and skill_link_list %}
<ul class="mb-0 ml-4 pl-0">
{% for routine_skill in skill_link_list %}
<li><a href="{% url 'educative_details' routine_skill.skill.id %}">{{ routine_skill.rank }} - {{ routine_skill.skill.notation }}</a></li>
{% endfor %}
</ul>
{% else %}
{% for educative in passe.educatives.all %}
<li><a href="{% url 'educative_details' educative.id %}">{{ educative }}</a></li>
{% endfor %}
{% if is_wc %}
{% for educative in traininground.educatives.all %}
<a href="{% url 'educative_details' educative.id %}">{{ educative.notation }}</a> -
{% endfor %}
{% else %}
<ul class="mb-0 ml-4 pl-0">
{% for educative in traininground.educatives.all %}
<li><a href="{% url 'educative_details' educative.id %}">{{ educative }}</a></li>
{% endfor %}
</ul>
{% endif %}
{% endif %}
</ul>
</div>
</div>
{% endif %}
{% if passe.regexp and passe.regexp != passe.label %}
{% if traininground.regexp and traininground.regexp != traininground.label %}
<div class="form-group row mb-0">
<label for="id_regexp" class="col-4 col-sm-6 col-md-5 col-lg-4 col-xl-3 col-form-label">Regexp</label>
<div class="col-8 col-sm-8 col-md-7 col-lg-8 col-xl-9 pt-2">
{{ passe.regexp }}
{{ traininground.regexp }}
</div>
</div>
{% endif %}
@ -54,22 +68,22 @@
<div class="form-group row mb-0">
<label for="id_regexp" class="col-4 col-sm-6 col-md-5 col-lg-4 col-xl-3 col-form-label">Number of Skill</label>
<div class="col-8 col-sm-8 col-md-7 col-lg-8 col-xl-9 pt-2">
{{ number_of_educative }}
{{ traininground.number_of_skill }}
</div>
</div>
<div class="form-group row mb-0">
<label for="id_regexp" class="col-4 col-sm-6 col-md-5 col-lg-4 col-xl-3 col-form-label">Difficulty</label>
<div class="col-8 col-sm-8 col-md-7 col-lg-8 col-xl-9 pt-2">
{{ difficulty }}
{{ traininground.difficulty }}
</div>
</div>
{% if information %}
{% if traininground.information %}
<div class="form-group row mb-0">
<label for="id_informations" class="col-4 col-sm-6 col-md-5 col-lg-4 col-xl-3 col-form-label">Informations</label>
<div class="col-8 col-sm-9 col-md-9 col-lg-9 col-xl-9 {% if form.id_information.errors %}has-danger{% endif %}">
{{ informations }}
{{ traininground.informations }}
</div>
</div>
{% endif %}

View File

@ -5,9 +5,8 @@ from jarvis.objective.models import (
TouchPosition,
Routine,
RoutineSkill,
Passe,
TrainingRound,
)
from jarvis.core.global_vars import ROUTINE_TYPE_CHOICE
class ToolsModels(TestCase):
@ -106,7 +105,7 @@ class ToolsModels(TestCase):
)
class PasseTestCase(TestCase):
class TrainingRoundTestCase(TestCase):
def setUp(self):
"""
Structure finale :
@ -123,137 +122,3 @@ class PasseTestCase(TestCase):
educ_3 = Educative.objects.create(
long_label="4 pattes", difficulty=0.1, level=1, rank=1
)
def test_is_valid_dot(self):
arg = ""
self.assertEqual(Passe.is_valid_dot(arg), True)
arg = "Q1R1"
self.assertEqual(Passe.is_valid_dot(arg), True)
arg = ".Q1R1"
self.assertEqual(Passe.is_valid_dot(arg), True)
arg = "Q1R1."
self.assertEqual(Passe.is_valid_dot(arg), True)
arg = "Q1.R1"
self.assertEqual(Passe.is_valid_dot(arg), False)
arg = ".Q1.R1."
self.assertEqual(Passe.is_valid_dot(arg), False)
def test_is_valid_subset(self):
subset = ""
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "[2-8]"
self.assertEqual(Passe.is_valid_subset(subset), True)
subset = "[2--8]"
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "[-5]"
self.assertEqual(Passe.is_valid_subset(subset), True)
subset = "[3-]"
self.assertEqual(Passe.is_valid_subset(subset), True)
subset = "[8-2]"
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "[-]"
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "[1-]"
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "[-1]"
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "[4]"
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "[6-6]"
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "4"
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "[6-6"
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "[66]"
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "4-8"
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "6-]"
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "[4-"
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "[6"
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "Q1R1 [4-8]"
self.assertEqual(Passe.is_valid_subset(subset), False)
subset = "Q1R1 [-8]"
self.assertEqual(Passe.is_valid_subset(subset), False)
def test_is_valid_routine_type(self):
for item in ROUTINE_TYPE_CHOICE:
self.assertEqual(Passe.is_valid_routine_type(item[1]), True)
routine_type = ""
self.assertEqual(Passe.is_valid_routine_type(routine_type), False)
routine_type = ".Q1R1"
self.assertEqual(Passe.is_valid_routine_type(routine_type), False)
routine_type = "SFS."
self.assertEqual(Passe.is_valid_routine_type(routine_type), False)
routine_type = "Q1R1 [4-8]"
self.assertEqual(Passe.is_valid_routine_type(routine_type), False)
routine_type = "SFS [-8]"
self.assertEqual(Passe.is_valid_routine_type(routine_type), False)
def test_is_valid_regexp_one_arg(self):
arg = ""
self.assertEqual(Passe.is_valid_regexp_one_arg(arg), False)
arg = "WC"
self.assertEqual(Passe.is_valid_regexp_one_arg(arg), True)
arg = "1|"
self.assertEqual(Passe.is_valid_regexp_one_arg(arg), True)
arg = ".Q1R1"
self.assertEqual(Passe.is_valid_regexp_one_arg(arg), True)
arg = "Q1R2."
self.assertEqual(Passe.is_valid_regexp_one_arg(arg), True)
arg = ".Q1R1 [4-8"
self.assertEqual(Passe.is_valid_regexp_one_arg(arg), False)
arg = "Q1R2. [4-8"
self.assertEqual(Passe.is_valid_regexp_one_arg(arg), False)
arg = "Q1R1 [4-8]"
self.assertEqual(Passe.is_valid_regexp_one_arg(arg), False)
arg = "Q1R1 [8-4]"
self.assertEqual(Passe.is_valid_regexp_one_arg(arg), False)
def test_is_valid_regexp_two_args(self):
arg1 = ""
arg2 = ""
self.assertEqual(Passe.is_valid_regexp_two_args(arg1, arg2), False)
arg1 = "Q1R1"
self.assertEqual(Passe.is_valid_regexp_two_args(arg1, arg2), False)
arg2 = "[4-8]"
self.assertEqual(Passe.is_valid_regexp_two_args(arg1, arg2), True)
arg2 = "[8-8]"
self.assertEqual(Passe.is_valid_regexp_two_args(arg1, arg2), False)
arg2 = "[8-4]"
self.assertEqual(Passe.is_valid_regexp_two_args(arg1, arg2), False)
arg2 = "[-8]"
self.assertEqual(Passe.is_valid_regexp_two_args(arg1, arg2), True)
arg2 = "[4-]"
self.assertEqual(Passe.is_valid_regexp_two_args(arg1, arg2), True)
arg1 = "Q1R1."
self.assertEqual(Passe.is_valid_regexp_two_args(arg1, arg2), True)
def test_is_valid_regexp(self):
regexp = ""
self.assertEqual(Passe.is_valid_regexp(regexp), False)
regexp = "Q1R1 [4-8]"
self.assertEqual(Passe.is_valid_regexp(regexp), True)
regexp = "Q1R1 [8-8]"
self.assertEqual(Passe.is_valid_regexp(regexp), False)
regexp = "Q1R1 [8-4]"
self.assertEqual(Passe.is_valid_regexp(regexp), False)
regexp = "Q1R1 [-8]"
self.assertEqual(Passe.is_valid_regexp(regexp), True)
regexp = "Q1R1 [4-]"
self.assertEqual(Passe.is_valid_regexp(regexp), True)
regexp = "Q1R1. [4-8]"
self.assertEqual(Passe.is_valid_regexp(regexp), True)
regexp = "Q1R1. [4-]"
self.assertEqual(Passe.is_valid_regexp(regexp), True)
regexp = ".Q1R1 [-8]"
self.assertEqual(Passe.is_valid_regexp(regexp), True)
regexp = "Q1R1. [8-8]"
self.assertEqual(Passe.is_valid_regexp(regexp), False)
regexp = ".Q1R1 [8-4]"
self.assertEqual(Passe.is_valid_regexp(regexp), False)

View File

@ -0,0 +1,209 @@
from django.test import TestCase
from jarvis.core.global_vars import ROUTINE_TYPE_CHOICE
from .validators import (
is_valid_dot,
is_valid_subset,
is_valid_regexp,
is_valid_routine_type,
is_valid_regexp_one_arg,
is_valid_regexp_two_args,
number_of_skill_from_subset,
)
class TestValidatorsModels(TestCase):
def test_number_of_skill_from_subset(self):
subset = ""
educative = None
self.assertEqual(number_of_skill_from_subset(subset, educative), (0, 0, 0))
subset = "[8-4]"
self.assertEqual(number_of_skill_from_subset(subset, educative), (0, 0, 0))
subset = "[4-8]"
self.assertEqual(number_of_skill_from_subset(subset, educative), (4, 8, 5))
subset = "[4-]"
self.assertEqual(number_of_skill_from_subset(subset, educative), (4, 10, 7))
subset = "[-8]"
self.assertEqual(number_of_skill_from_subset(subset, educative), (1, 8, 8))
def test_is_valid_regexp_one_arg(self):
arg = ""
self.assertEqual(is_valid_regexp_one_arg(arg), False)
arg = "WC"
self.assertEqual(is_valid_regexp_one_arg(arg), True)
arg = "1|"
self.assertEqual(is_valid_regexp_one_arg(arg), True)
arg = ".Q1R1"
self.assertEqual(is_valid_regexp_one_arg(arg), True)
arg = "Q1R2."
self.assertEqual(is_valid_regexp_one_arg(arg), True)
arg = ".Q1R1 [4-8"
self.assertEqual(is_valid_regexp_one_arg(arg), False)
arg = "Q1R2. [4-8"
self.assertEqual(is_valid_regexp_one_arg(arg), False)
arg = ".Q1R2. [4-8"
self.assertEqual(is_valid_regexp_one_arg(arg), False)
arg = "Q1R1 [4-8]"
self.assertEqual(is_valid_regexp_one_arg(arg), False)
arg = "Q1R1 [8-4]"
self.assertEqual(is_valid_regexp_one_arg(arg), False)
def test_is_valid_regexp_two_args(self):
routine = ""
subset = ""
self.assertEqual(is_valid_regexp_two_args(routine, subset), False)
routine = "Q1R1"
self.assertEqual(is_valid_regexp_two_args(routine, subset), False)
subset = "[4-8]"
self.assertEqual(is_valid_regexp_two_args(routine, subset), True)
subset = "[8-8]"
self.assertEqual(is_valid_regexp_two_args(routine, subset), False)
subset = "[8-4]"
self.assertEqual(is_valid_regexp_two_args(routine, subset), False)
subset = "[-8]"
self.assertEqual(is_valid_regexp_two_args(routine, subset), True)
subset = "[4-]"
self.assertEqual(is_valid_regexp_two_args(routine, subset), True)
routine = "Q1R1."
self.assertEqual(is_valid_regexp_two_args(routine, subset), True)
routine = ".Q1R1"
self.assertEqual(is_valid_regexp_two_args(routine, subset), True)
routine = ".Q1R1."
self.assertEqual(is_valid_regexp_two_args(routine, subset), False)
def test_is_valid_dot(self):
arg = ""
self.assertEqual(is_valid_dot(arg), True)
arg = "Q1R1"
self.assertEqual(is_valid_dot(arg), True)
arg = ".Q1R1"
self.assertEqual(is_valid_dot(arg), True)
arg = "Q1R1."
self.assertEqual(is_valid_dot(arg), True)
arg = "Q1.R1"
self.assertEqual(is_valid_dot(arg), False)
arg = ".Q1.R1."
self.assertEqual(is_valid_dot(arg), False)
def test_is_valid_routine_type(self):
for item in ROUTINE_TYPE_CHOICE:
self.assertEqual(is_valid_routine_type(item[1]), True)
routine_type = ""
self.assertEqual(is_valid_routine_type(routine_type), False)
routine_type = ".Q1R1"
self.assertEqual(is_valid_routine_type(routine_type), False)
routine_type = "SFS."
self.assertEqual(is_valid_routine_type(routine_type), False)
routine_type = "Q1R1 [4-8]"
self.assertEqual(is_valid_routine_type(routine_type), False)
routine_type = "SFS [-8]"
self.assertEqual(is_valid_routine_type(routine_type), False)
def test_is_valid_subset(self):
"""Exemples :
- [2-8] True
- [-5] True
- [3-] True
- [8-2] False
- [4--8] False
- [-] False
- [1-] False
- [-1] False
- [4] False
- [6-6] False
"""
subset = ""
self.assertEqual(is_valid_subset(subset), False)
subset = "[2-8]"
self.assertEqual(is_valid_subset(subset), True)
subset = "[2--8]"
self.assertEqual(is_valid_subset(subset), False)
subset = "[-5]"
self.assertEqual(is_valid_subset(subset), True)
subset = "[3-]"
self.assertEqual(is_valid_subset(subset), True)
subset = "[8-2]"
self.assertEqual(is_valid_subset(subset), False)
subset = "[-]"
self.assertEqual(is_valid_subset(subset), False)
subset = "[1-]"
self.assertEqual(is_valid_subset(subset), False)
subset = "[-1]"
self.assertEqual(is_valid_subset(subset), False)
subset = "[4]"
self.assertEqual(is_valid_subset(subset), False)
subset = "[6-6]"
self.assertEqual(is_valid_subset(subset), False)
subset = "4"
self.assertEqual(is_valid_subset(subset), False)
subset = "[6-6"
self.assertEqual(is_valid_subset(subset), False)
subset = "[66]"
self.assertEqual(is_valid_subset(subset), False)
subset = "4-8"
self.assertEqual(is_valid_subset(subset), False)
subset = "6-]"
self.assertEqual(is_valid_subset(subset), False)
subset = "[4-"
self.assertEqual(is_valid_subset(subset), False)
subset = "[6"
self.assertEqual(is_valid_subset(subset), False)
subset = "Q1R1 [4-8]"
self.assertEqual(is_valid_subset(subset), False)
subset = "Q1R1 [-8]"
self.assertEqual(is_valid_subset(subset), False)
def test_is_valid_regexp(self):
"""Exemples :
- "" True
- 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
"""
regexp = ""
self.assertEqual(is_valid_regexp(regexp), True)
regexp = "Q1R1"
self.assertEqual(is_valid_regexp(regexp), True)
regexp = "Q1R1 [4-8]"
self.assertEqual(is_valid_regexp(regexp), True)
regexp = "Q1R1 [8-8]"
self.assertEqual(is_valid_regexp(regexp), False)
regexp = "Q1R1 [8-4]"
self.assertEqual(is_valid_regexp(regexp), False)
regexp = "Q1R1 [-8]"
self.assertEqual(is_valid_regexp(regexp), True)
regexp = "Q1R1 [4-]"
self.assertEqual(is_valid_regexp(regexp), True)
regexp = "Q1R1. [4-8]"
self.assertEqual(is_valid_regexp(regexp), True)
regexp = "Q1R1. [4-]"
self.assertEqual(is_valid_regexp(regexp), True)
regexp = ".Q1R1 [-8]"
self.assertEqual(is_valid_regexp(regexp), True)
regexp = ".Q1R1. [-8]"
self.assertEqual(is_valid_regexp(regexp), False)
regexp = "Q1R1. [8-8]"
self.assertEqual(is_valid_regexp(regexp), False)
regexp = ".Q1R1 [8-4]"
self.assertEqual(is_valid_regexp(regexp), False)
regexp = "[2-8]"
self.assertEqual(is_valid_regexp(regexp), True)
regexp = "[-5]"
self.assertEqual(is_valid_regexp(regexp), True)
regexp = "WC"
self.assertEqual(is_valid_regexp(regexp), True)
regexp = "1|"
self.assertEqual(is_valid_regexp(regexp), True)

View File

@ -90,35 +90,35 @@ urlpatterns = [
),
path(r"combination/", views.combination_listing, name="combination_listing"),
#
# PASSES
# TRAININGROUND
#
path(
r"passe/<int:passe_id>/gymnast/<int:gymnast_id>/date/<str:date>/",
views.passe_details,
name="passe_details",
r"traininground/<int:traininground_id>/",
views.traininground_details,
name="traininground_details",
),
path(r"passe/", views.passe_listing, name="passe_listing"),
path(r"traininground/", views.traininground_listing, name="traininground_listing"),
path(
r"passe/add/",
views.passe_create_or_update,
name="passe_create",
r"traininground/add/",
views.traininground_create_or_update,
name="traininground_create",
),
path(
r"passe/edit/<int:passe_id>/",
views.passe_create_or_update,
name="passe_update",
r"traininground/edit/<int:passe_id>/",
views.traininground_create_or_update,
name="traininground_update",
),
#
# TRAININGPROGRAM
# GYMNASTTRAINING
#
path(
r"trainingprogram/detail/date/<str:date>/gymnast/<int:gymnast_id>/",
views.trainingprogram_details,
name="trainingprogram_details",
r"gymnasttraining/detail/<int:gymnast_training_id>/",
views.gymnast_training_details,
name="gymnast_training_details",
),
path(
r"trainingprogram/switch_trainingprogram_line/",
views.switch_trainingprogram_line,
name="switch_trainingprogram_line",
r"gymnasttraining/switch_traininground/",
views.switch_traininground,
name="switch_traininground",
),
]

View File

@ -0,0 +1,144 @@
import re
from jarvis.core.global_vars import ROUTINE_TYPE_CHOICE
# Subset function
def number_of_skill_from_subset(subset, educative=None):
"""Calcule le nombre de sauts par rapport à un subset.
Commence par vérifier que le subset est valide (car on ne sait jamais).
"""
if not is_valid_subset(subset):
return 0, 0, 0
subset = subset.replace("[", "").replace("]", "")
ranks = subset.split("-")
if ranks[0] == "":
start = 1
else:
start = int(ranks[0])
if ranks[1] == "":
if educative is not None:
end = educative.jumps.all().count()
else:
end = 10
else:
end = int(ranks[1])
number_of_skill = (end - start) + 1
return start, end, number_of_skill
# Difficulty function
def difficulty_from_regexp(educative, start, end):
"""Calcule la difficulté sur base du champs regexp d'un object."""
difficulty = 0
list_of_skill = educative.skill_links.filter(rank__gte=start, rank__lte=end)
# .aggregate(total=Sum("value"))
for routine_skill in list_of_skill:
difficulty += routine_skill.skill.difficulty
return difficulty
# Validation functions
def is_valid_regexp_one_arg(argument):
"""Vérifie une regexp avec un paramètre."""
if argument == "WC":
return True
if re.match(r"[1-9]+\|", argument):
return True
if not is_valid_dot(argument):
return False
value = argument.replace(".", "")
is_valid_routine = is_valid_routine_type(value)
if is_valid_routine:
return True
return is_valid_subset(argument)
def is_valid_regexp_two_args(routine, subset):
"""Vérifie une regexp avec deux paramètres."""
if not is_valid_dot(routine):
return False
routine_type = routine.replace(".", "")
is_valid_routine = is_valid_routine_type(routine_type)
if is_valid_routine:
return is_valid_subset(subset)
return False
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.
"""
if len(re.findall("\.", pattern)) > 1:
return False
if re.search("\.", pattern):
last_place = len(pattern) - 1
if pattern[0] != "." and pattern[last_place] != ".":
return False
return True
def is_valid_routine_type(routine_type):
"""Recoit une chaine de caractère et vérifie si elle est présente dans la liste
ROUTINE_TYPE_CHOICE (Educative vide !)
"""
is_valid_routine = False
for item in ROUTINE_TYPE_CHOICE:
if item[1] == routine_type:
is_valid_routine = True
break
return is_valid_routine
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
"""
if re.match(r"^\[(([2-9]+\-{1})|([2-9]+\-{1}[2-9]+)|(\-{1}[2-9]+))\]$", subset):
value = subset.replace("[", "").replace("]", "")
ranks = value.split("-")
if ranks[0] == "" or ranks[1] == "":
return True
if int(ranks[0]) < int(ranks[1]):
return True
return False
def is_valid_regexp(regexp):
"""Vérifie une regexp pour la classe TrainingRound."""
if not regexp:
return True
arguments = regexp.split(" ")
if len(arguments) >= 3:
return False
if len(arguments) == 2:
return is_valid_regexp_two_args(arguments[0], arguments[1])
return is_valid_regexp_one_arg(arguments[0])

View File

@ -5,25 +5,24 @@ from django.shortcuts import render, get_object_or_404
from django.views.decorators.http import require_http_methods
from django.urls import reverse
import pendulum
from jarvis.core.global_vars import ROUTINE_TYPE_CHOICE
from jarvis.people.models import Gymnast
from jarvis.followup.models import GymnastHasRoutine
from .validators import is_valid_routine_type
from .forms import (
SkillForm,
PasseForm,
TrainingRoundForm,
CombinationForm,
CombinationSkillForm,
)
from .models import (
Skill,
Passe,
TrainingRound,
Routine,
Educative,
RoutineSkill,
TrainingProgram,
GymnastTraining,
GymnastTrainingRound,
PrerequisiteClosure,
)
@ -514,55 +513,43 @@ def unlink_skill_from_combination(request):
@login_required
@require_http_methods(["GET"])
def passe_listing(request):
def traininground_listing(request):
"""Liste des passages."""
passe_listing = Passe.objects.all()
passe_listing = TrainingRound.objects.all()
context = {"passe_listing": passe_listing}
return render(request, "passes/list.html", context)
return render(request, "trainingrounds/list.html", context)
@login_required
@require_http_methods(["GET"])
def passe_details(request, passe_id, gymnast_id, date):
def traininground_details(request, traininground_id, date=None):
"""Détails d'un passage."""
is_skill = False
passe = get_object_or_404(Passe, pk=passe_id)
educative_list = passe.educatives.all()
# TODO: décryptage de la regexp
regexp = passe.regexp
traininground = get_object_or_404(TrainingRound, pk=traininground_id)
educative_list = traininground.educatives.all()
routine = None
skill_link_list = None
if regexp is not None:
operation_list = regexp.split(" ")
routine_type = None
traininground.update_traininground()
for item in ROUTINE_TYPE_CHOICE:
if item[1] == operation_list[0]:
routine_type = item[0]
break
if routine_type is not None:
# Récupération de la série
# Decryptage de la regexp
if traininground.regexp is not None and traininground.regexp != "":
arguments = traininground.regexp.split(" ")
if is_valid_routine_type(arguments[0]) and date is not None:
# get routine_type
routine_type = 0
ghr = GymnastHasRoutine.objects.filter(
gymnast=gymnast_id,
gymnast=traininground.gymnast,
date_begin__lte=date,
# date_end__gte=date,
routine_type=routine_type,
)
).first()
print(ghr.query)
if ghr.count() > 1:
print("Plus d'une série trouvée...")
print(ghr)
routine = ghr.first().routine
routine = ghr.routine
skill_link_list = routine.skill_links.all()
if len(operation_list) == 2:
content = operation_list[1].replace("[", "").replace("]", "")
if len(arguments) == 2:
content = arguments[1].replace("[", "").replace("]", "")
ranks = content.split("-")
if ranks[0] != "":
@ -571,94 +558,72 @@ def passe_details(request, passe_id, gymnast_id, date):
if ranks[1] != "":
skill_link_list = skill_link_list.filter(rank__lte=ranks[1])
number_of_educative = skill_link_list.count()
print(number_of_educative)
else:
number_of_educative = educative_list.count()
content = False
if skill_link_list or routine or educative_list:
content = True
context = {
"passe": passe,
"is_skill": is_skill,
"traininground": traininground,
"educative_list": educative_list,
"routine": routine,
"skill_link_list": skill_link_list,
"difficulty": passe.difficulty,
"number_of_skill": passe.number_of_skill,
"number_of_educative": number_of_educative,
"content": content,
"is_wc": traininground.is_regexp_wc
# "number_of_educative": number_of_educative,
}
return render(request, "passes/details.html", context)
return render(request, "trainingrounds/details.html", context)
@login_required
@require_http_methods(["GET", "POST"])
def passe_create_or_update(request, passe_id=None):
def traininground_create_or_update(request, traininground_id=None):
"""Création d'un passage.
Args:
passe_id (int) identifiant d'un object de classe <Passe>.
traininground_id (int) identifiant d'un object de classe <TrainingRound>.
"""
if passe_id:
passe = get_object_or_404(Passe, pk=passe_id)
if traininground_id:
traininground = get_object_or_404(TrainingRound, pk=traininground_id)
else:
passe = None
traininground = None
if request.method == "POST":
form = PasseForm(request.POST, instance=passe)
form = TrainingRoundForm(request.POST, instance=traininground)
if form.is_valid():
passe = form.save()
return HttpResponseRedirect(reverse("passe_details", args=(passe.pk,)))
traininground = form.save()
return HttpResponseRedirect(
reverse("traininground_details", args=(traininground.pk,))
)
# print(form.errors)
return render(request, "trainingrounds/create.html", {"form": form})
return render(request, "passes/create.html", {"form": form})
form = PasseForm(instance=passe)
context = {"form": form, "passe_id": passe_id}
return render(request, "passes/create.html", context)
form = TrainingRoundForm(instance=traininground)
context = {"form": form, "traininground_id": traininground_id}
return render(request, "trainingrounds/create.html", context)
@login_required
@require_http_methods(["GET"])
def trainingprogram_details(request, date=None, gymnast_id=None):
def gymnast_training_details(request, gymnast_training_id):
"""Détails d'un entraînement."""
gymnast = None
trainingprogram_id = None
if trainingprogram_id is not None:
trainingprogram = get_object_or_404(TrainingProgram, pk=trainingprogram_id)
trainingprogram_list = None
else:
trainingprogram = None
trainingprogram_list = TrainingProgram.objects.all()
parsed_date = pendulum.parse(date).date()
if date is not None:
trainingprogram_list = trainingprogram_list.filter(date=parsed_date)
if gymnast_id is not None:
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
trainingprogram_list = trainingprogram_list.filter(gymnast=gymnast_id)
difficulty = 0
number_of_skill = 0
for trainingprogram in trainingprogram_list:
difficulty += trainingprogram.difficulty
number_of_skill += trainingprogram.number_of_skill
gymnast_training = get_object_or_404(GymnastTraining, pk=gymnast_training_id)
training_rounds = gymnast_training.training_rounds.all()
context = {
"gymnast": gymnast,
"date": parsed_date,
"difficulty": difficulty,
"number_of_skill": number_of_skill,
"trainingprogram": trainingprogram,
"trainingprogram_list": trainingprogram_list,
"gymnast": gymnast_training.gymnast,
"gymnast_training": gymnast_training,
"training_rounds": training_rounds,
}
return render(request, "trainingprograms/details.html", context)
return render(request, "gymnasttrainings/details.html", context)
@require_http_methods(["POST"])
def switch_trainingprogram_line(request):
def switch_traininground(request):
"""
Recoit dans request deux identifiants de trainingprogram qu'il faut échanger () :
Recoit dans request deux identifiants de trainingprogram qu'il faut échanger :
- tp_id (int) identifiant d'une instance de TraiingProgram
- direction (str) la direction du changement (0: haut, 1: bas)
@ -669,31 +634,31 @@ def switch_trainingprogram_line(request):
target_trainingprogram_id = request.POST.get("tpid", None)
direction = int(request.POST.get("direction", 0))
target_trainingprogram = get_object_or_404(
TrainingProgram, pk=target_trainingprogram_id
target_trainingpround = get_object_or_404(
GymnastTrainingRound, pk=target_trainingprogram_id
)
if direction == 0:
source_trainingprogram = (
TrainingProgram.objects.filter(rank__lt=target_trainingprogram.rank)
source_traininground = (
GymnastTrainingRound.objects.filter(rank__lt=target_trainingpround.rank)
.order_by("-id")
.first()
)
else:
source_trainingprogram = (
TrainingProgram.objects.filter(rank__gt=target_trainingprogram.rank)
source_traininground = (
GymnastTrainingRound.objects.filter(rank__gt=target_trainingpround.rank)
.order_by("id")
.first()
)
saved_source_rank = source_trainingprogram.rank
saved_target_rank = target_trainingprogram.rank
source_trainingprogram.rank = 32767
source_trainingprogram.save()
target_trainingprogram.rank = saved_source_rank
target_trainingprogram.save()
source_trainingprogram.rank = saved_target_rank
source_trainingprogram.save()
saved_source_rank = source_traininground.rank
saved_target_rank = target_trainingpround.rank
source_traininground.rank = 32767
source_traininground.save()
target_trainingpround.rank = saved_source_rank
target_trainingpround.save()
source_traininground.rank = saved_target_rank
source_traininground.save()
return HttpResponse(200)
except Exception:

View File

@ -0,0 +1,21 @@
# Generated by Django 4.2 on 2024-03-13 11:38
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("people", "0011_gymnast_trainers"),
]
operations = [
migrations.AlterModelOptions(
name="gymnast",
options={
"ordering": ["first_name", "last_name"],
"verbose_name": "Gymnast",
"verbose_name_plural": "Gymnasts",
},
),
]

View File

@ -82,9 +82,9 @@
<h4>Training Program</h4>
</div>
<div class="card-body">
{% if date_list %}
{% for date in date_list %}
<a href="{% url 'trainingprogram_details' date gymnast_id %}">{{ date|date:"l j F Y" }}</a>
{% if training_list %}
{% for training in training_list %}
<a href="{% url 'gymnast_training_details' training.id %}">{{ training.date|date:"l j F Y" }}</a><br />
{% endfor %}
{% else %}
<p>Pas de training planifié.</p>

View File

@ -93,9 +93,7 @@
<table class="table table-condensed table-striped tablesorter" id="table_planned_skill">
<thead>
<tr>
{% if user_is_trainer %}
<th style="width: 15%"></th>
{% endif %}
<th style="width: 15%"></th>
<th style="width: 10%">Notation</th>
<th class="header text-left" style="width: 35%">Label</th>
<th class="header text-left" style="width: 10%">Phase</th>
@ -108,7 +106,6 @@
<tbody>
{% for plan in planned_skill %}
<tr>
{% if user_is_trainer %}
<td>
<a href="{% url 'plan_update' plan.id %}">
<span class="tim-icons icon-pencil text-warning"></span>
@ -126,7 +123,7 @@
<i class="far fa-thumbs-up"></i>
</button>
</td>
{% endif %}
<td>{{ plan.educative.skill.notation }}</td>
<td class="text-left">
<a href="{% url 'skill_details' plan.educative.id %}">
@ -156,9 +153,7 @@
<table class="table table-striped table-condensed tablesorter" id="table_confused_skill">
<thead>
<tr>
{% if user_is_trainer %}
<th style="width: 10%"></th>
{% endif %}
<th style="width: 10%"></th>
<th style="width: 10%">Notation</th>
<th class="header text-left" style="width: 35%">Label</th>
<th class="header" style="width: 7%">Diff.</th>
@ -169,7 +164,6 @@
<tbody>
{% for skill in confused_skill %}
<tr>
{% if user_is_trainer %}
<td>
<!-- WITH HELP -->
<button type="button" rel="tooltip" class="btn btn-success btn-link btn-sm btn-icon checkUnknownSkill text-without-help" data-gymnast_id="{{ gymnast.id }}" data-skill_id="{{ skill.id }}" data-learning_step="2">
@ -185,7 +179,7 @@
<i class="far fa-thumbs-down"></i>
</button>
</td>
{% endif %}
<td>{{ skill.notation }}</td>
<td class="text-left">
<a href="{% url 'skill_details' skill.id %}">{{ skill.long_label }}</a>
@ -205,9 +199,7 @@
<table class="table table-striped table-condensed tablesorter" id="table_skill_with_help">
<thead>
<tr>
{% if user_is_trainer %}
<th style="width: 10%"></th>
{% endif %}
<th style="width: 10%"></th>
<th style="width: 10%">Notation</th>
<th class="header text-left" style="width: 35%">Label</th>
<th class="header" style="width: 7%">Diff.</th>
@ -218,7 +210,6 @@
<tbody>
{% for skill in skill_whith_help %}
<tr>
{% if user_is_trainer %}
<td>
<!-- WITH HELP -->
<button type="button" rel="tooltip" class="btn btn-success btn-link btn-sm btn-icon checkUnknownSkill text-without-help" data-gymnast_id="{{ gymnast.id }}" data-skill_id="{{ skill.id }}" data-learning_step="2">
@ -234,7 +225,7 @@
<i class="far fa-thumbs-down"></i>
</button>
</td>
{% endif %}
<td>{{ skill.notation }}</td>
<td class="text-left"><a href="{% url 'skill_details' skill.id %}">{{ skill.long_label }}</a></td>
<td>{{ skill.difficulty }}</td>
@ -252,9 +243,7 @@
<table class="table table-striped table-condensed tablesorter" id="table_skill_without_help">
<thead>
<tr>
{% if user_is_trainer %}
<th style="width: 12%"></th>
{% endif %}
<th style="width: 12%"></th>
<th style="width: 10%">Notation</th>
<th class="header text-left" style="width: 55%">Label</th>
<th class="header" style="width: 7%">Diff.</th>
@ -265,7 +254,6 @@
<tbody>
{% for skill in skill_without_help %}
<tr>
{% if user_is_trainer %}
<td>
<button type="button" rel="tooltip" class="btn btn-success btn-link btn-sm btn-icon checkUnknownSkill text-with-help" data-gymnast_id="{{ gymnast.id }}" data-skill_id="{{ plan.educative.id }}" data-learning_step="1">
<i class="far fa-check"></i>
@ -281,7 +269,7 @@
<i class="far fa-thumbs-down"></i>
</button>
</td>
{% endif %}
<td>{{ skill.notation }}</td>
<td class="text-left"><a href="{% url 'skill_details' skill.id %}">{{ skill.long_label }}</a></td>
<td>{{ skill.difficulty }}</td>
@ -299,9 +287,7 @@
<table class="table table-striped table-condensed tablesorter" id="table_skill_chained">
<thead>
<tr>
{% if user_is_trainer %}
<th style="width: 12%"></th>
{% endif %}
<th style="width: 12%"></th>
<th style="width: 10%">Notation</th>
<th class="header text-left" style="width: 35%">Label</th>
<th class="header" style="width: 7%">Diff.</th>
@ -312,7 +298,6 @@
<tbody>
{% for skill in skill_chained %}
<tr>
{% if user_is_trainer %}
<td>
<button type="button" rel="tooltip" class="btn btn-success btn-link btn-sm btn-icon checkUnknownSkill text-with-help" data-gymnast_id="{{ gymnast.id }}" data-skill_id="{{ plan.educative.id }}" data-learning_step="1">
<i class="far fa-check"></i>
@ -328,7 +313,7 @@
<i class="far fa-thumbs-down"></i>
</button>
</td>
{% endif %}
<td>{{ skill.notation }}</td>
<td class="text-left"><a href="{% url 'skill_details' skill.id %}">{{ skill.long_label }}</a></td>
<td>{{ skill.difficulty }}</td>
@ -346,9 +331,7 @@
<table class="table table-striped table-condensed tablesorter" id="table_skill_masterised">
<thead>
<tr>
{% if user_is_trainer %}
<th style="width: 12%"></th>
{% endif %}
<th style="width: 12%"></th>
<th style="width: 10%">Notation</th>
<th class="header text-left" style="width: 35%">Label</th>
<th class="header" style="width: 7%">Diff.</th>
@ -359,7 +342,6 @@
<tbody>
{% for skill in skill_masterised %}
<tr>
{% if user_is_trainer %}
<td>
<button type="button" rel="tooltip" class="btn btn-success btn-link btn-sm btn-icon checkUnknownSkill text-with-help" data-gymnast_id="{{ gymnast.id }}" data-skill_id="{{ plan.educative.id }}" data-learning_step="1">
<i class="far fa-check"></i>
@ -375,7 +357,7 @@
<i class="far fa-thumbs-down"></i>
</button>
</td>
{% endif %}
<td>{{ skill.notation }}</td>
<td class="text-left"><a href="{% url 'skill_details' skill.id %}">{{ skill.long_label }}</a></td>
<td>{{ skill.difficulty }}</td>
@ -393,9 +375,7 @@
<table class="table table-striped table-condensed tablesorter" id="table_by_rank">
<thead>
<tr>
{% if user_is_trainer %}
<th style="width: 13%">Actions</th>
{% endif %}
<th style="width: 13%">Actions</th>
<th style="width: 10%">Notation</th>
<th class="header text-left" style="width: 50%">Label</th>
<th class="header" style="width: 7%">Diff.</th>
@ -406,7 +386,6 @@
<tbody>
{% for skill in skill_by_rank %}
<tr>
{% if user_is_trainer %}
<td>
<a href="{% url 'add_skill_for_gymnast' gymnast.id skill.id %}">
<button type="button" rel="tooltip" class="btn btn-success btn-link btn-sm btn-icon planify_skill">
@ -426,7 +405,7 @@
<i class="far fa-thumbs-up"></i>
</button>
</td>
{% endif %}
<td>{{ skill.notation }}</td>
<td class="text-left"><a href="{% url 'skill_details' skill.id %}">{{ skill.long_label }}</a></td>
<td>{{ skill.difficulty }}</td>
@ -444,9 +423,7 @@
<table class="table table-striped table-condensed tablesorter" id="table_by_level">
<thead>
<tr>
{% if user_is_trainer %}
<th style="width: 15%">Actions</th>
{% endif %}
<th style="width: 15%">Actions</th>
<th style="width: 10%">Notation</th>
<th class="header text-left" style="width: 50%">Label</th>
<th class="header" style="width: 7%">Diff.</th>
@ -457,7 +434,6 @@
<tbody>
{% for skill in skill_by_level %}
<tr>
{% if user_is_trainer %}
<td>
<a href="{% url 'add_skill_for_gymnast' gymnast.id skill.id %}">
<button type="button" rel="tooltip" class="btn btn-success btn-link btn-sm btn-icon planify_skill">
@ -477,7 +453,7 @@
<i class="far fa-thumbs-up"></i>
</button>
</td>
{% endif %}
<td>{{ skill.notation }}</td>
<td class="text-left"><a href="{% url 'skill_details' skill.id %}">{{ skill.long_label }}</a></td>
<td>{{ skill.difficulty }}</td>
@ -495,9 +471,7 @@
<table class="table table-striped table-condensed tablesorter" id="table_by_age">
<thead>
<tr>
{% if user_is_trainer %}
<th style="width: 15%">Actions</th>
{% endif %}
<th style="width: 15%">Actions</th>
<th style="width: 10%">Notation</th>
<th class="header text-left" style="width: 50%">Label</th>
<th class="header" style="width: 7%">Diff.</th>
@ -509,7 +483,6 @@
<tbody>
{% for skill in skill_by_age %}
<tr>
{% if user_is_trainer %}
<td>
<a href="{% url 'add_skill_for_gymnast' gymnast.id skill.id %}">
<button type="button" rel="tooltip" class="btn btn-success btn-link btn-sm btn-icon planify_skill">
@ -529,7 +502,7 @@
<i class="far fa-thumbs-up"></i>
</button>
</td>
{% endif %}
<td>{{ skill.notation }}</td>
<td class="text-left"><a href="{% url 'skill_details' skill.id %}">{{ skill.long_label }}</a></td>
<td>{{ skill.difficulty }}</td>
@ -547,9 +520,7 @@
<table class="table table-striped table-condensed tablesorter" id="table_unknown_skill">
<thead>
<tr>
{% if user_is_trainer %}
<th style="width: 15%">Actions</th>
{% endif %}
<th style="width: 15%">Actions</th>
<th style="width: 10%">Notation</th>
<th class="header text-left" style="width: 50%">Label</th>
<th class="header" style="width: 7%">Diff.</th>
@ -560,7 +531,6 @@
<tbody>
{% for skill in unknown_skill %}
<tr>
{% if user_is_trainer %}
<td>
<a href="{% url 'add_skill_for_gymnast' gymnast.id skill.id %}">
<button type="button" rel="tooltip" class="btn btn-success btn-link btn-sm btn-icon planify_skill">
@ -580,7 +550,7 @@
<i class="far fa-thumbs-up"></i>
</button>
</td>
{% endif %}
<td>{{ skill.notation }}</td>
<td class="text-left"><a href="{% url 'skill_details' skill.id %}">{{ skill.long_label }}</a></td>
<td>{{ skill.difficulty }}</td>

View File

@ -20,7 +20,8 @@ import pendulum
from jarvis.followup.models import Event
from jarvis.followup.forms import GymnastHasRoutineForm
from jarvis.objective.models import TrainingProgram
from jarvis.objective.models import GymnastTraining
from jarvis.followup.models import (
Note,
Plan,
@ -257,12 +258,7 @@ def gymnast_display_scores_chrono(request, gymnast_id):
).order_by("date")
base_queryset = chrono_list.values("date").annotate(score_avg=Avg("tof"))
today = pendulum.now().date()
date_list = (
TrainingProgram.objects.filter(gymnast=gymnast_id, date__gte=today)
.values_list("date", flat=True)
.order_by("date")
.distinct()
)
training_list = GymnastTraining.objects.filter(gymnast=gymnast_id, date__gte=today)
context = {
"intensity_list": intensity_list,
@ -271,7 +267,7 @@ def gymnast_display_scores_chrono(request, gymnast_id):
"chrono_r1": base_queryset.filter(chrono_type=1),
"chrono_r2": base_queryset.filter(chrono_type=2),
"chrono_rf": base_queryset.filter(chrono_type=3),
"date_list": date_list,
"training_list": training_list,
"gymnast_id": gymnast_id,
}
return render(request, "gymnasts/tabs/tab_intensity_and_chronos.html", context)

View File

@ -25,18 +25,11 @@ import pendulum
from jarvis.followup.models import Event
from jarvis.followup.models import (
Note,
Plan,
Skill,
Point,
Chrono,
Injury,
WellBeing,
Intensity,
LearnedSkill,
HeightWeight,
SeasonInformation,
NumberOfRoutineDone,
)
from jarvis.followup.models import LEARNING_STEP_CHOICES