2195 lines
86 KiB
Python
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()
|