From c309c2db565fb441454de109a6e1fc5dbcf72537 Mon Sep 17 00:00:00 2001 From: Argiris Deligiannidis Date: Wed, 8 Jan 2025 00:57:05 +0200 Subject: [PATCH] v1.0 --- =6.7.2, | 0 form.ui | 84 +++++++++++--- requirements.bk | 20 ++++ requirements.txt | 37 ++++-- rc_resources.py => resources_rc.py | 0 ui_form.py | 48 +++++--- widget.py | 173 +++++++++++++++++++---------- 7 files changed, 262 insertions(+), 100 deletions(-) delete mode 100644 =6.7.2, create mode 100644 requirements.bk rename rc_resources.py => resources_rc.py (100%) diff --git a/=6.7.2, b/=6.7.2, deleted file mode 100644 index e69de29..0000000 diff --git a/form.ui b/form.ui index 99283cf..c698551 100644 --- a/form.ui +++ b/form.ui @@ -9,8 +9,8 @@ 0 0 - 742 - 336 + 706 + 534 @@ -26,13 +26,13 @@ - Επιλογή Αρχείου CSV + Επιλογή δεδομένων Excel - 150 + 140 20 61 61 @@ -51,7 +51,7 @@ - 240 + 230 10 431 81 @@ -83,7 +83,7 @@ false - Επιλέξτε το αρχείο δεδομένων σε μορφή .csv + Επιλέξτε το αρχείο δεδομένων σε μορφή Excel @@ -92,14 +92,14 @@ - 310 - 290 - 111 - 34 + 480 + 250 + 211 + 61 - Δημιουργία pdf + Δημιουργία πιστοποιητικών @@ -112,7 +112,7 @@ - Επιλογή Αρχείου PDF + Επιλογή προσχέδιου Word @@ -131,7 +131,65 @@ false - Επιλέξτε το PDF αρχείο του προτύπου + Επιλέξτε το αρχείο του προτύπου (Word) + + + + + false + + + + 20 + 320 + 341 + 41 + + + + + + + 20 + 260 + 131 + 20 + + + + Αποστολή email + + + + + + 120 + 290 + 131 + 31 + + + + <html><head/><body><p>Το αρχείο που έχουμε επιλέξει</p></body></html> + + + false + + + Microsoft OAuth2 Token + + + + + false + + + + 10 + 390 + 681 + 131 + diff --git a/requirements.bk b/requirements.bk new file mode 100644 index 0000000..329ff33 --- /dev/null +++ b/requirements.bk @@ -0,0 +1,20 @@ +chardet==5.2.0 +pillow==10.4.0 +PyPDF2==3.0.1 +PySide6>=6.7.2 +PySide6_Addons>=6.7.2 +PySide6_Essentials>=6.7.2 +python-dateutil==2.9.0.post0 +pytz==2024.1 +reportlab==4.2.2 +shiboken6>=6.7.2 +six==1.16.0 +tzdata==2024.1 +altgraph==0.17.4 +packaging==24.1 +pyinstaller==6.9.0 +pyinstaller-hooks-contrib==2024.7 +setuptools==70.3.0 +python-docx==1.1.2 +python-docx==1.1.2 +msoffice2pdf==1.0.0 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 0ab7fb8..5ed8c2e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,17 +1,30 @@ -chardet==5.2.0 -pillow==10.4.0 -PyPDF2==3.0.1 -PySide6>=6.7.2 -PySide6_Addons>=6.7.2 -PySide6_Essentials>=6.7.2 -python-dateutil==2.9.0.post0 -pytz==2024.1 -reportlab==4.2.2 -shiboken6>=6.7.2 -six==1.16.0 -tzdata==2024.1 altgraph==0.17.4 +certifi==2024.12.14 +chardet==5.2.0 +charset-normalizer==3.4.1 +et_xmlfile==2.0.0 +idna==3.10 +lxml==5.3.0 +msoffice2pdf==1.0.0 +numpy==2.2.1 +openpyxl==3.1.5 packaging==24.1 +pandas==2.2.3 +pillow==10.4.0 pyinstaller==6.9.0 pyinstaller-hooks-contrib==2024.7 +PyPDF2==3.0.1 +PySide6==6.8.1 +PySide6_Addons==6.8.1 +PySide6_Essentials==6.8.1 +python-dateutil==2.9.0.post0 +python-docx==1.1.2 +pytz==2024.1 +reportlab==4.2.2 +requests==2.32.3 setuptools==70.3.0 +shiboken6==6.8.1 +six==1.16.0 +typing_extensions==4.12.2 +tzdata==2024.1 +urllib3==2.3.0 diff --git a/rc_resources.py b/resources_rc.py similarity index 100% rename from rc_resources.py rename to resources_rc.py diff --git a/ui_form.py b/ui_form.py index 97445d4..8d53377 100644 --- a/ui_form.py +++ b/ui_form.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- ################################################################################ -## Form generated from reading UI file 'form.ui' +## Form generated from reading UI file 'formrmZXPO.ui' ## -## Created by: Qt User Interface Compiler version 6.7.2 +## Created by: Qt User Interface Compiler version 6.8.1 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -15,27 +15,27 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont, QFontDatabase, QGradient, QIcon, QImage, QKeySequence, QLinearGradient, QPainter, QPalette, QPixmap, QRadialGradient, QTransform) -from PySide6.QtWidgets import (QApplication, QLabel, QPushButton, QSizePolicy, - QWidget) -import rc_resources +from PySide6.QtWidgets import (QApplication, QCheckBox, QLabel, QPlainTextEdit, + QPushButton, QSizePolicy, QWidget) +import resources_rc class Ui_Widget(object): def setupUi(self, Widget): if not Widget.objectName(): Widget.setObjectName(u"Widget") Widget.setEnabled(True) - Widget.resize(742, 336) + Widget.resize(706, 534) self.file_selector = QPushButton(Widget) self.file_selector.setObjectName(u"file_selector") self.file_selector.setGeometry(QRect(20, 140, 151, 34)) self.label = QLabel(Widget) self.label.setObjectName(u"label") - self.label.setGeometry(QRect(150, 20, 61, 61)) + self.label.setGeometry(QRect(140, 20, 61, 61)) self.label.setPixmap(QPixmap(u":/images/logo.png")) self.label.setScaledContents(True) self.label_2 = QLabel(Widget) self.label_2.setObjectName(u"label_2") - self.label_2.setGeometry(QRect(240, 10, 431, 81)) + self.label_2.setGeometry(QRect(230, 10, 431, 81)) font = QFont() font.setPointSize(26) font.setBold(True) @@ -47,7 +47,7 @@ class Ui_Widget(object): self.create_button = QPushButton(Widget) self.create_button.setObjectName(u"create_button") self.create_button.setEnabled(False) - self.create_button.setGeometry(QRect(310, 290, 111, 34)) + self.create_button.setGeometry(QRect(480, 250, 211, 61)) self.file_template = QPushButton(Widget) self.file_template.setObjectName(u"file_template") self.file_template.setGeometry(QRect(20, 190, 151, 34)) @@ -55,6 +55,21 @@ class Ui_Widget(object): self.label_4.setObjectName(u"label_4") self.label_4.setGeometry(QRect(180, 190, 561, 31)) self.label_4.setAutoFillBackground(False) + self.oauth2_token = QPlainTextEdit(Widget) + self.oauth2_token.setObjectName(u"oauth2_token") + self.oauth2_token.setEnabled(False) + self.oauth2_token.setGeometry(QRect(20, 320, 341, 41)) + self.enable_email = QCheckBox(Widget) + self.enable_email.setObjectName(u"enable_email") + self.enable_email.setGeometry(QRect(20, 260, 131, 20)) + self.label_5 = QLabel(Widget) + self.label_5.setObjectName(u"label_5") + self.label_5.setGeometry(QRect(120, 290, 131, 31)) + self.label_5.setAutoFillBackground(False) + self.log_output = QPlainTextEdit(Widget) + self.log_output.setObjectName(u"log_output") + self.log_output.setEnabled(False) + self.log_output.setGeometry(QRect(10, 390, 681, 131)) self.retranslateUi(Widget) @@ -63,18 +78,23 @@ class Ui_Widget(object): def retranslateUi(self, Widget): Widget.setWindowTitle(QCoreApplication.translate("Widget", u"Widget", None)) - self.file_selector.setText(QCoreApplication.translate("Widget", u"\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u0391\u03c1\u03c7\u03b5\u03af\u03bf\u03c5 CSV", None)) + self.file_selector.setText(QCoreApplication.translate("Widget", u"\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd Excel", None)) self.label.setText("") self.label_2.setText(QCoreApplication.translate("Widget", u"AKMI Certificate creator", None)) #if QT_CONFIG(whatsthis) self.label_3.setWhatsThis(QCoreApplication.translate("Widget", u"

