Split followup.views in smaller files. Add report week comparison

This commit is contained in:
Gregory Trullemans 2024-02-16 16:54:10 +01:00
parent c22d6062ea
commit d6d709ef44
10 changed files with 1672 additions and 1443 deletions

View File

@ -0,0 +1,53 @@
MAIL_HEADER = """
<html><head><style id="canary-invert">html {
filter: invert(100%) hue-rotate(180deg) !important;
}
img,
video,
:not(object):not(body)>embed,
object,
svg image,
[style*="background:url"],
[style*="background-image:url"],
[style*="background: url"],
[style*="background-image: url"],
[background],
twitterwidget,
.canary-emoji {
filter: invert(100%) hue-rotate(180deg) !important;
}
[style*="background:url"] *,
[style*="background-image:url"] *,
[style*="background: url"] *,
[style*="background-image: url"] *,
input,
[background] *,
img[src^="https://s0.wp.com/latex.php"],
twitterwidget .NaturalImage-image {
filter: none !important;
}
</style></head><body contenteditable="true" style="font-family:Helvetica;font-size:13px;">Gregory Trullemans<br>
</body></html>"""
MAIL_FOOTER = """<br />
<p>Excellente journée</p>
<p>Jarvis<br />
<b>Trampoline Trainer Help</b></p>
<table border="0">
<tbody>
<tr>
<td>
<img id="CB70323B-AC4A-4992-9DD8-3F25DC32658C" height="80px" src="https://www.flyingacrobaticstrampoline.be/img/logo_120px.png" style="max-width: 100vw;">
</td>
<td>
<b><span class="" style="color: rgb(253, 221, 12);">F</span><span class="" style="color: rgb(251, 214, 13);">l</span><span class="" style="color: rgb(249, 207, 14);">y</span><span class="" style="color: rgb(247, 200, 15);">i</span><span class="" style="color: rgb(245, 194, 16);">n</span><span class="" style="color: rgb(243, 187, 17);">g</span>&nbsp;<span class="" style="color: rgb(241, 180, 18);"></span><span class="" style="color: rgb(239, 173, 19);">A</span><span class="" style="color: rgb(237, 166, 20);">c</span><span class="" style="color: rgb(234, 159, 21);">r</span><span class="" style="color: rgb(232, 153, 22);">o</span><span class="" style="color: rgb(230, 146, 23);">b</span><span class="" style="color: rgb(228, 139, 24);">a</span><span class="" style="color: rgb(226, 132, 25);">t</span><span class="" style="color: rgb(224, 125, 26);">i</span><span class="" style="color: rgb(222, 118, 27);">c</span><span class="" style="color: rgb(220, 112, 28);">s</span>&nbsp;<span class="" style="color: rgb(218, 105, 29);"></span><span class="" style="color: rgb(216, 98, 30);">T</span><span class="" style="color: rgb(214, 91, 31);">r</span><span class="" style="color: rgb(212, 84, 32);">a</span><span class="" style="color: rgb(210, 77, 33);">m</span><span class="" style="color: rgb(208, 70, 34);">p</span><span class="" style="color: rgb(206, 64, 35);">o</span><span class="" style="color: rgb(204, 57, 36);">l</span><span class="" style="color: rgb(201, 50, 37);">i</span><span class="" style="color: rgb(199, 43, 38);">n</span><span class="" style="color: rgb(197, 36, 39);">e</span>&nbsp;<span class="" style="color: rgb(195, 29, 40);"></span><span class="" style="color: rgb(193, 23, 41);">C</span><span class="" style="color: rgb(191, 16, 42);">l</span><span class="" style="color: rgb(189, 9, 43);">u</span><span class="" style="color: rgb(187, 2, 44);">b</span></b><br>
<span style="font-size: 13px; letter-spacing: 0.01em; line-height: 1.2;">Rue René Francq, 7</span><br>
<span style="font-size: 13px; letter-spacing: 0.01em; line-height: 1.2;">1428 Lillois-Witterzée</span>
</td>
</tr>
</tbody>
</table>"""

View File

