18 Commits

Author SHA1 Message Date
0458ccd443 update gitignore 2025-05-09 12:06:31 +02:00
98ac7377ac add word_to_semap_as import 2025-04-28 10:18:39 +02:00
5923bfd7ff add logger 2025-04-28 10:18:28 +02:00
7abe3d8cc0 add logger 2025-04-28 10:18:07 +02:00
b4c6169649 add logger, media list refresh, type check ignore 2025-04-28 10:16:56 +02:00
8b83b8c305 add logger 2025-04-28 10:16:24 +02:00
3d2be0fd47 fix graph bug 2025-04-28 10:16:10 +02:00
bbeb9cf701 remove logger variable, declare in files from now on 2025-04-28 10:15:48 +02:00
eb0b7a1fec add testclass, rework logging 2025-04-28 10:15:22 +02:00
80b96865e7 add SemapDocument and Book dataclasses, improve word document parsing 2025-04-25 12:16:14 +02:00
da0e9e0725 fix bug in document creation 2025-04-14 11:08:15 +02:00
f0148d8855 add image, rework SemesterDocument 2025-04-14 11:07:48 +02:00
9cc08e2d91 start work on readme file 2025-04-14 11:07:06 +02:00
921a84304f add printer mail to config 2025-04-07 07:54:34 +02:00
6ff7a70d11 rework logging, fix calculation error 2025-04-07 07:53:57 +02:00
ac32b86b17 fix bug in extend_loan function 2025-04-07 07:53:36 +02:00
dbefd2049f add mail to deliver deletion information 2025-04-07 07:52:01 +02:00
50dd03aee7 extract printer mail to settings file 2025-04-07 07:49:38 +02:00
18 changed files with 640 additions and 416 deletions

1
.gitignore vendored
View File

@@ -228,3 +228,4 @@ config.yaml
**/tempCodeRunnerFile.py **/tempCodeRunnerFile.py
uv.lock uv.lock
uv.lock uv.lock
logs

View File

@@ -1,3 +1,27 @@
# Semesterapparate # SemesterapparatsManager
this repo will be used to create a GUI application to manage the semesterapparate of the PH Freiburg. SemesterapparatsManager is a graphical tool for managing semester apparatuses in the University of Education Freiburg. It allows the users to manage the semester apparatuses in a user-friendly way. It's functions include management of physical and digital semester apparatuses, as well as creating the citations for the digital files of the digital semester apparatuses. For that it uses Zotero, an open source reference management software. The semester apparatuses are stored in a SQLite database, which is created and managed by the SemesterapparatsManager. The SemesterapparatsManager is written in Python and uses the PyQt6 library for the graphical user interface
## Features
- Manage physical semester apparatuses
- Add semester apparatuses
- Edit semester apparatuses
- Delete semester apparatuses
- Extend semester apparatuses
- Notify professors about semester apparatuses creation or deletion
- Add messages to all semester apparatuses, or an individual semester apparatus
- Manage digital semester apparatuses
- Use text parsing to extract information from the submitted form and create the scans
- if a book is used multiple parts of a book are used, it can be split into the parts
- Create the matching citations for the files
- Statistics and Search
- Search semester apparatuses by various criteria
- Show statistics about the semester apparatuses creation and deletion
- Edit user data
## Images
![Main Window](docs/images/mainUI.png)
![Statistics](docs/images/statistics.png)

View File

@@ -37,6 +37,7 @@ class Mail:
sender: str sender: str
password: str password: str
use_user_name: bool use_user_name: bool
printer_mail: str
user_name: str user_name: str
signature: str | None = None signature: str | None = None
empty_signature = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> empty_signature = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">

BIN
docs/images/statistics.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -0,0 +1,18 @@
Subject: Information zur Auflösung des Semesterapparates {AppNr} - {Appname}
MIME-Version: 1.0
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: 8bit
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;">
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{greeting}</p>
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Ihr Semesterapparat "{Appname} ({AppNr})" wurde wie besprochen aufgelöst. </p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Die Medien sind von nun an wieder in den Regalen zu finden.</p>
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><pre class="moz-signature" cols="72">-- </p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{signature}</p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></pre></p></body></html>

View File

@@ -1,10 +1,10 @@
import sys
from config import Config from config import Config
import os import os
from loguru import logger as log
from datetime import datetime
settings = Config("config/config.yaml") settings = Config("config/config.yaml")
if not os.path.exists(settings.database.temp):
os.mkdir(settings.database.temp)
from .utils.icon import Icon from .utils.icon import Icon
__version__ = "0.2.1" __version__ = "0.2.1"
@@ -14,14 +14,3 @@ __author__ = "Alexander Kirchner"
if not os.path.exists("logs"): if not os.path.exists("logs"):
os.mkdir("logs") os.mkdir("logs")
# open and close the file to create it # open and close the file to create it
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)

View File

@@ -5,9 +5,18 @@ from pathlib import Path
from src.backend.database import Database from src.backend.database import Database
db = Database() db = Database()
import loguru
import sys
log = loguru.logger
log.remove()
log.add("application.log", rotation="1 week", retention="1 month")
log.add(
sys.stdout,
)
def recreateFile(name, app_id, filetype, open=True) -> Path: def recreateFile(name: str, app_id: int, filetype: str, open: bool = True) -> Path:
""" """
recreateFile creates a file from the database and opens it in the respective program, if the open parameter is set to True. recreateFile creates a file from the database and opens it in the respective program, if the open parameter is set to True.
@@ -24,6 +33,7 @@ def recreateFile(name, app_id, filetype, open=True) -> Path:
""" """
path = db.recreateFile(name, app_id, filetype=filetype) path = db.recreateFile(name, app_id, filetype=filetype)
path = Path(path) path = Path(path)
log.info(f"File created: {path}")
if open: if open:
if os.getenv("OS") == "Windows_NT": if os.getenv("OS") == "Windows_NT":
path = path.resolve() path = path.resolve()

View File

@@ -5,7 +5,7 @@ from pathlib import Path
from src import settings from src import settings
from typing import Any, List, Optional, Tuple, Union from typing import Any, List, Optional, Tuple, Union
import datetime import datetime
from src import logger
from src.backend.db import ( from src.backend.db import (
CREATE_ELSA_FILES_TABLE, CREATE_ELSA_FILES_TABLE,
CREATE_ELSA_MEDIA_TABLE, CREATE_ELSA_MEDIA_TABLE,
@@ -25,6 +25,20 @@ from src.logic.constants import SEMAP_MEDIA_ACCOUNTS
from src.utils import create_blob, dump_pickle, load_pickle from src.utils import create_blob, dump_pickle, load_pickle
from .semester import Semester from .semester import Semester
from string import ascii_lowercase as lower, digits, punctuation from string import ascii_lowercase as lower, digits, punctuation
import sys
from loguru import logger as log
logger = log
logger.remove()
logger.add("logs/database.log", rotation="1 week", enqueue=True)
log.add(
"logs/application.log",
rotation="1 day",
compression="zip",
)
# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")
logger.add(sys.stdout)
ascii_lowercase = lower + digits + punctuation ascii_lowercase = lower + digits + punctuation
@@ -47,6 +61,7 @@ class Database:
if db_path is None: if db_path is None:
self.db_path = self.database.path + self.database.name self.db_path = self.database.path + self.database.name
self.db_path = self.db_path.replace("~", str(Path.home())) self.db_path = self.db_path.replace("~", str(Path.home()))
logger.debug(self.db_path)
else: else:
self.db_path = db_path self.db_path = db_path
self.checkDatabaseStatus() self.checkDatabaseStatus()
@@ -54,7 +69,6 @@ class Database:
def checkDatabaseStatus(self): def checkDatabaseStatus(self):
path = self.database.path path = self.database.path
path = path.replace("~", str(Path.home())) path = path.replace("~", str(Path.home()))
# print(path)
path = os.path.abspath(path) path = os.path.abspath(path)
if not os.path.exists(path): if not os.path.exists(path):
# create path # create path
@@ -181,7 +195,7 @@ class Database:
# log_message = f"Querying database with query {query}" # log_message = f"Querying database with query {query}"
if "INTO user" in query: if "INTO user" in query:
log_message = f"Querying database with query {query}" log_message = f"Querying database with query {query}"
logger.debug(log_message) # logger.debug(f"DB Query: {log_message}")
try: try:
cursor.execute(query, args) cursor.execute(query, args)
rv = cursor.fetchall() rv = cursor.fetchall()
@@ -204,6 +218,7 @@ class Database:
app_id (str): The apparat id where the book should be added to app_id (str): The apparat id where the book should be added to
prof_id (str): The id of the professor where the book should be added to. prof_id (str): The id of the professor where the book should be added to.
""" """
logger.info(f"Adding book {bookdata.signature} to database")
if app_id is None or prof_id is None: if app_id is None or prof_id is None:
raise ValueError("Apparate ID or Prof ID is None") raise ValueError("Apparate ID or Prof ID is None")
conn = self.connect() conn = self.connect()
@@ -387,7 +402,7 @@ class Database:
def getBooks( def getBooks(
self, app_id: Union[str, int], prof_id: Union[str, int], deleted=0 self, app_id: Union[str, int], prof_id: Union[str, int], deleted=0
) -> list[dict[int, BookData, int]]: ) -> list[dict[str, Union[BookData, int]]]:
""" """
Get the Books based on the apparat id and the professor id Get the Books based on the apparat id and the professor id
@@ -406,14 +421,14 @@ class Database:
if qdata is None: if qdata is None:
return [] return []
for result_a in qdata: for result_a in qdata:
data = {"id": int, "bookdata": BookData, "available": int} data: dict[str, Any] = {"id": int, "bookdata": BookData, "available": int}
data["id"] = result_a[0] data["id"] = result_a[0]
data["bookdata"] = load_pickle(result_a[1]) data["bookdata"] = load_pickle(result_a[1])
data["available"] = result_a[2] data["available"] = result_a[2]
ret_result.append(data) ret_result.append(data)
return ret_result return ret_result
def updateBookdata(self, book_id, bookdata: BookData): def updateBookdata(self, book_id: int, bookdata: BookData):
""" """
Update the bookdata in the database Update the bookdata in the database
@@ -487,7 +502,7 @@ class Database:
str: The filename of the recreated file str: The filename of the recreated file
""" """
blob = self.getBlob(filename, app_id) blob = self.getBlob(filename, app_id)
tempdir = self.database.tempdir tempdir = self.database.temp
tempdir = tempdir.replace("~", str(Path.home())) tempdir = tempdir.replace("~", str(Path.home()))
tempdir_path = Path(tempdir) tempdir_path = Path(tempdir)
if not os.path.exists(tempdir_path): if not os.path.exists(tempdir_path):
@@ -816,7 +831,7 @@ class Database:
) )
else: else:
self.query_db( self.query_db(
"UPDATE semesterapparat SET verlängerung_bis=?, verlängerung_am=? WHERE appnr=?", "UPDATE semesterapparat SET verlängerung_bis=?, verlängert_am=? WHERE appnr=?",
(newDate, today, app_id), (newDate, today, app_id),
) )
@@ -1183,7 +1198,7 @@ class Database:
"UPDATE user SET password=? WHERE username=?", (new_password, user) "UPDATE user SET password=? WHERE username=?", (new_password, user)
) )
def getRole(self, user): def getRole(self, user: str) -> str:
"""get the role of the user """get the role of the user
Args: Args:
@@ -1510,7 +1525,7 @@ class Database:
else: else:
return [] return []
def getProfId(self, profdata: dict | Prof): def getProfId(self, profdata: dict[str, Any] | Prof):
"""Get the prof ID based on the profdata """Get the prof ID based on the profdata
Args: Args:

