[WIP] refactoring/location-club-statistics #71
|
@ -0,0 +1,13 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
.PHONY: coverage errors
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
cd src/; coverage run --source='.' manage.py test; coverage report -m; cd ..
|
||||||
|
@echo "Testing of coverage in the sources finished."
|
||||||
|
|
||||||
|
lint:
|
||||||
|
pylint src/* --disable=django-not-configured
|
||||||
|
|
||||||
|
errors:
|
||||||
|
pylint src/* --errors-only --disable=django-not-configured
|
|
@ -0,0 +1,16 @@
|
||||||
|
[run]
|
||||||
|
branch = true
|
||||||
|
omit =
|
||||||
|
*/tests/*
|
||||||
|
*/test*
|
||||||
|
*/migrations/*
|
||||||
|
*/urls.py
|
||||||
|
*/admin.py
|
||||||
|
*/settings/*
|
||||||
|
*/wsgi.py
|
||||||
|
*/__init__.py
|
||||||
|
manage.py
|
||||||
|
source = .
|
||||||
|
|
||||||
|
[report]
|
||||||
|
show_missing = true
|
|
@ -81,23 +81,8 @@ def __getEventInfo(request):
|
||||||
.. todo:: il refuse mon 'filter' que ce soit avant ou après le 'next(5)'. Une idée ?
|
.. todo:: il refuse mon 'filter' que ce soit avant ou après le 'next(5)'. Une idée ?
|
||||||
next_event = Event.objects.filter(club__in=(request.session["clubid"], )).next(5)
|
next_event = Event.objects.filter(club__in=(request.session["clubid"], )).next(5)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# rest = 0
|
|
||||||
# counted = 0
|
|
||||||
# event_list = []
|
|
||||||
# today = pendulum.now().date()
|
|
||||||
next_event_list = Event.objects.next(5)
|
next_event_list = Event.objects.next(5)
|
||||||
|
|
||||||
# for event in next_event:
|
|
||||||
# counted = event.get_number_of_occurence_to_event(today)
|
|
||||||
# # print('pouf !')
|
|
||||||
|
|
||||||
# unavailabilities = Unavailability.objects.filter(datebegin__lte=event.datebegin.date(), dateend__gte=today)
|
|
||||||
# for unavailable in unavailabilities:
|
|
||||||
# counted -= unavailable.get_total_occurence()
|
|
||||||
|
|
||||||
# event_list.append((event, counted, int((counted/16)*100)))
|
|
||||||
|
|
||||||
return next_event_list
|
return next_event_list
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,18 +115,9 @@ def __getCourseInfo(request):
|
||||||
else:
|
else:
|
||||||
rest = int((tmp.days + 1) / 7)
|
rest = int((tmp.days + 1) / 7)
|
||||||
|
|
||||||
# # select tous les unavailables liés au cours
|
|
||||||
# unavailabilities = Unavailability.objects.filter(course=course)
|
|
||||||
# for unavailable in unavailabilities:
|
|
||||||
# tmp = unavailable.get_total_occurence()
|
|
||||||
# counted -= tmp
|
|
||||||
# rest -= tmp # si un unavailability.date < today, on soustrait quand même de rest ??? Si oui => BUG !!!!
|
|
||||||
|
|
||||||
courses_left += rest
|
courses_left += rest
|
||||||
courses_done += counted - rest
|
courses_done += counted - rest
|
||||||
|
|
||||||
# courses.append((course, counted, (counted - rest)))
|
|
||||||
|
|
||||||
return courses, courses_done, courses_left
|
return courses, courses_done, courses_left
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -51,12 +51,46 @@ class Place(models.Model):
|
||||||
return "%s (%s)" % (self.name, self.city if self.city else "?")
|
return "%s (%s)" % (self.name, self.city if self.city else "?")
|
||||||
|
|
||||||
|
|
||||||
class Club(models.Model):
|
class GymnastStatistics():
|
||||||
"""
|
def __init__(self, gymnast):
|
||||||
Représente un club. Un club est associé à un lieu. Pour faciliter les filtres,
|
self.gymnast = gymnast
|
||||||
un club peut être actif ou non.
|
self.number_of_courses = 0
|
||||||
|
self.number_of_hours_by_week = timedelta()
|
||||||
|
self.number_of_trainings = 0
|
||||||
|
self.number_of_attended_trainings = 0
|
||||||
|
self.number_of_attendance_hours = timedelta()
|
||||||
|
|
||||||
.. todo:: Un club peut avoir plusieurs salle et une salle peut-être louée par plusieurs clubs... M2M ?
|
self.number_of_absences = 0
|
||||||
|
self.number_of_attendance = 0
|
||||||
|
|
||||||
|
def add_course(self, course):
|
||||||
|
"""Ajoute le fait que ce gymnaste a assisté à un cours"""
|
||||||
|
self.number_of_courses += 1
|
||||||
|
|
||||||
|
self.number_of_attended_trainings += self.gymnast.attendances(course).count()
|
||||||
|
self.total_number_of_trainings += course.total_number_of_trainings
|
||||||
|
|
||||||
|
self.number_of_attendance_hours += self.gymnast.attendances(course).count() * course.duration
|
||||||
|
self.number_of_total_hours += course.number_of_total_hours
|
||||||
|
|
||||||
|
def percentage_of_attended_hours(self):
|
||||||
|
return int(self.number_of_attendance_hours / self.number_of_total_hours) * 100
|
||||||
|
|
||||||
|
def percentage_of_attended_trainings(self):
|
||||||
|
return int(self.number_of_attended_trainings / self.total_number_of_trainings) * 100
|
||||||
|
|
||||||
|
|
||||||
|
class Club(models.Model):
|
||||||
|
"""Représentation d'un club.
|
||||||
|
|
||||||
|
Chaque club est associé à un lieu.
|
||||||
|
|
||||||
|
Remarks:
|
||||||
|
Pour faciliter les filtres, un club peut être actif ou non.
|
||||||
|
|
||||||
|
Todo:
|
||||||
|
* Un club peut avoir plusieurs salle
|
||||||
|
* Une salle peut-être louée par plusieurs clubs... M2M ?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -73,3 +107,59 @@ class Club(models.Model):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s (à %s)" % (self.name, self.place.city if self.place.city else "?")
|
return "%s (à %s)" % (self.name, self.place.city if self.place.city else "?")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_number_of_real_occurrences(self):
|
||||||
|
"""Retourne le nombre de fois où un cours associé au club a été donné.
|
||||||
|
"""
|
||||||
|
courses = Course.objects.filter(club=self).order_by(
|
||||||
|
"iso_day_number", "hour_begin"
|
||||||
|
)
|
||||||
|
|
||||||
|
return sum([x.get_number_of_real_occurrences for x in courses])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_gymnasts(self):
|
||||||
|
gymnasts = []
|
||||||
|
|
||||||
|
courses = Course.objects.filter(club=self).order_by(
|
||||||
|
"iso_day_number", "hour_begin"
|
||||||
|
)
|
||||||
|
|
||||||
|
for course in self.courses:
|
||||||
|
gymnasts.extend(Gymnast.objects.filter(to_gym__in=course.to_subgroup.all()))
|
||||||
|
|
||||||
|
def build_statistics(self, season):
|
||||||
|
"""Construit des statistiques liées à la saison passée en paramètre
|
||||||
|
|
||||||
|
Args:
|
||||||
|
season_id (pk): L'identifiant de la saison.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Une structure (dict) reprenant les informations suivantes:
|
||||||
|
|
||||||
|
{
|
||||||
|
"total_hours_paid": <Le nombre d'heures devant être payées pour ce cours>
|
||||||
|
}
|
||||||
|
|
||||||
|
* La liste des cours associés à ce club et à la saison passée en paramètre
|
||||||
|
* Des informations statistiques concernant les gymnastes associés à chacun de ces cours
|
||||||
|
* Le nombre total de cours réellement donnés
|
||||||
|
* Le nombre d'heures réellement passées pour l'ensemble des cours
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
courses = self.courses.filter(season=season)
|
||||||
|
|
||||||
|
all_gymnasts = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
"gymnasts": all_gymnasts,
|
||||||
|
"courses": courses,
|
||||||
|
"number_of_trainings": sum([x.number_of_trainings for x in courses])
|
||||||
|
"number_of_occured_courses": sum([x.get_number_of_real_occurrences for x in courses]),
|
||||||
|
"total_hours": sum([x.total_number_of_hours for x in courses]),
|
||||||
|
"total_hours_paid": sum([x.total_number_of_paid_hours for x in courses]),
|
||||||
|
}
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
# coding=UTF-8
|
|
||||||
|
|
||||||
from .models import (
|
|
||||||
Club,
|
|
||||||
Place,
|
|
||||||
Country
|
|
||||||
)
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
# class GymnastTestCase():
|
|
||||||
def test_country_tostring():
|
|
||||||
c = Country(namefr="Belgique", iso2="56")
|
|
||||||
assert str(c) == "Belgique (56)"
|
|
||||||
|
|
||||||
def test_place_tostring():
|
|
||||||
p = Place(name="FATC", city="Lillois")
|
|
||||||
assert str(p) == "FATC (Lillois)"
|
|
||||||
|
|
||||||
def test_club_tostring():
|
|
||||||
p = Place(name="FATC", city="Lillois")
|
|
||||||
club = Club(place=p, name="FATC2")
|
|
||||||
assert str(club) == "FATC2 (à Lillois)"
|
|
|
@ -24,23 +24,14 @@ from planning.models import (
|
||||||
from people.models import Gymnast, Accident # people model
|
from people.models import Gymnast, Accident # people model
|
||||||
from .models import (
|
from .models import (
|
||||||
Club,
|
Club,
|
||||||
Place,
|
|
||||||
Country,
|
Country,
|
||||||
|
GymnastStatistics,
|
||||||
|
Place,
|
||||||
)
|
)
|
||||||
from .forms import PlaceForm
|
from .forms import PlaceForm
|
||||||
from objective.models import Skill, Routine # objective model
|
from objective.models import Skill, Routine # objective model
|
||||||
|
|
||||||
|
|
||||||
def __diffTime(end, start):
|
|
||||||
"""
|
|
||||||
Prend deux `datetime.time` en paramètre et calcul la différence entre les deux.
|
|
||||||
"""
|
|
||||||
startdate = datetime(2000, 1, 1, start.hour, start.minute)
|
|
||||||
enddate = datetime(2000, 1, 1, end.hour, end.minute)
|
|
||||||
|
|
||||||
return enddate - startdate
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@require_http_methods(["GET"])
|
@require_http_methods(["GET"])
|
||||||
def place_lookup(request):
|
def place_lookup(request):
|
||||||
|
@ -153,11 +144,133 @@ def chooseStatistics(request):
|
||||||
return render(request, "club_statistics.html", context)
|
return render(request, "club_statistics.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def club_statistics_new(request, clubid):
|
||||||
|
"""Construit les statistiques d'un club, pour une saison choisie
|
||||||
|
|
||||||
|
Questions:
|
||||||
|
Tu dis que cela construit les stats d'un club __pour une saison__...
|
||||||
|
Mais je ne vois pas la saison dans les paramètres...
|
||||||
|
|
||||||
|
Todo:
|
||||||
|
* Tenir compte de la saison.
|
||||||
|
"""
|
||||||
|
club = Club.objects.get(pk=clubid)
|
||||||
|
courses = Course.objects.filter(club__in=clubid).order_by(
|
||||||
|
"iso_day_number", "hour_begin"
|
||||||
|
)
|
||||||
|
|
||||||
|
total_hours = 0
|
||||||
|
totalHoursByWeek = 0
|
||||||
|
totalHoursPaid = 0
|
||||||
|
gymnastsDict = {}
|
||||||
|
gymnasts = []
|
||||||
|
courseList = []
|
||||||
|
|
||||||
|
for course in courses:
|
||||||
|
list_of_gymnasts = Gymnast.objects.filter(to_gym__in=course.to_subgroup.all())
|
||||||
|
gymnasts.extend(list_of_gymnasts)
|
||||||
|
number_of_gymnasts = len(list_of_gymnasts)
|
||||||
|
|
||||||
|
number_of_course_hours = course.number_of_hours
|
||||||
|
totalHoursByWeek += number_of_course_hours.seconds
|
||||||
|
|
||||||
|
counted = course.get_number_of_real_occurrences
|
||||||
|
|
||||||
|
totalTimeForCourse = number_of_course_hours * counted # timedelta
|
||||||
|
totalHourForCourse = (totalTimeForCourse.days * 24) + (
|
||||||
|
totalTimeForCourse.seconds / 3600
|
||||||
|
)
|
||||||
|
totalHours += totalHourForCourse
|
||||||
|
totalHoursPaidForCourse = totalHourForCourse * course.number_of_trainers
|
||||||
|
totalHoursPaid += totalHoursPaidForCourse
|
||||||
|
|
||||||
|
hour = number_of_course_hours.seconds / 3600
|
||||||
|
|
||||||
|
courseList.append(
|
||||||
|
(
|
||||||
|
course,
|
||||||
|
course.number_of_trainers,
|
||||||
|
number_of_gymnasts,
|
||||||
|
hour,
|
||||||
|
course.get_number_of_real_occurrences,
|
||||||
|
totalHourForCourse,
|
||||||
|
totalHoursPaidForCourse,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for gymnast in list_of_gymnasts:
|
||||||
|
|
||||||
|
gymnast_stats = gymnastsDict.setdefault(gymnast.id, GymnastStatistics(gymnast))
|
||||||
|
|
||||||
|
attendance_list = Training.objects.filter(course=course, gymnast=gymnast)
|
||||||
|
number_of_attendance = attendance_list.count()
|
||||||
|
|
||||||
|
gymnast_stats.number_of_courses_by_week += 1
|
||||||
|
gymnast_stats.number_of_hours_by_week += nbhour
|
||||||
|
gymnast_stats.number_of_trainings += counted
|
||||||
|
gymnast_stats.number_of_attendance += number_of_attendance
|
||||||
|
gymnast_stats.number_of_training_hours += totalHourForCourse
|
||||||
|
gymnast_stats.number_of_attendance_hours += (
|
||||||
|
nbhour * number_of_attendance
|
||||||
|
)
|
||||||
|
|
||||||
|
# tous les cours ont été traités
|
||||||
|
totalHoursByWeek = totalHoursByWeek / 3600
|
||||||
|
|
||||||
|
gymnasts = set(gymnasts)
|
||||||
|
|
||||||
|
for gymnast in gymnasts:
|
||||||
|
tmp = int(gymnastsDict[gymnast.id]["nbhourbyweek"].seconds / 3600)
|
||||||
|
gymnastsDict[gymnast.id]["nbhourbyweek"] = "%d:%02d" % (
|
||||||
|
tmp,
|
||||||
|
(gymnastsDict[gymnast.id]["nbhourbyweek"].seconds - (tmp * 3600)) / 60,
|
||||||
|
)
|
||||||
|
|
||||||
|
gymnastsDict[gymnast.id]["nbabsence"] = (
|
||||||
|
gymnastsDict[gymnast.id]["nbtraining"]
|
||||||
|
- gymnastsDict[gymnast.id]["nbattendance"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# tmp = (gymnastsDict[gymnast.id]['nbhourattendance'].days * 24) + (gymnastsDict[gymnast.id]['nbhourattendance'].seconds/3600)
|
||||||
|
gymnastsDict[gymnast.id]["nbhourattendance"] = (
|
||||||
|
gymnastsDict[gymnast.id]["nbhourattendance"].days * 24
|
||||||
|
) + (gymnastsDict[gymnast.id]["nbhourattendance"].seconds / 3600)
|
||||||
|
|
||||||
|
gymnastsDict[gymnast.id]["nbhourabsence"] = (
|
||||||
|
gymnastsDict[gymnast.id]["nbhourtraining"]
|
||||||
|
- gymnastsDict[gymnast.id]["nbhourattendance"]
|
||||||
|
)
|
||||||
|
|
||||||
|
gymnastsDict[gymnast.id]["percentageattendance"] = int(
|
||||||
|
(
|
||||||
|
gymnastsDict[gymnast.id]["nbhourattendance"]
|
||||||
|
/ gymnastsDict[gymnast.id]["nbhourtraining"]
|
||||||
|
)
|
||||||
|
* 100
|
||||||
|
)
|
||||||
|
gymnastsDict[gymnast.id]["percentageabsence"] = int(
|
||||||
|
(
|
||||||
|
gymnastsDict[gymnast.id]["nbhourabsence"]
|
||||||
|
/ gymnastsDict[gymnast.id]["nbhourtraining"]
|
||||||
|
)
|
||||||
|
* 100
|
||||||
|
)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"courses": courseList,
|
||||||
|
"gymnasts": gymnastsDict,
|
||||||
|
"totalHoursByWeek": totalHoursByWeek,
|
||||||
|
"totalCourses": club.get_number_of_real_occurrences,
|
||||||
|
"totalHours": totalHours,
|
||||||
|
"totalHoursPaid": totalHoursPaid,
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def club_statistics(request, clubid):
|
def club_statistics(request, clubid):
|
||||||
"""
|
"""
|
||||||
Renvoie les statistiques d'un club pour une saison choisie.
|
Renvoie les statistiques d'un club pour une saison choisie.
|
||||||
|
|
||||||
.. todo:: tenir compte de la saison.
|
.. todo:: tenir compte de la saison.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -172,8 +285,9 @@ def club_statistics(request, clubid):
|
||||||
gymnastsDict = {}
|
gymnastsDict = {}
|
||||||
gymnasts = []
|
gymnasts = []
|
||||||
courseList = []
|
courseList = []
|
||||||
|
|
||||||
for course in courses:
|
for course in courses:
|
||||||
nbtrainer = course.trainers.count()
|
|
||||||
list_of_gymnasts = Gymnast.objects.filter(to_gym__in=course.to_subgroup.all())
|
list_of_gymnasts = Gymnast.objects.filter(to_gym__in=course.to_subgroup.all())
|
||||||
gymnasts.extend(list_of_gymnasts)
|
gymnasts.extend(list_of_gymnasts)
|
||||||
nbgymnast = len(list_of_gymnasts)
|
nbgymnast = len(list_of_gymnasts)
|
||||||
|
@ -182,38 +296,8 @@ def club_statistics(request, clubid):
|
||||||
nbhour = __diffTime(course.hour_end, course.hour_begin) # timedelta
|
nbhour = __diffTime(course.hour_end, course.hour_begin) # timedelta
|
||||||
totalHoursByWeek += nbhour.seconds
|
totalHoursByWeek += nbhour.seconds
|
||||||
|
|
||||||
counted = course.get_total_occurence()
|
|
||||||
|
|
||||||
# select tous les unavailables liés au cours
|
|
||||||
unavailabilities = Unavailability.objects.filter(course=course)
|
|
||||||
for unavailable in unavailabilities:
|
|
||||||
counted -= unavailable.get_total_occurence()
|
|
||||||
|
|
||||||
totalCourses += counted
|
|
||||||
totalTimeForCourse = nbhour * counted # timedelta
|
|
||||||
totalHourForCourse = (totalTimeForCourse.days * 24) + (
|
|
||||||
totalTimeForCourse.seconds / 3600
|
|
||||||
)
|
|
||||||
totalHours += totalHourForCourse
|
|
||||||
totalHoursPaidForCourse = totalHourForCourse * nbtrainer
|
|
||||||
totalHoursPaid += totalHoursPaidForCourse
|
|
||||||
|
|
||||||
# tmp = int(nbhour.seconds/3600)
|
|
||||||
# hour = "%d:%02d" % (tmp, (nbhour.seconds - (tmp * 3600)) / 60)
|
|
||||||
hour = nbhour.seconds / 3600
|
hour = nbhour.seconds / 3600
|
||||||
|
|
||||||
courseList.append(
|
|
||||||
(
|
|
||||||
course,
|
|
||||||
nbtrainer,
|
|
||||||
nbgymnast,
|
|
||||||
hour,
|
|
||||||
counted,
|
|
||||||
totalHourForCourse,
|
|
||||||
totalHoursPaidForCourse,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for gymnast in list_of_gymnasts:
|
for gymnast in list_of_gymnasts:
|
||||||
# print(gymnast)
|
# print(gymnast)
|
||||||
if gymnast.id not in gymnastsDict:
|
if gymnast.id not in gymnastsDict:
|
||||||
|
@ -236,14 +320,10 @@ def club_statistics(request, clubid):
|
||||||
|
|
||||||
# print(str(gymnast) + ' : ' + str(nbattendance) + ' for ' + str(course) )
|
# print(str(gymnast) + ' : ' + str(nbattendance) + ' for ' + str(course) )
|
||||||
|
|
||||||
gymnastsDict[gymnast.id]["nbcoursebyweek"] += 1
|
|
||||||
gymnastsDict[gymnast.id]["nbhourbyweek"] += nbhour # timedelta
|
gymnastsDict[gymnast.id]["nbhourbyweek"] += nbhour # timedelta
|
||||||
gymnastsDict[gymnast.id]["nbtraining"] += counted
|
|
||||||
gymnastsDict[gymnast.id]["nbattendance"] += nbattendance
|
gymnastsDict[gymnast.id]["nbattendance"] += nbattendance
|
||||||
gymnastsDict[gymnast.id]["nbhourtraining"] += totalHourForCourse
|
gymnastsDict[gymnast.id]["nbhourtraining"] += totalHourForCourse
|
||||||
gymnastsDict[gymnast.id]["nbhourattendance"] += (
|
|
||||||
nbhour * nbattendance
|
|
||||||
) # timedelta
|
|
||||||
|
|
||||||
# print(gymnastsDict[gymnast.id])
|
# print(gymnastsDict[gymnast.id])
|
||||||
|
|
||||||
|
@ -299,3 +379,4 @@ def club_statistics(request, clubid):
|
||||||
"totalHoursPaid": totalHoursPaid,
|
"totalHoursPaid": totalHoursPaid,
|
||||||
}
|
}
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
|
@ -62,8 +62,7 @@ class Gymnast(Markdownizable):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Gymnast"
|
verbose_name = "gymnaste"
|
||||||
verbose_name_plural = "Gymnasts"
|
|
||||||
ordering = ["user__last_name", "user__first_name"]
|
ordering = ["user__last_name", "user__first_name"]
|
||||||
|
|
||||||
GENDER_CHOICES = ((0, "Male"), (1, "Female"))
|
GENDER_CHOICES = ((0, "Male"), (1, "Female"))
|
||||||
|
@ -189,6 +188,12 @@ class Gymnast(Markdownizable):
|
||||||
period = pendulum.now() - pendulum.instance(self.created_at, "Europe/Brussels")
|
period = pendulum.now() - pendulum.instance(self.created_at, "Europe/Brussels")
|
||||||
return int(self.year_of_practice + period.in_years())
|
return int(self.year_of_practice + period.in_years())
|
||||||
|
|
||||||
|
def attendances(self, course):
|
||||||
|
"""Retourne les séances d'un cours où le gymnaste était présent
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.trainings.filter(course=course)
|
||||||
|
|
||||||
|
|
||||||
class Accident(Markdownizable):
|
class Accident(Markdownizable):
|
||||||
"""La classe `Accident` permet d'indiquer qu'un gymnaste est tombé durant un saut.
|
"""La classe `Accident` permet d'indiquer qu'un gymnaste est tombé durant un saut.
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
"""Tests liés aux personnes, aux gymnastes, ..."""
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from location.models import Club, Country, Place
|
||||||
|
from planning.models import Course, Training
|
||||||
|
|
||||||
|
from ..models import Gymnast
|
||||||
|
|
||||||
|
|
||||||
|
USER_MODEL = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class TestGymnast(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user1 = USER_MODEL.objects.create(username="james_bond", last_name="Bond", first_name="James")
|
||||||
|
self.club = Club.objects.create(
|
||||||
|
name="RCW",
|
||||||
|
place=Place.objects.create(
|
||||||
|
name="Somewhere",
|
||||||
|
postal=1080,
|
||||||
|
country=Country.objects.create(
|
||||||
|
namefr="Belgique",
|
||||||
|
isonum=56
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.course = Course.objects.create(
|
||||||
|
iso_day_number=2,
|
||||||
|
datebegin="2022-01-01",
|
||||||
|
dateend="2022-10-20",
|
||||||
|
hour_begin="08:30",
|
||||||
|
hour_end="20:00",
|
||||||
|
club=self.club
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_gymnast_should_return_lastname_and_firstname(self):
|
||||||
|
gymnast = Gymnast(user=self.user1)
|
||||||
|
self.assertEqual(str(gymnast), "Bond, James")
|
||||||
|
|
||||||
|
def test_gymnast_attendances_should_be_zero(self):
|
||||||
|
"""Vérifie qu'un gymnaste n'assiste par défaut à aucun entrainement"""
|
||||||
|
gymnast = Gymnast.objects.create(
|
||||||
|
user=self.user1,
|
||||||
|
birthdate="1980-01-01",
|
||||||
|
gender=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(gymnast.attendances(self.course).count(), 0)
|
||||||
|
|
||||||
|
def test_attendances_should_be_equals_two(self):
|
||||||
|
"""Vérifie que les entrainements auxquels le gymnaste a assisté sont bien pris en compte"""
|
||||||
|
gymnast = Gymnast.objects.create(
|
||||||
|
user=self.user1,
|
||||||
|
birthdate="1980-01-01",
|
||||||
|
gender=0,
|
||||||
|
)
|
||||||
|
Training.objects.create(gymnast=gymnast, course=self.course, trainingdate="2021-02-01")
|
||||||
|
|
||||||
|
self.assertEqual(gymnast.attendances(self.course).count(), 1)
|
||||||
|
|
||||||
|
Training.objects.create(gymnast=gymnast, course=self.course, trainingdate="2021-02-02")
|
||||||
|
|
||||||
|
self.assertEqual(gymnast.attendances(self.course).count(), 2)
|
|
@ -5,22 +5,6 @@ from .models import Gymnast, Accident
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
# class GymnastTestCase():
|
|
||||||
def test_gymnast_tostring():
|
|
||||||
g = Gymnast(lastname="Pauchou", firstname="Fred")
|
|
||||||
assert str(g) == "Pauchou, Fred"
|
|
||||||
|
|
||||||
def test_gymnaste_get_age():
|
|
||||||
g = Gymnast(lastname="Pauchou", firstname="Fred", birthdate=datetime.strptime('03/07/1985', '%d/%m/%Y'));
|
|
||||||
assert g.age == 35
|
|
||||||
|
|
||||||
def test_gymnaste_get_next_age():
|
|
||||||
g = Gymnast(lastname="Pauchou", firstname="Fred", birthdate=datetime.strptime('03/07/1985', '%d/%m/%Y'));
|
|
||||||
assert g.next_age == 36
|
|
||||||
|
|
||||||
def test_gymnaste_next_birthday():
|
|
||||||
g = Gymnast(lastname="Pauchou", firstname="Fred", birthdate=datetime.strptime('03/07/1985', '%d/%m/%Y'));
|
|
||||||
assert g.next_birthday == datetime.strptime('03/07/2021', '%d/%m/%Y')
|
|
||||||
|
|
||||||
def test_gymnast_known_skills():
|
def test_gymnast_known_skills():
|
||||||
# @Fred : Comment tester cela ?
|
# @Fred : Comment tester cela ?
|
||||||
|
|
|
@ -11,498 +11,572 @@ from base.models import Markdownizable
|
||||||
from location.models import Club
|
from location.models import Club
|
||||||
from people.models import Gymnast
|
from people.models import Gymnast
|
||||||
|
|
||||||
|
from .utils import time_diff
|
||||||
|
|
||||||
|
|
||||||
def get_week(a_date):
|
def get_week(a_date):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Remarks:
|
Remarks:
|
||||||
Je ne comprends pas trop cette fonction...
|
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 ?
|
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()
|
||||||
monday = the_date - timedelta(days=day)
|
monday = the_date - timedelta(days=day)
|
||||||
sunday = the_date + timedelta(days=(6 - day))
|
sunday = the_date + timedelta(days=(6 - day))
|
||||||
return monday, sunday
|
return monday, sunday
|
||||||
|
|
||||||
|
|
||||||
def get_number_of_weeks_between(start, stop):
|
def get_number_of_weeks_between(start, stop):
|
||||||
"""
|
"""
|
||||||
Renvoie le nombre de semaines entre deux dates.
|
Renvoie le nombre de semaines entre deux dates.
|
||||||
Par extension, cela permet de connaitre le nombre d'occurence d'un
|
Par extension, cela permet de connaitre le nombre d'occurence d'un
|
||||||
évènement (entraînement, par exemple) hebdromadaire entre deux dates
|
évènement (entraînement, par exemple) hebdromadaire entre deux dates
|
||||||
et ainsi pouvoir plannifier.
|
et ainsi pouvoir plannifier.
|
||||||
|
|
||||||
:param start: date de début de la période
|
:param start: date de début de la période
|
||||||
:type start: datetime.date
|
:type start: datetime.date
|
||||||
: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:
|
Remarks:
|
||||||
Proposition d'utiliser isocalendar() sur une date.
|
Proposition d'utiliser isocalendar() sur une date.
|
||||||
L'indice 1 de la valeur de retour donne la semaine correspondant.
|
L'indice 1 de la valeur de retour donne la semaine correspondant.
|
||||||
|
|
||||||
Eg.
|
Eg.
|
||||||
>>> from datetime import date
|
>>> from datetime import date
|
||||||
>>> d = date(2020, 9, 27)
|
>>> d = date(2020, 9, 27)
|
||||||
>>> d.isocalendar()
|
>>> d.isocalendar()
|
||||||
(2020, 39, 7)
|
(2020, 39, 7)
|
||||||
|
|
||||||
-> Est-ce qu'il ne suffirait pas de faire la différence ?
|
-> Est-ce qu'il ne suffirait pas de faire la différence ?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tmp = stop - start
|
tmp = stop - start
|
||||||
number_of_days = abs(tmp.days)
|
number_of_days = abs(tmp.days)
|
||||||
number_of_week = int((number_of_days + 1) / 7)
|
number_of_week = int((number_of_days + 1) / 7)
|
||||||
|
|
||||||
if ((number_of_days + 1) % 7) > 0:
|
if ((number_of_days + 1) % 7) > 0:
|
||||||
number_of_week += 1
|
number_of_week += 1
|
||||||
|
|
||||||
if tmp.days < 0:
|
if tmp.days < 0:
|
||||||
number_of_week *= -1
|
number_of_week *= -1
|
||||||
|
|
||||||
return number_of_week
|
return number_of_week
|
||||||
|
|
||||||
|
|
||||||
class TemporizableQuerySet(models.QuerySet):
|
class TemporizableQuerySet(models.QuerySet):
|
||||||
"""
|
"""
|
||||||
Classe permettant de spécifier le `QuerySet` de la classe `Temporizable`.
|
Classe permettant de spécifier le `QuerySet` de la classe `Temporizable`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def next(self, limit):
|
def next(self, limit):
|
||||||
"""
|
"""
|
||||||
Renvoie la liste des prochains "temporizable" (par rapport à la date du jour).
|
Renvoie la liste des prochains "temporizable" (par rapport à la date du jour).
|
||||||
|
|
||||||
:param limit: nombre d'éléments désirés.
|
:param limit: nombre d'éléments désirés.
|
||||||
:type limit: int
|
:type limit: int
|
||||||
:return: une liste de `limit` éléments temporizables.
|
:return: une liste de `limit` éléments temporizables.
|
||||||
"""
|
"""
|
||||||
return self.filter(datebegin__gte=timezone.now()).order_by("datebegin")[0:limit]
|
return self.filter(datebegin__gte=timezone.now()).order_by("datebegin")[0:limit]
|
||||||
|
|
||||||
def last(self, limit):
|
def last(self, limit):
|
||||||
"""
|
"""
|
||||||
Renvoie la liste des derniers "temporizable" (par rapport à la date du jour).
|
Renvoie la liste des derniers "temporizable" (par rapport à la date du jour).
|
||||||
|
|
||||||
:param limit: nombre d'éléments désirés.
|
:param limit: nombre d'éléments désirés.
|
||||||
:type limit: int
|
:type limit: int
|
||||||
:return: une liste de `limit` éléments temporizables
|
:return: une liste de `limit` éléments temporizables
|
||||||
"""
|
"""
|
||||||
return self.filter(dateend__lte=timezone.now()).order_by("-dateend")[0:limit]
|
return self.filter(dateend__lte=timezone.now()).order_by("-dateend")[0:limit]
|
||||||
|
|
||||||
# def get(self, date_string):
|
# def get(self, date_string):
|
||||||
# """
|
# """
|
||||||
# """
|
# """
|
||||||
# try:
|
# try:
|
||||||
# selected_object = self.get(datebegin__lte=date_string, dateend__gte=date_string)
|
# selected_object = self.get(datebegin__lte=date_string, dateend__gte=date_string)
|
||||||
# except self.DoesNotExist:
|
# except self.DoesNotExist:
|
||||||
# return None
|
# return None
|
||||||
# except self.MultipleObjectsReturned:
|
# except self.MultipleObjectsReturned:
|
||||||
# return None
|
# return None
|
||||||
|
|
||||||
# return selected_object
|
# return selected_object
|
||||||
|
|
||||||
|
|
||||||
class Temporizable(models.Model):
|
class Temporizable(models.Model):
|
||||||
"""Classe abstraite définissant une période comprise entre deux dates.
|
"""Classe abstraite définissant une période comprise entre deux dates.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
datebegin = models.DateTimeField(verbose_name="Début")
|
datebegin = models.DateTimeField(verbose_name="Début")
|
||||||
dateend = models.DateTimeField(blank=True, verbose_name="Fin")
|
dateend = models.DateTimeField(blank=True, verbose_name="Fin")
|
||||||
|
|
||||||
objects = models.Manager.from_queryset(TemporizableQuerySet)()
|
objects = models.Manager.from_queryset(TemporizableQuerySet)()
|
||||||
|
|
||||||
def get_total_occurence(self):
|
def get_total_occurence(self):
|
||||||
"""
|
"""
|
||||||
Renvoie le nombre de semaines entre les deux dates d'une instance de la
|
Renvoie le nombre de semaines entre les deux dates d'une instance de la
|
||||||
classe `Temporizable`.
|
classe `Temporizable`.
|
||||||
|
|
||||||
:return: nombre de semaines.
|
:return: nombre de semaines.
|
||||||
"""
|
"""
|
||||||
return get_number_of_weeks_between(self.datebegin.date(), self.dateend.date())
|
return get_number_of_weeks_between(self.datebegin.date(), self.dateend.date())
|
||||||
|
|
||||||
def get_number_of_occurence_to_event(self, the_date):
|
def get_number_of_occurence_to_event(self, the_date):
|
||||||
"""
|
"""
|
||||||
Renvoie le nombre semaines entre une date choisie et le début
|
Renvoie le nombre semaines entre une date choisie et le début
|
||||||
(datebegin) d'une instance de la classe `Temporizable`.
|
(datebegin) d'une instance de la classe `Temporizable`.
|
||||||
|
|
||||||
:param the_date: date par rapport à laquelle le calcul sera fait.
|
:param the_date: date par rapport à laquelle le calcul sera fait.
|
||||||
:type the_date: datetime.date
|
:type the_date: datetime.date
|
||||||
:return: nombre de semaines.
|
:return: nombre de semaines.
|
||||||
"""
|
"""
|
||||||
return get_number_of_weeks_between(the_date, self.datebegin.date())
|
return get_number_of_weeks_between(the_date, self.datebegin.date())
|
||||||
|
|
||||||
def get_number_of_occurence_inbetween(self, the_date, rest=True):
|
def get_number_of_occurence_inbetween(self, the_date, rest=True):
|
||||||
"""
|
"""
|
||||||
Renvoie le nombre semaines entre une date choisie et une instance de la
|
Renvoie le nombre semaines entre une date choisie et une instance de la
|
||||||
classe `Temporizable`. Le calcul peut se faire soit entre la date
|
classe `Temporizable`. Le calcul peut se faire soit entre la date
|
||||||
choisie et le date de fin d'une occurence de la classe, soit entre la
|
choisie et le date de fin d'une occurence de la classe, soit entre la
|
||||||
date de début d'une occurence de la classe et la date choisie.
|
date de début d'une occurence de la classe et la date choisie.
|
||||||
|
|
||||||
:param the_date: date par rapport à laquelle le calcul sera fait.
|
:param the_date: date par rapport à laquelle le calcul sera fait.
|
||||||
:type the_date: datetime.date
|
:type the_date: datetime.date
|
||||||
:param rest: paramètre définissant s'il faut calculer le reste des
|
:param rest: paramètre définissant s'il faut calculer le reste des
|
||||||
occurences à venir (depuis `the_date` jusqu'à la date de fin) ou
|
occurences à venir (depuis `the_date` jusqu'à la date de fin) ou
|
||||||
les occurences déjà passées (depuis la date de début jusqu'à
|
les occurences déjà passées (depuis la date de début jusqu'à
|
||||||
`the_date`)
|
`the_date`)
|
||||||
:type rest: booléen
|
:type rest: booléen
|
||||||
:return: nombre de semaines.
|
:return: nombre de semaines.
|
||||||
"""
|
"""
|
||||||
if rest:
|
if rest:
|
||||||
return get_number_of_weeks_between(the_date, self.dateend.date())
|
return get_number_of_weeks_between(the_date, self.dateend.date())
|
||||||
else:
|
else:
|
||||||
return get_number_of_weeks_between(self.datebegin.date(), the_date)
|
return get_number_of_weeks_between(self.datebegin.date(), the_date)
|
||||||
|
|
||||||
|
|
||||||
class Season(Temporizable):
|
class Season(Temporizable):
|
||||||
"""
|
"""
|
||||||
Classe représentant une saison. Une saison est déinie par :
|
Classe représentant une saison. Une saison est déinie par :
|
||||||
- un id,
|
- un id,
|
||||||
- un label,
|
- un label,
|
||||||
- une date de début et
|
- une date de début et
|
||||||
- une date de fin.
|
- une date de fin.
|
||||||
|
|
||||||
La date de début est très souvent le : 01/09/xxxx
|
La date de début est très souvent le : 01/09/xxxx
|
||||||
La date de fin est très souvent le : 31/08/xxxy
|
La date de fin est très souvent le : 31/08/xxxy
|
||||||
exemple : 1/9/2015 - 31/8/2016
|
exemple : 1/9/2015 - 31/8/2016
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Season"
|
verbose_name = "Season"
|
||||||
verbose_name_plural = "Seasons"
|
verbose_name_plural = "Seasons"
|
||||||
|
|
||||||
label = models.CharField(max_length=11, verbose_name="Label")
|
label = models.CharField(max_length=11, verbose_name="Label")
|
||||||
# active ou default = models.BooleanField(verbose_name='Défaut')
|
# active ou default = models.BooleanField(verbose_name='Défaut')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s" % (self.label)
|
return "%s" % (self.label)
|
||||||
|
|
||||||
def week_number_from_begin(self, target_date):
|
def week_number_from_begin(self, target_date):
|
||||||
return get_number_of_weeks_between(self.datebegin.date(), target_date)
|
return get_number_of_weeks_between(self.datebegin.date(), target_date)
|
||||||
|
|
||||||
|
|
||||||
class EventType(models.Model):
|
class EventType(models.Model):
|
||||||
"""
|
"""
|
||||||
Classe représentant les types d'évènements.
|
Classe représentant les types d'évènements.
|
||||||
C'est un dictionnaire fini :
|
C'est un dictionnaire fini :
|
||||||
- compétiton qualificative,
|
- compétiton qualificative,
|
||||||
- compétition finale,
|
- compétition finale,
|
||||||
- démonstration,
|
- démonstration,
|
||||||
- …
|
- …
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Event Type"
|
verbose_name = "Event Type"
|
||||||
verbose_name_plural = "Event Types"
|
verbose_name_plural = "Event Types"
|
||||||
|
|
||||||
name = models.CharField(max_length=255, verbose_name="Nom")
|
name = models.CharField(max_length=255, verbose_name="Nom")
|
||||||
acronym = models.CharField(max_length=5, verbose_name="Acronyme")
|
acronym = models.CharField(max_length=5, verbose_name="Acronyme")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s (%s)" % (self.name, self.acronym)
|
return "%s (%s)" % (self.name, self.acronym)
|
||||||
|
|
||||||
|
|
||||||
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 évènement est caractérisé par :
|
||||||
* un nom,
|
* un nom,
|
||||||
* un lieu (place),
|
* un lieu (place),
|
||||||
* un type (compétition, démonstration, …),
|
* un type (compétition, démonstration, …),
|
||||||
* des gymnastes (participation prévue).
|
* des gymnastes (participation prévue).
|
||||||
Je ne me rapelle plus à quoi sert le club.
|
Je ne me rapelle plus à quoi sert le club.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Event"
|
verbose_name = "Event"
|
||||||
verbose_name_plural = "Event"
|
verbose_name_plural = "Event"
|
||||||
|
|
||||||
place = models.ForeignKey(
|
place = models.ForeignKey(
|
||||||
"location.Place", verbose_name="Lieu", on_delete=models.CASCADE, default=None
|
"location.Place", verbose_name="Lieu", on_delete=models.CASCADE, default=None
|
||||||
)
|
)
|
||||||
eventtype = models.ForeignKey(
|
eventtype = models.ForeignKey(
|
||||||
EventType, verbose_name="Type", on_delete=models.CASCADE, default=None
|
EventType, verbose_name="Type", on_delete=models.CASCADE, default=None
|
||||||
)
|
)
|
||||||
name = models.CharField(max_length=255, verbose_name="Nom")
|
name = models.CharField(max_length=255, verbose_name="Nom")
|
||||||
# club = models.ManyToManyField('location.Club', related_name="concernate_by", blank=True)
|
# club = models.ManyToManyField('location.Club', related_name="concernate_by", blank=True)
|
||||||
gymnasts = models.ManyToManyField(
|
gymnasts = models.ManyToManyField(
|
||||||
"people.Gymnast",
|
"people.Gymnast",
|
||||||
through="Event_Participation",
|
through="Event_Participation",
|
||||||
related_name="participate_to",
|
related_name="participate_to",
|
||||||
verbose_name="Participants",
|
verbose_name="Participants",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s (à %s)" % (self.name, self.place.city)
|
return "%s (à %s)" % (self.name, self.place.city)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.checkdates()
|
self.checkdates()
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def checkdates(self):
|
def checkdates(self):
|
||||||
"""
|
"""
|
||||||
Fonction assignant la date de fin d'un évènement à la date de début, si la date
|
Fonction assignant la date de fin d'un évènement à la date de début, si la date
|
||||||
de fin n'est pas définie, l'heure de fin est par défaut 18h00.
|
de fin n'est pas définie, l'heure de fin est par défaut 18h00.
|
||||||
"""
|
"""
|
||||||
if self.dateend is None and self.datebegin is not None:
|
if self.dateend is None and self.datebegin is not None:
|
||||||
self.dateend = datetime.combine(self.datebegin.date(), time(18, 0))
|
self.dateend = datetime.combine(self.datebegin.date(), time(18, 0))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def number_of_week_from_today(self):
|
def number_of_week_from_today(self):
|
||||||
today = pendulum.now().date()
|
today = pendulum.now().date()
|
||||||
return get_number_of_weeks_between(today, self.datebegin.date())
|
return get_number_of_weeks_between(today, self.datebegin.date())
|
||||||
|
|
||||||
|
|
||||||
class Event_Participation(models.Model):
|
class Event_Participation(models.Model):
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
|
|
||||||
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||||
gymnast = models.ForeignKey("people.Gymnast", on_delete=models.CASCADE)
|
gymnast = models.ForeignKey("people.Gymnast", on_delete=models.CASCADE)
|
||||||
rank = models.PositiveSmallIntegerField(default=0)
|
rank = models.PositiveSmallIntegerField(default=0)
|
||||||
|
|
||||||
|
|
||||||
class Course(Markdownizable, Temporizable):
|
class Course(Markdownizable, Temporizable):
|
||||||
"""Classe représentant les cours.
|
"""Classe représentant les cours.
|
||||||
|
|
||||||
Un cours est défini par :
|
Un cours est défini par :
|
||||||
* une heure de début et une heure de fin,
|
* 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
|
* une date de début et une date de fin
|
||||||
ces deux dates) (hérite de la classe `Temporizable`)
|
|
||||||
* est associé à un ou plusieurs entraineurs,
|
|
||||||
* est associé à un club
|
|
||||||
* est associé à un jour de la semaine (numéro du jour dans la semaine : 0 = lundi, 6 = dimanche).
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Meta:
|
Il est considéré comme donné hebdomadairement entre deux dates
|
||||||
verbose_name = "Course"
|
|
||||||
verbose_name_plural = "Courses"
|
|
||||||
|
|
||||||
DAY_CHOICE = (
|
* est associé à un ou plusieurs entraineurs,
|
||||||
(1, "Lundi"),
|
* est associé à un club
|
||||||
(2, "Mardi"),
|
* est associé à un jour de la semaine (numéro du jour dans la semaine : 0 = lundi, 6 = dimanche).
|
||||||
(3, "Mercredi"),
|
|
||||||
(4, "Jeudi"),
|
|
||||||
(5, "Vendredi"),
|
|
||||||
(6, "Samedi"),
|
|
||||||
(7, "Dimanche"),
|
|
||||||
)
|
|
||||||
|
|
||||||
club = models.ForeignKey(
|
Remarks:
|
||||||
"location.Club", verbose_name="Club", on_delete=models.CASCADE, default=None
|
Les cours par défaut triés sur les jours et sur les heures de début.
|
||||||
)
|
"""
|
||||||
iso_day_number = models.PositiveSmallIntegerField(
|
|
||||||
choices=DAY_CHOICE, verbose_name="Jour"
|
|
||||||
)
|
|
||||||
hour_begin = models.TimeField(verbose_name="Heure de début")
|
|
||||||
hour_end = models.TimeField(verbose_name="Heure de fin")
|
|
||||||
season = models.ForeignKey(Season, on_delete=models.SET_NULL, null=True)
|
|
||||||
trainers = models.ManyToManyField(
|
|
||||||
User, verbose_name="Coach(es)", related_name="trainee"
|
|
||||||
)
|
|
||||||
gymnasts = models.ManyToManyField(
|
|
||||||
Gymnast, verbose_name="Gymnasts", related_name="courses"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
class Meta:
|
||||||
return "%s (%s à %s)" % (
|
verbose_name = "Cours"
|
||||||
self.get_iso_day_number_display(),
|
verbose_name_plural = "Cours"
|
||||||
self.hour_begin.strftime("%H:%M"),
|
ordering = ("iso_day_number", "hour_begin")
|
||||||
self.hour_end.strftime("%H:%M"),
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
DAY_CHOICE = (
|
||||||
def duration(self):
|
(1, "Lundi"),
|
||||||
"""
|
(2, "Mardi"),
|
||||||
Renvoie la durée d'un cours en heures
|
(3, "Mercredi"),
|
||||||
"""
|
(4, "Jeudi"),
|
||||||
date_begin = pendulum.datetime(
|
(5, "Vendredi"),
|
||||||
2000, 1, 1, self.hour_begin.hour, self.hour_begin.minute
|
(6, "Samedi"),
|
||||||
)
|
(7, "Dimanche"),
|
||||||
date_end = pendulum.datetime(
|
)
|
||||||
2000, 1, 1, self.hour_end.hour, self.hour_end.minute
|
|
||||||
)
|
club = models.ForeignKey(
|
||||||
return date_end.diff(date_begin).in_hours()
|
"location.Club", verbose_name="Club", on_delete=models.CASCADE, default=None, related_name="courses"
|
||||||
|
)
|
||||||
|
iso_day_number = models.PositiveSmallIntegerField(
|
||||||
|
choices=DAY_CHOICE, verbose_name="Jour"
|
||||||
|
)
|
||||||
|
hour_begin = models.TimeField(verbose_name="Heure de début")
|
||||||
|
hour_end = models.TimeField(verbose_name="Heure de fin")
|
||||||
|
season = models.ForeignKey(Season, on_delete=models.SET_NULL, null=True)
|
||||||
|
trainers = models.ManyToManyField(
|
||||||
|
User, verbose_name="Coach(es)", related_name="trainee"
|
||||||
|
)
|
||||||
|
gymnasts = models.ManyToManyField(
|
||||||
|
Gymnast, verbose_name="Gymnasts", related_name="courses"
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def number_of_trainers(self):
|
||||||
|
"""Retourne le nombre d'entraineurs liés à ce cours"""
|
||||||
|
return self.trainers.count()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def number_of_hours(self) -> timedelta:
|
||||||
|
"""Retourne le temps que dure le cours
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(datetime.timedelta) La durée du cours
|
||||||
|
"""
|
||||||
|
return time_diff(self.hour_begin, self.hour_end)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_number_of_hours(self) -> timedelta:
|
||||||
|
"""Retourne le temps total a été consacré à ce cours
|
||||||
|
"""
|
||||||
|
return self.get_number_of_real_occurrences * self.number_of_hours
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_number_of_paid_hours(self) -> timedelta:
|
||||||
|
"""Retourne le temps total consacré à ce cours par le(s) entraineur(s)
|
||||||
|
"""
|
||||||
|
return self.total_number_of_hours * self.number_of_trainers
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_number_of_planned_occurrences(self):
|
||||||
|
return self.get_total_occurence()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_number_of_trainings(self):
|
||||||
|
return Training.objects.filter(course=course).count()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_number_of_real_occurrences(self) -> int:
|
||||||
|
"""Retourne le nombre de fois où le cours a réellement été donné.
|
||||||
|
|
||||||
|
Ce résultat est calculé entre le nombre de fois où le cours était prévu,
|
||||||
|
auquel nous avons retiré les indisponibilités
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>>
|
||||||
|
"""
|
||||||
|
counted = self.get_number_of_planned_occurrences
|
||||||
|
|
||||||
|
unavailabilities = Unavailability.objects.filter(course=self)
|
||||||
|
for unavailable in unavailabilities:
|
||||||
|
counted -= unavailable.get_total_occurence()
|
||||||
|
|
||||||
|
return counted
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_real_time_spent_during_season(self):
|
||||||
|
"""Calcule le temps total dépensé pour ce cours
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.get_number_of_real_occurrences * self.duration
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_gymnasts(self):
|
||||||
|
return Gymnast.objects.filter(to_gym__in=self.to_subgroup.all())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def duration(self):
|
||||||
|
"""Renvoie la durée d'un cours en heures
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> course = Course(hour_begin=20, hour_end=22)
|
||||||
|
>>> course.duration
|
||||||
|
2
|
||||||
|
"""
|
||||||
|
date_begin = pendulum.datetime(
|
||||||
|
2000, 1, 1, self.hour_begin.hour, self.hour_begin.minute
|
||||||
|
)
|
||||||
|
date_end = pendulum.datetime(
|
||||||
|
2000, 1, 1, self.hour_end.hour, self.hour_end.minute
|
||||||
|
)
|
||||||
|
return date_end.diff(date_begin).in_hours()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s (%s à %s)" % (
|
||||||
|
self.get_iso_day_number_display(),
|
||||||
|
self.hour_begin.strftime("%H:%M"),
|
||||||
|
self.hour_end.strftime("%H:%M"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Group"
|
verbose_name = "Group"
|
||||||
verbose_name_plural = "Groups"
|
verbose_name_plural = "Groups"
|
||||||
|
|
||||||
club = models.ForeignKey("location.Club", on_delete=models.CASCADE, default=None)
|
club = models.ForeignKey("location.Club", on_delete=models.CASCADE, default=None)
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
acronym = models.CharField(max_length=50)
|
acronym = models.CharField(max_length=50)
|
||||||
active = models.BooleanField(default=1)
|
active = models.BooleanField(default=1)
|
||||||
season = models.CharField(
|
season = models.CharField(
|
||||||
max_length=9,
|
max_length=9,
|
||||||
default=str(timezone.now().year) + "-" + str(timezone.now().year + 1),
|
default=str(timezone.now().year) + "-" + str(timezone.now().year + 1),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s (%s)" % (self.name, self.acronym)
|
return "%s (%s)" % (self.name, self.acronym)
|
||||||
|
|
||||||
|
|
||||||
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).
|
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,
|
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.
|
nous pouvons connaître le(s) club(s) du gymnaste pour chaque saison.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Subgroup"
|
verbose_name = "Subgroup"
|
||||||
verbose_name_plural = "Subgroups"
|
verbose_name_plural = "Subgroups"
|
||||||
|
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
acronym = models.CharField(max_length=50)
|
acronym = models.CharField(max_length=50)
|
||||||
group = models.ForeignKey(Group, on_delete=models.CASCADE, default=None)
|
group = models.ForeignKey(Group, on_delete=models.CASCADE, default=None)
|
||||||
courses = models.ManyToManyField(Course, related_name="to_subgroup")
|
courses = models.ManyToManyField(Course, related_name="to_subgroup")
|
||||||
gymnasts = models.ManyToManyField(
|
gymnasts = models.ManyToManyField(
|
||||||
"people.Gymnast", related_name="to_gym", blank=True
|
"people.Gymnast", related_name="to_gym", blank=True
|
||||||
)
|
)
|
||||||
active = models.BooleanField(default=1)
|
active = models.BooleanField(default=1)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s (%s)" % (self.name, self.group.name)
|
return "%s (%s)" % (self.name, self.group.name)
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
return self.filter(datebegin__gte=timezone.now()).order_by("datebegin")[0:count]
|
return self.filter(datebegin__gte=timezone.now()).order_by("datebegin")[0:count]
|
||||||
|
|
||||||
def last(self, count):
|
def last(self, count):
|
||||||
return self.filter(dateend__lte=timezone.now()).order_by("-dateend")[0:count]
|
return self.filter(dateend__lte=timezone.now()).order_by("-dateend")[0:count]
|
||||||
|
|
||||||
|
|
||||||
class Unavailability(Markdownizable, Temporizable):
|
class Unavailability(Markdownizable, Temporizable):
|
||||||
"""Classe représentant les indisponibilités.
|
"""Classe représentant les indisponibilités.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Indisponibilité"
|
verbose_name = "Indisponibilité"
|
||||||
verbose_name_plural = "Indisponibilités"
|
verbose_name_plural = "Indisponibilités"
|
||||||
|
|
||||||
course = models.ManyToManyField(Course, related_name="unavailability")
|
course = models.ManyToManyField(Course, related_name="unavailability")
|
||||||
|
|
||||||
objects = UnavailabilityManager()
|
objects = UnavailabilityManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "du %s au %s" % (self.datebegin, self.dateend)
|
return "du %s au %s" % (self.datebegin, self.dateend)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.checkdates()
|
self.checkdates()
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def checkdates(self):
|
def checkdates(self):
|
||||||
if self.dateend is None and self.datebegin is not None:
|
if self.dateend is None and self.datebegin is not None:
|
||||||
self.dateend = self.datebegin
|
self.dateend = self.datebegin
|
||||||
|
|
||||||
|
|
||||||
class Training(models.Model):
|
class Training(models.Model):
|
||||||
"""Classe représentant les entraînements.
|
"""Un entraînement est une occurence d'un cours pendant lequel des gymnastes sont présents.
|
||||||
|
|
||||||
Un entraînement est une occurence d'un cours pendant lequel des gmnastes sont présents.
|
"""
|
||||||
|
|
||||||
Un objet de cette classe lie donc un cours et un gymnaste à une date donnée.
|
class Meta:
|
||||||
"""
|
verbose_name = "entraînement"
|
||||||
|
|
||||||
class Meta:
|
gymnast = models.ForeignKey(
|
||||||
verbose_name = "Training"
|
"people.Gymnast",
|
||||||
verbose_name_plural = "Trainings"
|
verbose_name="Gymnast",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
default=None,
|
||||||
|
related_name="trainings",
|
||||||
|
)
|
||||||
|
course = models.ForeignKey(
|
||||||
|
Course, verbose_name="Course", on_delete=models.CASCADE, default=None
|
||||||
|
)
|
||||||
|
trainingdate = models.DateField(verbose_name="Date")
|
||||||
|
|
||||||
gymnast = models.ForeignKey(
|
def __str__(self):
|
||||||
"people.Gymnast",
|
return "%s - %s, %s" % (self.trainingdate, self.course, self.gymnast)
|
||||||
verbose_name="Gymnast",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
default=None,
|
|
||||||
related_name="trainings",
|
|
||||||
)
|
|
||||||
course = models.ForeignKey(
|
|
||||||
Course, verbose_name="Course", on_delete=models.CASCADE, default=None
|
|
||||||
)
|
|
||||||
trainingdate = models.DateField(verbose_name="Date")
|
|
||||||
|
|
||||||
def __str__(self):
|
@staticmethod
|
||||||
return "%s - %s, %s" % (self.trainingdate, self.course, self.gymnast)
|
def create(gymnast, course, trainingdate):
|
||||||
|
t = Training()
|
||||||
@staticmethod
|
t.gymnast = gymnast
|
||||||
def create(gymnast, course, trainingdate):
|
t.course = course
|
||||||
t = Training()
|
t.trainingdate = trainingdate
|
||||||
t.gymnast = gymnast
|
t.save()
|
||||||
t.course = course
|
return t
|
||||||
t.trainingdate = trainingdate
|
|
||||||
t.save()
|
|
||||||
return t
|
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
verbose_name = "Round"
|
verbose_name = "Round"
|
||||||
verbose_name_plural = "Rounds"
|
verbose_name_plural = "Rounds"
|
||||||
|
|
||||||
EVALUATION_CHOICES = (
|
EVALUATION_CHOICES = (
|
||||||
(0, "- -"),
|
(0, "- -"),
|
||||||
(1, "- +"),
|
(1, "- +"),
|
||||||
(2, "+ -"),
|
(2, "+ -"),
|
||||||
(3, "+ +"),
|
(3, "+ +"),
|
||||||
)
|
)
|
||||||
|
|
||||||
training = models.ForeignKey(
|
training = models.ForeignKey(
|
||||||
Training, on_delete=models.CASCADE, default=None, related_name="rounds"
|
Training, on_delete=models.CASCADE, default=None, related_name="rounds"
|
||||||
)
|
)
|
||||||
educative = models.ForeignKey(
|
educative = models.ForeignKey(
|
||||||
"objective.Educative",
|
"objective.Educative",
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
default=None,
|
default=None,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
round_information = models.CharField(max_length=255, blank=True, null=True)
|
round_information = models.CharField(max_length=255, blank=True, null=True)
|
||||||
round_number = models.PositiveSmallIntegerField(blank=True, null=True)
|
round_number = models.PositiveSmallIntegerField(blank=True, null=True)
|
||||||
gymnast_evaluation = models.PositiveSmallIntegerField(
|
gymnast_evaluation = models.PositiveSmallIntegerField(
|
||||||
choices=EVALUATION_CHOICES, blank=True, null=True
|
choices=EVALUATION_CHOICES, blank=True, null=True
|
||||||
)
|
)
|
||||||
coach_evaluation = models.PositiveSmallIntegerField(blank=True, null=True)
|
coach_evaluation = models.PositiveSmallIntegerField(blank=True, null=True)
|
||||||
coachid = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)
|
coachid = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)
|
||||||
nb_of_realisation = models.PositiveSmallIntegerField(blank=True, null=True)
|
nb_of_realisation = models.PositiveSmallIntegerField(blank=True, null=True)
|
||||||
nb_of_success = models.PositiveSmallIntegerField(blank=True, null=True)
|
nb_of_success = models.PositiveSmallIntegerField(blank=True, null=True)
|
||||||
is_important = models.BooleanField(default=False)
|
is_important = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s" % (self.round_number)
|
return "%s" % (self.round_number)
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
verbose_name = "Planning Line"
|
verbose_name = "Planning Line"
|
||||||
verbose_name_plural = "Planning lines"
|
verbose_name_plural = "Planning lines"
|
||||||
# ordering = ['gymnast', 'date', 'order']
|
# ordering = ['gymnast', 'date', 'order']
|
||||||
|
|
||||||
gymnast = models.ForeignKey(Gymnast, on_delete=models.CASCADE, default=None)
|
gymnast = models.ForeignKey(Gymnast, on_delete=models.CASCADE, default=None)
|
||||||
date = models.DateField(verbose_name="Date")
|
date = models.DateField(verbose_name="Date")
|
||||||
order = models.PositiveSmallIntegerField()
|
order = models.PositiveSmallIntegerField()
|
||||||
todo = models.CharField(max_length=255)
|
todo = models.CharField(max_length=255)
|
||||||
|
|
|
@ -1,7 +1,171 @@
|
||||||
|
"""Tests liés au modèle de l'application planning"""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime, time, timedelta
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from ..models import get_number_of_weeks_between, Season
|
|
||||||
|
from location.models import Club, Place, Country
|
||||||
|
|
||||||
|
from ..models import get_number_of_weeks_between, Season, Course, Unavailability
|
||||||
|
|
||||||
|
|
||||||
|
USER_MODEL = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class TestCourse(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.club = Club.objects.create(
|
||||||
|
name="RCW",
|
||||||
|
place=Place.objects.create(
|
||||||
|
name="Somewhere",
|
||||||
|
postal=1080,
|
||||||
|
country=Country.objects.create(
|
||||||
|
namefr="Belgique",
|
||||||
|
isonum=56
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.user1 = USER_MODEL.objects.create(username="james_bond")
|
||||||
|
self.user2 = USER_MODEL.objects.create(username="doctor_no")
|
||||||
|
|
||||||
|
def test_number_of_planned_occurrences(self):
|
||||||
|
"""Vérifie le calcul du nombre de séances planifiées durant une saison"""
|
||||||
|
course = Course.objects.create(
|
||||||
|
iso_day_number=2,
|
||||||
|
datebegin=datetime(2021, 1, 1),
|
||||||
|
dateend=datetime(2021, 9, 30),
|
||||||
|
hour_begin=time(hour=19, minute=30),
|
||||||
|
hour_end=time(hour=22, minute=45),
|
||||||
|
club=self.club
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(course.get_number_of_real_occurrences, 39)
|
||||||
|
|
||||||
|
course = Course.objects.create(
|
||||||
|
iso_day_number=2,
|
||||||
|
datebegin=datetime(2022, 1, 1),
|
||||||
|
dateend=datetime(2022, 10, 20),
|
||||||
|
hour_begin=time(hour=8, minute=30),
|
||||||
|
hour_end=time(hour=20, minute=0),
|
||||||
|
club=self.club
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(course.get_number_of_real_occurrences, 42)
|
||||||
|
|
||||||
|
def test_number_of_real_occurrences_should_be_zero(self):
|
||||||
|
"""Vérifie le calcul du nombre de séances réalisées durant une saison"""
|
||||||
|
course = Course.objects.create(
|
||||||
|
iso_day_number=2,
|
||||||
|
datebegin=datetime(2021, 1, 1),
|
||||||
|
dateend=datetime(2021, 9, 30),
|
||||||
|
hour_begin=time(hour=19, minute=30),
|
||||||
|
hour_end=time(hour=22, minute=45),
|
||||||
|
club=self.club
|
||||||
|
)
|
||||||
|
|
||||||
|
unavailabiliy = Unavailability.objects.create(
|
||||||
|
datebegin=datetime(2021, 1, 1),
|
||||||
|
dateend=datetime(2021, 9, 30),
|
||||||
|
)
|
||||||
|
|
||||||
|
unavailabiliy.course.add(course)
|
||||||
|
|
||||||
|
self.assertEqual(course.get_number_of_real_occurrences, 0)
|
||||||
|
|
||||||
|
def test_total_number_of_hours_should_be_39_times_3(self):
|
||||||
|
"""Vérifie que le nombre total d'heures consacrées est bien égal à 39 séances * 3h
|
||||||
|
|
||||||
|
Remarks:
|
||||||
|
39 séances * 3h = 117h = 4 jours + 21h = timedelta(days=3, seconds=75600) :-)
|
||||||
|
"""
|
||||||
|
course = Course.objects.create(
|
||||||
|
iso_day_number=2,
|
||||||
|
datebegin=datetime(2021, 1, 1),
|
||||||
|
dateend=datetime(2021, 9, 30),
|
||||||
|
hour_begin=time(hour=19, minute=0),
|
||||||
|
hour_end=time(hour=22, minute=0),
|
||||||
|
club=self.club
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(course.total_number_of_hours, timedelta(days=4, seconds=75600))
|
||||||
|
|
||||||
|
def test_number_of_real_occurrences_should_be_21(self):
|
||||||
|
course = Course.objects.create(
|
||||||
|
iso_day_number=2,
|
||||||
|
datebegin=datetime(2021, 1, 1),
|
||||||
|
dateend=datetime(2021, 9, 30),
|
||||||
|
hour_begin=time(hour=19, minute=30),
|
||||||
|
hour_end=time(hour=22, minute=45),
|
||||||
|
club=self.club
|
||||||
|
)
|
||||||
|
|
||||||
|
unavailabiliy = Unavailability.objects.create(
|
||||||
|
datebegin=datetime(2021, 6, 1),
|
||||||
|
dateend=datetime(2021, 9, 30),
|
||||||
|
)
|
||||||
|
|
||||||
|
unavailabiliy.course.add(course)
|
||||||
|
|
||||||
|
self.assertEqual(course.get_number_of_real_occurrences, 21)
|
||||||
|
|
||||||
|
def test_number_of_trainers_should_be_calculated(self):
|
||||||
|
course = Course.objects.create(
|
||||||
|
iso_day_number=2,
|
||||||
|
datebegin=datetime(2021, 1, 1),
|
||||||
|
dateend=datetime(2021, 9, 30),
|
||||||
|
hour_begin=time(hour=19, minute=30),
|
||||||
|
hour_end=time(hour=22, minute=45),
|
||||||
|
club=self.club
|
||||||
|
)
|
||||||
|
|
||||||
|
course.trainers.add(self.user1)
|
||||||
|
course.trainers.add(self.user2)
|
||||||
|
|
||||||
|
self.assertEqual(course.number_of_trainers, 2)
|
||||||
|
|
||||||
|
def test_total_number_of_paid_hours(self):
|
||||||
|
course = Course.objects.create(
|
||||||
|
iso_day_number=2,
|
||||||
|
datebegin=datetime(2021, 1, 1),
|
||||||
|
dateend=datetime(2021, 9, 30),
|
||||||
|
hour_begin=time(hour=19, minute=0),
|
||||||
|
hour_end=time(hour=22, minute=0),
|
||||||
|
club=self.club
|
||||||
|
)
|
||||||
|
|
||||||
|
course.trainers.add(self.user1)
|
||||||
|
course.trainers.add(self.user2)
|
||||||
|
|
||||||
|
self.assertEqual(course.total_number_of_paid_hours, timedelta(days=4, seconds=75600) * 2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_number_of_course_hours(self):
|
||||||
|
course = Course.objects.create(
|
||||||
|
iso_day_number=2,
|
||||||
|
datebegin=datetime(2021, 1, 1),
|
||||||
|
dateend=datetime(2021, 9, 30),
|
||||||
|
hour_begin=time(hour=19, minute=30),
|
||||||
|
hour_end=time(hour=22, minute=45),
|
||||||
|
club=self.club
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(course.number_of_hours, timedelta(seconds=11700))
|
||||||
|
|
||||||
|
|
||||||
|
def test_course_duration(self):
|
||||||
|
"""Vérifie le calcul de durée d'un cours"""
|
||||||
|
course = Course.objects.create(
|
||||||
|
iso_day_number=2,
|
||||||
|
datebegin=datetime(2021, 1, 1),
|
||||||
|
dateend=datetime(2021, 9, 30),
|
||||||
|
hour_begin=time(hour=19, minute=30),
|
||||||
|
hour_end=time(hour=22, minute=45),
|
||||||
|
club=self.club
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(course.duration, 2)
|
||||||
|
|
||||||
|
|
||||||
class TestUtils(TestCase):
|
class TestUtils(TestCase):
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
"""Tests liés aux fonctions d'utilité publique :-)"""
|
||||||
|
|
||||||
|
from datetime import time, timedelta
|
||||||
|
|
||||||
|
from django.test import SimpleTestCase
|
||||||
|
|
||||||
|
from ..utils import time_diff
|
||||||
|
|
||||||
|
|
||||||
|
class TestTimeDiff(SimpleTestCase):
|
||||||
|
def test_time_should_be_timedelta(self):
|
||||||
|
td = time_diff(time(8, 30), time(10, 15))
|
||||||
|
|
||||||
|
self.assertEqual(td, timedelta(seconds=6300))
|
|
@ -0,0 +1,24 @@
|
||||||
|
"""Fonctions utilitaires liées aux plannings"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def time_diff(start, end):
|
||||||
|
"""Calcule la différence de temps entre deux moments
|
||||||
|
|
||||||
|
Args:
|
||||||
|
start (datetime.time)
|
||||||
|
end (datetime.time)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Le nombre de secondes de différence entre les paramètres `start` et `end`.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> from datetime import time
|
||||||
|
>>> time_difference(time(8, 30), time(10, 15))
|
||||||
|
datetime.timedelta(seconds=6300)
|
||||||
|
"""
|
||||||
|
startdate = datetime(2000, 1, 1, start.hour, start.minute)
|
||||||
|
enddate = datetime(2000, 1, 1, end.hour, end.minute)
|
||||||
|
|
||||||
|
return enddate - startdate
|
|
@ -1302,7 +1302,7 @@ def planningline_update(request, planninglineid=None):
|
||||||
else:
|
else:
|
||||||
form = PlanningLineForm(
|
form = PlanningLineForm(
|
||||||
initial={
|
initial={
|
||||||
"gymnast": planningline.gymnast.id,
|
"gymnast": planningline.gymnast.id,
|
||||||
"gymnast_related": planningline.gymnast,
|
"gymnast_related": planningline.gymnast,
|
||||||
"date": planningline.date,
|
"date": planningline.date,
|
||||||
"order": planningline.order,
|
"order": planningline.order,
|
||||||
|
|
Loading…
Reference in New Issue