From ebf8363b2a53ae0da4c679edf890f42b437bb203 Mon Sep 17 00:00:00 2001 From: WorldTeacher Date: Mon, 22 Sep 2025 09:45:51 +0200 Subject: [PATCH] UI: update main UI window to allow drops on document list, add support for newedition check & order --- src/ui/userInterface.py | 214 +++++++++++++++++++++++++++++----------- 1 file changed, 158 insertions(+), 56 deletions(-) diff --git a/src/ui/userInterface.py b/src/ui/userInterface.py index 3347e6b..47488c9 100644 --- a/src/ui/userInterface.py +++ b/src/ui/userInterface.py @@ -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") @@ -253,7 +255,6 @@ class Ui(Ui_Semesterapparat): self.mail_thread = None 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,21 +1049,33 @@ 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") - picker = FilePicker() - files = picker.pick_files() - for file in files: - # #log.debug(file) - filename = file.split("/")[-1] - filetype = filename.split(".")[-1] + if not url: + picker = FilePicker() + files = picker.pick_files() + for file in files: + # #log.debug(file) + filename = file.split("/")[-1] + filetype = filename.split(".")[-1] + self.document_list.insertRow(0) + 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(file)) + # 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(file)) + 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(file) + self.document_list.item(0, 3).setToolTip(url) self.document_list.item(0, 0).setToolTip(filename) def open_document(self): @@ -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" - ) - return + 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()