@ -1,84 +1,87 @@
from django.urls import path
from . import views
from . import views_chrono
from . import views_intensity
from . import views_physiological
urlpatterns = [
path(r"chrono/", views.chrono_listing, name="chrono_list"),
path(r"chrono/", views_chrono.chrono_listing, name="chrono_list"),
path(
r"chrono/gymnast/<int:gymnast_id>/",
views.chrono_listing,
views_chrono.chrono_listing,
name="chrono_list_for_gymnast",
),
path(r"chrono/add/", views.chrono_create_or_update, name="chrono_create"),
path(r"chrono/add/", views_chrono.chrono_create_or_update, name="chrono_create"),
path(
r"chrono/add/<int:gymnast_id>/",
views.chrono_create_or_update,
views_chrono.chrono_create_or_update,
name="chrono_create_for_gymnast",
),
path(
r"chrono/edit/<int:chrono_id>/",
views.chrono_create_or_update,
views_chrono.chrono_create_or_update,
name="chrono_update",
),
path(
r"chrono/details/<int:chrono_id>/",
views.jump_chrono_details,
views_chrono.jump_chrono_details,
name="jump_chrono_details",
),
path(
r"chrono/details/<int:chrono_id>/add/",
views.jump_chrono_values_create_or_update,
views_chrono.jump_chrono_values_create_or_update,
name="jump_chrono_values_create_or_update",
),
path(
r"chrono/add_jump_chrono_value/",
views.add_jump_chrono_value,
views_chrono.add_jump_chrono_value,
name="add_jump_chrono_value",
),
path(
r"chrono/remove_jump_chrono_value/",
views.remove_jump_chrono_value,
views_chrono.remove_jump_chrono_value,
name="remove_jump_chrono_value",
),
path(
r"chrono/range/<str:date_begin>/<str:date_end>/gymnast/<int:gymnast_id>/routine_type<int:routine_type>/",
views.average_jump_chrono_details_between_two_date,
views_chrono.average_jump_chrono_details_between_two_date,
name="average_jump_chrono_details_between_two_date",
),
path(
r"chrono/season/<str:season>/week/<int:week_number>/gymnast/<int:gymnast_id>/routine_type/<int:routine_type>/",
views.average_jump_chrono_details_for_gymnast,
views_chrono.average_jump_chrono_details_for_gymnast,
name="average_jump_chrono_details_for_week_of_season",
),
path(
r"chrono/statistics/gymnast/<int:gymnast_id>/routine_type/<int:routine_type>/season/<str:season>/week/<int:week_number>/",
views.average_jump_chrono_details_for_gymnast,
views_chrono.average_jump_chrono_details_for_gymnast,
name="average_jump_chrono_details_for_gymnast_routinetype_season_and_week",
),
path(
r"chrono/statistics/gymnast/<int:gymnast_id>/routine_type/<int:routine_type>/",
views.average_jump_chrono_details_for_gymnast,
views_chrono.average_jump_chrono_details_for_gymnast,
name="average_jump_chrono_details_for_gymnast_and_routinetype",
),
path(
r"chrono/statistics/gymnast/<int:gymnast_id>/",
views.average_jump_chrono_details_for_gymnast,
views_chrono.average_jump_chrono_details_for_gymnast,
name="average_jump_chrono_details_for_gymnast",
),
path(
r"chrono/get_distinct_season/gymnast/<int:gymnast_id>/",
views.get_chrono_detail_distinct_season,
views_chrono.get_chrono_detail_distinct_season,
name="get_chrono_detail_distinct_season",
),
path(
r"chrono/get_distinct_weeknumber/gymnast/<int:gymnast_id>/season/<str:season>/",
views.get_chrono_detail_distinct_weeknumber_for_season,
views_chrono.get_chrono_detail_distinct_weeknumber_for_season,
name="get_chrono_detail_distinct_weeknumber_for_season",
),
path(
r"chrono/get_average_jump_chrono_details_for_season_and_week/gymnast/<int:gymnast_id>/routine_type/<int:routine_type>/season/<str:season>/week/<int:week_number>/",
views.get_average_jump_chrono_details_for_season_and_week,
views_chrono.get_average_jump_chrono_details_for_season_and_week,
name="get_average_jump_chrono_details_for_season_and_week",
),
#
@ -87,7 +90,7 @@ urlpatterns = [
path(r"note/", views.note_listing, name="note_list"),
path(
r"note/gymnast/<int:gymnast_id>/",
views.chrono_listing,
views.note_listing,
name="note_list_for_gymnast",
),
path(r"note/add/", views.note_create_or_update, name="note_create"),
@ -101,26 +104,30 @@ urlpatterns = [
#
#
# INTENSITY
path(r"intensity/", views.intensity_listing, name="intensity_list"),
path(r"intensity/", views_intensity.intensity_listing, name="intensity_list"),
path(
r"intensity/gymnast/<int:gymnast_id>/",
views.intensity_listing,
views_intensity.intensity_listing,
name="intensity_list_for_gymnast",
),
path(r"intensity/add/", views.intensity_create_or_update, name="intensity_create"),
path(
r"intensity/add/",
views_intensity.intensity_create_or_update,
name="intensity_create",
),
path(
r"intensity/add/<int:gymnast_id>/",
views.intensity_create_or_update,
views_intensity.intensity_create_or_update,
name="intensity_create_for_gymnast",
),
path(
r"intensity/<int:intensity_id>/edit/",
views.intensity_create_or_update,
views_intensity.intensity_create_or_update,
name="intensity_update",
),
path(
r"intensity/details/<int:intensity_id>/",
views.intensity_details,
views_intensity.intensity_details,
name="intensity_details",
),
#
@ -158,43 +165,55 @@ urlpatterns = [
#
#
# ACCIDENT
path(r"injury/search/", views.injuries_listing, name="injury_search"),
path(r"injury/", views.injuries_listing, name="injuries_list"),
path(r"injury/add/", views.injury_create_or_update, name="injury_create"),
path(r"injury/search/", views_physiological.injuries_listing, name="injury_search"),
path(r"injury/", views_physiological.injuries_listing, name="injuries_list"),
path(
r"injury/add/",
views_physiological.injury_create_or_update,
name="injury_create",
),
path(
r"injury/add/<int:gymnast_id>/",
views.injury_create_or_update,
views_physiological.injury_create_or_update,
name="injury_create_for_gymnast",
),
path(
r"injury/edit/<int:injury_id>/",
views.injury_create_or_update,
views_physiological.injury_create_or_update,
name="injury_update",
),
path(r"injury/<int:injury_id>/", views.injury_details, name="injury_details"),
path(
r"injury/<int:injury_id>/",
views_physiological.injury_details,
name="injury_details",
),
#
#
# WELLBEING
path(r"wellbeing/", views.wellbeing_listing, name="wellbeing_list"),
path(r"wellbeing/", views_physiological.wellbeing_listing, name="wellbeing_list"),
path(
r"wellbeing/gymnast/<int:gymnast_id>/",
views.wellbeing_listing,
views_physiological.wellbeing_listing,
name="wellbeing_list_for_gymnast",
),
path(r"wellbeing/add/", views.wellbeing_create_or_update, name="wellbeing_create"),
path(
r"wellbeing/add/",
views_physiological.wellbeing_create_or_update,
name="wellbeing_create",
),
path(
r"wellbeing/add/<int:gymnast_id>/",
views.wellbeing_create_or_update,
views_physiological.wellbeing_create_or_update,
name="wellbeing_create_for_gymnast",
),
path(
r"wellbeing/edit/<int:wellbeing_id>/",
views.wellbeing_create_or_update,
views_physiological.wellbeing_create_or_update,
name="wellbeing_update",
),
path(
r"wellbeing/<int:wellbeing_id>/",
views.wellbeing_details,
views_physiological.wellbeing_details,
name="wellbeing_details",
),
#
@ -202,22 +221,22 @@ urlpatterns = [
# Height/Weight
path(
r"heightweight/gymnast/<int:gymnast_id>/",
views.heightweight_listing,
views_physiological.heightweight_listing,
name="heightweight_list_for_gymnast",
),
path(
r"heightweight/add/",
views.heightweight_create_or_update,
views_physiological.heightweight_create_or_update,
name="heightweight_create",
),
path(
r"heightweight/add/<int:gymnast_id>/",
views.heightweight_create_or_update,
views_physiological.heightweight_create_or_update,
name="heightweight_create_for_gymnast",
),
path(
r"heightweight/edit/<int:heightweight_id>/",
views.heightweight_create_or_update,
views_physiological.heightweight_create_or_update,
name="heightweight_update",
),
#

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,476 @@
from datetime import date, datetime
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.db.models import Min, Avg, Max, Sum
from django.urls import reverse
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.mail import send_mail
import pendulum
from jarvis.core.models import Email
from jarvis.people.models import Gymnast
from jarvis.tools.date_week_transition import from_date_to_week_number
from jarvis.tools.models import Season
from .models import (
Chrono,
ChronoDetails,
)
from .forms import ChronoForm
from .models import (
SCORE_TYPE_CHOICE,
CHRONO_TYPE_CHOICE,
)
from .email_vars import MAIL_HEADER, MAIL_FOOTER
User = get_user_model()
@login_required
@require_http_methods(["GET"])
def jump_chrono_details(request, chrono_id):
"""Récupère toutes les informations détaillées d'un chrono. La fonction en profite pour
recalculer le total et s'assure que cela correspond à la valeur stockée dans le model
Chrono.
Args:
chrono_id (int) identifiant chrono
QTF : Est-ce que je ne devrais pas faire un prefetch_related sur mon objet chrono pour
optimiser mon affichage ?
chrono = Chrono.object.get(pk=chrono_id).prefetch_related('chrono_details') ?
"""
chrono = get_object_or_404(Chrono, pk=chrono_id)
if not request.user.is_superuser and (
request.session.has_key("available_gymnast")
and chrono.gymnast.id not in request.session["available_gymnast"]
):
return chrono_listing(request)
sum_value = chrono.details.all().aggregate(total=Sum("value"))
if chrono.score != sum_value["total"]:
chrono.score = sum_value["total"]
if chrono.score_type == 0:
chrono.tof = Chrono.compute_tof(sum_value["total"])
chrono.save()
mean_value = chrono.details.all().aggregate(mean=Avg("value"))["mean"]
tmp_min_value = chrono.details.all().aggregate(min=Min("value"))["min"]
tmp_max_value = chrono.details.all().aggregate(max=Max("value"))["max"]
chart_min_value = mean_value - (tmp_min_value / 20)
chart_max_value = mean_value - (tmp_max_value / 20)
context = {
"chrono": chrono,
"mean_value": mean_value,
"chart_min_value": chart_min_value,
"chart_max_value": chart_max_value,
}
return render(request, "chronos/details.html", context)
@login_required
@require_http_methods(["GET"])
def average_jump_chrono_details_for_gymnast(
request, gymnast_id, routine_type=1, season=None, week_number=1
):
"""Récupère tout les chronos entre deux date pour un gymnaste et un type de série
Args:
gymnast_id (int) Identifiant d'un gymnaste
routine_type (int) Type de série (cf. jarvis/followup/models.py > ROUTINE_CHOICE)
season (string) Saison sous forme "xxxx-xxxy"
week_number (int) numéro de semaine (1, , 52)
"""
if season is None:
today = pendulum.now().date()
season, week_number = from_date_to_week_number(today)
else:
season = Season(season).label
week_number = min(week_number, 52)
week_number = max(week_number, 1)
return average_jump_chrono_details_for_season_and_week(
request,
gymnast_id,
routine_type,
season,
week_number,
)
@require_http_methods(["POST"])
def remove_jump_chrono_value(request):
"""
Recoit trois informations permettant de supprimer le chrono d'un saut à un chrono.
"""
chrono_id = request.POST.get("chrono_id", None)
order = request.POST.get("order", None)
chrono = get_object_or_404(Chrono, pk=chrono_id)
try:
ChronoDetails.objects.filter(chrono=chrono, order=order).delete()
except Exception:
return HttpResponse(409)
return HttpResponse(200)
@require_http_methods(["POST"])
def add_jump_chrono_value(request):
"""
Recoit trois informations permettant d'ajouter le chrono d'un saut à un chrono.
"""
chrono_id = request.POST.get("chrono_id", None)
order = request.POST.get("order", None)
value = request.POST.get("value", None)
chrono = get_object_or_404(Chrono, pk=chrono_id)
row, created = ChronoDetails.objects.get_or_create(
chrono=chrono, order=order, value=value
)
if created:
return HttpResponse(200, (row, created)) # devrait être un 201
return HttpResponse(400, (row, created))
@login_required
@require_http_methods(["GET"])
def jump_chrono_values_create_or_update(request, chrono_id):
"""
Ajoute des scores de saut à un chrono.
Args:
chrono_id (int) identifiant chrono
"""
chrono = get_object_or_404(Chrono, pk=chrono_id)
jump_list = chrono.details.all()
number_of_jump = jump_list.count()
context = {
"chrono": chrono,
"jump_list": jump_list,
"number_of_jump": number_of_jump,
"score_type": chrono.score_type,
}
return render(request, "chronos/add_details.html", context)
@login_required
@require_http_methods(["GET"])
def average_jump_chrono_details_between_two_date(
request, gymnast_id, routine_type=1, date_begin=None, date_end=None
):
"""Récupère tout les chronos entre deux date pour un gymnaste et un type de série
Args:
gymnast_id (int) Identifiant d'un gymnaste
routine_type (int) type de série (cf. jarvis/followup/models.py > ROUTINE_CHOICE)
date_begin (date) date de début
date_end (date) date de fin
QTF : le cast en date devrait être dans un try mais comment gérer correctement l'erreur - si
erreur il y a ?
"""
if date_end:
try:
date_end = datetime.strptime(date_end, "%Y-%m-%d").date()
except (ValueError, TypeError):
date_end = pendulum.now().date()
else:
date_end = pendulum.now().date()
if date_begin:
try:
date_begin = datetime.strptime(date_begin, "%Y-%m-%d").date()
except (ValueError, TypeError):
date_begin = datetime(date_end.year, 9, 1)
else:
date_begin = datetime(date_end.year, 9, 1)
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
stat_values = (
ChronoDetails.objects.filter(
chrono__gymnast=gymnast_id,
chrono__chrono_type=routine_type,
chrono__date__gte=date_begin,
chrono__date__lte=date_end,
)
.values("order")
.annotate(
avg_score=Avg("value"), max_score=Max("value"), min_score=Min("value")
)
.order_by("order")
)
chrono_list = Chrono.objects.filter(
gymnast=gymnast_id,
date__gte=date_begin,
date__lte=date_end,
chrono_type=routine_type,
)
context = {
"gymnast": gymnast,
"date_begin": date_begin,
"date_end": date_end,
"chrono_list": chrono_list,
"stat_values": stat_values,
}
return render(request, "chronos/list_details.html", context)
@require_http_methods(["GET"])
def get_chrono_detail_distinct_season(request, gymnast_id):
"""Récupère toutes les saisons pour lesquelles le gymnaste a des chronos détaillés.
Args:
gymnast_id (int) Identifiant d'un gymnaste
"""
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
season_list = list(
gymnast.chronos.values_list("season", flat=True)
.distinct("season")
.order_by("season")
)
return JsonResponse(season_list, safe=False)
@require_http_methods(["GET"])
def get_chrono_detail_distinct_weeknumber_for_season(request, gymnast_id, season):
"""Récupère toutes les week_number pour lesquelles le gymnaste a des chronos détaillés au cours
d'une saison.
Args:
gymnast_id (int) Identifiant d'un gymnaste
season (string) Season
"""
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
weeknumber_list = list(
gymnast.chronos.values_list("week_number", flat=True)
.filter(season=season)
.distinct("week_number")
.order_by("week_number")
)
return JsonResponse(weeknumber_list, safe=False)
@require_http_methods(["GET"])
def get_average_jump_chrono_details_for_season_and_week(
request, gymnast_id, routine_type, season, week_number
):
"""Récupère tout les chronos moyen par saut pour une saison & semaine d'un gymnaste
Args:
gymnast_id (int) Identifiant d'un gymnaste
routine_type (int) Type de série (cf. jarvis/followup/models.py > ROUTINE_CHOICE)
season (string) Season
week_number (int) Numero de la semaine
"""
stat_values = list(
ChronoDetails.objects.filter(
chrono__gymnast=gymnast_id,
chrono__chrono_type=routine_type,
chrono__season=season,
chrono__week_number=week_number,
)
.values("order")
.annotate(avg_score=Avg("value"))
.order_by("order")
)
return JsonResponse(stat_values, safe=False)
@login_required
@require_http_methods(["GET"])
def average_jump_chrono_details_for_season_and_week(
request, gymnast_id, routine_type, season, week_number
):
"""Récupère tout les chronos entre deux date pour un gymnaste et un type de série
Args:
gymnast_id (int) Identifiant d'un gymnaste
routine_type (int) Type de série (cf. jarvis/followup/models.py > ROUTINE_CHOICE)
season (string) Season
week_number (int) Numero de la semaine
"""
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
stat_values = (
ChronoDetails.objects.filter(
chrono__gymnast=gymnast_id,
chrono__chrono_type=routine_type,
chrono__season=season,
chrono__week_number=week_number,
)
.values("order")
.annotate(
avg_score=Avg("value"), max_score=Max("value"), min_score=Min("value")
)
.order_by("order")
)
# print(stat_values)
distinct_season_list = (
gymnast.chronos.values_list("season", flat=True)
.distinct("season")
.order_by("season")
)
distinct_week_number_list = (
gymnast.chronos.values_list("week_number", flat=True)
.filter(season=season)
.distinct("week_number")
.order_by("week_number")
)
distinct_routine_type_list = (
gymnast.chronos.values_list("chrono_type", flat=True)
.distinct("chrono_type")
.order_by("chrono_type")
)
chrono_list = Chrono.objects.filter(
gymnast=gymnast_id,
season=season,
week_number=week_number,
chrono_type=routine_type,
)
# print(chrono_list)
context = {
"gymnast": gymnast,
"selected_season": season,
"selected_week_number": week_number,
"selected_routine_type": routine_type,
"chrono_list": chrono_list,
"stat_values": stat_values,
"distinct_season_list": distinct_season_list,
"distinct_week_number_list": distinct_week_number_list,
"distinct_routine_type_list": distinct_routine_type_list,
}
return render(request, "chronos/list_details.html", context)
@login_required
@require_http_methods(["GET"])
def chrono_listing(request, gymnast_id=None):
"""
Récupère les chronos des gymnastes autorisé(e)s.
Args:
gymnast_id (int) identifiant d'un gymnaste
"""
gymnast = None
if gymnast_id and (
request.user.is_superuser
or (
request.session.has_key("available_gymnast")
and gymnast_id in request.session["available_gymnast"]
)
):
chrono_list = Chrono.objects.filter(gymnast=gymnast_id)
gymnast = Gymnast.objects.get(pk=gymnast_id)
else:
if request.user.is_superuser:
chrono_list = Chrono.objects.all()
else:
chrono_list = Chrono.objects.filter(
gymnast__in=request.session["available_gymnast"]
)
context = {"chrono_list": chrono_list, "gymnast": gymnast}
return render(request, "chronos/list.html", context)
@login_required
@require_http_methods(["GET", "POST"])
def chrono_create_or_update(request, chrono_id=None, gymnast_id=None):
"""Création ou modification d'un chrono
Args:
chrono_id (int) identifiant d'un chrono
gymnast_id (int) identifiant d'un gymnaste
"""
if chrono_id:
chrono = get_object_or_404(Chrono, pk=chrono_id)
if not request.user.is_superuser and (
request.session.has_key("available_gymnast")
and chrono.gymnast.id not in request.session["available_gymnast"]
):
return chrono_listing(request)
data = {
"gymnast": chrono.gymnast.id,
"gymnast_related": str(chrono.gymnast),
}
else:
chrono = None
data = None
if gymnast_id is not None:
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
data = {"gymnast": gymnast_id, "gymnast_related": gymnast}
if request.method == "POST":
form = ChronoForm(request.POST, instance=chrono)
if form.is_valid():
new_chrono = form.save(commit=False)
if new_chrono.score_type == 1:
new_chrono.tof = new_chrono.score
else:
new_chrono.tof = Chrono.compute_tof(new_chrono.score)
new_chrono.save()
# notification
receivers = []
functionality = ContentType.objects.get(model="chrono")
for notification in new_chrono.gymnast.notifications.filter(
functionality=functionality
):
receivers.append(notification.user.email)
title = f"{new_chrono.gymnast} : Nouveau chrono"
body = f"""<p>Bonjour,</p><p>Nouveau chrono pour {new_chrono.gymnast} : {SCORE_TYPE_CHOICE[new_chrono.score_type][1]} {CHRONO_TYPE_CHOICE[new_chrono.chrono_type][1]} - {new_chrono.score}.</p>"""
Email.objects.create(
receivers=receivers,
title=title,
body=body,
)
send_mail(
title,
f"{new_chrono.gymnast} a enregistrer un nouveau chrono ({date})",
settings.EMAIL_HOST_USER,
receivers,
fail_silently=False,
html_message=body + MAIL_FOOTER,
)
return HttpResponseRedirect(
reverse("gymnast_details_tab", args=(new_chrono.gymnast.id, "scores"))
)
return render(request, "chronos/create.html", {"form": form})
form = ChronoForm(instance=chrono, initial=data)
context = {"form": form, "chrono_id": chrono_id}
return render(request, "chronos/create.html", context)

View File

@ -0,0 +1,211 @@
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.http import HttpResponseRedirect
from django.db.models import Min, Avg, Max
from django.core.mail import send_mail
from django.urls import reverse
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from jarvis.core.models import Email
from .models import Gymnast
from .models import Intensity
from .forms import IntensityForm
from .email_vars import MAIL_HEADER, MAIL_FOOTER
def get_intensity_stats_for_season_week(gymnast_id, season, week_number):
"""Calcule le min, max et moyenne de bien-être d'un gymnaste pour une semaine donnéee
Args:
gymnast_id (int) identifiant d'un gymnaste
season (str) saison
week_number (int) numéro de semaine
"""
intensity_score = Intensity.objects.filter(
gymnast_id=gymnast_id, season=season, week_number=week_number
).aggregate(
average_intensity_time_value=Avg("time"),
average_intensity_difficulty_value=Avg("difficulty"),
average_quantity_of_skill_value=Avg("quantity_of_skill"),
average_number_of_passes_value=Avg("number_of_passes"),
average_time_quality=Avg("time_quality"),
average_difficulty_quality=Avg("difficulty_quality"),
average_quantity_of_skill_quality=Avg("quantity_of_skill_quality"),
average_number_of_passes_quality=Avg("number_of_passes_quality"),
average_average_training_quality=Avg("average_training_quality"),
min_intensity_time_value=Min("time"),
min_intensity_difficulty_value=Min("difficulty"),
min_quantity_of_skill_value=Min("quantity_of_skill"),
min_number_of_passes_value=Min("number_of_passes"),
min_time_quality=Min("time_quality"),
min_difficulty_quality=Min("difficulty_quality"),
min_quantity_of_skill_quality=Min("quantity_of_skill_quality"),
min_number_of_passes_quality=Min("number_of_passes_quality"),
min_average_training_quality=Min("average_training_quality"),
max_intensity_time_value=Max("time"),
max_intensity_difficulty_value=Max("difficulty"),
max_quantity_of_skill_value=Max("quantity_of_skill"),
max_number_of_passes_value=Max("number_of_passes"),
max_time_quality=Max("time_quality"),
max_difficulty_quality=Max("difficulty_quality"),
max_quantity_of_skill_quality=Max("quantity_of_skill_quality"),
max_number_of_passes_quality=Max("number_of_passes_quality"),
max_average_training_quality=Max("average_training_quality"),
)
return intensity_score
@login_required
@require_http_methods(["GET"])
def intensity_listing(request, gymnast_id=None):
"""
Si la personne connectée est un entraîneur, la fonction récupère la liste des intensités d'un
gymnaste précis ou de tout le monde.
Si la personne connectée est un gymnaste, la fonction récupère la liste de ses intensités.
Args:
gymnast_id (int) identifiant d'un gymnaste
"""
gymnast = None
if gymnast_id and (
request.user.is_superuser
or (
request.session.has_key("available_gymnast")
and gymnast_id in request.session["available_gymnast"]
)
):
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
intensity_list = Intensity.objects.filter(gymnast=gymnast_id)
else:
if request.user.is_superuser:
intensity_list = Intensity.objects.all()
else:
intensity_list = Intensity.objects.filter(
gymnast__in=request.session["available_gymnast"]
)
context = {"intensity_list": intensity_list, "gymnast": gymnast}
return render(request, "intensities/list.html", context)
@login_required
@require_http_methods(["GET"])
def intensity_details(request, intensity_id):
"""
Récupère toutes les informations d'une intensité.
Args:
intensity_id (int) identifiant d'une intensité
"""
intensity = get_object_or_404(Intensity, pk=intensity_id)
if not request.user.is_superuser and (
request.session.has_key("available_gymnast")
and intensity.gymnast.id not in request.session["available_gymnast"]
):
return intensity_listing(request)
return render(request, "intensities/details.html", {"intensity": intensity})
@login_required
@require_http_methods(["GET", "POST"])
def intensity_create_or_update(request, intensity_id=None, gymnast_id=None):
"""Création d'un record de la class Intentity.
Args:
intensity_id (int) identifiant d'une intensité (classe <Intensity>).
gymnast_id (int) identifiant d'un gymnaste (classe <Gymnast>).
"""
if intensity_id:
intensity = get_object_or_404(Intensity, pk=intensity_id)
if not request.user.is_superuser and (
request.session.has_key("available_gymnast")
and intensity.gymnast.id not in request.session["available_gymnast"]
):
return intensity_listing(request)
data = {
"gymnast": intensity.gymnast.id,
"gymnast_related": str(intensity.gymnast),
}
else:
intensity = None
data = {}
if gymnast_id:
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
data["gymnast"] = gymnast_id
data["gymnast_related"] = str(gymnast)
if request.method == "POST":
form = IntensityForm(request.POST, instance=intensity)
if form.is_valid():
intensity = form.save()
# notification
receivers = []
date = form.cleaned_data["date"]
functionality = ContentType.objects.get(model="intensity")
for notification in intensity.gymnast.notifications.filter(
functionality=functionality
):
receivers.append(notification.user.email)
title = f"{intensity.gymnast} : Nouvelle intensité"
body = f"""<p>Bonjour,</p><p>{intensity.gymnast} a encodé une nouvelle intensité pour le {date.strftime('%d %B %Y')}:</p>
<ul>
<li>{intensity.number_of_passes} passages</li>
<li>{intensity.time} minutes</li>
<li>{intensity.quantity_of_skill} figures</li>
<li>{intensity.difficulty_in_unit} difficulty</li>
</ul>
<p><u>Quality:</u></p>
<ul>
<li>Time: {intensity.time_quality:.1f}%</li>
<li>Diff: {intensity.difficulty_quality:.1f}%</li>
<li>Skill: {intensity.quantity_of_skill_quality:.1f}%</li>
<li>Passe: {intensity.number_of_passes_quality:.1f}%</li>
<li><b>Passe: {intensity.average_quality:.2f}%</b></li>
</ul>
<p><u>Statistics:</u></p>
<ul>
<li>Passe/time: {intensity.mean_time_by_passe:.2f}min</li>
<li>Skill/time: {intensity.average_quantity_of_skill_by_time:.2f}min</li>
<li>Skill/passe: {intensity.average_quantity_of_skill_by_passe:.2f}</li>
<li>Diff/passe: {intensity.average_difficulty_by_passe:.2f}</li>
<li>Diff/skill: {intensity.average_difficulty_by_skill:.2f}</li>
</ul>"""
Email.objects.create(
receivers=receivers,
title=title,
body=body,
)
send_mail(
title,
f"Une nouvelle information de saison enregistrée pour {intensity.gymnast}",
settings.EMAIL_HOST_USER,
receivers,
fail_silently=False,
html_message=body + MAIL_FOOTER,
)
return HttpResponseRedirect(
reverse(
"intensity_details",
args=(intensity.id,),
)
)
return render(request, "intensities/create.html", {"form": form})
form = IntensityForm(instance=intensity, initial=data)
context = {"form": form, "intensity_id": intensity_id}
return render(request, "intensities/create.html", context)

View File

@ -0,0 +1,438 @@
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.http import HttpResponseRedirect
from django.db.models import Min, Avg, Max
from django.core.mail import send_mail
from django.urls import reverse
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from jarvis.core.models import Email
from .models import Gymnast
from .models import (
Injury,
WellBeing,
HeightWeight,
)
from .forms import (
InjuryForm,
WellBeingForm,
HeightWeightForm,
)
from .models import (
INJURY_MECHANISM_CHOICE,
INJURY_BODY_SIDE_CHOICE,
INJURY_TYPE_CHOICE,
INJURY_LOCATION_CHOICE,
)
from .email_vars import MAIL_HEADER, MAIL_FOOTER
@login_required
@require_http_methods(["GET"])
def injuries_listing(request, gymnast_id=None):
"""
Récupère la liste des bessures.
Si c'est un gymnaste qui est connecté, il ne peut récupérer que la liste de ses blessures.
Si c'est un autre utilisateur (entraîneur), la liste peut répondre à un pattern si celui-ci est
définit.
"""
gymnast = None
if gymnast_id and (
request.user.is_superuser
or (
request.session.has_key("available_gymnast")
and gymnast_id in request.session["available_gymnast"]
)
):
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
injuries_list = Injury.objects.filter(gymnast=gymnast_id)
else:
if request.user.is_superuser:
injuries_list = Injury.objects.all()
else:
injuries_list = Injury.objects.filter(
gymnast__in=request.session["available_gymnast"]
)
context = {"injuries_list": injuries_list, "gymnast": gymnast}
return render(request, "injuries/list.html", context)
@login_required
@require_http_methods(["GET", "POST"])
def injury_create_or_update(request, injury_id=None, gymnast_id=None):
"""
Formulaire de création d'un nouvel blessure.
Args:
injury_id (int) identifiant d'une blessure
gymnast_id (int) identifiant d'un gymnaste
"""
if injury_id:
injury = get_object_or_404(Injury, pk=injury_id)
if not request.user.is_superuser and (
request.session.has_key("available_gymnast")
and injury.gymnast.id not in request.session["available_gymnast"]
):
return injury_listing(request)
data = {
"gymnast_related": injury.gymnast,
"skill_related": injury.skill,
}
else:
injury = None
data = None
if gymnast_id is not None:
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
data = {"gymnast": gymnast_id, "gymnast_related": str(gymnast)}
if request.method == "POST":
form = InjuryForm(request.POST, instance=injury)
if form.is_valid():
injury = form.save()
# notification
receivers = []
date = form.cleaned_data["date"]
functionality = ContentType.objects.get(model="injury")
for notification in injury.gymnast.notifications.filter(
functionality=functionality
):
receivers.append(notification.user.email)
title = f"{injury.gymnast} : Nouvelle blessure enregistrée"
body = f"""<p>Bonjour,</p>
<p>Un nouvelle blessure enregistrée pour {injury.gymnast} pour le {date.strftime('%d %B %Y')}:</p>
<ul>
<li>{INJURY_TYPE_CHOICE[injury.injury_type][1]},</li>
<li>caused by {INJURY_MECHANISM_CHOICE[injury.mechanism][1]},</li>
<li>on {INJURY_LOCATION_CHOICE[injury.location][1]},</li>
<li>{INJURY_BODY_SIDE_CHOICE[injury.body_side][1]} side,</li>
</ul>"""
Email.objects.create(receivers=receivers, title=title, body=body)
send_mail(
title,
f"{injury.gymnast} a ajouté état de bien être ({date})",
settings.EMAIL_HOST_USER,
receivers,
fail_silently=False,
html_message=body + MAIL_FOOTER,
)
return HttpResponseRedirect(reverse("injury_details", args=(injury.pk,)))
return render(request, "injuries/create.html", {"form": form})
form = InjuryForm(instance=injury, initial=data)
context = {"form": form, "injury_id": injury_id}
return render(request, "injuries/create.html", context)
@login_required
@require_http_methods(["GET"])
def injury_details(request, injury_id):
"""
Récupère toutes les informations d'une blessure.
Args:
injury_id (int) identifiant d'une blessure
"""
injury = get_object_or_404(Injury, pk=injury_id)
if not request.user.is_superuser and (
request.session.has_key("available_gymnast")
and injury.gymnast.id not in request.session["available_gymnast"]
):
return injuries_listing(request)
return render(request, "injuries/details.html", {"injury": injury})
@login_required
@require_http_methods(["GET"])
def wellbeing_listing(request, gymnast_id=None):
"""
Récupère la liste des évaluations de bien-être.
Args:
gymnast_id (int) identifiant d'un gymnaste
"""
gymnast = None
if gymnast_id and (
request.user.is_superuser
or (
request.session.has_key("available_gymnast")
and gymnast_id in request.session["available_gymnast"]
)
):
# wellbeing_list = WellBeing.objects.filter(gymnast=gymnast_id)
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
wellbeing_list = gymnast.wellbeings.all()
else:
if request.user.is_superuser:
# les super user peuvent voir tout le monde.
wellbeing_list = WellBeing.objects.all()
else:
# les autres entraîneurs ne peuvent voir que certains élèves.
wellbeing_list = WellBeing.objects.filter(
gymnast__in=request.session["available_gymnast"]
)
context = {"wellbeing_list": wellbeing_list, "gymnast": gymnast}
return render(request, "wellbeing/list.html", context)
def get_wellbeing_stats_for_season_week(gymnast_id, season, week_number):
"""Calcule le min, max et moyenne de bien-être d'un gymnaste pour une semaine donnéee
Args:
gymnast_id (int) identifiant d'un gymnaste
season (str) saison
week_number (int) numéro de semaine
"""
wellbeing_score = WellBeing.objects.filter(
gymnast_id=gymnast_id, season=season, week_number=week_number
).aggregate(
min_mindstate_value=Min("mindstate"),
average_mindstate_value=Avg("mindstate"),
max_mindstate_value=Max("mindstate"),
min_sleep_value=Min("sleep"),
average_sleep_value=Avg("sleep"),
max_sleep_value=Max("sleep"),
min_stress_value=Min("stress"),
average_stress_value=Avg("stress"),
max_stress_value=Max("stress"),
min_fatigue_value=Min("fatigue"),
average_fatigue_value=Avg("fatigue"),
max_fatigue_value=Max("fatigue"),
min_muscle_soreness_value=Min("muscle_soreness"),
average_muscle_soreness_value=Avg("muscle_soreness"),
max_muscle_soreness_value=Max("muscle_soreness"),
)
return wellbeing_score
@login_required
@require_http_methods(["GET", "POST"])
def wellbeing_create_or_update(
request, wellbeing_id=None, gymnast_id=None, event_id=None
):
"""
Formulaire de création d'une nouvelle blessure.
Args:
wellbeing_id (int) identifiant d'une blessure
gymnast_id (int) identifiant d'un gymnaste
event_id (int) identifiant d'un événement
"""
if wellbeing_id:
wellbeing = get_object_or_404(WellBeing, pk=wellbeing_id)
if not request.user.is_superuser and (
request.session.has_key("available_gymnast")
and wellbeing.gymnast.id not in request.session["available_gymnast"]
):
return wellbeing_listing(request)
data = {"gymnast_related": wellbeing.gymnast, "event_related": wellbeing.event}
else:
wellbeing = None
data = None
if gymnast_id is not None:
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
data = {"gymnast": gymnast_id, "gymnast_related": str(gymnast)}
if event_id is not None:
event = get_object_or_404(Event, pk=event_id)
data = {"event": event_id, "event_related": str(event)}
if request.method == "POST":
form = WellBeingForm(request.POST, instance=wellbeing)
if form.is_valid():
wellbeing = form.save()
# notification
receivers = []
date = form.cleaned_data["date"]
functionality = ContentType.objects.get(model="wellbeing")
for notification in wellbeing.gymnast.notifications.filter(
functionality=functionality
):
receivers.append(notification.user.email)
title = f"{wellbeing.gymnast} : Nouveau score de bien être"
html_message = f"""<p>Bonjour,</p>
<p>{wellbeing.gymnast} a ajouté son état de bien être pour le ({date.strftime('%d %B %Y')}) :</p>
<ul>
<li>Mindstate: {wellbeing.mindstate}</li>
<li>Sleep: {wellbeing.sleep}</li>
<li>Stress: {wellbeing.stress}</li>
<li>Fatigue: {wellbeing.fatigue}</li>
<li>Muscle soreness: {wellbeing.muscle_soreness}</li>
</ul>
{wellbeing.informations}"""
Email.objects.create(receivers=receivers, title=title, body=html_message)
send_mail(
title,
f"{wellbeing.gymnast} a ajouté état de bien être ({date})",
settings.EMAIL_HOST_USER,
receivers,
fail_silently=False,
html_message=html_message + MAIL_FOOTER,
)
return HttpResponseRedirect(
reverse("wellbeing_details", args=(wellbeing.pk,))
)
return render(request, "wellbeing/create.html", {"form": form})
form = WellBeingForm(instance=wellbeing, initial=data)
context = {"form": form, "wellbeing_id": wellbeing_id}
return render(request, "wellbeing/create.html", context)
@login_required
@require_http_methods(["GET"])
def wellbeing_details(request, wellbeing_id):
"""
Récupère toutes les informations d'une évaluation psychologique.
Args:
wellbeing_id (int) identifiant d'une évaluation psycho
"""
wellbeing = get_object_or_404(WellBeing, pk=wellbeing_id)
if not request.user.is_superuser and (
request.session.has_key("available_gymnast")
and wellbeing.gymnast.id not in request.session["available_gymnast"]
):
return wellbeing_listing(request)
return render(request, "wellbeing/details.html", {"wellbeing": wellbeing})
@login_required
@require_http_methods(["GET"])
def heightweight_listing(request, gymnast_id=None):
"""
Récupère la liste des couples taille/poids suivant (d'un gymnast si définit en paramètre).
Args:
gymnast_id (int) identifiant d'un gymnaste
"""
gymnast = None
if gymnast_id and (
request.user.is_superuser
or (
request.session.has_key("available_gymnast")
and gymnast_id in request.session["available_gymnast"]
)
):
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
heightweight_list = HeightWeight.objects.filter(gymnast=gymnast_id)
else:
if request.user.is_superuser:
heightweight_list = HeightWeight.objects.all()
else:
heightweight_list = HeightWeight.objects.filter(
gymnast__in=request.session["available_gymnast"]
)
context = {"heightweight_list": heightweight_list, "gymnast": gymnast}
return render(request, "heightweight/list.html", context)
@login_required
@require_http_methods(["GET", "POST"])
def heightweight_create_or_update(request, heightweight_id=None, gymnast_id=None):
"""
Formulaire de creation et modification d'un couple taille/couple.
Args:
heightweight_id (int) identifiant d'un couple taille/couple
gymnast_id (int) identifiant d'un gymnaste
"""
if heightweight_id:
heightweight = get_object_or_404(HeightWeight, pk=heightweight_id)
if not request.user.is_superuser and (
request.session.has_key("available_gymnast")
and heightweight.gymnast.id not in request.session["available_gymnast"]
):
return heightweight_listing(request)
data = {"gymnast_related": heightweight.gymnast}
else:
heightweight = None
data = None
if gymnast_id:
heightweight = (
HeightWeight.objects.filter(gymnast=gymnast_id)
.order_by("-date")
.first()
)
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
data = {"gymnast": gymnast_id, "gymnast_related": str(gymnast)}
if request.method == "POST":
form = HeightWeightForm(request.POST, instance=heightweight)
if form.is_valid():
heightweight = form.save()
# notification
receivers = []
date = form.cleaned_data["date"]
functionality = ContentType.objects.get(model="heightweight")
for notification in heightweight.gymnast.notifications.filter(
functionality=functionality
):
receivers.append(notification.user.email)
title = f"{heightweight.gymnast} : Nouveau poids/taille enregistré"
body = f"""<p>Bonjour,</p>
<p>Un nouveau poids/taille enregistré pour {heightweight.gymnast} pour le {date.strftime('%d %B %Y')}:</p>
<ul>
<li>Height: {heightweight.height} cm</li>
<li>Weight: {heightweight.weight} kg</li>
<li>BMI: {heightweight.bmi}</li>
</ul>"""
Email.objects.create(receivers=receivers, title=title, body=body)
send_mail(
title,
f"Un nouveau poids/taille enregistré pour {heightweight.gymnast} ({date}) : {heightweight.height} cm / {heightweight.weight} kg (BMI: {heightweight.bmi}).", # pylint: disable=line-too-long
settings.EMAIL_HOST_USER,
receivers,
fail_silently=False,
html_message=body + MAIL_FOOTER,
)
return HttpResponseRedirect(
reverse(
"gymnast_details_tab",
args=(form.cleaned_data["gymnast"].id, "physiological"),
)
)
return render(request, "heightweight/create.html", {"form": form})
form = HeightWeightForm(instance=heightweight, initial=data)
context = {
"form": form,
"gymnast_id": gymnast_id,
"heightweight_id": heightweight_id,
}
return render(request, "heightweight/create.html", context)

View File

@ -89,7 +89,6 @@
{% endfor %}
</select>
</div>
<label id="month_management" for="select_month_number" class="col-md-1 col-form-label">month</label>
<div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2">
<select id="select_month_number" class="selectpicker">
<option value="">---</option>

View File

@ -0,0 +1,335 @@
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="keywords" content="">
<meta name="description" content="">
<meta name="author" content="Gregory Trullemans">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="apple-touch-icon" sizes="76x76" href="{% static "img/apple-icon.png" %}">
<link rel="icon" type="image/png" href="{% static "img/favicon.png" %}">
<title>{{ gymnast.first_name }} {{ gymnast.last_name }}</title>
<!-- Fonts and icons -->
<link href="https://fonts.googleapis.com/css?family=Poppins:200,300,400,600,700,800" rel="stylesheet" />
<!-- Font Awesome Pro -->
<link href="{% static "css/gymnast_report.css" %}" rel="stylesheet" />
<link href="{% static "css/font_awesome_all_5.15.3.css" %}" rel="stylesheet" />
<link href="{% static "css/black-dashboard_report.css" %}" rel="stylesheet" />
</head>
<header class="white-content">
<div class="row">
<div id="header-left" class="col-7 text-12">
{{ SITE_TITLE }} - {{ CLUB_NAME }}<br />
{{ ADDRESS }} - {{ ZIP }} {{ CITY }}<br />
(tbd)
</div>
<div id="header-right" class="col-5 text-right text-12">
Head Coach : {{ HEAD_COACH }}<br />
{{ HEAD_COACH_EMAIL }}<br />
{{ today | date:"j F Y" }}
</div>
</div>
</header>
<br />
<body class="white-content">
<div class="row">
<div class="col-2">
<img src="{% static 'img/default-avatar.png' %}" class="profil_img" />
</div>
<div class="col-9" style="text-align: justify;">
<h3 class="title mb-0">{{ gymnast.first_name }} {{ gymnast.last_name }}</h3>
{% if gymnast.informations %}
{{ gymnast.to_markdown | safe }}
{% endif %}
</div>
</div>
<br />
<div class="row">
<div class="col-6">
<h4 class="mb-1">Physiological ({{ source_wellbeing_score_quantity }})</h4>
{% if source_wellbeing_score.average_mindstate_value %}
<table class="table">
<thead>
<th class="pt-0 pb-0"></th>
<th class="pt-0 pb-0 text-center">Min</th>
<th class="pt-0 pb-0 text-center">Average</th>
<th class="pt-0 pb-0 text-center">Max</th>
</thead>
<tr>
<td class="pt-0 pb-0"><b>Mind state</b></td>
<td class="pt-0 pb-0 text-center">{{ source_wellbeing_score.min_mindstate_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ source_wellbeing_score.average_mindstate_value|stringformat:".1f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ source_wellbeing_score.max_mindstate_value }}</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b>Sleep</b></td>
<td class="pt-0 pb-0 text-center">{{ source_wellbeing_score.min_sleep_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ source_wellbeing_score.average_sleep_value|stringformat:".1f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ source_wellbeing_score.max_sleep_value }}</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b>Stress</b></td>
<td class="pt-0 pb-0 text-center">{{ source_wellbeing_score.min_stress_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ source_wellbeing_score.average_stress_value|stringformat:".1f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ source_wellbeing_score.max_stress_value }}</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b>Fatigue</b></td>
<td class="pt-0 pb-0 text-center">{{ source_wellbeing_score.min_fatigue_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ source_wellbeing_score.average_fatigue_value|stringformat:".1f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ source_wellbeing_score.max_fatigue_value }}</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b>Muscle soreness</b></td>
<td class="pt-0 pb-0 text-center">{{ source_wellbeing_score.min_muscle_soreness_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ source_wellbeing_score.average_muscle_soreness_value|stringformat:".1f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ source_wellbeing_score.max_muscle_soreness_value }}</td>
</tr>
</table>
{% else %}
No information encoded for selected week.
{% endif %}
</div>
<div class="col-6">
<h4 class="mb-1">Physiological ({{ target_wellbeing_score_quantity }})</h4>
{% if target_wellbeing_score.average_mindstate_value %}
<table class="table">
<thead>
<th class="pt-0 pb-0"></th>
<th class="pt-0 pb-0 text-center">Min</th>
<th class="pt-0 pb-0 text-center">Average</th>
<th class="pt-0 pb-0 text-center">Max</th>
</thead>
<tr>
<td class="pt-0 pb-0"><b>Mind state</b></td>
<td class="pt-0 pb-0 text-center">{{ target_wellbeing_score.min_mindstate_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ target_wellbeing_score.average_mindstate_value|stringformat:".1f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ target_wellbeing_score.max_mindstate_value }}</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b>Sleep</b></td>
<td class="pt-0 pb-0 text-center">{{ target_wellbeing_score.min_sleep_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ target_wellbeing_score.average_sleep_value|stringformat:".1f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ target_wellbeing_score.max_sleep_value }}</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b>Stress</b></td>
<td class="pt-0 pb-0 text-center">{{ target_wellbeing_score.min_stress_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ target_wellbeing_score.average_stress_value|stringformat:".1f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ target_wellbeing_score.max_stress_value }}</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b>Fatigue</b></td>
<td class="pt-0 pb-0 text-center">{{ target_wellbeing_score.min_fatigue_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ target_wellbeing_score.average_fatigue_value|stringformat:".1f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ target_wellbeing_score.max_fatigue_value }}</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b>Muscle soreness</b></td>
<td class="pt-0 pb-0 text-center">{{ target_wellbeing_score.min_muscle_soreness_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ target_wellbeing_score.average_muscle_soreness_value|stringformat:".1f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ target_wellbeing_score.max_muscle_soreness_value }}</td>
</tr>
</table>
{% else %}
No information encoded for selected week.
{% endif %}
</div>
</div>
<div class="row">
<div class="col-6">
<h4 class="mb-1">Intensity ({{ source_intensity_quantity }})</h4>
{% if source_intensity_quantity %}
<table class="table">
<thead>
<th class="pt-0 pb-0 text-right"></th>
<th class="pt-0 pb-0 text-center">Min</th>
<th class="pt-0 pb-0 text-center">Average</th>
<th class="pt-0 pb-0 text-center">Max</th>
</thead>
<tbody>
<tr>
<td class="pt-0 pb-0"><b>Time</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.min_intensity_time_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ source_intensity_value.average_intensity_time_value|stringformat:".0f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.max_intensity_time_value }}</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b>Diff.</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.min_intensity_difficulty_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ source_intensity_value.average_intensity_difficulty_value|stringformat:".0f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.max_intensity_difficulty_value }}</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b># Skill</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.min_quantity_of_skill_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ source_intensity_value.average_quantity_of_skill_value|stringformat:".0f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.max_quantity_of_skill_value }}</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b># Passes</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.min_number_of_passes_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ source_intensity_value.average_number_of_passes_value|stringformat:".0f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.max_number_of_passes_value }}</td>
</tr>
</tbody>
</table>
{% else %}
No training intensity encoded for the selected period.
{% endif %}
</div>
<div class="col-6">
<h4 class="mb-1">Intensity ({{ target_intensity_quantity }})</h4>
{% if target_intensity_quantity %}
<table class="table">
<thead>
<th class="pt-0 pb-0 text-right"></th>
<th class="pt-0 pb-0 text-center">Min</th>
<th class="pt-0 pb-0 text-center">Average</th>
<th class="pt-0 pb-0 text-center">Max</th>
</thead>
<tbody>
<tr>
<td class="pt-0 pb-0"><b>Time</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.min_intensity_time_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ target_intensity_value.average_intensity_time_value|stringformat:".0f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.max_intensity_time_value }}</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b>Diff.</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.min_intensity_difficulty_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ target_intensity_value.average_intensity_difficulty_value|stringformat:".0f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.max_intensity_difficulty_value }}</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b># Skill</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.min_quantity_of_skill_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ target_intensity_value.average_quantity_of_skill_value|stringformat:".0f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.max_quantity_of_skill_value }}</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b># Passes</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.min_number_of_passes_value }}</td>
<td class="pt-0 pb-0 text-center"><b>{{ target_intensity_value.average_number_of_passes_value|stringformat:".0f" }}</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.max_number_of_passes_value }}</td>
</tr>
</tbody>
</table>
{% else %}
No training intensity encoded for the selected period.
{% endif %}
</div>
</div>
<div class="row">
<div class="col-6">
<h4 class="mb-1">Training quality</h4>
{% if source_intensity_value.average_time_quality %}
<table class="table">
<thead>
<th class="pt-0 pb-0 text-right"></th>
<th class="pt-0 pb-0 text-center">Min</th>
<th class="pt-0 pb-0 text-center">Average</th>
<th class="pt-0 pb-0 text-center">Max</th>
</thead>
<tbody>
<tr>
<td class="pt-0 pb-0"><b>Time</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.min_time_quality | stringformat:".0f" }}%</td>
<td class="pt-0 pb-0 text-center"><b>{{ source_intensity_value.average_time_quality | stringformat:".0f" }}%</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.max_time_quality | stringformat:".0f" }}%</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b>Diff.</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.min_difficulty_quality | stringformat:".0f" }}%</td>
<td class="pt-0 pb-0 text-center"><b>{{ source_intensity_value.average_difficulty_quality | stringformat:".0f" }}%</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.max_difficulty_quality | stringformat:".0f" }}%</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b># Skill</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.min_quantity_of_skill_quality | stringformat:".0f" }}%</td>
<td class="pt-0 pb-0 text-center"><b>{{ source_intensity_value.average_quantity_of_skill_quality | stringformat:".0f" }}%</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.max_quantity_of_skill_quality | stringformat:".0f" }}%</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b># Passes</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.min_number_of_passes_quality | stringformat:".0f" }}%</td>
<td class="pt-0 pb-0 text-center"><b>{{ source_intensity_value.average_number_of_passes_quality | stringformat:".0f" }}%</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.max_number_of_passes_quality | stringformat:".0f" }}%</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b>Average</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.min_average_training_quality | stringformat:".0f" }}%</td>
<td class="pt-0 pb-0 text-center"><b>{{ source_intensity_value.average_average_training_quality | stringformat:".0f" }}%</b></td>
<td class="pt-0 pb-0 text-center">{{ source_intensity_value.max_average_training_quality | stringformat:".0f" }}%</td>
</tr>
</tbody>
</table>
{% else %}
No training quality computed for the selected period.
{% endif %}
</div>
<div class="col-6">
<h4 class="mb-1">Training quality</h4>
{% if target_intensity_value.average_time_quality %}
<table class="table">
<thead>
<th class="pt-0 pb-0 text-right"></th>
<th class="pt-0 pb-0 text-center">Min</th>
<th class="pt-0 pb-0 text-center">Average</th>
<th class="pt-0 pb-0 text-center">Max</th>
</thead>
<tbody>
<tr>
<td class="pt-0 pb-0"><b>Time</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.min_time_quality | stringformat:".0f" }}%</td>
<td class="pt-0 pb-0 text-center"><b>{{ target_intensity_value.average_time_quality | stringformat:".0f" }}%</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.max_time_quality | stringformat:".0f" }}%</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b>Diff.</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.min_difficulty_quality | stringformat:".0f" }}%</td>
<td class="pt-0 pb-0 text-center"><b>{{ target_intensity_value.average_difficulty_quality | stringformat:".0f" }}%</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.max_difficulty_quality | stringformat:".0f" }}%</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b># Skill</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.min_quantity_of_skill_quality | stringformat:".0f" }}%</td>
<td class="pt-0 pb-0 text-center"><b>{{ target_intensity_value.average_quantity_of_skill_quality | stringformat:".0f" }}%</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.max_quantity_of_skill_quality | stringformat:".0f" }}%</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b># Passes</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.min_number_of_passes_quality | stringformat:".0f" }}%</td>
<td class="pt-0 pb-0 text-center"><b>{{ target_intensity_value.average_number_of_passes_quality | stringformat:".0f" }}%</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.max_number_of_passes_quality | stringformat:".0f" }}%</td>
</tr>
<tr>
<td class="pt-0 pb-0"><b>Average</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.min_average_training_quality | stringformat:".0f" }}%</td>
<td class="pt-0 pb-0 text-center"><b>{{ target_intensity_value.average_average_training_quality | stringformat:".0f" }}%</b></td>
<td class="pt-0 pb-0 text-center">{{ target_intensity_value.max_average_training_quality | stringformat:".0f" }}%</td>
</tr>
</tbody>
</table>
{% else %}
No training quality computed for the selected period.
{% endif %}
</div>
</div>
</body>
</html>

