Inhalt
Aktueller Ordner:
duesseldorfer-schuelerinventar-python-clientduesk_client_text.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Düsseldorfer Schülerinventar (DÜSK) - Python Client
GUI mit reiner Textdarstellung (keine Diagramme)
"""
import sys
import subprocess
import requests
import math
from datetime import datetime
# GUI imports
try:
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QLabel, QLineEdit, QPushButton,
QTableWidget, QTableWidgetItem, QMessageBox,
QHeaderView, QDialog, QDialogButtonBox,
QFormLayout, QProgressDialog, QGroupBox,
QTabWidget, QComboBox, QScrollArea, QTextEdit,
QSplitter, QRadioButton, QButtonGroup,
QFileDialog, QToolBar)
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer
from PyQt6.QtGui import QFont, QAction
except ImportError:
print("Installiere PyQt6...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "PyQt6", "requests"])
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QLabel, QLineEdit, QPushButton,
QTableWidget, QTableWidgetItem, QMessageBox,
QHeaderView, QDialog, QDialogButtonBox,
QFormLayout, QProgressDialog, QGroupBox,
QTabWidget, QComboBox, QScrollArea, QTextEdit,
QSplitter, QRadioButton, QButtonGroup,
QFileDialog, QToolBar)
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer
from PyQt6.QtGui import QFont, QAction
API_BASE_URL = "https://paul-koop.org/api/"
# ============ NORMWERTE AUS IHRER DATENBANK ============
NORM_SE_HS = {1: [21.33, 25.33, 29.33, 33.32, 37.32], 2: [20.87, 24.95, 29.03, 33.13, 37.18],
3: [17.93, 21.37, 24.80, 28.23, 31.67], 4: [13.98, 17.71, 21.44, 25.17, 28.90],
5: [24.60, 28.55, 33.04, 37.53, 42.01], 6: [15.53, 18.97, 22.40, 25.83, 29.27]}
NORM_FE_HS = {1: [12.66, 18.16, 23.66, 29.16, 34.66], 2: [13.33, 18.42, 23.51, 28.60, 33.69],
3: [10.75, 15.41, 20.07, 24.73, 29.39], 4: [14.22, 15.30, 16.38, 17.46, 18.54],
5: [14.12, 20.21, 26.30, 32.39, 38.48], 6: [10.53, 14.51, 18.49, 22.47, 26.45]}
NORM_SE_FS = {1: [17.54, 24.03, 30.53, 37.02, 43.51], 2: [17.80, 24.26, 30.73, 37.19, 43.65],
3: [18.03, 22.41, 26.79, 31.17, 35.55], 4: [14.28, 15.55, 16.83, 18.10, 19.37],
5: [20.69, 27.49, 34.29, 41.09, 47.89], 6: [12.44, 18.06, 23.68, 29.29, 34.91]}
NORM_FE_FS = {1: [15.30, 19.79, 24.28, 28.77, 33.26], 2: [14.63, 18.94, 23.25, 27.56, 31.87],
3: [14.62, 17.81, 21.00, 24.19, 27.38], 4: [15.00, 15.55, 16.10, 16.65, 17.20],
5: [18.44, 22.61, 26.78, 30.95, 35.12], 6: [9.79, 13.97, 18.15, 22.33, 26.51]}
class APIWorker(QThread):
finished = pyqtSignal(object)
error = pyqtSignal(str)
def __init__(self, endpoint, method="GET", data=None, params=None, user_id=None, session=None):
super().__init__()
self.endpoint = endpoint
self.method = method
self.data = data
self.params = params
self.user_id = user_id
self.session = session
def run(self):
try:
url = API_BASE_URL + self.endpoint
headers = {'Content-Type': 'application/json'}
if self.user_id and self.session:
headers['X-User-ID'] = str(self.user_id)
headers['X-Session'] = self.session
if self.method == "GET":
response = requests.get(url, headers=headers, params=self.params, timeout=30)
elif self.method == "POST":
response = requests.post(url, json=self.data, headers=headers, timeout=30)
elif self.method == "PUT":
response = requests.put(url, json=self.data, headers=headers, timeout=30)
elif self.method == "DELETE":
response = requests.delete(url, headers=headers, params=self.params, timeout=30)
else:
self.error.emit(f"Unbekannte Methode: {self.method}")
return
if response.status_code == 200:
self.finished.emit(response.json())
else:
self.error.emit(f"HTTP {response.status_code}")
except Exception as e:
self.error.emit(str(e))
class ProfileViewDialog(QDialog):
"""Profilansicht mit reinen Text-Tabellen (keine Diagramme)"""
KOMPETENZEN = ["Arbeitsverhalten", "Lernverhalten", "Sozialverhalten",
"Fachkompetenz", "Personale Kompetenz", "Methodenkompetenz"]
def __init__(self, profile_data, parent=None):
super().__init__(parent)
self.profile = profile_data
self.current_norm = "HS"
self.init_ui()
self.calculate()
def init_ui(self):
self.setWindowTitle(f"Profil: {self.profile.get('name', 'Unbekannt')}")
self.setMinimumSize(900, 700)
layout = QVBoxLayout()
# Kopfzeile
header = QHBoxLayout()
header.addWidget(QLabel(f"<h2>{self.profile.get('name', 'Unbekannt')}</h2>"))
header.addStretch()
header.addWidget(QLabel(f"<b>Profil-ID:</b> {self.profile.get('profilID', '?')}"))
layout.addLayout(header)
# Normauswahl
norm_row = QHBoxLayout()
norm_row.addWidget(QLabel("Normtabelle:"))
self.norm_combo = QComboBox()
self.norm_combo.addItem("Hauptschule (HS)", "HS")
self.norm_combo.addItem("Förderschule (FS)", "FS")
self.norm_combo.currentIndexChanged.connect(self.on_norm_change)
norm_row.addWidget(self.norm_combo)
norm_row.addStretch()
layout.addLayout(norm_row)
# Tabellen nebeneinander
tables_widget = QWidget()
tables_layout = QHBoxLayout(tables_widget)
# Selbsteinschätzung
self.se_table = self.create_competence_table("Selbsteinschätzung (SE)")
tables_layout.addWidget(self.se_table)
# Fremdeinschätzung
self.fe_table = self.create_competence_table("Fremdeinschätzung (FE)")
tables_layout.addWidget(self.fe_table)
layout.addWidget(tables_widget)
# Statistik
stats_group = QGroupBox("Statistische Auswertung")
stats_layout = QVBoxLayout()
self.corr_label = QLabel()
self.agree_label = QLabel()
self.analysis = QTextEdit()
self.analysis.setReadOnly(True)
self.analysis.setMaximumHeight(120)
stats_layout.addWidget(self.corr_label)
stats_layout.addWidget(self.agree_label)
stats_layout.addWidget(self.analysis)
stats_group.setLayout(stats_layout)
layout.addWidget(stats_group)
# Alle Items (36) in scrollbarer Tabelle
items_group = QGroupBox("Item-Werte (alle 36 Items)")
items_layout = QVBoxLayout()
scroll = QScrollArea()
scroll.setWidgetResizable(True)
self.items_table = QTableWidget(36, 3)
self.items_table.setHorizontalHeaderLabels(["Item", "Selbst", "Fremd"])
self.items_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
scroll.setWidget(self.items_table)
items_layout.addWidget(scroll)
items_group.setLayout(items_layout)
layout.addWidget(items_group)
# Schließen
close_btn = QPushButton("Schließen")
close_btn.setStyleSheet("background-color: #6699CC; color: white; padding: 8px;")
close_btn.clicked.connect(self.accept)
layout.addWidget(close_btn)
self.setLayout(layout)
def create_competence_table(self, title):
widget = QWidget()
layout = QVBoxLayout(widget)
layout.addWidget(QLabel(f"<b>{title}</b>", alignment=Qt.AlignmentFlag.AlignCenter))
table = QTableWidget(6, 5)
table.setHorizontalHeaderLabels(["1", "2", "3", "4", "5"])
table.setVerticalHeaderLabels(self.KOMPETENZEN)
table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
layout.addWidget(table)
if title == "Selbsteinschätzung (SE)":
self.se_table = table
else:
self.fe_table = table
return widget
def on_norm_change(self):
self.current_norm = self.norm_combo.currentData()
self.calculate()
def calculate_sums(self, items):
sums = [0] * 7
sums[1] = sum(items[0:10])
sums[2] = sum(items[10:20])
sums[3] = sum(items[20:28]) + items[8] + items[9]
sums[4] = sum(items[28:36])
sums[5] = items[0] + items[1] + items[5] + items[6] + items[7] + items[8] + items[9] + items[11] + items[12] + items[13] + items[14]
sums[6] = items[2] + items[3] + items[4] + items[8] + items[9] + items[10] + items[16] + items[17]
return sums
def get_profile_values(self, sums, norm):
values = [0] * 6
for k in range(1, 7):
placed = False
for p in range(5):
if sums[k] < norm[k][p]:
values[k-1] = p
placed = True
break
if not placed:
values[k-1] = 4
return values
def fill_table(self, table, values):
for i, v in enumerate(values):
for j in range(5):
table.setItem(i, j, QTableWidgetItem("X" if j == v else ""))
table.item(i, j).setTextAlignment(Qt.AlignmentFlag.AlignCenter)
table.resizeColumnsToContents()
def calculate(self):
# Items auslesen
se_items = [int(self.profile.get(f'item{i}', 2)) for i in range(1, 37)]
fe_items = [int(self.profile.get(f'feitem{i}', 2)) for i in range(1, 37)]
# Summen
se_sums = self.calculate_sums(se_items)
fe_sums = self.calculate_sums(fe_items)
# Normen
if self.current_norm == "HS":
norm_se, norm_fe = NORM_SE_HS, NORM_FE_HS
else:
norm_se, norm_fe = NORM_SE_FS, NORM_FE_FS
# Profile (0-4)
se_prof = self.get_profile_values(se_sums, norm_se)
fe_prof = self.get_profile_values(fe_sums, norm_fe)
# Tabellen füllen
self.fill_table(self.se_table, se_prof)
self.fill_table(self.fe_table, fe_prof)
# Korrelation (Werte 1-5 für Korrelation)
se_vals = [v + 1 for v in se_prof]
fe_vals = [v + 1 for v in fe_prof]
corr = self.calc_correlation(se_vals, fe_vals)
agree = sum(1 for s, f in zip(se_items, fe_items) if s == f) * 100 / 36
self.corr_label.setText(f"<b>Korrelation:</b> {corr:.2f}")
self.agree_label.setText(f"<b>Übereinstimmung:</b> {agree:.1f}%")
# Auswertung
ratings = ["weit unterdurchschnittlich", "unterdurchschnittlich", "durchschnittlich",
"überdurchschnittlich", "weit überdurchschnittlich"]
text = "Selbsteinschätzung:\n"
for i, v in enumerate(se_vals):
text += f" • {self.KOMPETENZEN[i]}: {ratings[v-1]}\n"
text += "\nFremdeinschätzung:\n"
for i, v in enumerate(fe_vals):
text += f" • {self.KOMPETENZEN[i]}: {ratings[v-1]}\n"
text += f"\nInterpretation (Korrelation {corr:.2f}): "
if corr >= 0.8:
text += "Sehr gute Übereinstimmung zwischen Selbst- und Fremdeinschätzung."
elif corr >= 0.5:
text += "Mäßige Übereinstimmung zwischen Selbst- und Fremdeinschätzung."
elif corr >= 0.3:
text += "Schwache Übereinstimmung zwischen Selbst- und Fremdeinschätzung."
else:
text += "Keine signifikante Übereinstimmung zwischen Selbst- und Fremdeinschätzung."
self.analysis.setText(text)
# Item-Tabelle (alle 36)
names = [
"1. Zuverlässigkeit", "2. Arbeitstempo", "3. Arbeitsplanung", "4. Organisationsfähigkeit",
"5. Geschicklichkeit", "6. Ordnung", "7. Sorgfalt", "8. Kreativität",
"9. Problemlösungsfähigkeit", "10. Abstraktionsvermögen", "11. Selbstständigkeit",
"12. Belastbarkeit", "13. Konzentrationsfähigkeit", "14. Verantwortungsbewusstsein",
"15. Eigeninitiative", "16. Leistungsbereitschaft", "17. Auffassungsgabe",
"18. Merkfähigkeit", "19. Motivationsfähigkeit", "20. Reflektionsfähigkeit",
"21. Teamfähigkeit", "22. Hilfsbereitschaft", "23. Kontaktfähigkeit",
"24. Respektvoller Umgang", "25. Kommunikationsfähigkeit", "26. Einfühlungsvermögen",
"27. Konfliktfähigkeit", "28. Kritikfähigkeit", "29. Schreiben", "30. Lesen",
"31. Mathematik", "32. Naturwissenschaft", "33. Fremdsprachen",
"34. Präsentationsfähigkeit", "35. PC Kenntnisse", "36. Fächerübergreifendes Denken"
]
for i, name in enumerate(names):
self.items_table.setItem(i, 0, QTableWidgetItem(name))
self.items_table.setItem(i, 1, QTableWidgetItem(str(se_items[i])))
self.items_table.setItem(i, 2, QTableWidgetItem(str(fe_items[i])))
def calc_correlation(self, a, b):
ma, mb = sum(a)/6, sum(b)/6
num = sum((a[i]-ma)*(b[i]-mb) for i in range(6))
den = math.sqrt(sum((a[i]-ma)**2 for i in range(6)) * sum((b[i]-mb)**2 for i in range(6)))
return num/den if den != 0 else 0
class ProfileEditDialog(QDialog):
"""Dialog zum Erstellen/Bearbeiten eines Profils"""
ITEMS = [
"Zuverlässigkeit", "Arbeitstempo", "Arbeitsplanung", "Organisationsfähigkeit",
"Geschicklichkeit", "Ordnung", "Sorgfalt", "Kreativität", "Problemlösungsfähigkeit",
"Abstraktionsvermögen", "Selbstständigkeit", "Belastbarkeit", "Konzentrationsfähigkeit",
"Verantwortungsbewusstsein", "Eigeninitiative", "Leistungsbereitschaft", "Auffassungsgabe",
"Merkfähigkeit", "Motivationsfähigkeit", "Reflektionsfähigkeit", "Teamfähigkeit",
"Hilfsbereitschaft", "Kontaktfähigkeit", "Respektvoller Umgang", "Kommunikationsfähigkeit",
"Einfühlungsvermögen", "Konfliktfähigkeit", "Kritikfähigkeit", "Schreiben", "Lesen",
"Mathematik", "Naturwissenschaft", "Fremdsprachen", "Präsentationsfähigkeit",
"PC Kenntnisse", "Fächerübergreifendes Denken"
]
def __init__(self, user_id, session, profile=None, groups=None, parent=None):
super().__init__(parent)
self.user_id, self.session = user_id, session
self.profile = profile or {}
self.is_edit = profile is not None
self.groups = groups or []
self.se_btns, self.fe_btns = {}, {}
self.init_ui()
def init_ui(self):
self.setWindowTitle("Profil bearbeiten" if self.is_edit else "Neues Profil")
self.setMinimumSize(800, 600)
layout = QVBoxLayout()
# Basisinformationen
info_group = QGroupBox("Profil-Informationen")
info_layout = QFormLayout()
self.name_edit = QLineEdit()
self.name_edit.setText(self.profile.get('name', ''))
info_layout.addRow("Name:", self.name_edit)
self.group_combo = QComboBox()
if self.groups:
for g in self.groups:
self.group_combo.addItem(g.get('name', ''), g.get('gruppeID'))
else:
self.group_combo.addItem("Keine Gruppen", None)
if self.profile.get('gruppeID'):
idx = self.group_combo.findData(self.profile.get('gruppeID'))
if idx >= 0:
self.group_combo.setCurrentIndex(idx)
info_layout.addRow("Gruppe:", self.group_combo)
self.new_group = QLineEdit()
self.new_group.setPlaceholderText("Neue Gruppe (optional)")
info_layout.addRow("Neue Gruppe:", self.new_group)
info_group.setLayout(info_layout)
layout.addWidget(info_group)
# Tabs für SE und FE
tabs = QTabWidget()
se_widget = self.create_item_tab("Selbsteinschätzung", "se")
tabs.addTab(se_widget, "Selbsteinschätzung")
fe_widget = self.create_item_tab("Fremdeinschätzung", "fe")
tabs.addTab(fe_widget, "Fremdeinschätzung")
layout.addWidget(tabs)
# Buttons
btns = QHBoxLayout()
save = QPushButton("Speichern")
save.setStyleSheet("background-color: #4CAF50; color: white; padding: 8px;")
save.clicked.connect(self.save)
cancel = QPushButton("Abbrechen")
cancel.clicked.connect(self.reject)
btns.addStretch()
btns.addWidget(save)
btns.addWidget(cancel)
layout.addLayout(btns)
self.setLayout(layout)
self.load_values()
def create_item_tab(self, title, prefix):
scroll = QScrollArea()
scroll.setWidgetResizable(True)
container = QWidget()
layout = QVBoxLayout(container)
btns = self.se_btns if prefix == "se" else self.fe_btns
for i, name in enumerate(self.ITEMS, 1):
group = QGroupBox(f"{i:2d}. {name}")
hbox = QHBoxLayout()
bg = QButtonGroup()
for val, label in [(4, "trifft voll zu (4)"), (3, "trifft zu (3)"),
(2, "trifft teilweise zu (2)"), (1, "trifft nicht zu (1)")]:
rb = QRadioButton(label)
hbox.addWidget(rb)
bg.addButton(rb, val)
group.setLayout(hbox)
layout.addWidget(group)
btns[i] = bg
scroll.setWidget(container)
return scroll
def load_values(self):
for i in range(1, 37):
if i in self.se_btns:
val = self.profile.get(f'item{i}', 2)
if btn := self.se_btns[i].button(val):
btn.setChecked(True)
if i in self.fe_btns:
val = self.profile.get(f'feitem{i}', 2)
if btn := self.fe_btns[i].button(val):
btn.setChecked(True)
def save(self):
name = self.name_edit.text().strip()
if not name:
QMessageBox.warning(self, "Fehler", "Bitte Name eingeben")
return
data = {'name': name}
if self.group_combo.currentData():
data['gruppeID'] = self.group_combo.currentData()
if ng := self.new_group.text().strip():
data['namegruppe'] = ng
for i in range(1, 37):
data[f'item{i}'] = self.se_btns[i].checkedId() if i in self.se_btns and self.se_btns[i].checkedId() != -1 else 2
data[f'feitem{i}'] = self.fe_btns[i].checkedId() if i in self.fe_btns and self.fe_btns[i].checkedId() != -1 else 2
if self.is_edit:
data['profilID'] = self.profile.get('profilID')
worker = APIWorker("api_profiles.php", "PUT", data, user_id=self.user_id, session=self.session)
else:
worker = APIWorker("api_profiles.php", "POST", data, user_id=self.user_id, session=self.session)
worker.finished.connect(self.save_done)
worker.error.connect(lambda e: QMessageBox.critical(self, "Fehler", e))
worker.start()
self.progress = QProgressDialog("Speichern...", None, 0, 0, self)
self.progress.show()
def save_done(self, resp):
self.progress.close()
if resp.get('success'):
QMessageBox.information(self, "Erfolg", "Profil gespeichert")
self.accept()
else:
QMessageBox.critical(self, "Fehler", resp.get('error', 'Unbekannter Fehler'))
class LoginDialog(QDialog):
def __init__(self):
super().__init__()
self.user_id = self.session = None
self.setWindowTitle("DÜSK - Anmeldung")
self.setModal(True)
self.setMinimumSize(350, 200)
layout = QVBoxLayout()
layout.addWidget(QLabel("DÜSK - Düsseldorfer Schülerinventar",
alignment=Qt.AlignmentFlag.AlignCenter,
font=QFont("Arial", 16, QFont.Weight.Bold)))
form = QFormLayout()
self.user_edit = QLineEdit()
self.user_edit.setText("gast")
self.pw_edit = QLineEdit()
self.pw_edit.setText("gast")
self.pw_edit.setEchoMode(QLineEdit.EchoMode.Password)
form.addRow("Benutzername:", self.user_edit)
form.addRow("Passwort:", self.pw_edit)
layout.addLayout(form)
btns = QDialogButtonBox()
login = btns.addButton("Anmelden", QDialogButtonBox.ButtonRole.AcceptRole)
cancel = btns.addButton("Abbrechen", QDialogButtonBox.ButtonRole.RejectRole)
login.clicked.connect(self.do_login)
cancel.clicked.connect(self.reject)
layout.addWidget(btns)
layout.addWidget(QLabel("Server: paul-koop.org\nBenutzung mit gast/gast möglich",
alignment=Qt.AlignmentFlag.AlignCenter,
styleSheet="color: gray; font-size: 10px;"))
self.setLayout(layout)
def do_login(self):
self.login_btn = self.findChild(QDialogButtonBox).buttons()[0]
self.login_btn.setEnabled(False)
worker = APIWorker("api_login.php", "POST",
{"username": self.user_edit.text().strip(),
"password": self.pw_edit.text().strip()})
worker.finished.connect(self.on_success)
worker.error.connect(self.on_error)
worker.start()
self.progress = QProgressDialog("Anmeldung...", None, 0, 0, self)
self.progress.show()
def on_success(self, resp):
self.progress.close()
if resp.get('success') or resp.get('userID'):
self.user_id = resp['userID']
self.session = resp['session']
self.accept()
else:
self.login_btn.setEnabled(True)
QMessageBox.warning(self, "Fehler", "Anmeldung fehlgeschlagen")
def on_error(self, error):
self.progress.close()
self.login_btn.setEnabled(True)
QMessageBox.critical(self, "Fehler", f"Verbindungsfehler: {error}")
class MainWindow(QMainWindow):
def __init__(self, user_id, session):
super().__init__()
self.user_id, self.session = user_id, session
self.profiles = []
self.groups = []
self.setWindowTitle("DÜSK - Düsseldorfer Schülerinventar")
self.setMinimumSize(1000, 500)
central = QWidget()
self.setCentralWidget(central)
layout = QVBoxLayout(central)
# Header
header = QLabel("DÜSK - Düsseldorfer Schülerinventar")
header.setAlignment(Qt.AlignmentFlag.AlignCenter)
header.setStyleSheet("font-size: 24px; font-weight: bold; background-color: #6699CC; color: white; padding: 15px;")
layout.addWidget(header)
# Toolbar
toolbar = QToolBar()
new_btn = QPushButton("Neues Profil")
new_btn.clicked.connect(self.new_profile)
toolbar.addWidget(new_btn)
refresh_btn = QPushButton("Aktualisieren")
refresh_btn.clicked.connect(self.load_profiles)
toolbar.addWidget(refresh_btn)
groups_btn = QPushButton("Gruppen verwalten")
groups_btn.clicked.connect(self.manage_groups)
toolbar.addWidget(groups_btn)
toolbar.addSeparator()
logout_btn = QPushButton("Abmelden")
logout_btn.setStyleSheet("background-color: #f44336; color: white;")
logout_btn.clicked.connect(self.logout)
toolbar.addWidget(logout_btn)
self.addToolBar(toolbar)
# Tabelle
self.table = QTableWidget()
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(["Name", "Gruppe", "ProfilID", "Aktion"])
self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
layout.addWidget(self.table)
# Footer
footer = QLabel("Paul Koop M.A. - Thomashofstrasse 19, 52070 Aachen")
footer.setAlignment(Qt.AlignmentFlag.AlignCenter)
footer.setStyleSheet("background-color: #6699CC; color: white; padding: 10px; margin-top: 10px;")
layout.addWidget(footer)
# Gruppen laden (asynchron, aber ohne das Fenster zu blockieren)
self.load_groups()
self.load_profiles()
def load_groups(self):
worker = APIWorker("api_groups.php", "GET", user_id=self.user_id, session=self.session)
worker.finished.connect(self.groups_loaded)
worker.error.connect(lambda e: print(f"Fehler beim Laden der Gruppen: {e}"))
worker.start()
def groups_loaded(self, resp):
if isinstance(resp, list):
self.groups = resp
print(f"Gruppen geladen: {len(self.groups)}")
def load_profiles(self):
worker = APIWorker("api_profiles.php", "GET", user_id=self.user_id, session=self.session)
worker.finished.connect(self.on_profiles)
worker.error.connect(lambda e: QMessageBox.critical(self, "Fehler", e))
worker.start()
def on_profiles(self, resp):
if isinstance(resp, list):
self.profiles = resp
self.table.setRowCount(len(resp))
for i, p in enumerate(resp):
self.table.setItem(i, 0, QTableWidgetItem(p.get('name', '')))
self.table.setItem(i, 1, QTableWidgetItem(p.get('gruppename', '')))
self.table.setItem(i, 2, QTableWidgetItem(str(p.get('profilID', ''))))
actions = QWidget()
hbox = QHBoxLayout(actions)
hbox.setContentsMargins(0, 0, 0, 0)
view = QPushButton("Anzeigen")
view.setStyleSheet("background-color: #4CAF50; color: white;")
view.clicked.connect(lambda _, pid=p.get('profilID'): self.view_profile(pid))
hbox.addWidget(view)
edit = QPushButton("Bearbeiten")
edit.clicked.connect(lambda _, pid=p.get('profilID'): self.edit_profile(pid))
hbox.addWidget(edit)
delete = QPushButton("Löschen")
delete.setStyleSheet("background-color: #f44336; color: white;")
delete.clicked.connect(lambda _, pid=p.get('profilID'): self.delete_profile(pid))
hbox.addWidget(delete)
self.table.setCellWidget(i, 3, actions)
self.table.resizeRowsToContents()
def view_profile(self, pid):
worker = APIWorker("api_profiles.php", "GET", params={"id": pid},
user_id=self.user_id, session=self.session)
worker.finished.connect(lambda r: ProfileViewDialog(r).exec() if isinstance(r, dict) else None)
worker.error.connect(lambda e: QMessageBox.critical(self, "Fehler", e))
worker.start()
def edit_profile(self, pid):
worker = APIWorker("api_profiles.php", "GET", params={"id": pid},
user_id=self.user_id, session=self.session)
worker.finished.connect(lambda r: self.open_editor(r))
worker.start()
def open_editor(self, profile):
if isinstance(profile, dict):
dlg = ProfileEditDialog(self.user_id, self.session, profile, self.groups)
if dlg.exec():
self.load_profiles()
def new_profile(self):
dlg = ProfileEditDialog(self.user_id, self.session, groups=self.groups)
if dlg.exec():
self.load_profiles()
def delete_profile(self, pid):
if QMessageBox.question(self, "Löschen", "Profil wirklich löschen?") == QMessageBox.StandardButton.Yes:
worker = APIWorker("api_profiles.php", "DELETE", params={"id": pid},
user_id=self.user_id, session=self.session)
worker.finished.connect(lambda r: self.load_profiles())
worker.start()
def manage_groups(self):
dlg = GroupManagerDialog(self.user_id, self.session, self.groups)
if dlg.exec():
self.load_groups()
self.load_profiles()
def logout(self):
APIWorker("api_logout.php", "POST", user_id=self.user_id, session=self.session).start()
self.close()
class GroupManagerDialog(QDialog):
def __init__(self, user_id, session, groups, parent=None):
super().__init__(parent)
self.user_id, self.session = user_id, session
self.groups = groups
self.init_ui()
def init_ui(self):
self.setWindowTitle("Gruppenverwaltung")
self.setMinimumSize(400, 300)
layout = QVBoxLayout()
self.group_list = QTableWidget()
self.group_list.setColumnCount(2)
self.group_list.setHorizontalHeaderLabels(["Gruppenname", "Aktion"])
self.group_list.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
self.refresh_list()
layout.addWidget(self.group_list)
add_layout = QHBoxLayout()
self.new_name = QLineEdit()
self.new_name.setPlaceholderText("Neuer Gruppenname")
add_btn = QPushButton("Hinzufügen")
add_btn.clicked.connect(self.add_group)
add_layout.addWidget(self.new_name)
add_layout.addWidget(add_btn)
layout.addLayout(add_layout)
close_btn = QPushButton("Schließen")
close_btn.clicked.connect(self.accept)
layout.addWidget(close_btn)
self.setLayout(layout)
def refresh_list(self):
self.group_list.setRowCount(len(self.groups))
for i, g in enumerate(self.groups):
self.group_list.setItem(i, 0, QTableWidgetItem(g.get('name', '')))
del_btn = QPushButton("Löschen")
del_btn.clicked.connect(lambda _, gid=g.get('gruppeID'): self.delete_group(gid))
self.group_list.setCellWidget(i, 1, del_btn)
def add_group(self):
name = self.new_name.text().strip()
if not name:
QMessageBox.warning(self, "Fehler", "Bitte Namen eingeben")
return
worker = APIWorker("api_groups.php", "POST", data={"name": name},
user_id=self.user_id, session=self.session)
worker.finished.connect(self.on_added)
worker.start()
def on_added(self, resp):
if resp.get('success'):
self.new_name.clear()
self.load_groups()
def delete_group(self, group_id):
if QMessageBox.question(self, "Löschen", "Gruppe wirklich löschen?") == QMessageBox.StandardButton.Yes:
worker = APIWorker("api_groups.php", "DELETE", params={"id": group_id},
user_id=self.user_id, session=self.session)
worker.finished.connect(lambda r: self.load_groups())
worker.start()
def load_groups(self):
worker = APIWorker("api_groups.php", "GET", user_id=self.user_id, session=self.session)
worker.finished.connect(self.on_loaded)
worker.start()
def on_loaded(self, resp):
if isinstance(resp, list):
self.groups = resp
self.refresh_list()
def main():
print("Starte DÜSK Client...")
app = QApplication(sys.argv)
login = LoginDialog()
if login.exec() == QDialog.DialogCode.Accepted:
print(f"Login erfolgreich - UserID: {login.user_id}")
win = MainWindow(login.user_id, login.session)
win.show()
sys.exit(app.exec())
else:
print("Login abgebrochen")
sys.exit(0)
if __name__ == "__main__":
main()