View File

@@ -11,7 +11,9 @@ class Semester:
_semester: str | None = None _semester: str | None = None
_month: int | None = datetime.datetime.now().month _month: int | None = datetime.datetime.now().month
value: str = None value: str = None
logger.debug(
f"Initialized Semester class with values: month: {_month}, semester: {_semester}, year {_year}"
)
def __post_init__(self): def __post_init__(self):
if isinstance(self._year, str): if isinstance(self._year, str):
self._year = int(self._year) self._year = int(self._year)
@@ -27,7 +29,7 @@ class Semester:
return self.value return self.value
def generateSemester(self): def generateSemester(self):
if self._month < 4 or self._month < 9: if self._month <= 3 or self._month > 9:
self._semester = "WiSe" self._semester = "WiSe"
else: else:
self._semester = "SoSe" self._semester = "SoSe"
@@ -36,11 +38,12 @@ class Semester:
def computeValue(self): def computeValue(self):
# year is only last two digits # year is only last two digits
year = self._year year = self._year
valueyear = str(year)
if self._semester == "WiSe": if self._semester == "WiSe":
if self._month < 4: if self._month < 4:
valueyear = str(year - 1) + "/" + str(year) valueyear = str(year - 1) + "/" + str(year)
else: else:
valueyear = str(year) valueyear = str(year) + "/" + str(year + 1)
self.value = f"{self._semester} {valueyear}" self.value = f"{self._semester} {valueyear}"
@logger.catch @logger.catch

View File

@@ -5,13 +5,27 @@ from PyQt6.QtCore import pyqtSignal as Signal
from src.backend import Database from src.backend import Database
from src.logic.webrequest import BibTextTransformer, WebRequest from src.logic.webrequest import BibTextTransformer, WebRequest
import sys
from loguru import logger as log
logger = log
logger.remove()
logger.add("logs/bookgrabber_thread.log", rotation="1 week", enqueue=True)
log.add(
"logs/application.log",
rotation="1 day",
compression="zip",
)
# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")
logger.add(sys.stdout)
class BookGrabber(QThread): class BookGrabber(QThread):
updateSignal = Signal(int, int) updateSignal = Signal(int, int)
done = Signal() done = Signal()
def __init__(self, appnr): def __init__(self, appnr: int):
super(BookGrabber, self).__init__(parent=None) super(BookGrabber, self).__init__(parent=None)
self.is_Running = True self.is_Running = True
logger.info("Starting worker thread") logger.info("Starting worker thread")
@@ -25,7 +39,9 @@ class BookGrabber(QThread):
self.appnr = appnr self.appnr = appnr
self.tstate = (self.app_id, self.prof_id, self.mode, self.data) self.tstate = (self.app_id, self.prof_id, self.mode, self.data)
def add_values(self, app_id, prof_id, mode, data, any_book=False, exact=False): def add_values(
self, app_id: int, prof_id: int, mode: str, data, any_book=False, exact=False
):
self.app_id = app_id self.app_id = app_id
self.prof_id = prof_id self.prof_id = prof_id
self.mode = mode self.mode = mode
@@ -85,11 +101,13 @@ class BookGrabber(QThread):
break break
logger.info(f"State of {signature}: {state}") logger.info(f"State of {signature}: {state}")
# print("updating availability of " + str(self.book_id) + " to " + str(state)) print("updating availability of " + str(self.book_id) + " to " + str(state))
try: try:
self.db.setAvailability(self.book_id, state) self.db.setAvailability(self.book_id, state)
except sqlite3.OperationalError as e: print("Added book to database")
except Exception as e:
logger.error(f"Failed to update availability: {e}") logger.error(f"Failed to update availability: {e}")
print("Failed to update availability: " + str(e))
# time.sleep(5) # time.sleep(5)
item += 1 item += 1
@@ -102,87 +120,89 @@ class BookGrabber(QThread):
self.is_Running = False self.is_Running = False
# class BookGrabber(object): class BookGrabberTest(QThread):
# updateSignal = Signal(int, int) updateSignal = Signal(int, int)
# done = Signal() done = Signal()
# def __init__(self, app_id, prof_id, mode, data, parent=None): def __init__(self, appnr: int):
# super(BookGrabber, self).__init__(parent=None) super(BookGrabberTest, self).__init__(parent=None)
# self.is_Running = True self.is_Running = True
# logger = MyLogger("Worker") logger.info("Starting worker thread")
# logger.info("Starting worker thread") self.data = None
# self.data = data self.app_id = None
# logger.info(f"Working on {len(self.data)} entries") self.prof_id = None
# self.app_id = app_id self.mode = None
# self.prof_id = prof_id self.book_id = None
# self.mode = mode self.use_any = False
# self.book_id = None self.use_exact = False
# self.state = (self.app_id, self.prof_id, self.mode, self.data) self.appnr = appnr
# # print(self.state) self.tstate = (self.app_id, self.prof_id, self.mode, self.data)
# logger.info("state: " + str(self.state)) self.results = []
# # time.sleep(2)
# def resetValues(self): def add_values(
# self.app_id = None self, app_id: int, prof_id: int, mode: str, data, any_book=False, exact=False
# self.prof_id = None ):
# self.mode = None self.app_id = app_id
# self.data = None self.prof_id = prof_id
# self.book_id = None self.mode = mode
self.data = data
self.use_any = any_book
self.use_exact = exact
logger.info(f"Working on {len(self.data)} entries")
self.tstate = (self.app_id, self.prof_id, self.mode, self.data)
logger.debug("State: " + str(self.tstate))
# print(self.tstate)
# def run(self): def run(self):
# while self.is_Running: item = 0
# self.db = Database() iterdata = self.data
# item = 0 # print(iterdata)
# iterdata = self.data for entry in iterdata:
# # print(iterdata) # print(entry)
# for entry in iterdata: signature = str(entry)
# # print(entry) logger.info("Processing entry: " + signature)
# signature = str(entry)
# logger.info("Processing entry: " + signature)
# webdata = WebRequest().get_ppn(entry).get_data() webdata = WebRequest().set_apparat(self.appnr).get_ppn(entry)
# if webdata == "error": if self.use_any:
# continue webdata = webdata.use_any_book
# bd = BibTextTransformer(self.mode).get_data(webdata).return_data() webdata = webdata.get_data()
# transformer = BibTextTransformer("RDS")
# rds = transformer.get_data(webdata).return_data("rds_availability")
# bd.signature = entry
# # confirm lock is acquired
# self.db.addBookToDatabase(bd, self.app_id, self.prof_id)
# # get latest book id
# self.book_id = self.db.getLastBookId()
# logger.info("Added book to database")
# state = 0
# # print(len(rds.items))
# for rds_item in rds.items:
# sign = rds_item.superlocation
# loc = rds_item.location
# # logger.debug(sign, loc)
# # logger.debug(rds_item)
# if self.app_id in sign or self.app_id in loc:
# state = 1
# break
# logger.info(f"State of {signature}: {state}") if webdata == "error":
# # print( continue
# "updating availability of "
# + str(self.book_id)
# + " to "
# + str(state)
# )
# try:
# self.db.setAvailability(self.book_id, state)
# except sqlite3.OperationalError as e:
# logger.error(f"Failed to update availability: {e}")
# # time.sleep(5) bd = BibTextTransformer(self.mode)
# item += 1 if self.mode == "ARRAY":
# self.updateSignal.emit(item, len(self.data)) if self.use_exact:
# logger.info("Worker thread finished") bd = bd.use_signature(entry)
# # self.done.emit() bd = bd.get_data(webdata).return_data()
# self.stop() if bd is None:
# if not self.is_Running: # bd = BookData
# break continue
bd.signature = entry
transformer = (
BibTextTransformer("RDS").get_data(webdata).return_data("rds_data")
)
# def stop(self): # confirm lock is acquired
# self.is_Running = False # get latest book id
logger.info("Added book to database")
state = 0
for result in transformer.RDS_DATA:
# print(result.RDS_LOCATION)
if str(self.app_id) in result.RDS_LOCATION:
state = 1
break
logger.info(f"State of {signature}: {state}")
# print("updating availability of " + str(self.book_id) + " to " + str(state))
self.results.append(bd)
# time.sleep(5)
item += 1
self.updateSignal.emit(item, len(self.data))
logger.info("Worker thread finished")
# self.done.emit()
self.quit()
def stop(self):
self.is_Running = False

