diff --git a/src/objective/admin.py b/src/objective/admin.py index 7cc2677..672a63d 100644 --- a/src/objective/admin.py +++ b/src/objective/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin -# Register your models here. +from django_extensions.admin import ForeignKeyAutocompleteAdmin + from .models import ( Educative, TouchPosition, @@ -9,7 +10,6 @@ from .models import ( Routine_Skill, Chrono, ) -from django_extensions.admin import ForeignKeyAutocompleteAdmin class TouchPositionAdmin(admin.ModelAdmin): diff --git a/src/objective/forms.py b/src/objective/forms.py index 5244d58..5fa8c7f 100644 --- a/src/objective/forms.py +++ b/src/objective/forms.py @@ -1,7 +1,9 @@ -# coding=UTF-8 +"""Formulaires d'édition des éducatifs, séries et compétences.""" + +from datetime import date from django import forms -from datetime import date + from .models import Routine, Routine_Skill, Chrono @@ -12,10 +14,10 @@ class RoutineForm(forms.ModelForm): widgets = { # Champs obligatoires de la classe mère. "longLabel": forms.TextInput( - attrs={"class": "form-control", "placeholder": "Routine's long name"} + attrs={"class": "form-control", "placeholder": "Routine long name"} ), "shortLabel": forms.TextInput( - attrs={"class": "form-control", "placeholder": "Routine's short name"} + attrs={"class": "form-control", "placeholder": "Routine short name"} ), "difficulty": forms.HiddenInput(), "level": forms.HiddenInput(), diff --git a/src/objective/models.py b/src/objective/models.py index 47fe5f3..643ad7d 100644 --- a/src/objective/models.py +++ b/src/objective/models.py @@ -1,40 +1,52 @@ -# coding=UTF-8 +"""Modélisation des éducatifs, objectifs et des compétences.""" from datetime import date + +from django.contrib.auth.models import User from django.db import models from django.db.models import Q + from base.models import Markdownizable -from django.contrib.auth.models import User class Educative(Markdownizable): - """ - Classe `mère`. + """Un éducatif représente une étape d'apprentissage. + + Elle sert de classe "mère" pour toutes les autres choses pouvant être apprises. + + Elle est décrite par: + * Un libellé court et long + * Une difficulté + * Un niveau + * Des prérequis à réaliser + * Un âge minimum auquel il peut être réalisé, suivant que le gymnaste soit une fille ou un garçon + + Un éducatif peut être lié à plusieurs autres éducatifs. """ class Meta: verbose_name = "Educatif" verbose_name_plural = "Educatifs" - ordering = ["longLabel", "shortLabel"] # 'level', + ordering = ["longLabel", "shortLabel"] - longLabel = models.CharField(max_length = 255, verbose_name = "Long Name") - shortLabel = models.CharField(max_length = 255, verbose_name = "Short Name") + longLabel = models.CharField(max_length=255, verbose_name="Long Name") + shortLabel = models.CharField(max_length=255, verbose_name="Short Name") difficulty = models.DecimalField( - max_digits = 3, decimal_places = 1, verbose_name = "Difficulty" + max_digits=3, decimal_places=1, verbose_name="Difficulty" ) - level = models.PositiveSmallIntegerField(verbose_name = "Level", default = 0) - rank = models.PositiveSmallIntegerField(verbose_name = "Rank", default = 0) + level = models.PositiveSmallIntegerField(verbose_name="Level", default=0) + rank = models.PositiveSmallIntegerField(verbose_name="Rank", default=0) educative = models.ManyToManyField( - "self", related_name = "educativeOf", blank = True, symmetrical = False + "self", related_name="educativeOf", blank=True, symmetrical=False ) prerequisite = models.ManyToManyField( - "self", related_name = "prerequisiteOf", blank = True, symmetrical = False + "self", related_name="prerequisiteOf", blank=True, symmetrical=False ) ageBoy = models.PositiveSmallIntegerField( - blank = True, null = True, verbose_name = "Boy's age" + blank=True, null=True, verbose_name="Boy's age" ) ageGirl = models.PositiveSmallIntegerField( - blank = True, null = True, verbose_name = "Girl's age" + blank=True, null=True, verbose_name="Girl's age" ) def __str__(self): @@ -47,8 +59,9 @@ class Educative(Markdownizable): class TouchPosition(models.Model): - """ - Classe représentant les différentes position d'arrivée/départ (landing position) en trampoline. + """Représentation des différentes positions d'arrivée et de départ en trampoline. + + Cette classe fait référence aux *landing positions*. """ class Meta: @@ -56,10 +69,10 @@ class TouchPosition(models.Model): verbose_name_plural = "Landings" ordering = ["longLabel", "shortLabel", "default", "competition"] - longLabel = models.CharField(max_length = 30, verbose_name = "Nom long") - shortLabel = models.CharField(max_length = 15, verbose_name = "Nom court") - competition = models.BooleanField(verbose_name = "Compétition") - default = models.BooleanField(verbose_name = "Défaut") + longLabel = models.CharField(max_length=30, verbose_name="Nom long") + shortLabel = models.CharField(max_length=15, verbose_name="Nom court") + competition = models.BooleanField(verbose_name="Compétition") + default = models.BooleanField(verbose_name="Défaut") def __str__(self): return "%s" % (self.longLabel) @@ -67,7 +80,7 @@ class TouchPosition(models.Model): def get_default_position(): """ - Renvoie la position d'arrivée/départ par définie par défaut si elle existe. Sinon, renvoie None. + Renvoie la position d'arrivée/départ par définie par défaut si elle existe. Sinon, renvoie None. """ try: return TouchPosition.objects.get(default=True).id @@ -76,13 +89,14 @@ def get_default_position(): class Skill(Educative): - """ - Classe représentant une figure (aka un saut acrobatique). - """ + """Les `skills` représentent une figure ou un saut acrobatique. - # SELECT * FROM `objective_skill` WHERE educative_ptr_id NOT IN (SELECT DISTINCT(from_educative_id) FROM `objective_educative_prerequisite`) + Chaque saut est caractérisé par: + * Une position de départ + * Une position d'arrivée + * Un type de rotation - # SELECT * FROM `objective_skill`, `objective_educative` WHERE `objective_educative`.id = `objective_skill`.educative_ptr_id + """ class Meta: verbose_name = "Skill" @@ -104,29 +118,29 @@ class Skill(Educative): (2, "Backward"), ) - position = models.CharField(max_length = 2, choices = POSITION_CHOICES) + position = models.CharField(max_length=2, choices=POSITION_CHOICES) departure = models.ForeignKey( TouchPosition, - related_name = "depart_of", - default = get_default_position, - verbose_name = "Take-off position", - on_delete = models.CASCADE, + related_name="depart_of", + default=get_default_position, + verbose_name="Take-off position", + on_delete=models.CASCADE, ) landing = models.ForeignKey( TouchPosition, - related_name = "landing_of", - default = get_default_position, - verbose_name = "Landing position", - on_delete = models.CASCADE, + related_name="landing_of", + default=get_default_position, + verbose_name="Landing position", + on_delete=models.CASCADE, ) rotationType = models.PositiveSmallIntegerField( - choices = ROTATION_CHOICES, verbose_name = "Type de rotation" + choices=ROTATION_CHOICES, verbose_name="Type de rotation" ) - rotation = models.PositiveSmallIntegerField(verbose_name = "1/4 de rotation") - twist = models.PositiveSmallIntegerField(verbose_name = "1/2 Vrille") - notation = models.CharField(max_length = 25) - simplyNotation = models.CharField(max_length = 25, verbose_name = "Notation simplifiée") - is_competitive = models.BooleanField(default = False) + rotation = models.PositiveSmallIntegerField(verbose_name="1/4 de rotation") + twist = models.PositiveSmallIntegerField(verbose_name="1/2 Vrille") + notation = models.CharField(max_length=25) + simplyNotation = models.CharField(max_length=25, verbose_name="Notation simplifiée") + is_competitive = models.BooleanField(default=False) # importance = models.PositiveSmallIntegerField(default = 1) def __str__(self): @@ -134,9 +148,7 @@ class Skill(Educative): class Routine(Educative): - """ - Classe représentant une série (enchainement de plusieurs figures). - """ + """Une routine représente une série, càd un enchaînement de plusieurs figures.""" class Meta: verbose_name = "Routine" @@ -144,9 +156,9 @@ class Routine(Educative): active = models.BooleanField() jumps = models.ManyToManyField( - Skill, through = "Routine_Skill", verbose_name = "routine" - ) # ceci n'est pas un vrai champ - is_competitive = models.BooleanField(default = False) + Skill, through="Routine_Skill", verbose_name="routine" + ) # ceci n'est pas un vrai champ (- Magritte ?) + is_competitive = models.BooleanField(default=False) def __str__(self): return "%s (%s)" % (self.shortLabel, self.shortLabel) @@ -216,10 +228,10 @@ class Routine_Skill(models.Model): ordering = ("rank",) routine = models.ForeignKey( - Routine, on_delete = models.CASCADE, default = None, related_name = "skill_links" + Routine, on_delete=models.CASCADE, default=None, related_name="skill_links" ) skill = models.ForeignKey( - Skill, on_delete = models.CASCADE, default = None, related_name = "routine_links" + Skill, on_delete=models.CASCADE, default=None, related_name="routine_links" ) rank = models.PositiveSmallIntegerField() @@ -232,10 +244,9 @@ class Routine_Skill(models.Model): class Chrono(models.Model): - """ - Classe représentant le temps réalisé par un élève pour faire une série. + """Représentation du temps demandé à un gymnaste pour réaliser une série. - .. todo:: lors de l'affichage, aller chercher les ToF contenu dans les points de compétition (avec traitement adéquat). + TODO: lors de l'affichage, aller chercher les ToF contenu dans les points de compétition (avec traitement adéquat). """ ROUTINETYPE_CHOICE = ( @@ -247,13 +258,13 @@ class Chrono(models.Model): (99, "Other"), ) - routine = models.ForeignKey(Routine, on_delete = models.CASCADE, default = None) - routine_type = models.PositiveSmallIntegerField(choices = ROUTINETYPE_CHOICE) + routine = models.ForeignKey(Routine, on_delete=models.CASCADE, default=None) + routine_type = models.PositiveSmallIntegerField(choices=ROUTINETYPE_CHOICE) gymnast = models.ForeignKey( - "people.gymnast", on_delete = models.CASCADE, default = None + "people.gymnast", on_delete=models.CASCADE, default=None ) - date = models.DateField(default = date.today, verbose_name = "Date") - score = models.DecimalField(max_digits = 5, decimal_places = 2) + date = models.DateField(default=date.today, verbose_name="Date") + score = models.DecimalField(max_digits=5, decimal_places=2) def __str__(self): return "%s le %s : %s pour %s" % ( @@ -274,17 +285,16 @@ class Chrono(models.Model): class Evaluation(models.Model): - """ - Classe permettant l'évaluation des éducatifs. + """Evaluation d'un éducatif par un utilisateur. """ - date = models.DateField(default = date.today, verbose_name = "Date") - value = models.PositiveSmallIntegerField(default = 0) + date = models.DateField(default=date.today, verbose_name="Date") + value = models.PositiveSmallIntegerField(default=0) educative = models.ForeignKey( Educative, - related_name = "depart_of", - verbose_name = "Take-off position", - on_delete = models.CASCADE, + related_name="depart_of", + verbose_name="Take-off position", + on_delete=models.CASCADE, ) - type_of_evaluator = models.BooleanField(default = 0) - evaluator = models.ForeignKey(User, null = True, on_delete = models.SET_NULL) + type_of_evaluator = models.BooleanField(default=0) + evaluator = models.ForeignKey(User, null=True, on_delete=models.SET_NULL) diff --git a/src/objective/tests.py b/src/objective/tests.py index 366bc76..0119787 100644 --- a/src/objective/tests.py +++ b/src/objective/tests.py @@ -1,15 +1,26 @@ -# coding=UTF-8 +"""Tests des routines, éducatifs, compétences, ...""" -from django.urls import reverse from django.test import TestCase, Client +from django.urls import reverse from .models import Routine_Skill, Routine, Skill, TouchPosition from .views import link_skill_to_routine class RoutineTest(TestCase): + """Tests unitaires liés aux séries. + + Attributes: + routine (Routine) + touch_position (TouchPosition) + skill (Skill) + client (django.test.Client) + """ + def setUp(self): - self.routine, routine_created = Routine.objects.get_or_create( + """Instancie une série (*routine*), des TouchPositions, ... + """ + self.routine, _ = Routine.objects.get_or_create( longLabel="Routine test", shortLabel="Routine test", difficulty=0, @@ -17,14 +28,14 @@ class RoutineTest(TestCase): ) ( self.touch_position, - touch_position_created, + _, ) = TouchPosition.objects.get_or_create( longLabel="Touch test", shortLabel="Touch test", competition=True, default=True, ) - self.skill, skill_created = Skill.objects.get_or_create( + self.skill, _ = Skill.objects.get_or_create( longLabel="Skill test", shortLabel="Skill test", difficulty=0, @@ -38,17 +49,19 @@ class RoutineTest(TestCase): simplyNotation="t", ) self.client = Client() - self.url_details = { + + def test_valid_link_skill_to_routine(self): + """Vérifie que l'association d'un Skill à une série fonctionne correctement. + """ + url_details = { "routineid": self.routine.id, "skillid": self.skill.id, "order": 1, } - def test_valid_link_skill_to_routine(self): - c = Client() - url = reverse("link_skill_to_routine", kwargs=self.url_details) - response = c.get(url) + url = reverse("link_skill_to_routine", kwargs=url_details) + + response = self.client.get(url) - # print(response) self.assertEquals(response.status_code, 200) self.assertTrue(Routine_Skill.objects.exists()) diff --git a/src/objective/urls.py b/src/objective/urls.py index 6c3154c..435bf9f 100644 --- a/src/objective/urls.py +++ b/src/objective/urls.py @@ -1,4 +1,11 @@ -# coding=UTF-8 +"""Définition des URLs associées à l'application `objective`. + +Il existe trois grandes rubriques pour cette application: + +* chrono +* skill +* routine +""" from django.urls import path, re_path @@ -46,7 +53,7 @@ routine_urlpatterns = [ r"suggest/", views.suggest_routine, name="suggest_routine", - ) + ), ] # Chrono diff --git a/src/objective/views.py b/src/objective/views.py index b7b46da..d722841 100644 --- a/src/objective/views.py +++ b/src/objective/views.py @@ -48,8 +48,7 @@ def __lookup(lookup_class, lookup_value): @login_required def skill_listing(request, field=None, expression=None, value=None, level=None): - """ - Récupère la liste des skills suivant un pattern si celui-ci est définit. + """Récupère la liste des skills suivant un pattern si celui-ci est défini. """ pattern = None @@ -116,8 +115,7 @@ def skill_detail(request, skillid): @login_required @require_http_methods(["POST"]) def linkSkillGymnast(request): - """ - Lie un gymnast à une figure. + """Lie un gymnaste à une figure. """ # utiliser un FORM pour cette fonction. gymnastid = request.POST.get("gymnastid", None) @@ -370,7 +368,7 @@ def __construct_routine( competition (bool): indique si la série doit respecter les règles de compétition. logic (bool): indique si la série doit suivre les règles de logique (sportive). gymnast (gymnast): gymnaste. - + Returns: ??? (list ?): liste des séries correspondantes aux criètres. """ @@ -457,7 +455,7 @@ def suggest_routine( logic (bool): indique si la série doit suivre les règles de logique (sportive). gymnast (gymnast): gymnaste. last_jump (skill): dernier saut sélectionné pour la série. - + Returns: ??? (list): liste des séries correspondantes aux criètres.