\u03a4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03c0\u03bf\u03c5 \u03ad\u03c7\u03bf\u03c5\u03bc\u03b5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b5\u03b9

", None)) #endif // QT_CONFIG(whatsthis) - self.label_3.setText(QCoreApplication.translate("Widget", u"\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03c3\u03b5 \u03bc\u03bf\u03c1\u03c6\u03ae .csv", None)) - self.create_button.setText(QCoreApplication.translate("Widget", u"\u0394\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 pdf", None)) - self.file_template.setText(QCoreApplication.translate("Widget", u"\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u0391\u03c1\u03c7\u03b5\u03af\u03bf\u03c5 PDF", None)) + self.label_3.setText(QCoreApplication.translate("Widget", u"\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03c3\u03b5 \u03bc\u03bf\u03c1\u03c6\u03ae Excel", None)) + self.create_button.setText(QCoreApplication.translate("Widget", u"\u0394\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03ce\u03bd", None)) + self.file_template.setText(QCoreApplication.translate("Widget", u"\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c0\u03c1\u03bf\u03c3\u03c7\u03ad\u03b4\u03b9\u03bf\u03c5 Word", None)) #if QT_CONFIG(whatsthis) self.label_4.setWhatsThis(QCoreApplication.translate("Widget", u"

\u03a4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03c0\u03bf\u03c5 \u03ad\u03c7\u03bf\u03c5\u03bc\u03b5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b5\u03b9