View File

@@ -2,5 +2,5 @@ from .dataclass import ApparatData, BookData, Prof, Apparat, ELSA
from .c_sort import custom_sort, sort_semesters_list from .c_sort import custom_sort, sort_semesters_list
from .constants import APP_NRS, PROF_TITLES, SEMAP_MEDIA_ACCOUNTS from .constants import APP_NRS, PROF_TITLES, SEMAP_MEDIA_ACCOUNTS
from .csvparser import csv_to_list from .csvparser import csv_to_list
from .wordparser import elsa_word_to_csv, word_docx_to_csv from .wordparser import elsa_word_to_csv, word_docx_to_csv, word_to_semap
from .zotero import ZoteroController from .zotero import ZoteroController

View File

@@ -1,13 +1,106 @@
import pandas as pd import pandas as pd
from docx import Document from docx import Document
from dataclasses import dataclass
import sys
from loguru import logger as log
logger = log
logger.remove()
logger.add("logs/wordparser.log", rotation="1 week", enqueue=True)
log.add(
f"logs/application.log",
rotation="1 day",
compression="zip",
enqueue=True,
)
# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")
logger.add(sys.stdout)
letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
def word_docx_to_csv(path) -> pd.DataFrame: @dataclass
class Book:
author: str = None
year: str = None
edition: str = None
title: str = None
location: str = None
publisher: str = None
signature: str = None
internal_notes: str = None
@property
def has_signature(self) -> bool:
return self.signature is not None and self.signature != ""
@property
def is_empty(self) -> bool:
return all(
[
self.author == "",
self.year == "",
self.edition == "",
self.title == "",
self.location == "",
self.publisher == "",
self.signature == "",
self.internal_notes == "",
]
)
def from_dict(self, data: dict):
for key, value in data.items():
if value == "\u2002\u2002\u2002\u2002\u2002":
value = ""
if key == "Autorenname(n):Nachname, Vorname":
self.author = value
elif key == "Jahr/Auflage":
self.year = value.split("/")[0] if "/" in value else value
self.edition = value.split("/")[1] if "/" in value else ""
elif key == "Titel":
self.title = value
elif key == "Ort und Verlag":
self.location = value.split(",")[0] if "," in value else value
self.publisher = value.split(",")[1] if "," in value else ""
elif key == "Standnummer":
self.signature = value
elif key == "Interne Vermerke":
self.internal_notes = value
@dataclass
class SemapDocument:
subject: str = None
phoneNumber: int = None
mail: str = None
title: str = None
semester: str = None
books: list[Book] = 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}"
@property
def signatures(self) -> list[str]:
if self.books is not None:
return [book.signature for book in self.books if book.has_signature]
return []
def word_docx_to_csv(path: str) -> list[pd.DataFrame]:
doc = Document(path) doc = Document(path)
tables = doc.tables tables = doc.tables
m_data = [] m_data = []
for table in tables: for table in tables:
data = [] data = []
@@ -24,8 +117,9 @@ def word_docx_to_csv(path) -> pd.DataFrame:
m_data.append(df) m_data.append(df)
df = m_data[2] # for df[0, 1]: merge i and i+1 as key, value
return df
return m_data
def makeDict(): def makeDict():
@@ -122,6 +216,45 @@ def elsa_word_to_csv(path):
return tuple_to_dict(data, doctype), doctype return tuple_to_dict(data, doctype), doctype
def word_to_semap(word_path: str) -> SemapDocument:
logger.info("Parsing Word Document {}", word_path)
semap = 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:"]
apparatdata = df[1]
apparatdata = apparatdata.to_dict()
keys = list(apparatdata.keys())
appdata = {keys[i]: keys[i + 1] for i in range(0, len(keys), 2)}
semap.title = appdata["Veranstaltung:"]
semap.semester = appdata["Semester:"]
semap.renameSemester
books = df[2]
booklist = []
for i in range(len(books)):
if books.iloc[i].isnull().all():
continue
data = books.iloc[i].to_dict()
book = Book()
book.from_dict(data)
if book.is_empty:
continue
elif not book.has_signature:
continue
else:
booklist.append(book)
logger.info("Found {} books", len(booklist))
semap.books = booklist
return semap
if __name__ == "__main__": if __name__ == "__main__":
else_df = elsa_word_to_csv("C:/Users/aky547/Desktop/Antrag ELSA Schweitzer.docx") else_df = word_to_semap(
# print(else_df) "C:/Users/aky547/Desktop/SA 80 titelmeldung_SoSe2025 Burth.docx"
)

View File

