From 825013990818cc8487b3ee302392521b7e88ef7c Mon Sep 17 00:00:00 2001 From: Gregory Trullemans Date: Sat, 19 Nov 2022 18:51:20 +0100 Subject: [PATCH] Big update on report generation --- config/settings.py | 9 + templates/people/gymnasts/report.html | 588 +++++++++------------- templates/ultron/dashboard/dashboard.html | 2 +- ultron/people/views.py | 216 ++++++++ 4 files changed, 463 insertions(+), 352 deletions(-) diff --git a/config/settings.py b/config/settings.py index 8caf95981a..025e7c26de 100644 --- a/config/settings.py +++ b/config/settings.py @@ -161,3 +161,12 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # https://warehouse.python.org/project/whitenoise/ STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" + +SITE_TITLE = env("SITE_TITLE", default=None) +CLUB_NAME = env("CLUB_NAME", default=None) +ADDRESS = env("ADDRESS", default=None) +CITY = env("CITY", default=None) +ZIP = env("ZIP", default=None) +HEAD_COACH = env("HEAD_COACH", default=None) +MOBILE_PHONE = env("MOBILE_PHONE", default=None) +HEAD_COACH_EMAIL = env("HEAD_COACH_EMAIL", default=None) diff --git a/templates/people/gymnasts/report.html b/templates/people/gymnasts/report.html index 2535f97000..530b968e26 100644 --- a/templates/people/gymnasts/report.html +++ b/templates/people/gymnasts/report.html @@ -1,7 +1,4 @@ {% load static %} -{% load menuitems %} -{% load has_group %} -{% load is_user_equal_to_gymnast %} @@ -18,364 +15,253 @@ - • Ultron - {{ gymnast.first_name }} {{ gymnast.last_name }} • + {{ gymnast.first_name }} {{ gymnast.last_name }} + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - -
-
-
-
-
- Rapport - -

{{ gymnast.first_name }} {{ gymnast.last_name }}

- {{ gymnast.club.name }}
- {{ gymnast.trainings_by_week }} training/week for {{ gymnast.hours_by_week }} hours/week
- - {% if height_weight %} - {{ height_weight.0.height }}cm - {{ height_weight.0.weight }}kg ({{ height_weight.0.date | date:"d-m-Y" }})
- {% endif %} - -
- {% if user_is_trainer and gymnast.informations %} -

{{ gymnast.informations }}

-
- {% endif %} - -

Personnal bests :

