Files
SemesterapparatsManager/src/ui/widgets/searchPage.py

602 lines
25 KiB
Python

from typing import List
from natsort import natsorted
from PySide6 import QtCore, QtGui, QtWidgets
from PySide6.QtCore import Signal
from src.backend import Database
from src.logic import BookData, Prof, Semester, custom_sort, sort_semesters_list
from src.logic.dataclass import Apparat
from src.shared.logging import log
from src.ui.dialogs import ApparatExtendDialog, Mail_Dialog, ReminderDialog
from src.ui.widgets import DataQtGraph, StatusWidget
from src.ui.widgets.signature_update import UpdaterThread
from .widget_sources.search_statistic_page_ui import Ui_Dialog
class MyComboBox(QtWidgets.QComboBox):
def __init__(self, parent=None):
super().__init__(parent)
class SearchStatisticPage(QtWidgets.QDialog, Ui_Dialog):
apparat_open = Signal(str)
reloadSignal = Signal()
refreshSignal = Signal()
updateCalendar = Signal(int, list)
status_update = Signal(int, int)
def __init__(self):
log.info("SearchStatisticPage started")
super().__init__()
self.setupUi(self)
self.book_search_result.horizontalHeader().setSectionResizeMode(
QtWidgets.QHeaderView.ResizeMode.Stretch
)
self.check_deletable.stateChanged.connect(self.gridchange)
self.tableWidget.horizontalHeader().setSectionResizeMode(
QtWidgets.QHeaderView.ResizeMode.Stretch
)
self.btn_del_select_apparats.clicked.connect(self.delete_selected_apparats)
self.statistics_table.doubleClicked.connect(self.display_detailed_data)
self.tabWidget_2.currentChanged.connect(self.tabW2_changed)
self.btn_search.clicked.connect(self.statistics)
self.tableWidget.customContextMenuRequested.connect(
self.statistics_table_context_menu
)
# statistic side buttons
self.btn_notify_for_deletion.clicked.connect(self.notify_for_deletion)
self.btn_notify_for_deletion.setEnabled(False)
self.btn_del_select_apparats.setEnabled(False)
self.btn_extendSelection.clicked.connect(self.mass_extend_apparats)
self.btn_extendSelection.setEnabled(False)
self.tableWidget.resizeColumnsToContents()
self.tableWidget.resizeRowsToContents()
self.db = Database()
self.appnrs = self.db.getUnavailableApparatNumbers()
self.box_appnrs.addItems(str(i) for i in self.appnrs)
self.splitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal)
# insert splitter in apparatResult to allow resizing of the columns
self.splitter.addWidget(self.app_results)
self.splitter.addWidget(self.stats)
self.apparatResult.layout().removeWidget(self.stats)
self.apparatResult.layout().removeWidget(self.app_results)
self.apparatResult.layout().addWidget(self.splitter)
# set tableWidget column 0 to be 50px wide
self.tableWidget.setColumnWidth(0, 50)
self.semester = Semester().value
self.search_by_signature.returnPressed.connect(self.search_book)
self.search_by_title.returnPressed.connect(self.search_book)
# add context menu to the book_search_result
self.book_search_result.setContextMenuPolicy(
QtCore.Qt.ContextMenuPolicy.CustomContextMenu
)
self.book_search_result.customContextMenuRequested.connect(
self.book_search_result_context_menu
)
self.populate_tab()
def book_search_result_context_menu(self, position):
menu = QtWidgets.QMenu()
open_apparat_action = menu.addAction("Semesterapparat öffnen")
open_apparat_action.triggered.connect(self.open_selected_apparat)
update_books_action = menu.addAction("Bücher aktualisieren")
update_books_action.triggered.connect(self.update_books)
menu.exec(self.book_search_result.mapToGlobal(position))
def open_selected_apparat(self):
selected_rows = self.book_search_result.selectionModel().selectedRows()
if len(selected_rows) == 0:
return
row = selected_rows[0].row()
apparat_text = self.book_search_result.item(row, 2).text()
apparat_number = apparat_text.split(" (")[0]
self.apparat_open.emit(apparat_number)
def update_books(self):
# update all books in the book_search_result
self.updater = UpdaterThread()
books = []
for row in range(self.book_search_result.rowCount()):
signature = self.book_search_result.item(row, 1).text()
booksdata = self.db.query_db(
"SELECT id, bookdata from media WHERE bookdata LIKE ?",
(f'%"{signature}"%',),
one=True,
)
books.append(
{"id": booksdata[0], "bookdata": BookData().from_string(booksdata[1])}
)
self.updater.books = books # type: ignore
self.updater.currtot.connect(self.status_update_emit)
self.updater.finished.connect(self.updater.deleteLater)
self.updater.finished.connect(self.search_book)
self.updater.start()
def status_update_emit(self, current: int, total: int):
self.status_update.emit(current, total)
def mass_extend_apparats(self):
extend = ApparatExtendDialog()
extend.exec()
if extend.result() == QtWidgets.QDialog.DialogCode.Accepted:
data = extend.get_data()
log.debug(data)
for i in range(self.tableWidget.rowCount()):
if self.tableWidget.cellWidget(i, 0).isChecked():
app_name = self.tableWidget.item(i, 1).text()
app_id = self.db.getApparatId(app_name)
self.db.setNewSemesterDate(
app_id, data["semester"], data["dauerapp"]
)
# remove the row
self.tableWidget.removeRow(i)
self.refreshSignal.emit()
def restore_apparat(self):
selected_rows = self.tableWidget.selectionModel().selectedRows()
apparats = []
if len(selected_rows) == 0:
# get position of right click
row = self.tableWidget.currentRow()
apparats.append(self.tableWidget.item(row, 1).text())
else:
for row in selected_rows:
apparats.append(self.tableWidget.item(row.row(), 1).text())
for apparat in apparats:
apparat_id = self.db.getApparatId(apparat)
self.db.restoreApparat(apparat_id, apparat)
# remove the red color from the row
# get row where the apparat is
row = self.tableWidget.findItems(apparat, QtCore.Qt.MatchFlag.MatchExactly)[
0
].row()
for j in range(5):
self.tableWidget.item(row, j).setBackground(QtGui.QColor(255, 255, 255))
self.reloadSignal.emit()
def statistics_table_context_menu(self, position):
menu = QtWidgets.QMenu()
restore_action = menu.addAction("Wiederherstellen")
extend_action = menu.addAction("Verlängern")
remind_action = menu.addAction("Erinnerung")
menu.addAction(restore_action)
restore_action.triggered.connect(self.restore_apparat)
extend_action.triggered.connect(self.extend_apparat)
remind_action.triggered.connect(self.reminder)
menu.exec(self.tableWidget.mapToGlobal(position))
def extend_apparat(self):
extend = ApparatExtendDialog()
extend.exec()
if extend.result() == QtWidgets.QDialog.DialogCode.Accepted:
data = extend.get_data()
log.debug(data)
app_name = self.tableWidget.item(self.tableWidget.currentRow(), 1).text()
app_id = self.db.getApparatId(app_name)
self.db.setNewSemesterDate(app_id, data["semester"], data["dauerapp"])
# remove the row
self.tableWidget.removeRow(self.tableWidget.currentRow())
self.refreshSignal.emit()
def reminder(self):
log.info("Opening reminder dialog")
reminder = ReminderDialog()
reminder.exec()
tableposition = self.tableWidget.currentRow()
appnr = self.tableWidget.item(tableposition, 2).text()
if reminder.result() == QtWidgets.QDialog.DialogCode.Accepted:
data = reminder.return_message()
# ##print(data)
self.db.addMessage(
data,
"admin",
appnr,
)
self.updateCalendar.emit(data)
log.info("committed message to database")
def tabW2_changed(self):
if self.tabWidget_2.currentIndex() == 0:
self.stackedWidget_4.setCurrentIndex(0)
else:
self.stackedWidget_4.setCurrentIndex(1)
def search_book(self):
self.no_result.setText("")
self.book_search_result.setRowCount(0)
signature = self.search_by_signature.text()
title = self.search_by_title.text()
params = {
"signature": signature if signature != "" else None,
"title": title if title != "" else None,
}
params = {key: value for key, value in params.items() if value is not None}
log.info(params)
retdata = self.db.searchBook(params)
if retdata == [] or retdata is None:
self.no_result.setText("Keine Ergebnisse gefunden")
return
for book in retdata:
log.debug(book)
self.book_search_result.insertRow(0)
self.book_search_result.setItem(
0, 0, QtWidgets.QTableWidgetItem(book[0].title)
)
self.book_search_result.setItem(
0, 1, QtWidgets.QTableWidgetItem(book[0].signature)
)
# ##print(book[1])
self.book_search_result.setItem(
0,
2,
QtWidgets.QTableWidgetItem(
self.db.fetch_one(
"SELECT semesterapparat.appnr || ' (' || semesterapparat.name || ')' AS formatted_result from semesterapparat WHERE semesterapparat.id = ?",
(book[1],),
)[0],
),
)
def notify_for_deletion(self):
# get all selected apparats
selected_apparats: list[dict] = []
for i in range(self.tableWidget.rowCount()):
data = {}
if self.tableWidget.cellWidget(i, 0).isChecked():
data["app_id"] = self.tableWidget.item(i, 2).text()
data["app_name"] = self.tableWidget.item(i, 1).text()
data["prof_name"] = self.tableWidget.item(i, 3).text()
selected_apparats.append(data)
# delete all selected apparats
log.debug(selected_apparats)
dialogs = []
for i in selected_apparats:
app_id = i["app_id"]
app_name = i["app_name"]
prof_name = i["prof_name"]
prof_mail = self.db.getProfData(prof_name).mail
self.mail_thread = Mail_Dialog(
app_id=app_id,
app_name=app_name,
prof_name=prof_name,
prof_mail=prof_mail,
app_subject="",
default_mail="Information bezüglich der Auflösung des Semesterapparates",
)
dialogs.append(self.mail_thread)
for dialog in dialogs:
dialog.exec()
self.btn_notify_for_deletion.setEnabled(False)
def setStatisticTableSize(self):
# # ##print(self.statistics_table.size(), self.statistics_table.rowCount())
size = self.statistics_table.size()
h = size.height()
w = size.width()
rows = self.statistics_table.rowCount()
rowheight = round(h / rows) - 5
header_width = round(w / 3) - 5
for i in range(3):
self.statistics_table.setColumnWidth(i, header_width)
for i in range(rows):
self.statistics_table.setRowHeight(i, rowheight)
def __table_or_graph(self):
return self.tabWidget_3.currentIndex()
def gridchange(self):
if self.check_deletable.isChecked():
self.box_semester.setEnabled(False)
self.box_appnrs.setEnabled(False)
self.box_appnrs.clear()
self.box_person.setEnabled(False)
self.box_person.clear()
self.box_fach.setEnabled(False)
self.box_fach.clear()
self.box_erstellsemester.setEnabled(False)
self.box_erstellsemester.clear()
self.box_dauerapp.setEnabled(False)
self.box_dauerapp.clear()
self.populate_tab(self.__table_or_graph())
else:
self.box_semester.setEnabled(True)
self.box_appnrs.setEnabled(True)
self.box_person.setEnabled(True)
self.box_fach.setEnabled(True)
self.box_erstellsemester.setEnabled(True)
self.box_dauerapp.setEnabled(True)
def populate_tab(self, table_or_graph=0):
log.info("populate_tab started")
# add default values to the dropdowns
self.box_appnrs.clear()
self.box_appnrs.addItem("")
self.box_appnrs.setCurrentText("")
self.box_person.clear()
self.box_person.addItem("")
self.box_person.setCurrentText("")
self.box_fach.clear()
self.box_fach.addItem("")
self.box_fach.setCurrentText("")
self.box_erstellsemester.clear()
self.box_erstellsemester.addItem("")
self.box_erstellsemester.setCurrentText("")
self.box_semester.clear()
self.box_semester.addItem("")
self.box_semester.setCurrentText("")
self.box_dauerapp.clear()
self.box_dauerapp.addItems(["Ja", "Nein", ""])
self.box_dauerapp.setCurrentText("")
# add custom vaules
appnrs = self.appnrs
apparats = natsorted(appnrs)
apparats = [str(apparat) for apparat in apparats]
self.box_appnrs.addItems(apparats)
persons: List[Prof] = sorted(self.db.getProfs(), key=lambda x: x.lastname)
self.box_person.addItems(
[f"{person.lastname}, {person.firstname}" for person in persons]
)
self.box_fach.addItems(subject[1] for subject in self.db.getSubjects())
semester = self.db.getSemesters()
semester = sort_semesters_list(semester)
self.box_erstellsemester.addItems(semester)
self.box_semester.addItems(semester)
self.statistics_table.setRowCount(0)
# set data for table and graph in tab 2 tableWidget_3
data = self.db.getApparatCountBySemester()
data = custom_sort(data)
# self.tabWidget_3.clear()
self.tabWidget_3.removeTab(1)
self.statistics_table.setRowCount(len(data))
for i in range(len(data)):
self.statistics_table.setItem(i, 0, QtWidgets.QTableWidgetItem(data[i][0]))
self.statistics_table.setItem(
i, 1, QtWidgets.QTableWidgetItem(str(data[i][1]))
)
self.statistics_table.setItem(
i, 2, QtWidgets.QTableWidgetItem(str(data[i][2]))
)
self.statistics_table.resizeColumnsToContents()
self.statistics_table.resizeRowsToContents()
# self.setStatisticTableSize()
# get height of the table
# create graph
graph_data = {
"x": [i[0] for i in data],
"y": {"Erstellt": [i[1] for i in data], "Gelöscht": [i[2] for i in data]},
}
graph = DataQtGraph(
title="Erstellte und gelöschte Apparate",
data=graph_data,
generateMissing=False,
y_label="Anzahl der Apparate",
)
# place the graph into tabWidget_3
self.tabWidget_3.addTab(graph, "Graph")
self.tabWidget_3.setCurrentIndex(table_or_graph)
log.info("populate_tab finished")
def delete_selected_apparats(self):
# get all selected apparats
selected_apparats = []
selected_apparat_rows = []
for i in range(self.tableWidget.rowCount()):
if self.tableWidget.cellWidget(i, 0).isChecked():
selected_apparats.append(
Apparat(
appnr=self.tableWidget.item(i, 2).text(),
name=self.tableWidget.item(i, 1).text(),
)
)
selected_apparat_rows.append(i)
# delete all selected apparats
# # ##print(selected_apparats)
log.info(f"Deleting apparats: {selected_apparats}")
for apparat in selected_apparats:
self.db.deleteApparat(apparat, self.semester)
for row in selected_apparat_rows:
# set the background of the row to red
for j in range(5):
self.tableWidget.item(row, j).setBackground(QtGui.QColor(235, 74, 71))
# refresh the table
self.populate_tab(self.__table_or_graph())
self.btn_del_select_apparats.setEnabled(False)
def statistics(self):
"""Generate the statistics based on the selected filters."""
self.tableWidget.setRowCount(0)
active_semseter = self.semester
self.db_err_message.setText("")
self.btn_del_select_apparats.setEnabled(False)
self.btn_notify_for_deletion.setEnabled(False)
params = {
"appnr": (
self.box_appnrs.currentText()
if self.box_appnrs.currentText() != ""
else None
),
"prof_id": (
self.db.getProfId(Prof(fullname=self.box_person.currentText()))
if self.box_person.currentText() != ""
else None
),
"fach": (
self.box_fach.currentText()
if self.box_fach.currentText() != ""
else None
),
"erstellsemester": (
self.box_erstellsemester.currentText()
if self.box_erstellsemester.currentText() != ""
else None
),
"dauer": (
"1"
if self.box_dauerapp.currentText() == "Ja"
else "0"
if self.box_dauerapp.currentText() == "Nein"
else None
),
"endsemester": (
self.box_semester.currentText()
if self.box_semester.currentText() != ""
else None
),
"deletable": "True" if self.check_deletable.isChecked() else None,
"deletesemester": active_semseter,
}
params = {
key: value for key, value in params.items() if value is not None
} # remove empty lines to prevent errors
params = {
key: value for key, value in params.items() if value != "Alle"
} # remove empty lines to prevent errors
result = self.db.statistic_request(**params)
# add QTableWidgetItems to the table
# self.tableWidget.setRowCount(len(result))
if len(result) == 0:
self.db_err_message.setText("Keine Ergebnisse gefunden")
return
data = []
for entry in result:
data.append(entry)
self.tableWidget.setRowCount(len(data))
if len(data) > 0:
self.btn_del_select_apparats.setEnabled(True)
self.btn_notify_for_deletion.setEnabled(True)
for i in range(len(data)):
# set the items 0 = clickable checkbox, 1 = appname, 2 = profname, 3 = fach
self.tableWidget.setItem(i, 0, QtWidgets.QTableWidgetItem(""))
self.tableWidget.setItem(i, 1, QtWidgets.QTableWidgetItem(data[i][1]))
# set tooltip for the apparat name
self.tableWidget.item(i, 1).setToolTip(data[i][1])
self.tableWidget.setItem(i, 2, QtWidgets.QTableWidgetItem(str(data[i][4])))
self.tableWidget.setItem(i, 3, QtWidgets.QTableWidgetItem(data[i][2]))
self.tableWidget.setItem(i, 4, QtWidgets.QTableWidgetItem(data[i][3]))
# replace the 0 with a checkbox
checkbox = QtWidgets.QCheckBox()
checkbox.setChecked(False)
self.tableWidget.setCellWidget(i, 0, checkbox)
# if i[9] is 1, set the background of the row to red
if int(data[i][9]) == 1:
for j in range(5):
self.tableWidget.item(i, j).setBackground(QtGui.QColor(235, 74, 71))
# disable the checkbox
self.tableWidget.cellWidget(i, 0).setEnabled(False)
# set the tooltip
self.tableWidget.cellWidget(i, 0).setToolTip(
"Dieser Semesterapparat kann nicht gelöscht werden, da er bereits gelöscht wurde"
)
# # remove empty rows
for row in range(self.tableWidget.rowCount()):
if self.tableWidget.item(row, 1) is None:
self.tableWidget.removeRow(row)
def display_detailed_data(self):
selected_semester = self.statistics_table.item(
self.statistics_table.currentRow(), 0
).text()
# get all apparats from the selected semester
data = self.db.getApparatsBySemester(selected_semester)
# replace keys for german names
# split to two lists
created = {"Erstellt": data["created"]}
deleted = {"Gelöscht": data["deleted"]}
created_status = StatusWidget(created, selected_semester)
deleted_status = StatusWidget(deleted, selected_semester)
created_status.setSizePolicy(
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Expanding,
)
deleted_status.setSizePolicy(
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Expanding,
)
# add them to the gridLayout_4
if self.dataLayout.count() > 0:
self.dataLayout.itemAt(0).widget().deleteLater()
self.dataLayout.itemAt(1).widget().deleteLater()
self.dataLayout.addWidget(created_status)
self.dataLayout.addWidget(deleted_status)
# self.setStatisticTableSize()
created_status.person_double_clicked.connect(self.open_apparat)
created_status.setToolTip("Doppelklick um den Semesterapparat zu öffnen")
deleted_status.setToolTip("Nur zur Übersicht")
# set deleted_status background to slightly gray
def open_apparat(self, *args):
header = "created"
# in *args
parent_depth = args[2]
apparat = args[1]
if header == "deleted" and parent_depth == 2:
log.warning("Semesterapparat wurde bereits gelöscht")
QtWidgets.QMessageBox.information(
self,
"Information",
"Der Semesterapparat wurde bereits gelöscht und kann nicht angezeigt werden.",
)
if parent_depth == 1:
# person selected case - open all apparats from this person in the tableWidget
self.tableWidget.setRowCount(0)
name = apparat.split("(")[0].strip()
prof_id = self.db.getProfId({"profname": name})
apparats = self.db.getApparatsByProf(prof_id)
for app in apparats:
# set the items 0 = clickable checkbox, 1 = appname, 2 = profname, 3 = fach
# insert new row
self.tableWidget.insertRow(0)
self.tableWidget.setItem(0, 0, QtWidgets.QTableWidgetItem(""))
self.tableWidget.setItem(0, 1, QtWidgets.QTableWidgetItem(app.name))
self.tableWidget.setItem(
0, 2, QtWidgets.QTableWidgetItem(str(app.appnr))
)
self.tableWidget.setItem(0, 3, QtWidgets.QTableWidgetItem(name))
self.tableWidget.setItem(0, 4, QtWidgets.QTableWidgetItem(app.subject))
# replace the 0 with a checkbox
checkbox = QtWidgets.QCheckBox()
checkbox.setChecked(False)
self.tableWidget.setCellWidget(0, 0, checkbox)
# if i[9] is 1, set the background of the row to red
if int(app.deleted) == 1:
for j in range(5):
self.tableWidget.item(0, j).setBackground(
QtGui.QColor(235, 74, 71)
)
# disable the checkbox
self.tableWidget.cellWidget(0, 0).setEnabled(False)
# set the tooltip
self.tableWidget.cellWidget(0, 0).setToolTip(
"Dieser Semesterapparat kann nicht gelöscht werden, da er bereits gelöscht wurde"
)
elif parent_depth == 2:
# ##print("depth", parent_depth)
# apparat selected case - open the apparat in the frame
self.apparat_open.emit(apparat)
return
def emit_signal(self, *args):
# ##print("emit_signal", *args)
self.apparat_open.emit(args[1])
def show_ui():
app = QtWidgets.QApplication([])
window = SearchStatisticPage()
window.show()
app.exec()