@@ -14,10 +14,7 @@ class Ui_Dialog(object):
Dialog.setObjectName("Dialog") Dialog.setObjectName("Dialog")
Dialog.setWindowModality(QtCore.Qt.WindowModality.NonModal) Dialog.setWindowModality(QtCore.Qt.WindowModality.NonModal)
Dialog.resize(651, 679) Dialog.resize(651, 679)
sizePolicy = QtWidgets.QSizePolicy( sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding)
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(Dialog.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(Dialog.sizePolicy().hasHeightForWidth())
@@ -25,10 +22,7 @@ class Ui_Dialog(object):
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog) self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setObjectName("verticalLayout")
self.toolBox = QtWidgets.QToolBox(parent=Dialog) self.toolBox = QtWidgets.QToolBox(parent=Dialog)
sizePolicy = QtWidgets.QSizePolicy( sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding)
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.toolBox.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.toolBox.sizePolicy().hasHeightForWidth())
@@ -37,10 +31,7 @@ class Ui_Dialog(object):
self.toolBox.setObjectName("toolBox") self.toolBox.setObjectName("toolBox")
self.page_1 = QtWidgets.QWidget() self.page_1 = QtWidgets.QWidget()
self.page_1.setGeometry(QtCore.QRect(0, 0, 633, 511)) self.page_1.setGeometry(QtCore.QRect(0, 0, 633, 511))
sizePolicy = QtWidgets.QSizePolicy( sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding)
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.page_1.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.page_1.sizePolicy().hasHeightForWidth())
@@ -73,12 +64,7 @@ class Ui_Dialog(object):
self.save_path = QtWidgets.QLineEdit(parent=self.page_1) self.save_path = QtWidgets.QLineEdit(parent=self.page_1)
self.save_path.setObjectName("save_path") self.save_path.setObjectName("save_path")
self.gridLayout_3.addWidget(self.save_path, 2, 1, 1, 1) self.gridLayout_3.addWidget(self.save_path, 2, 1, 1, 1)
spacerItem = QtWidgets.QSpacerItem( spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
self.gridLayout_3.addItem(spacerItem, 3, 1, 1, 1) self.gridLayout_3.addItem(spacerItem, 3, 1, 1, 1)
self.toolBox.addItem(self.page_1, "") self.toolBox.addItem(self.page_1, "")
self.page_2 = QtWidgets.QWidget() self.page_2 = QtWidgets.QWidget()
@@ -99,10 +85,7 @@ class Ui_Dialog(object):
self.label_3.setObjectName("label_3") self.label_3.setObjectName("label_3")
self.gridLayout.addWidget(self.label_3, 1, 0, 1, 1) self.gridLayout.addWidget(self.label_3, 1, 0, 1, 1)
self.zotero_api_key = QtWidgets.QLineEdit(parent=self.page_2) self.zotero_api_key = QtWidgets.QLineEdit(parent=self.page_2)
self.zotero_api_key.setInputMethodHints( self.zotero_api_key.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhHiddenText|QtCore.Qt.InputMethodHint.ImhSensitiveData)
QtCore.Qt.InputMethodHint.ImhHiddenText
| QtCore.Qt.InputMethodHint.ImhSensitiveData
)
self.zotero_api_key.setObjectName("zotero_api_key") self.zotero_api_key.setObjectName("zotero_api_key")
self.gridLayout.addWidget(self.zotero_api_key, 0, 2, 1, 1) self.gridLayout.addWidget(self.zotero_api_key, 0, 2, 1, 1)
self.label_2 = QtWidgets.QLabel(parent=self.page_2) self.label_2 = QtWidgets.QLabel(parent=self.page_2)
@@ -112,12 +95,7 @@ class Ui_Dialog(object):
self.toggle_api_visibility.setText("") self.toggle_api_visibility.setText("")
self.toggle_api_visibility.setObjectName("toggle_api_visibility") self.toggle_api_visibility.setObjectName("toggle_api_visibility")
self.gridLayout.addWidget(self.toggle_api_visibility, 0, 3, 1, 1) self.gridLayout.addWidget(self.toggle_api_visibility, 0, 3, 1, 1)
spacerItem1 = QtWidgets.QSpacerItem( spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
self.gridLayout.addItem(spacerItem1, 3, 2, 1, 1) self.gridLayout.addItem(spacerItem1, 3, 2, 1, 1)
self.toolBox.addItem(self.page_2, "") self.toolBox.addItem(self.page_2, "")
self.page_3 = QtWidgets.QWidget() self.page_3 = QtWidgets.QWidget()
@@ -133,67 +111,63 @@ class Ui_Dialog(object):
self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.gridLayout_2 = QtWidgets.QGridLayout() self.gridLayout_2 = QtWidgets.QGridLayout()
self.gridLayout_2.setObjectName("gridLayout_2") self.gridLayout_2.setObjectName("gridLayout_2")
self.smtp_address = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2) self.use_username_smtp_login = QtWidgets.QCheckBox(parent=self.email_settingsPage1_2)
self.smtp_address.setClearButtonEnabled(True)
self.smtp_address.setObjectName("smtp_address")
self.gridLayout_2.addWidget(self.smtp_address, 0, 1, 1, 1)
self.label_8 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label_8.setObjectName("label_8")
self.gridLayout_2.addWidget(self.label_8, 3, 0, 1, 1)
self.use_username_smtp_login = QtWidgets.QCheckBox(
parent=self.email_settingsPage1_2
)
self.use_username_smtp_login.setTristate(False) self.use_username_smtp_login.setTristate(False)
self.use_username_smtp_login.setObjectName("use_username_smtp_login") self.use_username_smtp_login.setObjectName("use_username_smtp_login")
self.gridLayout_2.addWidget(self.use_username_smtp_login, 4, 1, 1, 1) self.gridLayout_2.addWidget(self.use_username_smtp_login, 4, 1, 1, 1)
self.label_6 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label_6.setObjectName("label_6")
self.gridLayout_2.addWidget(self.label_6, 1, 0, 1, 1)
self.smtp_port = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
self.smtp_port.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhDigitsOnly|QtCore.Qt.InputMethodHint.ImhPreferNumbers)
self.smtp_port.setClearButtonEnabled(True)
self.smtp_port.setObjectName("smtp_port")
self.gridLayout_2.addWidget(self.smtp_port, 1, 1, 1, 1)
self.label_7 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label_7.setObjectName("label_7")
self.gridLayout_2.addWidget(self.label_7, 2, 0, 1, 1)
self.sender_email = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
self.sender_email.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhEmailCharactersOnly)
self.sender_email.setClearButtonEnabled(True)
self.sender_email.setObjectName("sender_email")
self.gridLayout_2.addWidget(self.sender_email, 2, 1, 1, 1)
self.mail_username = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2) self.mail_username = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
self.mail_username.setClearButtonEnabled(True) self.mail_username.setClearButtonEnabled(True)
self.mail_username.setObjectName("mail_username") self.mail_username.setObjectName("mail_username")
self.gridLayout_2.addWidget(self.mail_username, 3, 1, 1, 1) self.gridLayout_2.addWidget(self.mail_username, 3, 1, 1, 1)
self.smtp_port = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
self.smtp_port.setInputMethodHints(
QtCore.Qt.InputMethodHint.ImhDigitsOnly
| QtCore.Qt.InputMethodHint.ImhPreferNumbers
)
self.smtp_port.setClearButtonEnabled(True)
self.smtp_port.setObjectName("smtp_port")
self.gridLayout_2.addWidget(self.smtp_port, 1, 1, 1, 1)
self.label_10 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label_10.setObjectName("label_10")
self.gridLayout_2.addWidget(self.label_10, 5, 0, 1, 1)
self.label_7 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label_7.setObjectName("label_7")
self.gridLayout_2.addWidget(self.label_7, 2, 0, 1, 1)
self.label_9 = QtWidgets.QLabel(parent=self.email_settingsPage1_2) self.label_9 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label_9.setText("") self.label_9.setText("")
self.label_9.setObjectName("label_9") self.label_9.setObjectName("label_9")
self.gridLayout_2.addWidget(self.label_9, 6, 0, 1, 1) self.gridLayout_2.addWidget(self.label_9, 7, 0, 1, 1)
self.sender_email = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
self.sender_email.setInputMethodHints(
QtCore.Qt.InputMethodHint.ImhEmailCharactersOnly
)
self.sender_email.setClearButtonEnabled(True)
self.sender_email.setObjectName("sender_email")
self.gridLayout_2.addWidget(self.sender_email, 2, 1, 1, 1)
self.label = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label.setObjectName("label")
self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1)
self.password = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2) self.password = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
self.password.setInputMethodHints( self.password.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhHiddenText|QtCore.Qt.InputMethodHint.ImhSensitiveData)
QtCore.Qt.InputMethodHint.ImhHiddenText
| QtCore.Qt.InputMethodHint.ImhSensitiveData
)
self.password.setClearButtonEnabled(True) self.password.setClearButtonEnabled(True)
self.password.setObjectName("password") self.password.setObjectName("password")
self.gridLayout_2.addWidget(self.password, 5, 1, 1, 1) self.gridLayout_2.addWidget(self.password, 5, 1, 1, 1)
self.label_6 = QtWidgets.QLabel(parent=self.email_settingsPage1_2) self.smtp_address = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
self.label_6.setObjectName("label_6") self.smtp_address.setClearButtonEnabled(True)
self.gridLayout_2.addWidget(self.label_6, 1, 0, 1, 1) self.smtp_address.setObjectName("smtp_address")
self.gridLayout_2.addWidget(self.smtp_address, 0, 1, 1, 1)
self.label = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label.setObjectName("label")
self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1)
self.label_10 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label_10.setObjectName("label_10")
self.gridLayout_2.addWidget(self.label_10, 5, 0, 1, 1)
self.togglePassword = QtWidgets.QPushButton(parent=self.email_settingsPage1_2) self.togglePassword = QtWidgets.QPushButton(parent=self.email_settingsPage1_2)
self.togglePassword.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.togglePassword.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.togglePassword.setText("") self.togglePassword.setText("")
self.togglePassword.setObjectName("togglePassword") self.togglePassword.setObjectName("togglePassword")
self.gridLayout_2.addWidget(self.togglePassword, 5, 2, 1, 1) self.gridLayout_2.addWidget(self.togglePassword, 5, 2, 1, 1)
self.label_8 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label_8.setObjectName("label_8")
self.gridLayout_2.addWidget(self.label_8, 3, 0, 1, 1)
self.label_13 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label_13.setObjectName("label_13")
self.gridLayout_2.addWidget(self.label_13, 6, 0, 1, 1)
self.printermail = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
self.printermail.setObjectName("printermail")
self.gridLayout_2.addWidget(self.printermail, 6, 1, 1, 1)
self.horizontalLayout_4.addLayout(self.gridLayout_2) self.horizontalLayout_4.addLayout(self.gridLayout_2)
self.email_settings.addTab(self.email_settingsPage1_2, "") self.email_settings.addTab(self.email_settingsPage1_2, "")
self.email_settingsPage2_2 = QtWidgets.QWidget() self.email_settingsPage2_2 = QtWidgets.QWidget()
@@ -204,12 +178,7 @@ class Ui_Dialog(object):
self.verticalLayout_2.setObjectName("verticalLayout_2") self.verticalLayout_2.setObjectName("verticalLayout_2")
self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.horizontalLayout_3.setObjectName("horizontalLayout_3")
spacerItem2 = QtWidgets.QSpacerItem( spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_3.addItem(spacerItem2) self.horizontalLayout_3.addItem(spacerItem2)
self.bold = QtWidgets.QPushButton(parent=self.email_settingsPage2_2) self.bold = QtWidgets.QPushButton(parent=self.email_settingsPage2_2)
self.bold.setCheckable(True) self.bold.setCheckable(True)
@@ -223,12 +192,7 @@ class Ui_Dialog(object):
self.underscore.setCheckable(True) self.underscore.setCheckable(True)
self.underscore.setObjectName("underscore") self.underscore.setObjectName("underscore")
self.horizontalLayout_3.addWidget(self.underscore) self.horizontalLayout_3.addWidget(self.underscore)
spacerItem3 = QtWidgets.QSpacerItem( spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_3.addItem(spacerItem3) self.horizontalLayout_3.addItem(spacerItem3)
self.verticalLayout_2.addLayout(self.horizontalLayout_3) self.verticalLayout_2.addLayout(self.horizontalLayout_3)
self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout = QtWidgets.QHBoxLayout()
@@ -254,12 +218,7 @@ class Ui_Dialog(object):
self.font_size.addItem("") self.font_size.addItem("")
self.font_size.addItem("") self.font_size.addItem("")
self.horizontalLayout.addWidget(self.font_size) self.horizontalLayout.addWidget(self.font_size)
spacerItem4 = QtWidgets.QSpacerItem( spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout.addItem(spacerItem4) self.horizontalLayout.addItem(spacerItem4)
self.verticalLayout_2.addLayout(self.horizontalLayout) self.verticalLayout_2.addLayout(self.horizontalLayout)
self.verticalLayout_3.addLayout(self.verticalLayout_2) self.verticalLayout_3.addLayout(self.verticalLayout_2)
@@ -316,10 +275,7 @@ class Ui_Dialog(object):
self.verticalLayout.addWidget(self.toolBox) self.verticalLayout.addWidget(self.toolBox)
self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog) self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons( self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Ok
)
self.buttonBox.setObjectName("buttonBox") self.buttonBox.setObjectName("buttonBox")
self.verticalLayout.addWidget(self.buttonBox) self.verticalLayout.addWidget(self.buttonBox)
self.label_5.setBuddy(self.db_name) self.label_5.setBuddy(self.db_name)
@@ -327,7 +283,7 @@ class Ui_Dialog(object):
self.label_11.setBuddy(self.db_path) self.label_11.setBuddy(self.db_path)
self.retranslateUi(Dialog) self.retranslateUi(Dialog)
self.toolBox.setCurrentIndex(3) self.toolBox.setCurrentIndex(2)
self.email_settings.setCurrentIndex(0) self.email_settings.setCurrentIndex(0)
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
@@ -337,53 +293,29 @@ class Ui_Dialog(object):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog")) Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.db_name.setText(_translate("Dialog", "sap.db")) self.db_name.setText(_translate("Dialog", "sap.db"))
self.label_5.setToolTip( self.label_5.setToolTip(_translate("Dialog", "<html><head/><body><p>Name der Datenbank, welche verwendet werden soll. <span style=\" font-weight:600;\">Muss</span> auf .db enden</p></body></html>"))
_translate(
"Dialog",
'<html><head/><body><p>Name der Datenbank, welche verwendet werden soll. <span style=" font-weight:600;">Muss</span> auf .db enden</p></body></html>',
)
)
self.label_5.setText(_translate("Dialog", "Datenbankname")) self.label_5.setText(_translate("Dialog", "Datenbankname"))
self.label_12.setToolTip( self.label_12.setToolTip(_translate("Dialog", "Pfad, an dem heruntergeladene Dateien gespeichert werden sollen"))
_translate(
"Dialog",
"Pfad, an dem heruntergeladene Dateien gespeichert werden sollen",
)
)
self.label_12.setText(_translate("Dialog", "Temporäre Dateien")) self.label_12.setText(_translate("Dialog", "Temporäre Dateien"))
self.label_11.setText(_translate("Dialog", "Datenbankpfad")) self.label_11.setText(_translate("Dialog", "Datenbankpfad"))
self.tb_set_save_path.setText(_translate("Dialog", "...")) self.tb_set_save_path.setText(_translate("Dialog", "..."))
self.tb_select_db.setText(_translate("Dialog", "...")) self.tb_select_db.setText(_translate("Dialog", "..."))
self.toolBox.setItemText( self.toolBox.setItemText(self.toolBox.indexOf(self.page_1), _translate("Dialog", "Datenbank"))
self.toolBox.indexOf(self.page_1), _translate("Dialog", "Datenbank")
)
self.label_4.setText(_translate("Dialog", "Bibliothekstyp")) self.label_4.setText(_translate("Dialog", "Bibliothekstyp"))
self.label_3.setText(_translate("Dialog", "Bibliotheks-ID")) self.label_3.setText(_translate("Dialog", "Bibliotheks-ID"))
self.label_2.setText(_translate("Dialog", "API Key")) self.label_2.setText(_translate("Dialog", "API Key"))
self.toolBox.setItemText( self.toolBox.setItemText(self.toolBox.indexOf(self.page_2), _translate("Dialog", "Zotero"))
self.toolBox.indexOf(self.page_2), _translate("Dialog", "Zotero") self.use_username_smtp_login.setStatusTip(_translate("Dialog", "Anklicken, wenn Nutzername benötigt wird, um sich beim Server anzumelden"))
) self.use_username_smtp_login.setText(_translate("Dialog", "Nutzername zum\n"
self.label_8.setText(_translate("Dialog", "Nutzername")) " Anmelden verwenden"))
self.use_username_smtp_login.setStatusTip(
_translate(
"Dialog",
"Anklicken, wenn Nutzername benötigt wird, um sich beim Server anzumelden",
)
)
self.use_username_smtp_login.setText(
_translate("Dialog", "Nutzername zum\n Anmelden verwenden")
)
self.mail_username.setStatusTip(
_translate("Dialog", "Kürzel, von der Hochschule vergeben, bsp: Aky547")
)
self.label_10.setText(_translate("Dialog", "Passwort"))
self.label_7.setText(_translate("Dialog", "Sender-eMail"))
self.label.setText(_translate("Dialog", "SMTP-Server"))
self.label_6.setText(_translate("Dialog", "Port")) self.label_6.setText(_translate("Dialog", "Port"))
self.email_settings.setTabText( self.label_7.setText(_translate("Dialog", "Sender-eMail"))
self.email_settings.indexOf(self.email_settingsPage1_2), self.mail_username.setStatusTip(_translate("Dialog", "Kürzel, von der Hochschule vergeben, bsp: Aky547"))
_translate("Dialog", "Allgemeines"), self.label.setText(_translate("Dialog", "SMTP-Server"))
) self.label_10.setText(_translate("Dialog", "Passwort"))
self.label_8.setText(_translate("Dialog", "Nutzername"))
self.label_13.setText(_translate("Dialog", "Printmail"))
self.email_settings.setTabText(self.email_settings.indexOf(self.email_settingsPage1_2), _translate("Dialog", "Allgemeines"))
self.bold.setText(_translate("Dialog", "Fett")) self.bold.setText(_translate("Dialog", "Fett"))
self.italic.setText(_translate("Dialog", "Kursiv")) self.italic.setText(_translate("Dialog", "Kursiv"))
self.underscore.setText(_translate("Dialog", "Unterstrichen")) self.underscore.setText(_translate("Dialog", "Unterstrichen"))
@@ -403,14 +335,7 @@ class Ui_Dialog(object):
self.font_size.setItemText(13, _translate("Dialog", "48")) self.font_size.setItemText(13, _translate("Dialog", "48"))
self.font_size.setItemText(14, _translate("Dialog", "72")) self.font_size.setItemText(14, _translate("Dialog", "72"))
self.debug.setText(_translate("Dialog", "Debug")) self.debug.setText(_translate("Dialog", "Debug"))
self.email_settings.setTabText( self.email_settings.setTabText(self.email_settings.indexOf(self.email_settingsPage2_2), _translate("Dialog", "Signatur"))
self.email_settings.indexOf(self.email_settingsPage2_2), self.toolBox.setItemText(self.toolBox.indexOf(self.page_3), _translate("Dialog", "e-Mail"))
_translate("Dialog", "Signatur"),
)
self.toolBox.setItemText(
self.toolBox.indexOf(self.page_3), _translate("Dialog", "e-Mail")
)
self.groupBox.setTitle(_translate("Dialog", "Farben")) self.groupBox.setTitle(_translate("Dialog", "Farben"))
self.toolBox.setItemText( self.toolBox.setItemText(self.toolBox.indexOf(self.page_4), _translate("Dialog", "Icons"))
self.toolBox.indexOf(self.page_4), _translate("Dialog", "Icons")
)

View File

@@ -35,7 +35,7 @@
<set>Qt::ImhNone</set> <set>Qt::ImhNone</set>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>3</number> <number>2</number>
</property> </property>
<widget class="QWidget" name="page_1"> <widget class="QWidget" name="page_1">
<property name="geometry"> <property name="geometry">
@@ -232,20 +232,6 @@
<layout class="QHBoxLayout" name="horizontalLayout_4"> <layout class="QHBoxLayout" name="horizontalLayout_4">
<item> <item>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
<widget class="QLineEdit" name="smtp_address">
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Nutzername</string>
</property>
</widget>
</item>
<item row="4" column="1"> <item row="4" column="1">
<widget class="QCheckBox" name="use_username_smtp_login"> <widget class="QCheckBox" name="use_username_smtp_login">
<property name="statusTip"> <property name="statusTip">
@@ -260,13 +246,10 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="1" column="0">
<widget class="QLineEdit" name="mail_username"> <widget class="QLabel" name="label_6">
<property name="statusTip"> <property name="text">
<string>Kürzel, von der Hochschule vergeben, bsp: Aky547</string> <string>Port</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
@@ -280,13 +263,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Passwort</string>
</property>
</widget>
</item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_7"> <widget class="QLabel" name="label_7">
<property name="text"> <property name="text">
@@ -294,13 +270,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="QLineEdit" name="sender_email"> <widget class="QLineEdit" name="sender_email">
<property name="inputMethodHints"> <property name="inputMethodHints">
@@ -311,10 +280,20 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item row="3" column="1">
<widget class="QLabel" name="label"> <widget class="QLineEdit" name="mail_username">
<property name="statusTip">
<string>Kürzel, von der Hochschule vergeben, bsp: Aky547</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_9">
<property name="text"> <property name="text">
<string>SMTP-Server</string> <string/>
</property> </property>
</widget> </widget>
</item> </item>
@@ -328,10 +307,24 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="0" column="1">
<widget class="QLabel" name="label_6"> <widget class="QLineEdit" name="smtp_address">
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Port</string> <string>SMTP-Server</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Passwort</string>
</property> </property>
</widget> </widget>
</item> </item>
@@ -345,6 +338,23 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Nutzername</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Printmail</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="printermail"/>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>

View File

@@ -78,6 +78,8 @@ class Settings(QtWidgets.QDialog, _settings):
self.sender_email.setText(settings.mail.sender) self.sender_email.setText(settings.mail.sender)
self.mail_username.setText(settings.mail.user_name) self.mail_username.setText(settings.mail.user_name)
self.password.setText(settings.mail.password) self.password.setText(settings.mail.password)
self.printermail.setText(settings.mail.printer_mail)
self.printermail.setPlaceholderText("E-Mail-Adresse des Druckers")
self.use_username_smtp_login.setChecked( self.use_username_smtp_login.setChecked(
settings.mail.use_user_name if settings.mail.use_user_name else False settings.mail.use_user_name if settings.mail.use_user_name else False
) )
@@ -200,6 +202,7 @@ class Settings(QtWidgets.QDialog, _settings):
settings.set_mail_attr( settings.set_mail_attr(
"use_user_name", self.use_username_smtp_login.isChecked() "use_user_name", self.use_username_smtp_login.isChecked()
) )
settings.set_mail_attr("printer_mail", self.printermail.text())
settings.set_mail_attr("signature", signature) settings.set_mail_attr("signature", signature)
settings.set_zotero_attr("api_key", self.zotero_api_key.text()) settings.set_zotero_attr("api_key", self.zotero_api_key.text())
settings.set_zotero_attr("library_id", self.zotero_library_id.text()) settings.set_zotero_attr("library_id", self.zotero_library_id.text())

View File

@@ -6,13 +6,13 @@ import sys
import tempfile import tempfile
import webbrowser import webbrowser
from pathlib import Path from pathlib import Path
from typing import Any
from natsort import natsorted from natsort import natsorted
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import QThread from PyQt6.QtCore import QThread
from PyQt6.QtGui import QRegularExpressionValidator from PyQt6.QtGui import QRegularExpressionValidator
from src import Icon, logger from src import Icon, settings
from src.backend import Database, BookGrabber, AvailChecker, DocumentationThread from src.backend import Database, BookGrabber, AvailChecker, DocumentationThread
from src.backend.semester import Semester from src.backend.semester import Semester
from src.backend.create_file import recreateFile from src.backend.create_file import recreateFile
@@ -24,7 +24,7 @@ from src.logic import (
ApparatData, ApparatData,
BookData, BookData,
csv_to_list, csv_to_list,
word_docx_to_csv, word_to_semap,
Prof, Prof,
Apparat, Apparat,
) )
@@ -51,64 +51,79 @@ from src.ui.widgets import (
EditProf, EditProf,
) )
from src.utils import SemesterDocument from src.utils import SemesterDocument
from datetime import datetime
from loguru import logger as log
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)
valid_input = (0, 0, 0, 0, 0, 0) valid_input = (0, 0, 0, 0, 0, 0)
class Ui(Ui_Semesterapparat): class Ui(Ui_Semesterapparat):
# use the Ui_MainWindow class from mainwindow.py # use the Ui_MainWindow class from mainwindow.py
def __init__(self, MainWindow, username: str) -> None: def __init__(self, MainWindow, username: str) -> None: # type:ignore
logger.info("Starting Semesterapparatsmanagement") logger.info("Starting Semesterapparatsmanagement")
super().__init__() super().__init__()
self.active_user = username self.active_user = username
self.setupUi(MainWindow) self.setupUi(MainWindow) # type:ignore
self.MainWindow = MainWindow self.MainWindow = MainWindow # type:ignore
# set the window title # set the window title
MainWindow.setWindowTitle("Semesterapparatsmanagement") MainWindow.setWindowTitle("Semesterapparatsmanagement") # type:ignore
MainWindow.setWindowIcon(Icon("logo").icon) MainWindow.setWindowIcon(Icon("logo").icon) # type:ignore
self.db = Database() self.db = Database()
self.btn_add_document.clicked.connect(self.add_document) self.btn_add_document.clicked.connect(self.add_document) # type:ignore
self.check_file.clicked.connect( self.check_file.clicked.connect( # type:ignore
self.btn_check_file_threaded self.btn_check_file_threaded
) # default: self.add_media_from_file ) # default: self.add_media_from_file
self.create_new_app.clicked.connect(self.btn_create_new_apparat) self.create_new_app.clicked.connect(self.btn_create_new_apparat) # type:ignore
self.btn_apparat_save.clicked.connect(lambda: self.btn_save_apparat(True)) self.btn_apparat_save.clicked.connect(lambda: self.btn_save_apparat(True)) # type:ignore
self.btn_apparat_apply.clicked.connect(self.update_apparat) self.btn_apparat_apply.clicked.connect(self.update_apparat) # type:ignore
self.btn_open_document.clicked.connect(self.open_document) self.btn_open_document.clicked.connect(self.open_document) # type:ignore
self.add_medium.clicked.connect(self.btn_add_medium) self.add_medium.clicked.connect(self.btn_add_medium) # type:ignore
self.btn_copy_adis_command.clicked.connect(self.text_to_clipboard) self.btn_copy_adis_command.clicked.connect(self.text_to_clipboard) # type:ignore
self.btn_reserve.clicked.connect(self.check_availability) self.btn_reserve.clicked.connect(self.check_availability) # type:ignore
self.create_document.clicked.connect(self.create_doc) self.create_document.clicked.connect(self.create_doc) # type:ignore
self.calendarWidget = MessageCalendar(self.calendar_frame) self.calendarWidget = MessageCalendar(self.calendar_frame)
self.calendarWidget.setGridVisible(True) self.calendarWidget.setGridVisible(True)
self.calendarWidget.setVerticalHeaderFormat( self.calendarWidget.setVerticalHeaderFormat(
QtWidgets.QCalendarWidget.VerticalHeaderFormat.NoVerticalHeader QtWidgets.QCalendarWidget.VerticalHeaderFormat.NoVerticalHeader
) )
self.calendarWidget.setObjectName("MessageCalendar") self.calendarWidget.setObjectName("MessageCalendar")
self.calendarWidget.clicked.connect(self.open_reminder) self.calendarWidget.clicked.connect(self.open_reminder) # type:ignore
# assign a context menu to the calendar # assign a context menu to the calendar
self.calendarlayout.addWidget(self.calendarWidget) self.calendarlayout.addWidget(self.calendarWidget)
self.tableWidget_apparat_media.horizontalHeader().setSectionResizeMode( self.tableWidget_apparat_media.horizontalHeader().setSectionResizeMode( # type:ignore
QtWidgets.QHeaderView.ResizeMode.Stretch QtWidgets.QHeaderView.ResizeMode.Stretch
) )
self.tableWidget_apparate.horizontalHeader().setSectionResizeMode( self.tableWidget_apparate.horizontalHeader().setSectionResizeMode( # type:ignore
QtWidgets.QHeaderView.ResizeMode.Stretch QtWidgets.QHeaderView.ResizeMode.Stretch
) )
self.tableWidget_apparate.setSortingEnabled(True) self.tableWidget_apparate.setSortingEnabled(True)
self.saveandcreate.hide() self.saveandcreate.hide()
# Actions # Actions
self.actionEinstellungen.triggered.connect(self.open_settings) self.actionEinstellungen.triggered.connect(self.open_settings) # type:ignore
Icon("settings", self.actionEinstellungen) Icon("settings", self.actionEinstellungen)
self.actionDokumentation_lokal.triggered.connect(self.open_documentation) self.actionDokumentation_lokal.triggered.connect(self.open_documentation) # type:ignore
Icon("offAction", self.actionBeenden) Icon("offAction", self.actionBeenden)
self.actionBeenden.triggered.connect(self.quit) self.actionBeenden.triggered.connect(self.quit) # type:ignore
self.actionAbout.triggered.connect(self.open_about) self.actionAbout.triggered.connect(self.open_about) # type:ignore
# set validators # set validators
self.sem_sommer.clicked.connect(lambda: self.toggleButton(self.sem_winter)) self.sem_sommer.clicked.connect(lambda: self.toggleButton(self.sem_winter)) # type:ignore
self.sem_winter.clicked.connect(lambda: self.toggleButton(self.sem_sommer)) self.sem_winter.clicked.connect(lambda: self.toggleButton(self.sem_sommer)) # type:ignore
self.sem_year.setText(str(QtCore.QDate.currentDate().year())) self.sem_year.setText(str(QtCore.QDate.currentDate().year()))
self.prof_mail.setValidator( self.prof_mail.setValidator(
QRegularExpressionValidator( QRegularExpressionValidator(
@@ -137,7 +152,7 @@ class Ui(Ui_Semesterapparat):
self.tableWidget_apparate.addScrollBarWidget( self.tableWidget_apparate.addScrollBarWidget(
QtWidgets.QScrollBar(), QtCore.Qt.AlignmentFlag.AlignRight QtWidgets.QScrollBar(), QtCore.Qt.AlignmentFlag.AlignRight
) )
self.tableWidget_apparate.doubleClicked.connect(self.load_app_data) self.tableWidget_apparate.doubleClicked.connect(self.load_app_data) # type:ignore
# #print(f"user:{self.active_user}") # #print(f"user:{self.active_user}")
userrole = self.db.getRole(self.active_user) userrole = self.db.getRole(self.active_user)
@@ -150,18 +165,18 @@ class Ui(Ui_Semesterapparat):
self.populate_prof_dropdown() self.populate_prof_dropdown()
self.populate_appfach_dropdown() self.populate_appfach_dropdown()
# if the focus is changed from the prof name dropdown, set the prof data if the prof exists in the database, otherwise show a message # if the focus is changed from the prof name dropdown, set the prof data if the prof exists in the database, otherwise show a message
self.drpdwn_prof_name.currentIndexChanged.connect(self.set_prof_data) self.drpdwn_prof_name.currentIndexChanged.connect(self.set_prof_data) # type:ignore
self.cancel_active_selection.clicked.connect(self.btn_cancel_active_selection) self.cancel_active_selection.clicked.connect(self.btn_cancel_active_selection) # type:ignore
self.check_eternal_app.stateChanged.connect(self.set_state) self.check_eternal_app.stateChanged.connect(self.set_state) # type:ignore
# validate inputs # validate inputs
self.prof_mail.textChanged.connect(self.validate_prof_mail) self.prof_mail.textChanged.connect(self.validate_prof_mail) # type:ignore
self.drpdwn_prof_name.editTextChanged.connect(self.validate_prof_name) self.drpdwn_prof_name.editTextChanged.connect(self.validate_prof_name) # type:ignore
self.prof_tel_nr.textChanged.connect(self.validate_prof_tel) self.prof_tel_nr.textChanged.connect(self.validate_prof_tel) # type:ignore
self.app_name.textChanged.connect(self.validate_app_name) self.app_name.textChanged.connect(self.validate_app_name) # type:ignore
self.app_fach.currentTextChanged.connect(self.validate_app_fach) self.app_fach.currentTextChanged.connect(self.validate_app_fach) # type:ignore
self.sem_year.textChanged.connect(self.validate_semester) self.sem_year.textChanged.connect(self.validate_semester) # type:ignore
self.check_eternal_app.stateChanged.connect(self.validate_semester) self.check_eternal_app.stateChanged.connect(self.validate_semester) # type:ignore
self.chkbx_show_del_media.stateChanged.connect(self.update_app_media_list) self.chkbx_show_del_media.stateChanged.connect(self.update_app_media_list) # type:ignore
self.progress_label.setText("Bitte warten...") self.progress_label.setText("Bitte warten...")
# Set visibility/enabled state of certain entries # Set visibility/enabled state of certain entries
@@ -178,16 +193,16 @@ class Ui(Ui_Semesterapparat):
self.automation_add_selected_books.hide() self.automation_add_selected_books.hide()
# self.btn_del_select_apparats.setEnabled(False) # self.btn_del_select_apparats.setEnabled(False)
self.tabWidget.currentChanged.connect(self.tabW1_changed) self.tabWidget.currentChanged.connect(self.tabW1_changed) # type:ignore
# create a thread, that continually checks the validity of the inputs # create a thread, that continually checks the validity of the inputs
self.validate_thread = QThread() self.validate_thread = QThread()
self.validate_thread.started.connect(self.thread_check) self.validate_thread.started.connect(self.thread_check) # type:ignore
self.validate_thread.start() self.validate_thread.start()
self.add_medium.setEnabled(False) self.add_medium.setEnabled(False)
self.docu = DocumentationThread() self.docu = DocumentationThread()
self.docu.start() self.docu.start()
self.actionDokumentation_lokal.triggered.connect(self.open_documentation) self.actionDokumentation_lokal.triggered.connect(self.open_documentation) # type:ignore
# get all current apparats and cache them in a list # get all current apparats and cache them in a list
self.apparats = self.get_apparats() self.apparats = self.get_apparats()
@@ -203,15 +218,15 @@ class Ui(Ui_Semesterapparat):
self.tableWidget_apparat_media.setContextMenuPolicy( self.tableWidget_apparat_media.setContextMenuPolicy(
QtCore.Qt.ContextMenuPolicy.CustomContextMenu QtCore.Qt.ContextMenuPolicy.CustomContextMenu
) )
self.tableWidget_apparate.customContextMenuRequested.connect( self.tableWidget_apparate.customContextMenuRequested.connect( # type:ignore
self.open_context_menu self.open_context_menu # type:ignore
) )
self.tableWidget_apparat_media.customContextMenuRequested.connect( self.tableWidget_apparat_media.customContextMenuRequested.connect( # type:ignore
self.media_context_menu self.media_context_menu # type:ignore
) )
# admin buttons # admin buttons
self.select_action_box.currentTextChanged.connect(self.adminActions) self.select_action_box.currentTextChanged.connect(self.adminActions) # type:ignore
self.select_action_box.addItem("") self.select_action_box.addItem("")
self.select_action_box.setCurrentText("") self.select_action_box.setCurrentText("")
self.admin_action.setLayout(QtWidgets.QVBoxLayout()) self.admin_action.setLayout(QtWidgets.QVBoxLayout())
@@ -241,7 +256,8 @@ class Ui(Ui_Semesterapparat):
"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", "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?", "document erstellen?",
) )
if result == QtWidgets.QDialog.DialogCode.Accepted: logger.debug(f"Result: {result}")
if result == 1:
# print("Creating document") # print("Creating document")
apparats = self.apparats apparats = self.apparats
apps = [] apps = []
@@ -250,15 +266,19 @@ class Ui(Ui_Semesterapparat):
data = (apparat[4], f"{prof.lastname} ({apparat[1]})") data = (apparat[4], f"{prof.lastname} ({apparat[1]})")
apps.append(data) apps.append(data)
# print(apps) # print(apps)
logger.info("Using apparats: {}", apps)
doc = SemesterDocument( doc = SemesterDocument(
semester=Semester(), semester=Semester().value,
filename="Semesterapparate", filename="Semesterapparate",
apparats=apps, apparats=apps,
full=True,
config=settings,
) )
doc.make_document() # doc.make_document()
doc.create_pdf() # doc.create_pdf()
doc.print_document() # doc.print_document()
doc.cleanup() # doc.cleanup()
# logger.info("Document created and sent to printer")
# kill thread after execution done # kill thread after execution done
@@ -758,7 +778,6 @@ class Ui(Ui_Semesterapparat):
return return
def check_availability(self): def check_availability(self):
def _update_progress(current, all_titles): def _update_progress(current, all_titles):
self.avail_status.setText("{}/{}".format(current, all_titles)) self.avail_status.setText("{}/{}".format(current, all_titles))
@@ -999,11 +1018,11 @@ class Ui(Ui_Semesterapparat):
app_id = self.active_apparat app_id = self.active_apparat
prof_id = self.db.getProfId(self.profdata) prof_id = self.db.getProfId(self.profdata)
def __open_dialog(signatures): def __open_dialog(signatures: list[str]):
dialog = QtWidgets.QDialog() dialog = QtWidgets.QDialog()
frame = parsed_titles_ui() frame = parsed_titles_ui()
frame.setupUi(dialog) frame.setupUi(dialog)
dialog.show() dialogger.show()
frame.signatures = signatures frame.signatures = signatures
frame.populate_table() frame.populate_table()
frame.progressBar.setMaximum(len(signatures)) frame.progressBar.setMaximum(len(signatures))
@@ -1064,12 +1083,8 @@ class Ui(Ui_Semesterapparat):
bookdata=book, app_id=app_id, prof_id=prof_id bookdata=book, app_id=app_id, prof_id=prof_id
) )
if file_type == "docx": if file_type == "docx":
data = word_docx_to_csv(file) data = word_to_semap(file)
signatures = [ signatures = data.signatures
i
for i in data["Standnummer"].values
if i != "\u2002\u2002\u2002\u2002\u2002"
]
data = __open_dialog(signatures) data = __open_dialog(signatures)
# if no data was returned, return # if no data was returned, return
if data == []: if data == []:
@@ -1100,21 +1115,20 @@ class Ui(Ui_Semesterapparat):
# if app_id not in database, create apparat # if app_id not in database, create apparat
created = False created = False
if not self.db.checkApparatExistsById(app_id): if not self.db.checkApparatExistsById(app_id):
logger.info("Apparat does not exist, creating new apparat")
# create apparat # create apparat
# #print("Creating apparat") # #print("Creating apparat")
if not self.btn_save_apparat(False): if not self.btn_save_apparat(False):
return return
created = True created = True
if self.document_list.rowCount() == 0: if self.document_list.rowCount() == 0:
# #print("No file selected") logger.info("No file selected")
self.tableWidget_apparate.setEnabled(True) self.tableWidget_apparate.setEnabled(True)
self.tableWidget_apparate.setToolTip("") self.tableWidget_apparate.setToolTip("")
return return
else: else:
# if file is selected, check for books in the file # if file is selected, check for books in the file
# #print("File selected") # #print("File selected")
file = self.document_list.item(self.document_list.currentRow(), 3).text()
file_type = self.document_list.item( file_type = self.document_list.item(
self.document_list.currentRow(), 1 self.document_list.currentRow(), 1
).text() ).text()
@@ -1124,10 +1138,15 @@ class Ui(Ui_Semesterapparat):
file_name = self.document_list.item( file_name = self.document_list.item(
self.document_list.currentRow(), 0 self.document_list.currentRow(), 0
).text() ).text()
logger.info("File selected: {}, {}", file_name, file_location)
if file_location == "Database": if file_location == "Database":
logger.debug("Using file from database")
file = recreateFile(file_name, app_id, file_type, open=False) file = recreateFile(file_name, app_id, file_type, open=False)
logger.debug("recreated file from database")
else: else:
logger.debug("File not in database")
if not created: if not created:
logger.debug("File was not created, ")
self.add_files(prof_id) self.add_files(prof_id)
if file_type == "pdf": if file_type == "pdf":
# Todo: implement parser here # Todo: implement parser here
@@ -1139,16 +1158,11 @@ class Ui(Ui_Semesterapparat):
signatures = csv_to_list(file) signatures = csv_to_list(file)
# add the data to the database # add the data to the database
if file_type == "docx": if file_type == "docx":
data = word_docx_to_csv(file) data = word_to_semap(file)
signatures = [ logger.info("Converted data from semap file")
i logger.debug("Got the data: {}", data)
for i in data["Standnummer"].values signatures = data.signatures
if i != "\u2002\u2002\u2002\u2002\u2002" logger.info("Got the signatures: {}", signatures)
]
signatures = [i for i in signatures if i != ""]
# logger.debug(signatures)
# #print("starting thread")
if prof_id is None: if prof_id is None:
prof_id = self.db.getProfId(self.profdata) prof_id = self.db.getProfId(self.profdata)
@@ -1171,6 +1185,8 @@ class Ui(Ui_Semesterapparat):
autoGrabber.start() autoGrabber.start()
while autoGrabber.isRunning(): while autoGrabber.isRunning():
QtWidgets.QApplication.processEvents() QtWidgets.QApplication.processEvents()
# refresh book table
self.update_app_media_list()
# end of thread # end of thread
# self.autoGrabber.exit() # self.autoGrabber.exit()
# self.__clear_fields() # self.__clear_fields()
@@ -1276,7 +1292,15 @@ class Ui(Ui_Semesterapparat):
} }
def add_files(self, prof_id=None): def add_files(self, prof_id=None):
files = [] """
Add Files to the associated prof in the database
Parameters
----------
prof_id : int, optional
The ID associated to the prof, by default None
"""
files: list[dict[str, Any]] = []
for i in range(self.document_list.rowCount()): for i in range(self.document_list.rowCount()):
files.append( files.append(
{ {
@@ -1372,7 +1396,7 @@ class Ui(Ui_Semesterapparat):
reminder.exec() reminder.exec()
tableposition = self.tableWidget_apparate.currentRow() tableposition = self.tableWidget_apparate.currentRow()
appnr = self.tableWidget_apparate.item(tableposition, 0).text() appnr = self.tableWidget_apparate.item(tableposition, 0).text()
if reminder.result() == QtWidgets.QDialog.DialogCode.Accepted: if reminder.result() == QtWidgets.QDialogger.DialogCode.Accepted:
data = reminder.return_message() data = reminder.return_message()
# #print(data) # #print(data)
self.db.addMessage( self.db.addMessage(
@@ -1402,14 +1426,16 @@ class Ui(Ui_Semesterapparat):
dialog = CalendarEntry(messages=messages, date=selected_date) dialog = CalendarEntry(messages=messages, date=selected_date)
# append dialog to self.frame_2 # append dialog to self.frame_2
self.calendarlayout.addWidget(dialog) self.calendarlayout.addWidget(dialog)
dialog.repaintSignal.connect(lambda: self.calendarWidget.reload(selected_date)) dialogger.repaintSignal.connect(
lambda: self.calendarWidget.reload(selected_date)
)
def open_settings(self): def open_settings(self):
# print(settings.dict()) # print(settings.dict())
settingsUI = Settings(self.active_user) settingsUI = Settings(self.active_user)
settingsUI.exec() settingsUI.exec()
if settingsUI.result() == QtWidgets.QDialog.DialogCode.Accepted: if settingsUI.result() == QtWidgets.QDialogger.DialogCode.Accepted:
settingsUI.save() settingsUI.save()
# print(settings.dict()) # print(settings.dict())
@@ -1499,7 +1525,7 @@ class Ui(Ui_Semesterapparat):
self.confirm_popup("Keine weiteren Apparate vorhanden", title="Fehler") self.confirm_popup("Keine weiteren Apparate vorhanden", title="Fehler")
return (None, None) return (None, None)
dialog = QtWidgets.QDialog() dialog = QtWidgets.QDialog()
dialog.setWindowTitle(title) dialogger.setWindowTitle(title)
# add a label to the dialog # add a label to the dialog
label = QtWidgets.QLabel() label = QtWidgets.QLabel()
label.setText(message) label.setText(message)
@@ -1518,12 +1544,12 @@ class Ui(Ui_Semesterapparat):
cancel_button = QtWidgets.QPushButton("Abbrechen") cancel_button = QtWidgets.QPushButton("Abbrechen")
layout.addWidget(okay_button) layout.addWidget(okay_button)
layout.addWidget(cancel_button) layout.addWidget(cancel_button)
okay_button.clicked.connect(dialog.accept) okay_button.clicked.connect(dialogger.accept)
cancel_button.clicked.connect(dialog.reject) cancel_button.clicked.connect(dialogger.reject)
dialog.setLayout(layout) dialogger.setLayout(layout)
return dialog.exec(), self.db.getApparatId( return dialogger.exec(), self.db.getApparatId(
self.db.getApparatNameByAppNr(drpdwn.currentText()) self.db.getApparatNameByAppNr(drpdwn.currentText())
) )
@@ -1562,7 +1588,7 @@ class Ui(Ui_Semesterapparat):
widget.setWindowTitle("Metadaten") widget.setWindowTitle("Metadaten")
bookedit.populate_fields(data) bookedit.populate_fields(data)
widget.exec() widget.exec()
if widget.result() == QtWidgets.QDialog.DialogCode.Accepted: if widget.result() == QtWidgets.QDialogger.DialogCode.Accepted:
data = bookedit.get_data() data = bookedit.get_data()
# #print(data) # #print(data)
self.db.updateBookdata(bookdata=data, book_id=book_id) self.db.updateBookdata(bookdata=data, book_id=book_id)
@@ -1621,7 +1647,7 @@ class Ui(Ui_Semesterapparat):
framework = ApparatExtendDialog() framework = ApparatExtendDialog()
framework.exec() framework.exec()
# return data from dialog if ok is pressed # return data from dialog if ok is pressed
if framework.result() == QtWidgets.QDialog.DialogCode.Accepted: if framework.result() == QtWidgets.QDialogger.DialogCode.Accepted:
data = framework.get_data() data = framework.get_data()
# #print(data) # #print(data)
# return data # return data
@@ -1745,10 +1771,10 @@ def launch_gui():
elif ui.lresult == 0: elif ui.lresult == 0:
warning_dialog = QtWidgets.QMessageBox() warning_dialog = QtWidgets.QMessageBox()
warning_dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) warning_dialogger.setIcon(QtWidgets.QMessageBox.Icon.Warning)
warning_dialog.setText("Invalid username or password. Please try again.") warning_dialogger.setText("Invalid username or password. Please try again.")
warning_dialog.setWindowTitle("Login Failed") warning_dialogger.setWindowTitle("Login Failed")
warning_dialog.exec() warning_dialogger.exec()
atexit.register(tempdelete) atexit.register(tempdelete)

View File

@@ -70,7 +70,7 @@ class ElsaDialog(QtWidgets.QDialog, Ui_Dialog):
##Variables ##Variables
self.db = Database() self.db = Database()
self.graph_data = {"x": [Semester().value], "y": [0]} self.graph_data = {"x": [""], "y": [0]}
self.createProf = False self.createProf = False
self.profs = self.getProfs() self.profs = self.getProfs()

View File

@@ -6,22 +6,57 @@ from docx.oxml import OxmlElement
from docx.oxml.ns import qn from docx.oxml.ns import qn
import os import os
from os.path import basename 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: class SemesterDocument:
def __init__(self, apparats: list[tuple[int, str]], semester: str, filename): def __init__(
assert isinstance(apparats, list), "Apparats must be a list of tuples" self,
assert all(isinstance(apparat, tuple) for apparat in apparats), ( apparats: list[tuple[int, str]],
semester: str,
filename,
config,
full: bool = False,
):
assert isinstance(apparats, list), SemesterError(
"Apparats must be a list of tuples" "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" "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" "Apparat names must be strings"
) )
assert isinstance(semester, str), "Semester must be a string" assert isinstance(semester, str), SemesterError("Semester must be a string")
assert "." not in filename and isinstance(filename, str), ( assert "." not in filename and isinstance(filename, str), SemesterError(
"Filename must be a string and not contain an extension" "Filename must be a string and not contain an extension"
) )
self.doc = Document() self.doc = Document()
@@ -35,7 +70,17 @@ class SemesterDocument:
self.color_red = RGBColor(255, 0, 0) self.color_red = RGBColor(255, 0, 0)
self.color_blue = RGBColor(0, 0, 255) self.color_blue = RGBColor(0, 0, 255)
self.filename = filename 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): def set_table_border(self, table):
""" """
Adds a full border to the table. Adds a full border to the table.
@@ -161,13 +206,13 @@ class SemesterDocument:
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication from email.mime.application import MIMEApplication
from email.mime.text import MIMEText from email.mime.text import MIMEText
from src import settings as config
config = self.settings
smtp = config.mail.smtp_server smtp = config.mail.smtp_server
port = config.mail.port port = config.mail.port
sender_email = config.mail.sender sender_email = config.mail.sender
password = config.mail.password password = config.mail.password
receiver = "mobileprint@ph-freiburg.de" receiver = config.mail.printer_mail
message = MIMEMultipart() message = MIMEMultipart()
message["From"] = sender_email message["From"] = sender_email
message["To"] = receiver message["To"] = receiver
@@ -202,7 +247,7 @@ class SemesterDocument:
doc.SaveAs(f"{curdir}/{self.filename}.pdf", FileFormat=17) doc.SaveAs(f"{curdir}/{self.filename}.pdf", FileFormat=17)
doc.Close() doc.Close()
word.Quit() word.Quit()
print("PDF saved") logger.debug("PDF saved")
def cleanup(self): def cleanup(self):
os.remove(f"{self.filename}.docx") os.remove(f"{self.filename}.docx")
@@ -210,14 +255,15 @@ class SemesterDocument:
if __name__ == "__main__": if __name__ == "__main__":
apparat = [(i, f"Item {i}") for i in range(405, 438)] pass
doc = SemesterDocument( # apparat = [(i, f"Item {i}") for i in range(405, 438)]
apparat, # doc = SemesterDocument(
"WiSe 24/25", # apparat,
"semap", # "WiSe 24/25",
) # "semap",
doc.make_document() # )
doc.create_pdf() # doc.make_document()
# doc.create_pdf()
# doc.print_document() # doc.print_document()