This commit is contained in:
Trullemans Gregory 2020-10-26 18:36:03 +01:00
commit 4955fe232b
10 changed files with 203 additions and 92 deletions

View File

@ -1,4 +1,14 @@
"""Administration des gymnastes et des personnes.
Remarks:
* Je ne pense pas qu'il faille encore passer par `ForeignKeyAutocompleteAdmin`.
https://docs.djangoproject.com/fr/3.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.autocomplete_fields
"""
from django.contrib import admin from django.contrib import admin
from django_extensions.admin import ForeignKeyAutocompleteAdmin
from .models import ( from .models import (
Gymnast, Gymnast,
Accident, Accident,
@ -7,10 +17,6 @@ from .models import (
GymnastHasRoutine, GymnastHasRoutine,
) )
# from objective.models import Educative
from django_extensions.admin import ForeignKeyAutocompleteAdmin
class CanDoRelationAdmin(ForeignKeyAutocompleteAdmin): class CanDoRelationAdmin(ForeignKeyAutocompleteAdmin):
model = CanDoRelation model = CanDoRelation

View File

@ -1,4 +1,4 @@
# coding=UTF-8 """Formulaires de gestion des données entrantes pour les gymnastes et accidents."""
from django import forms from django import forms

View File

@ -1,18 +1,64 @@
# coding=UTF-8 """Modélisation des gymnastes, accidents et relations à faire/faites.
Notes:
Est-ce qu'il ne faudrait pas refactoriser les GSM père/mère ?
Avec une table d'association, et un champ qui indique la qualité du contact ?
Du coup, cela permettrait se débarasser des champs phone, gsm, gsm_father et gsm_mother.
Comment sont gérées les évolutions ? Changement de clubs, de fédération,
A quoi correspond le champ `year_of_practice` ?
Comment se comportera-t-il dans un an ? Dans deux ans ?
Est-ce qu'il ne faudrait pas une date de début, plutôt ?
Idem pour la méthode `actual_year_of_pratice`.
Comme elle se base sur le champ `created_at`, il suffit que l'enregistrement ne soit pas
réalisé correctement pour que la méthode retourne une mauvaise information.
Que signifie qu'un gymnaste soit actif ou inactif ? Est-ce que cela ne devrait pas plutôt
être géré au niveau des utilisateurs ?
Au niveau des utilisateurs, comme un User Django a déjà les champs lastname/firstname
pourquoi ne pas les réutiliser ? On garde la clé OneToOne, mais on déplace dans l'autre
classe les champs qui peuvent l'être. `Email` s'y retrouve également.
Les relations `cando`, `haveToDo` et `have_routine` ne sont pas correctement nommées.
Si tu as une instance de `Gymnast`, tu devrais faire ceci :
>>> gregg = Gymnast.objects.first()
>>> gregg.have_routine <-- pas bien
>>> gregg.routines <-- ok
Idéalement, cela pourrait même être une indirection.
>>> gregg.routines <-- retourne la relation de type `have_routine`
>>> gregg.educatives.can_do <-- retourne les éducatifs qu'il **peut** faire
>>> gregg.educatives.must_do <-- retourne les éducatifs qu'il **doit** faire
(j'avoue ne pas tout à fait comprendre la nuance entre les deux)
<!> Tu as des fonctions qui ne sont pas du tout utilisées et qui pourrissent un peu le fichier.
>>> next_age() ? N'est appelé nulle part ailleurs.
>>> known_skills() <-- peut être récupéré directement via l'attribut `cando`
(du coup, tu as sans doute une piste de renommage ;-))
"""
from django.db import models
from django.contrib.auth.models import User
from datetime import date from datetime import date
from base.models import Markdownizable
from django.contrib.auth.models import User
from django.db import models from django.db import models
import pendulum import pendulum
# from location.models import Club from base.models import Markdownizable
class Gymnast(Markdownizable): class Gymnast(Markdownizable):
""" """Représente un gymnaste.
Représente un gymnaste. En plus des données personnels (nom, prénom, date de naissance, ) un gymnaste aura une photo et une orientation (de vrille). Un gymnaste peut être actif ou inactif.
En plus de sa signalétique (nom, prénom, date de naissance, ...),
un gymnaste aura une photo et une orientation (de vrille).
Un gymnaste peut être actif ou inactif.
""" """
class Meta: class Meta:
@ -90,16 +136,16 @@ class Gymnast(Markdownizable):
@property @property
def next_birthday(self): def next_birthday(self):
"""Définit la prochaine date (de fête) d'anniversaire pour cette personne. """Définit la prochaine date (de fête) d'anniversaire pour cette personne.
Returns: Returns:
Soit le jour/mois pour cette année Soit le jour/mois pour cette année
Soit le jour/mois pour l'année prochaine. Soit le jour/mois pour l'année prochaine.
Examples: en supposant qu'on soit le 23/05/2019 Examples: en supposant qu'on soit le 23/05/2019
>>> from datetime import date >>> from datetime import date
>>> gregg = People(name='Tru', firstname='Gregg', birthdate=date(1982, 2, 5) >>> gregg = People(name='Tru', firstname='Gregg', birthdate=date(1982, 2, 5)
>>> gregg.next_birthday() >>> gregg.next_birthday()
Date(2020, 2, 5) Date(2020, 2, 5)
""" """
now = pendulum.now() now = pendulum.now()
@ -149,8 +195,7 @@ class Gymnast(Markdownizable):
class Accident(Markdownizable): class Accident(Markdownizable):
""" """La classe `Accident` permet d'indiquer qu'un gymnaste est tombé durant un saut.
Classe représentant un accident. Un accident lie un saut à un gymnaste pour une date donnée.
""" """
class Meta: class Meta:

View File

@ -1,6 +1,9 @@
# coding=UTF-8 """Administration des plannings, évènements et saisons."""
from django.contrib import admin from django.contrib import admin
from django_extensions.admin import ForeignKeyAutocompleteAdmin
from .models import ( from .models import (
EventType, EventType,
Event, Event,
@ -14,19 +17,17 @@ from .models import (
Round, Round,
PlanningLine, PlanningLine,
) )
from django_extensions.admin import ForeignKeyAutocompleteAdmin
def duplicate_record(modeladmin, request, queryset): def duplicate_record(modeladmin, request, queryset):
""" """*Custom action* permettant de dupliquer plusieurs enregistrements.
Duplication de record sélectionner.
""" """
for object in queryset: for object in queryset:
object.id = None object.id = None
object.save() object.save()
duplicate_record.short_description = "Duplicate selected record" duplicate_record.short_description = "Duplicate selected records"
class SeasonAdmin(admin.ModelAdmin): class SeasonAdmin(admin.ModelAdmin):

View File

@ -1,11 +1,11 @@
# coding=UTF-8
from datetime import date
from django import forms from django import forms
from datetime import date
from .models import Unavailability, Event, PlanningLine
from people.models import Gymnast
from django.contrib.admin.widgets import FilteredSelectMultiple from django.contrib.admin.widgets import FilteredSelectMultiple
from people.models import Gymnast
from .models import Unavailability, Event, PlanningLine
class UnavailabilityForm(forms.ModelForm): class UnavailabilityForm(forms.ModelForm):
class Meta: class Meta:

View File

@ -1,18 +1,23 @@
# coding=UTF-8
from django.db import models from datetime import datetime, date, time, timedelta
from django.contrib.auth.models import User from django.contrib.auth.models import User
from datetime import datetime, date, time from django.db import models
from base.models import Markdownizable
from django.utils import timezone from django.utils import timezone
from people.models import Gymnast
from location.models import Club
from datetime import datetime, timedelta
import pendulum import pendulum
from base.models import Markdownizable
from location.models import Club
from people.models import Gymnast
def get_week(a_date): def get_week(a_date):
""" """
Remarks:
Je ne comprends pas trop cette fonction...
Tu pars d'une date, et tu récupères le lundi et le samedi de la semaine correspondant ?
""" """
the_date = pendulum.parse(a_date) the_date = pendulum.parse(a_date)
day = the_date.weekday() day = the_date.weekday()
@ -33,6 +38,18 @@ def get_number_of_weeks_between(start, stop):
:param stop: date de fin de la période :param stop: date de fin de la période
:type stop: datetime.date :type stop: datetime.date
:return: Le nombre de semaines entre les deux dates. :return: Le nombre de semaines entre les deux dates.
Remarks:
Proposition d'utiliser isocalendar() sur une date.
L'indice 1 de la valeur de retour donne la semaine correspondant.
Eg.
>>> from datetime import date
>>> d = date(2020, 9, 27)
>>> d.isocalendar()
(2020, 39, 7)
-> Est-ce qu'il ne suffirait pas de faire la différence ?
""" """
tmp = stop - start tmp = stop - start
@ -87,9 +104,8 @@ class TemporizableQuerySet(models.QuerySet):
class Temporizable(models.Model): class Temporizable(models.Model):
""" """Classe abstraite définissant une période comprise entre deux dates.
Classe abstraite définissant deux dates (une da te de début, une date de
fin) et des méthodes de calculs sur base de ces dates.
""" """
class Meta: class Meta:
@ -198,12 +214,13 @@ class EventType(models.Model):
class Event(Markdownizable, Temporizable): class Event(Markdownizable, Temporizable):
""" """Classe représentant les évènements.
Classe représentant les évènements. Un évènement est caractèrisé par :
- un nom, Un évènement est caractérisé par :
- un lieu (place), * un nom,
- un type (compétition, démonstration, ), * un lieu (place),
- des gymnastes (participation prévue). * un type (compétition, démonstration, ),
* des gymnastes (participation prévue).
Je ne me rapelle plus à quoi sert le club. Je ne me rapelle plus à quoi sert le club.
""" """
@ -257,14 +274,15 @@ class Event_Participation(models.Model):
class Course(Markdownizable, Temporizable): class Course(Markdownizable, Temporizable):
""" """Classe représentant les cours.
Classe représentant les cours. Un cours est défini par :
- une heure de début et une heure de fin, Un cours est défini par :
- une date de début et une date de fin (un cours est considéré comme donné hebdromadairement entre * une heure de début et une heure de fin,
* une date de début et une date de fin (un cours est considéré comme donné hebdromadairement entre
ces deux dates) (hérite de la classe `Temporizable`) ces deux dates) (hérite de la classe `Temporizable`)
- est associé à un ou plusieurs entraineurs, * est associé à un ou plusieurs entraineurs,
- est associé à un club * est associé à un club
- est associé à un jour de la semaine (numéro du jour dans la semaine : 0 = lundi, 6 = dimanche). * est associé à un jour de la semaine (numéro du jour dans la semaine : 0 = lundi, 6 = dimanche).
""" """
class Meta: class Meta:
@ -319,8 +337,7 @@ class Course(Markdownizable, Temporizable):
class Group(models.Model): class Group(models.Model):
""" """Classe représentant les groupes (Loisir, D1, D2, A, B, …).
Classe représentant les groupes (Loisir, D1, D2, A, B, ).
Un groupe appartient à un club. Un groupe appartient à un club.
""" """
@ -343,10 +360,12 @@ class Group(models.Model):
class Subgroup(models.Model): class Subgroup(models.Model):
""" """Classe représentant les sous-groupes.
Classe représentant les sous-groupes.
Un sous-groupe appartient à un groupe (lui-même lié à un club). De la sorte, quand un gymnaste est mis dans un sous-groupe, en remontant via le groupe, nous pouvons connaître le(s) club(s) du gymnaste pour chaque saison. Un sous-groupe appartient à un groupe (lui-même lié à un club).
De cette manière, quand un gymnaste est mis dans un sous-groupe, en remontant via le groupe,
nous pouvons connaître le(s) club(s) du gymnaste pour chaque saison.
""" """
class Meta: class Meta:
@ -367,8 +386,7 @@ class Subgroup(models.Model):
class UnavailabilityManager(models.Manager): class UnavailabilityManager(models.Manager):
""" """Classe représentant le manager de la classe `Unavailability`.
Classe représentant le manager de la classe `Unavailability`.
""" """
def next(self, count): def next(self, count):
@ -379,8 +397,7 @@ class UnavailabilityManager(models.Manager):
class Unavailability(Markdownizable, Temporizable): class Unavailability(Markdownizable, Temporizable):
""" """Classe représentant les indisponibilités.
Classe représentant les indisponibilités.
""" """
class Meta: class Meta:
@ -405,10 +422,11 @@ class Unavailability(Markdownizable, Temporizable):
class Training(models.Model): class Training(models.Model):
""" """Classe représentant les entraînements.
Classe représentant les entraînements. Un entraînement est une occurence
d'un cours auquel sont présent des gymnastes pour une date donnée. Un objet Un entraînement est une occurence d'un cours pendant lequel des gmnastes sont présents.
de cette classe lie donc un cours et un gymnaste à une date donnée.
Un objet de cette classe lie donc un cours et un gymnaste à une date donnée.
""" """
class Meta: class Meta:
@ -441,9 +459,9 @@ class Training(models.Model):
class Round(Markdownizable): class Round(Markdownizable):
""" """Classe représentant les passages des élèves lors d'un entrainement.
Classe représentant les passages des élèves lors d'un entrainement. Chaque record
représente un passage. Il est donc lié à un record de la classe `Training`. Chaque record représente un passage. Il est donc lié à un record de la classe `Training`.
""" """
class Meta: class Meta:
@ -483,8 +501,7 @@ class Round(Markdownizable):
class PlanningLine(Markdownizable): class PlanningLine(Markdownizable):
""" """Classe représentant les passages prévisionnels (incubating idea).
Classe représentant les passages prévisionnels (incubating idea).
""" """
class Meta: class Meta:

View File

View File

@ -1,12 +1,13 @@
# coding: utf-8
from datetime import datetime from datetime import datetime
from django.test import TestCase from django.test import TestCase
from .models import get_number_of_weeks_between, Season from ..models import get_number_of_weeks_between, Season
class TestUtils(TestCase): class TestUtils(TestCase):
def test_get_number_of_weeks(self): def test_get_number_of_weeks(self):
"""Evalue le nombre de semaines qu'il y a entre deux dates.
"""
x = datetime(2016, 1, 1) x = datetime(2016, 1, 1)
y = datetime(2016, 2, 5) y = datetime(2016, 2, 5)
z = datetime(2016, 2, 4) z = datetime(2016, 2, 4)

View File

@ -1,4 +1,3 @@
# coding=UTF-8
from django.urls import path, re_path from django.urls import path, re_path

View File

@ -1,4 +1,3 @@
# coding=UTF-8
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.http import ( from django.http import (
@ -65,16 +64,25 @@ def event_lookup(request):
@login_required @login_required
@require_http_methods(["GET", "POST"]) @require_http_methods(["GET", "POST"])
def event_create_or_update(request, eventid=None): def event_create_or_update(request, eventid=None):
""" """Création ou mise à jour d'un évènement.
Création ou mise à jour d'un event.
Remarks:
* Il faut éviter de cibler une URL précise.
Pour cela, il faut passer par la fonction `reverse`:
https://docs.djangoproject.com/fr/3.1/ref/urlresolvers/
Si jamais l'URL `/event` devait être modifiée, cela ne fonctionnerait plus.
* J'ai modifié l'ordre des conditions pour un peu plus de clarté.
Notamment, tu faisais une première requête pour récupérer le nom du lieu
Mais tu n'utilisais cette valeur que dans le cas d'un GET.
Dans ce cas-ci, une CBV serait intéressante, parce qu'elle découplerait
complètement le GET du POST.
""" """
if eventid:
event = get_object_or_404(Event, pk=eventid)
data = {"place_related": event.place.name}
else:
event = None
data = {}
if request.method == "POST": if request.method == "POST":
form = EventForm(request.POST, instance=event) form = EventForm(request.POST, instance=event)
@ -86,6 +94,14 @@ def event_create_or_update(request, eventid=None):
else: else:
return HttpResponseRedirect("/event/") return HttpResponseRedirect("/event/")
else: else:
data = {}
if eventid:
event = get_object_or_404(Event, pk=eventid)
data["place_related"] = event.place.name
else:
event = None
form = EventForm(instance=event, initial=data) form = EventForm(instance=event, initial=data)
context = {"form": form, "eventid": eventid} context = {"form": form, "eventid": eventid}
@ -94,8 +110,16 @@ def event_create_or_update(request, eventid=None):
@require_http_methods(["GET"]) @require_http_methods(["GET"])
def link_gymnast_to_event(request, eventid, gymnastid): def link_gymnast_to_event(request, eventid, gymnastid):
""" """Crée un lien entre un gymnaste et un évènement.
Crée un lien entre un gymnaste et un évènement.
Returns:
Si tout se passe bien, un code 200 (Success) est retourné.
Excepts:
Si une erreur se produit lors de l'association, un code HTTP 409 (Conflict) est retourné.
Remarks:
Tu ne veux pas retourner le lien qui vient d'être créé ?
""" """
try: try:
gymnast = get_object_or_404(Gymnast, pk=gymnastid) gymnast = get_object_or_404(Gymnast, pk=gymnastid)
@ -110,8 +134,14 @@ def link_gymnast_to_event(request, eventid, gymnastid):
@require_http_methods(["GET"]) @require_http_methods(["GET"])
def remove_link_between_gymnast_and_event(request, eventid, gymnastid): def remove_link_between_gymnast_and_event(request, eventid, gymnastid):
""" """Supprime le lien entre un gymnaste et un évènement.
Supprime le lien entre un gymnaste et un évènement.
Remarks:
En fait, tes fonctions `link_gymnast_to_event`
et `remove_link_between_gymnast_and_event` sont _très_ similaires.
Il faudrait sans doute mieux passer par une CBV, voire mieux: DRF ;-)
Surtout qu'ici, on gère directement des `link_between_g_and_e`, à ajouter ou supprimer.
""" """
try: try:
gymnast = get_object_or_404(Gymnast, pk=gymnastid) gymnast = get_object_or_404(Gymnast, pk=gymnastid)
@ -125,20 +155,30 @@ def remove_link_between_gymnast_and_event(request, eventid, gymnastid):
def __get_event_list(request): def __get_event_list(request):
"""Récupère une liste d'évènement.
Par défaut, la liste est triée chronologiquement - le plus ancien étant le premier élément.
Cette fonction est utilisée pour l'affichage des évènements et au niveau du calendrier.
Args:
request (HttpRequest): La requête en entrée
pattern (str?): Optionnel. Permet de spécifier un pattern à appliquer à la recherche.
"""
pattern = request.GET.get("pattern", None) pattern = request.GET.get("pattern", None)
if pattern: if pattern:
event_list = Event.objects.filter(name__icontains=pattern).order_by("datebegin") event_list = Event.objects.filter(name__icontains=pattern)
else: else:
event_list = Event.objects.all().order_by("datebegin") event_list = Event.objects.all()
return event_list
return event_list.order_by("datebegin")
@login_required @login_required
@require_http_methods(["GET"]) @require_http_methods(["GET"])
def calendar(request): def calendar(request):
""" """Récupère la liste de tous évènements suivant un pattern si celui-ci est définit.
Récupère la liste de tous évènements suivant un pattern si celui-ci est
définit.
""" """
event_list = __get_event_list(request) event_list = __get_event_list(request)
context = {"event_list": event_list} context = {"event_list": event_list}
@ -217,7 +257,7 @@ def event_detail(request, eventid):
@require_http_methods(["GET"]) @require_http_methods(["GET"])
def course_detail(request, courseid, date=None): def course_detail(request, courseid, date=None):
""" """
Récupère toutes les informations d'un course. Récupère toutes les informations d'un cours.
:return: une instance de la classe `Course`, une liste de gymnaste associés :return: une instance de la classe `Course`, une liste de gymnaste associés
à ce cours, à ce cours,
@ -1239,8 +1279,10 @@ def program_date_listing(request, gymnastid=None):
@login_required @login_required
@require_http_methods(["GET", "POST"]) @require_http_methods(["GET", "POST"])
def planningline_update(request, planninglineid=None): def planningline_update(request, planninglineid=None):
""" """Mise à jour d'une ligne de programme.
Mise à jour d'une ligne de programme.
Remarks:
Pourquoi ne pas juste faire un `form.save()` plutôt que de réattribuer chaque valeur ?
""" """
planningline = get_object_or_404(PlanningLine, pk=planninglineid) planningline = get_object_or_404(PlanningLine, pk=planninglineid)