Zwei echte Fehler aus der Pruefkette behoben
## 1. Der Temp-Schutz war geschrieben, aber nicht angeschlossen
_ist_temp() stand seit heute Vormittag in ablage.py, ausfuehrlich begruendet
("Ein Merkzettel, der dorthin zeigt, wird deshalb verworfen") - und wurde von
NIEMANDEM gerufen. Der Schutz war ein Kommentar, keine Massnahme.
Zwei Pruefagenten haben das unabhaengig voneinander gefunden, einer mit
Messung: das Journal landete weiterhin im Temp-Ordner. Windows raeumt den von
selbst auf; es geht um aufbewahrungspflichtige Aufzeichnungen (§ 147 AO).
⚠️ Das ist die schlimmste Sorte Fehler - ein Kommentar, der eine
Schutzmassnahme zusichert, die es nicht gibt. Wer die Datei liest, haelt die
Falle fuer geschlossen. Und ich hatte sie dem Auftraggeber als behoben
gemeldet.
Behoben: _gemerkter_ordner() prueft jetzt wirklich.
pruef_temp_merkzettel.py sichert es ab - er prueft nicht, ob die FUNKTION
existiert, sondern ob sie WIRKT. Dazu die Gegenprobe, dass ein GUELTIGER
Merkzettel weiter befolgt wird: sonst haette der Schutz jemandem sein
Netzlaufwerk weggenommen.
⚠️ Im ersten Anlauf war dieser Pruefstand rot - weil ich seine Spielwiese mit
tempfile.mkdtemp() angelegt hatte und die selbst im Temp liegt. Wer das
Verhalten IM Temp-Ordner prueft, darf seine Spielwiese nicht dort aufbauen.
## 2. Der Darstellungsdialog zerstoerte sich beim ersten Klick selbst
Gleiche Ursache und gleiche Behebung wie im Rechnungstool: _oberflaeche_neu()
loeschte alle Kinder des Hauptfensters, und ein tk.Toplevel ist eines.
pruef_darstellung drueckt jetzt den Radioknopf statt cfg von Hand zu setzen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
This commit is contained in:
parent
dcc556217b
commit
039a23478f
4 changed files with 190 additions and 1 deletions
|
|
@ -103,6 +103,14 @@ def _gemerkter_ordner():
|
|||
if not pfad or "\x00" in pfad or not os.path.isabs(pfad):
|
||||
log.warning("Merkzettel unbrauchbar - es wird neu angelegt")
|
||||
return None, None
|
||||
# HIER wird _ist_temp tatsaechlich benutzt. Bis zum 07.09.2026 stand die
|
||||
# Funktion samt Begruendung da und wurde von niemandem gerufen - der
|
||||
# Schutz war ein Kommentar, keine Massnahme. Zwei Pruefagenten haben das
|
||||
# unabhaengig gefunden, einer mit Messung: das Journal landete weiter im
|
||||
# Temp-Ordner.
|
||||
if _ist_temp(pfad):
|
||||
log.warning("Merkzettel zeigt in den Temp-Ordner - verworfen: %s", pfad)
|
||||
return None, None
|
||||
try:
|
||||
laufwerk = os.path.splitdrive(pfad)[0]
|
||||
erreichbar = (not laufwerk) or os.path.exists(
|
||||
|
|
|
|||
13
app.py
13
app.py
|
|
@ -216,8 +216,19 @@ class App(theme.ThemeMixin, tk.Tk):
|
|||
except tk.TclError:
|
||||
pass
|
||||
|
||||
# Fremde Fenster stehen lassen! Ein tk.Toplevel IST ein Kind des
|
||||
# Hauptfensters - der offene Darstellungsdialog wurde hier bisher
|
||||
# mitgeloescht, und die Zeile danach fasste eine Leiche an
|
||||
# ("invalid command name .!toplevel"). Gefunden am 07.09.2026 von der
|
||||
# Pruefkette, mit dem echten Bedienweg gemessen: Klick auf "Dunkel"
|
||||
# -> Farbe wechselt, Fenster ist weg, Traceback im Log.
|
||||
#
|
||||
# Der Pruefstand hat es nicht gefunden, weil er cfg["darstellung"] von
|
||||
# Hand setzte und _oberflaeche_neu() direkt rief - also alles ausser
|
||||
# dem Weg, den ein Mensch geht.
|
||||
for kind in self.winfo_children():
|
||||
kind.destroy()
|
||||
if not isinstance(kind, tk.Toplevel):
|
||||
kind.destroy()
|
||||
|
||||
self._apply_theme()
|
||||
self._menue()
|
||||
|
|
|
|||
|
|
@ -178,6 +178,66 @@ pruefe(d.winfo_rooty() >= f.winfo_rooty() - 40,
|
|||
d.destroy()
|
||||
pumpen(0.3)
|
||||
|
||||
# ---- Der Dialog muss den Klick UEBERLEBEN -------------------------------
|
||||
# Gefunden am 07.09.2026 von der Pruefkette: _oberflaeche_neu() loeschte alle
|
||||
# Kinder des Hauptfensters - und ein Toplevel IST ein Kind. Der offene Dialog
|
||||
# wurde also beim ersten Klick mitgeloescht, die Zeile danach fasste eine
|
||||
# Leiche an ("invalid command name .!toplevel").
|
||||
#
|
||||
# Dieser Pruefstand hat es NICHT gefunden, weil er cfg["darstellung"] von Hand
|
||||
# setzte. Hier wird jetzt der Radioknopf gedrueckt - derselbe Weg, den ein
|
||||
# Mensch geht.
|
||||
d = f.dlg_darstellung()
|
||||
pumpen(1.0)
|
||||
|
||||
|
||||
def radioknoepfe(fenster):
|
||||
"""Alle Radiobuttons unterhalb eines Fensters, mit ihrer Beschriftung."""
|
||||
treffer = []
|
||||
|
||||
def durchgehen(w):
|
||||
for kind in w.winfo_children():
|
||||
if kind.winfo_class() == "TRadiobutton":
|
||||
try:
|
||||
treffer.append((str(kind.cget("text")), kind))
|
||||
except tk.TclError:
|
||||
pass
|
||||
durchgehen(kind)
|
||||
|
||||
durchgehen(fenster)
|
||||
return treffer
|
||||
|
||||
|
||||
knoepfe = radioknoepfe(d)
|
||||
pruefe(len(knoepfe) == len(theme.MODI),
|
||||
f"der Dialog zeigt alle {len(theme.MODI)} Moeglichkeiten "
|
||||
f"({[t for t, _ in knoepfe]})")
|
||||
|
||||
ziel = next((w for t, w in knoepfe if t == theme.MODUS_NAMEN["dunkel"]), None)
|
||||
pruefe(ziel is not None, "der Knopf 'Dunkel' ist da")
|
||||
if ziel is not None:
|
||||
ziel.invoke() # der echte Klick
|
||||
pumpen(1.2)
|
||||
pruefe(bool(d.winfo_exists()),
|
||||
"der Dialog lebt nach dem Klick noch")
|
||||
pruefe(f._dark_mode is True,
|
||||
f"und die Darstellung ist umgestellt (Hintergrund {f.col_bg})")
|
||||
# Und noch einmal, denn genau das war vorher unmoeglich:
|
||||
hell = next((w for t, w in knoepfe if t == theme.MODUS_NAMEN["hell"]), None)
|
||||
if hell is not None:
|
||||
try:
|
||||
hell.invoke()
|
||||
pumpen(1.0)
|
||||
pruefe(f._dark_mode is False,
|
||||
"ein ZWEITER Klick im selben Dialog wirkt auch")
|
||||
except tk.TclError as e:
|
||||
pruefe(False, f"der zweite Klick scheiterte ({e})")
|
||||
try:
|
||||
d.destroy()
|
||||
except tk.TclError:
|
||||
pass
|
||||
pumpen(0.3)
|
||||
|
||||
# ---- Zurueck auf automatisch --------------------------------------------
|
||||
f.cfg["darstellung"] = "auto"
|
||||
f._oberflaeche_neu()
|
||||
|
|
|
|||
110
pruef_temp_merkzettel.py
Normal file
110
pruef_temp_merkzettel.py
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Pruefstand: ein Merkzettel in den Temp-Ordner wird verworfen.
|
||||
|
||||
DER FALL, DER DAZU GEFUEHRT HAT (07.09.2026)
|
||||
--------------------------------------------
|
||||
Die installierte Fassung fuehrte ihr Journal in
|
||||
`%TEMP%/abnahme_bst/Daten` - haengengeblieben aus einem Abnahmelauf vom 03.09.
|
||||
Windows raeumt den Temp-Ordner von selbst auf (Datentraegerbereinigung,
|
||||
Speicheroptimierung); das Journal waere ohne Vorwarnung verschwunden. Es geht
|
||||
um aufbewahrungspflichtige Aufzeichnungen, zehn Jahre (§ 147 AO).
|
||||
|
||||
Ich habe daraufhin `_ist_temp()` geschrieben, ausfuehrlich begruendet - und
|
||||
NICHT ANGESCHLOSSEN. Die Funktion stand da und wurde von niemandem gerufen;
|
||||
der Schutz war ein Kommentar, keine Massnahme. Zwei Pruefagenten haben das
|
||||
unabhaengig voneinander gefunden, einer davon mit einer Messung, die zeigte,
|
||||
dass das Journal weiterhin im Temp landet.
|
||||
|
||||
⚠️ Das ist die schlimmste Sorte Fehler: ein Kommentar, der eine Schutzmassnahme
|
||||
zusichert, die es nicht gibt. Wer die Datei liest, haelt die Falle fuer
|
||||
geschlossen. Deshalb dieser Pruefstand - er prueft nicht, ob die FUNKTION
|
||||
existiert, sondern ob sie WIRKT.
|
||||
|
||||
Angefasst wird nichts Echtes: LOCALAPPDATA zeigt waehrend des Laufs auf einen
|
||||
eigenen Ordner, der danach wieder verschwindet.
|
||||
|
||||
⚠️ Dieser Ordner liegt AUSSERHALB des Temp-Ordners - im ersten Anlauf hatte
|
||||
ich ihn mit tempfile.mkdtemp() angelegt, und der liegt selbst im Temp. Dann
|
||||
erkennt _ist_temp auch die Vergleichspfade als Temp und der Pruefstand meldet
|
||||
Fehler, die es nicht gibt. Wer das Verhalten IM Temp-Ordner prueft, darf seine
|
||||
Spielwiese nicht dort aufbauen.
|
||||
"""
|
||||
import importlib
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
HIER = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HIER)
|
||||
|
||||
fehler = []
|
||||
|
||||
|
||||
def pruefe(bedingung, text):
|
||||
zeile = (" OK | " if bedingung else "FEHLER | ") + text
|
||||
kod = sys.stdout.encoding or "utf-8"
|
||||
print(zeile.encode(kod, "replace").decode(kod))
|
||||
if not bedingung:
|
||||
fehler.append(text)
|
||||
|
||||
|
||||
echtes_lad = os.environ.get("LOCALAPPDATA", "")
|
||||
spielwiese = os.path.join(os.path.expanduser("~"), ".pruef_merkzettel")
|
||||
shutil.rmtree(spielwiese, ignore_errors=True)
|
||||
os.makedirs(spielwiese, exist_ok=True)
|
||||
os.environ["LOCALAPPDATA"] = spielwiese
|
||||
|
||||
# Als EXE tun, sonst nimmt ablage.py den Quellordner und der Merkzettel
|
||||
# spielt gar keine Rolle.
|
||||
sys.frozen = True
|
||||
sys.argv = [os.path.join(spielwiese, "Steuerrechnungstool.exe")]
|
||||
|
||||
import ablage # noqa: E402
|
||||
importlib.reload(ablage)
|
||||
|
||||
# ---- 1. Die Funktion erkennt einen Temp-Pfad ---------------------------
|
||||
temp = tempfile.gettempdir()
|
||||
pruefe(ablage._ist_temp(os.path.join(temp, "abnahme_bst", "Daten")),
|
||||
"ein Pfad im Temp-Ordner wird als solcher erkannt")
|
||||
pruefe(not ablage._ist_temp(os.path.join(spielwiese, "ravokk")),
|
||||
"ein normaler Pfad nicht")
|
||||
|
||||
# ---- 2. Und sie WIRKT - das ist der Punkt ------------------------------
|
||||
# Genau so sah der Merkzettel bei ihm aus.
|
||||
falscher = os.path.join(temp, "abnahme_bst_pruefstand", "Daten")
|
||||
os.makedirs(falscher, exist_ok=True)
|
||||
zeiger = ablage._zeiger_datei()
|
||||
os.makedirs(os.path.dirname(zeiger), exist_ok=True)
|
||||
with open(zeiger, "w", encoding="utf-8") as f:
|
||||
f.write(falscher)
|
||||
|
||||
gewaehlt = ablage.daten_ordner()
|
||||
print(f" (Merkzettel zeigte auf {falscher})")
|
||||
print(f" (gewaehlt wurde {gewaehlt})")
|
||||
|
||||
pruefe(not ablage._ist_temp(gewaehlt),
|
||||
f"der Datenordner liegt NICHT im Temp-Ordner ({gewaehlt})")
|
||||
pruefe(os.path.normcase(gewaehlt) != os.path.normcase(falscher),
|
||||
"dem Merkzettel wurde nicht gefolgt")
|
||||
pruefe("ravokk" in gewaehlt.lower(),
|
||||
f"stattdessen der Ordner der Firma ({gewaehlt})")
|
||||
|
||||
# ---- 3. Ein GUELTIGER Merkzettel wird weiter befolgt -------------------
|
||||
# Der Schutz darf nicht dazu fuehren, dass jemand seinen bewusst gewaehlten
|
||||
# Ordner verliert - etwa ein Netzlaufwerk.
|
||||
gewollt = os.path.join(spielwiese, "woanders", "Daten")
|
||||
os.makedirs(gewollt, exist_ok=True)
|
||||
with open(zeiger, "w", encoding="utf-8") as f:
|
||||
f.write(gewollt)
|
||||
pruefe(os.path.normcase(ablage.daten_ordner()) == os.path.normcase(gewollt),
|
||||
"ein gueltiger Merkzettel wird weiter befolgt")
|
||||
|
||||
os.environ["LOCALAPPDATA"] = echtes_lad
|
||||
shutil.rmtree(spielwiese, ignore_errors=True)
|
||||
shutil.rmtree(os.path.dirname(falscher), ignore_errors=True)
|
||||
|
||||
if fehler:
|
||||
print(f"ROT | {len(fehler)} Punkt(e) nicht erfuellt")
|
||||
sys.exit(1)
|
||||
print("GRUEN | Der Temp-Schutz ist angeschlossen und wirkt")
|
||||
Loading…
Add table
Add a link
Reference in a new issue