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 .date_week_transition import from_date_to_week_number # 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(from_date_to_week_number())) 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 : 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 : 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 : 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 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 += '
' + 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)