Improve PDF report
This commit is contained in:
parent
9ecf6816f1
commit
1a884b0544
|
@ -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é. -->
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
),
|
||||
]
|
|
@ -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",
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue