Inhalt
Aktueller Ordner:
duesseldorfer-schuelerinventar-python-clientduesk_client_minimal_tinker.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
DÜSK - Tkinter Version (keine PyQt6 nötig)
"""
import tkinter as tk
from tkinter import ttk, messagebox
import requests
import math
import threading
API_BASE_URL = "https://paul-koop.org/api/"
# Normwerte
NORM_SE_HS = {1: [21.33, 25.33, 29.33, 33.32, 37.32], 2: [20.87, 24.95, 29.03, 33.13, 37.18],
3: [17.93, 21.37, 24.80, 28.23, 31.67], 4: [13.98, 17.71, 21.44, 25.17, 28.90],
5: [24.60, 28.55, 33.04, 37.53, 42.01], 6: [15.53, 18.97, 22.40, 25.83, 29.27]}
NORM_FE_HS = {1: [12.66, 18.16, 23.66, 29.16, 34.66], 2: [13.33, 18.42, 23.51, 28.60, 33.69],
3: [10.75, 15.41, 20.07, 24.73, 29.39], 4: [14.22, 15.30, 16.38, 17.46, 18.54],
5: [14.12, 20.21, 26.30, 32.39, 38.48], 6: [10.53, 14.51, 18.49, 22.47, 26.45]}
NORM_SE_FS = {1: [17.54, 24.03, 30.53, 37.02, 43.51], 2: [17.80, 24.26, 30.73, 37.19, 43.65],
3: [18.03, 22.41, 26.79, 31.17, 35.55], 4: [14.28, 15.55, 16.83, 18.10, 19.37],
5: [20.69, 27.49, 34.29, 41.09, 47.89], 6: [12.44, 18.06, 23.68, 29.29, 34.91]}
NORM_FE_FS = {1: [15.30, 19.79, 24.28, 28.77, 33.26], 2: [14.63, 18.94, 23.25, 27.56, 31.87],
3: [14.62, 17.81, 21.00, 24.19, 27.38], 4: [15.00, 15.55, 16.10, 16.65, 17.20],
5: [18.44, 22.61, 26.78, 30.95, 35.12], 6: [9.79, 13.97, 18.15, 22.33, 26.51]}
class DueskApp:
def __init__(self, root):
self.root = root
self.root.title("DÜSK - Düsseldorfer Schülerinventar")
self.root.geometry("1000x600")
self.user_id = None
self.session = None
self.profiles = []
# Login Frame
self.login_frame = ttk.Frame(root)
self.login_frame.pack(fill="both", expand=True)
ttk.Label(self.login_frame, text="DÜSK - Düsseldorfer Schülerinventar",
font=("Arial", 20, "bold")).pack(pady=20)
form = ttk.Frame(self.login_frame)
form.pack(pady=20)
ttk.Label(form, text="Benutzername:").grid(row=0, column=0, padx=5, pady=5)
self.username_entry = ttk.Entry(form)
self.username_entry.grid(row=0, column=1, padx=5, pady=5)
self.username_entry.insert(0, "gast")
ttk.Label(form, text="Passwort:").grid(row=1, column=0, padx=5, pady=5)
self.password_entry = ttk.Entry(form, show="*")
self.password_entry.grid(row=1, column=1, padx=5, pady=5)
self.password_entry.insert(0, "gast")
ttk.Button(self.login_frame, text="Anmelden", command=self.do_login).pack(pady=20)
# Main Frame (wird nach Login sichtbar)
self.main_frame = ttk.Frame(root)
# Toolbar
toolbar = ttk.Frame(self.main_frame)
toolbar.pack(fill="x", padx=5, pady=5)
ttk.Button(toolbar, text="Aktualisieren", command=self.load_profiles).pack(side="left", padx=5)
ttk.Button(toolbar, text="Abmelden", command=self.logout).pack(side="left", padx=5)
# Tabelle
self.tree = ttk.Treeview(self.main_frame, columns=("name", "gruppe", "id"), show="headings")
self.tree.heading("name", text="Name")
self.tree.heading("gruppe", text="Gruppe")
self.tree.heading("id", text="ProfilID")
self.tree.column("name", width=200)
self.tree.column("gruppe", width=150)
self.tree.column("id", width=80)
self.tree.pack(fill="both", expand=True, padx=5, pady=5)
# Doppelklick auf Profil
self.tree.bind("<Double-1>", self.on_profile_double_click)
# Statusbar
self.status_var = tk.StringVar()
self.status_var.set("Bereit")
statusbar = ttk.Label(self.main_frame, textvariable=self.status_var, relief="sunken")
statusbar.pack(fill="x", padx=5, pady=5)
def do_login(self):
username = self.username_entry.get()
password = self.password_entry.get()
try:
resp = requests.post(API_BASE_URL + "api_login.php",
json={"username": username, "password": password})
data = resp.json()
if data.get('success') or data.get('userID'):
self.user_id = data['userID']
self.session = data['session']
self.login_frame.pack_forget()
self.main_frame.pack(fill="both", expand=True)
self.load_profiles()
else:
messagebox.showerror("Fehler", "Anmeldung fehlgeschlagen")
except Exception as e:
messagebox.showerror("Fehler", f"Verbindungsfehler: {e}")
def load_profiles(self):
self.status_var.set("Lade Profile...")
self.root.update()
try:
headers = {'X-User-ID': str(self.user_id), 'X-Session': self.session}
resp = requests.get(API_BASE_URL + "api_profiles.php", headers=headers)
profiles = resp.json()
if isinstance(profiles, list):
self.profiles = profiles
# Tabelle leeren
for item in self.tree.get_children():
self.tree.delete(item)
# Einträge hinzufügen
for p in profiles:
self.tree.insert("", "end", values=(p.get('name', ''), p.get('gruppename', ''), p.get('profilID', '')))
self.status_var.set(f"{len(profiles)} Profile geladen")
else:
self.status_var.set("Keine Profile gefunden")
except Exception as e:
self.status_var.set(f"Fehler: {e}")
def on_profile_double_click(self, event):
selection = self.tree.selection()
if selection:
item = self.tree.item(selection[0])
profile_id = item['values'][2]
self.show_profile(profile_id)
def show_profile(self, profile_id):
# Profil laden
headers = {'X-User-ID': str(self.user_id), 'X-Session': self.session}
resp = requests.get(API_BASE_URL + "api_profiles.php", params={"id": profile_id}, headers=headers)
profile = resp.json()
if isinstance(profile, dict):
self.open_profile_window(profile)
def open_profile_window(self, profile):
"""Öffnet ein neues Fenster mit der Profilansicht"""
win = tk.Toplevel(self.root)
win.title(f"Profil: {profile.get('name', 'Unbekannt')}")
win.geometry("900x700")
notebook = ttk.Notebook(win)
notebook.pack(fill="both", expand=True, padx=10, pady=10)
# Tab: Selbsteinschätzung
se_frame = ttk.Frame(notebook)
notebook.add(se_frame, text="Selbsteinschätzung")
self.create_competence_view(se_frame, profile, "se")
# Tab: Fremdeinschätzung
fe_frame = ttk.Frame(notebook)
notebook.add(fe_frame, text="Fremdeinschätzung")
self.create_competence_view(fe_frame, profile, "fe")
# Tab: Statistik
stats_frame = ttk.Frame(notebook)
notebook.add(stats_frame, text="Statistik")
self.create_stats_view(stats_frame, profile)
# Tab: Alle Items
items_frame = ttk.Frame(notebook)
notebook.add(items_frame, text="Alle Items")
self.create_items_view(items_frame, profile)
def create_competence_view(self, parent, profile, prefix):
"""Erstellt die Ansicht für Kompetenzen (SE oder FE)"""
# Normauswahl
norm_frame = ttk.Frame(parent)
norm_frame.pack(fill="x", padx=10, pady=5)
ttk.Label(norm_frame, text="Normtabelle:").pack(side="left", padx=5)
norm_var = tk.StringVar(value="HS")
hs_radio = ttk.Radiobutton(norm_frame, text="Hauptschule (HS)", variable=norm_var, value="HS")
fs_radio = ttk.Radiobutton(norm_frame, text="Förderschule (FS)", variable=norm_var, value="FS")
hs_radio.pack(side="left", padx=5)
fs_radio.pack(side="left", padx=5)
# Tabelle für Kompetenzen
table_frame = ttk.Frame(parent)
table_frame.pack(fill="both", expand=True, padx=10, pady=5)
tree = ttk.Treeview(table_frame, columns=("kompetenz", "1", "2", "3", "4", "5"), show="headings", height=6)
tree.heading("kompetenz", text="Kompetenz")
tree.heading("1", text="1")
tree.heading("2", text="2")
tree.heading("3", text="3")
tree.heading("4", text="4")
tree.heading("5", text="5")
tree.column("kompetenz", width=150)
for col in ["1", "2", "3", "4", "5"]:
tree.column(col, width=50, anchor="center")
tree.pack(fill="both", expand=True)
# Update-Funktion bei Normwechsel
def update_view(*args):
# Items extrahieren
items = [int(profile.get(f'{prefix}item{i}', 2)) for i in range(1, 37)]
# Summen berechnen
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]
# Normen auswählen
if norm_var.get() == "HS":
norm = NORM_SE_HS if prefix == "se" else NORM_FE_HS
else:
norm = NORM_SE_FS if prefix == "se" else NORM_FE_FS
# Profilwerte berechnen (0-4)
values = [0] * 6
kompetenzen = ["Arbeitsverhalten", "Lernverhalten", "Sozialverhalten",
"Fachkompetenz", "Personale Kompetenz", "Methodenkompetenz"]
for k in range(1, 7):
placed = False
for p in range(5):
if sums[k] < norm[k][p]:
values[k-1] = p
placed = True
break
if not placed:
values[k-1] = 4
# Tabelle füllen
for item in tree.get_children():
tree.delete(item)
for i, (komp, val) in enumerate(zip(kompetenzen, values)):
row = [komp]
for j in range(5):
row.append("X" if j == val else "")
tree.insert("", "end", values=row)
norm_var.trace_add("write", update_view)
update_view()
def create_stats_view(self, parent, profile):
"""Statistik-Ansicht"""
frame = ttk.Frame(parent)
frame.pack(fill="both", expand=True, padx=10, pady=10)
# Items extrahieren
se_items = [int(profile.get(f'item{i}', 2)) for i in range(1, 37)]
fe_items = [int(profile.get(f'feitem{i}', 2)) for i in range(1, 37)]
# Summen für Korrelation (vereinfacht)
se_sums = [0] * 6
fe_sums = [0] * 6
for i in range(6):
se_sums[i] = sum(se_items[i*10:(i+1)*10]) if i < 3 else sum(se_items[28:36]) if i == 3 else 0
fe_sums[i] = sum(fe_items[i*10:(i+1)*10]) if i < 3 else sum(fe_items[28:36]) if i == 3 else 0
# Korrelation berechnen
def calc_corr(a, b):
ma, mb = sum(a)/6, sum(b)/6
num = sum((a[i]-ma)*(b[i]-mb) for i in range(6))
den = math.sqrt(sum((a[i]-ma)**2 for i in range(6)) * sum((b[i]-mb)**2 for i in range(6)))
return num/den if den != 0 else 0
corr = calc_corr(se_sums, fe_sums)
agree = sum(1 for s, f in zip(se_items, fe_items) if s == f) * 100 / 36
ttk.Label(frame, text=f"Korrelation: {corr:.2f}", font=("Arial", 12)).pack(pady=5)
ttk.Label(frame, text=f"Übereinstimmung: {agree:.1f}%", font=("Arial", 12)).pack(pady=5)
# Interpretation
text = tk.Text(frame, wrap="word", height=10)
text.pack(fill="both", expand=True, pady=10)
interpretation = f"Die Korrelation von {corr:.2f} bedeutet: "
if corr >= 0.8:
interpretation += "Sehr gute Übereinstimmung zwischen Selbst- und Fremdeinschätzung."
elif corr >= 0.5:
interpretation += "Mäßige Übereinstimmung zwischen Selbst- und Fremdeinschätzung."
elif corr >= 0.3:
interpretation += "Schwache Übereinstimmung zwischen Selbst- und Fremdeinschätzung."
else:
interpretation += "Keine signifikante Übereinstimmung zwischen Selbst- und Fremdeinschätzung."
text.insert("1.0", interpretation)
text.config(state="disabled")
def create_items_view(self, parent, profile):
"""Alle Items anzeigen"""
frame = ttk.Frame(parent)
frame.pack(fill="both", expand=True, padx=10, pady=10)
# Scrollbare Tabelle
container = ttk.Frame(frame)
container.pack(fill="both", expand=True)
scroll_y = ttk.Scrollbar(container, orient="vertical")
scroll_x = ttk.Scrollbar(container, orient="horizontal")
tree = ttk.Treeview(container, columns=("item", "se", "fe"), show="headings",
yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set)
tree.heading("item", text="Item")
tree.heading("se", text="Selbst")
tree.heading("fe", text="Fremd")
tree.column("item", width=250)
tree.column("se", width=50, anchor="center")
tree.column("fe", width=50, anchor="center")
scroll_y.config(command=tree.yview)
scroll_x.config(command=tree.xview)
scroll_y.pack(side="right", fill="y")
scroll_x.pack(side="bottom", fill="x")
tree.pack(fill="both", expand=True)
items = [
"Zuverlässigkeit", "Arbeitstempo", "Arbeitsplanung", "Organisationsfähigkeit",
"Geschicklichkeit", "Ordnung", "Sorgfalt", "Kreativität", "Problemlösungsfähigkeit",
"Abstraktionsvermögen", "Selbstständigkeit", "Belastbarkeit", "Konzentrationsfähigkeit",
"Verantwortungsbewusstsein", "Eigeninitiative", "Leistungsbereitschaft", "Auffassungsgabe",
"Merkfähigkeit", "Motivationsfähigkeit", "Reflektionsfähigkeit", "Teamfähigkeit",
"Hilfsbereitschaft", "Kontaktfähigkeit", "Respektvoller Umgang", "Kommunikationsfähigkeit",
"Einfühlungsvermögen", "Konfliktfähigkeit", "Kritikfähigkeit", "Schreiben", "Lesen",
"Mathematik", "Naturwissenschaft", "Fremdsprachen", "Präsentationsfähigkeit",
"PC Kenntnisse", "Fächerübergreifendes Denken"
]
for i, name in enumerate(items, 1):
se_val = profile.get(f'item{i}', 2)
fe_val = profile.get(f'feitem{i}', 2)
tree.insert("", "end", values=(name, se_val, fe_val))
def logout(self):
try:
headers = {'X-User-ID': str(self.user_id), 'X-Session': self.session}
requests.post(API_BASE_URL + "api_logout.php", headers=headers)
except:
pass
self.main_frame.pack_forget()
self.login_frame.pack(fill="both", expand=True)
self.user_id = None
self.session = None
def main():
root = tk.Tk()
app = DueskApp(root)
root.mainloop()
if __name__ == "__main__":
main()