diff --git a/icons/print.svg b/icons/print.svg
new file mode 100644
index 0000000..fefb7f0
--- /dev/null
+++ b/icons/print.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/backend/database.py b/src/backend/database.py
index 88d66c1..c2dddf9 100644
--- a/src/backend/database.py
+++ b/src/backend/database.py
@@ -11,7 +11,6 @@ from src.backend.db import (
CREATE_ELSA_MEDIA_TABLE,
CREATE_ELSA_TABLE,
CREATE_TABLE_APPARAT,
- CREATE_TABLE_APPKONTOS,
CREATE_TABLE_FILES,
CREATE_TABLE_MEDIA,
CREATE_TABLE_MESSAGES,
@@ -136,7 +135,6 @@ class Database:
cursor.execute(CREATE_TABLE_APPARAT)
cursor.execute(CREATE_TABLE_MESSAGES)
cursor.execute(CREATE_TABLE_MEDIA)
- cursor.execute(CREATE_TABLE_APPKONTOS)
cursor.execute(CREATE_TABLE_FILES)
cursor.execute(CREATE_TABLE_PROF)
cursor.execute(CREATE_TABLE_USER)
@@ -164,8 +162,11 @@ class Database:
@logger.catch
def query_db(
- self, query: str, args: Tuple = (), one: bool = False
- ) -> Union[Tuple, List[Tuple]]:
+ self,
+ query: str,
+ args: Tuple[Any, Any] = (), # type:ignore
+ one: bool = False, # type:ignore
+ ) -> Union[Tuple[Any, Any], List[Tuple[Any, Any]]]:
"""
Query the Database for the sent query.
@@ -180,6 +181,7 @@ class Database:
conn = self.connect()
cursor = conn.cursor()
logs_query = query
+
logs_args = args
if "fileblob" in query:
# set fileblob arg in logger to "too long"
@@ -448,7 +450,7 @@ class Database:
self.query_db("UPDATE media SET deleted=1 WHERE id=?", (book_id,))
# File Interactions
- def getBlob(self, filename, app_id: Union[str, int]):
+ def getBlob(self, filename: str, app_id: Union[str, int]) -> bytes:
"""
Get a blob from the database
@@ -706,6 +708,18 @@ class Database:
query += " FROM prof WHERE id=?"
return self.query_db(query, (prof_id,), one=True)[0]
+ def getProfById(self, prof_id: Union[str, int]) -> Prof:
+ """Get a professor based on the id
+
+ Args:
+ prof_id (Union[str,int]): the id of the professor
+
+ Returns:
+ Prof: a Prof object containing the data of the professor
+ """
+ data = self.query_db("SELECT * FROM prof WHERE id=?", (prof_id,), one=True)
+ return Prof().from_tuple(data)
+
def getProfData(self, profname: str):
"""Get mail, telephone number and title of a professor based on the name
@@ -881,7 +895,7 @@ class Database:
self.query_db(query)
return None
- def getApparatsByProf(self, prof_id: Union[str, int]) -> list[tuple]:
+ def getApparatsByProf(self, prof_id: Union[str, int]) -> list[Apparat]:
"""Get all apparats based on the professor id
Args:
@@ -1432,7 +1446,7 @@ class Database:
"SELECT fileblob FROM elsa_files WHERE filename=?", (filename,), one=True
)[0]
# logger.debug(blob)
- tempdir = self.database.tempdir
+ tempdir = self.database.temp
tempdir = tempdir.replace("~", str(Path.home()))
tempdir_path = Path(tempdir)
if not os.path.exists(tempdir_path):
@@ -1450,9 +1464,11 @@ class Database:
Returns:
list[tuple]: a list of tuples containing the ELSA apparats
"""
- return self.query_db("SELECT * FROM elsa")
+ return self.query_db(
+ "SELECT * FROM elsa ORDER BY substr(date, 7, 4) || '-' || substr(date, 4, 2) || '-' || substr(date, 1, 2)"
+ )
- def getElsaId(self, prof_id, semester, date):
+ def getElsaId(self, prof_id: int, semester: str, date: str) -> int:
"""get the id of an ELSA apparat based on the professor, semester and date
Args:
@@ -1534,9 +1550,7 @@ class Database:
"""
conn = self.connect()
cursor = conn.cursor()
- if isinstance(profdata, Prof):
- fullname = profdata.name()
- else:
+ if isinstance(profdata, dict):
name = profdata["profname"]
if "," in name:
fname = name.split(", ")[1].strip()
@@ -1544,6 +1558,8 @@ class Database:
fullname = f"{lname} {fname}"
else:
fullname = profdata["profname"]
+ else:
+ fullname = profdata.name()
query = f"SELECT id FROM prof WHERE fullname = '{fullname}'"
logger.debug(query)
diff --git a/src/backend/db.py b/src/backend/db.py
index 16cb35c..5416b90 100644
--- a/src/backend/db.py
+++ b/src/backend/db.py
@@ -12,7 +12,7 @@ CREATE_TABLE_APPARAT = """CREATE TABLE semesterapparat (
deleted_date TEXT,
apparat_id_adis INTEGER,
prof_id_adis INTEGER,
- konto INTEGER REFERENCES app_kontos (id),
+ konto INTEGER,
FOREIGN KEY (prof_id) REFERENCES prof (id)
)"""
CREATE_TABLE_MEDIA = """CREATE TABLE media (
@@ -26,13 +26,7 @@ CREATE_TABLE_MEDIA = """CREATE TABLE media (
FOREIGN KEY (prof_id) REFERENCES prof (id),
FOREIGN KEY (app_id) REFERENCES semesterapparat (id)
)"""
-CREATE_TABLE_APPKONTOS = """CREATE TABLE app_kontos (
- id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
- app_id INTEGER,
- konto INTEGER,
- passwort TEXT,
- FOREIGN KEY (app_id) REFERENCES semesterapparat (id)
- )"""
+
CREATE_TABLE_FILES = """CREATE TABLE files (
id INTEGER PRIMARY KEY,
filename TEXT,
diff --git a/src/backend/semester.py b/src/backend/semester.py
index b167cec..9185c0a 100644
--- a/src/backend/semester.py
+++ b/src/backend/semester.py
@@ -119,10 +119,16 @@ class Semester:
return True
return False
- def from_string(self, val):
- self.value = val
- self._year = int(val[-2:])
- self._semester = val[:4]
+ def from_string(self, val: str):
+ if " " in val:
+ values = val.split(" ")
+ if len(values) != 2:
+ raise ValueError("Invalid semester format")
+ self._semester = values[0]
+ if len(values[1]) == 4:
+ self._year = int(values[1][2:])
+ # self._year = int(values[1])
+ self.computeValue()
return self
@property
diff --git a/src/backend/threads_autoadder.py b/src/backend/threads_autoadder.py
index ab1f31c..c5cd6b6 100644
--- a/src/backend/threads_autoadder.py
+++ b/src/backend/threads_autoadder.py
@@ -5,7 +5,19 @@ from PyQt6.QtCore import QThread
from PyQt6.QtCore import pyqtSignal as Signal
from src.backend import Database
+from loguru import logger as log
+import sys
+logger = log
+logger.remove()
+logger.add("logs/application.log", rotation="1 week", enqueue=True)
+log.add(
+ "logs/autoadder.log",
+ compression="zip",
+)
+
+# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")
+logger.add(sys.stdout)
# from src.transformers import RDS_AVAIL_DATA
diff --git a/src/backend/threads_availchecker.py b/src/backend/threads_availchecker.py
index bc981e9..4ae0e4e 100644
--- a/src/backend/threads_availchecker.py
+++ b/src/backend/threads_availchecker.py
@@ -9,6 +9,18 @@ from src.backend.database import Database
from src.logic.webrequest import BibTextTransformer, WebRequest
# from src.transformers import RDS_AVAIL_DATA
+from loguru import logger as log
+import sys
+
+logger = log
+logger.remove()
+logger.add("logs/application.log", rotation="1 week", enqueue=True)
+log.add(
+ "logs/availthread.log",
+)
+
+# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")
+logger.add(sys.stdout)
class AvailChecker(QThread):
diff --git a/src/logic/webrequest.py b/src/logic/webrequest.py
index 9c06bdb..998d660 100644
--- a/src/logic/webrequest.py
+++ b/src/logic/webrequest.py
@@ -4,10 +4,11 @@ from bs4 import BeautifulSoup
# import sleep_and_retry decorator to retry requests
from ratelimit import limits, sleep_and_retry
-from typing import Union, Any
+from typing import Union, Any, Literal, Optional
from src.logic.dataclass import BookData
from src.transformers import ARRAYData, BibTeXData, COinSData, RDSData, RISData
+from src.transformers.transformers import RDS_AVAIL_DATA, RDS_GENERIC_DATA
import sys
from loguru import logger as log
@@ -105,6 +106,7 @@ class WebRequest:
def get_data(self) -> Union[list[str], None]:
links = self.get_book_links(self.ppn)
logger.debug(f"Links: {links}")
+ return_data: list[str] = []
for link in links:
result: str = self.search(link) # type:ignore
# in result search for class col-xs-12 rds-dl RDS_LOCATION
@@ -116,9 +118,9 @@ class WebRequest:
item_location = location.find(
"div", class_="col-xs-12 col-md-7 col-lg-8 rds-dl-panel"
).text.strip()
+ logger.debug(f"Item location: {item_location}")
if self.use_any:
pre_tag = soup.find_all("pre")
- return_data: list[str] = []
if pre_tag:
for tag in pre_tag:
data = tag.text.strip()
@@ -142,9 +144,9 @@ class WebRequest:
logger.error(
f"Signature {self.signature} not found in {item_location}"
)
- return_data = []
+ # return_data = []
- return return_data
+ return return_data
def get_data_elsa(self):
links = self.get_book_links(self.ppn)
@@ -225,7 +227,15 @@ class BibTextTransformer:
self.data = line
return self
- def return_data(self, option: Any = None) -> Union[BookData, None]:
+ def return_data(
+ self, option: Any = None
+ ) -> Union[
+ Optional[BookData],
+ Optional[RDS_GENERIC_DATA],
+ Optional[RDS_AVAIL_DATA],
+ None,
+ dict[str, Union[RDS_AVAIL_DATA, RDS_GENERIC_DATA]],
+ ]:
"""Return Data to caller.
Args:
diff --git a/src/logic/wordparser.py b/src/logic/wordparser.py
index 29d6259..69daa46 100644
--- a/src/logic/wordparser.py
+++ b/src/logic/wordparser.py
@@ -3,6 +3,8 @@ from docx import Document
from dataclasses import dataclass
import sys
from loguru import logger as log
+from src.backend import Semester
+from typing import Union, Any
logger = log
logger.remove()
@@ -51,8 +53,9 @@ class Book:
]
)
- def from_dict(self, data: dict):
+ def from_dict(self, data: dict[str, Any]):
for key, value in data.items():
+ value = value.strip()
if value == "\u2002\u2002\u2002\u2002\u2002":
value = ""
@@ -78,18 +81,21 @@ class SemapDocument:
phoneNumber: int = None
mail: str = None
title: str = None
- semester: str = None
+ semester: Union[str, Semester] = None
books: list[Book] = None
+ eternal: bool = False
+ personName: str = None
+ personTitle: str = None
@property
def renameSemester(self) -> None:
- if self.semester is not None:
- if "sommersemester" in self.semester.lower():
- year = self.semester.split(" ")[-1]
- self.semester = f"SoSe {year}"
- elif "wintersemester" in self.semester.lower():
- year = self.semester.split(" ")[-1]
- self.semester = f"WiSe {year}"
+ if ", Dauer" in self.semester:
+ self.semester = self.semester.split(",")[0]
+ self.eternal = True
+ self.semester = Semester().from_string(self.semester)
+ else:
+ logger.warning("Semester {} is not valid", self.semester)
+ self.semester = None
@property
def signatures(self) -> list[str]:
@@ -181,7 +187,7 @@ def tuple_to_dict(tlist: tuple, type: str) -> dict:
return ret
-def elsa_word_to_csv(path):
+def elsa_word_to_csv(path: str):
doc = Document(path)
# # print all lines in doc
doctype = [para.text for para in doc.paragraphs if para.text != ""][-1]
@@ -192,18 +198,18 @@ def elsa_word_to_csv(path):
}
tables = doc.tables
- m_data = []
+ m_data: list[pd.DataFrame] = []
for table in tables:
- data = []
+ data: list[list[str]] = []
for row in table.rows:
- row_data = []
+ row_data: list[str] = []
for cell in row.cells:
text = cell.text
text = text.replace("\n", "")
text = text.replace("\u2002", "")
row_data.append(text)
data.append(row_data)
- df = pd.DataFrame(data)
+ df = pd.DataFrame(data)
df.columns = df.iloc[0]
df = df.iloc[1:]
m_data.append(df)
@@ -222,11 +228,15 @@ def word_to_semap(word_path: str) -> SemapDocument:
df = word_docx_to_csv(word_path)
apparatdata = df[0]
apparatdata = apparatdata.to_dict()
+
keys = list(apparatdata.keys())
+
appdata = {keys[i]: keys[i + 1] for i in range(0, len(keys), 2)}
semap.phoneNumber = appdata["Telefon:"]
semap.subject = appdata["Ihr Fach:"]
semap.mail = appdata["Mailadresse:"]
+ semap.personName = ",".join(appdata["Ihr Name und Titel:"].split(",")[:-1])
+ semap.personTitle = ",".join(appdata["Ihr Name und Titel:"].split(",")[-1:]).strip()
apparatdata = df[1]
apparatdata = apparatdata.to_dict()
keys = list(apparatdata.keys())
@@ -255,6 +265,7 @@ def word_to_semap(word_path: str) -> SemapDocument:
if __name__ == "__main__":
- else_df = word_to_semap(
- "C:/Users/aky547/Desktop/SA 80 titelmeldung_SoSe2025 Burth.docx"
+ else_df = elsa_word_to_csv(
+ "C:/Users/aky547/Desktop/ELSA_Bestellung Scann Der Westen und der Rest.docx"
)
+ print(else_df)
diff --git a/src/ui/dialogs/dialog_sources/documentprint.ui b/src/ui/dialogs/dialog_sources/documentprint.ui
new file mode 100644
index 0000000..c4b85aa
--- /dev/null
+++ b/src/ui/dialogs/dialog_sources/documentprint.ui
@@ -0,0 +1,233 @@
+
+
SELECT
\n" +"semesterapparat.name,
\n" +"prof.lname
\n" +"from
\n" +"semesterapparat
\n" +"INNER JOIN prof ON semesterapparat.prof_id = prof.id
\n" +"WHERE
\n" +"(erstellsemester = \'SoSe 25\'
\n" +"OR erstellsemester = \'WiSe 24/25\')
\n" +"and semesterapparat.deletion_status = 0
")) + self.manualCheck.setText(_translate("Dialog", "Anfragen und anzeigen")) + item = self.tableWidget.horizontalHeaderItem(1) + item.setText(_translate("Dialog", "Name")) + self.toolBox.setItemText(self.toolBox.indexOf(self.page_2), _translate("Dialog", "Semesterapparatsschilder")) diff --git a/src/ui/dialogs/docuprint.py b/src/ui/dialogs/docuprint.py new file mode 100644 index 0000000..dea62ef --- /dev/null +++ b/src/ui/dialogs/docuprint.py @@ -0,0 +1,152 @@ +from .dialog_sources.documentprint_ui import Ui_Dialog +from PyQt6 import QtWidgets, QtCore +from src import Icon + +from src.utils.richtext import SemapSchilder, SemesterDocument +from src.backend import Semester, Database +from natsort import natsorted + + +class DocumentPrintDialog(QtWidgets.QDialog, Ui_Dialog): + def __init__(self, parent=None): + super().__init__(parent) + self.setupUi(self) + self.setWindowIcon(Icon("print").icon) + self.frame.hide() + + self.semester = Semester() + + self.db = Database() + + self.insert_table_data() + self.expertMode.clicked.connect(self.enable_expert_mode) + # Ensure the signal is connected only once + try: + self.pushButton_2.clicked.disconnect() + except TypeError: + pass # Signal was not connected before + self.pushButton_2.clicked.connect(self.on_pushButton_2_clicked) + + try: + self.pushButton.clicked.disconnect() + except TypeError: + pass + self.pushButton.clicked.connect(self.on_pushButton_clicked) + + try: + self.btn_load_current_apparats.clicked.disconnect() + except TypeError: + pass + self.btn_load_current_apparats.clicked.connect(self.load_current_clicked) + + try: + self.manualCheck.clicked.disconnect() + except TypeError: + pass + self.manualCheck.clicked.connect(self.manual_request) + + def manual_request(self): + self.tableWidget.setRowCount(0) + request_text = self.textBrowser.toPlainText() + data = self.db.query_db(request_text) + apparats: list[str] = [] + if not data: + self.tableWidget.setRowCount(0) + return + for row in data: + apparats.append(f"{row[1]} ({row[0]})") + + self.tableWidget.setHorizontalHeaderLabels(["", "Semesterapparat"]) + self.tableWidget.setColumnWidth(0, 50) + for entry in apparats: + # insert the entry, column 1 should be a checkbox, column 2 the data + self.tableWidget.insertRow(0) + self.tableWidget.setItem(0, 0, QtWidgets.QTableWidgetItem("")) + checkbox = QtWidgets.QCheckBox() + + self.tableWidget.setCellWidget(0, 0, checkbox) + + self.tableWidget.setItem(0, 1, QtWidgets.QTableWidgetItem(entry)) + # align row 0 column 0 to center + + def load_current_clicked(self): + entries = self.get_valid_apparats_for_signs() + self.tableWidget.setHorizontalHeaderLabels(["", "Semesterapparat"]) + self.tableWidget.setColumnWidth(0, 50) + self.tableWidget.setRowCount(0) + for entry in entries: + # insert the entry, column 1 should be a checkbox, column 2 the data + self.tableWidget.insertRow(0) + self.tableWidget.setItem(0, 0, QtWidgets.QTableWidgetItem("")) + checkbox = QtWidgets.QCheckBox() + + self.tableWidget.setCellWidget(0, 0, checkbox) + + self.tableWidget.setItem(0, 1, QtWidgets.QTableWidgetItem(entry)) + + def enable_expert_mode(self): + # if self.exportMode. + if self.expertMode.isChecked(): + self.frame.show() + self.expertMode.setText("Expertenmodus deaktivieren") + else: + self.frame.hide() + self.expertMode.setText("Expertenmodus aktivieren") + + def on_pushButton_2_clicked(self): + # get the checked items from the table + checked_items = [] + for i in range(self.tableWidget.rowCount()): + checkbox = self.tableWidget.cellWidget(i, 0) + if isinstance(checkbox, QtWidgets.QCheckBox) and checkbox.isChecked(): + item = self.tableWidget.item(i, 1) + if item is not None: + checked_items.append(item.text()) + document = SemapSchilder(checked_items) + + def on_pushButton_clicked(self): + apparats: list[tuple[int, str]] = [] + apps = self.db.getAllAparats(0) + apps = natsorted(apps, key=lambda x: x[4], reverse=True) + for app in apps: + prof = self.db.getProfById(app[2]) + data = (app[4], f"{prof.lastname} ({app[1]})") + apparats.append(data) + semapDocument = SemesterDocument( + semester=self.semester.value, + filename="Semesterapparat", + full=True, + apparats=apparats, + ) + + def insert_table_data(self): + entries = self.get_valid_apparats_for_signs() + self.tableWidget.setHorizontalHeaderLabels(["", "Semesterapparat"]) + self.tableWidget.setColumnWidth(0, 50) + for entry in entries: + # insert the entry, column 1 should be a checkbox, column 2 the data + self.tableWidget.insertRow(0) + self.tableWidget.setItem(0, 0, QtWidgets.QTableWidgetItem("")) + checkbox = QtWidgets.QCheckBox() + + self.tableWidget.setCellWidget(0, 0, checkbox) + + self.tableWidget.setItem(0, 1, QtWidgets.QTableWidgetItem(entry)) + # align row 0 column 0 to center + + def get_valid_apparats_for_signs(self): + this_sem = self.db.query_db( + query="SELECT prof.lname, semesterapparat.name from semesterapparat INNER JOIN prof ON semesterapparat.prof_id = prof.id WHERE (erstellsemester = ? OR erstellsemester = ?) AND semesterapparat.deletion_status=0", + args=(str(self.semester.value), str(self.semester.previous)), + ) + apparats: list[str] = [] + for row in this_sem: + apparats.append(f"{row[0]} ({row[1]})") + return apparats + + +def launch(): + app = QtWidgets.QApplication([]) + dialog = DocumentPrintDialog() + dialog.show() + app.exec() diff --git a/src/ui/userInterface.py b/src/ui/userInterface.py index 7636603..7f8ed91 100644 --- a/src/ui/userInterface.py +++ b/src/ui/userInterface.py @@ -13,7 +13,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets from PyQt6.QtCore import QThread from PyQt6.QtGui import QRegularExpressionValidator -from src import Icon, settings +from src import Icon from src.backend import Database, BookGrabber, AvailChecker, DocumentationThread from src.backend.semester import Semester from src.backend.create_file import recreateFile @@ -41,6 +41,8 @@ from src.ui.dialogs import ( login_ui, parsed_titles_ui, reminder_ui, + DocumentPrintDialog, + launch, ) from src.ui.widgets import ( ElsaDialog, @@ -52,7 +54,7 @@ from src.ui.widgets import ( EditUser, EditProf, ) -from src.utils import SemesterDocument + from datetime import datetime from loguru import logger as log @@ -259,35 +261,10 @@ class Ui(Ui_Semesterapparat): self.valid_check_semester.clicked.connect(self.display_valid_semester) # type:ignore def create_doc(self): - result = self.confirm_popup( - "Mit dem Klick auf Okay wird eine Übersicht aller aktiven Semesterapparate erstellt und an den FollowME Drucker gesendet. Es kann bis zu 10 Minuten dauern, bis das document im Drucker angezeigt wird", - "document erstellen?", - ) - logger.debug(f"Result: {result}") - if result == 1: - # logger.debug("Creating document") - apparats = self.apparats - apps = [] - for apparat in apparats: - prof = self.db.getProf(apparat[2]) - data = (apparat[4], f"{prof.lastname} ({apparat[1]})") - apps.append(data) - # logger.debug(apps) - logger.info("Using apparats: {}", apps) - doc = SemesterDocument( - semester=Semester().value, - filename="Semesterapparate", - apparats=apps, - full=True, - config=settings, - ) - # doc.make_document() - # doc.create_pdf() - # doc.print_document() - # doc.cleanup() - # logger.info("Document created and sent to printer") - - # kill thread after execution done + logger.debug("Creating document") + # open DocumentPrintDialog + dialog = DocumentPrintDialog(self.MainWindow) + dialog.show() def checkValidInput(self): if valid_input == (1, 1, 1, 1, 1, 1): @@ -812,7 +789,8 @@ class Ui(Ui_Semesterapparat): self.tableWidget_apparat_media.currentRow(), 1 ).text() ] - items = len(links) + # get the number of selected rows from the table + items = self.tableWidget_apparat_media.rowCount() self.label_20.setText("Verfügbarkeit wird geprüft, bitte warten...") self.label_20.show() self.avail_status.setText(f"0/{items}") @@ -1109,7 +1087,7 @@ class Ui(Ui_Semesterapparat): self.update_app_media_list() # #logger.debug(len(signatures)) - def extract_document_data(self) -> Union[None, list[str], SemapDocument]: + def extract_document_data(self) -> Union[list[str], SemapDocument]: file_type = self.document_list.item(self.document_list.currentRow(), 1).text() file_location = self.document_list.item( self.document_list.currentRow(), 3 @@ -1119,15 +1097,11 @@ class Ui(Ui_Semesterapparat): logger.info("File selected: {}, {}", file_name, file_location) if file_location == "Database": # create warning, then return - self.confirm_popup( - "Dateien aus der Datenbank werden nicht unterstützt!", - title="Fehler", - ) - return None + self.db.recreateFile(file_name, self.active_apparat, filetype=file_type) if file_type == "pdf": # Todo: implement parser here self.confirm_popup("PDF Dateien werden nicht unterstützt!", title="Fehler") - return + return [""] if file_type == "csv": signatures = csv_to_list(file) # add the data to the database @@ -1151,8 +1125,14 @@ class Ui(Ui_Semesterapparat): self.prof_tel_nr.setText(str(data.phoneNumber)) self.app_name.setText(data.title) self.app_fach.setCurrentText(data.subject) + self.prof_title.setText(data.personTitle) + self.drpdwn_prof_name.setCurrentText(data.personName) + self.sem_year.setText("20" + data.semester.year) def btn_check_file_threaded(self): + for runner in self.bookGrabber: + if not runner.isRunning(): + runner.deleteLater() # #logger.debug("Checking file") # get active app_id and prof_id self.tableWidget_apparate.setEnabled(False) @@ -1224,7 +1204,6 @@ class Ui(Ui_Semesterapparat): autoGrabber.start() self.bookGrabber.append(autoGrabber) # refresh book table - logger.debug("Finished adding media") # end of thread # self.autoGrabber.exit() # self.__clear_fields() @@ -1434,7 +1413,7 @@ class Ui(Ui_Semesterapparat): reminder.exec() tableposition = self.tableWidget_apparate.currentRow() appnr = self.tableWidget_apparate.item(tableposition, 0).text() - if reminder.result() == QtWidgets.QDialogger.DialogCode.Accepted: + if reminder.result() == QtWidgets.QDialog.DialogCode.Accepted: data = reminder.return_message() # #logger.debug(data) self.db.addMessage( @@ -1473,7 +1452,7 @@ class Ui(Ui_Semesterapparat): settingsUI = Settings(self.active_user) settingsUI.exec() - if settingsUI.result() == QtWidgets.QDialogger.DialogCode.Accepted: + if settingsUI.result() == QtWidgets.QDialog.DialogCode.Accepted: settingsUI.save() # logger.debug(settings.dict()) @@ -1563,7 +1542,7 @@ class Ui(Ui_Semesterapparat): self.confirm_popup("Keine weiteren Apparate vorhanden", title="Fehler") return (None, None) dialog = QtWidgets.QDialog() - dialogger.setWindowTitle(title) + dialog.setWindowTitle(title) # add a label to the dialog label = QtWidgets.QLabel() label.setText(message) @@ -1626,7 +1605,7 @@ class Ui(Ui_Semesterapparat): widget.setWindowTitle("Metadaten") bookedit.populate_fields(data) widget.exec() - if widget.result() == QtWidgets.QDialogger.DialogCode.Accepted: + if widget.result() == QtWidgets.QDialog.DialogCode.Accepted: data = bookedit.get_data() # #logger.debug(data) self.db.updateBookdata(bookdata=data, book_id=book_id) @@ -1685,7 +1664,7 @@ class Ui(Ui_Semesterapparat): framework = ApparatExtendDialog() framework.exec() # return data from dialog if ok is pressed - if framework.result() == QtWidgets.QDialogger.DialogCode.Accepted: + if framework.result() == QtWidgets.QDialog.DialogCode.Accepted: data = framework.get_data() # #logger.debug(data) # return data diff --git a/src/ui/widgets/elsa_main.py b/src/ui/widgets/elsa_main.py index 31233a4..0969e5c 100644 --- a/src/ui/widgets/elsa_main.py +++ b/src/ui/widgets/elsa_main.py @@ -1,5 +1,4 @@ import os -from natsort import natsorted from .widget_sources.Ui_elsa_maindialog import Ui_Dialog from PyQt6 import QtCore, QtWidgets, QtGui from PyQt6.QtGui import QRegularExpressionValidator @@ -9,9 +8,11 @@ from src.backend import Semester, Database from src.logic import elsa_word_to_csv, Prof from src.ui.dialogs import ElsaAddEntry, popus_confirm from src.ui.widgets import FilePicker, DataGraph +from src.backend import recreateElsaFile import sys from loguru import logger as log + logger = log logger.remove() logger.add("logs/application.log", rotation="1 week", enqueue=True) @@ -232,11 +233,11 @@ class ElsaDialog(QtWidgets.QDialog, Ui_Dialog): lastname=prof.split(", ")[0], mail=self.newProf_mail.text(), telnr=self.newProf_telnr.text(), - title=self.newProf_title.text(), + _title=self.newProf_title.text(), fullname=f"{prof.split(', ')[0]} {prof.split(', ')[1]}", ) prof_id = self.db.getProfId(profdata) - logger.debug(profdata, prof_id) + logger.debug(f"ProfData: {profdata}, id:{prof_id}") if prof_id is None: self.db.createProf(profdata) @@ -411,10 +412,7 @@ class ElsaDialog(QtWidgets.QDialog, Ui_Dialog): self.elsa_date.text(), ) logger.debug( - elsa_id, - self.elsa_prof.currentText(), - self.elsa_semester.text(), - self.elsa_date.text(), + f"elsa_id: {elsa_id}, prof: {self.elsa_prof.currentText()}, semester: {self.elsa_semester.text()}, date: {self.elsa_date.text()}" ) for row in data: if self.seperateEntries.isChecked(): @@ -450,7 +448,7 @@ class ElsaDialog(QtWidgets.QDialog, Ui_Dialog): logger.debug("No tab to remove") self.elsa_table.setRowCount(0) elsa_apparats = self.db.getElsaApparats() - elsa_apparats = natsorted(elsa_apparats, key=lambda x: x[2], reverse=True) + # elsa_apparats = natsorted(elsa_apparats, key=lambda x: x[2], reverse=True) # x = semester, y = number of apparats for apparat in elsa_apparats: @@ -463,13 +461,13 @@ class ElsaDialog(QtWidgets.QDialog, Ui_Dialog): else: index = self.graph_data["x"].index(semester) self.graph_data["y"][index] += number - - generateMissing = True if len(self.graph_data["x"]) > 1 else False + self.graph_data["x"].pop(0) + self.graph_data["y"].pop(0) + # generateMissing = True if len(self.graph_data["x"]) > 2 else False graph = DataGraph( - "ELSA Apparate pro Semester", - self.graph_data, - generateMissing, - "Anzahl der Apparate", + title="ELSA Apparate pro Semester", + data=self.graph_data, + label="Anzahl der Apparate", ) logger.debug(self.graph_data) self.elsa_statistics_table.setRowCount(0) diff --git a/src/ui/widgets/graph.py b/src/ui/widgets/graph.py index a4a2ddd..d21f95f 100644 --- a/src/ui/widgets/graph.py +++ b/src/ui/widgets/graph.py @@ -3,6 +3,18 @@ from typing import Union import pyqtgraph as pg from PyQt6 import QtWidgets +import sys +from loguru import logger as log + +logger = log +logger.remove() +logger.add("logs/application.log", rotation="1 week", enqueue=True) +log.add( + "logs/graph.log", +) + +# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO") +logger.add(sys.stdout) def mergedicts(d1, d2): @@ -31,7 +43,11 @@ class DataGraph(QtWidgets.QWidget): label=None, ): super().__init__() - + logger.debug( + "Initialized with options: {}, {}, {}, {}".format( + title, data, generateMissing, label + ) + ) lst = [] if generateMissing: x_data = data["x"] @@ -142,20 +158,10 @@ if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) - data_1 = { - "x": ["SoSe 10", "WiSe 10/11", "SoSe 11", "SoSe 14"], - "y": { - "Added": [1, 2, 3, 4], - "Deleted": [4, 3, 2, 1], - }, - } - data_2 = { - "x": ["SoSe 10"], - "y": [2], - } - graph_data = {"x": ["SoSe 24"], "y": [1]} + + graph_data = {"x": ["WiSe 25/26", "WiSe 24/25", "SoSe 25"], "y": [1, 2, 1]} widget = DataGraph( - "ELSA Apparate pro Semester", data_2, True, "Anzahl der Apparate" + "ELSA Apparate pro Semester", graph_data, True, "Anzahl der Apparate" ) widget.show() sys.exit(app.exec()) diff --git a/src/ui/widgets/searchPage.py b/src/ui/widgets/searchPage.py index 5606d84..f70c8eb 100644 --- a/src/ui/widgets/searchPage.py +++ b/src/ui/widgets/searchPage.py @@ -493,7 +493,7 @@ class SearchStatisticPage(QtWidgets.QDialog, Ui_Dialog): checkbox.setChecked(False) self.tableWidget.setCellWidget(0, 0, checkbox) # if i[9] is 1, set the background of the row to red - if int(app[9]) == 1: + if int(app.deleted) == 1: for j in range(5): self.tableWidget.item(0, j).setBackground( QtGui.QColor(235, 74, 71) diff --git a/src/utils/richtext.py b/src/utils/richtext.py index 8da249d..f1c11e9 100644 --- a/src/utils/richtext.py +++ b/src/utils/richtext.py @@ -8,6 +8,7 @@ import os from os.path import basename from loguru import logger as log import sys +from src import settings logger = log @@ -22,11 +23,46 @@ log.add( # logger.add(sys.stderr, format="{time} {level} {message}", level="INFO") logger.add(sys.stdout) +font = "Cascadia Mono" + + +def print_document(file: str): + # send document to printer as attachment of email + import smtplib + from email.mime.multipart import MIMEMultipart + from email.mime.application import MIMEApplication + from email.mime.text import MIMEText + + smtp = settings.mail.smtp_server + port = settings.mail.port + sender_email = settings.mail.sender + password = settings.mail.password + receiver = settings.mail.printer_mail + message = MIMEMultipart() + message["From"] = sender_email + message["To"] = receiver + message["cc"] = settings.mail.sender + message["Subject"] = "." + mail_body = "." + message.attach(MIMEText(mail_body, "html")) + with open(file, "rb") as fil: + part = MIMEApplication(fil.read(), Name=basename(file)) + # After the file is closed + part["Content-Disposition"] = 'attachment; filename="%s"' % basename(file) + message.attach(part) + mail = message.as_string() + with smtplib.SMTP_SSL(smtp, port) as server: + server.connect(smtp, port) + server.login(settings.mail.user_name, password) + server.sendmail(sender_email, receiver, mail) + server.quit() + logger.success("Mail sent") + class SemesterError(Exception): """Custom exception for semester-related errors.""" - def __init__(self, message): + def __init__(self, message: str): super().__init__(message) logger.error(message) @@ -39,8 +75,7 @@ class SemesterDocument: self, apparats: list[tuple[int, str]], semester: str, - filename, - config, + filename: str, full: bool = False, ): assert isinstance(apparats, list), SemesterError( @@ -62,25 +97,26 @@ class SemesterDocument: self.doc = Document() self.apparats = apparats self.semester = semester - self.table_font = "Arial" - self.header_font = "Times New Roman" + self.table_font_normal = font + self.table_font_bold = font + self.header_font = font self.header_font_size = Pt(26) self.sub_header_font_size = Pt(18) self.table_font_size = Pt(10) 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() + print_document(self.filename + ".pdf") logger.info("Document printed") - self.cleanup() + self.cleanup logger.info("Cleanup done") + def set_table_border(self, table): """ Adds a full border to the table. @@ -110,7 +146,7 @@ class SemesterDocument: table.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # Set column widths by directly modifying the cell properties - widths = [Cm(1.19), Cm(10.39)] + widths = [Cm(1.19), Cm(18)] for col_idx, width in enumerate(widths): for cell in table.columns[col_idx].cells: cell_width_element = cell._element.xpath(".//w:tcPr")[0] @@ -136,7 +172,7 @@ class SemesterDocument: # Set font for the first column (number) cell_number_paragraph = row.cells[0].paragraphs[0] cell_number_run = cell_number_paragraph.add_run(str(number)) - cell_number_run.font.name = self.table_font + cell_number_run.font.name = self.table_font_bold cell_number_run.font.size = self.table_font_size cell_number_run.font.bold = True cell_number_run.font.color.rgb = self.color_red @@ -149,13 +185,13 @@ class SemesterDocument: # Add the first word in bold bold_run = cell_name_paragraph.add_run(words[0]) bold_run.font.bold = True - bold_run.font.name = self.table_font + bold_run.font.name = self.table_font_bold bold_run.font.size = self.table_font_size # Add the rest of the words normally if len(words) > 1: normal_run = cell_name_paragraph.add_run(" " + " ".join(words[1:])) - normal_run.font.name = self.table_font + normal_run.font.name = self.table_font_normal normal_run.font.size = self.table_font_size cell_name_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT @@ -198,42 +234,6 @@ class SemesterDocument: def save_document(self, name): # Save the document self.doc.save(name) - print(f"Document saved as {name}") - - def print_document(self): - # send document to printer as attachment of email - import smtplib - from email.mime.multipart import MIMEMultipart - from email.mime.application import MIMEApplication - from email.mime.text import MIMEText - - config = self.settings - smtp = config.mail.smtp_server - port = config.mail.port - sender_email = config.mail.sender - password = config.mail.password - receiver = config.mail.printer_mail - message = MIMEMultipart() - message["From"] = sender_email - message["To"] = receiver - message["cc"] = config.mail.sender - message["Subject"] = "." - mail_body = "." - message.attach(MIMEText(mail_body, "html")) - with open(self.filename + ".pdf", "rb") as fil: - part = MIMEApplication(fil.read(), Name=basename(self.filename + "pdf")) - # After the file is closed - part["Content-Disposition"] = 'attachment; filename="%s"' % basename( - self.filename + ".pdf" - ) - message.attach(part) - mail = message.as_string() - with smtplib.SMTP_SSL(smtp, port) as server: - server.connect(smtp, port) - server.login(config.mail.user_name, password) - server.sendmail(sender_email, receiver, mail) - server.quit() - print("Mail sent") def create_pdf(self): # Save the document @@ -249,29 +249,112 @@ class SemesterDocument: word.Quit() logger.debug("PDF saved") + @property def cleanup(self): os.remove(f"{self.filename}.docx") os.remove(f"{self.filename}.pdf") + @property + def send(self): + print_document(self.filename + ".pdf") + logger.debug("Document sent to printer") + + +class SemapSchilder: + def __init__(self, entries: list[str]): + self.entries = entries + self.filename = "Schilder" + self.font_size = Pt(23) + self.font_name = font + self.doc = Document() + self.define_doc_properties() + self.add_entries() + self.cleanup() + self.create_pdf() + + def define_doc_properties(self): + # set the doc to have a top margin of 1cm, left and right are 0.5cm, bottom is 0cm + section = self.doc.sections[0] + section.top_margin = Cm(1) + section.bottom_margin = Cm(0) + section.left_margin = Cm(0.5) + section.right_margin = Cm(0.5) + + # set the font to Times New Roman, size 23 bold, color black + for paragraph in self.doc.paragraphs: + for run in paragraph.runs: + run.font.name = self.font_name + run.font.size = self.font_size + run.font.bold = True + run.font.color.rgb = RGBColor(0, 0, 0) + paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + + # if the length of the text is + + def add_entries(self): + for entry in self.entries: + paragraph = self.doc.add_paragraph(entry) + paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + paragraph.paragraph_format.line_spacing = Pt(20) # Set fixed line spacing + paragraph.paragraph_format.space_before = Pt(4) # Remove spacing before + paragraph.paragraph_format.space_after = Pt(4) # Remove spacing after + + run = paragraph.runs[0] + run.font.name = self.font_name + run.font.size = self.font_size + run.font.bold = True + run.font.color.rgb = RGBColor(0, 0, 0) + + # Add a line to be used as a guideline for cutting + line = self.doc.add_paragraph() + line.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + line.paragraph_format.line_spacing = Pt(20) # Match line spacing + line.paragraph_format.space_before = Pt(4) # Remove spacing before + line.paragraph_format.space_after = Pt(4) # Remove spacing after + line.add_run("--------------------------") + + for paragraph in self.doc.paragraphs: + content = paragraph.text.strip() + if len(content) > 45: + paragraph.runs[0].font.size = Pt(20) + + def save_document(self): + # Save the document + self.doc.save(f"{self.filename}.docx") + logger.debug(f"Document saved as {self.filename}.docx") + + def create_pdf(self): + # Save the document + import comtypes.client + + word = comtypes.client.CreateObject("Word.Application") + self.save_document() + docpath = os.path.abspath(f"{self.filename}.docx") + doc = word.Documents.Open(docpath) + curdir = os.getcwd() + doc.SaveAs(f"{curdir}/{self.filename}.pdf", FileFormat=17) + doc.Close() + word.Quit() + logger.debug("PDF saved") + + def cleanup(self): + if os.path.exists(f"{self.filename}.docx"): + os.remove(f"{self.filename}.docx") + if os.path.exists(f"{self.filename}.pdf"): + os.remove(f"{self.filename}.pdf") + + @property + def send(self): + print_document(self.filename + ".pdf") + logger.debug("Document sent to printer") + if __name__ == "__main__": - 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() - - -# def printers(): -# printers = win32print.EnumPrinters( -# win32print.PRINTER_ENUM_LOCAL | win32print.PRINTER_ENUM_CONNECTIONS -# ) -# for i, printer in enumerate(printers): -# print(f"{i}: {printer[2]}") - -# list printers + entries = [ + "Schlenke (Glaube und Handels. Luthers Freiheitsschrift)", + "Lüsebrink (Theorie und Praxis der Leichtathletik)", + "Tester (Apparatstester)", + "Entry 4", + "Entry 5", + ] + doc = SemapSchilder(entries).send