Inhalt
Aktueller Ordner:
duesseldorfer-schuelerinventar-python-clientduesk_client.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Düsseldorfer Schülerinventar (DÜSK) - Python Client
Mit exakter PHP-Berechnungslogik - KORRIGIERT
"""
import sys
import subprocess
import requests
import math
# 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)
from PyQt6.QtCore import Qt, QThread, pyqtSignal
from PyQt6.QtGui import QFont
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)
from PyQt6.QtCore import Qt, QThread, pyqtSignal
from PyQt6.QtGui import QFont
API_BASE_URL = "https://paul-koop.org/api/"
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)
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 Fehler {response.status_code}")
except Exception as e:
self.error.emit(str(e))
class ProfileViewDialog(QDialog):
"""Profilansicht mit exakter PHP-Berechnungslogik"""
# Normwerte aus Ihrer Datenbank (normSEhs)
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]
}
# Normwerte Förderschule (normSEfs)
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]
}
def __init__(self, profile_data, parent=None):
super().__init__(parent)
self.profile_data = profile_data
self.current_norm = "HS" # HS oder FS
self.init_ui()
self.calculate_and_display()
def init_ui(self):
self.setWindowTitle(f"Profil: {self.profile_data.get('name', 'Unbekannt')}")
self.setMinimumSize(1000, 700)
layout = QVBoxLayout()
# Name
name_label = QLabel(f"<h2>{self.profile_data.get('name', 'Unbekannt')}</h2>")
name_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(name_label)
# Norm-Auswahl
norm_layout = QHBoxLayout()
norm_label = 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_changed)
norm_layout.addWidget(norm_label)
norm_layout.addWidget(self.norm_combo)
norm_layout.addStretch()
layout.addLayout(norm_layout)
# Hauptinhalt mit zwei Spalten
main_widget = QWidget()
main_layout = QHBoxLayout(main_widget)
# Selbsteinschätzung
se_widget = QWidget()
se_layout = QVBoxLayout(se_widget)
se_title = QLabel("<b>Selbsteinschätzung</b>")
se_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
se_layout.addWidget(se_title)
self.se_table = QTableWidget(6, 5)
self.se_table.setHorizontalHeaderLabels(["1", "2", "3", "4", "5"])
self.se_table.setVerticalHeaderLabels([
"Arbeitsverhalten", "Lernverhalten", "Sozialverhalten",
"Fachkompetenz", "Personale Kompetenz", "Methodenkompetenz"
])
self.se_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
se_layout.addWidget(self.se_table)
# Fremdeinschätzung
fe_widget = QWidget()
fe_layout = QVBoxLayout(fe_widget)
fe_title = QLabel("<b>Fremdeinschätzung</b>")
fe_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
fe_layout.addWidget(fe_title)
self.fe_table = QTableWidget(6, 5)
self.fe_table.setHorizontalHeaderLabels(["1", "2", "3", "4", "5"])
self.fe_table.setVerticalHeaderLabels([
"Arbeitsverhalten", "Lernverhalten", "Sozialverhalten",
"Fachkompetenz", "Personale Kompetenz", "Methodenkompetenz"
])
self.fe_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
fe_layout.addWidget(self.fe_table)
main_layout.addWidget(se_widget)
main_layout.addWidget(fe_widget)
layout.addWidget(main_widget)
# Item-Tabelle
items_group = QGroupBox("Item-Werte")
items_layout = QVBoxLayout()
self.items_table = QTableWidget(36, 3)
self.items_table.setHorizontalHeaderLabels(["Item", "Selbst", "Fremd"])
self.items_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
item_names = [
"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"
]
for i, name in enumerate(item_names, 1):
se_val = self.profile_data.get(f'item{i}', 0)
fe_val = self.profile_data.get(f'feitem{i}', 0)
self.items_table.setItem(i-1, 0, QTableWidgetItem(name))
self.items_table.setItem(i-1, 1, QTableWidgetItem(str(se_val)))
self.items_table.setItem(i-1, 2, QTableWidgetItem(str(fe_val)))
items_layout.addWidget(self.items_table)
items_group.setLayout(items_layout)
layout.addWidget(items_group)
# Schließen-Button
close_btn = QPushButton("Schließen")
close_btn.clicked.connect(self.accept)
layout.addWidget(close_btn)
self.setLayout(layout)
def on_norm_changed(self):
"""Wird aufgerufen, wenn die Normtabelle geändert wird"""
self.current_norm = self.norm_combo.currentData()
self.calculate_and_display()
def calculate_sums(self, items):
"""Exakte PHP-Berechnung der Summen"""
if len(items) < 36:
items.extend([2] * (36 - len(items)))
sums = [0] * 7 # Index 1-6
sums[1] = sum(items[0:10]) # item1-item10
sums[2] = sum(items[10:20]) # item11-item20
sums[3] = sum(items[20:28]) + items[8] + items[9] # item21-item28 + item9 + item10
sums[4] = sum(items[28:36]) # item29-item36
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 calculate_profile_values(self, sums, norm):
"""Exakte PHP-Berechnung der Profilwerte (wo das X gesetzt wird)"""
values = [0] * 6
for kompetenz in range(1, 7):
x_gesetzt = False
for punkt in range(5):
if sums[kompetenz] < norm[kompetenz][punkt]:
values[kompetenz - 1] = punkt
x_gesetzt = True
break
if not x_gesetzt:
values[kompetenz - 1] = 4
return values
def fill_table(self, table, values):
"""Füllt die Tabelle mit X an der richtigen Position"""
for i, value in enumerate(values):
for j in range(5):
if j == value:
table.setItem(i, j, QTableWidgetItem("X"))
else:
table.setItem(i, j, QTableWidgetItem(""))
table.item(i, j).setTextAlignment(Qt.AlignmentFlag.AlignCenter)
table.resizeColumnsToContents()
def calculate_and_display(self):
"""Hauptberechnung - exakt wie in PHP"""
# Items aus dem Profil extrahieren
se_items = []
fe_items = []
for i in range(1, 37):
se_val = self.profile_data.get(f'item{i}', 2)
fe_val = self.profile_data.get(f'feitem{i}', 2)
se_items.append(int(se_val) if se_val else 2)
fe_items.append(int(fe_val) if fe_val else 2)
# Summen berechnen
se_sums = self.calculate_sums(se_items)
fe_sums = self.calculate_sums(fe_items)
# Normen auswählen
if self.current_norm == "HS":
norm_se = self.NORM_SE_HS
norm_fe = self.NORM_FE_HS
else:
norm_se = self.NORM_SE_FS
norm_fe = self.NORM_FE_FS
# Profilwerte berechnen
se_values = self.calculate_profile_values(se_sums, norm_se)
fe_values = self.calculate_profile_values(fe_sums, norm_fe)
# Tabellen füllen
self.fill_table(self.se_table, se_values)
self.fill_table(self.fe_table, fe_values)
class LoginDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.user_id = None
self.session = None
self.init_ui()
def init_ui(self):
self.setWindowTitle("DÜSK - Anmeldung")
self.setModal(True)
self.setMinimumSize(300, 200)
layout = QVBoxLayout()
title = QLabel("DÜSK - Anmeldung")
title.setFont(QFont("Arial", 16, QFont.Weight.Bold))
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(title)
form_layout = QFormLayout()
self.username_input = QLineEdit()
self.username_input.setText("gast")
self.password_input = QLineEdit()
self.password_input.setText("gast")
self.password_input.setEchoMode(QLineEdit.EchoMode.Password)
form_layout.addRow("Benutzername:", self.username_input)
form_layout.addRow("Passwort:", self.password_input)
layout.addLayout(form_layout)
button_box = QDialogButtonBox()
login_btn = button_box.addButton("Anmelden", QDialogButtonBox.ButtonRole.AcceptRole)
cancel_btn = button_box.addButton("Abbrechen", QDialogButtonBox.ButtonRole.RejectRole)
login_btn.clicked.connect(self.do_login)
cancel_btn.clicked.connect(self.reject)
layout.addWidget(button_box)
self.setLayout(layout)
def do_login(self):
username = self.username_input.text().strip()
password = self.password_input.text().strip()
self.worker = APIWorker("api_login.php", "POST", {"username": username, "password": password})
self.worker.finished.connect(self.login_success)
self.worker.error.connect(self.login_error)
self.worker.start()
self.progress = QProgressDialog("Anmeldung...", None, 0, 0, self)
self.progress.setWindowModality(Qt.WindowModality.WindowModal)
self.progress.show()
def login_success(self, response):
self.progress.close()
if response.get('success') or response.get('userID'):
self.user_id = response['userID']
self.session = response['session']
self.accept()
else:
QMessageBox.warning(self, "Fehler", "Anmeldung fehlgeschlagen")
def login_error(self, error):
self.progress.close()
QMessageBox.critical(self, "Fehler", f"Verbindungsfehler: {error}")
class MainWindow(QMainWindow):
def __init__(self, user_id, session):
super().__init__()
self.user_id = user_id
self.session = session
self.init_ui()
self.load_profiles()
def init_ui(self):
self.setWindowTitle("DÜSK - Düsseldorfer Schülerinventar")
self.setMinimumSize(800, 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: 20px; font-weight: bold; background-color: #6699CC; color: white; padding: 10px;")
layout.addWidget(header)
# Refresh Button
refresh_btn = QPushButton("Aktualisieren")
refresh_btn.clicked.connect(self.load_profiles)
layout.addWidget(refresh_btn)
# Tabelle
self.table = QTableWidget()
self.table.setColumnCount(3)
self.table.setHorizontalHeaderLabels(["Name", "Gruppe", "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: 8px; margin-top: 10px;")
layout.addWidget(footer)
def load_profiles(self):
self.worker = APIWorker("api_profiles.php", "GET", user_id=self.user_id, session=self.session)
self.worker.finished.connect(self.profiles_loaded)
self.worker.error.connect(lambda e: QMessageBox.critical(self, "Fehler", f"Fehler: {e}"))
self.worker.start()
def profiles_loaded(self, response):
if isinstance(response, list):
self.table.setRowCount(len(response))
for i, p in enumerate(response):
self.table.setItem(i, 0, QTableWidgetItem(p.get('name', '')))
self.table.setItem(i, 1, QTableWidgetItem(p.get('gruppename', '')))
view_btn = QPushButton("Profil anzeigen")
profile_id = p.get('profilID')
view_btn.clicked.connect(lambda checked, pid=profile_id: self.load_and_show_profile(pid))
self.table.setCellWidget(i, 2, view_btn)
self.table.resizeRowsToContents()
def load_and_show_profile(self, profile_id):
self.progress = QProgressDialog("Lade Profil...", None, 0, 0, self)
self.progress.setWindowModality(Qt.WindowModality.WindowModal)
self.progress.show()
self.worker2 = APIWorker("api_profiles.php", "GET", params={"id": profile_id},
user_id=self.user_id, session=self.session)
self.worker2.finished.connect(lambda r: self.show_profile(r))
self.worker2.error.connect(lambda e: self.on_profile_error(e))
self.worker2.start()
def on_profile_error(self, error):
self.progress.close()
QMessageBox.critical(self, "Fehler", f"Konnte Profil nicht laden: {error}")
def show_profile(self, profile_data):
self.progress.close()
if isinstance(profile_data, dict):
if 'error' in profile_data:
QMessageBox.critical(self, "Fehler", profile_data.get('error'))
else:
dialog = ProfileViewDialog(profile_data)
dialog.exec()
else:
QMessageBox.critical(self, "Fehler", "Unerwartete Antwort vom Server")
def main():
app = QApplication(sys.argv)
login = LoginDialog()
if login.exec() == QDialog.DialogCode.Accepted:
window = MainWindow(login.user_id, login.session)
window.show()
sys.exit(app.exec())
else:
sys.exit(0)
if __name__ == "__main__":
main()