Inhalt
Aktueller Ordner:
duesseldorfer-schuelerinventar-python-clientduesk.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Düsseldorfer Schülerinventar (DÜSK) - Desktop-Anwendung
Autor: Paul Koop M.A.
Version: 1.0
"""
import sys
import os
import subprocess
import sqlite3
import hashlib
import secrets
import math
from datetime import datetime
from typing import Dict, List, Optional, Tuple, Any
# GUI imports
try:
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QGridLayout, QLabel, QLineEdit,
QPushButton, QTableWidget, QTableWidgetItem,
QTabWidget, QGroupBox, QRadioButton, QButtonGroup,
QMessageBox, QHeaderView, QSplitter, QFrame,
QScrollArea, QTextEdit, QComboBox, QDialog,
QDialogButtonBox, QFormLayout, QCheckBox)
from PyQt6.QtCore import Qt, QSize
from PyQt6.QtGui import QFont, QPalette, QColor, QPainter, QPen, QBrush
except ImportError:
print("Installiere benötigte Pakete...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "PyQt6"])
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QGridLayout, QLabel, QLineEdit,
QPushButton, QTableWidget, QTableWidgetItem,
QTabWidget, QGroupBox, QRadioButton, QButtonGroup,
QMessageBox, QHeaderView, QSplitter, QFrame,
QScrollArea, QTextEdit, QComboBox, QDialog,
QDialogButtonBox, QFormLayout, QCheckBox)
from PyQt6.QtCore import Qt, QSize
from PyQt6.QtGui import QFont, QPalette, QColor, QPainter, QPen, QBrush
# SVG-Grafik für Profil-Diagramm
class ProfileChart(QWidget):
"""Widget für die Darstellung des Profildiagramms als SVG-ähnliche Grafik"""
def __init__(self, parent=None):
super().__init__(parent)
self.setMinimumSize(400, 300)
self.values = [0, 0, 0, 0, 0, 0] # Werte 0-4 für die 6 Kompetenzen
self.labels = ["Arbeitsverhalten", "Lernverhalten", "Sozialverhalten",
"Fachkompetenz", "Personale Kompetenz", "Methodenkompetenz"]
def set_values(self, values: List[int]):
"""Setzt die Werte für das Diagramm (0-4 für jede Kompetenz)"""
self.values = values[:6]
self.update()
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
width = self.width()
height = self.height()
# Diagramm-Parameter
chart_top = 40
chart_height = height - 80
row_height = chart_height / 6
chart_left = 120
chart_width = width - 160
# Gitternetzlinien (horizontal)
pen = QPen(QColor(255, 0, 0), 1)
painter.setPen(pen)
for i in range(7):
y = chart_top + i * row_height
painter.drawLine(chart_left - 10, 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)
# Achsenbeschriftungen (Kompetenzen)
font = QFont("Arial", 10)
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)
# Punktbeschriftungen 1-5
for i in range(5):
x = chart_left + i * (chart_width / 5) + (chart_width / 5) / 2
painter.drawText(int(x) - 5, chart_top - 10, str(i + 1))
# Polyline für das Profil
pen = QPen(QColor(0, 116, 217), 3)
painter.setPen(pen)
points = []
for i, value in enumerate(self.values):
x = chart_left + value * (chart_width / 4) # value 0-4
y = chart_top + i * row_height + row_height / 2
points.append((int(x), int(y)))
# Linie zeichnen
for i in range(len(points) - 1):
painter.drawLine(points[i][0], points[i][1], points[i+1][0], points[i+1][1])
# Punkte (Kreise) zeichnen
pen = QPen(QColor(0, 116, 217), 5)
painter.setPen(pen)
painter.setBrush(QBrush(QColor(255, 255, 255)))
for x, y in points:
painter.drawEllipse(x - 8, y - 8, 16, 16)
class LoginDialog(QDialog):
"""Login-Dialog für die Authentifizierung"""
def __init__(self, db, parent=None):
super().__init__(parent)
self.db = db
self.user_id = None
self.session = None
self.init_ui()
def init_ui(self):
self.setWindowTitle("Anmeldung - Düsseldorfer Schülerinventar")
self.setModal(True)
self.setMinimumSize(300, 200)
layout = QVBoxLayout()
# Titel
title = QLabel("DÜSK - Anmeldung")
title_font = QFont("Arial", 16, QFont.Weight.Bold)
title.setFont(title_font)
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(title)
# Formular
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)
# Buttons
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)
# Gast-Hinweis
guest_info = QLabel("Hinweis: Mit 'gast/gast' können Sie sich als Gast anmelden.\nBei neuen Benutzern werden Account und Profil automatisch erstellt.")
guest_info.setStyleSheet("color: gray; font-size: 10px;")
guest_info.setWordWrap(True)
layout.addWidget(guest_info)
self.setLayout(layout)
def do_login(self):
username = self.username_input.text()
password = self.password_input.text()
# Benutzer suchen oder erstellen
cursor = self.db.conn.cursor()
cursor.execute("SELECT ID FROM user WHERE user = ? AND pass = ?", (username, password))
row = cursor.fetchone()
if row:
self.user_id = row[0]
else:
# Neuen Benutzer erstellen
cursor.execute("INSERT INTO user (user, pass) VALUES (?, ?)", (username, password))
self.db.conn.commit()
self.user_id = cursor.lastrowid
# Session erstellen
self.session = secrets.token_hex(16)
cursor.execute("INSERT INTO anmeldung (userID, session) VALUES (?, ?)",
(self.user_id, self.session))
self.db.conn.commit()
self.accept()
class ProfileEditDialog(QDialog):
"""Dialog zum Bearbeiten/Einfügen eines Profils"""
def __init__(self, db, user_id, session, profile_id=None, parent=None):
super().__init__(parent)
self.db = db
self.user_id = user_id
self.session = session
self.profile_id = profile_id
self.item_fields = [] # Speichert die RadioButton-Gruppen
self.feitem_fields = []
self.init_ui()
if profile_id:
self.load_profile()
def init_ui(self):
self.setWindowTitle("Profil bearbeiten" if self.profile_id else "Neues Profil")
self.setMinimumSize(800, 600)
main_layout = QVBoxLayout()
# Scrollbereich für das gesamte Formular
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll_widget = QWidget()
scroll_layout = QVBoxLayout(scroll_widget)
# Datensatz-Informationen
info_group = QGroupBox("Datensatz")
info_layout = QFormLayout()
self.name_input = QLineEdit()
self.name_input.setPlaceholderText("Name des Schülers/der Schülerin")
info_layout.addRow("Name:", self.name_input)
# Gruppenauswahl
self.group_combo = QComboBox()
self.load_groups()
info_layout.addRow("Gruppe wählen:", 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)
scroll_layout.addWidget(info_group)
# Tabs für Selbst- und Fremdeinschätzung
tabs = QTabWidget()
# Selbsteinschätzung
se_widget = self.create_item_table("SE", range(1, 37), self.item_fields)
tabs.addTab(se_widget, "Selbsteinschätzung")
# Fremdeinschätzung
fe_widget = self.create_item_table("FE", range(1, 37), self.feitem_fields)
tabs.addTab(fe_widget, "Fremdeinschätzung")
scroll_layout.addWidget(tabs)
# Buttons
button_layout = QHBoxLayout()
save_btn = QPushButton("Speichern")
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)
scroll_layout.addLayout(button_layout)
scroll.setWidget(scroll_widget)
main_layout.addWidget(scroll)
self.setLayout(main_layout)
def load_groups(self):
"""Lädt die Gruppen des Benutzers in die ComboBox"""
self.group_combo.clear()
cursor = self.db.conn.cursor()
cursor.execute("SELECT gruppeID, name FROM gruppe WHERE userID = ?", (self.user_id,))
for row in cursor.fetchall():
self.group_combo.addItem(row[1], row[0])
def create_item_table(self, prefix, items, field_list):
"""Erstellt eine Tabelle mit Items als RadioButtons"""
widget = QWidget()
layout = QVBoxLayout(widget)
# Tabellendefinition
items_data = [
(1, "Zuverlässigkeit", "Ich beachte beim Erfüllen eines Auftrags genau die Hinweise, Vorgaben und Absprachen."),
(2, "Arbeitstempo", "Ich schaffe schulische Aufgaben in der vorgegebenen Zeit."),
(3, "Arbeitsplanung", "Ich mache einen Plan zur Vorgehensweise."),
(4, "Organisationsfähigkeit", "Ich mache Vorschläge zur Aufgabenverteilung."),
(5, "Geschicklichkeit", "Ich bin geschickt und sicher bei der Benutzung von Werkzeugen und Arbeitsgeräten."),
(6, "Ordnung", "Mein Arbeitsplatz ist immer ordentlich und übersichtlich."),
(7, "Sorgfalt", "Ich gehe sachgerecht und verantwortungsbewusst mit Materialien um."),
(8, "Kreativität", "Ich habe immer wieder neue Ideen, wie ich Lösungen finden kann."),
(9, "Problemlösungsfähigkeit", "Knifflige Aufgaben machen mir Spaß."),
(10, "Abstraktionsvermögen", "Ich kann Wichtiges von Unwichtigem unterscheiden."),
(11, "Selbstständigkeit", "Ich kann Aufgaben ohne Anleitung ausführen."),
(12, "Belastbarkeit", "Ich arbeite auch bei Schwierigkeiten an einer Aufgabe weiter."),
(13, "Konzentrationsfähigkeit", "Ich kann eine Tätigkeit über längere Zeit ausüben, ohne mich ablenken zu lassen."),
(14, "Verantwortungsbewusstsein", "Ich gestalte Entscheidungen mit und übernehme auch die Verantwortung für sie."),
(15, "Eigeninitiative", "Ich setze mir eigene Ziele und verwirkliche sie auch ohne Anstoß von außen."),
(16, "Leistungsbereitschaft", "Ich arbeite auch über das geforderte Maß an einer Aufgabe weiter."),
(17, "Auffassungsgabe", "Ich kann Zusammenhänge leicht und schnell begreifen."),
(18, "Merkfähigkeit", "Einmal Gelerntes weiß ich nach längerer Zeit noch."),
(19, "Motivationsfähigkeit", "Ich gehe mit Begeisterung an neue Aufgaben."),
(20, "Reflektionsfähigkeit", "Ich erkenne, wie meine Arbeitsleistung war."),
(21, "Teamfähigkeit", "Ich kann zielgerichtet und förderlich mit anderen zusammenarbeiten."),
(22, "Hilfsbereitschaft", "Wenn jemand Unterstützung braucht, bin ich gern bereit zu helfen."),
(23, "Kontaktfähigkeit", "Ich kann auf andere zugehen und bin an deren Vorschlägen interessiert."),
(24, "Respektvoller Umgang", "Ich beachte die Formen der Höflichkeit im Umgang mit anderen Menschen."),
(25, "Kommunikationsfähigkeit", "Ich gehe auf Fragen ein und höre anderen aufmerksam zu."),
(26, "Einfühlungsvermögen", "Ich kann mich in andere hineinversetzen."),
(27, "Konfliktfähigkeit", "Ich versuche bei einem Streit, eine gemeinsame Lösung zu finden."),
(28, "Kritikfähigkeit", "Ich kann Kritik angemessen vorbringen, annehmen und umsetzen."),
(29, "Schreiben", "Ich schreibe gern Texte."),
(30, "Lesen", "Ich kann wichtige Informationen aus einem Text entnehmen."),
(31, "Mathematik", "Ich kann gut mit Zahlen und Formeln umgehen."),
(32, "Naturwissenschaft", "Ich interessiere mich für die Natur und ihre Gesetze."),
(33, "Fremdsprachen", "Ich kann mich in einer Fremdsprache ausdrücken."),
(34, "Präsentationsfähigkeit", "Wenn ich präsentiere, hören mir andere gern zu."),
(35, "PC Kenntnisse", "Ich kann den PC für die schulische Arbeit nutzen."),
(36, "Fächerübergreifendes Denken", "Ich kann Wissen, das ich erworben habe, auch in anderen Zusammenhängen anwenden.")
]
# Tabelle erstellen
table = QTableWidget(len(items_data), 5)
table.setHorizontalHeaderLabels(["Item", "trifft voll zu (4)", "trifft zu (3)", "trifft teilweise zu (2)", "trifft nicht zu (1)"])
table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
for row_idx, (item_id, title, description) in enumerate(items_data):
# Item-Text
item_text = QTableWidgetItem(f"{title}\n\n{description}")
item_text.setFlags(item_text.flags() & ~Qt.ItemFlag.ItemIsEditable)
table.setItem(row_idx, 0, item_text)
# RadioButtons für die Bewertungen
button_group = QButtonGroup()
buttons = []
for col_idx, value in enumerate([4, 3, 2, 1]):
radio = QRadioButton()
table.setCellWidget(row_idx, col_idx + 1, radio)
button_group.addButton(radio, value)
buttons.append(radio)
button_group.buttonClicked.connect(lambda checked, bg=button_group, idx=item_id: None)
field_list.append((item_id, button_group))
table.setRowHeight(0, 80)
table.verticalHeader().setVisible(False)
layout.addWidget(table)
return widget
def load_profile(self):
"""Lädt ein vorhandenes Profil aus der Datenbank"""
cursor = self.db.conn.cursor()
cursor.execute("SELECT * FROM profil WHERE profilID = ?", (self.profile_id,))
row = cursor.fetchone()
if row:
self.name_input.setText(row[2] if row[2] else "")
# Gruppe auswählen
group_id = row[4]
index = self.group_combo.findData(group_id)
if index >= 0:
self.group_combo.setCurrentIndex(index)
# Items laden (Spalten 5-40 sind SE-Items, 41-76 sind FE-Items)
for i, (item_id, button_group) in enumerate(self.item_fields):
value = row[5 + i] # item1 beginnt bei Index 5
if value and value != "0":
button = button_group.button(int(value))
if button:
button.setChecked(True)
for i, (item_id, button_group) in enumerate(self.feitem_fields):
value = row[41 + i] # feitem1 beginnt bei Index 41
if value and value != "0":
button = button_group.button(int(value))
if button:
button.setChecked(True)
def save_profile(self):
"""Speichert das Profil in der Datenbank"""
name = self.name_input.text().strip()
if not name:
QMessageBox.warning(self, "Warnung", "Bitte geben Sie einen Namen ein.")
return
# Gruppe ermitteln
group_id = self.group_combo.currentData()
new_group = self.new_group_input.text().strip()
if new_group:
cursor = self.db.conn.cursor()
cursor.execute("INSERT INTO gruppe (name, userID) VALUES (?, ?)", (new_group, self.user_id))
self.db.conn.commit()
group_id = cursor.lastrowid
# Werte sammeln
se_values = []
for item_id, button_group in self.item_fields:
checked_id = button_group.checkedId()
value = checked_id if checked_id != -1 else 2 # Standard: 2 (trifft teilweise zu)
se_values.append(value)
fe_values = []
for item_id, button_group in self.feitem_fields:
checked_id = button_group.checkedId()
value = checked_id if checked_id != -1 else 2
fe_values.append(value)
if self.profile_id:
# Update
fields = ["name = ?", "gruppeID = ?"]
values = [name, group_id]
for i, val in enumerate(se_values, 1):
fields.append(f"item{i} = ?")
values.append(val)
for i, val in enumerate(fe_values, 1):
fields.append(f"feitem{i} = ?")
values.append(val)
values.append(self.profile_id)
query = f"UPDATE profil SET {', '.join(fields)} WHERE profilID = ?"
cursor = self.db.conn.cursor()
cursor.execute(query, values)
else:
# Insert
placeholders = ["?"] * (2 + 36 + 36 + 1) # name, userID, gruppeID, 36 SE, 36 FE
query = f"""INSERT INTO profil (name, userID, gruppeID,
item1,item2,item3,item4,item5,item6,item7,item8,item9,item10,
item11,item12,item13,item14,item15,item16,item17,item18,item19,item20,
item21,item22,item23,item24,item25,item26,item27,item28,item29,item30,
item31,item32,item33,item34,item35,item36,
feitem1,feitem2,feitem3,feitem4,feitem5,feitem6,feitem7,feitem8,feitem9,feitem10,
feitem11,feitem12,feitem13,feitem14,feitem15,feitem16,feitem17,feitem18,feitem19,feitem20,
feitem21,feitem22,feitem23,feitem24,feitem25,feitem26,feitem27,feitem28,feitem29,feitem30,
feitem31,feitem32,feitem33,feitem34,feitem35,feitem36)
VALUES ({','.join(placeholders)})"""
values = [name, self.user_id, group_id] + se_values + fe_values
cursor = self.db.conn.cursor()
cursor.execute(query, values)
self.db.conn.commit()
self.accept()
class ProfileViewDialog(QDialog):
"""Dialog zur Anzeige eines Profils mit Diagramm und Auswertung"""
def __init__(self, db, profile_id, norm_table="hs", parent=None):
super().__init__(parent)
self.db = db
self.profile_id = profile_id
self.norm_table = norm_table
self.init_ui()
self.load_and_calculate()
def init_ui(self):
self.setWindowTitle("Profilansicht")
self.setMinimumSize(900, 800)
main_layout = QVBoxLayout()
# Norm-Tabellen 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.change_norm)
norm_layout.addWidget(norm_label)
norm_layout.addWidget(self.norm_combo)
norm_layout.addStretch()
# Druckansicht Button
self.print_btn = QPushButton("Druckansicht")
self.print_btn.clicked.connect(self.print_view)
norm_layout.addWidget(self.print_btn)
main_layout.addLayout(norm_layout)
# Scrollbereich
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll_widget = QWidget()
scroll_layout = QVBoxLayout(scroll_widget)
# Profil-Header
self.name_label = QLabel()
self.name_label.setStyleSheet("font-size: 18px; font-weight: bold;")
scroll_layout.addWidget(self.name_label)
# Splitter für SE und FE
splitter = QSplitter(Qt.Orientation.Horizontal)
# Selbsteinschätzung
se_widget = QWidget()
se_layout = QVBoxLayout(se_widget)
se_title = QLabel("Selbsteinschätzung")
se_title.setStyleSheet("font-size: 16px; font-weight: bold;")
se_layout.addWidget(se_title)
self.se_table = QTableWidget(6, 5)
self.se_table.setHorizontalHeaderLabels(["1", "2", "3", "4", "5"])
self.se_table.verticalHeader().setVisible(True)
self.se_table.setVerticalHeaderLabels(["Arbeitsverhalten", "Lernverhalten", "Sozialverhalten",
"Fachkompetenz", "Personale Kompetenz", "Methodenkompetenz"])
se_layout.addWidget(self.se_table)
self.se_chart = ProfileChart()
se_layout.addWidget(self.se_chart)
splitter.addWidget(se_widget)
# Fremdeinschätzung
fe_widget = QWidget()
fe_layout = QVBoxLayout(fe_widget)
fe_title = QLabel("Fremdeinschätzung")
fe_title.setStyleSheet("font-size: 16px; font-weight: bold;")
fe_layout.addWidget(fe_title)
self.fe_table = QTableWidget(6, 5)
self.fe_table.setHorizontalHeaderLabels(["1", "2", "3", "4", "5"])
self.fe_table.verticalHeader().setVisible(True)
self.fe_table.setVerticalHeaderLabels(["Arbeitsverhalten", "Lernverhalten", "Sozialverhalten",
"Fachkompetenz", "Personale Kompetenz", "Methodenkompetenz"])
fe_layout.addWidget(self.fe_table)
self.fe_chart = ProfileChart()
fe_layout.addWidget(self.fe_chart)
splitter.addWidget(fe_widget)
scroll_layout.addWidget(splitter)
# Korrelation und Übereinstimmung
stats_group = QGroupBox("Korrelation / Übereinstimmung")
stats_layout = QVBoxLayout()
self.correlation_label = QLabel()
self.agreement_label = QLabel()
self.correlation_text = QTextEdit()
self.correlation_text.setReadOnly(True)
self.correlation_text.setMaximumHeight(150)
stats_layout.addWidget(self.correlation_label)
stats_layout.addWidget(self.agreement_label)
stats_layout.addWidget(self.correlation_text)
stats_group.setLayout(stats_layout)
scroll_layout.addWidget(stats_group)
# Item-Tabelle
items_group = QGroupBox("Itemkodierung")
items_layout = QVBoxLayout()
self.items_table = QTableWidget(36, 3)
self.items_table.setHorizontalHeaderLabels(["Item", "SE", "FE"])
self.items_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
items_layout.addWidget(self.items_table)
items_group.setLayout(items_layout)
scroll_layout.addWidget(items_group)
scroll.setWidget(scroll_widget)
main_layout.addWidget(scroll)
# Schließen-Button
close_btn = QPushButton("Schließen")
close_btn.clicked.connect(self.accept)
main_layout.addWidget(close_btn)
self.setLayout(main_layout)
def change_norm(self):
self.norm_table = self.norm_combo.currentData()
self.load_and_calculate()
def load_and_calculate(self):
"""Lädt das Profil und führt die Berechnungen durch"""
cursor = self.db.conn.cursor()
# Profil laden
cursor.execute("SELECT * FROM profil WHERE profilID = ?", (self.profile_id,))
profile = cursor.fetchone()
if not profile:
return
self.name_label.setText(f"Name: {profile[2] if profile[2] else ''}")
# Items extrahieren
se_items = [int(profile[i]) if profile[i] else 2 for i in range(5, 41)] # item1-item36
fe_items = [int(profile[i]) if profile[i] else 2 for i in range(41, 77)] # feitem1-feitem36
# Normtabellen laden
norm_se = self.load_norm_table(f"normSE{self.norm_table}")
norm_fe = self.load_norm_table(f"normFE{self.norm_table}")
# Summen für Kompetenzen berechnen
se_sums = self.calculate_competence_sums(se_items)
fe_sums = self.calculate_competence_sums(fe_items)
# Profile berechnen (Werte 0-4)
se_profile_values = self.calculate_profile_values(se_sums, norm_se)
fe_profile_values = self.calculate_profile_values(fe_sums, norm_fe)
# Tabellen füllen
self.fill_table(self.se_table, se_profile_values)
self.fill_table(self.fe_table, fe_profile_values)
# Diagramme aktualisieren
self.se_chart.set_values(se_profile_values)
self.fe_chart.set_values(fe_profile_values)
# Korrelation berechnen
correlation = self.calculate_correlation(se_sums, fe_sums)
agreement = self.calculate_agreement(se_items, fe_items)
self.correlation_label.setText(f"Korrelation: {correlation:.2f}")
self.agreement_label.setText(f"Übereinstimmung: {agreement:.1f}%")
# Auswertungstext
evaluation = self.generate_evaluation(se_profile_values, fe_profile_values, correlation)
self.correlation_text.setText(evaluation)
# Item-Tabelle füllen
items_data = self.get_items_data()
self.items_table.setRowCount(36)
for i, (item_name, _) in enumerate(items_data):
self.items_table.setItem(i, 0, QTableWidgetItem(item_name))
self.items_table.setItem(i, 1, QTableWidgetItem(str(se_items[i])))
self.items_table.setItem(i, 2, QTableWidgetItem(str(fe_items[i])))
# Kompetenzwerte in Datenbank speichern
cursor.execute("""UPDATE profil SET
kompetenz1 = ?, kompetenz2 = ?, kompetenz3 = ?,
kompetenz4 = ?, kompetenz5 = ?, kompetenz6 = ?
WHERE profilID = ?""",
(se_profile_values[0]+1, se_profile_values[1]+1, se_profile_values[2]+1,
se_profile_values[3]+1, se_profile_values[4]+1, se_profile_values[5]+1,
self.profile_id))
self.db.conn.commit()
def load_norm_table(self, table_name):
"""Lädt eine Normtabelle aus der Datenbank"""
norm = {i: [] for i in range(1, 7)}
cursor = self.db.conn.cursor()
cursor.execute(f"SELECT kompetenzID, p1, p2, p3, p4, p5 FROM {table_name} ORDER BY kompetenzID")
for row in cursor.fetchall():
kompetenz = row[0]
norm[kompetenz] = [row[1], row[2], row[3], row[4], row[5]]
return norm
def calculate_competence_sums(self, items):
"""Berechnet die Summen für die 6 Kompetenzen"""
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):
"""Berechnet die Profilwerte (0-4) basierend auf den Normtabellen"""
values = [0] * 6
for kompetenz in range(1, 7):
value_found = False
for punkt in range(5):
if sums[kompetenz] < norm[kompetenz][punkt]:
values[kompetenz - 1] = punkt
value_found = True
break
if not value_found:
values[kompetenz - 1] = 4
return values
def fill_table(self, table, values):
"""Füllt eine Tabelle mit X an der entsprechenden Position"""
kompetenzen = ["Arbeitsverhalten", "Lernverhalten", "Sozialverhalten",
"Fachkompetenz", "Personale Kompetenz", "Methodenkompetenz"]
for i, value in enumerate(values):
for j in range(5):
item = QTableWidgetItem("X" if j == value else "")
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
table.setItem(i, j, item)
# Tabelle anpassen
table.resizeColumnsToContents()
def calculate_correlation(self, se_sums, fe_sums):
"""Berechnet die Korrelation zwischen SE und FE"""
se_mean = sum(se_sums[1:7]) / 6
fe_mean = sum(fe_sums[1:7]) / 6
numerator = 0
se_variance = 0
fe_variance = 0
for i in range(1, 7):
se_diff = se_mean - se_sums[i]
fe_diff = fe_mean - fe_sums[i]
numerator += se_diff * fe_diff
se_variance += se_diff ** 2
fe_variance += fe_diff ** 2
if se_variance == 0 or fe_variance == 0:
return 0
return numerator / math.sqrt(se_variance * fe_variance)
def calculate_agreement(self, se_items, fe_items):
"""Berechnet die prozentuale Übereinstimmung zwischen SE und FE"""
matches = sum(1 for s, f in zip(se_items, fe_items) if s == f)
return matches * 100 / 36
def get_items_data(self):
"""Gibt die Item-Namen zurück"""
return [
(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")
]
def generate_evaluation(self, se_values, fe_values, correlation):
"""Generiert einen Auswertungstext"""
texts = []
kompetenzen = ["Arbeitsverhalten", "Lernverhalten", "Sozialverhalten",
"Fachkompetenz", "Personale Kompetenz", "Methodenkompetenz"]
bewertungen = [
["weit unterdurchschnittlich", "unterdurchschnittlich",
"durchschnittlich", "überdurchschnittlich", "weit überdurchschnittlich"]
] * 6
# Selbsteinschätzung
texts.append("Selbsteinschätzung im Verhältnis zur Vergleichsgruppe:\n")
for i, value in enumerate(se_values):
texts.append(f"• {kompetenzen[i]}: {bewertungen[0][value]}")
texts.append("\nFremdeinschätzung im Verhältnis zur Vergleichsgruppe:\n")
for i, value in enumerate(fe_values):
texts.append(f"• {kompetenzen[i]}: {bewertungen[0][value]}")
texts.append(f"\nKorrelation: {correlation:.2f}")
if correlation >= 0.8:
texts.append("Die Fremdeinschätzung und Selbsteinschätzung weisen eine starke Korrelation auf.")
elif correlation >= 0.5:
texts.append("Die Fremdeinschätzung und Selbsteinschätzung weisen eine moderate Korrelation auf.")
elif correlation >= 0.3:
texts.append("Die Fremdeinschätzung und Selbsteinschätzung weisen eine schwache Korrelation auf.")
else:
texts.append("Die Fremdeinschätzung und Selbsteinschätzung weisen keine signifikante Korrelation auf.")
return "\n".join(texts)
def print_view(self):
"""Öffnet eine Druckvorschau (vereinfacht)"""
QMessageBox.information(self, "Druckansicht",
"In einer vollständigen Implementierung würde hier die Druckansicht geöffnet werden.\n"
"Die Daten können auch über Datei > Drucken ausgedruckt werden.")
class MainWindow(QMainWindow):
"""Hauptfenster der DÜSK-Anwendung"""
def __init__(self, db, user_id, session):
super().__init__()
self.db = db
self.user_id = user_id
self.session = session
self.init_ui()
self.load_profiles()
def init_ui(self):
self.setWindowTitle("Düsseldorfer Schülerinventar (DÜSK)")
self.setMinimumSize(800, 600)
# Menüleiste
menubar = self.menuBar()
file_menu = menubar.addMenu("Datei")
logout_action = file_menu.addAction("Abmelden")
logout_action.triggered.connect(self.logout)
exit_action = file_menu.addAction("Beenden")
exit_action.triggered.connect(self.close)
# Hauptwidget
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
# 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: 10px;")
main_layout.addWidget(header)
# Toolbar
toolbar_layout = QHBoxLayout()
insert_btn = QPushButton("Neues Profil einfügen")
insert_btn.clicked.connect(self.insert_profile)
edit_btn = QPushButton("Profile bearbeiten")
edit_btn.clicked.connect(self.edit_profiles)
logout_btn = QPushButton("Abmelden")
logout_btn.clicked.connect(self.logout)
toolbar_layout.addWidget(insert_btn)
toolbar_layout.addWidget(edit_btn)
toolbar_layout.addStretch()
toolbar_layout.addWidget(logout_btn)
main_layout.addLayout(toolbar_layout)
# Profil-Tabelle
self.profile_table = QTableWidget()
self.profile_table.setColumnCount(5)
self.profile_table.setHorizontalHeaderLabels(["Name", "Gruppe", "Löschen", "Profilansicht HS", "Profilansicht FS"])
self.profile_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
self.profile_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
self.profile_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
main_layout.addWidget(self.profile_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;")
main_layout.addWidget(footer)
def load_profiles(self):
"""Lädt alle Profile des Benutzers in die Tabelle"""
cursor = self.db.conn.cursor()
cursor.execute("""
SELECT profil.profilID, profil.name, gruppe.name
FROM profil
LEFT JOIN gruppe ON profil.gruppeID = gruppe.gruppeID
WHERE profil.userID = ?
ORDER BY gruppe.name, profil.name
""", (self.user_id,))
profiles = cursor.fetchall()
self.profile_table.setRowCount(len(profiles))
for row_idx, (profile_id, name, group_name) in enumerate(profiles):
self.profile_table.setItem(row_idx, 0, QTableWidgetItem(name if name else ""))
self.profile_table.setItem(row_idx, 1, QTableWidgetItem(group_name if group_name else ""))
# Löschen-Button
delete_btn = QPushButton("löschen")
delete_btn.clicked.connect(lambda checked, pid=profile_id: self.delete_profile(pid))
self.profile_table.setCellWidget(row_idx, 2, delete_btn)
# Profilansicht HS Button
view_hs_btn = QPushButton("Profilansicht HS")
view_hs_btn.clicked.connect(lambda checked, pid=profile_id, norm="hs": self.view_profile(pid, norm))
self.profile_table.setCellWidget(row_idx, 3, view_hs_btn)
# Profilansicht FS Button
view_fs_btn = QPushButton("Profilansicht FS")
view_fs_btn.clicked.connect(lambda checked, pid=profile_id, norm="fs": self.view_profile(pid, norm))
self.profile_table.setCellWidget(row_idx, 4, view_fs_btn)
self.profile_table.resizeRowsToContents()
def delete_profile(self, profile_id):
"""Löscht ein Profil nach Bestätigung"""
reply = QMessageBox.question(self, "Löschen bestätigen",
"Möchten Sie dieses Profil wirklich löschen?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
if reply == QMessageBox.StandardButton.Yes:
cursor = self.db.conn.cursor()
cursor.execute("DELETE FROM profil WHERE profilID = ?", (profile_id,))
self.db.conn.commit()
self.load_profiles()
def insert_profile(self):
"""Öffnet den Dialog zum Einfügen eines neuen Profils"""
dialog = ProfileEditDialog(self.db, self.user_id, self.session)
if dialog.exec():
self.load_profiles()
def edit_profiles(self):
"""Öffnet den Dialog zum Bearbeiten eines ausgewählten Profils"""
current_row = self.profile_table.currentRow()
if current_row >= 0:
# Profil-ID aus der ersten Spalte ermitteln (über die Daten)
# Einfacher: Ein neues Fenster mit allen Profilen zum Bearbeiten
# Hier: Bearbeiten des ausgewählten Profils
profile_id = None
cursor = self.db.conn.cursor()
cursor.execute("SELECT profilID FROM profil WHERE userID = ? ORDER BY name LIMIT 1 OFFSET ?",
(self.user_id, current_row))
row = cursor.fetchone()
if row:
profile_id = row[0]
if profile_id:
dialog = ProfileEditDialog(self.db, self.user_id, self.session, profile_id)
if dialog.exec():
self.load_profiles()
else:
QMessageBox.information(self, "Hinweis", "Bitte wählen Sie ein Profil aus der Liste aus.")
def view_profile(self, profile_id, norm_table):
"""Öffnet die Profilansicht"""
dialog = ProfileViewDialog(self.db, profile_id, norm_table)
dialog.exec()
def logout(self):
"""Meldet den Benutzer ab und kehrt zum Login zurück"""
cursor = self.db.conn.cursor()
cursor.execute("DELETE FROM anmeldung WHERE userID = ? AND session = ?",
(self.user_id, self.session))
self.db.conn.commit()
self.close()
class Database:
"""Datenbankverwaltung für SQLite"""
def __init__(self, db_path="duesk.db"):
self.db_path = db_path
self.conn = None
self.init_database()
def connect(self):
"""Stellt die Datenbankverbindung her"""
self.conn = sqlite3.connect(self.db_path)
self.conn.row_factory = sqlite3.Row
return self.conn
def init_database(self):
"""Initialisiert die Datenbank mit allen benötigten Tabellen"""
self.connect()
cursor = self.conn.cursor()
# Tabelle: user
cursor.execute("""
CREATE TABLE IF NOT EXISTS user (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
user TEXT,
pass TEXT
)
""")
# Tabelle: anmeldung
cursor.execute("""
CREATE TABLE IF NOT EXISTS anmeldung (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
userID INTEGER,
session TEXT,
FOREIGN KEY (userID) REFERENCES user(ID)
)
""")
# Tabelle: gruppe
cursor.execute("""
CREATE TABLE IF NOT EXISTS gruppe (
gruppeID INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
userID INTEGER,
FOREIGN KEY (userID) REFERENCES user(ID)
)
""")
# Tabelle: profil
cursor.execute("""
CREATE TABLE IF NOT EXISTS profil (
profilID INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
userID INTEGER,
gruppeID INTEGER,
item1 INTEGER, item2 INTEGER, item3 INTEGER, item4 INTEGER, item5 INTEGER,
item6 INTEGER, item7 INTEGER, item8 INTEGER, item9 INTEGER, item10 INTEGER,
item11 INTEGER, item12 INTEGER, item13 INTEGER, item14 INTEGER, item15 INTEGER,
item16 INTEGER, item17 INTEGER, item18 INTEGER, item19 INTEGER, item20 INTEGER,
item21 INTEGER, item22 INTEGER, item23 INTEGER, item24 INTEGER, item25 INTEGER,
item26 INTEGER, item27 INTEGER, item28 INTEGER, item29 INTEGER, item30 INTEGER,
item31 INTEGER, item32 INTEGER, item33 INTEGER, item34 INTEGER, item35 INTEGER,
item36 INTEGER,
feitem1 INTEGER, feitem2 INTEGER, feitem3 INTEGER, feitem4 INTEGER, feitem5 INTEGER,
feitem6 INTEGER, feitem7 INTEGER, feitem8 INTEGER, feitem9 INTEGER, feitem10 INTEGER,
feitem11 INTEGER, feitem12 INTEGER, feitem13 INTEGER, feitem14 INTEGER, feitem15 INTEGER,
feitem16 INTEGER, feitem17 INTEGER, feitem18 INTEGER, feitem19 INTEGER, feitem20 INTEGER,
feitem21 INTEGER, feitem22 INTEGER, feitem23 INTEGER, feitem24 INTEGER, feitem25 INTEGER,
feitem26 INTEGER, feitem27 INTEGER, feitem28 INTEGER, feitem29 INTEGER, feitem30 INTEGER,
feitem31 INTEGER, feitem32 INTEGER, feitem33 INTEGER, feitem34 INTEGER, feitem35 INTEGER,
feitem36 INTEGER,
kompetenz1 INTEGER, kompetenz2 INTEGER, kompetenz3 INTEGER,
kompetenz4 INTEGER, kompetenz5 INTEGER, kompetenz6 INTEGER,
FOREIGN KEY (userID) REFERENCES user(ID),
FOREIGN KEY (gruppeID) REFERENCES gruppe(gruppeID)
)
""")
# Normtabellen für Hauptschule
cursor.execute("""
CREATE TABLE IF NOT EXISTS normSEhs (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
kompetenzID INTEGER,
p1 INTEGER, p2 INTEGER, p3 INTEGER, p4 INTEGER, p5 INTEGER
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS normFEhs (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
kompetenzID INTEGER,
p1 INTEGER, p2 INTEGER, p3 INTEGER, p4 INTEGER, p5 INTEGER
)
""")
# Normtabellen für Förderschule
cursor.execute("""
CREATE TABLE IF NOT EXISTS normSEfs (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
kompetenzID INTEGER,
p1 INTEGER, p2 INTEGER, p3 INTEGER, p4 INTEGER, p5 INTEGER
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS normFEfs (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
kompetenzID INTEGER,
p1 INTEGER, p2 INTEGER, p3 INTEGER, p4 INTEGER, p5 INTEGER
)
""")
# Normdaten einfügen, falls nicht vorhanden
self.insert_norm_data()
self.conn.commit()
def insert_norm_data(self):
"""Fügt die Normdaten für die Tabellen ein (Beispieldaten)"""
cursor = self.conn.cursor()
# Prüfen ob bereits Daten vorhanden sind
cursor.execute("SELECT COUNT(*) FROM normSEhs")
if cursor.fetchone()[0] == 0:
# Beispiel-Normdaten für Hauptschule
norm_data_hs = {
1: [10, 20, 30, 40, 50], # Arbeitsverhalten
2: [10, 20, 30, 40, 50], # Lernverhalten
3: [10, 20, 30, 40, 50], # Sozialverhalten
4: [10, 20, 30, 40, 50], # Fachkompetenz
5: [10, 20, 30, 40, 50], # Personale Kompetenz
6: [10, 20, 30, 40, 50] # Methodenkompetenz
}
for kompetenz, values in norm_data_hs.items():
cursor.execute("""
INSERT INTO normSEhs (kompetenzID, p1, p2, p3, p4, p5)
VALUES (?, ?, ?, ?, ?, ?)
""", (kompetenz, values[0], values[1], values[2], values[3], values[4]))
cursor.execute("""
INSERT INTO normFEhs (kompetenzID, p1, p2, p3, p4, p5)
VALUES (?, ?, ?, ?, ?, ?)
""", (kompetenz, values[0], values[1], values[2], values[3], values[4]))
# Normdaten für Förderschule (leicht niedrigere Schwellen)
norm_data_fs = {
1: [8, 16, 24, 32, 40],
2: [8, 16, 24, 32, 40],
3: [8, 16, 24, 32, 40],
4: [8, 16, 24, 32, 40],
5: [8, 16, 24, 32, 40],
6: [8, 16, 24, 32, 40]
}
for kompetenz, values in norm_data_fs.items():
cursor.execute("""
INSERT INTO normSEfs (kompetenzID, p1, p2, p3, p4, p5)
VALUES (?, ?, ?, ?, ?, ?)
""", (kompetenz, values[0], values[1], values[2], values[3], values[4]))
cursor.execute("""
INSERT INTO normFEfs (kompetenzID, p1, p2, p3, p4, p5)
VALUES (?, ?, ?, ?, ?, ?)
""", (kompetenz, values[0], values[1], values[2], values[3], values[4]))
self.conn.commit()
def close(self):
"""Schließt die Datenbankverbindung"""
if self.conn:
self.conn.close()
def main():
"""Hauptfunktion der Anwendung"""
app = QApplication(sys.argv)
# Datenbank initialisieren
db = Database()
# Login-Dialog anzeigen
login_dialog = LoginDialog(db)
if login_dialog.exec() == QDialog.DialogCode.Accepted:
# Hauptfenster öffnen
window = MainWindow(db, login_dialog.user_id, login_dialog.session)
window.show()
sys.exit(app.exec())
else:
sys.exit(0)
if __name__ == "__main__":
main()