View File

@ -32,21 +32,11 @@ gymnast_urlpatterns = [
views.gymnast_display_events_and_notes,
name="gymnast_display_events_and_notes",
),
# path(
# r"details/<int:gymnast_id>/injury/",
# views.gymnast_display_injury,
# name="gymnast_display_injury",
# ),
path(
r"details/<int:gymnast_id>/scores_chrono/",
views.gymnast_display_scores_chrono,
name="gymnast_display_scores_chrono",
),
# path(
# r"details/<int:gymnast_id>/wellbeing/",
# views.gymnast_display_wellbeing,
# name="gymnast_display_wellbeing",
# ),
path(
r"details/<int:gymnast_id>/physiological/",
views.gymnast_display_physiological,
@ -74,11 +64,6 @@ gymnast_urlpatterns = [
views_reports.get_distinct_week_number_for_season_and_gymnast,
name="get_distinct_week_number_for_season_and_gymnast",
),
# path(
# r"report/<int:gymnast_id>/",
# views_reports.generate_week_report,
# name="gymnast_report_export",
# ),
path(
r"report/periodical/<int:gymnast_id>/season/<str:season>/week_number/<int:week_number>/",
views_reports.generate_week_report,
@ -94,6 +79,11 @@ gymnast_urlpatterns = [
views_reports.generate_season_report,
name="gymnast_report_export_for_season",
),
path(
r"report/comparison/<int:gymnast_id>/season/<str:season_source>/week/<int:week_source>/season/<str:season_target>/week/<int:week_target>",
views_reports.generate_report_week_comparison,
name="gymnast_generate_report_week_comparison",
),
path(
r"report/timeline/<int:gymnast_id>/",
views_reports.generate_timeline_report,

View File

@ -41,6 +41,9 @@ from jarvis.followup.models import (
from jarvis.followup.models import LEARNING_STEP_CHOICES
from jarvis.followup.views_physiological import get_wellbeing_stats_for_season_week
from jarvis.followup.views_intensity import get_intensity_stats_for_season_week
from jarvis.tools.models import Season
# from jarvis.tools.pdf_generator import GymnastReportDocument
@ -177,7 +180,10 @@ def __get_distinct_week_number_for_season_and_gymnast(gymnast_id, season):
.distinct("week_number")
.order_by("week_number")
)
return list(dict.fromkeys(weeknumber_list))
weeknumber_list = list(dict.fromkeys(weeknumber_list))
weeknumber_list.sort(reverse=True)
return weeknumber_list
def get_distinct_week_number_for_season_and_gymnast(gymnast_id, season):
@ -208,15 +214,15 @@ def report_choice(request, gymnast_id):
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
season, week_number = from_date_to_week_number(today)
season_list = __get_distinct_followup_season_for_gymnast(gymnast_id)
week_number_list = sorted(
__get_distinct_week_number_for_season_and_gymnast(gymnast_id, season)
week_number_list = __get_distinct_week_number_for_season_and_gymnast(
gymnast_id, season
)
context = {
"gymnast": gymnast,
"actual_season": season,
"season_list": season_list,
"week_number": week_number,
"actual_week_number": week_number,
"week_number_list": week_number_list,
}
return render(request, "gymnasts/report_choices.html", context)
@ -776,307 +782,50 @@ def generate_report_for_period(
@login_required
@require_http_methods(["GET"])
def generate_report_for_period(
request, gymnast_id, season, period_value, date_begin, date_end, period="week"
def generate_report_week_comparison(
request, gymnast_id, season_source, week_source, season_target, week_target
):
"""Génère un rapport de toutes les informations entre les deux dates passées en paramètre.
"""Génère un rapport avec une comparaison de deux semaines entre elles.
Args:
gymnast_id (int) Identifiant de la classe Gymnast
date_begin (datetime) Date de début de la période à considérer # pendulum
date_end (datetime) Date de fin de la période à considérer # pendulum
gymnast_id (int) Identifiant de la classe Gymnast
season_source (str) saison de comparaison
week_source (int) semaine de comparaison
season_target (str) saison comparée
week_target (int) semaine comparée
"""
today = pendulum.now().date()
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
season_informations = gymnast.season_informations.filter(season=season).first()
# season_informations = gymnast.season_informations.filter(season=season).first()
# PHYSIOLOGICAL INFORMATIONS
# WellBeing Score
wellbeing_score_quantity = gymnast.wellbeings.filter(
date__gte=date_begin, date__lte=date_end
source_wellbeing_score_quantity = gymnast.wellbeings.filter(
season=season_source, week_number=week_source
).count()
wellbeing_score = gymnast.wellbeings.filter(
date__gte=date_begin, date__lte=date_end
).aggregate(
min_mindstate_value=Min("mindstate"),
average_mindstate_value=Avg("mindstate"),
max_mindstate_value=Max("mindstate"),
min_sleep_value=Min("sleep"),
average_sleep_value=Avg("sleep"),
max_sleep_value=Max("sleep"),
min_stress_value=Min("stress"),
average_stress_value=Avg("stress"),
max_stress_value=Max("stress"),
min_fatigue_value=Min("fatigue"),
average_fatigue_value=Avg("fatigue"),
max_fatigue_value=Max("fatigue"),
min_muscle_soreness_value=Min("muscle_soreness"),
average_muscle_soreness_value=Avg("muscle_soreness"),
max_muscle_soreness_value=Max("muscle_soreness"),
source_wellbeing_score = get_wellbeing_stats_for_season_week(
gymnast_id, season_source, week_source
)
height_weight_quantity = gymnast.height_weight.filter(
date__gte=date_begin, date__lte=date_end
target_wellbeing_score_quantity = gymnast.wellbeings.filter(
season=season_target, week_number=week_target
).count()
height_weight_value = gymnast.height_weight.filter(
date__gte=date_begin, date__lte=date_end
).aggregate(
min_height_value=Min("height"),
average_height_value=Avg("height"),
max_height_value=Max("height"),
min_weight_value=Min("weight"),
average_weight_value=Avg("weight"),
max_weight_value=Max("weight"),
target_wellbeing_score = get_wellbeing_stats_for_season_week(
gymnast_id, season_target, week_target
)
intensity_quantity = gymnast.intensities.filter(
date__gte=date_begin, date__lte=date_end
# Intensity Score
source_intensity_quantity = gymnast.intensities.filter(
season=season_source, week_number=week_source
).count()
intensity_value = gymnast.intensities.filter(
date__gte=date_begin, date__lte=date_end
).aggregate(
average_intensity_time_value=Avg("time"),
average_intensity_difficulty_value=Avg("difficulty"),
average_quantity_of_skill_value=Avg("quantity_of_skill"),
average_number_of_passes_value=Avg("number_of_passes"),
average_time_quality=Avg("time_quality"),
average_difficulty_quality=Avg("difficulty_quality"),
average_quantity_of_skill_quality=Avg("quantity_of_skill_quality"),
average_number_of_passes_quality=Avg("number_of_passes_quality"),
average_average_training_quality=Avg("average_training_quality"),
min_intensity_time_value=Min("time"),
min_intensity_difficulty_value=Min("difficulty"),
min_quantity_of_skill_value=Min("quantity_of_skill"),
min_number_of_passes_value=Min("number_of_passes"),
min_time_quality=Min("time_quality"),
min_difficulty_quality=Min("difficulty_quality"),
min_quantity_of_skill_quality=Min("quantity_of_skill_quality"),
min_number_of_passes_quality=Min("number_of_passes_quality"),
min_average_training_quality=Min("average_training_quality"),
max_intensity_time_value=Max("time"),
max_intensity_difficulty_value=Max("difficulty"),
max_quantity_of_skill_value=Max("quantity_of_skill"),
max_number_of_passes_value=Max("number_of_passes"),
max_time_quality=Max("time_quality"),
max_difficulty_quality=Max("difficulty_quality"),
max_quantity_of_skill_quality=Max("quantity_of_skill_quality"),
max_number_of_passes_quality=Max("number_of_passes_quality"),
max_average_training_quality=Max("average_training_quality"),
source_intensity_value = get_intensity_stats_for_season_week(
gymnast_id, season_source, week_source
)
injury_list = gymnast.injuries.filter(
date__gte=date_begin, date__lte=date_end
).order_by("date")
# BEST TOF
number_of_tof_straightjump = Chrono.objects.filter(
gymnast=gymnast, date__gte=date_begin, date__lte=date_end, chrono_type=0
target_intensity_quantity = gymnast.intensities.filter(
season=season_target, week_number=week_target
).count()
best_tof_straightjump = Chrono.objects.filter(
gymnast=gymnast, date__gte=date_begin, date__lte=date_end, chrono_type=0
).aggregate(
min_score=Min("score"),
min_tof=Min("tof"),
average_score=Avg("score"),
average_tof=Avg("tof"),
max_score=Max("score"),
max_tof=Max("tof"),
)
number_of_tof_q1r1 = Chrono.objects.filter(
gymnast=gymnast, date__gte=date_begin, date__lte=date_end, chrono_type=1
).count()
best_tof_q1r1 = Chrono.objects.filter(
gymnast=gymnast, date__gte=date_begin, date__lte=date_end, chrono_type=1
).aggregate(
min_score=Min("score"),
min_tof=Min("tof"),
average_score=Avg("score"),
average_tof=Avg("tof"),
max_score=Max("score"),
max_tof=Max("tof"),
)
number_of_tof_q1r2 = Chrono.objects.filter(
gymnast=gymnast, date__gte=date_begin, date__lte=date_end, chrono_type=2
).count()
best_tof_q1r2 = Chrono.objects.filter(
gymnast=gymnast, date__gte=date_begin, date__lte=date_end, chrono_type=2
).aggregate(
min_score=Min("score"),
min_tof=Min("tof"),
average_score=Avg("score"),
average_tof=Avg("tof"),
max_score=Max("score"),
max_tof=Max("tof"),
)
number_of_tof_q2r1 = Chrono.objects.filter(
gymnast=gymnast, date__gte=date_begin, date__lte=date_end, chrono_type=3
).count()
best_tof_q2r1 = Chrono.objects.filter(
gymnast=gymnast, date__gte=date_begin, date__lte=date_end, chrono_type=3
).aggregate(
min_score=Min("score"),
min_tof=Min("tof"),
average_score=Avg("score"),
average_tof=Avg("tof"),
max_score=Max("score"),
max_tof=Max("tof"),
)
number_of_tof_sf = Chrono.objects.filter(
gymnast=gymnast, date__gte=date_begin, date__lte=date_end, chrono_type=3
).count()
best_tof_sf = Chrono.objects.filter(
gymnast=gymnast, date__gte=date_begin, date__lte=date_end, chrono_type=3
).aggregate(
min_score=Min("score"),
min_tof=Min("tof"),
average_score=Avg("score"),
average_tof=Avg("tof"),
max_score=Max("score"),
max_tof=Max("tof"),
)
number_of_tof_f = Chrono.objects.filter(
gymnast=gymnast, date__gte=date_begin, date__lte=date_end, chrono_type=3
).count()
best_tof_f = Chrono.objects.filter(
gymnast=gymnast, date__gte=date_begin, date__lte=date_end, chrono_type=3
).aggregate(
min_score=Min("score"),
min_tof=Min("tof"),
average_score=Avg("score"),
average_tof=Avg("tof"),
max_score=Max("score"),
max_tof=Max("tof"),
)
# BEST SCORES
best_point_routine_1 = (
Point.objects.filter(
gymnast=gymnast, event__date_begin__lte=date_begin, routine_type=1
)
.order_by("-total")
.first()
)
best_point_routine_2 = (
Point.objects.filter(
gymnast=gymnast, event__date_begin__lte=date_begin, routine_type=2
)
.order_by("-total")
.first()
)
best_point_routine_3 = (
Point.objects.filter(
gymnast=gymnast, event__date_begin__lte=date_begin, routine_type=3
)
.order_by("-total")
.first()
)
best_point_routine_4 = (
Point.objects.filter(
gymnast=gymnast, event__date_begin__lte=date_begin, routine_type=4
)
.order_by("-total")
.first()
)
best_point_routine_5 = (
Point.objects.filter(
gymnast=gymnast, event__date_begin__lte=date_begin, routine_type=5
)
.order_by("-total")
.first()
)
# ROUTINES
q1r1 = (
gymnast.has_routine.filter(routine_type=1)
.filter(date_begin__lte=date_begin)
.filter(Q(date_end__gte=date_begin) | Q(date_end__isnull=True))
.first()
)
routine_1_done_stat = gymnast.number_of_routine_done.filter(
routine_type=1, date__gte=date_begin, date__lte=date_end
).aggregate(
total_try=Sum("number_of_try"), total_succeeded=Sum("number_of_successes")
)
q1r2 = (
gymnast.has_routine.filter(routine_type=2)
.filter(date_begin__lte=date_begin)
.filter(Q(date_end__gte=date_begin) | Q(date_end__isnull=True))
.first()
)
routine_2_done_stat = gymnast.number_of_routine_done.filter(
routine_type=2, date__gte=date_begin, date__lte=date_end
).aggregate(
total_try=Sum("number_of_try"), total_succeeded=Sum("number_of_successes")
)
q2r1 = (
gymnast.has_routine.filter(routine_type=3)
.filter(date_begin__lte=date_begin)
.filter(Q(date_end__gte=date_begin) | Q(date_end__isnull=True))
.first()
)
routine_3_done_stat = gymnast.number_of_routine_done.filter(
routine_type=3, date__gte=date_begin, date__lte=date_end
).aggregate(
total_try=Sum("number_of_try"), total_succeeded=Sum("number_of_successes")
)
sfinal = (
gymnast.has_routine.filter(routine_type=4)
.filter(date_begin__lte=date_begin)
.filter(Q(date_end__gte=date_begin) | Q(date_end__isnull=True))
.first()
)
routine_4_done_stat = gymnast.number_of_routine_done.filter(
routine_type=4, date__gte=date_begin, date__lte=date_end
).aggregate(
total_try=Sum("number_of_try"), total_succeeded=Sum("number_of_successes")
)
final = (
gymnast.has_routine.filter(routine_type=5)
.filter(date_begin__lte=date_begin)
.filter(Q(date_end__gte=date_begin) | Q(date_end__isnull=True))
.first()
)
routine_5_done_stat = gymnast.number_of_routine_done.filter(
routine_type=5, date__gte=date_begin, date__lte=date_end
).aggregate(
total_try=Sum("number_of_try"), total_succeeded=Sum("number_of_successes")
)
# LAST LEARNED SKILLS
learned_skills = LearnedSkill.objects.filter(
gymnast=gymnast.id, date__gte=date_begin, date__lte=date_end
).order_by("-date")
# PLANNED SKILLS
plan_list = (
Plan.objects.filter(gymnast=gymnast, educative__in=Skill.objects.all())
.filter(Q(is_done=False) | Q(date__gte=date.today()))
.order_by("date")
.distinct()[:6]
)
# NEXT EVENTS
next_event_list = Event.objects.filter(
gymnasts=gymnast, date_begin__gte=date_begin
).order_by("date_begin")[:5]
# NOTES
# begin_of_the_week = date_begin
# if date_begin.weekday() != 0:
# begin_of_the_week -= timedelta(date_begin.weekday())
notes = (
gymnast.remarks.filter(date__gte=date_begin, date__lte=date_end)
.filter(status=1)
.order_by("date")
target_intensity_value = get_intensity_stats_for_season_week(
gymnast_id, season_target, week_target
)
context = {
@ -1089,67 +838,71 @@ def generate_report_for_period(
"HEAD_COACH": settings.HEAD_COACH,
"MOBILE_PHONE": settings.MOBILE_PHONE,
"HEAD_COACH_EMAIL": settings.HEAD_COACH_EMAIL,
"season": season,
"date_begin": date_begin,
"date_end": date_end,
"period": period,
"period_value": period_value,
# "season": season,
# "date_begin": date_begin,
# "date_end": date_end,
# "period": period,
# "period_value": period_value,
"today": today,
# GYMNAST INFORMATIONS
"gymnast": gymnast,
"season_informations": season_informations,
# "season_informations": season_informations,
# MEDICAL INFORMATIONS
"wellbeing_score_quantity": wellbeing_score_quantity,
"wellbeing_score": wellbeing_score,
"height_weight_value": height_weight_value,
"injury_list": injury_list,
# INTENSITY
"intensity_quantity": intensity_quantity,
"intensity_value": intensity_value,
# TOF
"number_of_tof_straightjump": number_of_tof_straightjump,
"best_tof_straightjump": best_tof_straightjump,
"number_of_tof_q1r1": number_of_tof_q1r1,
"best_tof_q1r1": best_tof_q1r1,
"number_of_tof_q1r2": number_of_tof_q1r2,
"best_tof_q1r2": best_tof_q1r2,
"number_of_tof_q2r1": number_of_tof_q2r1,
"best_tof_q2r1": best_tof_q2r1,
"number_of_tof_sf": number_of_tof_sf,
"best_tof_sf": best_tof_sf,
"number_of_tof_f": number_of_tof_f,
"best_tof_f": best_tof_f,
# SCORES
"best_point_q1r1": best_point_routine_1,
"best_point_q1r2": best_point_routine_2,
"best_point_q2r1": best_point_routine_3,
"best_point_sf": best_point_routine_4,
"best_point_f": best_point_routine_5,
"q1r1": q1r1,
"q1r2": q1r2,
"q2r1": q2r1,
"sfinal": sfinal,
"final": final,
"q1r1_done_stat": routine_1_done_stat,
"q1r2_done_stat": routine_2_done_stat,
"q2r1_done_stat": routine_3_done_stat,
"sfinal_done_stat": routine_4_done_stat,
"final_done_stat": routine_5_done_stat,
"LEARNING_STEP_CHOICES": LEARNING_STEP_CHOICES,
"learned_skills": learned_skills,
"plan_list": plan_list,
"next_event_list": next_event_list,
"notes": notes,
"source_wellbeing_score_quantity": source_wellbeing_score_quantity,
"source_wellbeing_score": source_wellbeing_score,
"target_wellbeing_score_quantity": target_wellbeing_score_quantity,
"target_wellbeing_score": target_wellbeing_score,
# "height_weight_value": height_weight_value,
# "injury_list": injury_list,
# # INTENSITY
"source_intensity_quantity": source_intensity_quantity,
"source_intensity_value": source_intensity_value,
"target_intensity_quantity": target_intensity_quantity,
"target_intensity_value": target_intensity_value,
# # TOF
# "number_of_tof_straightjump": number_of_tof_straightjump,
# "best_tof_straightjump": best_tof_straightjump,
# "number_of_tof_q1r1": number_of_tof_q1r1,
# "best_tof_q1r1": best_tof_q1r1,
# "number_of_tof_q1r2": number_of_tof_q1r2,
# "best_tof_q1r2": best_tof_q1r2,
# "number_of_tof_q2r1": number_of_tof_q2r1,
# "best_tof_q2r1": best_tof_q2r1,
# "number_of_tof_sf": number_of_tof_sf,
# "best_tof_sf": best_tof_sf,
# "number_of_tof_f": number_of_tof_f,
# "best_tof_f": best_tof_f,
# # SCORES
# "best_point_q1r1": best_point_routine_1,
# "best_point_q1r2": best_point_routine_2,
# "best_point_q2r1": best_point_routine_3,
# "best_point_sf": best_point_routine_4,
# "best_point_f": best_point_routine_5,
# "q1r1": q1r1,
# "q1r2": q1r2,
# "q2r1": q2r1,
# "sfinal": sfinal,
# "final": final,
# "q1r1_done_stat": routine_1_done_stat,
# "q1r2_done_stat": routine_2_done_stat,
# "q2r1_done_stat": routine_3_done_stat,
# "sfinal_done_stat": routine_4_done_stat,
# "final_done_stat": routine_5_done_stat,
# "LEARNING_STEP_CHOICES": LEARNING_STEP_CHOICES,
# "learned_skills": learned_skills,
# "plan_list": plan_list,
# "next_event_list": next_event_list,
# "notes": notes,
}
return render(request, "gymnasts/reports/report_periodical.html", context)
return render(request, "gymnasts/reports/report_week_comparison.html", context)
# response = HttpResponse(content_type="application/pdf")
# response[
# "Content-Disposition"
# ] = f"attachment; filename={gymnast.last_name}_{gymnast.first_name}_{period}-report_{date_begin}_{date_end}.pdf" # pylint: disable=line-too-long
# html = render_to_string("gymnasts/reports/report_periodical.html", context)
# html = render_to_string("gymnasts/reports/report_week_comparison.html", context)
# # font_config = FontConfiguration()
# HTML(string=html, base_url=request.build_absolute_uri()).write_pdf(