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):
|
if not pfad or "\x00" in pfad or not os.path.isabs(pfad):
|
||||||
log.warning("Merkzettel unbrauchbar - es wird neu angelegt")
|
log.warning("Merkzettel unbrauchbar - es wird neu angelegt")
|
||||||
return None, None
|
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:
|
try:
|
||||||
laufwerk = os.path.splitdrive(pfad)[0]
|
laufwerk = os.path.splitdrive(pfad)[0]
|
||||||
erreichbar = (not laufwerk) or os.path.exists(
|
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:
|
except tk.TclError:
|
||||||
pass
|
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():
|
for kind in self.winfo_children():
|
||||||
kind.destroy()
|
if not isinstance(kind, tk.Toplevel):
|
||||||
|
kind.destroy()
|
||||||
|
|
||||||
self._apply_theme()
|
self._apply_theme()
|
||||||
self._menue()
|
self._menue()
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,66 @@ pruefe(d.winfo_rooty() >= f.winfo_rooty() - 40,
|
||||||
d.destroy()
|
d.destroy()
|
||||||
pumpen(0.3)
|
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 --------------------------------------------
|
# ---- Zurueck auf automatisch --------------------------------------------
|
||||||
f.cfg["darstellung"] = "auto"
|
f.cfg["darstellung"] = "auto"
|
||||||
f._oberflaeche_neu()
|
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