Inhalt
Aktueller Ordner:
duesseldorfer-schuelerinventar-python-clientduesk_client2.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Düsseldorfer Schülerinventar (DÜSK) - Python Client
Vollversion mit Diagrammen, Korrelation, PDF-Export, Profilbearbeitung und Gruppenverwaltung
"""
import sys
import subprocess
import requests
import math
import json
import tempfile
import os
from datetime import datetime
from typing import List, Dict, Any, Optional
# PDF imports
try:
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4, landscape
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.pdfgen import canvas
except ImportError:
print("Installiere reportlab für PDF-Export...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "reportlab"])
# 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, QInputDialog, QMenu, QToolBar)
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QSize
from PyQt6.QtGui import QFont, QPainter, QPen, QBrush, QColor, 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, QInputDialog, QMenu, QToolBar)
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QSize
from PyQt6.QtGui import QFont, QPainter, QPen, QBrush, QColor, QAction
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)
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 Fehler {response.status_code}")
except Exception as e:
self.error.emit(str(e))
class ProfileChart(QWidget):
"""Diagramm für Kompetenzwerte"""
def __init__(self, parent=None):
super().__init__(parent)
self.setMinimumSize(400, 250)
self.setMaximumHeight(250)
self.se_values = [3, 3, 3, 3, 3, 3]
self.fe_values = [3, 3, 3, 3, 3, 3]
self.labels = ["Arbeits-\nverhalten", "Lern-\nverhalten", "Sozial-\nverhalten",
"Fach-\nkompetenz", "Personale\nKompetenz", "Methoden-\nkompetenz"]
def set_values(self, se_values, fe_values):
if se_values and len(se_values) >= 6:
self.se_values = se_values[:6]
if fe_values and len(fe_values) >= 6:
self.fe_values = fe_values[:6]
self.update()
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
width = self.width()
height = self.height()
chart_top = 50
chart_height = height - 100
row_height = chart_height / 6
chart_left = 90
chart_width = width - 110
# Gitternetzlinien
pen = QPen(QColor(200, 200, 200), 1)
painter.setPen(pen)
for i in range(7):
y = chart_top + i * row_height
painter.drawLine(chart_left - 5, y, chart_left + chart_width, y)
# Vertikale Linien für Punkte 1-5
for i in range(6):
x = chart_left + i * (chart_width / 5)
painter.drawLine(x, chart_top, x, chart_top + chart_height)
# Beschriftungen
font = QFont("Arial", 8)
painter.setFont(font)
painter.setPen(QColor(0, 0, 0))
for i, label in enumerate(self.labels):
y = chart_top + i * row_height + row_height / 2 + 5
painter.drawText(5, int(y), label)
# Punkt-Beschriftungen 1-5
for i in range(5):
x = chart_left + i * (chart_width / 5) + (chart_width / 5) / 2
painter.drawText(int(x) - 4, chart_top - 10, str(i + 1))
# Selbsteinschätzung (blaue Linie)
pen = QPen(QColor(0, 100, 200), 3)
painter.setPen(pen)
se_points = []
for i, value in enumerate(self.se_values):
pos = max(0, min(4, value - 1))
x = chart_left + pos * (chart_width / 4)
y = chart_top + i * row_height + row_height / 2
se_points.append((int(x), int(y)))
for i in range(len(se_points) - 1):
painter.drawLine(se_points[i][0], se_points[i][1], se_points[i+1][0], se_points[i+1][1])
# Selbsteinschätzung Punkte (blaue Kreise)
pen = QPen(QColor(0, 100, 200), 3)
painter.setPen(pen)
painter.setBrush(QBrush(QColor(0, 100, 200, 100)))
for x, y in se_points:
painter.drawEllipse(x - 6, y - 6, 12, 12)
# Fremdeinschätzung (rote Linie)
pen = QPen(QColor(200, 0, 0), 3)
painter.setPen(pen)
fe_points = []
for i, value in enumerate(self.fe_values):
pos = max(0, min(4, value - 1))
x = chart_left + pos * (chart_width / 4)
y = chart_top + i * row_height + row_height / 2
fe_points.append((int(x), int(y)))
for i in range(len(fe_points) - 1):
painter.drawLine(fe_points[i][0], fe_points[i][1], fe_points[i+1][0], fe_points[i+1][1])
# Fremdeinschätzung Punkte (rote Kreise)
pen = QPen(QColor(200, 0, 0), 3)
painter.setPen(pen)
painter.setBrush(QBrush(QColor(200, 0, 0, 100)))
for x, y in fe_points:
painter.drawEllipse(x - 6, y - 6, 12, 12)
# Legende
painter.setPen(QColor(0, 0, 0))
painter.setBrush(QBrush(QColor(0, 100, 200, 100)))
painter.drawEllipse(chart_left + chart_width - 60, chart_top - 25, 10, 10)
painter.setPen(QColor(0, 0, 0))
painter.drawText(chart_left + chart_width - 45, chart_top - 18, "SE")
painter.setBrush(QBrush(QColor(200, 0, 0, 100)))
painter.drawEllipse(chart_left + chart_width - 20, chart_top - 25, 10, 10)
painter.setPen(QColor(0, 0, 0))
painter.drawText(chart_left + chart_width - 5, chart_top - 18, "FE")
class GroupTimeSeriesDialog(QDialog):
"""Zeigt die Zeitreihe einer Gruppe an (Profile einer Person über Zeit)"""
def __init__(self, user_id, session, group_id, group_name, profiles, parent=None):
super().__init__(parent)
self.user_id = user_id
self.session = session
self.group_id = group_id
self.group_name = group_name
self.profiles = sorted(profiles, key=lambda x: x.get('profilID', 0))
self.init_ui()
def init_ui(self):
self.setWindowTitle(f"Zeitreihe - Gruppe: {self.group_name}")
self.setMinimumSize(900, 600)
layout = QVBoxLayout()
# Info
info_label = QLabel(f"<b>Gruppe: {self.group_name}</b><br>Profile in dieser Gruppe: {len(self.profiles)}")
info_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(info_label)
# Tabelle mit Zeitreihe
self.table = QTableWidget(len(self.profiles), 8)
self.table.setHorizontalHeaderLabels(["Profil-ID", "Name", "Arbeits-\nverhalten", "Lern-\nverhalten",
"Sozial-\nverhalten", "Fach-\nkompetenz", "Personale\nKompetenz",
"Methoden-\nkompetenz"])
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
for i, profile in enumerate(self.profiles):
self.table.setItem(i, 0, QTableWidgetItem(str(profile.get('profilID', ''))))
self.table.setItem(i, 1, QTableWidgetItem(profile.get('name', '')))
self.table.setItem(i, 2, QTableWidgetItem(str(profile.get('kompetenz1', '-'))))
self.table.setItem(i, 3, QTableWidgetItem(str(profile.get('kompetenz2', '-'))))
self.table.setItem(i, 4, QTableWidgetItem(str(profile.get('kompetenz3', '-'))))
self.table.setItem(i, 5, QTableWidgetItem(str(profile.get('kompetenz4', '-'))))
self.table.setItem(i, 6, QTableWidgetItem(str(profile.get('kompetenz5', '-'))))
self.table.setItem(i, 7, QTableWidgetItem(str(profile.get('kompetenz6', '-'))))
layout.addWidget(self.table)
# Schließen-Button
close_btn = QPushButton("Schließen")
close_btn.clicked.connect(self.accept)
layout.addWidget(close_btn)
self.setLayout(layout)
class ProfileEditDialog(QDialog):
"""Dialog zum Erstellen/Bearbeiten eines Profils"""
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"
]
def __init__(self, user_id, session, profile_data=None, groups=None, parent=None):
super().__init__(parent)
self.user_id = user_id
self.session = session
self.profile_data = profile_data or {}
self.groups = groups or []
self.is_edit = profile_data is not None
self.se_groups = {} # item_id -> QButtonGroup
self.fe_groups = {}
self.init_ui()
def init_ui(self):
self.setWindowTitle("Profil bearbeiten" if self.is_edit else "Neues Profil")
self.setMinimumSize(900, 700)
layout = QVBoxLayout()
# Basis-Informationen
info_group = QGroupBox("Profil-Informationen")
info_layout = QFormLayout()
self.name_input = QLineEdit()
self.name_input.setText(self.profile_data.get('name', ''))
info_layout.addRow("Name:", self.name_input)
self.group_combo = QComboBox()
for group in self.groups:
self.group_combo.addItem(group.get('name', 'Unbenannt'), group.get('gruppeID'))
current_group_id = self.profile_data.get('gruppeID')
if current_group_id:
index = self.group_combo.findData(current_group_id)
if index >= 0:
self.group_combo.setCurrentIndex(index)
info_layout.addRow("Gruppe:", self.group_combo)
self.new_group_input = QLineEdit()
self.new_group_input.setPlaceholderText("Neue Gruppe (optional)")
info_layout.addRow("Neue Gruppe:", self.new_group_input)
info_group.setLayout(info_layout)
layout.addWidget(info_group)
# Tabs für Selbst- und Fremdeinschätzung
tabs = QTabWidget()
# Selbsteinschätzung Tab
se_widget = self.create_items_tab("Selbsteinschätzung", "item", self.se_groups)
tabs.addTab(se_widget, "Selbsteinschätzung")
# Fremdeinschätzung Tab
fe_widget = self.create_items_tab("Fremdeinschätzung", "feitem", self.fe_groups)
tabs.addTab(fe_widget, "Fremdeinschätzung")
layout.addWidget(tabs)
# Buttons
button_layout = QHBoxLayout()
save_btn = QPushButton("Speichern")
save_btn.setStyleSheet("background-color: #4CAF50; color: white; padding: 8px;")
save_btn.clicked.connect(self.save_profile)
cancel_btn = QPushButton("Abbrechen")
cancel_btn.clicked.connect(self.reject)
button_layout.addStretch()
button_layout.addWidget(save_btn)
button_layout.addWidget(cancel_btn)
layout.addLayout(button_layout)
self.setLayout(layout)
# Werte laden, wenn vorhanden
if self.is_edit:
self.load_values()
def create_items_tab(self, title, prefix, groups_dict):
"""Erstellt einen Tab mit allen Items"""
widget = QWidget()
layout = QVBoxLayout(widget)
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll_widget = QWidget()
scroll_layout = QVBoxLayout(scroll_widget)
for i, item_name in enumerate(self.ITEM_NAMES, 1):
group_box = QGroupBox(f"{i}. {item_name}")
group_layout = QHBoxLayout()
button_group = QButtonGroup()
for value, label in [(4, "trifft voll zu"), (3, "trifft zu"),
(2, "trifft teilweise zu"), (1, "trifft nicht zu")]:
radio = QRadioButton(label)
radio.setProperty("value", value)
group_layout.addWidget(radio)
button_group.addButton(radio, value)
group_box.setLayout(group_layout)
scroll_layout.addWidget(group_box)
groups_dict[i] = button_group
scroll.setWidget(scroll_widget)
layout.addWidget(scroll)
return widget
def load_values(self):
"""Lädt vorhandene Werte in die RadioButtons"""
for i in range(1, 37):
# SE Werte
se_val = self.profile_data.get(f'item{i}', 2)
if i in self.se_groups:
btn = self.se_groups[i].button(se_val)
if btn:
btn.setChecked(True)
# FE Werte
fe_val = self.profile_data.get(f'feitem{i}', 2)
if i in self.fe_groups:
btn = self.fe_groups[i].button(fe_val)
if btn:
btn.setChecked(True)
def save_profile(self):
"""Speichert das Profil"""
name = self.name_input.text().strip()
if not name:
QMessageBox.warning(self, "Warnung", "Bitte geben Sie einen Namen ein.")
return
# Gruppe bestimmen
group_id = self.group_combo.currentData()
new_group = self.new_group_input.text().strip()
data = {'name': name}
if group_id:
data['gruppeID'] = group_id
if new_group:
data['namegruppe'] = new_group
# SE Werte sammeln
for i in range(1, 37):
if i in self.se_groups:
checked_id = self.se_groups[i].checkedId()
data[f'item{i}'] = checked_id if checked_id != -1 else 2
else:
data[f'item{i}'] = 2
# FE Werte sammeln
for i in range(1, 37):
if i in self.fe_groups:
checked_id = self.fe_groups[i].checkedId()
data[f'feitem{i}'] = checked_id if checked_id != -1 else 2
else:
data[f'feitem{i}'] = 2
if self.is_edit:
data['profilID'] = self.profile_data.get('profilID')
endpoint = "api_profiles.php"
method = "PUT"
else:
endpoint = "api_profiles.php"
method = "POST"
self.worker = APIWorker(endpoint, method, data=data, user_id=self.user_id, session=self.session)
self.worker.finished.connect(self.save_finished)
self.worker.error.connect(self.save_error)
self.worker.start()
self.progress = QProgressDialog("Speichere Profil...", None, 0, 0, self)
self.progress.setWindowModality(Qt.WindowModality.WindowModal)
self.progress.show()
def save_finished(self, response):
self.progress.close()
if response.get('success'):
QMessageBox.information(self, "Erfolg", "Profil erfolgreich gespeichert.")
self.accept()
else:
QMessageBox.critical(self, "Fehler", response.get('error', 'Unbekannter Fehler'))
def save_error(self, error):
self.progress.close()
QMessageBox.critical(self, "Fehler", f"Fehler beim Speichern: {error}")
class GroupManagerDialog(QDialog):
"""Dialog zur Gruppenverwaltung"""
def __init__(self, user_id, session, groups, parent=None):
super().__init__(parent)
self.user_id = user_id
self.session = session
self.groups = groups
self.init_ui()
def init_ui(self):
self.setWindowTitle("Gruppenverwaltung")
self.setMinimumSize(500, 400)
layout = QVBoxLayout()
# Gruppenliste
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_groups()
layout.addWidget(self.group_list)
# Neue Gruppe
add_layout = QHBoxLayout()
self.new_group_name = QLineEdit()
self.new_group_name.setPlaceholderText("Neuer Gruppenname")
add_btn = QPushButton("Hinzufügen")
add_btn.clicked.connect(self.add_group)
add_layout.addWidget(self.new_group_name)
add_layout.addWidget(add_btn)
layout.addLayout(add_layout)
# Schließen-Button
close_btn = QPushButton("Schließen")
close_btn.clicked.connect(self.accept)
layout.addWidget(close_btn)
self.setLayout(layout)
def refresh_groups(self):
self.group_list.setRowCount(len(self.groups))
for i, group in enumerate(self.groups):
self.group_list.setItem(i, 0, QTableWidgetItem(group.get('name', '')))
delete_btn = QPushButton("Löschen")
group_id = group.get('gruppeID')
delete_btn.clicked.connect(lambda checked, gid=group_id: self.delete_group(gid))
self.group_list.setCellWidget(i, 1, delete_btn)
self.group_list.resizeRowsToContents()
def add_group(self):
name = self.new_group_name.text().strip()
if not name:
QMessageBox.warning(self, "Warnung", "Bitte einen Gruppennamen eingeben.")
return
worker = APIWorker("api_groups.php", "POST", data={"name": name},
user_id=self.user_id, session=self.session)
worker.finished.connect(self.group_added)
worker.error.connect(lambda e: QMessageBox.critical(self, "Fehler", f"Fehler: {e}"))
worker.start()
def group_added(self, response):
if response.get('success'):
self.new_group_name.clear()
# Gruppen neu laden
self.load_groups()
else:
QMessageBox.critical(self, "Fehler", response.get('error', 'Unbekannter Fehler'))
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: QMessageBox.critical(self, "Fehler", f"Fehler: {e}"))
worker.start()
def groups_loaded(self, response):
if isinstance(response, list):
self.groups = response
self.refresh_groups()
def delete_group(self, group_id):
reply = QMessageBox.question(self, "Gruppe löschen",
"Möchten Sie diese Gruppe wirklich löschen?\nProfile in dieser Gruppe bleiben erhalten.",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
if reply == 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.error.connect(lambda e: QMessageBox.critical(self, "Fehler", f"Fehler: {e}"))
worker.start()
class ProfileViewDialog(QDialog):
"""Profilansicht mit Diagrammen, Korrelation und PDF-Export"""
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]}
def __init__(self, profile_data, parent=None):
super().__init__(parent)
self.profile_data = profile_data
self.current_norm = "HS"
self.init_ui()
self.calculate_and_display()
def init_ui(self):
self.setWindowTitle(f"Profil: {self.profile_data.get('name', 'Unbekannt')}")
self.setMinimumSize(1200, 800)
layout = QVBoxLayout()
# Name und Toolbar
header_layout = QHBoxLayout()
name_label = QLabel(f"<h2>{self.profile_data.get('name', 'Unbekannt')}</h2>")
header_layout.addWidget(name_label)
header_layout.addStretch()
# PDF Export Button
pdf_btn = QPushButton("PDF Export")
pdf_btn.setStyleSheet("background-color: #f44336; color: white; padding: 5px;")
pdf_btn.clicked.connect(self.export_pdf)
header_layout.addWidget(pdf_btn)
layout.addLayout(header_layout)
# 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)
# Diagramm
chart_group = QGroupBox("Profil-Diagramm")
chart_layout = QVBoxLayout()
self.chart = ProfileChart()
chart_layout.addWidget(self.chart)
chart_group.setLayout(chart_layout)
layout.addWidget(chart_group)
# Hauptinhalt mit zwei Spalten (Tabellen)
main_widget = QWidget()
main_layout = QHBoxLayout(main_widget)
# Selbsteinschätzung
se_widget = self.create_competence_table("Selbsteinschätzung (SE)", "se")
main_layout.addWidget(se_widget)
# Fremdeinschätzung
fe_widget = self.create_competence_table("Fremdeinschätzung (FE)", "fe")
main_layout.addWidget(fe_widget)
layout.addWidget(main_widget)
# Korrelation und Statistik
stats_group = QGroupBox("Statistische Auswertung")
stats_layout = QVBoxLayout()
self.corr_label = QLabel()
self.agree_label = QLabel()
self.corr_text = QTextEdit()
self.corr_text.setReadOnly(True)
self.corr_text.setMaximumHeight(120)
stats_layout.addWidget(self.corr_label)
stats_layout.addWidget(self.agree_label)
stats_layout.addWidget(self.corr_text)
stats_group.setLayout(stats_layout)
layout.addWidget(stats_group)
# Item-Tabelle
items_group = QGroupBox("Item-Werte (alle 36 Items)")
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.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, prefix):
"""Erstellt eine Tabelle für Kompetenzen"""
widget = QWidget()
layout = QVBoxLayout(widget)
label = QLabel(f"<b>{title}</b>")
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(label)
table = QTableWidget(6, 5)
table.setHorizontalHeaderLabels(["1", "2", "3", "4", "5"])
table.setVerticalHeaderLabels(["Arbeitsverhalten", "Lernverhalten", "Sozialverhalten",
"Fachkompetenz", "Personale Kompetenz", "Methodenkompetenz"])
table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
layout.addWidget(table)
if prefix == "se":
self.se_table = table
else:
self.fe_table = table
return widget
def on_norm_changed(self):
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
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 calculate_profile_values(self, sums, norm):
"""Berechnet die Profilwerte (0-4)"""
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-Markierungen"""
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_correlation(self, se_values, fe_values):
"""Berechnet die Korrelation"""
se_mean = sum(se_values) / 6
fe_mean = sum(fe_values) / 6
numerator = 0
se_var = 0
fe_var = 0
for i in range(6):
se_diff = se_values[i] - se_mean
fe_diff = fe_values[i] - fe_mean
numerator += se_diff * fe_diff
se_var += se_diff ** 2
fe_var += fe_diff ** 2
if se_var == 0 or fe_var == 0:
return 0.0
return numerator / math.sqrt(se_var * fe_var)
def calculate_agreement(self, se_items, fe_items):
"""Berechnet die prozentuale Übereinstimmung"""
matches = sum(1 for s, f in zip(se_items, fe_items) if s == f)
return matches * 100 / 36
def calculate_and_display(self):
"""Hauptberechnung"""
# Items extrahieren
se_items = [int(self.profile_data.get(f'item{i}', 2)) for i in range(1, 37)]
fe_items = [int(self.profile_data.get(f'feitem{i}', 2)) for i in range(1, 37)]
# 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)
# Für Diagramm: Werte auf 1-5 umrechnen (statt 0-4)
se_chart_values = [v + 1 for v in se_values]
fe_chart_values = [v + 1 for v in fe_values]
# Tabellen füllen
self.fill_table(self.se_table, se_values)
self.fill_table(self.fe_table, fe_values)
# Diagramm aktualisieren
self.chart.set_values(se_chart_values, fe_chart_values)
# Korrelation und Übereinstimmung
correlation = self.calculate_correlation(se_chart_values, fe_chart_values)
agreement = self.calculate_agreement(se_items, fe_items)
self.corr_label.setText(f"<b>Korrelation:</b> {correlation:.2f}")
self.agree_label.setText(f"<b>Übereinstimmung:</b> {agreement:.1f}%")
# Auswertungstext
bewertungen = ["weit unterdurchschnittlich", "unterdurchschnittlich",
"durchschnittlich", "überdurchschnittlich", "weit überdurchschnittlich"]
text = "Selbsteinschätzung:\n"
kompetenzen = ["Arbeitsverhalten", "Lernverhalten", "Sozialverhalten",
"Fachkompetenz", "Personale Kompetenz", "Methodenkompetenz"]
for i, v in enumerate(se_chart_values):
text += f"• {kompetenzen[i]}: {bewertungen[v-1]}\n"
text += "\nFremdeinschätzung:\n"
for i, v in enumerate(fe_chart_values):
text += f"• {kompetenzen[i]}: {bewertungen[v-1]}\n"
text += f"\nInterpretation der Korrelation ({correlation:.2f}):\n"
if correlation >= 0.8:
text += "Die Selbst- und Fremdeinschätzung stimmen sehr gut überein."
elif correlation >= 0.5:
text += "Die Selbst- und Fremdeinschätzung stimmen mäßig überein."
elif correlation >= 0.3:
text += "Die Selbst- und Fremdeinschätzung stimmen schwach überein."
else:
text += "Es besteht keine signifikante Übereinstimmung zwischen Selbst- und Fremdeinschätzung."
self.corr_text.setText(text)
def export_pdf(self):
"""Exportiert das Profil als PDF"""
filename, _ = QFileDialog.getSaveFileName(self, "PDF speichern",
f"{self.profile_data.get('name', 'profil')}.pdf",
"PDF Dateien (*.pdf)")
if not filename:
return
try:
doc = SimpleDocTemplate(filename, pagesize=A4)
styles = getSampleStyleSheet()
story = []
# Titel
title_style = ParagraphStyle('CustomTitle', parent=styles['Heading1'], fontSize=16, alignment=1)
story.append(Paragraph(f"DÜSK - Profil: {self.profile_data.get('name', 'Unbekannt')}", title_style))
story.append(Spacer(1, 20))
# Datum
date_style = ParagraphStyle('Date', parent=styles['Normal'], fontSize=10, alignment=2)
story.append(Paragraph(f"Erstellt am: {datetime.now().strftime('%d.%m.%Y %H:%M')}", date_style))
story.append(Spacer(1, 20))
# Kompetenz-Tabellen
story.append(Paragraph("Kompetenzwerte", styles['Heading2']))
story.append(Spacer(1, 10))
# Selbsteinschätzung Tabelle
se_data = [["Selbsteinschätzung", "1", "2", "3", "4", "5"]]
kompetenzen = ["Arbeitsverhalten", "Lernverhalten", "Sozialverhalten",
"Fachkompetenz", "Personale Kompetenz", "Methodenkompetenz"]
for i, kompetenz in enumerate(kompetenzen):
row = [kompetenz]
for j in range(5):
item = self.se_table.item(i, j)
row.append("X" if item and item.text() == "X" else "")
se_data.append(row)
se_table = Table(se_data)
se_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('GRID', (0, 0), (-1, -1), 1, colors.black)
]))
story.append(se_table)
story.append(Spacer(1, 20))
# Fremdeinschätzung Tabelle
fe_data = [["Fremdeinschätzung", "1", "2", "3", "4", "5"]]
for i, kompetenz in enumerate(kompetenzen):
row = [kompetenz]
for j in range(5):
item = self.fe_table.item(i, j)
row.append("X" if item and item.text() == "X" else "")
fe_data.append(row)
fe_table = Table(fe_data)
fe_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('GRID', (0, 0), (-1, -1), 1, colors.black)
]))
story.append(fe_table)
story.append(Spacer(1, 20))
# Korrelation und Auswertung
story.append(Paragraph("Statistische Auswertung", styles['Heading2']))
story.append(Spacer(1, 10))
story.append(Paragraph(self.corr_label.text(), styles['Normal']))
story.append(Paragraph(self.agree_label.text(), styles['Normal']))
story.append(Spacer(1, 10))
story.append(Paragraph(self.corr_text.toPlainText(), styles['Normal']))
doc.build(story)
QMessageBox.information(self, "PDF Export", f"PDF erfolgreich gespeichert:\n{filename}")
except Exception as e:
QMessageBox.critical(self, "Fehler", f"Fehler beim PDF-Export: {e}")
class GroupTimeSeriesDialog(QDialog):
"""Zeitreihe einer Gruppe (Profile einer Person)"""
def __init__(self, group_name, profiles, parent=None):
super().__init__(parent)
self.group_name = group_name
self.profiles = sorted(profiles, key=lambda x: x.get('profilID', 0))
self.init_ui()
def init_ui(self):
self.setWindowTitle(f"Zeitreihe - {self.group_name}")
self.setMinimumSize(900, 500)
layout = QVBoxLayout()
info = QLabel(f"<b>{self.group_name}</b> - {len(self.profiles)} Profile in dieser Gruppe")
info.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(info)
# Tabelle
self.table = QTableWidget(len(self.profiles), 8)
self.table.setHorizontalHeaderLabels(["Profil-ID", "Name", "Arbeits-\nverhalten", "Lern-\nverhalten",
"Sozial-\nverhalten", "Fach-\nkompetenz", "Personale\nKompetenz",
"Methoden-\nkompetenz"])
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
for i, p in enumerate(self.profiles):
self.table.setItem(i, 0, QTableWidgetItem(str(p.get('profilID', ''))))
self.table.setItem(i, 1, QTableWidgetItem(p.get('name', '')))
self.table.setItem(i, 2, QTableWidgetItem(str(p.get('kompetenz1', '-'))))
self.table.setItem(i, 3, QTableWidgetItem(str(p.get('kompetenz2', '-'))))
self.table.setItem(i, 4, QTableWidgetItem(str(p.get('kompetenz3', '-'))))
self.table.setItem(i, 5, QTableWidgetItem(str(p.get('kompetenz4', '-'))))
self.table.setItem(i, 6, QTableWidgetItem(str(p.get('kompetenz5', '-'))))
self.table.setItem(i, 7, QTableWidgetItem(str(p.get('kompetenz6', '-'))))
layout.addWidget(self.table)
close_btn = QPushButton("Schließen")
close_btn.clicked.connect(self.accept)
layout.addWidget(close_btn)
self.setLayout(layout)
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(350, 250)
layout = QVBoxLayout()
title = QLabel("DÜSK - Düsseldorfer Schülerinventar")
title.setFont(QFont("Arial", 18, QFont.Weight.Bold))
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(title)
form_layout = QFormLayout()
form_layout.setSpacing(15)
self.username_input = QLineEdit()
self.username_input.setText("gast")
self.username_input.setPlaceholderText("Benutzername")
self.password_input = QLineEdit()
self.password_input.setText("gast")
self.password_input.setEchoMode(QLineEdit.EchoMode.Password)
self.password_input.setPlaceholderText("Passwort")
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)
info = QLabel("Server: paul-koop.org\nBenutzung mit gast/gast möglich")
info.setStyleSheet("color: gray; font-size: 10px;")
info.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(info)
self.setLayout(layout)
def do_login(self):
username = self.username_input.text().strip()
password = self.password_input.text().strip()
if not username or not password:
QMessageBox.warning(self, "Fehler", "Bitte Benutzername und Passwort eingeben.")
return
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.profiles = []
self.groups = []
self.init_ui()
self.load_profiles()
self.load_groups()
def init_ui(self):
self.setWindowTitle("DÜSK - Düsseldorfer Schülerinventar")
self.setMinimumSize(1000, 600)
# Menüleiste
menubar = self.menuBar()
profil_menu = menubar.addMenu("Profil")
new_action = QAction("Neues Profil", self)
new_action.triggered.connect(self.new_profile)
profil_menu.addAction(new_action)
gruppe_menu = menubar.addMenu("Gruppe")
manage_groups_action = QAction("Gruppen verwalten", self)
manage_groups_action.triggered.connect(self.manage_groups)
gruppe_menu.addAction(manage_groups_action)
# Toolbar
toolbar = QToolBar()
self.addToolBar(toolbar)
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)
# Hauptinhalt
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)
# Tabelle
self.table = QTableWidget()
self.table.setColumnCount(5)
self.table.setHorizontalHeaderLabels(["Name", "Gruppe", "ProfilID", "Aktionen", "Zeitreihe"])
self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
self.table.setAlternatingRowColors(True)
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)
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.profiles = response
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', '')))
self.table.setItem(i, 2, QTableWidgetItem(str(p.get('profilID', ''))))
# Aktions-Buttons
actions_widget = QWidget()
actions_layout = QHBoxLayout(actions_widget)
actions_layout.setContentsMargins(0, 0, 0, 0)
view_btn = QPushButton("Anzeigen")
view_btn.setStyleSheet("background-color: #4CAF50; color: white;")
view_btn.clicked.connect(lambda checked, pid=p.get('profilID'): self.view_profile(pid))
actions_layout.addWidget(view_btn)
edit_btn = QPushButton("Bearbeiten")
edit_btn.clicked.connect(lambda checked, pid=p.get('profilID'): self.edit_profile(pid))
actions_layout.addWidget(edit_btn)
delete_btn = QPushButton("Löschen")
delete_btn.setStyleSheet("background-color: #f44336; color: white;")
delete_btn.clicked.connect(lambda checked, pid=p.get('profilID'): self.delete_profile(pid))
actions_layout.addWidget(delete_btn)
self.table.setCellWidget(i, 3, actions_widget)
# Zeitreihe Button (nur wenn Gruppe vorhanden)
if p.get('gruppeID'):
ts_btn = QPushButton("Zeitreihe")
ts_btn.clicked.connect(lambda checked, gid=p.get('gruppeID'), gname=p.get('gruppename'):
self.show_time_series(gid, gname))
self.table.setCellWidget(i, 4, ts_btn)
else:
self.table.setItem(i, 4, QTableWidgetItem("-"))
self.table.resizeRowsToContents()
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, response):
if isinstance(response, list):
self.groups = response
def new_profile(self):
dialog = ProfileEditDialog(self.user_id, self.session, groups=self.groups)
if dialog.exec():
self.load_profiles()
def edit_profile(self, profile_id):
# Profil laden
worker = APIWorker("api_profiles.php", "GET", params={"id": profile_id},
user_id=self.user_id, session=self.session)
worker.finished.connect(lambda r: self.edit_profile_loaded(r))
worker.error.connect(lambda e: QMessageBox.critical(self, "Fehler", f"Fehler: {e}"))
worker.start()
def edit_profile_loaded(self, profile_data):
if isinstance(profile_data, dict) and 'error' not in profile_data:
dialog = ProfileEditDialog(self.user_id, self.session, profile_data, self.groups)
if dialog.exec():
self.load_profiles()
def view_profile(self, profile_id):
worker = APIWorker("api_profiles.php", "GET", params={"id": profile_id},
user_id=self.user_id, session=self.session)
worker.finished.connect(lambda r: self.show_profile(r))
worker.error.connect(lambda e: QMessageBox.critical(self, "Fehler", f"Fehler: {e}"))
worker.start()
def show_profile(self, profile_data):
if isinstance(profile_data, dict) and 'error' not in profile_data:
dialog = ProfileViewDialog(profile_data)
dialog.exec()
elif isinstance(profile_data, dict) and profile_data.get('error'):
QMessageBox.critical(self, "Fehler", profile_data.get('error'))
def delete_profile(self, profile_id):
reply = QMessageBox.question(self, "Profil löschen",
"Möchten Sie dieses Profil wirklich löschen?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
if reply == QMessageBox.StandardButton.Yes:
worker = APIWorker("api_profiles.php", "DELETE", params={"id": profile_id},
user_id=self.user_id, session=self.session)
worker.finished.connect(lambda r: self.load_profiles())
worker.error.connect(lambda e: QMessageBox.critical(self, "Fehler", f"Fehler: {e}"))
worker.start()
def show_time_series(self, group_id, group_name):
# Alle Profile der Gruppe laden
group_profiles = [p for p in self.profiles if p.get('gruppeID') == group_id]
if group_profiles:
dialog = GroupTimeSeriesDialog(group_name, group_profiles)
dialog.exec()
else:
QMessageBox.information(self, "Info", "Keine weiteren Profile in dieser Gruppe.")
def manage_groups(self):
dialog = GroupManagerDialog(self.user_id, self.session, self.groups)
if dialog.exec():
self.load_groups()
self.load_profiles()
def logout(self):
worker = APIWorker("api_logout.php", "POST", user_id=self.user_id, session=self.session)
worker.start()
self.close()
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()