Files
SemesterapparatsManager/src/ui/userInterface.py

2195 lines
86 KiB
Python

# encoding: utf-8
import atexit
import os
import sys
import tempfile
import time
import webbrowser
from pathlib import Path
from typing import Any, List, Optional, Tuple, Union
from natsort import natsorted
from PySide6 import QtCore, QtGui, QtWidgets
from PySide6.QtCore import QThread
from PySide6.QtGui import QRegularExpressionValidator
from PySide6.QtMultimedia import QAudioOutput, QMediaPlayer
from src import Icon
from src.backend import (
AvailChecker,
BookGrabber,
Database,
DocumentationThread,
NewEditionCheckerThread,
)
from src.backend.create_file import recreateFile
from src.backend.delete_temp_contents import delete_temp_contents as tempdelete
from src.logic import (
APP_NRS,
Apparat,
ApparatData,
BookData,
Prof,
SemapDocument,
Semester,
csv_to_list,
eml_to_semap,
pdf_to_semap,
word_to_semap,
)
from src.shared.logging import log
from src.ui import Ui_Semesterapparat
from src.ui.dialogs import (
About,
ApparatExtendDialog,
BookDataUI,
DeleteDialog,
DocumentPrintDialog,
LoginDialog,
Mail_Dialog,
MedienAdder,
NewEditionDialog,
ParsedTitles,
ReminderDialog,
Settings,
popus_confirm,
)
from src.ui.widgets import (
CalendarEntry,
EditProf,
EditUser,
ElsaDialog,
FilePicker,
MessageCalendar,
NewEditionChecker,
NewEditionCheckSelector,
SearchStatisticPage,
UpdaterThread,
UpdateSignatures,
UserCreate,
)
log.success("UI started")
valid_input = (0, 0, 0, 0, 0, 0)
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")
super().__init__()
self.active_user = username
self.setupUi(MainWindow) # type:ignore
self.MainWindow = MainWindow # type:ignore
# set the window title
MainWindow.setWindowTitle(
f"Semesterapparatsmanagement Semester: {Semester().value}"
) # 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.documentation_open = False
Icon("offAction", self.actionBeenden)
self.actionBeenden.triggered.connect(self.quit) # type:ignore
self.actionAbout.triggered.connect(self.open_about) # type:ignore
self.actionMedien_loeschen.triggered.connect(self.open_delete_dialog)
self.actionMedien_loeschen.setIcon(Icon("trash").icon)
# 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.progressBar.setValue(0)
self.progressBar.hide()
self.progressBar.setMinimum(0)
self.avail_status.hide()
# self.chkbx_show_del_media.hide()
self.chkbx_show_only_wit_neweditions.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.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.newEditionChecker = NewEditionCheckerThread()
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.player = QMediaPlayer()
self.audio_output = QAudioOutput()
self.status_progress = QtWidgets.QProgressBar()
self.statusBar.addWidget(self.status_progress)
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 open_delete_dialog(self):
# this will open a dialog to select and delete multiple entries. by default, all books will be loaded, and a search bar can be used to fuzzy search the books and select books. on a button press, the selected books will be deleted from the apparat(s) they are part in
# raise NotImplementedError("This feature is not yet implemented.")
dialog = DeleteDialog()
dialog.exec()
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):
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")
# 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")
elif self.select_action_box.currentText() == "Medien bearbeiten":
self.setWidget(UpdateSignatures())
self.admin_action.setTitle("Medien 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 play_sound(self, sound_file: str):
self.player.setAudioOutput(self.audio_output)
self.audio_output.setVolume(50)
self.player.setSource(QtCore.QUrl.fromLocalFile(f"src/sounds/{sound_file}"))
self.player.play()
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")
self.statusBar.showMessage("Dokumentation wird geöffnet", 5000)
if not self.documentation_open:
# write "opening documentation in 5s into status bar"
self.documentation_open = True
self.docu.start()
time.sleep(5)
webbrowser.open("http://localhost:8000")
self.statusBar.showMessage("")
def update_calendar(self, data: list[dict[str, Any]]):
self.calendarWidget.setMessages(data)
self.calendarWidget.updateCells()
def status_bar_progress(self, current: int, total: int):
self.status_progress.setRange(0, total)
self.status_progress.setValue(current)
if current == total:
self.status_progress.hide()
self.status_progress.setValue(0)
else:
self.status_progress.show()
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)
statistics.status_update.connect(self.status_bar_progress)
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: Union[int, str]):
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: QtWidgets.QTableWidget, value: str):
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 # type: ignore
):
return i, j
return (None, None)
def load_app_data(self, app_id: Optional[Union[int, str]] = 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_nr = self.db.getId(self.app_name.text())
prof_id = self.db.getProfId(self.profdata)
log.debug(f"{prof_id}, {app_nr}")
# check if app_id is in database
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
bookGrabber = BookGrabber()
bookGrabber.add_values(
mode=mode,
prof_id=prof_id,
app_id=app_nr,
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.drpdwn_app_nr.currentText()
# #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):
app_id = self.db.getId(self.app_name.text())
prof_id = self.db.getProfId(self.profdata)
books: list[dict[int, BookData, int]] = self.db.getBooks(app_id, prof_id, 0)
# # #log.debug(books)
# take the dataclass from the tuple
# booklist:list[BookData]=[book[0] for book in books]
self.tableWidget_apparat_media.clearContents()
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),
)
label = QtWidgets.QLabel(f'<a href="{book_data.link}">Katalog</a>')
label.setOpenExternalLinks(True)
label.setTextFormat(QtCore.Qt.TextFormat.RichText)
label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
label.setTextInteractionFlags(
QtCore.Qt.TextInteractionFlag.TextBrowserInteraction
)
self.tableWidget_apparat_media.setCellWidget(
self.tableWidget_apparat_media.rowCount() - 1, 6, label
)
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")
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, url: Optional[str] = None):
# #log.debug("Add document")
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(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 = ""
_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 Dokument 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.db.getId(self.app_name.text())
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(app_id)))
temp_file.close()
file = temp_file.name
if file_type == "pdf":
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)
# 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
)
if file_type == "eml":
data = eml_to_semap(file)
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
file = 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()
self.bookGrabber.remove(runner)
# #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 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=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.db.getId(self.app_name.text())
@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")
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,
order_newedition_action,
]
)
# 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)
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(
lambda: self.contact_prof(pid=pid, apparat=app_id)
)
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_nr": app_nr,
"app_name": self.db.getApparatName(app_nr, prof_id),
}
orderDialog = NewEditionDialog(app_id, mail_data)
orderDialog.exec()
def update_status(self, curr, total):
self.avail_status.show()
self.label_20.show()
self.progressBar.show()
self.avail_status.setText(f"{curr}/{total}")
self.progressBar.setValue(curr)
if curr == total:
self.avail_status.hide()
self.label_20.hide()
self.progressBar.hide()
self.progressBar.setValue(0)
self.avail_status.setText("0/0")
def check_new_editions(self):
# create a dialog that asks "Prof oder Apparat" with a button for each. based on that either search through the books of the apparat, or all books associated with the prof
selector = NewEditionCheckSelector()
selector.exec()
pick = selector.selection
app_id = self.tableWidget_apparate.item(
self.tableWidget_apparate.currentRow(), 0
).text()
prof_id: int = self.db.getProfIDByApparat(app_id)
app_name = self.tableWidget_apparate.item(
self.tableWidget_apparate.currentRow(), 1
).text()
subject = self.tableWidget_apparate.item(
self.tableWidget_apparate.currentRow(), 4
).text()
if pick == "professor":
books = self.db.getBooksByProfId(prof_id)
app_name = "Sammelmail"
app_id = ", ".join(
[str(app.appnr) for app in self.db.getApparatsByProf(prof_id)]
)
else:
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)
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(searchable_books))
self.newEditionChecker.updateSignal.connect(self.update_status)
self.newEditionChecker.start()
while self.newEditionChecker.isRunning():
QtWidgets.QApplication.processEvents()
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
if accepted_books == []:
return
for book in accepted_books:
oldBookId = self.db.getBookIdByPPN(book.old_book.ppn)
apparats_id = self.db.getId(
self.db.getApparatNameByAppNr(book.old_book.library_location)
)
self.db.insertNewEdition(book, oldBookId, apparats_id)
pass
self.mail_thread = Mail_Dialog(
prof_name=self.db.getSpecificProfData(prof_id, ["fullname"]),
prof_mail=self.db.getProfMailById(prof_id),
app_id=app_id,
app_name=app_name,
app_subject=subject,
accepted_books=accepted_books,
default_mail="Neuauflagen für Semesterapparat",
)
self.mail_thread.show()
def reset_eta(self):
self.label_eta.setText("")
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")
replace_old_editions = QtGui.QAction("Neuauflagen ersetzen")
apparatmenu = menu.addMenu("Apparate")
generalmenu = menu.addMenu("Allgemeines")
apparatmenu.addActions( # type: ignore
[
apparat_add_action,
apparat_copy_action,
apparat_move_action,
replace_old_editions,
]
)
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
replace_old_editions.triggered.connect(self.replace_old_edition) # type: ignore
menu.exec(self.tableWidget_apparat_media.mapToGlobal(position)) # type: ignore
def replace_old_edition(self):
# open dialog
dialog = QtWidgets.QDialog()
dialog.setWindowTitle("Neuauflagen:")
layout = QtWidgets.QVBoxLayout()
label = QtWidgets.QLabel("Folgende Medien haben Neuauflagen:")
layout.addWidget(label)
table = QtWidgets.QTableWidget()
table.setColumnCount(4)
table.setHorizontalHeaderLabels(["Titel", "Auflage", "Signatur", "Neues Werk"])
table.horizontalHeader().setStretchLastSection(True)
new_editions = self.db.getBooksWithNewEditions(
self.active_apparat,
)
for book in new_editions:
table.insertRow(0)
table.setItem(0, 0, QtWidgets.QTableWidgetItem(book[0].title))
table.setItem(0, 1, QtWidgets.QTableWidgetItem(str(book[0].edition)))
table.setItem(0, 2, QtWidgets.QTableWidgetItem(book[0].signature))
new_ed_data = (
f"{book[1].title} (Auflage {book[1].edition}, {book[1].signature})"
)
table.setItem(0, 3, QtWidgets.QTableWidgetItem(new_ed_data))
layout.addWidget(table)
dialog.setLayout(layout)
dialog.exec()
def update_data(self):
signatures = [
self.tableWidget_apparat_media.item(row, 1).text()
for row in range(self.tableWidget_apparat_media.rowCount())
] # type: ignore
prof_id = self.db.getProfId(self.profdata) # type: ignore
app_id = self.db.getId(self.app_name.text()) # type: ignore
books: List[Tuple[int, BookData]] = []
for signature in signatures:
book = self.db.getBookBasedOnSignature(
app_id=app_id,
signature=signature,
prof_id=prof_id,
)
book_id = self.db.getBookIdBasedOnSignature(
app_id,
prof_id,
signature,
)
books.append((book_id, book))
# self.autoUpdater.entries = books
# self.autoUpdater.finished.connect(self.autoUpdater.reset)
self.updater = UpdaterThread()
u_books = []
for book_id, book in books:
u_books.append({"id": book_id, "bookdata": book})
self.updater.books = u_books
self.progressBar.setMaximum(len(books))
self.updater.finished.connect(self.updater.deleteLater)
self.updater.finished.connect(self.update_app_media_list)
self.updater.currtot.connect(self.update_status)
self.updater.start()
# ppn = book.link.split("kid=")[-1]
# result = cat.get_book(ppn)
# if result:
# book.signature = result.signature
# book.in_apparat = True
# #print(book)
# self.db.updateBookdata(book_id, book)
# self.update_app_media_list()
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.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")
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.db.getId(self.app_name.text()),
signature=book,
prof_id=prof_id,
)
book_id = self.db.getBookIdBasedOnSignature(
self.db.getId(self.app_name.text()),
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.active_apparat
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="",
accepted_books=None,
app_id="",
):
log.debug(
"Got these values apparat: {}, location: {}, mail: {}, pid: {}, accepted_books: {}, app_id: {}".format(
apparat, location, mail, pid, accepted_books, app_id
)
)
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)
apparat = Apparat(
appnr=int(selected_apparat_id),
name=self.tableWidget_apparate.item(
self.tableWidget_apparate.currentRow(), 1
).text(),
)
self.db.deleteApparat(apparat=apparat, semester=Semester().value)
# delete the corresponding entry from self.apparats
for apparat in self.apparats:
if apparat.appnr == 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.instance()
if app is None:
app = QtWidgets.QApplication(sys.argv)
else:
log.info("Using existing QApplication instance")
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()
app.aboutToQuit.connect(
aui.validate_thread.quit
) # if that thread uses an event loop
app.aboutToQuit.connect(aui.docu.terminate) # our new slot
app.aboutToQuit.connect(aui.docu.wait)
app.aboutToQuit.connect(aui.newEditionChecker.terminate)
atexit.register(tempdelete)
# atexit.register(aui.validate_thread.quit)
# atexit.register(aui.docu.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()