Improve PDF report

This commit is contained in:
Gregory Trullemans 2022-09-27 08:24:01 +02:00
parent 9ecf6816f1
commit 1a884b0544
8 changed files with 822 additions and 61 deletions

View File

@ -34,7 +34,7 @@
<b>{{ gymnast.trainings_by_week }} training/week</b> for <b>{{ gymnast.hours_by_week }} hours/week</b><br />
<br />
{% if user_is_trainer and gymnast.informations %}
<p>{{ gymnast.informations }}</p>
<p>{{ gymnast.to_markdown | safe }}</p>
<br />
{% endif %}
<li>
@ -76,7 +76,7 @@
<i class="tim-icons icon-sound-wave"></i> <!-- Level -->
</a>
</li>
<li class="nav-item">
<a class="nav-link get-info{% if tab == 'routine' %} active{% endif %}" data-toggle="tab" href="#routine" data-ref="#routine" data-url="routine/" id="display_routines">
<i class="tim-icons icon-components"></i> <!-- Routines -->
@ -105,7 +105,7 @@
<div class="col-12 col-sm-11 col-md-11 col-lg-11 pr-0">
<div class="tab-content">
<div class="tab-pane{% if tab is None or tab == 'skill' %} active{% endif %}" id="skill"></div>
<div class="tab-pane{% if tab == 'routine' %} active{% endif %}" id="routine"></div>
<div class="tab-pane{% if tab == 'scores' %} active{% endif %}" id="scores"></div>
@ -113,7 +113,7 @@
{% if user_is_trainer or request.user|is_user_equal_to_gymnast:gymnast.id %}
<div class="tab-pane{% if tab == 'physiological' %} active{% endif %}" id="physiological"></div>
{% endif %}
<div class="tab-pane{% if tab == 'event' %} active{% endif %}" id="event"></div>
<!-- TODO : message d'erreur si TAB non géré. -->

View File

@ -7,16 +7,17 @@ from django_admin_listfilter_dropdown.filters import (
)
from .models import (
Chrono,
ChronoDetails,
LearnedSkill,
Plan,
Note,
Point,
Chrono,
Accident,
MindState,
GymnastHasRoutine,
NumberOfRoutineDone,
HeightWeight,
Plan
LearnedSkill,
ChronoDetails,
GymnastHasRoutine,
NumberOfRoutineDone
)
@ -170,7 +171,27 @@ class PlanAdmin(admin.ModelAdmin):
autocomplete_fields = ("gymnast",)
class NoteAdmin(admin.ModelAdmin):
model = Note
list_display = ("gymnast", "coach")
readonly_fields = ('created_at', 'updated_at')
list_filter = (
('gymnast', RelatedDropdownFilter),
('coach', RelatedDropdownFilter),
)
search_fields = (
"gymnast__firstname",
"gymnast__lastname",
"coach__last_name",
"coach__first_name",
)
date_hierarchy = "created_at"
autocomplete_fields = ("gymnast",)
admin.site.register(Plan, PlanAdmin)
admin.site.register(Note, NoteAdmin)
admin.site.register(Point, PointAdmin)
admin.site.register(Chrono, ChronoAdmin)
admin.site.register(Accident, AccidentAdmin)

View File

@ -0,0 +1,63 @@
# Generated by Django 4.1.1 on 2022-09-26 10:18
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("people", "0004_gymnast_email_trainer"),
("followup", "0020_rename_datebegin_gymnasthasroutine_date_begin_and_more"),
]
operations = [
migrations.CreateModel(
name="Notes",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"informations",
models.TextField(
blank=True,
help_text="Only MarkDown is authorized",
null=True,
verbose_name="Comments",
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"coach",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="notes",
to=settings.AUTH_USER_MODEL,
),
),
(
"gymnast",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="remarks",
to="people.gymnast",
),
),
],
options={
"abstract": False,
},
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 4.1.1 on 2022-09-26 10:24
from django.conf import settings
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("people", "0004_gymnast_email_trainer"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("followup", "0021_notes"),
]
operations = [
migrations.RenameModel(
old_name="Notes",
new_name="Note",
),
]

View File

