UI: update main UI window to allow drops on document list, add support for newedition check & order

This commit is contained in:
2025-09-22 09:45:51 +02:00
parent a2631570ec
commit ebf8363b2a

View File

@@ -35,6 +35,7 @@ from src.logic import (
Prof,
SemapDocument,
csv_to_list,
pdf_to_semap,
word_to_semap,
)
from src.ui import Ui_Semesterapparat
@@ -46,6 +47,7 @@ from src.ui.dialogs import (
LoginDialog,
Mail_Dialog,
MedienAdder,
NewEditionDialog,
ParsedTitles,
ReminderDialog,
Settings,
@@ -69,7 +71,7 @@ from src.ui.widgets import (
log = loguru.logger
log.remove()
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
log.add(f"{LOG_DIR}/application.log", rotation="3 MB", retention="10 days")
log.add(
f"{LOG_DIR}/{datetime.now().strftime('%Y-%m-%d')}.log",
@@ -80,7 +82,7 @@ log.success("UI started")
valid_input = (0, 0, 0, 0, 0, 0)
class Ui(Ui_Semesterapparat):
class Ui(QtWidgets.QMainWindow, Ui_Semesterapparat):
# use the Ui_MainWindow class from mainwindow.py
def __init__(self, MainWindow, username: str) -> None: # type:ignore
log.info("Starting Semesterapparatsmanagement")
@@ -254,7 +256,6 @@ class Ui(Ui_Semesterapparat):
self.autoGrabber = None
self.newEditionChecker = NewEditionCheckerThread()
self.elsatab.setLayout(QtWidgets.QVBoxLayout())
self.search_statistics.setLayout(QtWidgets.QVBoxLayout())
@@ -276,8 +277,54 @@ class Ui(Ui_Semesterapparat):
self.status_progress.hide()
self.valid_check_semester.clicked.connect(self.display_valid_semester) # type:ignore
# allow files to be dragged into document_list
self.document_list.setDragDropMode(
QtWidgets.QAbstractItemView.DragDropMode.DropOnly
)
self.document_list.setAcceptDrops(True)
self.document_list.viewport().setAcceptDrops(True)
self.document_list.installEventFilter(self)
self.document_list.viewport().installEventFilter(self)
def eventFilter(self, obj, event):
# Only handle events for document_list and its viewport
if obj in (self.document_list, self.document_list.viewport()):
et = event.type()
if et == QtCore.QEvent.Type.DragEnter:
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.acceptProposedAction()
else:
event.ignore()
return True
elif et == QtCore.QEvent.Type.DragMove:
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.acceptProposedAction()
else:
event.ignore()
return True
elif et == QtCore.QEvent.Type.Drop:
if not self.app_group_box.isEnabled():
self.confirm_popup(
"Bitte öffnen Sie zuerst einen Apparat!", title="Fehler"
)
return True
if event.mimeData().hasUrls():
for url in event.mimeData().urls():
self.add_document(url.toLocalFile())
event.setDropAction(QtCore.Qt.CopyAction)
event.acceptProposedAction()
else:
event.ignore()
return True
return super().eventFilter(obj, event)
def update_eta(self, eta: str):
self.label_eta.setText(f"Bitte warten... (ETA: {eta})")
eta = int(eta)
# transform seconds eta to HH:MM:SS
transformed_eta = f"{eta // 3600}:{(eta % 3600) // 60}:{eta % 60}"
self.label_eta.setText(f"Bitte warten... (ETA: {transformed_eta})")
def create_doc(self):
log.debug("Creating document")
@@ -320,9 +367,9 @@ class Ui(Ui_Semesterapparat):
elif self.select_action_box.currentText() == "Lehrperson bearbeiten":
self.setWidget(EditProf())
self.admin_action.setTitle("Lehrperson bearbeiten")
elif self.select_action_box.currentText() == "Signaturen aktualisieren":
elif self.select_action_box.currentText() == "Medien bearbeiten":
self.setWidget(UpdateSignatures())
self.admin_action.setTitle("Signaturen aktualisieren")
self.admin_action.setTitle("Medien bearbeiten")
else:
self.hideWidget()
self.admin_action.setTitle("")
@@ -374,7 +421,7 @@ class Ui(Ui_Semesterapparat):
self.statusBar.showMessage("")
def update_calendar(self, data: list[dict[str, Any]]):
self.calendarWidget.setMessages([data])
self.calendarWidget.setMessages(data)
self.calendarWidget.updateCells()
def status_bar_progress(self, current: int, total: int):
@@ -778,13 +825,14 @@ class Ui(Ui_Semesterapparat):
"Bitte mindestens ein Medium hinzufügen!", title="Fehler"
)
app_id = self.active_apparat
app_nr = self.db.getId(self.app_name.text())
prof_id = self.db.getProfId(self.profdata)
log.debug(prof_id)
log.debug(f"{prof_id}, {app_nr}")
# check if app_id is in database
if self.db.checkApparatExistsById(app_id) is False:
if app_nr is None:
# create apparat
self.btn_save_apparat(False)
app_nr = self.db.getId(self.app_name.text())
# create a thread that updates the progress label after each medium
# self.bookGrabber = None
@@ -792,7 +840,7 @@ class Ui(Ui_Semesterapparat):
bookGrabber.add_values(
mode=mode,
prof_id=prof_id,
app_id=app_id,
app_id=app_nr,
data=data,
any_book=use_any,
exact=use_exact,
@@ -894,7 +942,6 @@ class Ui(Ui_Semesterapparat):
deleted = 0 if not self.chkbx_show_del_media.isChecked() else 1
app_id = self.db.getId(self.app_name.text())
prof_id = self.db.getProfId(self.profdata)
print(app_id, prof_id)
books: list[dict[int, BookData, int]] = self.db.getBooks(
app_id, prof_id, deleted
)
@@ -965,10 +1012,6 @@ class Ui(Ui_Semesterapparat):
self.tableWidget_apparat_media.rowCount() - 1, 4
).setToolTip("Das Medium wurde nicht im Apparat gefunden")
# make table link clickable
# self.tableWidget_apparat_media.itemClicked.connect(self.open_link)
# self.tableWidget_apparat_media.
def open_link(self, item):
def __openLink(link):
if link == "":
@@ -1006,8 +1049,9 @@ class Ui(Ui_Semesterapparat):
for prof in profs:
self.drpdwn_prof_name.addItem(prof)
def add_document(self):
def add_document(self, url: Optional[str] = None):
# #log.debug("Add document")
if not url:
picker = FilePicker()
files = picker.pick_files()
for file in files:
@@ -1022,6 +1066,17 @@ class Ui(Ui_Semesterapparat):
# set tooltip of row 3 to the file path for each row
self.document_list.item(0, 3).setToolTip(file)
self.document_list.item(0, 0).setToolTip(filename)
else:
self.document_list.insertRow(0)
filename = url.split("/")[-1]
filetype = filename.split(".")[-1]
self.document_list.setItem(0, 0, QtWidgets.QTableWidgetItem(filename))
self.document_list.setItem(0, 1, QtWidgets.QTableWidgetItem(filetype))
self.document_list.setItem(0, 2, QtWidgets.QTableWidgetItem("*"))
self.document_list.setItem(0, 3, QtWidgets.QTableWidgetItem(url))
# set tooltip of row 3 to the file path for each row
self.document_list.item(0, 3).setToolTip(url)
self.document_list.item(0, 0).setToolTip(filename)
def open_document(self):
_selected_doc_name = ""
@@ -1037,7 +1092,7 @@ class Ui(Ui_Semesterapparat):
self.document_list.currentRow(), 1
).text()
except AttributeError:
self.confirm_popup("Bitte erst ein document auswählen!", title="Fehler")
self.confirm_popup("Bitte erst ein Dokument auswählen!", title="Fehler")
return
if not _selected_doc_location == "Database":
path = Path(_selected_doc_location)
@@ -1056,7 +1111,7 @@ class Ui(Ui_Semesterapparat):
)
def add_media_from_file(self):
app_id = self.active_apparat
app_id = self.db.getId(self.app_name.text())
prof_id = self.db.getProfId(self.profdata)
def __open_dialog(signatures: list[str]):
@@ -1102,17 +1157,24 @@ class Ui(Ui_Semesterapparat):
temp_file = tempfile.NamedTemporaryFile(
delete=False, suffix="." + file_type
)
temp_file.write(
self.db.getBlob(file_name, int(self.active_apparat))
)
temp_file.write(self.db.getBlob(file_name, int(app_id)))
temp_file.close()
file = temp_file.name
if file_type == "pdf":
# Todo: implement parser here
self.confirm_popup(
"PDF Dateien werden noch nicht unterstützt!", title="Fehler"
)
data = pdf_to_semap(file)
signatures = data.signatures
data = __open_dialog(signatures)
# if no data was returned, return
if data == []:
return
for book in data:
if not isinstance(book, BookData):
continue
self.db.addBookToDatabase(
bookdata=book,
app_id=app_id,
prof_id=prof_id,
)
if file_type == "csv":
signatures = csv_to_list(file)
data = __open_dialog(signatures)
@@ -1203,7 +1265,7 @@ class Ui(Ui_Semesterapparat):
dialog.exec()
if dialog.result() == QtWidgets.QDialog.DialogCode.Accepted:
print("Selected title:", dropdown.currentText())
# print("Selected title:", dropdown.currentText())
self.app_name.setText(dropdown.currentText().split(" [")[0].strip())
else:
self.app_name.setText("CHANGEME")
@@ -1251,7 +1313,7 @@ class Ui(Ui_Semesterapparat):
)
self.db.createProf(prof)
# if app_id not in database, create apparat
if not self.db.checkApparatExistsById(app_id):
if not app_id:
log.info("Apparat does not exist, creating new apparat")
# create apparat
# #log.debug("Creating apparat")
@@ -1281,7 +1343,7 @@ class Ui(Ui_Semesterapparat):
autoGrabber = BookGrabber()
autoGrabber.add_values(
mode="ARRAY",
app_id=int(app_id),
app_id=app_id,
prof_id=int(prof_id),
data=signatures,
any_book=True,
@@ -1393,7 +1455,7 @@ class Ui(Ui_Semesterapparat):
@property
def active_apparat(self):
return self.drpdwn_app_nr.currentText()
return self.db.getId(self.app_name.text())
@property
def profdata(self):
@@ -1488,13 +1550,17 @@ class Ui(Ui_Semesterapparat):
delete_action = menu.addAction("Löschen")
remind_action = menu.addAction("Erinnerung")
new_edition_check = menu.addAction("Auf Neuauflagen prüfen")
order_newedition_action = menu.addAction("Neuauflagen bestellen")
menu.addAction(extend_action)
menu.addActions(
[contact_action, delete_action, remind_action, new_edition_check]
[
contact_action,
delete_action,
remind_action,
new_edition_check,
order_newedition_action,
]
)
extend_action.triggered.connect(self.extend_apparat)
remind_action.triggered.connect(self.reminder)
new_edition_check.triggered.connect(self.check_new_editions)
# convert point to row and column
row = self.tableWidget_apparate.rowAt(position.y())
column = self.tableWidget_apparate.columnAt(position.x())
@@ -1504,6 +1570,12 @@ class Ui(Ui_Semesterapparat):
app_id = self.tableWidget_apparate.item(row, 0).text()
pid = self.db.getProfIDByApparat(app_id)
log.debug(app_id, pid)
extend_action.triggered.connect(self.extend_apparat)
remind_action.triggered.connect(self.reminder)
new_edition_check.triggered.connect(lambda: self.check_new_editions())
order_newedition_action.triggered.connect(
lambda: self.order_new_editions(app_id, pid)
)
delete_action.triggered.connect(lambda: self.delete_apparat(pos))
# pass pos to contact_prof
contact_action.triggered.connect(
@@ -1511,6 +1583,19 @@ class Ui(Ui_Semesterapparat):
)
menu.exec(self.tableWidget_apparate.mapToGlobal(position))
def order_new_editions(self, app_nr, prof_id):
log.info("Opening order new editions dialog")
app_id = self.db.getId(self.db.getApparatNameByAppNr(app_nr))
prof_id = prof_id
mail_data = {
"prof_name": "Erwerbung",
"prof_mail": "erw@ph-freiburg.de",
"app_id": app_nr,
"app_name": self.db.getApparatName(app_id, prof_id),
}
orderDialog = NewEditionDialog(app_id, mail_data)
orderDialog.exec()
def update_status(self, curr, total):
self.avail_status.show()
self.label_20.show()
@@ -1547,35 +1632,54 @@ class Ui(Ui_Semesterapparat):
[str(app.appnr) for app in self.db.getApparatsByProf(prof_id)]
)
else:
books = self.db.getBooks(app_id, prof_id, deleted=0)
books = [book["bookdata"] for book in books]
log.info(f"Checking {len(books)} for new editions")
apparats_id = self.db.getId(app_name)
books = self.db.getBooks(apparats_id, prof_id, deleted=0)
searchable_books = []
for book in books:
b = book["bookdata"]
b.in_apparat = True
b.library_location = self.db.query_db(
"""SELECT s.appnr FROM media AS m
JOIN semesterapparat AS s on m.app_id = s.id
WHERE m.id = ?""",
(book["id"],),
one=True,
)[0]
searchable_books.append(b)
self.newEditionChecker.entries = books
log.info(f"Checking {len(searchable_books)} for new editions")
self.newEditionChecker.entries = searchable_books
self.newEditionChecker.finished.connect(self.newEditionChecker.reset)
self.newEditionChecker.finished.connect(self.reset_eta)
self.newEditionChecker.etaSignal.connect(self.update_eta)
self.progressBar.setMaximum(len(books))
self.progressBar.setMaximum(len(searchable_books))
self.newEditionChecker.updateSignal.connect(self.update_status)
self.newEditionChecker.start()
while self.newEditionChecker.isRunning():
QtWidgets.QApplication.processEvents()
self.play_sound("ding.mp3")
results = self.newEditionChecker.results
log.success("Finished checking for new editions, got {} results", len(results))
if results == []:
self.play_sound("error.mp3")
return
self.play_sound("ding.mp3")
log.info(f"Found {len(results)} possible new editions - opening dialog")
newEditionChecker = NewEditionChecker(results=results)
newEditionChecker.exec()
accepted_books = newEditionChecker.accepted_books
# print(accepted_books)
print(accepted_books)
if accepted_books == []:
return
for book in accepted_books:
oldBookId = self.db.getBookIdByPPN(book.old_book.ppn)
self.db.insertNewEdition(book, oldBookId, apparats_id)
pass
self.mail_thread = Mail_Dialog(
prof_name=self.db.getSpecificProfData(prof_id, ["fullname"]),
@@ -1713,7 +1817,7 @@ class Ui(Ui_Semesterapparat):
# if result:
# book.signature = result.signature
# book.in_apparat = True
# print(book)
# #print(book)
# self.db.updateBookdata(book_id, book)
# self.update_app_media_list()
@@ -1761,7 +1865,7 @@ class Ui(Ui_Semesterapparat):
def confirm_action_dialog(self, message, title="Bestätigung"):
appnrs = self.db.getUnavailableApparatNumbers()
appnrs = [str(i) for i in appnrs]
appnrs.remove(self.active_apparat)
appnrs.remove(self.drpdwn_app_nr.currentText())
if len(appnrs) == 0:
# create a warning dialog, saying no apparats present
self.confirm_popup("Keine weiteren Apparate vorhanden", title="Fehler")
@@ -1842,9 +1946,7 @@ class Ui(Ui_Semesterapparat):
pass
def delete_medium(self):
selected_apparat_id = self.tableWidget_apparate.item(
self.tableWidget_apparate.currentRow(), 0
).text()
selected_apparat_id = self.active_apparat
prof_id = self.db.getProfId(self.profdata)
# check how many rows are selected
selected_rows = self.tableWidget_apparat_media.selectionModel().selectedRows()