Compare commits
9 Commits
master
...
test_delet
Author | SHA1 | Date |
---|---|---|
Gregory Trullemans | bfab5db6a8 | |
Gregory Trullemans | 4597b859f5 | |
Gregory Trullemans | 42467ac19c | |
Gregory Trullemans | 14af37a04c | |
Gregory Trullemans | d3d271e244 | |
Gregory Trullemans | 954a86463d | |
Gregory Trullemans | 198ceeafc0 | |
Gregory Trullemans | 8e192e1c11 | |
Gregory Trullemans | 6a63776145 |
17
README.md
17
README.md
|
@ -30,6 +30,23 @@ services:
|
|||
Aller dans l'administration de django -> Group. Là, il faut créer deux groupes : "trainer" et "gymnast".
|
||||
Ensuite, attribuer au super user créé le groupe "trainer".
|
||||
|
||||
### Accèder à PGSQL
|
||||
docker exec -it 79cdf0c98c16c5a03ba4e7448fd1c658f9846c691768b298797cb85db2d09f49 bash
|
||||
|
||||
psql -d jarvis_db -U Iamironman
|
||||
|
||||
liste des tables : \dt ou \dt+
|
||||
|
||||
Détails d'une table : \d+ objective_gymnasttraininground
|
||||
|
||||
SELECT : SELECT * FROM objective_gymnasttraininground;
|
||||
Max ID : SELECT MAX(id) FROM objective_gymnasttraininground;
|
||||
Next ID : SELECT nextval(pg_get_serial_sequence('objective_gymnasttraininground', 'id'));
|
||||
|
||||
Set next ID : SELECT setval(pg_get_serial_sequence('objective_gymnasttraininground', 'id'), 1000);
|
||||
|
||||
|
||||
|
||||
### Installation de weasyprint
|
||||
En plus du `pip install weasyprint` et `pip install django-weasyprint`, il faut installer weasyprint (via homebrew, …)
|
||||
puis, **pour les mac M1** exécuter les commandes :
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
action="{% if intensity_id %}{% url 'intensity_update' intensity_id %}{% else %}{% url 'intensity_create' %}{% endif %}"
|
||||
method="post" class="form-horizontal" id="formulaire" name="formulaire">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-group row ">
|
||||
<label for="id_date" class="col-4 col-sm-2 col-md-3 col-lg-3 col-xl-2 col-form-label pr-0">Gymnast <span class="text-danger"><b>*</b></span></label>
|
||||
<div class="col-8 col-sm-9 col-md-9 col-lg-9 col-lg-8 col-xl-8 {% if form.gymnast.errors %}has-danger{% endif %}">
|
||||
|
@ -22,6 +23,7 @@
|
|||
{% if form.gymnast.errors %} <span class="btn btn-sm btn-danger-outline">{% for error in form.gymnast.errors %}{{ error }}{% endfor %}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row ">
|
||||
<label for="id_date" class="col-4 col-sm-2 col-md-3 col-lg-3 col-xl-2 col-form-label pr-0">Date <span class="text-danger"><b>*</b></span></label>
|
||||
<div class="col-5 col-sm-3 col-md-4 col-lg-4 col-xl-3 {% if form.date.errors %}has-danger{% endif %}">
|
||||
|
|
|
@ -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
|
||||
|
@ -166,7 +174,10 @@ class RoutineSkillAdmin(admin.ModelAdmin):
|
|||
model = RoutineSkill
|
||||
|
||||
list_display = ("routine", "skill", "rank")
|
||||
list_filter = (("rank", DropdownFilter),)
|
||||
list_filter = (
|
||||
("routine", RelatedDropdownFilter),
|
||||
("rank", DropdownFilter),
|
||||
)
|
||||
search_fields = (
|
||||
"routine__long_label",
|
||||
"routine__short_label",
|
||||
|
@ -194,9 +205,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 +225,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 +240,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),
|
||||
)
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
import re
|
||||
from datetime import date
|
||||
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,
|
||||
GymnastTraining,
|
||||
GymnastTrainingRound
|
||||
)
|
||||
|
||||
from .validators import is_valid_routine_type, is_valid_subset, is_valid_regexp
|
||||
|
||||
|
||||
class SkillForm(forms.ModelForm):
|
||||
class Meta:
|
||||
|
@ -74,7 +78,7 @@ class CombinationSkillForm(forms.ModelForm):
|
|||
}
|
||||
|
||||
|
||||
class PasseForm(forms.ModelForm):
|
||||
class TrainingRoundForm(forms.ModelForm):
|
||||
|
||||
educatives = forms.ModelMultipleChoiceField(
|
||||
required=False,
|
||||
|
@ -87,11 +91,10 @@ class PasseForm(forms.ModelForm):
|
|||
css = {
|
||||
"all": ["admin/css/widgets.css"],
|
||||
}
|
||||
# Adding this javascript is crucial
|
||||
js = ["/admin/jsi18n/"]
|
||||
|
||||
class Meta:
|
||||
model = Passe
|
||||
model = TrainingRound
|
||||
fields = ("label", "regexp", "educatives", "informations")
|
||||
|
||||
widgets = {
|
||||
|
@ -102,9 +105,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 +116,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 +133,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 +160,88 @@ 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()
|
||||
|
||||
class GymnastTrainingForm(forms.ModelForm):
|
||||
|
||||
# trainingrounds = forms.ModelMultipleChoiceField(
|
||||
# required=False,
|
||||
# queryset=Educative.objects.all(),
|
||||
# widget=FilteredSelectMultiple("TrainingRound", is_stacked=False),
|
||||
# # widget=customFilteredSelectMultiple(verbose_name='test2',is_stacked=False)
|
||||
# )
|
||||
|
||||
# class Media:
|
||||
# css = {
|
||||
# "all": ["admin/css/widgets.css"],
|
||||
# }
|
||||
# js = ["/admin/jsi18n/"]
|
||||
|
||||
class Meta:
|
||||
model = GymnastTraining
|
||||
fields = (
|
||||
"date",
|
||||
"label",
|
||||
"gymnast",
|
||||
"informations",
|
||||
)
|
||||
|
||||
widgets = {
|
||||
"gymnast": forms.HiddenInput(),
|
||||
"date": forms.DateInput(
|
||||
attrs={
|
||||
"class": "form-control datepicker",
|
||||
"placeholder": date.today().strftime("%Y-%m-%d"),
|
||||
"value": date.today().strftime("%Y-%m-%d"),
|
||||
}
|
||||
),
|
||||
"label": forms.TextInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"placeholder": "Label (not mandatory)",
|
||||
"maxlength": 30,
|
||||
}
|
||||
),
|
||||
"informations": forms.Textarea(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"placeholder": "Informations about the passe…", # pylint: disable=line-too-long
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
gymnast_related = forms.CharField(
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"placeholder": "Searching…",
|
||||
"data-ref": "#id_gymnast",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
class GymnastTrainingRoundForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = GymnastTrainingRound
|
||||
fields = (
|
||||
"gymnast_training",
|
||||
"training_round",
|
||||
"repetition",
|
||||
"rank",
|
||||
)
|
||||
widgets = {
|
||||
"gymnast_training": forms.HiddenInput(),
|
||||
"training_round": forms.HiddenInput(),
|
||||
"repetition": forms.NumberInput(),
|
||||
"rank": forms.NumberInput(),
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
]
|
|
@ -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):
|
||||
|
@ -452,7 +455,8 @@ class RoutineSkill(models.Model):
|
|||
"""
|
||||
|
||||
class Meta:
|
||||
ordering = ("rank",)
|
||||
ordering = ["routine", "rank"]
|
||||
unique_together = ["routine", "rank"]
|
||||
|
||||
routine = models.ForeignKey(
|
||||
Routine, on_delete=models.CASCADE, default=None, related_name="skill_links"
|
||||
|
@ -466,7 +470,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 +480,134 @@ 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})"
|
||||
return f"{self.label} ({self.number_of_skill} skills - {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=0)
|
||||
|
||||
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"
|
||||
ordering = [
|
||||
"rank",
|
||||
]
|
||||
unique_together = ["date", "gymnast", "rank"]
|
||||
verbose_name = "Gymnast Training Round"
|
||||
verbose_name_plural = "Gymnast Training Rounds"
|
||||
ordering = ["gymnast_training", "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="rounds_links",
|
||||
)
|
||||
training_round = models.ForeignKey(
|
||||
TrainingRound,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="trainings_links",
|
||||
)
|
||||
rank = models.PositiveSmallIntegerField()
|
||||
repetition = models.PositiveSmallIntegerField(default=1)
|
||||
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)
|
||||
rank = models.PositiveSmallIntegerField(default=1) # TO DELETE
|
||||
score = models.PositiveSmallIntegerField(blank=True, null=True) # TO DELETE
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
f"{self.gymnast} {self.date} - {self.rank} : {self.passe} {self.repetition}"
|
||||
)
|
||||
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)
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,173 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
<!-- {% block page_title %}.: New Routine :.{% endblock %} -->
|
||||
<!-- {% block title %}Routine{% endblock %} -->
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-sm-12 col-md-8 col-lg-8 col-xl-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="">Compose Training : {{ gymnast_training.gymnast }} - {{ gymnast_training.date|date:"l j F Y" }}</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="no-bullets" id="ul-rounds-list"><li>
|
||||
<div class="form-group row pb-0 mb-0">
|
||||
<label class="col-2 col-form-label pb-0 text-center">Rank</label>
|
||||
<label class="col-7 col-form-label pb-0">Label</label>
|
||||
<label class="col-3 col-form-label pb-0">Repetition</label>
|
||||
</div>
|
||||
</li>
|
||||
{% for gtr in gtr_list %}
|
||||
<li>
|
||||
<div class="form-group row pb-0 mb-0">
|
||||
<label class="col-2 col-form-label pt-2 pb-0 text-center">{{ gtr.rank }}</label>
|
||||
<div class="col-7 pt-2 text-danger">
|
||||
{{ gtr.training_round.label }}
|
||||
</div>
|
||||
<div class="col-3 pt-2 text-danger">
|
||||
{{ gtr.repetition }}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li>
|
||||
<div class="form-group row pb-0 mb-0">
|
||||
<label class="col-2 col-form-label text-right mb-0 text-center" id="label_rank">{{ rank }}</label>
|
||||
<div class="col-7 mb-0">
|
||||
<input type="hidden" name="traininground_id" id="id_traininground_id" value="">
|
||||
<input type="text" name="traininground" placeholder="Training round" class="form-control selectpicker" id="id_traininground">
|
||||
</div>
|
||||
<div class="col-3 mb-0">
|
||||
<select type="text" name="repetition" placeholder="Repetition" class="form-control selectpicker" id="id_repetition">
|
||||
<option>-----</option>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option>3</option>
|
||||
<option>4</option>
|
||||
<option>5</option>
|
||||
<option>6</option>
|
||||
<option>7</option>
|
||||
<option>8</option>
|
||||
<option>9</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-footer pt-0">
|
||||
<a href="{% url 'gymnast_training_details' gymnast_training.id %}">
|
||||
<button type="submit" value="add" class="btn btn-icon btn-warning ">
|
||||
<i class="tim-icons icon-double-left"></i>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block footerscript %}
|
||||
<script type="text/javascript">
|
||||
const csrf_token = "{{ csrf_token|escapejs }}";
|
||||
|
||||
$(document).ready(function() {
|
||||
var repetition = 0;
|
||||
var round_label = "";
|
||||
var rank = {{ rank }};
|
||||
$('#id_traininground').focus();
|
||||
|
||||
function insert_round(repetition)
|
||||
{
|
||||
$.ajax({
|
||||
url: "{% url 'link_round_to_training' %}",
|
||||
method: "POST",
|
||||
data: {
|
||||
gymnast_training_id: {{ gymnast_training.id }},
|
||||
traininground_id: $('#id_traininground_id').val(),
|
||||
rank: rank,
|
||||
repetition: $('#id_repetition').val(),
|
||||
csrfmiddlewaretoken: '{{ csrf_token }}'
|
||||
},
|
||||
}).done(function() {
|
||||
insert_selected_round(round_label, repetition);
|
||||
});
|
||||
}
|
||||
|
||||
$('#id_repetition').on("change", function(){
|
||||
if($("select option:selected").val() != "-----")
|
||||
insert_round($("select option:selected").val());
|
||||
});
|
||||
|
||||
function insert_selected_round(round_label="Test", repetition=1)
|
||||
{
|
||||
// $('#minusButton').remove();
|
||||
$('#ul-rounds-list li:last-child').before('<div class="form-group row pb-0 mb-0"><label class="col-2 col-form-label pt-2 pb-0 text-center">' + rank + '</label><div class="col-7 pt-2 text-danger">' + round_label + '</div><div class="col-3 pt-2 text-danger">' + repetition + '</div></div>');
|
||||
rank += 1;
|
||||
$('#label_rank').html(rank);
|
||||
$('#id_traininground').val('').focus();
|
||||
}
|
||||
|
||||
// $('body').on('click', '#minusButton', function(event){
|
||||
// $.ajax({
|
||||
// url: "{% url 'unlink_skill_from_combination' %}",
|
||||
// method: "POST",
|
||||
// data: {
|
||||
// combination_id: {{ routine.id }},
|
||||
// rank: number_of_skill,
|
||||
// csrfmiddlewaretoken: '{{ csrf_token }}'
|
||||
// },
|
||||
// }).done(function() {
|
||||
// $('ol li:last-child').prev().remove();
|
||||
// });
|
||||
// });
|
||||
|
||||
$('#id_traininground').autocomplete({
|
||||
source: function(request, response) {
|
||||
$.ajax({
|
||||
url: "{% url 'traininground_lookup' %}",
|
||||
method: "POST",
|
||||
data: {
|
||||
pattern: request.term,
|
||||
csrfmiddlewaretoken: csrf_token
|
||||
},
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
if(data.length != 0) {
|
||||
response($.map(data, function(item) {
|
||||
return {
|
||||
label: item.Label,
|
||||
value: item.Label,
|
||||
trainingroun_id: item.ID,
|
||||
notation: item.Label,
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
response([{ label: 'No result found.', value: '' }]);
|
||||
};
|
||||
},
|
||||
|
||||
error: function (exception) {
|
||||
console.log(exception);
|
||||
}
|
||||
});
|
||||
},
|
||||
minLength: 2,
|
||||
select: function (event, ui) {
|
||||
$('#id_traininground_id').val(ui.item.trainingroun_id);
|
||||
round_label = ui.item.label;
|
||||
$('#id_repetition').focus();
|
||||
},
|
||||
{% if request.session.template == 0 %}
|
||||
classes: {
|
||||
"ui-widget-content": "custom_autocomplete_ul",
|
||||
"ui-autocomplete": "custom_autocomplete_ul",
|
||||
"ui-menu-item-wrapper": "custom_autocomplete_li",
|
||||
"ui-menu-item": "custom_autocomplete_li",
|
||||
},
|
||||
{% endif %}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -0,0 +1,79 @@
|
|||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-sm-12 col-md-8 col-lg-7 col-xl-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="">{% if gymnasttraining_id %}Edit{% else %}Add{% endif %} Training</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{% if gymnasttraining_id %}{% url 'gymnast_training_update' gymnasttraining_id %}{% else %}{% url 'gymnast_training_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 ">
|
||||
<label for="id_gymnast" class="col-4 col-sm-2 col-md-3 col-lg-3 col-xl-2 col-form-label pr-0">Gymnast <span class="text-danger"><b>*</b></span></label>
|
||||
<div class="col-8 col-sm-9 col-md-9 col-lg-9 col-lg-8 col-xl-8 {% if form.gymnast.errors %}has-danger{% endif %}">
|
||||
{{ form.gymnast }}
|
||||
{{ form.gymnast_related }}
|
||||
{% if form.gymnast.errors %} <span class="btn btn-sm btn-danger-outline">{% for error in form.gymnast.errors %}{{ error }}{% endfor %}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row ">
|
||||
<label for="id_date" class="col-4 col-sm-2 col-md-3 col-lg-3 col-xl-2 col-form-label pr-0">Date <span class="text-danger"><b>*</b></span></label>
|
||||
<div class="col-5 col-sm-3 col-md-4 col-lg-4 col-xl-3 {% if form.date.errors %}has-danger{% endif %}">
|
||||
{{ form.date }}
|
||||
{% if form.date.errors %}<span class="btn btn-sm btn-danger-outline">{% for error in form.date.errors %}{{ error }}{% endfor %}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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</label>
|
||||
<div class="col-8 col-sm-10 col-md-10 col-lg-10 col-xl-10">
|
||||
{{ form.label }}
|
||||
{% if form.label.errors %} <span class="btn btn-sm btn-danger-outline">{% for error in form.label.errors %}{{ error }}{% endfor %}</span>{% endif %}
|
||||
</div>
|
||||
</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.trainingrounds.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.trainingrounds }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row ">
|
||||
<label for="id_informations" class="col-4 col-sm-2 col-md-2 col-lg-2 col-xl-2 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 %}">
|
||||
{{ form.informations }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group text-center">
|
||||
<input type="submit" value="Save" class="btn btn-warning" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block footerscript %}
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
blackDashboard.initDateTimePicker();
|
||||
});
|
||||
</script>
|
||||
{% if request.session.template == 0 %}
|
||||
<script src="{% static "js/template_users/gymnast_autocomplete_black.js" %}"></script>
|
||||
{% else %}
|
||||
<script src="{% static "js/template_users/gymnast_autocomplete.js" %}"></script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,5 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
{% load has_group %}
|
||||
{% load is_user_equal_to_gymnast %}
|
||||
|
||||
{% block page_title %}Training Program{% endblock %}
|
||||
|
||||
|
@ -7,30 +8,36 @@
|
|||
<div class="row justify-content-center">
|
||||
<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 %}
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h4 class="text-center">
|
||||
{{ gymnast_training.gymnast }} - {{ gymnast_training.date|date:"l j F Y" }}
|
||||
</h4>
|
||||
</div>
|
||||
<!-- <div class="col-1 ml-auto">test</div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body pt-0 pb-0">
|
||||
{% if training_rounds %}
|
||||
<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>
|
||||
</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 %} {{ 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 %} {{ 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,15 +49,31 @@
|
|||
</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 %}
|
||||
</tr>
|
||||
</table>
|
||||
{% else %}
|
||||
No training round associated for this training.
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card-footer pt-0 row">
|
||||
<div class="col-md-6 text-muted pt-0">
|
||||
</div>
|
||||
<div class="col-md-6 text-right text-muted pt-0">
|
||||
{% if request.user|has_group:"trainer" or request.user|is_user_equal_to_gymnast:gymnast_id %}
|
||||
<a href="{% url 'gymnast_training_compose' gymnast_training.id %}">
|
||||
<button type="submit" value="add" class="btn btn-icon btn-warning ">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -67,7 +90,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 +108,7 @@ $(document).ready(function(){
|
|||
});
|
||||
} else {
|
||||
$.ajax({
|
||||
url: "{% url 'switch_trainingprogram_line' %}",
|
||||
url: "{% url 'switch_traininground' %}",
|
||||
method: "POST",
|
||||
data: {
|
||||
tpid: $(this).data('tp_id'),
|
|
@ -0,0 +1,75 @@
|
|||
{% extends "listing.html" %}
|
||||
|
||||
{% block datacontent %}
|
||||
<div class="card mb-0">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<h4 class="">
|
||||
{% if gymnast %}
|
||||
<i><a href="{% url 'gymnast_details_tab' gymnast.id 'routine' %}">{{ gymnast }}</a></i>'s
|
||||
{% endif %} Training List
|
||||
</h4>
|
||||
</div>
|
||||
<div class="col-1 ml-auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body pt-0">
|
||||
<div class="table-responsive">
|
||||
{% if gymnasttrainings %}
|
||||
<table class="table tablesorter table-striped" id="trainings_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="header text-center" style="width: 10%">Date</th>
|
||||
<th class="header text-center" style="width: 25%">gymnast</th>
|
||||
<th class="header text-center" style="width: 25%">label</th>
|
||||
<th class="header text-center" style="width: 15%">difficulty</th>
|
||||
<th class="header text-center" style="width: 15%"># skill</th>
|
||||
<th class="header text-center" style="width: 10%">score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for training in gymnasttrainings %}
|
||||
<tr role="row" class="{% cycle 'odd' 'even' %}">
|
||||
<td class="text-center">
|
||||
<a href="{% url 'gymnast_training_details' training.id %}">
|
||||
{{ training.date | date:"j-n-Y" }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center">{{ training.gymnast }}</td>
|
||||
<td class="text-center">{{ training.label }}</td>
|
||||
<td class="text-center">{{ training.difficulty }}</td>
|
||||
<td class="text-center">{{ training.number_of_skill }}</td>
|
||||
<td class="text-center">{{ training.score }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="muted-text">There are no training corresponding to your criterias.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block footerscript %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$('#trainings_table').tablesorter({
|
||||
dateFormat: "uk",
|
||||
sortList: [[0, 1]],
|
||||
});
|
||||
|
||||
$('#trainings_table').DataTable({
|
||||
scrollY: '50vh',
|
||||
scrollCollapse: true,
|
||||
paging: false,
|
||||
searching: false,
|
||||
ordering: false,
|
||||
"bInfo": false,
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -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 %} <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 %} <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 }}
|
|
@ -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> {{ 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> {{ 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 %}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -37,7 +37,7 @@ urlpatterns = [
|
|||
# COMBINATION
|
||||
#
|
||||
path(
|
||||
r"combination/lookup/<str:search_type>",
|
||||
r"combination/lookup/", # <str:search_type>
|
||||
views.combination_lookup,
|
||||
name="combination_lookup",
|
||||
),
|
||||
|
@ -90,35 +90,71 @@ urlpatterns = [
|
|||
),
|
||||
path(r"combination/", views.combination_listing, name="combination_listing"),
|
||||
#
|
||||
# PASSES
|
||||
# TRAININGROUND
|
||||
#
|
||||
path(r"traininground/lookup/", views.traininground_lookup, name="traininground_lookup"),
|
||||
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",
|
||||
),
|
||||
path(
|
||||
r"traininground/switch_traininground/",
|
||||
views.switch_traininground,
|
||||
name="switch_traininground",
|
||||
),
|
||||
#
|
||||
# TRAININGPROGRAM
|
||||
# GYMNASTTRAINING
|
||||
#
|
||||
path(
|
||||
r"trainingprogram/detail/date/<str:date>/gymnast/<int:gymnast_id>/",
|
||||
views.trainingprogram_details,
|
||||
name="trainingprogram_details",
|
||||
r"gymnasttraining/<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/compose/traininground/",
|
||||
views.link_round_to_training,
|
||||
name="link_round_to_training",
|
||||
),
|
||||
path(
|
||||
r"gymnasttraining/compose/<int:gymnast_training_id>/",
|
||||
views.gymnast_training_compose,
|
||||
name="gymnast_training_compose",
|
||||
),
|
||||
path(
|
||||
r"gymnasttraining/add/",
|
||||
views.gymnast_training_create_or_update,
|
||||
name="gymnast_training_create",
|
||||
),
|
||||
path(
|
||||
r"gymnasttraining/add/<int:gymnast_id>/",
|
||||
views.gymnast_training_create_or_update,
|
||||
name="gymnast_training_create_for_gymnast",
|
||||
),
|
||||
path(
|
||||
r"gymnasttraining/edit/<int:gymnasttraining_id>/",
|
||||
views.gymnast_training_create_or_update,
|
||||
name="gymnast_training_update",
|
||||
),
|
||||
path(
|
||||
r"gymnasttraining/gymnast/<int:gymnast_id>/",
|
||||
views.gymnast_training_listing,
|
||||
name="gymnast_training_listing_for_gymnast",
|
||||
),
|
||||
path(
|
||||
r"gymnasttraining/",
|
||||
views.gymnast_training_listing,
|
||||
name="gymnast_training_listing",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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])
|
|
@ -5,25 +5,26 @@ 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,
|
||||
CombinationForm,
|
||||
TrainingRoundForm,
|
||||
GymnastTrainingForm,
|
||||
CombinationSkillForm,
|
||||
GymnastTrainingRoundForm,
|
||||
)
|
||||
from .models import (
|
||||
Skill,
|
||||
Passe,
|
||||
TrainingRound,
|
||||
Routine,
|
||||
Educative,
|
||||
RoutineSkill,
|
||||
TrainingProgram,
|
||||
GymnastTraining,
|
||||
GymnastTrainingRound,
|
||||
PrerequisiteClosure,
|
||||
)
|
||||
|
||||
|
@ -484,12 +485,8 @@ def link_skill_to_combination(request):
|
|||
form = CombinationSkillForm(data)
|
||||
|
||||
if form.is_valid():
|
||||
link, created = RoutineSkill.objects.get_or_create(
|
||||
routine=form.cleaned_data["routine"],
|
||||
skill=form.cleaned_data["skill"],
|
||||
rank=form.cleaned_data["rank"],
|
||||
)
|
||||
return HttpResponse(200, (link, created))
|
||||
form.save()
|
||||
return HttpResponse(200)
|
||||
|
||||
return HttpResponse(406)
|
||||
|
||||
|
@ -514,55 +511,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,130 +556,241 @@ 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,))
|
||||
)
|
||||
|
||||
return render(request, "passes/create.html", {"form": form})
|
||||
return render(request, "trainingrounds/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.rounds_links.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)
|
||||
|
||||
J'utilise `32767` comme valeur intermédiaire pour le `rank` car c'est la limite supérieure d'un
|
||||
PositiveSmallIntegerField.
|
||||
|
||||
TODO: ramener un rank d'un seul coup.
|
||||
"""
|
||||
try:
|
||||
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()
|
||||
)
|
||||
new_rank = (target_trainingpround.rank - source_traininground.rank) - 1
|
||||
is_switchable = (source_traininground.rank == new_rank)
|
||||
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()
|
||||
)
|
||||
new_rank = (source_traininground.rank - target_trainingpround.rank) - 1
|
||||
is_switchable = (source_traininground.rank == new_rank)
|
||||
|
||||
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()
|
||||
if is_switchable:
|
||||
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()
|
||||
else:
|
||||
if direction == 0:
|
||||
new_rank = source_traininground.rank + 1
|
||||
else:
|
||||
new_rank = source_traininground.rank - 1
|
||||
target_trainingpround.rank = new_rank
|
||||
target_trainingpround.save()
|
||||
|
||||
return HttpResponse(200)
|
||||
except Exception:
|
||||
return HttpResponse(409)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def gymnast_training_create_or_update(request, gymnasttraining_id=None, gymnast_id=None):
|
||||
"""Création d'une série.
|
||||
|
||||
Args:
|
||||
gymnasttraining_id (int) identifiant d'un objet de la classe <GymnastTraining>
|
||||
gymnast_id (int) identifiant d'un objet de la classe <Gymnast>
|
||||
"""
|
||||
if gymnasttraining_id:
|
||||
gymnasttraining = get_object_or_404(GymnastTraining, pk=gymnasttraining_id)
|
||||
else:
|
||||
gymnasttraining = None
|
||||
data = {}
|
||||
if gymnast_id is not None:
|
||||
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
|
||||
data["gymnast"] = gymnast_id
|
||||
data["gymnast_related"] = str(gymnast)
|
||||
|
||||
if request.method == "POST":
|
||||
form = GymnastTrainingForm(request.POST, instance=gymnasttraining)
|
||||
|
||||
if form.is_valid():
|
||||
gymnasttraining = form.save()
|
||||
return HttpResponseRedirect(
|
||||
reverse("gymnast_training_details", args=(gymnasttraining.pk,))
|
||||
)
|
||||
|
||||
return render(request, "gymnasttrainings/create.html", {"form": form})
|
||||
|
||||
form = GymnastTrainingForm(instance=gymnasttraining, initial=data)
|
||||
context = {"form": form, "gymnasttraining_id": gymnasttraining_id}
|
||||
return render(request, "gymnasttrainings/create.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def gymnast_training_listing(request, gymnast_id=None):
|
||||
"""Liste des entraînements."""
|
||||
|
||||
if gymnast_id:
|
||||
gymnast = Gymnast.objects.get(pk=gymnast_id)
|
||||
gymnasttrainings = gymnast.trainings.all()
|
||||
else:
|
||||
gymnast = None
|
||||
gymnasttrainings = GymnastTraining.objects.all()
|
||||
|
||||
context = {"gymnasttrainings": gymnasttrainings, "gymnast": gymnast}
|
||||
return render(request, "gymnasttrainings/list.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def gymnast_training_compose(request, gymnast_training_id):
|
||||
"""
|
||||
Récupère un entraînement et les sauts associés sur base d'un id passé en paramètre.
|
||||
|
||||
Args:
|
||||
gymnast_training_id (int) identifiant d'un object de classe <GymnastTraining>.
|
||||
"""
|
||||
|
||||
gymnast_training = get_object_or_404(GymnastTraining, pk=gymnast_training_id)
|
||||
gtr_list = gymnast_training.rounds_links.all()
|
||||
rank = gtr_list.count()
|
||||
if not rank:
|
||||
rank = 1
|
||||
|
||||
context = {
|
||||
"gymnast_training": gymnast_training,
|
||||
"gtr_list": gtr_list,
|
||||
"rank": rank,
|
||||
}
|
||||
return render(request, "gymnasttrainings/compose.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["POST"])
|
||||
def traininground_lookup(request):
|
||||
"""
|
||||
Récupère la liste des skill à la volée suivant des caractères de recherche entrés (min 3
|
||||
caractères).
|
||||
"""
|
||||
trainingrounds = []
|
||||
pattern = request.POST.get("pattern", None)
|
||||
|
||||
if pattern is not None and len(pattern) > 1:
|
||||
traininground_list = TrainingRound.objects.filter(label__icontains=pattern)
|
||||
trainingrounds = [
|
||||
{"ID": x.id, "Label": str(x)} for x in traininground_list
|
||||
]
|
||||
|
||||
return JsonResponse(trainingrounds, safe=False)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["POST"])
|
||||
def link_round_to_training(request):
|
||||
"""
|
||||
Recoit dans request trois informations permettant de lier complètement un saut à une routine :
|
||||
- gymnasttraining_id (int) identifiant d'un object de classe <GymnastTraining>
|
||||
- traininground_id (int) identifiant d'un object de classe <TrainingRound>
|
||||
- repetition (int) nombre de répétition du round dans l'entraînement
|
||||
- rank (int) numéro de place du round dans l'entraînement
|
||||
"""
|
||||
data = {
|
||||
"gymnast_training": get_object_or_404(GymnastTraining, pk=request.POST.get("gymnast_training_id", 0)),
|
||||
"training_round": get_object_or_404(TrainingRound, pk=request.POST.get("traininground_id", 0)),
|
||||
"repetition": request.POST.get("repetition", 0),
|
||||
"rank": request.POST.get("rank", 0),
|
||||
}
|
||||
form = GymnastTrainingRoundForm(data)
|
||||
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return HttpResponse(200)
|
||||
|
||||
return HttpResponse(406)
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
),
|
||||
]
|
|
@ -75,6 +75,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
|
@ -82,14 +83,32 @@
|
|||
<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>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-footer pt-0 row">
|
||||
<div class="col-md-6 text-muted pt-0">
|
||||
</div>
|
||||
<div class="col-md-6 text-right text-muted pt-0">
|
||||
<a href="{% url 'gymnast_training_listing_for_gymnast' gymnast_id %}">
|
||||
<button type="submit" value="add" class="btn btn-icon btn-warning ">
|
||||
<i class="fas fa-list"></i>
|
||||
</button>
|
||||
</a>
|
||||
{% if request.user|has_group:"trainer" or request.user|is_user_equal_to_gymnast:gymnast_id %}
|
||||
<a href="{% url 'gymnast_training_create_for_gymnast' gymnast_id %}">
|
||||
<button type="submit" value="add" class="btn btn-icon btn-warning ">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -61,10 +61,15 @@ class Season:
|
|||
if not re.search(pattern, self.label):
|
||||
return False
|
||||
|
||||
print()
|
||||
print()
|
||||
first_year = int(self.label[:dash_position])
|
||||
print(first_year)
|
||||
second_year = int(self.label[dash_position + 1 :])
|
||||
print(second_year)
|
||||
|
||||
if first_year != second_year - 1:
|
||||
print("Not valid")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
|
@ -7,32 +7,25 @@ from .models import Season, max_even_if_none
|
|||
class ModelTestCase(TestCase):
|
||||
def test_init_season(self):
|
||||
season = Season("2022-2023")
|
||||
self.assertEqual(season.is_valid(), True)
|
||||
self.assertEqual(season.label, "2022-2023")
|
||||
|
||||
season = Season("2022-2024")
|
||||
self.assertEqual(season.is_valid(), True)
|
||||
self.assertEqual(season.label, "2022-2023")
|
||||
self.assertEqual(season.label, "2023-2024")
|
||||
|
||||
season = Season("2024-2023")
|
||||
self.assertEqual(season.is_valid(), True)
|
||||
self.assertEqual(season.label, "2022-2023")
|
||||
self.assertEqual(season.label, "2023-2024")
|
||||
|
||||
season = Season("1358-5682")
|
||||
self.assertEqual(season.is_valid(), True)
|
||||
self.assertEqual(season.label, "2022-2023")
|
||||
self.assertEqual(season.label, "2023-2024")
|
||||
|
||||
season = Season("fgkrs-gkml")
|
||||
self.assertEqual(season.is_valid(), True)
|
||||
self.assertEqual(season.label, "2022-2023")
|
||||
self.assertEqual(season.label, "2023-2024")
|
||||
|
||||
season = Season("drgnldsjgklfdtngl")
|
||||
self.assertEqual(season.is_valid(), True)
|
||||
self.assertEqual(season.label, "2022-2023")
|
||||
self.assertEqual(season.label, "2023-2024")
|
||||
|
||||
season = Season("12675353878354")
|
||||
self.assertEqual(season.is_valid(), True)
|
||||
self.assertEqual(season.label, "2022-2023")
|
||||
self.assertEqual(season.label, "2023-2024")
|
||||
|
||||
def test_season_is_valid(self):
|
||||
season = Season("2022-2023")
|
||||
|
|
|
@ -143,4 +143,10 @@ textarea.form-control {
|
|||
background-image: linear-gradient(to bottom left, #ec250d, #fd5d93, #ec250d);
|
||||
background-size: 210% 210%;
|
||||
background-position: top right;
|
||||
}
|
||||
|
||||
ul.no-bullets {
|
||||
list-style-type: none; /* Remove bullets */
|
||||
padding: 0; /* Remove padding */
|
||||
margin: 0; /* Remove margins */
|
||||
}
|
Loading…
Reference in New Issue