", None)) #endif // QT_CONFIG(whatsthis) - self.label_4.setText(QCoreApplication.translate("Widget", u"\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf PDF \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03c4\u03bf\u03c5 \u03c0\u03c1\u03bf\u03c4\u03cd\u03c0\u03bf\u03c5", None)) + self.label_4.setText(QCoreApplication.translate("Widget", u"\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03c4\u03bf\u03c5 \u03c0\u03c1\u03bf\u03c4\u03cd\u03c0\u03bf\u03c5 (Word)", None)) + self.enable_email.setText(QCoreApplication.translate("Widget", u"\u0391\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae email", None)) +#if QT_CONFIG(whatsthis) + self.label_5.setWhatsThis(QCoreApplication.translate("Widget", u"

\u03a4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03c0\u03bf\u03c5 \u03ad\u03c7\u03bf\u03c5\u03bc\u03b5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b5\u03b9

", None)) +#endif // QT_CONFIG(whatsthis) + self.label_5.setText(QCoreApplication.translate("Widget", u"Microsoft OAuth2 Token", None)) # retranslateUi diff --git a/widget.py b/widget.py index c560408..cf7bf35 100644 --- a/widget.py +++ b/widget.py @@ -2,13 +2,12 @@ import os import io import sys -import csv import shutil -from PyPDF2 import PdfReader, PdfWriter -from reportlab.pdfgen import canvas -from reportlab.lib.pagesizes import A4 -from reportlab.pdfbase import pdfmetrics -from reportlab.pdfbase.ttfonts import TTFont +import base64 +from docx import Document +from msoffice2pdf import convert +import pandas as pd +import requests from PySide6.QtWidgets import QApplication, QWidget, QFileDialog from PySide6.QtCore import QFile, QIODevice @@ -18,7 +17,7 @@ from PySide6.QtCore import QFile, QIODevice # pyside2-uic form.ui -o ui_form.py from ui_form import Ui_Widget -import rc_resources +import resources_rc class Widget(QWidget): @@ -30,6 +29,7 @@ class Widget(QWidget): self.ui.file_selector.clicked.connect(self.file_selector) self.ui.file_template.clicked.connect(self.file_template) self.ui.create_button.clicked.connect(self.create_button) + self.ui.enable_email.clicked.connect(self.enable_token) self.selected_file = False self.selected_template = False self.filepath_template = "" @@ -49,90 +49,141 @@ class Widget(QWidget): if self.selected_file and self.selected_template: self.ui.create_button.setEnabled(True) + def enable_token(self): + print(self.ui.oauth2_token.isEnabled()) + if self.ui.oauth2_token.isEnabled(): + self.ui.oauth2_token.setEnabled(False) + else: + self.ui.oauth2_token.setEnabled(True) + def file_selector(self): self.filepath = QFileDialog.getOpenFileName(self, "Επιλογή Αρχείου") if self.filepath[0]: ext = os.path.splitext(self.filepath[0])[-1].lower() - if ext != ".csv": - self.ui.label_3.setText("Το αρχείο πρέπει να είναι της μορφής .csv") - self.selected_file = False - else: + if ext in [".xlsx", ".xls"]: + self.ui.log_output.appendPlainText("Επιλέχθηκε το αρχείο δεδομενων: {}".format(self.filepath[0])) self.ui.label_3.setText("{}".format(self.filepath[0])) self.selected_file = True self.in_file = self.filepath[0] self.file_path = os.path.dirname(self.filepath[0]) + else: + self.ui.label_3.setText("Το αρχείο πρέπει να είναι της μορφής .xlsx ή .xls") + self.selected_file = False self.enable_create() def file_template(self): self.filepath_template = QFileDialog.getOpenFileName(self, "Επιλογή Αρχείου") if self.filepath_template[0]: ext = os.path.splitext(self.filepath_template[0])[-1].lower() - if ext != ".pdf": - self.ui.label_4.setText("Το αρχείο πρέπει να είναι της μορφής .pdf") - self.selected_template = False - else: + if ext in [".docx", ".doc"]: + self.ui.log_output.appendPlainText("Επιλέχθηκε το προσχέδιο: {}".format(self.filepath_template[0])) self.ui.label_4.setText("{}".format(self.filepath_template[0])) self.selected_template = True self.in_template = self.filepath_template[0] self.template_path = os.path.dirname(self.filepath_template[0]) + else: + self.ui.label_4.setText("Το αρχείο πρέπει να είναι της μορφής .docx ή .doc") + self.selected_template = False + self.enable_create() def create_button(self): - with open(self.in_file, encoding='utf-8') as fin: - csvin = csv.DictReader(fin) - self.create_certificates(csvin) + df = pd.read_excel("{}".format(self.filepath[0])) + doc = Document("{}".format(self.filepath_template[0])) + self.create_certificates(df, doc) - def create_certificates(self, csv_dict): - count = 0 - shutil.copyfile( - "{}".format(self.filepath_template[0]), - "{}/RESULT.pdf".format(self.file_path), - ) - for in_row in csv_dict: - count += 1 - packet = io.BytesIO() - pdfmetrics.registerFont( - TTFont("NotoMedium", self.qrc_to_bytes(":/FONTS/NotoSans-Medium.ttf")) - ) - pdfmetrics.registerFont( - TTFont( - "NotoSemiMedium", - self.qrc_to_bytes(":/FONTS/NotoSans-SemiCondensedMedium.ttf"), - ) - ) - # pdfmetrics.registerFont(TTFont('NotoRegular', '{}/NotoSans-Regular.ttf'.format(self.font_dir))) - # pdfmetrics.registerFont(TTFont('NotoBold', '{}/NotoSans-Bold.ttf'.format(self.font_dir))) - # pdfmetrics.registerFont(TTFont('NotoSemi', '{}/NotoSans-SemiCondensed.ttf'.format(self.font_dir))) - # pdfmetrics.registerFont(TTFont('NotoSemiMedium', '{}/NotoSans-SemiCondensedMedium.ttf'.format(self.font_dir))) + def replace_text_styled(self, paragraph, key, value): + if key in paragraph.text: + inline = paragraph.runs + for i in range(len(inline)): + if key in inline[i].text: + text = inline[i].text.replace(key, value) + inline[i].text = text - can = canvas.Canvas(packet, pagesize=A4) - can.setFont("NotoMedium", 22) - x = can._pagesize[0] / 2 - can.drawString(x, 315, "{} {}".format(in_row["SURNAME"], in_row["NAME"])) - can.setFont("NotoSemiMedium", 18) - can.drawString(x, 250, "{}".format(in_row["DEPARTMENT"])) - can.save() + def send_email(self, data_row, attachement): + endpoint = "https://graph.microsoft.com/v1.0/me/sendMail" + headers = { + "Authorization": "{}".format(self.ui.oauth2_token.toPlainText()), + "Content-Type": "application/json" + } + + cert_file = open("./OUTPUT/{}".format(attachement), "rb") + data = cert_file.read() + + email_data = { + "message": { + "subject": "ΠΙΣΤΟΠΟΙΗΤΙΚΟ ΠΑΡΑΚΟΛΟΥΘΗΣΗΣ ΣΕΜΙΝΑΡΙΟΥ: {}".format(data_row['ΘΕΜΑ ΣΕΜΙΝΑΡΙΟΥ']), + "body": { + "contentType": "Text", + "content": "Χαίρετε,\nΣτο παρόν email επισυνάπτεται το πιστοποιητικό παρακολούθησης του σεμιναρίου {} που παρακολουθήσατε στη δομή {} το μήνα {}".format(data_row['ΘΕΜΑ ΣΕΜΙΝΑΡΙΟΥ'], data_row['CAMPUS'], data_row['DATE']) + }, + "toRecipients": [ + { + "emailAddress": { + "address": "{}".format(data_row['EMAIL']) + } + } + ], + "attachments": [ + { + "@odata.type": "#microsoft.graph.fileAttachment", + "name": "{}".format(attachement), + "contentType": "application/pdf", + "contentBytes": base64.b64encode(data).decode('utf-8') + } + ] + }, + "saveToSentItems": "true" + } - packet.seek(0) - new_pdf = PdfReader(packet) + response = requests.post(endpoint, headers=headers, json=email_data) + cert_file.close() - existing_pdf = PdfReader(open("{}/RESULT.pdf".format(self.file_path), "rb")) - output = PdfWriter() + if response.status_code == 202: + self.ui.log_output.appendPlainText("Email στάλθηκε: {}".format(data_row['EMAIL'])) + else: + self.ui.log_output.appendPlainText(f"Αποτυχία αποστολής. Status code: {response.status_code}, Error: {response.text}") + + def create_certificates(self, in_df, in_doc): + if not os.path.exists("./OUTPUT"): + os.makedirs("./OUTPUT") + + for index, row in in_df.iterrows(): + if row['ΟΝΟΜΑΤΕΠΩΝΥΜΟ'] in ['EMPTY', None] or pd.isna(row['ΟΝΟΜΑΤΕΠΩΝΥΜΟ']): + student_name = "{} {} του {}".format(row['ΕΠΩΝΥΜΟ'],row['ΟΝΟΜΑ'], row['ΠΑΤΡΩΝΥΜΟ']) + else: + student_name = "{} {} του {}".format(row['ΟΝΟΜΑΤΕΠΩΝΥΜΟ'], row['ΠΑΤΡΩΝΥΜΟ']) - for page in existing_pdf.pages: - output.add_page(page) - page = existing_pdf.pages[0] - page.merge_page(new_pdf.pages[0]) - output.add_page(page) + mock_data = { + 'ΟΝΟΜΑΤΕΠΩΝΥΜΟ': student_name, + 'ΘΕΜΑ_ΣΕΜΙΝΑΡΙΟΥ': row['ΘΕΜΑ ΣΕΜΙΝΑΡΙΟΥ'], + 'DATE': row['DATE'], + 'CAMPUS': row['CAMPUS'], + 'EMAIL': row['EMAIL'] + } - outputStream = open("{}/RESULT.pdf".format(self.file_path), "wb") - output.write(outputStream) - outputStream.close() + for paragraph in in_doc.paragraphs: + for key in mock_data: + self.replace_text_styled(paragraph, key, mock_data[key]) + + if os.path.exists("./edit.docx"): + os.remove("./edit.docx") + in_doc.save('edit.docx') + + output = convert(source="edit.docx", output_dir="./", soft=0) + fname = output.rsplit('/')[-1] + shutil.move("./{}".format(fname), "./OUTPUT/{}.pdf".format(student_name.replace(" ", "_"))) + + print("PDF created: {}.pdf".format(student_name.replace(" ", "_"))) + if self.ui.enable_email.isChecked(): + self.send_email(row, attachement="{}.pdf".format(student_name.replace(" ", "_"))) self.ui.label_4.setText( - "Το αρχείο PDF δημιουργήθηκε εντός του φακέλου του .csv" + "Τα αρχείο PDF δημιουργήθηκαν εντός του φακέλου OUTPUT." ) self.ui.create_button.setEnabled(False) - + + if os.path.exists("./edit.docx"): + os.remove("./edit.docx") if __name__ == "__main__": app = QApplication(sys.argv)