@ -1,8 +1,9 @@
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
from datetime import date
from django.db import models
from ultron.tools.models import Markdownizable
from ultron.people.models import Gymnast
from ultron.planning.models import Event
@ -364,3 +365,17 @@ class HeightWeight(models.Model):
def __str__(self):
return "%s : %s/%s - %s" % (self.gymnast, self.height, self.hips_height, self.weight)
class Note(Markdownizable):
"""
Notes relatives à un gymnaste
"""
gymnast = models.ForeignKey(Gymnast, on_delete=models.CASCADE, related_name="remarks")
coach = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, related_name="notes")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return "%s - %s : %s" % (self.gymnast, self.created_at, self.coach)

View File

@ -3,7 +3,30 @@
from django.db import models
from django.utils import timezone
from datetime import date
import markdown
import pendulum
def get_number_of_week_from_begin_of_season(the_date=None):
""" Renvoie le nombre de semaine depuis le début de la saison.
Une saison commence le 1er septembre.
La semaine contenant le 1er septembre est la semaine 1, même si le 1er septembre est un
diamnche.
Args:
date (date) : date
Returns :
(int) nombre de semaine entre la date en paramètre et le début de la saison.
"""
if the_date is None:
the_date = pendulum.today().date()
begin_season = date(the_date.year, 9, 1)
number_of_week = get_number_of_weeks_between(begin_season, the_date)
if begin_season.weekday() != 0:
number_of_week += 1
return number_of_week
def get_number_of_weeks_between(start, stop):
@ -32,14 +55,14 @@ def get_number_of_weeks_between(start, stop):
-> Est-ce qu'il ne suffirait pas de faire la différence ?
"""
tmp = stop - start
number_of_days = abs(tmp.days)
delta = stop - start
number_of_days = abs(delta.days)
number_of_week = int((number_of_days + 1) / 7)
if ((number_of_days + 1) % 7) > 0:
number_of_week += 1
if tmp.days < 0:
if delta.days < 0:
number_of_week *= -1
return number_of_week

View File

@ -1,35 +1,28 @@
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
from reportlab.platypus import Table, TableStyle, Paragraph
from reportlab.lib import colors
from django.conf import settings
from datetime import date, datetime, timedelta
from django.db.models import Q, Max
import os
import locale
from PIL import Image
from ultron.people.models import Gymnast
from ultron.followup.models import (
Plan,
Point,
Chrono,
Accident,
MindState,
HeightWeight,
)
from ultron.objective.models import Skill
import os
import re
from datetime import date, datetime, timedelta
from statistics import mean
import os
import pendulum
import yaml
import re
from django.conf import settings
from django.db.models import Max, Q
from html2rml import html2rml # ####
from PIL import Image
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import cm
from reportlab.pdfgen.canvas import Canvas
from reportlab.platypus import Paragraph, Table, TableStyle
from ultron.followup.models import (Accident, Chrono, HeightWeight, MindState,
Plan, Point)
from ultron.objective.models import Skill
from ultron.people.models import Gymnast
from ultron.planning.models import Event
from .models import get_number_of_week_from_begin_of_season
# EXPENSES = 0
# RECETTES = 1
@ -65,6 +58,8 @@ class PDFDocument(object):
# Create the PDF object, using the response object as its "file."
self.document = Canvas(response, pagesize=A4)
self.y = Y - X
self.styles = getSampleStyleSheet()
self.style = self.styles["Normal"]
self.__load_config()
def __load_config(self):
@ -126,6 +121,7 @@ class PDFDocument(object):
ne retourne rien.
"""
self.document.setFillColorRGB(0.75,0.75,0.75)
self.add_vspace(15)
self.add_new_line(X, self.club_infos["SITE_TITLE"] + ' - ' + self.club_infos['CLUB_NAME'])
self.document.drawRightString(
RIGHT_X,
@ -136,29 +132,24 @@ class PDFDocument(object):
+ " "
+ self.club_infos["CITY"],
)
self.add_new_line(
X,
"Head Coach : "
+ self.club_infos["HEAD_COACH"]
)
# N° de semaine
self.document.drawRightString(
RIGHT_X,
self.y,
self.club_infos["MOBILE_PHONE"]
)
# self.add_new_line(
# INDENTED_X,
# "Head Coach : "
# + self.club_infos["HEAD_COACH"]
# + " ("
# + self.club_infos["MOBILE_PHONE"]
# + ")"
# )
# if contract is not None:
# self.document.drawRightString(
# INDENTED_RIGHT_X, self.y, "N° de Référence : " + str(contract.reference)
# )
today = pendulum.now().date()
# print(today)
self.add_new_line(X, str(today))
begin_season = date(today.year, 9, 1)
self.document.drawRightString(RIGHT_X, self.y, "Week " + str(get_number_of_week_from_begin_of_season()))
self.document.setFillColorRGB(0,0,0)
self.add_vspace(BIG_LINE_HEIGHT)
@ -189,7 +180,8 @@ class GymnastReportDocument(PDFDocument):
self.add_gymnast_active_routine(gymnast)
# self.add_gymnast_level_information(gymnast)
self.add_gymnast_next_skills(gymnast)
# self.add_gymnast_active_routine(gymnast)
self.add_gymnast_next_events(gymnast)
self.add_gymnast_week_notes(gymnast)
def add_gymnast_personnal_information(self, gymnast):
""" Ajoute les informations personnelles du gymnast.
@ -300,7 +292,7 @@ class GymnastReportDocument(PDFDocument):
table = Table(data, [2*cm, 1.5*cm, 1.5*cm])
table.setStyle(style)
width, height = table.wrapOn(self.document, 19*cm, 15.5*cm)
table.drawOn(self.document, 13.5*cm, self.y - height - 5)
table.drawOn(self.document, 13.3*cm, self.y - height - 5)
# last_accident = Accident.objects.filter(gymnast=gymnast).order_by("-date").first()
# print(last_accident)
@ -364,7 +356,6 @@ class GymnastReportDocument(PDFDocument):
data = [["", "Exe.", "Diff.", "ToF", "HD", "Tot.", ""]]
best_point_routine_1 = Point.objects.filter(gymnast=gymnast).filter(routine_type=1).order_by('-total').first()
print(best_point_routine_1)
if best_point_routine_1:
data.append(
[
@ -377,8 +368,8 @@ class GymnastReportDocument(PDFDocument):
best_point_routine_1.event.date_begin.strftime("%d-%m-%Y"),
]
)
best_point_routine_2 = Point.objects.filter(gymnast=gymnast).filter(routine_type=2).order_by('-total').first()
print(best_point_routine_2)
if best_point_routine_2:
data.append(
[
@ -403,7 +394,6 @@ class GymnastReportDocument(PDFDocument):
]
)
print(data)
style = TableStyle(
[
('ALIGN', (0,0), (-1,0), 'CENTER'),
@ -511,3 +501,66 @@ class GymnastReportDocument(PDFDocument):
self.add_new_line(
X, planified_skill.skill.short_label + " (" + planified_skill.skill.notation + ") for (todo: compute deadline)"
)
def add_gymnast_next_events(self, gymnast):
""" Ajoute les évènements futurs du gymnaste """
self.y = 13.5*cm
self.add_new_line(
X,
"Next event",
font_decoration="Bold",
)
self.add_vspace(-3)
today = pendulum.now().date()
next_event_list = Event.objects.filter(gymnasts=gymnast.id, date_begin__gte=today).order_by("date_begin")[:5]
data = []
for event in next_event_list:
data.append([event.date_begin.strftime("%d-%m-%Y"), "in " + str(event.number_of_week_from_today) + " week(s)", event.name])
if not data:
return
style = TableStyle(
[
('ALIGN', (1,0), (1,-1), 'CENTER'),
# ('BOX', (0, 0), (-1, -1), 0.25, colors.black),
# ('GRID', (0,0), (-1,-1), 0.25, colors.black),
# ('LINEABOVE', (0,-1), (-1,-1), 0.25, colors.black),
]
)
table = Table(data, [2.3*cm, 2.2*cm, 8*cm])
table.setStyle(style)
width, height = table.wrapOn(self.document, 19*cm, 15.5*cm)
table.drawOn(self.document, X - 6, self.y - height - 5)
def add_gymnast_week_notes(self, gymnast):
""" Ajoute les notes de la semaine du gymnaste passé en paramètre """
self.y = 10.3*cm
self.add_new_line(
X,
"Notes",
font_decoration="Bold",
)
self.add_vspace(-2*cm)
today = pendulum.today().date()
begin_week = today
if today.weekday() != 0:
begin_week -= today.weekday()
notes = gymnast.remarks.filter(created_at__gte=begin_week)
html_text = ''
for note in notes:
html_text += '<br />' + note.to_markdown()
html_text = html_text[6:]
print(html_text)
paragraph = Paragraph(html_text, self.style, bulletText='*')
width, height = paragraph.wrap(18*cm, 18*cm)
paragraph.drawOn(self.document, X, self.y)

View File

@ -0,0 +1,566 @@
import locale
import os
import re
from datetime import date, datetime, timedelta
from statistics import mean
import pendulum
import yaml
from django.conf import settings
from django.db.models import Max, Q
from html2rml import html2rml # ####
from PIL import Image
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import cm
from reportlab.pdfgen.canvas import Canvas
from reportlab.platypus import Paragraph, Table, TableStyle
from ultron.followup.models import (Accident, Chrono, HeightWeight, MindState,
Plan, Point)
from ultron.objective.models import Skill
from ultron.people.models import Gymnast
from ultron.planning.models import Event
from .models import get_number_of_week_from_begin_of_season
# EXPENSES = 0
# RECETTES = 1
X = 35
Y = 841.89
INDENT = 5
RIGHT_X = 595.27 - X
TITLED_X = 125
INDENTED_X = X + INDENT
INDENTED_RIGHT_X = RIGHT_X - INDENT
MIDDLE = (RIGHT_X - X) / 2
PRESTATION_COLUMN_2 = INDENTED_X + 65
PRESTATION_COLUMN_3 = INDENTED_X + 400
PRESTATION_COLUMN_4 = INDENTED_X + 455
COMMON_LINE_HEIGHT = -15
SMALL_LINE_HEIGHT = COMMON_LINE_HEIGHT + 5
LARGE_LINE_HEIGHT = COMMON_LINE_HEIGHT - 5
DOUBLE_LINE_HEIGHT = COMMON_LINE_HEIGHT * 2
BIG_LINE_HEIGHT = COMMON_LINE_HEIGHT * 3
HUGE_LINE_HEIGHT = COMMON_LINE_HEIGHT * 4
class PDFDocument(object):
# Create the PDF object, using the response object as its "file."
# http://www.reportlab.com/docs/reportlab-userguide.pdf
# canvas.rect(x, y, width, height, stroke=1, fill=0)
# localhost:8000/billing/contract/pdf/2
def __init__(self, response):
# Create the PDF object, using the response object as its "file."
self.document = Canvas(response, pagesize=A4)
self.y = Y - X
self.styles = getSampleStyleSheet()
self.style = self.styles["Normal"]
self.__load_config()
def __load_config(self):
""" Charge le contenu du fichier SITE_CONFIG.YAML qui contient les données relatives à
l'ASBL.
"""
current_path = os.path.dirname(os.path.realpath(__file__))
self.club_infos = None
with open(os.path.join(current_path, "site_config.yaml"), "r") as stream:
try:
self.club_infos = yaml.load(stream, Loader=yaml.FullLoader)
except yaml.YAMLError as exc:
print(exc)
def add_vspace(self, height=COMMON_LINE_HEIGHT):
""" Passe à la ligne, la hauteur de la ligne étant passée en paramètre.
Args:
height (int): hauteur de la ligne.
Returns:
ne retourne rien.
"""
self.y += height
print(self.y)
# if y < 120;
# document.PageBreak()
# y = 790
def add_string(
self, x, string, font_family="Helvetica", font_decoration=None, font_size=10
):
if font_decoration:
font_family += "-" + font_decoration
self.document.setFont(font_family, font_size)
self.document.drawString(x, self.y, string)
def add_new_line(
self,
x,
string,
height=COMMON_LINE_HEIGHT,
font_family="Helvetica",
font_decoration=None,
font_size=10,
):
self.add_vspace(height)
self.add_string(x, string, font_family, font_decoration, font_size)
def add_header(self, contract=None):
""" Génère le header du document.
Args:
contract (contract): instance de la class Contract.
Returns:
ne retourne rien.
"""
self.document.setFillColorRGB(0.75,0.75,0.75)
self.add_vspace(15)
self.add_new_line(X, self.club_infos["SITE_TITLE"] + ' - ' + self.club_infos['CLUB_NAME'])
self.document.drawRightString(
RIGHT_X,
self.y,
self.club_infos["ADDRESS"]
+ " - "
+ self.club_infos["ZIP"]
+ " "
+ self.club_infos["CITY"],
)
self.add_new_line(
X,
"Head Coach : "
+ self.club_infos["HEAD_COACH"]
)
self.document.drawRightString(
RIGHT_X,
self.y,
self.club_infos["MOBILE_PHONE"]
)
today = pendulum.now().date()
# print(today)
self.add_new_line(X, str(today))
begin_season = date(today.year, 9, 1)
self.document.drawRightString(RIGHT_X, self.y, "Week " + str(get_number_of_week_from_begin_of_season()))
self.document.setFillColorRGB(0,0,0)
self.add_vspace(BIG_LINE_HEIGHT)
def download(self):
# Close the PDF object cleanly, and we're done.
self.document.showPage()
self.document.save()
class GymnastReportDocument(PDFDocument):
def generate(self, gymnast_id):
""" Genère un document aux normes du SPF Finance.
Args:
accounting_year (int): année comptable.
Returns:
ne retourne rien.
"""
gymnast = Gymnast.objects.get(pk=gymnast_id)
self.document.setTitle(gymnast.first_name + ' ' + gymnast.last_name)
self.add_header()
self.add_gymnast_personnal_information(gymnast)
self.add_gymnast_physiological_information(gymnast)
self.add_gymnast_best_scores(gymnast)
self.add_gymnast_active_routine(gymnast)
# self.add_gymnast_level_information(gymnast)
self.add_gymnast_next_skills(gymnast)
self.add_gymnast_next_events(gymnast)
self.add_gymnast_week_notes(gymnast)
def add_gymnast_personnal_information(self, gymnast):
""" Ajoute les informations personnelles du gymnast.
Args:
gymnast <Gymnast>: gymnaste
Returns:
ne retourne rien.
"""
self.y = 26*cm
url = os.path.join(settings.STATICFILES_DIRS[0], "img/default-avatar.png")
self.document.drawImage(url, X, self.y - 65, width=80, height=80)
self.add_string(
130,
str(gymnast),
font_decoration="Bold",
font_size=14,
)
# self.add_vspace()
self.document.setFillColorRGB(0.75, 0.75, 0.75)
self.add_new_line(
130,
str(gymnast.age)
)
self.document.setFillColorRGB(0, 0, 0)
self.add_new_line(
130,
str(gymnast.informations)
)
# self.add_vspace(HUGE_LINE_HEIGHT)
def analyse_score(self, value, value_list):
""" Analyse une value (value) par rapport à la moyenne de value_list et à la dernière
valeur de value_list
Args:
value
value_list
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
def add_gymnast_physiological_information(self, gymnast):
""" Ajoute les informations physique et psychologique.
Args:
gymnast <Gymnast>: gymnaste
Returns:
ne retourne rien
"""
self.y = 26*cm
self.add_new_line(
13.5*cm,
"Physics/Mind state",
font_decoration="Bold",
)
data = []
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])
res = self.analyse_score(last_mindstate.score, lasts_mindstate)
data.append(["Mind state", str(last_mindstate.score), res])
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])
res = self.analyse_score(last_height_weigth.height, lasts_height)
data.append(["Height", str(last_height_weigth.height), res])
res = self.analyse_score(last_height_weigth.weight, lasts_weight)
data.append(["Weight", str(last_height_weigth.weight), res])
style = TableStyle(
[
('ALIGN', (1,0), (-1,-1), 'RIGHT'),
# ('GRID', (0,0), (-1,-1), 0.25, colors.black),
# ('BOX', (0,0), (-1,-1), 0.25, colors.black),
]
)
table = Table(data, [2*cm, 1.5*cm, 1.5*cm])
table.setStyle(style)
width, height = table.wrapOn(self.document, 19*cm, 15.5*cm)
table.drawOn(self.document, 13.3*cm, self.y - height - 5)
# last_accident = Accident.objects.filter(gymnast=gymnast).order_by("-date").first()
# print(last_accident)
def add_gymnast_best_scores(self, gymnast):
""" Ajoute les meilleurs scores du gymnaste (Tof, compétition, …).
Args:
gymnast <Gymnast>: gymnaste
Returns:
ne retourne rien
"""
self.y = 23*cm
self.add_new_line(
X,
"Best ToF",
font_decoration="Bold",
)
# self.add_vspace()
best_tof = (
Chrono.objects.filter(gymnast=gymnast)
.filter(chrono_type=0)
.order_by("-score")
.first()
)
data = [
["ToF |:", str(best_tof.tof), str(best_tof.score), "(" + best_tof.date.strftime("%d-%m-%Y") + ")"],
]
best_tof = (
Chrono.objects.filter(gymnast=gymnast)
.filter(chrono_type=1)
.order_by("-score")
.first()
)
data.append(["ToF R1:", str(best_tof.tof), str(best_tof.score), "(" + best_tof.date.strftime("%d-%m-%Y") + ")"])
best_tof = (
Chrono.objects.filter(gymnast=gymnast)
.filter(chrono_type=2)
.order_by("-score")
.first()
)
data.append(["ToF R2:", str(best_tof.tof), str(best_tof.score), "(" + best_tof.date.strftime("%d-%m-%Y") + ")"])
style = TableStyle(
[
('TEXTCOLOR', (-1,0), (-1,-1), '#AAAAAA'),
]
)
table = Table(data)
table.setStyle(style)
width, height = table.wrapOn(self.document, 19*cm, 15*cm)
table.drawOn(self.document, X - 6, self.y - height - 5)
# self.add_vspace(self.y - height - 5)
self.y = 20*cm
self.add_new_line(
X,
"Best Scores",
font_decoration="Bold",
)
data = [["", "Exe.", "Diff.", "ToF", "HD", "Tot.", ""]]
best_point_routine_1 = Point.objects.filter(gymnast=gymnast).filter(routine_type=1).order_by('-total').first()
if best_point_routine_1:
data.append(
[
"R1: ",
best_point_routine_1.point_execution,
best_point_routine_1.point_difficulty,
best_point_routine_1.point_time_of_flight,
best_point_routine_1.point_horizontal_displacement,
best_point_routine_1.total,
best_point_routine_1.event.date_begin.strftime("%d-%m-%Y"),
]
)
best_point_routine_2 = Point.objects.filter(gymnast=gymnast).filter(routine_type=2).order_by('-total').first()
if best_point_routine_2:
data.append(
[
"R2 :",
best_point_routine_2.point_execution,
best_point_routine_2.point_difficulty,
best_point_routine_2.point_time_of_flight,
best_point_routine_2.point_horizontal_displacement,
best_point_routine_2.total,
best_point_routine_2.event.date_begin.strftime("%d-%m-%Y"),
]
)
else:
data.append(
[
"R2:",
"-",
"-",
"-",
"-",
"-",
]
)
style = TableStyle(
[
('ALIGN', (0,0), (-1,0), 'CENTER'),
('ALIGN', (1,1), (-1,-1), 'RIGHT'),
('TEXTCOLOR', (-1,0), (-1,-1), '#AAAAAA'),
# ('BOX', (0, 0), (-1, -1), 0.25, colors.black),
# ('LINEABOVE', (0,-1), (-1,-1), 0.25, colors.black),
]
)
table = Table(data)
table.setStyle(style)
width, height = table.wrapOn(self.document, 19*cm, 15*cm)
table.drawOn(self.document, X - 6, self.y - height - 5)
# routine_1 = gymnast.has_routine.prefetch_related("routine").filter(
# date_end__isnull=True
# )
self.add_vspace(HUGE_LINE_HEIGHT)
def add_gymnast_active_routine(self, gymnast):
""" Ajoute les routines actives """
self.y = 23*cm
self.add_new_line(
15.9*cm,
"Routines",
font_decoration="Bold",
)
routine_1 = gymnast.has_routine.filter(routine_type=1).filter(date_begin__lte=date.today()).filter(Q(date_end__gte=date.today()) | Q(date_end__isnull=True)).first()
routine_2 = gymnast.has_routine.filter(routine_type=2).filter(date_begin__lte=date.today()).filter(Q(date_end__gte=date.today()) | Q(date_end__isnull=True)).first()
data = []
for routine_skill in routine_1.routine.skill_links.all():
data.append([routine_skill.skill.notation, routine_skill.skill.difficulty])
data.append([None, routine_1.routine.difficulty])
style = TableStyle(
[
('ALIGN', (1,0), (1,-1), 'RIGHT'),
# ('BOX', (0, 0), (-1, -1), 0.25, colors.black),
('LINEABOVE', (0,-1), (-1,-1), 0.25, colors.black),
]
)
table = Table(data, [2*cm, 1*cm])
table.setStyle(style)
width, height = table.wrapOn(self.document, 19*cm, 15*cm)
table.drawOn(self.document, 13.5*cm, self.y - height - 5)
data = []
for routine_skill in routine_2.routine.skill_links.all():
data.append([routine_skill.skill.notation, routine_skill.skill.difficulty])
data.append([None, routine_2.routine.difficulty])
style = TableStyle(
[
('ALIGN', (1,0), (1,-1), 'RIGHT'),
# ('BOX', (0, 0), (-1, -1), 0.25, colors.black),
('LINEABOVE', (0,-1), (-1,-1), 0.25, colors.black),
]
)
table = Table(data, [2*cm, 1*cm])
table.setStyle(style)
width, height = table.wrapOn(self.document, 19*cm, 15.5*cm)
table.drawOn(self.document, 17*cm, self.y - height - 5)
def add_gymnast_next_skills(self, gymnast):
""" Ajoute les prochains skill (skill planifié) à apprendre
Args:
gymnast <Gymnast> gymnaste
Returns:
Ne retourne rien
"""
self.y = 17*cm
self.add_new_line(
X,
"Next skills to learn",
font_decoration="Bold",
)
self.add_vspace(-3)
planified_skills = (
Skill.objects.filter(plan__gymnast=gymnast.id)
.filter(
Q(plan__is_done=False)
| Q(plan__date__gte=date.today())
)
.order_by("-plan__date")[:6]
)
# Ne permet pas de récupérer que les skill, or je voudrais bien.
# planified_skills = (
# Plan.objects.filter(gymnast=gymnast.id)
# .filter(
# Q(is_done=False)
# | Q(date__gte=date.today())
# )
# .order_by("-date")[:6]
# )
for planified_skill in planified_skills:
self.add_new_line(
X, planified_skill.skill.short_label + " (" + planified_skill.skill.notation + ") for (todo: compute deadline)"
)
def add_gymnast_next_events(self, gymnast):
""" Ajoute les évènements futurs du gymnaste """
self.y = 13.5*cm
self.add_new_line(
X,
"Next event",
font_decoration="Bold",
)
self.add_vspace(-3)
today = pendulum.now().date()
next_event_list = Event.objects.filter(gymnasts=gymnast.id, date_begin__gte=today).order_by("date_begin")[:5]
data = []
for event in next_event_list:
data.append([event.date_begin.strftime("%d-%m-%Y"), "in " + str(event.number_of_week_from_today) + " week(s)", event.name])
if not data:
return
style = TableStyle(
[
('ALIGN', (1,0), (1,-1), 'CENTER'),
# ('BOX', (0, 0), (-1, -1), 0.25, colors.black),
# ('GRID', (0,0), (-1,-1), 0.25, colors.black),
# ('LINEABOVE', (0,-1), (-1,-1), 0.25, colors.black),
]
)
table = Table(data, [2.3*cm, 2.2*cm, 8*cm])
table.setStyle(style)
width, height = table.wrapOn(self.document, 19*cm, 15.5*cm)
table.drawOn(self.document, X - 6, self.y - height - 5)
def add_gymnast_week_notes(self, gymnast):
""" Ajoute les notes de la semaine du gymnaste passé en paramètre """
self.y = 10.3*cm
self.add_new_line(
X,
"Notes",
font_decoration="Bold",
)
self.add_vspace(-2*cm)
today = pendulum.today().date()
begin_week = today
if today.weekday() != 0:
begin_week -= today.weekday()
notes = gymnast.remarks.filter(created_at__gte=begin_week)
html_text = ''
for note in notes:
html_text += '<br />' + note.to_markdown()
html_text = html_text[6:]
print(html_text)
paragraph = Paragraph(html_text, self.style, bulletText='*')
width, height = paragraph.wrap(18*cm, 18*cm)
paragraph.drawOn(self.document, X, self.y)