From f0148d8855f5f32d8aefddfc15819e86e1a00d81 Mon Sep 17 00:00:00 2001 From: WorldTeacher Date: Mon, 14 Apr 2025 11:07:48 +0200 Subject: [PATCH] add image, rework SemesterDocument --- docs/images/statistics.png | Bin 0 -> 30670 bytes src/utils/richtext.py | 82 +++++++++++++++++++++++++++++-------- 2 files changed, 64 insertions(+), 18 deletions(-) create mode 100644 docs/images/statistics.png diff --git a/docs/images/statistics.png b/docs/images/statistics.png new file mode 100644 index 0000000000000000000000000000000000000000..55dea3fb91830b9cbfa09cc424a44523e53cd010 GIT binary patch literal 30670 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 literal 0 HcmV?d00001 diff --git a/src/utils/richtext.py b/src/utils/richtext.py index 9ceb250..8da249d 100644 --- a/src/utils/richtext.py +++ b/src/utils/richtext.py @@ -6,22 +6,57 @@ from docx.oxml import OxmlElement from docx.oxml.ns import qn import os from os.path import basename +from loguru import logger as log +import sys + + +logger = log +logger.remove() +logger.add("logs/application.log", rotation="1 week", enqueue=True) +log.add( + f"logs/{datetime.now().strftime('%Y-%m-%d')}.log", + rotation="1 day", + compression="zip", +) + +# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO") +logger.add(sys.stdout) + + +class SemesterError(Exception): + """Custom exception for semester-related errors.""" + + def __init__(self, message): + super().__init__(message) + logger.error(message) + + def __str__(self): + return f"SemesterError: {self.args[0]}" class SemesterDocument: - def __init__(self, apparats: list[tuple[int, str]], semester: str, filename): - assert isinstance(apparats, list), "Apparats must be a list of tuples" - assert all(isinstance(apparat, tuple) for apparat in apparats), ( + def __init__( + self, + apparats: list[tuple[int, str]], + semester: str, + filename, + config, + full: bool = False, + ): + assert isinstance(apparats, list), SemesterError( "Apparats must be a list of tuples" ) - assert all(isinstance(apparat[0], int) for apparat in apparats), ( + assert all(isinstance(apparat, tuple) for apparat in apparats), SemesterError( + "Apparats must be a list of tuples" + ) + assert all(isinstance(apparat[0], int) for apparat in apparats), SemesterError( "Apparat numbers must be integers" ) - assert all(isinstance(apparat[1], str) for apparat in apparats), ( + assert all(isinstance(apparat[1], str) for apparat in apparats), SemesterError( "Apparat names must be strings" ) - assert isinstance(semester, str), "Semester must be a string" - assert "." not in filename and isinstance(filename, str), ( + assert isinstance(semester, str), SemesterError("Semester must be a string") + assert "." not in filename and isinstance(filename, str), SemesterError( "Filename must be a string and not contain an extension" ) self.doc = Document() @@ -35,7 +70,17 @@ class SemesterDocument: self.color_red = RGBColor(255, 0, 0) self.color_blue = RGBColor(0, 0, 255) self.filename = filename - + self.settings = config + if full: + logger.info("Full document generation") + self.make_document() + logger.info("Document created") + self.create_pdf() + logger.info("PDF created") + self.print_document() + logger.info("Document printed") + self.cleanup() + logger.info("Cleanup done") def set_table_border(self, table): """ Adds a full border to the table. @@ -161,8 +206,8 @@ class SemesterDocument: from email.mime.multipart import MIMEMultipart from email.mime.application import MIMEApplication from email.mime.text import MIMEText - from src import settings as config + config = self.settings smtp = config.mail.smtp_server port = config.mail.port sender_email = config.mail.sender @@ -202,7 +247,7 @@ class SemesterDocument: doc.SaveAs(f"{curdir}/{self.filename}.pdf", FileFormat=17) doc.Close() word.Quit() - print("PDF saved") + logger.debug("PDF saved") def cleanup(self): os.remove(f"{self.filename}.docx") @@ -210,14 +255,15 @@ class SemesterDocument: if __name__ == "__main__": - apparat = [(i, f"Item {i}") for i in range(405, 438)] - doc = SemesterDocument( - apparat, - "WiSe 24/25", - "semap", - ) - doc.make_document() - doc.create_pdf() + pass + # apparat = [(i, f"Item {i}") for i in range(405, 438)] + # doc = SemesterDocument( + # apparat, + # "WiSe 24/25", + # "semap", + # ) + # doc.make_document() + # doc.create_pdf() # doc.print_document()