-
  • - 10 | : {{ best_straightjump.0.tof }} ({{ best_straightjump.0.date | date:"d-m-Y" }}) -
  • -
  • - R1 : {{ best_tof_routine_1.0.tof }} ({{ best_tof_routine_1.0.date | date:"d-m-Y" }}) -
  • -
  • - R2 : {{ best_tof_routine_2.0.tof }} ({{ best_tof_routine_2.0.date | date:"d-m-Y" }}) -
  • -
  • - Routine : {% if best_routine %}{{ best_routine.0.tof }} ({{ best_routine.0.date | date:"d-m-Y" }}){% else %} (no information){% endif %} -
  • - -
    -
    -
    - -
    - (doughnut) -
    - -
    - (stat par level/rank) -
    - -
    - -
    -
    - -
    -
    -
    -
    - -
    - -
    - - {% if user_is_trainer or request.user|is_user_equal_to_gymnast:gymnast.id %} -
    - {% endif %} - -
    - - -
    -
    -
    - -
    +
    +
    +
    + {{ SITE_TITLE }} - {{ CLUB_NAME }}
    + {{ ADDRESS }} - {{ ZIP }} {{ CITY }}
    + Season {{ season }} - week {{ week_number }} +
    +
    + Head Coach : {{ HEAD_COACH }}
    + {{ HEAD_COACH_EMAIL }}
    + {{ today | date:"j F Y" }}
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
    + {% if last_mindstate or last_height_weigth or mindstate_analyse or height_analyse or weight_analyse %} +

    Physiological

    + + + + + + + + + + + + + + + + +
    Mind state{{ last_mindstate.score }}{{ mindstate_analyse }}
    Height{{ last_height_weigth.height }}{{ height_analyse }}
    Weight{{ last_height_weigth.weight }}{{ weight_analyse }}
    + {% endif %} +
    + +
    +
    +
    +
    +

    Best ToF

    + {% if best_tof_straightjump or best_tof_routine_1 or best_tof_routine_2 %} + + + + + + + + {% if best_tof_straightjump %} + + + + + + + {% endif %} + {% if best_tof_routine_1 %} + + + + + + + {% endif %} + {% if best_tof_routine_2 %} + + + + + + + {% endif %} +
    ChronoToF
    ToF |{{ best_tof_straightjump.score }}{{ best_tof_straightjump.tof }}{{ best_tof_straightjump.date|date:"j M Y" }}
    Routine 1{{ best_tof_routine_1.score }}{{ best_tof_routine_1.tof }}{{ best_tof_routine_1.date|date:"j M Y" }}
    Routine 2{{ best_tof_routine_2.score }}{{ best_tof_routine_2.tof }}{{ best_tof_routine_2.date|date:"j M Y" }}
    + {% endif %} +
    +
    +
    +

    Best Scores

    + {% if best_point_routine_1 or best_point_routine_2 %} + + + + + + + + + + + {% if best_point_routine_1 %} + + + + + + + + + + {% endif %} + {% if best_point_routine_2 %} + + + + + + + + + + {% endif %} +
    Exe.Diff.HDToFTotal
    Routine 1{{ best_point_routine_1.point_execution }}{{ best_point_routine_1.point_difficulty }}{{ best_point_routine_1.point_horizontal_displacement }}{{ best_point_routine_1.point_time_of_flight }}{{ best_point_routine_1.total }}{{ best_point_routine_1.event.date_begin|date:"j M Y" }}
    Routine 2{{ best_point_routine_2.point_execution }}{{ best_point_routine_2.point_difficulty }}{{ best_point_routine_2.point_horizontal_displacement }}{{ best_point_routine_2.point_time_of_flight }}{{ best_point_routine_2.total }}{{ best_point_routine_2.event.date_begin|date:"j M Y" }}
    + {% endif %} +
    +
    +
    + {% if routine_1 or routine_2 %} +
    +

    Routines

    +
    +
    + {% if routine_1 %} + + {% for routine_skill in routine_1.routine.skill_links.all %} + + + + + {% endfor %} + + + + +
    {{ routine_skill.skill.notation }}{{ routine_skill.skill.difficulty }}
    {{ routine_1.routine.difficulty }}
    + {% else %} + No compulsary routine defined. + {% endif %} +
    +
    + {% if routine_2 %} + + {% for routine_skill in routine_2.routine.skill_links.all %} + + + + + {% endfor %} + + + + +
    {{ routine_skill.skill.notation }}{{ routine_skill.skill.difficulty }}
    {{ routine_2.routine.difficulty }}
    + {% else %} + No volontary routine defined. + {% endif %} +
    + {% endif %} +
    +
    +
    +
    +
    +

    Last learned skills

    + + {% for learned_skill in learned_skills %} + + + + + + + {% endfor %} +
    {{ learned_skill.skill.short_label }}({{ learned_skill.get_learning_step_display }}){{ learned_skill.skill.notation }}{{ learned_skill.date|date:"j M Y" }}
    +
    +
    +

    Next skills to learn

    + {% if plan_list %} + + {% for plan in plan_list %} + + + + + + + {% endfor %} +
    {{ plan.educative.short_label }}(){{ plan.skill.notation }}{{ plan.date | date:"j M Y" }}
    + {% else %} + No objective defined. + {% endif %} +
    +
    +
    +
    +
    +

    Next Events

    + {% if next_event_list %} + + {% for event in next_event_list %} + + + + + + {% endfor %} +
    {{ event.date_begin | date:"j M Y" }}in {{ event.number_of_week_from_today }} week(s){{ event.name }}
    + {% endif %} +
    +
    +
    +
    +
    +

    Notes

    + {% if notes %} + {% for note in notes %} + {{ note.to_markdown | safe }} + {% endfor %} + {% else %} + No note this week. + {% endif %} +
    +
    diff --git a/templates/ultron/dashboard/dashboard.html b/templates/ultron/dashboard/dashboard.html index d49ff0249a..c2edb7e8cf 100644 --- a/templates/ultron/dashboard/dashboard.html +++ b/templates/ultron/dashboard/dashboard.html @@ -11,7 +11,7 @@

    Hi {{ user.username }} !

    - Welcome to Ultron v0.71 (last update : 13-11-2022)
    + Welcome to Ultron v0.72 (last update : 19-11-2022)
    This application is here to help coaches to manage the gymnasts (evolution, evaluation, routines, scores, …). This tool is not perfect so feel free to make improvement proposals, bug reports, … by sending me an email.

    diff --git a/ultron/people/views.py b/ultron/people/views.py index 240c210d73..e47d9333fb 100644 --- a/ultron/people/views.py +++ b/ultron/people/views.py @@ -566,3 +566,219 @@ def gymnast_display_skill(request, gymnast_id): name="trainer" ).exists() # TODO: utiliser les {{ perms }} return render(request, "people/gymnasts/tab_skill.html", context) + + +def analyse_score(value, value_list): + """Analyse une value (value) par rapport à la moyenne de value_list et à la dernière + valeur de value_list + + Args: + value float valeur + value_list array liste de valeurs + + Returns: + string + + Examples: + """ + res = "" + mean_value = mean(value_list) + + if value > value_list[-1]: + res += "+" + elif value < value_list[-1]: + res += "-" + else: + res += "=" + + if value > mean_value: + res = "+" + res + elif value < mean_value: + res = "-" + res + else: + res = "=" + res + + return res + + +@login_required +@require_http_methods(["GET"]) +def generate_report(request, gymnast_id): + gymnast = get_object_or_404(Gymnast, pk=gymnast_id) + today = pendulum.now().date() + season, week_number = from_date_to_week_number(today) + # season = Season() + + # + # PHYSIOLOGICAL INFORMATIONS + # + mindstate_queryset = MindState.objects.filter(gymnast=gymnast).order_by("-date") + last_mindstate = mindstate_queryset.first() + lasts_mindstate = list(mindstate_queryset.values_list("score", flat=True)[1:6]) + + have_physiological = False + if lasts_mindstate: + mindstate_analyse = analyse_score(last_mindstate.score, lasts_mindstate) + + height_weight_queryset = HeightWeight.objects.filter(gymnast=gymnast).order_by( + "-date" + ) + last_height_weigth = height_weight_queryset.first() + lasts_height = list(height_weight_queryset.values_list("height", flat=True)[1:6]) + lasts_weight = list(height_weight_queryset.values_list("weight", flat=True)[1:6]) + + if lasts_height: + height_analyse = analyse_score(last_height_weigth.height, lasts_height) + + if lasts_weight: + weight_analyse = analyse_score(last_height_weigth.weight, lasts_weight) + + # + # BEST TOF + # + best_tof_straightjump = ( + Chrono.objects.filter(gymnast=gymnast) + .filter(chrono_type=0) + .order_by("-score") + .first() + ) + best_tof_routine_1 = ( + Chrono.objects.filter(gymnast=gymnast) + .filter(chrono_type=1) + .order_by("-score") + .first() + ) + best_tof_routine_2 = ( + Chrono.objects.filter(gymnast=gymnast) + .filter(chrono_type=2) + .order_by("-score") + .first() + ) + + # + # BEST SCORES + # + best_point_routine_1 = ( + Point.objects.filter(gymnast=gymnast) + .filter(routine_type=1) + .order_by("-total") + .first() + ) + best_point_routine_2 = ( + Point.objects.filter(gymnast=gymnast) + .filter(routine_type=2) + .order_by("-total") + .first() + ) + + # + # ROUTINES + # + routine_1 = ( + gymnast.has_routine.filter(routine_type=1) + .filter(date_begin__lte=today) + .filter(Q(date_end__gte=today) | Q(date_end__isnull=True)) + .first() + ) + routine_2 = ( + gymnast.has_routine.filter(routine_type=2) + .filter(date_begin__lte=today) + .filter(Q(date_end__gte=today) | Q(date_end__isnull=True)) + .first() + ) + # print(routine_2) + + # + # LAST LEARNED SKILLS + # + learned_skills = ( + LearnedSkill.objects.filter(gymnast=gymnast.id) + .annotate(skill_notation=F("skill__notation")) + .order_by("skill_notation", "-date") + .distinct("skill_notation")[:6] + ) + + # + # 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("educative", "-date") + .distinct()[:6] + ) + + # + # NEXT EVENTS + # + next_event_list = Event.objects.filter( + gymnasts=gymnast.id, date_begin__gte=today + ).order_by("date_begin")[:5] + + # + # NOTES + # + begin_of_the_week = today + if today.weekday() != 0: + begin_of_the_week -= timedelta(today.weekday()) + + notes = ( + gymnast.remarks.filter(date__gte=begin_of_the_week) + .filter(status=1) + .order_by("date") + ) + + context = { + "SITE_TITLE": settings.SITE_TITLE, + "CLUB_NAME": settings.CLUB_NAME, + "ADDRESS": settings.ADDRESS, + "CITY": settings.CITY, + "ZIP": settings.ZIP, + "HEAD_COACH": settings.HEAD_COACH, + "MOBILE_PHONE": settings.MOBILE_PHONE, + "HEAD_COACH_EMAIL": settings.HEAD_COACH_EMAIL, + "week_number": week_number, + "gymnast": gymnast, + "today": today, + "season": season, + "last_mindstate": last_mindstate, + "mindstate_analyse": mindstate_analyse, + "last_height_weigth": last_height_weigth, + "height_analyse": height_analyse, + "weight_analyse": weight_analyse, + "best_tof_straightjump": best_tof_straightjump, + "best_tof_routine_1": best_tof_routine_1, + "best_tof_routine_2": best_tof_routine_2, + "best_point_routine_1": best_point_routine_1, + "best_point_routine_2": best_point_routine_2, + "routine_1": routine_1, + "routine_2": routine_2, + "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, "people/gymnasts/report.html", context) + + response = HttpResponse(content_type="application/pdf") + response[ + "Content-Disposition" + ] = "inline; filename={lastname}-{firstname}-report.pdf".format( + lastname=gymnast.last_name, + firstname=gymnast.first_name, + ) + + html = render_to_string("people/gymnasts/report.html", context) + + # font_config = FontConfiguration() + HTML(string=html).write_pdf( + response, + stylesheets=[ + CSS(settings.STATICFILES_DIRS[0] + "/css/gymnast_report.css"), + CSS(settings.STATICFILES_DIRS[0] + "/css/black-dashboard_report.css"), + CSS(settings.STATICFILES_DIRS[0] + "/css/font_awesome_all_5.15.3.css"), + ], + ) # , font_config=font_config) + return response