# encoding: utf-8 import atexit import os import sys import tempfile import time import webbrowser from datetime import datetime from pathlib import Path from typing import Any, Union import loguru from natsort import natsorted from PyQt6 import QtCore, QtGui, QtWidgets from PyQt6.QtCore import QThread from PyQt6.QtGui import QRegularExpressionValidator from src import LOG_DIR, Icon from src.backend import AvailChecker, BookGrabber, Database, DocumentationThread from src.backend.create_file import recreateFile from src.backend.delete_temp_contents import delete_temp_contents as tempdelete from src.backend.semester import Semester from src.logic import ( APP_NRS, Apparat, # PROF_TITLES, ApparatData, BookData, Prof, SemapDocument, csv_to_list, word_to_semap, ) from src.ui import Ui_Semesterapparat from src.ui.dialogs import ( About, ApparatExtendDialog, BookDataUI, DocumentPrintDialog, LoginDialog, Mail_Dialog, MedienAdder, ParsedTitles, ReminderDialog, Settings, popus_confirm, ) from src.ui.widgets import ( CalendarEntry, EditProf, EditUser, ElsaDialog, FilePicker, MessageCalendar, SearchStatisticPage, UserCreate, ) 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}/{datetime.now().strftime('%Y-%m-%d')}.log", rotation="1 day", retention="1 month", ) valid_input = (0, 0, 0, 0, 0, 0) class Ui(Ui_Semesterapparat): # use the Ui_MainWindow class from mainwindow.py def __init__(self, MainWindow, username: str) -> None: # type:ignore log.info("Starting Semesterapparatsmanagement") super().__init__() self.active_user = username self.setupUi(MainWindow) # type:ignore self.MainWindow = MainWindow # type:ignore # set the window title MainWindow.setWindowTitle("Semesterapparatsmanagement") # type:ignore MainWindow.setWindowIcon(Icon("logo").icon) # type:ignore self.db = Database() self.btn_add_document.clicked.connect(self.add_document) # type:ignore self.check_file.clicked.connect( # type:ignore self.btn_check_file_threaded ) # default: self.add_media_from_file self.btn_extract_data_from_document.clicked.connect( # type:ignore self.import_data_from_document ) 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)) # type:ignore self.btn_apparat_apply.clicked.connect(self.update_apparat) # type:ignore self.btn_open_document.clicked.connect(self.open_document) # type:ignore self.add_medium.clicked.connect(self.btn_add_medium) # type:ignore self.btn_copy_adis_command.clicked.connect(self.text_to_clipboard) # type:ignore self.btn_reserve.clicked.connect(self.check_availability) # type:ignore self.create_document.clicked.connect(self.create_doc) # type:ignore self.calendarWidget = MessageCalendar(self.calendar_frame) self.calendarWidget.setGridVisible(True) self.calendarWidget.setVerticalHeaderFormat( QtWidgets.QCalendarWidget.VerticalHeaderFormat.NoVerticalHeader ) self.calendarWidget.setObjectName("MessageCalendar") self.calendarWidget.clicked.connect(self.open_reminder) # type:ignore # assign a context menu to the calendar self.calendarlayout.addWidget(self.calendarWidget) self.tableWidget_apparat_media.horizontalHeader().setSectionResizeMode( # type:ignore QtWidgets.QHeaderView.ResizeMode.Stretch ) self.tableWidget_apparate.horizontalHeader().setSectionResizeMode( # type:ignore QtWidgets.QHeaderView.ResizeMode.Stretch ) self.saveandcreate.hide() # Actions self.actionEinstellungen.triggered.connect(self.open_settings) # type:ignore Icon("settings", self.actionEinstellungen) self.actionDokumentation_lokal.triggered.connect(self.open_documentation) # type:ignore Icon("offAction", self.actionBeenden) self.actionBeenden.triggered.connect(self.quit) # type:ignore self.actionAbout.triggered.connect(self.open_about) # type:ignore # set validators self.sem_sommer.clicked.connect(lambda: self.toggleButton(self.sem_winter)) # type:ignore self.sem_winter.clicked.connect(lambda: self.toggleButton(self.sem_sommer)) # type:ignore self.sem_year.setText(str(QtCore.QDate.currentDate().year())) self.prof_mail.setValidator( QRegularExpressionValidator( QtCore.QRegularExpression( r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}" ) ) ) self.prof_tel_nr.setValidator(QtGui.QIntValidator()) self.prof_tel_nr.setValidator( QtGui.QRegularExpressionValidator(QtCore.QRegularExpression(r"^\d{3,14}")) ) # #log.debug(self.prof_tel_nr.maxLength()) self.app_fach.setValidator( # validator to allow typing in the app_fach field QtGui.QRegularExpressionValidator( QtCore.QRegularExpression(r"[a-zA-Z0-9\s\W]+") ) ) # allow only letters, numbers, whitespaces, symbols for the apparat name self.app_name.setValidator( QtGui.QRegularExpressionValidator( QtCore.QRegularExpression(r"[a-zA-Z0-9\s\W]+") ) ) self.tableWidget_apparate.addScrollBarWidget( QtWidgets.QScrollBar(), QtCore.Qt.AlignmentFlag.AlignRight ) self.tableWidget_apparate.doubleClicked.connect(self.load_app_data) # type:ignore # #log.debug(f"user:{self.active_user}") userrole = self.db.getRole(self.active_user) # hide admin interface when non-admin is logged in if userrole == "admin": self.tabWidget.setTabVisible(3, True) else: self.tabWidget.setTabVisible(3, False) # self.update_app_media_list() self.populate_prof_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 self.drpdwn_prof_name.currentIndexChanged.connect(self.set_prof_data) # type:ignore self.cancel_active_selection.clicked.connect(self.btn_cancel_active_selection) # type:ignore self.check_eternal_app.stateChanged.connect(self.set_state) # type:ignore # validate inputs self.prof_mail.textChanged.connect(self.validate_prof_mail) # type:ignore self.drpdwn_prof_name.editTextChanged.connect(self.validate_prof_name) # type:ignore self.prof_tel_nr.textChanged.connect(self.validate_prof_tel) # type:ignore self.app_name.textChanged.connect(self.validate_app_name) # type:ignore self.app_fach.currentTextChanged.connect(self.validate_app_fach) # type:ignore self.sem_year.textChanged.connect(self.validate_semester) # type:ignore self.check_eternal_app.stateChanged.connect(self.validate_semester) # type:ignore self.chkbx_show_del_media.stateChanged.connect(self.update_app_media_list) # type:ignore self.progress_label.setText("Bitte warten...") # Set visibility/enabled state of certain entries self.chkbx_show_del_media.setEnabled(False) self.label_info.hide() self.app_group_box.setEnabled(False) self.line_2.hide() self.progress_label.hide() self.btn_reserve.hide() self.label_20.hide() self.line_3.hide() self.avail_status.hide() self.chkbx_show_del_media.hide() self.automation_add_selected_books.hide() # self.btn_del_select_apparats.setEnabled(False) self.tabWidget.currentChanged.connect(self.tabW1_changed) # type:ignore # create a thread, that continually checks the validity of the inputs self.validate_thread = QThread() self.validate_thread.started.connect(self.thread_check) # type:ignore self.validate_thread.start() self.add_medium.setEnabled(False) self.docu = DocumentationThread() self.actionDokumentation_lokal.triggered.connect(self.open_documentation) # type:ignore # get all current apparats and cache them in a list self.apparats = self.get_apparats() self.old_apparats = self.apparats # create a clone to compare against later # if length of apparats changes, update box_apparats # if tab is changed, gather data needed # Context Menus self.tableWidget_apparate.setContextMenuPolicy( QtCore.Qt.ContextMenuPolicy.CustomContextMenu ) self.tableWidget_apparat_media.setContextMenuPolicy( QtCore.Qt.ContextMenuPolicy.CustomContextMenu ) self.tableWidget_apparate.customContextMenuRequested.connect( # type:ignore self.open_context_menu # type:ignore ) self.tableWidget_apparat_media.customContextMenuRequested.connect( # type:ignore self.media_context_menu # type:ignore ) # admin buttons self.select_action_box.currentTextChanged.connect(self.adminActions) # type:ignore self.select_action_box.addItem("") self.select_action_box.setCurrentText("") self.admin_action.setLayout(QtWidgets.QVBoxLayout()) self.admin_action.setTitle("") # Create instances to be used by the threads in the application self.bookGrabber: list[QThread] = [] self.availChecker = None self.mail_thread = None self.autoGrabber = None self.elsatab.setLayout(QtWidgets.QVBoxLayout()) self.search_statistics.setLayout(QtWidgets.QVBoxLayout()) # add splitter self.splitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Vertical) self.splitter.addWidget(self.calendar_frame) self.splitter.addWidget(self.frame_creation_progress) self.verticalLayout.removeWidget(self.calendar_frame) self.verticalLayout.removeWidget(self.frame_creation_progress) self.verticalLayout.addWidget(self.splitter) self.steps.hide() self.valid_check_semester.clicked.connect(self.display_valid_semester) # type:ignore def create_doc(self): log.debug("Creating document") # open DocumentPrintDialog dialog = DocumentPrintDialog(self.MainWindow) # type: ignore dialog.show() def checkValidInput(self): if valid_input == (1, 1, 1, 1, 1, 1): self.check_file.setEnabled(True) else: self.check_file.setEnabled(False) def setWidget(self, widget: QtWidgets.QWidget): # remove all widgets from localwidget self.hideWidget() # add widget to localwidget self.admin_action.layout().addWidget(widget) # type: ignore def hideWidget(self): try: widgets = [ self.admin_action.layout().itemAt(i).widget() # type: ignore for i in range(self.admin_action.layout().count()) # type: ignore ] except AttributeError: return for widget in widgets: self.admin_action.layout().removeWidget(widget) # type: ignore widget.deleteLater() # type: ignore def adminActions(self): if self.select_action_box.currentText() == "Nutzer anlegen": self.setWidget(UserCreate()) self.admin_action.setTitle("Nutzer anlegen") elif self.select_action_box.currentText() == "Nutzer bearbeiten": self.setWidget(EditUser()) self.admin_action.setTitle("Nutzer bearbeiten") elif self.select_action_box.currentText() == "Lehrperson bearbeiten": self.setWidget(EditProf()) self.admin_action.setTitle("Lehrperson bearbeiten") else: self.hideWidget() self.admin_action.setTitle("") def toggleButton(self, button: QtWidgets.QCheckBox): if button.isChecked(): button.setChecked(False) self.validate_semester() def open_about(self): About().exec() def quit(self): # delete all temporary files tempdelete() sys.exit() def get_apparats(self): alist = self.db.getAllAparats(deleted=0) alist = natsorted(alist, key=lambda x: x.appnr, reverse=True) self.tableWidget_apparate.setRowCount(0) for apparat in alist: self.insert_apparat_into_table(apparat) return alist def populate_appfach_dropdown(self): self.app_fach.clear() self.app_fach.addItem("") self.app_fach.setCurrentText("") self.app_fach.addItems([subject[1] for subject in self.db.getSubjects()]) # type: ignore def open_documentation(self): log.info("Opening Documentation") if not self.docu.isRunning(): self.docu.start() time.sleep(5) webbrowser.open("http://localhost:8000") def update_calendar(self, data: list[dict[str, Any]]): self.calendarWidget.setMessages([data]) self.calendarWidget.updateCells() def tabW1_changed(self): if self.tabWidget.currentIndex() == 1: # Statistics stats_layout = self.search_statistics.layout() if stats_layout is not None: # delete tabpage, re-add with same name at same position for widget in [ stats_layout.itemAt(i).widget() for i in range(stats_layout.count()) ]: stats_layout.removeWidget(widget) widget.deleteLater() statistics = SearchStatisticPage() statistics.apparat_open.connect(self.open_apparat) statistics.refreshSignal.connect(self.update_apparat_list) statistics.updateCalendar.connect(self.update_calendar) stats_layout.addWidget(statistics) # #log.debug("searchpage") if self.tabWidget.currentIndex() == 0: # Apparate # clear all entries from the table self.tableWidget_apparate.setRowCount(0) self.get_apparats() if self.tabWidget.currentIndex() == 2: # Elsa elsa_layout = self.elsatab.layout() if elsa_layout is not None: # delete tabpage, re-add with same name at same position widgets = [ self.elsatab.layout().itemAt(i).widget() for i in range(self.elsatab.layout().count()) ] for widget in widgets: self.elsatab.layout().removeWidget(widget) widget.deleteLater() elsa_layout.addWidget(ElsaDialog()) # log.debug("added") pass def generateSemester(self, today=False): """Generates the current semester. Args: today (bool, optional): If True, the current semester is generated. Defaults to False. Returns: str: The current semester """ if today: return Semester() currentYear = self.sem_year.text() currentYear = int(currentYear[-2:]) semester = "SoSe" if self.sem_sommer.isChecked() else "WiSe" if semester == "SoSe": return "SoSe " + str(currentYear) else: return f"WiSe {currentYear}/{currentYear + 1}" def open_apparat(self, apparat): if self.load_app_data(apparat): # change tab focus to tab 0 self.tabWidget.setCurrentIndex(0) def populate_dropdown(self, box, data): box.clear() box.addItem("") box.setCurrentText("") box.addItems(data) def populate_frame(self, appdata: ApparatData): # populate the frame with the data from the database self.drpdwn_app_nr.setCurrentText(str(appdata.apparat.appnr)) self.prof_title.setText(appdata.prof.title) prof_name = appdata.prof.name(True) self.drpdwn_prof_name.setCurrentText(prof_name) self.prof_mail.setText(appdata.prof.mail) self.prof_tel_nr.setText(appdata.prof.telnr) self.app_name.setText(appdata.apparat.name) # #log.debug("changing dropdown app_fach from '' to ", appdata.app_fach) self.app_fach.setCurrentText(appdata.apparat.subject) # #log.debug("changed dropdown app_fach to ", self.app_fach.currentText()) self.sem_year.setText(appdata.apparat.get_semester.split(" ")[1]) match appdata.apparat.get_semester.split(" ")[0]: case "SoSe": self.sem_sommer.setChecked(True) self.sem_winter.setChecked(False) case "WiSe": self.sem_sommer.setChecked(False) self.sem_winter.setChecked(True) self.check_eternal_app.setChecked(appdata.apparat.eternal) self.prof_id_adis.setText(str(appdata.apparat.prof_id_adis)) self.apparat_id_adis.setText(str(appdata.apparat.apparat_id_adis)) self.app_group_box.setEnabled(True) self.validateLoadedData() def validateLoadedData(self): self.validate_prof_mail() self.validate_prof_name() self.validate_prof_tel() self.validate_app_name() self.validate_app_fach() self.validate_semester() def update_apparat(self): prof = Prof() app = Apparat() app.subject = self.app_fach.currentText() app.name = self.app_name.text() app.appnr = self.active_apparat app.eternal = self.check_eternal_app.isChecked() prof.mail = self.prof_mail.text() prof.telnr = self.prof_tel_nr.text() prof.title = self.prof_title.text() prof.fullname = self.drpdwn_prof_name.currentText().replace(",", "") app.created_semester = ( self.sem_sommer.text() + " " + self.sem_year.text() if self.sem_sommer.isChecked() else self.sem_winter.text() + " " + self.sem_year.text() ) app.prof_id_adis = self.prof_id_adis.text() self.add_files() app.apparat_id_adis = self.apparat_id_adis.text() appdata = ApparatData(prof=prof, apparat=app) self.db.updateApparat(appdata) self.update_app_media_list() self.cancel_active_selection.click() self.check_send_mail.show() self.chkbx_show_del_media.show() self.cancel_active_selection.setEnabled(False) self.add_medium.setEnabled(False) # update apparat table self.get_apparats() def confirm_popup(self, message: str, title: str): popup = popus_confirm(title=title) popup.textEdit.setReadOnly(True) popup.textEdit.setText(message) popup.exec() return popup.result() def thread_check(self): # #log.debug("Thread started") self.prof_mail.textChanged.connect(self.validate_prof_mail) self.drpdwn_prof_name.editTextChanged.connect(self.validate_prof_name) self.prof_tel_nr.textChanged.connect(self.validate_prof_tel) self.app_name.textChanged.connect(self.validate_app_name) self.app_fach.currentTextChanged.connect(self.validate_app_fach) self.sem_year.textChanged.connect(self.validate_semester) self.check_eternal_app.stateChanged.connect(self.validate_semester) # else: # pass # # self.drpdwn_prof_name.setStyleSheet("border: 1px solid black;") # Validators def __setValidState(self, widget, state, mand, index): if state: Icon("valid_true", widget, True, color="success") mand.setText("") self.change_state(index, 1) else: Icon("valid_false", widget, recolor=True, color="warning") mand.setText("*") self.change_state(index, 0) def validate_prof_name(self): if ( self.app_group_box.isEnabled() and "," in self.drpdwn_prof_name.currentText() ): self.__setValidState(self.valid_check_profname, 1, self.profname_mand, 0) else: self.__setValidState(self.valid_check_profname, 0, self.profname_mand, 0) def validate_prof_mail(self): if self.app_group_box.isEnabled(): if self.prof_mail.hasAcceptableInput(): self.__setValidState(self.valid_check_mail, 1, self.mail_mand, 1) else: self.__setValidState(self.valid_check_mail, 0, self.mail_mand, 1) else: self.__setValidState(self.valid_check_mail, 0, self.mail_mand, 1) def validate_prof_tel(self): if self.app_group_box.isEnabled(): if self.prof_tel_nr.text() != "" and self.prof_tel_nr.hasAcceptableInput(): self.__setValidState(self.valid_check_telnr, 1, self.telnr_mand, 2) else: self.__setValidState(self.valid_check_telnr, 0, self.telnr_mand, 2) else: self.__setValidState(self.valid_check_telnr, 0, self.telnr_mand, 2) def validate_app_name(self): if self.app_group_box.isEnabled() and self.app_name.hasAcceptableInput(): self.__setValidState(self.valid_check_appname, 1, self.appname_mand, 3) else: self.__setValidState(self.valid_check_appname, 0, self.appname_mand, 3) def validate_app_fach(self): if self.app_group_box.isEnabled() and self.app_fach.currentText() != "": self.__setValidState(self.valid_check_app_fach, 1, self.fach_mand, 4) else: self.__setValidState(self.valid_check_app_fach, 0, self.fach_mand, 4) def validate_semester(self): valid = (self.sem_sommer.isChecked() or self.sem_winter.isChecked()) and len( self.sem_year.text() ) >= 2 if valid or self.check_eternal_app.isChecked(): self.__setValidState(self.valid_check_semester, 1, self._mand, 5) self.check_eternal_app.setEnabled(True) else: self.__setValidState(self.valid_check_semester, 0, self._mand, 5) self.check_eternal_app.setEnabled(False) return valid def display_valid_semester(self): print(f""" Semester: {self.sem_year.text()} Sommer: {self.sem_sommer.isChecked()} Winter: {self.sem_winter.isChecked()} Eternal: {self.check_eternal_app.isChecked()} Valid: {self.validate_semester()} """) def change_state(self, index, state): global valid_input valid_input = list(valid_input) valid_input[index] = state valid_input = tuple(valid_input) self.checkValidInput() def set_state(self): # set state of semester and year if self.check_eternal_app.isChecked(): self.sem_winter.setEnabled(False) self.sem_sommer.setEnabled(False) self.sem_year.setEnabled(False) else: self.sem_winter.setEnabled(True) self.sem_sommer.setEnabled(True) self.sem_year.setEnabled(True) def validate_fields(self): return all(valid_input) def set_prof_data(self): self.prof_title.clear() if "," not in self.drpdwn_prof_name.currentText(): self.prof_mail.clear() self.prof_tel_nr.clear() return selected_prof = self.drpdwn_prof_name.currentText() data = self.db.getProfData(selected_prof) # log.debug(data) prof_title = data.title if prof_title == "None": prof_title = "Kein Titel" self.prof_title.setText(prof_title) self.prof_tel_nr.setText(data.telnr) self.prof_mail.setText(data.mail) self.app_name.setFocus() def get_index_of_value(self, table_widget, value): for i in range(table_widget.rowCount()): for j in range(table_widget.columnCount()): if ( table_widget.item(i, j) is not None and table_widget.item(i, j).text() == value ): return i, j return (None, None) def load_app_data(self, app_id=None): self.cancel_active_selection.setEnabled(True) self.add_medium.setEnabled(True) for child in self.app_group_box.findChildren(QtWidgets.QToolButton): child.show() if isinstance(app_id, str): # double click the tableWidget_apparate row with the given app_id row, column = self.get_index_of_value(self.tableWidget_apparate, app_id) if row is None and column is None: return False # set the current index to the row self.tableWidget_apparate.setCurrentCell(row, 0) self.check_send_mail.hide() app_pos = self.tableWidget_apparate.currentIndex() appnr = self.tableWidget_apparate.item(app_pos.row(), 0).text() appname = self.tableWidget_apparate.item(app_pos.row(), 1).text() self.sem_sommer.setEnabled(False) self.sem_winter.setEnabled(False) self.sem_year.setEnabled(False) self.document_list.setRowCount(0) self.chkbx_show_del_media.setEnabled(True) appdata = self.db.getApparatData(appnr, appname) self.populate_frame(appdata) self.btn_apparat_save.hide() self.btn_reserve.show() self.chkbx_show_del_media.show() self.drpdwn_app_nr.setDisabled(True) self.update_app_media_list() self.update_document_list() return True def update_document_list(self): app_id = self.active_apparat prof_id = self.db.getProfByName( self.drpdwn_prof_name.currentText().replace(",", "") ).id files = self.db.getFiles(app_id, prof_id) for file in files: self.document_list.insertRow(0) self.document_list.setItem(0, 0, QtWidgets.QTableWidgetItem(file[0])) self.document_list.setItem(0, 1, QtWidgets.QTableWidgetItem(file[1])) self.document_list.setItem(0, 2, QtWidgets.QTableWidgetItem("")) self.document_list.setItem(0, 3, QtWidgets.QTableWidgetItem("Database")) self.document_list.item(0, 0).setToolTip(file[0]) def btn_create_new_apparat(self): global valid_input self.steps.show() for child in self.app_group_box.findChildren(QtWidgets.QToolButton): child.show() # *create a new apparat self.btn_apparat_save.show() if self.btn_apparat_save.isHidden() else None # clear document_list self.__clear_fields() self.document_list.setRowCount(0) self.cancel_active_selection.setEnabled(True) self.app_group_box.setEnabled(True) self.add_medium.setEnabled(True) self.sem_year.setEnabled(True) self.sem_sommer.setEnabled(True) self.sem_winter.setEnabled(True) self.chkbx_show_del_media.setEnabled(True) self.drpdwn_app_nr.setEnabled(True) self.app_fach.setEnabled(True) self.check_send_mail.show() self.check_file.setEnabled(False) self.drpdwn_app_nr.setFocus() if self.tableWidget_apparat_media.rowCount() > 0: self.tableWidget_apparat_media.setRowCount(0) # clear all fields for item in self.app_group_box.findChildren(QtWidgets.QLineEdit): item.clear() self.drpdwn_app_nr.clear() self.prof_title.clear() self.drpdwn_prof_name.clear() # set drop down menu for apparat numbers to only available numbers taken_app_nrs = self.db.getUnavailableApparatNumbers() self.drpdwn_app_nr.addItems([str(i) for i in APP_NRS if i not in taken_app_nrs]) # type:ignore valid_input = (0, 0, 0, 0, 0, 0) self.populate_prof_dropdown() def update_progress_label(self, curr: int, total: int): text = f"Medium {curr}/{total}" log.info(text) self.progress_label.setText(text) # update tableWidget_apparat_media self.update_app_media_list() def hide_progress_label(self): log.info("Finished adding media, hiding progress label") self.progress_label.hide() self.progress_label.setText("Bitte warten...") self.line_2.hide() self.label_info.hide() def btn_add_medium(self): media = MedienAdder() media.exec() mode = media.mode data = media.data use_any = media.use_any use_exact = media.use_exact result = media.result() if result == 1: self.progress_label.show() self.line_2.show() self.label_info.show() self.progress_label.setText("Bitte warten...") if data == []: self.confirm_popup( "Bitte mindestens ein Medium hinzufügen!", title="Fehler" ) app_id = self.active_apparat prof_id = self.db.getProfId(self.profdata) log.debug(prof_id) # check if app_id is in database if self.db.checkApparatExistsById(app_id) is False: # create apparat self.btn_save_apparat(False) # create a thread that updates the progress label after each medium # self.bookGrabber = None bookGrabber = BookGrabber() bookGrabber.add_values( mode=mode, prof_id=prof_id, app_id=app_id, data=data, any_book=use_any, exact=use_exact, ) bookGrabber.finished.connect(self.hide_progress_label) bookGrabber.finished.connect(self.update_app_media_list) bookGrabber.updateSignal.connect(self.update_progress_label) bookGrabber.start() while bookGrabber.isRunning(): # #log.debug("waiting for thread to finish") QtWidgets.QApplication.processEvents() # self.__clear_fields() else: return def check_availability(self): def _update_progress(current, all_titles): self.avail_status.setText("{}/{}".format(current, all_titles)) def _hide_progress_label(): self.label_20.hide() self.avail_status.hide() self.avail_status.setText("0/0") # get all links from the table # if no index in tableWidget_apparat_media is selected, check all if self.tableWidget_apparat_media.currentRow() == -1: links = [ self.tableWidget_apparat_media.item(i, 1).text() for i in range(self.tableWidget_apparat_media.rowCount()) if self.tableWidget_apparat_media.item(i, 4).text() == "❌" or self.tableWidget_apparat_media.item(i, 4).text() == "" ] else: links = [ self.tableWidget_apparat_media.item( self.tableWidget_apparat_media.currentRow(), 1 ).text() ] # get the number of selected rows from the table items = self.tableWidget_apparat_media.rowCount() self.label_20.setText("Verfügbarkeit wird geprüft, bitte warten...") self.label_20.show() self.avail_status.setText(f"0/{items}") self.avail_status.show() books = self.db.getBooks( self.active_apparat, self.db.getProfId(self.profdata), deleted=0, ) # thread = QThread() appnumber = self.active_apparat # #log.debug(links) self.availChecker = AvailChecker(links, appnumber, books=books) # availcheck.moveToThread(thread) # availcheck.finished.connect(thread.quit) self.availChecker.finished.connect(self.availChecker.deleteLater) self.availChecker.finished.connect(self.update_app_media_list) self.availChecker.updateProgress.connect(_update_progress) self.availChecker.finished.connect(_hide_progress_label) self.availChecker.start() # kill availcheck after completion # self.grabbers.append(availcheck) def btn_cancel_active_selection(self): self.steps.hide() # clear the rows of the table self.tableWidget_apparat_media.setRowCount(0) self.document_list.setRowCount(0) self.app_group_box.setEnabled(False) self.app_fach.setCurrentText("") self.chkbx_show_del_media.hide() self.check_send_mail.hide() self.btn_reserve.hide() self.check_eternal_app.setEnabled(False) # set all radio buttons to unchecked self.sem_sommer.setChecked(False) self.sem_winter.setChecked(False) self.check_eternal_app.setChecked(False) self.add_medium.setEnabled(False) for child in self.app_group_box.findChildren(QtWidgets.QLineEdit): child.clear() for child in self.app_group_box.findChildren(QtWidgets.QToolButton): child.hide() self.validate_app_fach() self.validate_app_name() self.validate_prof_mail() self.validate_prof_name() self.validate_prof_tel() self.validate_semester() def update_app_media_list(self): deleted = 0 if not self.chkbx_show_del_media.isChecked() else 1 app_id = self.active_apparat prof_id = self.db.getProfId(self.profdata) books: list[dict[int, BookData, int]] = self.db.getBooks( app_id, prof_id, deleted ) # # #log.debug(books) # take the dataclass from the tuple # booklist:list[BookData]=[book[0] for book in books] self.tableWidget_apparat_media.setRowCount(0) for book in books: book["id"] book_data = book["bookdata"] availability = book["available"] # bd = BookData().from_string(book) # # #log.debug(bd, type(bd)) # create a new row below the last one self.tableWidget_apparat_media.insertRow( self.tableWidget_apparat_media.rowCount() ) # #set the data self.tableWidget_apparat_media.setItem( self.tableWidget_apparat_media.rowCount() - 1, 0, QtWidgets.QTableWidgetItem(book_data.title), ) self.tableWidget_apparat_media.setItem( self.tableWidget_apparat_media.rowCount() - 1, 1, QtWidgets.QTableWidgetItem(book_data.signature), ) self.tableWidget_apparat_media.setItem( self.tableWidget_apparat_media.rowCount() - 1, 2, QtWidgets.QTableWidgetItem(book_data.edition), ) self.tableWidget_apparat_media.setItem( self.tableWidget_apparat_media.rowCount() - 1, 3, QtWidgets.QTableWidgetItem(book_data.author), ) self.tableWidget_apparat_media.setItem( self.tableWidget_apparat_media.rowCount() - 1, 6, QtWidgets.QTableWidgetItem(book_data.link), ) if availability == 1: # display green checkmark at column 4 in the row self.tableWidget_apparat_media.setItem( self.tableWidget_apparat_media.rowCount() - 1, 4, QtWidgets.QTableWidgetItem("✅"), ) # set tooltip self.tableWidget_apparat_media.item( self.tableWidget_apparat_media.rowCount() - 1, 4 ).setToolTip("Das Medium wurde im Apparat gefunden") else: self.tableWidget_apparat_media.setItem( self.tableWidget_apparat_media.rowCount() - 1, 4, QtWidgets.QTableWidgetItem("❌"), ) self.tableWidget_apparat_media.item( 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 == "": return if "http" not in link: link = "https://" + link return link # # get the name of the column columnname = self.tableWidget_apparat_media.horizontalHeaderItem( item.column() ).text() if columnname == "Link": link = __openLink(item.text()) if link is not None: webbrowser.open(link) # os.system("start " + link) return else: pass def text_to_clipboard(self): app_id = self.active_apparat text = f"SQ=select distinct akkey from aupr01 where aufst='{app_id}' union select pr_isn from aks4pd where akruf ='{app_id}'" clipboard = QtWidgets.QApplication.clipboard() clipboard.setText(text) def populate_prof_dropdown(self): profs = self.db.getProfs() # add empty entry to dropdown and set it as current self.drpdwn_prof_name.addItem("Kein Name") profs = [f"{prof.lastname}, {prof.firstname}" for prof in profs] profs.sort() for prof in profs: self.drpdwn_prof_name.addItem(prof) def add_document(self): # #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] 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) def open_document(self): _selected_doc_name = "" _selected_doc_filetype = "" try: _selected_doc_name = self.document_list.item( self.document_list.currentRow(), 0 ).text() _selected_doc_location = self.document_list.item( self.document_list.currentRow(), 3 ).text() _selected_doc_filetype = self.document_list.item( self.document_list.currentRow(), 1 ).text() except AttributeError: self.confirm_popup("Bitte erst ein document auswählen!", title="Fehler") return if not _selected_doc_location == "Database": path = Path(_selected_doc_location) # path: Path = path.resolve() # path. # path = path + "/" + _selected_doc_name if os.getenv("OS") == "Windows_NT": path = path.resolve() os.startfile(path) else: path = path.resolve() os.system(f"open {path}") else: recreateFile( _selected_doc_name, self.active_apparat, filetype=_selected_doc_filetype ) def add_media_from_file(self): app_id = self.active_apparat prof_id = self.db.getProfId(self.profdata) def __open_dialog(signatures: list[str]): dialog = QtWidgets.QDialog() frame = ParsedTitles() frame.setupUi(dialog) dialog.show() frame.signatures = signatures frame.populate_table() frame.progressBar.setMaximum(len(signatures)) frame.progressBar.setValue(0) frame.progressBar.show() frame.count.setText(str(len(signatures))) frame.toolButton.click() data = frame.return_data() # if no data was returned, return return data # get # if files are in the table, and are selected, check for books in the file if self.document_list.rowCount() == 0: return else: # if file is selected, check for books in the file if self.document_list.currentRow() != -1: # #log.debug("File selected") file = self.document_list.item( self.document_list.currentRow(), 3 ).text() file_type = self.document_list.item( self.document_list.currentRow(), 1 ).text() file_location = self.document_list.item( self.document_list.currentRow(), 3 ).text() file_name = self.document_list.item( self.document_list.currentRow(), 0 ).text() if file_location == "Database": # create a temporaty file to use, delete it after use temp_file = tempfile.NamedTemporaryFile( delete=False, suffix="." + file_type ) temp_file.write( self.db.getBlob(file_name, int(self.active_apparat)) ) 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 if file_type == "csv": signatures = csv_to_list(file) data = __open_dialog(signatures) # add the data to the database 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 == "docx": data = word_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 ) self.update_app_media_list() # #log.debug(len(signatures)) def extract_document_data(self) -> Union[list[str], SemapDocument]: file_type = self.document_list.item(self.document_list.currentRow(), 1).text() file_location = self.document_list.item( self.document_list.currentRow(), 3 ).text() file_name = self.document_list.item(self.document_list.currentRow(), 0).text() file = file_location log.info("File selected: {}, {}", file_name, file_location) if file_location == "Database": # create warning, then return self.db.recreateFile(file_name, self.active_apparat, filetype=file_type) if file_type == "pdf": # Todo: implement parser here self.confirm_popup("PDF Dateien werden nicht unterstützt!", title="Fehler") return [""] if file_type == "csv": signatures = csv_to_list(file) # add the data to the database return signatures if file_type == "docx": data = word_to_semap(file) log.info("Converted data from semap file") log.debug("Got the data: {}", data) return data def import_data_from_document(self): global valid_input data = self.extract_document_data() if data is None: return if isinstance(data, list): return self.prof_mail.setText(data.mail) self.prof_tel_nr.setText(str(data.phoneNumber).replace("-", "")) if len(data.title_suggestions) > 0: # create a dialog that has a dropdown with the suggestions, and oc and cancel button. on ok return the selected text and set it as title dialog = QtWidgets.QDialog() dialog.setWindowTitle("Titelvorschläge") dialog.setModal(True) layout = QtWidgets.QVBoxLayout() label = QtWidgets.QLabel( f"Bitte wählen Sie einen Titel aus:/nDer Titel darf max. {data.title_max_length} Zeichen lang sein." ) layout.addWidget(label) dropdown = QtWidgets.QComboBox() titles = [f"{title} [{len(title)}]" for title in data.title_suggestions] dropdown.addItems(titles) layout.addWidget(dropdown) button_box = QtWidgets.QDialogButtonBox() button_box.setStandardButtons( QtWidgets.QDialogButtonBox.StandardButton.Cancel | QtWidgets.QDialogButtonBox.StandardButton.Ok ) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) layout.addWidget(button_box) dialog.setLayout(layout) dialog.exec() if dialog.result() == QtWidgets.QDialog.DialogCode.Accepted: print("Selected title:", dropdown.currentText()) self.app_name.setText(dropdown.currentText().split(" [")[0].strip()) else: self.app_name.setText("CHANGEME") # self.app_name.setText(data.title) subjects = self.db.getSubjects() subjects = [subject[1] for subject in subjects] self.app_fach.setCurrentText(data.subject if data.subject in subjects else "") self.prof_title.setText(data.personTitle) self.drpdwn_prof_name.setCurrentText(data.personName) self.sem_year.setText("20" + str(data.semester.year)) if data.semester.semester == "SoSe": self.sem_sommer.setChecked(True) self.sem_winter.setChecked(False) else: self.sem_winter.setChecked(True) self.sem_sommer.setChecked(False) if data.eternal: self.check_eternal_app.setChecked(True) self.validate_semester() def btn_check_file_threaded(self): for runner in self.bookGrabber: if not runner.isRunning(): runner.deleteLater() # #log.debug("Checking file") # get active app_id and prof_id self.tableWidget_apparate.setEnabled(False) self.tableWidget_apparate.setToolTip( "Bitte warten, bis alle Medien hinzugefügt wurden" ) app_id = self.active_apparat log.debug(self.profdata) prof_id = self.db.getProfId(self.profdata) log.debug("Prof id: {}", prof_id) # check if apparat in database if prof_id is None: prof = Prof( fullname=self.drpdwn_prof_name.currentText(), telnr=self.prof_tel_nr.text(), mail=self.prof_mail.text(), firstname=self.drpdwn_prof_name.currentText().split(", ")[1], lastname=self.drpdwn_prof_name.currentText().split(", ")[0], ) self.db.createProf(prof) # if app_id not in database, create apparat if not self.db.checkApparatExistsById(app_id): log.info("Apparat does not exist, creating new apparat") # create apparat # #log.debug("Creating apparat") if not self.btn_save_apparat(False): return if self.document_list.rowCount() == 0: log.info("No file selected") self.tableWidget_apparate.setEnabled(True) self.tableWidget_apparate.setToolTip("") return else: # if file is selected, check for books in the file # #log.debug("File selected") if prof_id is None: prof_id = self.db.getProfId(self.profdata) # log.debug("Prof ID is None", prof_id) document = self.extract_document_data() if document is None: log.error("Document is None") elif isinstance(document, SemapDocument): signatures = document.signatures else: signatures = document autoGrabber = BookGrabber() autoGrabber.add_values( mode="ARRAY", app_id=int(app_id), prof_id=int(prof_id), data=signatures, any_book=True, exact=True, ) self.label_info.show() self.progress_label.show() self.line_2.show() # grabber.finished.connect(thread.quit) # self.autoGrabber.finished.connect(self.autoGrabber.deleteLater) autoGrabber.finished.connect(self.hide_progress_label) autoGrabber.finished.connect(self.unlock_apparate) autoGrabber.updateSignal.connect(self.update_progress_label) # worker.finished.connect(worker.deleteLater) autoGrabber.start() self.bookGrabber.append(autoGrabber) # refresh book table # end of thread # self.autoGrabber.exit() # self.__clear_fields() # self.btn_cancel_active_selection() def unlock_apparate(self): self.tableWidget_apparate.setEnabled(True) self.tableWidget_apparate.setToolTip("") def __clear_fields(self): self.drpdwn_app_nr.setCurrentText("") self.prof_title.clear() self.drpdwn_prof_name.clearMask() self.drpdwn_prof_name.setCurrentText("") self.app_name.clear() self.prof_mail.clear() self.prof_tel_nr.clear() self.app_fach.setCurrentText("") self.app_name.clear() self.sem_year.clear() self.document_list.setRowCount(0) self.sem_winter.setChecked(False) self.sem_sommer.setChecked(False) self.check_eternal_app.setChecked(False) self.prof_id_adis.clear() self.prof_id_adis.clear() self.apparat_id_adis.clear() self.drpdwn_prof_name.clear() self.tableWidget_apparat_media.setRowCount(0) self.app_group_box.setEnabled(False) self.check_send_mail.setChecked(False) self.cancel_active_selection.setEnabled(False) # check if sem_sommer, sem_winter and check_eternal_app are checked. if yes, set them to unchecked def btn_save_apparat(self, clear_fields=True): self.steps.hide() if not self.validate_fields(): self.confirm_popup("Bitte alle Pflichtfelder ausfüllen!", title="Fehler") return False prof = Prof( fullname=self.drpdwn_prof_name.currentText(), telnr=self.prof_tel_nr.text(), mail=self.prof_mail.text(), ) prof.title = self.prof_title.text() apparat = Apparat( appnr=self.active_apparat, name=self.app_name.text(), created_semester=self.generateSemester(), eternal=1 if self.check_eternal_app.isChecked() else 0, subject=self.app_fach.currentText(), deleted=0, prof_id_adis=self.prof_id_adis.text(), apparat_id_adis=self.apparat_id_adis.text(), ) appd = ApparatData(prof=prof, apparat=apparat) error = self.db.createApparat(appd) if error: self.confirm_popup(error.__str__(), title="Fehler") return False if self.document_list.rowCount() > 0: self.add_files() if error is not None: self.confirm_popup(error.__str__(), title="Fehler") return appdata = self.db.getAllAparats() # merge self.appdata and appdata, remove duplicates self.apparats = list(set(self.apparats + appdata)) self.apparats = natsorted(self.apparats, key=lambda x: x[4], reverse=True) self.update_apparat_list() # self.btn_load_apparat() if self.check_send_mail.isChecked(): self.contact_prof( apparat=appd.apparat.appnr, mail="Information zum Semesterapparat", location="", pid=appd.prof.fullname, ) if clear_fields: # #log.debug("clearing fields") self.__clear_fields() return True def send_mail_preview(self): pass @property def active_apparat(self): return self.drpdwn_app_nr.currentText() @property def profdata(self): return { "title": self.prof_title.text(), "profname": self.drpdwn_prof_name.currentText(), "prof_mail": self.prof_mail.text(), "prof_tel": self.prof_tel_nr.text(), } def add_files(self): """ 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()): files.append( { "name": self.document_list.item(i, 0).text(), "type": self.document_list.item(i, 1).text(), "date": self.document_list.item(i, 2).text(), "path": self.document_list.item(i, 3).text(), } ) self.document_list.item(i, 2).setText("") self.db.insertFile( files, self.active_apparat, self.db.getProfId( { "title": self.prof_title.text(), "profname": self.drpdwn_prof_name.currentText(), "prof_mail": self.prof_mail.text(), "prof_tel": self.prof_tel_nr.text(), } ), ) def update_apparat_list(self): self.tableWidget_apparate.setRowCount(0) for apparat in self.apparats: self.insert_apparat_into_table(apparat) log.info("Inserted {} apparats into table".format(len(self.apparats))) def insert_apparat_into_table(self, apparat: Apparat): # log.debug(apparat) def __dauer_check(apparat: Apparat): return "Ja" if apparat.eternal == 1 else "Nein" semester = ( apparat.extend_until if apparat.extend_until is not None else apparat.created_semester ) self.tableWidget_apparate.insertRow(0) self.tableWidget_apparate.setItem( 0, 0, QtWidgets.QTableWidgetItem(str(apparat.appnr)) ) self.tableWidget_apparate.setItem( 0, 1, QtWidgets.QTableWidgetItem(str(apparat.name)) ) self.tableWidget_apparate.setItem( 0, 2, QtWidgets.QTableWidgetItem( self.db.getProfNameById(apparat.prof_id, add_title=False) ), ) self.tableWidget_apparate.setItem( 0, 3, QtWidgets.QTableWidgetItem(str(semester)), ) self.tableWidget_apparate.setItem( 0, 4, QtWidgets.QTableWidgetItem(__dauer_check(apparat)) ) self.tableWidget_apparate.setItem( 0, 5, QtWidgets.QTableWidgetItem(str(apparat.konto)) ) def open_context_menu(self, position): menu = QtWidgets.QMenu() extend_action = menu.addAction("Verlängern") contact_action = menu.addAction("Kontaktieren") delete_action = menu.addAction("Löschen") remind_action = menu.addAction("Erinnerung") menu.addAction(extend_action) menu.addActions([contact_action, delete_action, remind_action]) extend_action.triggered.connect(self.extend_apparat) remind_action.triggered.connect(self.reminder) # convert point to row and column row = self.tableWidget_apparate.rowAt(position.y()) column = self.tableWidget_apparate.columnAt(position.x()) pos = (str(row), str(column)) if len(self.tableWidget_apparate.selectedItems()) == 0: return app_id = self.tableWidget_apparate.item(row, 0).text() pid = self.db.getProfIDByApparat(app_id) log.debug(app_id, pid) delete_action.triggered.connect(lambda: self.delete_apparat(pos)) # pass pos to contact_prof contact_action.triggered.connect( lambda: self.contact_prof(pid=pid, apparat=app_id) ) menu.exec(self.tableWidget_apparate.mapToGlobal(position)) def reminder(self): log.info("Opening reminder dialog") reminder = ReminderDialog() reminder.exec() tableposition = self.tableWidget_apparate.currentRow() appnr = self.tableWidget_apparate.item(tableposition, 0).text() if reminder.result() == QtWidgets.QDialog.DialogCode.Accepted: data = reminder.return_message() # #log.debug(data) self.db.addMessage( data, self.active_user, self.active_apparat if self.active_apparat != "" else appnr, ) self.update_calendar(data) # self.db.update_bookdata(data, book_id) # self.db.update_bookdata(data) log.info("committed message to database") # self.update_app_media_list() def get_reminders(self): messages = self.db.getAllMessages() log.info(f"Got {len(messages)} messages from database") self.calendarWidget.setMessages(messages) self.calendarWidget.updateCells() def open_reminder(self): selected_date = self.calendarWidget.selectedDate().toString("yyyy-MM-dd") # # #log.debug(selected_date) messages = self.db.getMessages(selected_date) if messages == []: return dialog = CalendarEntry(messages=messages, date=selected_date) # append dialog to self.frame_2 self.calendarlayout.addWidget(dialog) dialog.repaintSignal.connect(lambda: self.calendarWidget.reload(selected_date)) # type: ignore def open_settings(self): # log.debug(settings.dict()) settingsUI = Settings(self.active_user) settingsUI.exec() if settingsUI.result() == QtWidgets.QDialog.DialogCode.Accepted: settingsUI.save() # log.debug(settings.dict()) # self.reload() def reload(self): state = self.confirm_popup( "Bitte das Programm neustarten, um Änderungen zu übernehemen", "Einstellungen geändert", ) if state == 1: exit() else: pass def media_context_menu(self, position): # type: ignore menu = QtWidgets.QMenu() delete_action = QtGui.QAction("Löschen") edit_action = QtGui.QAction("Bearbeiten") update_data_action = QtGui.QAction("Daten aktualisieren") apparat_add_action = QtGui.QAction("Zum Apparat hinzufügen") apparat_move_action = QtGui.QAction("In Apparat verschieben") apparat_copy_action = QtGui.QAction("In Apparat kopieren") apparatmenu = menu.addMenu("Apparate") generalmenu = menu.addMenu("Allgemeines") apparatmenu.addActions( # type: ignore [apparat_add_action, apparat_copy_action, apparat_move_action] ) generalmenu.addActions([edit_action, delete_action, update_data_action]) # type: ignore # disable apparat_add_action apparat_add_action.setEnabled(False) delete_action.triggered.connect(self.delete_medium) # type: ignore edit_action.triggered.connect(self.edit_medium) # type: ignore apparat_add_action.triggered.connect(self.add_to_apparat) # type: ignore apparat_copy_action.triggered.connect(self.copy_to_apparat) # type: ignore apparat_move_action.triggered.connect(self.move_to_apparat) # type: ignore update_data_action.triggered.connect(self.update_data) # type: ignore menu.exec(self.tableWidget_apparat_media.mapToGlobal(position)) # type: ignore def update_data(self): # TODO: use link in table, parse data and if needed, update location / signature pass def copy_to_apparat(self): selected_rows = self.tableWidget_apparat_media.selectionModel().selectedRows() # type: ignore signatures: list[int] = [] for row in selected_rows: signature = self.tableWidget_apparat_media.item(row.row(), 1).text() # type: ignore book_id = self.db.getBookIdBasedOnSignature( self.active_apparat, self.db.getProfId(self.profdata), # type: ignore signature, ) signatures.append(book_id) # type: ignore result, apparat = self.confirm_action_dialog( # type: ignore "In welchen Apparat sollen die Medien kopiert werden?" ) if result == 1: for book_id in signatures: self.db.copyBookToApparat(book_id, apparat) else: return def move_to_apparat(self): selected_rows = self.tableWidget_apparat_media.selectionModel().selectedRows() signatures = [] for row in selected_rows: signature = self.tableWidget_apparat_media.item(row.row(), 1).text() book_id = self.db.getBookIdBasedOnSignature( self.active_apparat, self.db.getProfId(self.profdata), signature, ) signatures.append(book_id) result, apparat = self.confirm_action_dialog( "In welchen Apparat sollen die Medien verschoben werden?" ) if result == 1: for book_id in signatures: self.db.moveBookToApparat(book_id, apparat) self.update_app_media_list() else: return 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) if len(appnrs) == 0: # create a warning dialog, saying no apparats present self.confirm_popup("Keine weiteren Apparate vorhanden", title="Fehler") return (None, None) dialog = QtWidgets.QDialog() dialog.setWindowTitle(title) # add a label to the dialog label = QtWidgets.QLabel() label.setText(message) # add a combobox to the dialog drpdwn = QtWidgets.QComboBox() drpdwn.addItems(appnrs) drpdwn.addItem("") # set layout of dialog layout = QtWidgets.QVBoxLayout() layout.addWidget(label) layout.addWidget(drpdwn) # add buttons okay_button = QtWidgets.QPushButton("Okay") cancel_button = QtWidgets.QPushButton("Abbrechen") layout.addWidget(okay_button) layout.addWidget(cancel_button) okay_button.clicked.connect(dialog.accept) cancel_button.clicked.connect(dialog.reject) dialog.setLayout(layout) return dialog.exec(), self.db.getApparatId( self.db.getApparatNameByAppNr(drpdwn.currentText()) ) def add_to_apparat(self): """use playwright in background to add medium to apparat""" signature = self.tableWidget_apparat_media.item( self.tableWidget_apparat_media.currentRow(), 1 ).text() self.db.getBookBasedOnSignature( self.drpdwn_app_nr.currentText(), signature=signature, prof_id=self.db.getProfId(self.profdata), ) # log.debug(medium.adis_idn, medium.signature) def edit_medium(self): book = self.tableWidget_apparat_media.item( self.tableWidget_apparat_media.currentRow(), 1 ).text() prof_id = self.db.getProfId(self.profdata) data = self.db.getBookBasedOnSignature( app_id=self.active_apparat, signature=book, prof_id=prof_id, ) book_id = self.db.getBookIdBasedOnSignature( self.active_apparat, prof_id, book, ) widget = QtWidgets.QDialog() bookedit = BookDataUI() bookedit.setupUi(widget) widget.setWindowIcon(Icon("settings").icon) # change title of dialog widget.setWindowTitle("Metadaten") bookedit.populate_fields(data) widget.exec() if widget.result() == QtWidgets.QDialog.DialogCode.Accepted: data = bookedit.get_data() # #log.debug(data) self.db.updateBookdata(bookdata=data, book_id=book_id) # self.db.update_bookdata(data) # #log.debug("accepted") self.update_app_media_list() else: return pass def delete_medium(self): selected_apparat_id = self.tableWidget_apparate.item( self.tableWidget_apparate.currentRow(), 0 ).text() prof_id = self.db.getProfId(self.profdata) # check how many rows are selected selected_rows = self.tableWidget_apparat_media.selectionModel().selectedRows() if len(selected_rows) == 1: signature = self.tableWidget_apparat_media.item( self.tableWidget_apparat_media.currentRow(), 1 ).text() book_id = self.db.getBookIdBasedOnSignature( selected_apparat_id, prof_id=prof_id, signature=signature, ) message = f'Soll das Medium "{self.tableWidget_apparat_media.item(self.tableWidget_apparat_media.currentRow(), 0).text()}" wirklich gelöscht werden?' state = self.confirm_popup(message, title="Löschen?") # #log.debug(state) if state == 1: self.db.deleteBook(book_id) self.update_app_media_list() pass else: # get all selected rows ranges = self.tableWidget_apparat_media.selectedRanges() rows = [] for r in ranges: for row in range(r.topRow(), r.bottomRow() + 1): rows.append(row) # #log.debug(rows) message = f"Sollen die {len(rows)} Medien wirklich gelöscht werden?" state = self.confirm_popup(message, title="Löschen?") if state == 1: for _ in rows: signature = self.tableWidget_apparat_media.item(_, 1).text() book_id = self.db.getBookIdBasedOnSignature( app_id=selected_apparat_id, prof_id=prof_id, signature=signature, ) self.db.deleteBook(book_id) self.update_app_media_list() def extend_apparat(self): framework = ApparatExtendDialog() framework.exec() # return data from dialog if ok is pressed if framework.result() == QtWidgets.QDialog.DialogCode.Accepted: data = framework.get_data() # #log.debug(data) # return data selected_apparat_id = self.tableWidget_apparate.item( self.tableWidget_apparate.currentRow(), 0 ).text() # #log.debug(selected_apparat_id) self.db.setNewSemesterDate( selected_apparat_id, data["semester"], dauerapp=data["dauerapp"] ) # update the table self.get_apparats() else: return def __contact_dialog(self, apparat, location: tuple | str, mail=None, pid=""): log.debug( "Got these values apparat: {}, location: {}, mail: {}, pid: {}".format( apparat, location, mail, pid ) ) active_apparat_id = ( self.drpdwn_app_nr.currentText() if apparat is None else apparat ) if not active_apparat_id: # get column 0 of the selected row pass if isinstance(pid, str): prof_id = self.db.getProfByName(pid).id else: prof_id = pid # if profname == "Name Kein": # profname = pid if self.app_name.text() != "": app_name = self.app_name.text() else: app_name = self.db.getApparatName(active_apparat_id, prof_id) if self.app_fach.currentText() != "": app_subject = self.app_fach.currentText() else: app_subject = self.db.getApparatData(active_apparat_id, app_name) app_subject = app_subject.apparat.subject if prof_id: pmail = self.db.getSpecificProfData(prof_id, ["mail"]) prof_name = self.db.getSpecificProfData(prof_id, ["fullname"]) else: pmail = self.prof_mail.text() self.mail_thread = Mail_Dialog( app_id=active_apparat_id, prof_name=prof_name, prof_mail=pmail, app_name=app_name, app_subject=app_subject, default_mail=mail if mail != "" else "Information zum Semesterapparat", ) self.mail_thread.show() def contact_prof(self, apparat="", location="", mail="", pid=""): log.debug(apparat) log.debug(location) if self.active_apparat == "": if apparat is False: self.confirm_popup( "Bitte erst einen Apparat auswählen!", title="Apparat auswählen" ) return self.__contact_dialog(apparat, mail=mail, pid=pid, location=location) def delete_apparat(self, position): selected_apparat_id = self.tableWidget_apparate.item( self.tableWidget_apparate.currentRow(), 0 ).text() message = f"Soll der Apparat {selected_apparat_id} wirklich gelöscht werden?" state = self.confirm_popup(message, title="Löschen?") # #log.debug(state) log.info("Result state: {}", state) if state == 1: log.debug("Deleting apparat {}", selected_apparat_id) pid = self.db.getProfIDByApparat(selected_apparat_id) self.db.deleteApparat(selected_apparat_id, Semester().value) # delete the corresponding entry from self.apparats for apparat in self.apparats: if apparat[4] == int(selected_apparat_id): self.apparats.remove(apparat) break self.old_apparats = self.apparats # #log.debug(self.apparats) # remove the row from the table self.tableWidget_apparate.removeRow(self.tableWidget_apparate.currentRow()) # send mail to prof self.contact_prof(mail="deleted", apparat=selected_apparat_id, pid=pid) def launch_gui(): # #log.debug("trying to login") # #log.debug("checking if database available") log.info("Starting login dialog") app = QtWidgets.QApplication(sys.argv) login_dialog = QtWidgets.QDialog() ui = LoginDialog() ui.setupUi(login_dialog) login_dialog.exec() # This will block until the dialog is closed if ui.lresult == 1: # if login is successful, open main window # show login dialog # #log.debug(ui.lusername) MainWindow = QtWidgets.QMainWindow() aui = Ui(MainWindow, username=ui.lusername) # #log.debug(aui.active_user) MainWindow.show() # atexit.register() atexit.register(tempdelete) atexit.register(aui.validate_thread.quit) sys.exit(app.exec()) elif ui.lresult == 0: warning_dialog = QtWidgets.QMessageBox() warning_dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) warning_dialog.setText("Invalid username or password. Please try again.") warning_dialog.setWindowTitle("Login Failed") warning_dialog.exec() atexit.register(tempdelete) if __name__ == "__main__": # #log.debug("This is the main window") # app = QtWidgets.QApplication(sys.argv) # window = MainWindow() # app.exec() # open login screen launch_gui()