2026-08-22 07:58:39 +00:00
|
|
|
|
# Modellbaum-Vergleich (5.0 ↔ 8.0)
|
|
|
|
|
|
|
|
|
|
|
|
Werkzeug für die [Testdaten-Pipeline](TESTDATEN.md): Bevor Testdaten von
|
|
|
|
|
|
Hand ergänzt werden, erstmal statisch (kein laufendes Tryton nötig)
|
|
|
|
|
|
Schwarz auf Weiß sehen, welche Modelle/Felder es in einer Version
|
|
|
|
|
|
überhaupt gibt - damit weder Felder aus 8.0 übernommen werden, die es in
|
|
|
|
|
|
5.0 noch gar nicht gibt (würde beim Anlegen nur Fehler werfen), noch
|
|
|
|
|
|
Aufwand in Modelle gesteckt wird, die sich zwischen den Versionen gar
|
|
|
|
|
|
nicht verändert haben.
|
|
|
|
|
|
|
|
|
|
|
|
## Workflow
|
|
|
|
|
|
|
|
|
|
|
|
### 1. `extract_model_tree.py` – Modellbaum aus dem Quellcode ziehen
|
|
|
|
|
|
|
|
|
|
|
|
Scannt den Python-Code ausgewählter Module per AST (kein Import, keine
|
|
|
|
|
|
laufende Instanz) und baut pro Modell (`__name__`) die Liste aller per
|
|
|
|
|
|
`fields.X(...)` definierten Felder, mit Typ, Relationsziel
|
|
|
|
|
|
(Many2One/One2Many/Many2Many/One2One/Reference) und - falls literal im
|
|
|
|
|
|
Code erkennbar - `required`. Modelle werden über PoolMeta-Erweiterungen
|
|
|
|
|
|
hinweg zusammengeführt: Fügt z. B. `stock_split` eine Klasse mit
|
|
|
|
|
|
`__name__ = 'stock.move'` hinzu, landen deren Felder unter demselben
|
|
|
|
|
|
Modell-Eintrag, mit Vermerk, aus welchem Modul sie stammen.
|
|
|
|
|
|
|
|
|
|
|
|
Pro Modell wird zusätzlich `persisted` erkannt (erbt die Klasse `ModelSQL`
|
|
|
|
|
|
o. ä., oder nur `ModelView` - dann ist es ein reines Wizard-/
|
|
|
|
|
|
Transient-Formular ohne eigene Tabelle).
|
|
|
|
|
|
|
2026-08-22 17:45:46 +02:00
|
|
|
|
Verfolgt zusätzlich ganz normale **Python-Vererbung innerhalb derselben
|
|
|
|
|
|
Datei**: erbt eine Klasse mit eigenem `__name__` von einer anderen lokalen
|
|
|
|
|
|
Klasse - z. B. `class BOMOutput(BOMInput): __name__ = 'production.bom.output'`
|
|
|
|
|
|
in `production/bom.py`, die selbst keine einzige `fields.X(...)`-Zeile
|
|
|
|
|
|
deklariert, sondern alle Felder von `BOMInput` übernimmt - werden die
|
|
|
|
|
|
geerbten Felder mit aufgenommen (eigene Felder überschreiben geerbte bei
|
|
|
|
|
|
Namensgleichheit). Ohne das sähe ein rein über Vererbung wiederverwendetes
|
|
|
|
|
|
Modell wie eines OHNE Felder aus, und Änderungen daran (z. B. eine
|
|
|
|
|
|
Feldumbenennung im Elternteil) blieben komplett unsichtbar - genau das hat
|
|
|
|
|
|
`sql_dump_diff.py` (Schritt 4) einmal live aufgedeckt.
|
|
|
|
|
|
|
|
|
|
|
|
Die Basisklassen-Auflösung ist bewusst auf "gleiche Datei" beschränkt
|
|
|
|
|
|
(nicht "gleiches Modul"): derselbe lokale Klassenname wie `Cron` taucht
|
|
|
|
|
|
innerhalb eines Moduls oft mehrfach für völlig unterschiedliche Klassen
|
|
|
|
|
|
auf (Tryton-Konvention: eine PoolMeta-Erweiterung heißt oft genauso wie
|
|
|
|
|
|
das Original, z. B. `currency/currency.py` definiert `class Cron(ModelSQL,
|
|
|
|
|
|
ModelView): __name__ = 'currency.cron'`, während `currency/ir.py`
|
|
|
|
|
|
unabhängig davon eine andere Klasse `class Cron(metaclass=PoolMeta):` zur
|
|
|
|
|
|
Erweiterung von `ir.cron` definiert) - ein modulweites Registry würde sich
|
|
|
|
|
|
da gegenseitig überschreiben.
|
|
|
|
|
|
|
2026-08-22 07:58:39 +00:00
|
|
|
|
```bash
|
|
|
|
|
|
python3 extract_model_tree.py --repo /mnt/tryton \
|
|
|
|
|
|
--modules company country currency party product ... \
|
|
|
|
|
|
--output reports/model_tree_5.0.json --report reports/model_tree_5.0.md
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Für eine zweite Version denselben Befehl gegen einen zweiten Checkout
|
|
|
|
|
|
laufen lassen (z. B. per `git worktree add /tmp/tryton-8.0 8.0`, ohne den
|
|
|
|
|
|
Haupt-Checkout umzuschalten).
|
|
|
|
|
|
|
|
|
|
|
|
### 2. `model_diff.py` – zwei Modellbäume vergleichen
|
|
|
|
|
|
|
|
|
|
|
|
Sortiert jedes Modell in eine von fünf Kategorien:
|
|
|
|
|
|
|
|
|
|
|
|
- **entfernt** – Modell gibt es in Version A, nicht mehr in B
|
|
|
|
|
|
- **geändert** – Feld(er) entfernt oder Typ/Relation/required geändert →
|
|
|
|
|
|
echter Kandidat für einen Vergleichslauf
|
|
|
|
|
|
- **neu** – Modell gibt es erst in B
|
|
|
|
|
|
- **nur ergänzt** – nur neue Felder dazugekommen, Rest unverändert
|
|
|
|
|
|
- **unverändert** – komplett identisch geblieben
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
python3 model_diff.py --tree-a reports/model_tree_5.0.json --label-a 5.0 \
|
|
|
|
|
|
--tree-b reports/model_tree_8.0.json --label-b 8.0 \
|
|
|
|
|
|
--output reports/model_diff.json --report reports/model_diff.md
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. `model_coverage.py` – gegen die aktuelle Testdaten-Abdeckung abgleichen
|
|
|
|
|
|
|
|
|
|
|
|
Vergleicht einen Modellbaum mit einer gemergten `dataset.yaml`
|
|
|
|
|
|
(`reports/<version>_testdata_merged.yaml`, siehe
|
|
|
|
|
|
[TESTDATEN.md](TESTDATEN.md)) und zeigt komplett unabgedeckte Modelle sowie
|
|
|
|
|
|
pro teilweise abgedecktem Modell die ungenutzten Felder (required zuerst).
|
|
|
|
|
|
|
|
|
|
|
|
Drei Kategorien werden aus der Liste unabgedeckter Modelle per Default
|
|
|
|
|
|
herausgefiltert (nie von Hand testdatenrelevant, jeweils per Flag wieder
|
|
|
|
|
|
einschaltbar):
|
|
|
|
|
|
|
|
|
|
|
|
- `ir.*`/`res.*`-Kernmodelle (`--include-framework`)
|
|
|
|
|
|
- Wizard-/Transient-Formulare, `persisted: false` (`--include-transient`)
|
|
|
|
|
|
- automatisch verwaltete Many2Many-Verknüpfungstabellen, erkennbar am
|
|
|
|
|
|
Bindestrich im Namen wie `party.party-party.category`
|
|
|
|
|
|
(`--include-relations`)
|
|
|
|
|
|
|
|
|
|
|
|
Mit `--diff reports/model_diff.json` wird zusätzlich pro Modell die
|
|
|
|
|
|
Diff-Kategorie aus Schritt 2 eingeblendet und die Liste danach sortiert:
|
|
|
|
|
|
`geändert`/`entfernt` zuerst (lohnt sich, dort Testdaten zu ergänzen),
|
|
|
|
|
|
`nur ergänzt`/`unverändert` zuletzt (dort ist über den reinen
|
|
|
|
|
|
Codevergleich hinaus erstmal nichts Auffälliges zu erwarten).
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
python3 model_coverage.py --model-tree reports/model_tree_5.0.json \
|
|
|
|
|
|
--dataset reports/5_testdata_merged.yaml --diff reports/model_diff.json \
|
|
|
|
|
|
--report reports/coverage_5.0.md
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-22 17:45:46 +02:00
|
|
|
|
### 4. `sql_dump_diff.py` – gegen den echten DB-Zustand validieren
|
|
|
|
|
|
|
|
|
|
|
|
Alles bisherige ist reine Code-Analyse. Zum Cross-Check: zwei mit
|
|
|
|
|
|
`pg_dump` (Klartext-Format) erzeugte Dumps frisch initialisierter (leerer)
|
|
|
|
|
|
Datenbanken beider Versionen strukturell vergleichen - Tabellen
|
|
|
|
|
|
entfernt/neu, Spalten hinzugefügt/entfernt/Typ geändert, Zeilenzahl
|
|
|
|
|
|
(z. B. mehr XML-Fixture-Daten in der neueren Version). Zeilen werden
|
|
|
|
|
|
bereits vollständig geparst (für eine spätere inhaltliche
|
|
|
|
|
|
Vergleichs-Erweiterung, sobald es Snapshots mit echten Testdaten gibt),
|
|
|
|
|
|
aktuell nutzt der Report davon aber nur die Zeilenzahl.
|
|
|
|
|
|
|
|
|
|
|
|
Mit `--model-diff reports/model_diff.json` wird pro betroffener Tabelle
|
|
|
|
|
|
zusätzlich geprüft, ob die rein statische Code-Analyse (Schritt 2)
|
|
|
|
|
|
dieselbe Änderung schon vorhergesagt hatte (Modellname -> Tabellenname:
|
|
|
|
|
|
Punkte durch Unterstriche ersetzen, wie `extract_updates.py`/
|
|
|
|
|
|
`compare_snapshots.py` es auch tun) - Übereinstimmung bzw. Abweichung wird
|
|
|
|
|
|
markiert.
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
python3 sql_dump_diff.py --dump-a data/leer5.sql --label-a 5.0 \
|
|
|
|
|
|
--dump-b data/leer8.sql --label-b 8.0 \
|
|
|
|
|
|
--model-diff reports/model_diff.json \
|
|
|
|
|
|
--output reports/dump_diff_5.0_8.0.json --report reports/dump_diff_5.0_8.0.md
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Stand:** 78 von 92 vergleichbaren Fällen stimmen überein (~85%). Die
|
|
|
|
|
|
verbleibenden Abweichungen gehen meist in eine erwartbare Richtung: die
|
|
|
|
|
|
Code-Analyse findet MEHR Veränderung, als im leeren DB-Schema sichtbar
|
|
|
|
|
|
ist (z. B. ein entferntes Function-Feld ohne eigene Spalte, oder
|
|
|
|
|
|
Selection-Wertänderungen ohne Typänderung) - kein Fehler, sondern zwei
|
|
|
|
|
|
Werkzeuge, die unterschiedliche Ebenen messen (Python-Feld-Definition vs.
|
|
|
|
|
|
physisches SQL-Schema). Der `production.bom.output`-Fund oben (Schritt 1)
|
|
|
|
|
|
kam genau aus einer vorherigen Abweichung dieser Art zustande.
|
|
|
|
|
|
|
2026-08-22 07:58:39 +00:00
|
|
|
|
## Bekannte Grenzen
|
|
|
|
|
|
|
|
|
|
|
|
- Rein statische Analyse: Felder, die nicht als einfaches
|
|
|
|
|
|
`name = fields.X(...)` auf Klassenebene stehen (z. B. per Schleife/Helper
|
|
|
|
|
|
dynamisch erzeugt), werden nicht erkannt.
|
2026-08-22 17:45:46 +02:00
|
|
|
|
- Python-Vererbung wird nur INNERHALB derselben Datei aufgelöst (s. o.).
|
|
|
|
|
|
Erbt eine Klasse von einer Basisklasse aus einer anderen Datei desselben
|
|
|
|
|
|
oder eines anderen Moduls (Import statt lokaler Definition), werden
|
|
|
|
|
|
deren Felder nicht gefunden - unauffällig, aber ein bekannter blinder
|
|
|
|
|
|
Fleck.
|
2026-08-22 07:58:39 +00:00
|
|
|
|
- `persisted`-Erkennung ist eine Textsuche über die Basisklassen-Ausdrücke
|
|
|
|
|
|
(`ModelSQL`/`Workflow`/`DeactivableMixin`) - deckt den Normalfall ab, ist
|
|
|
|
|
|
aber kein echtes Type-Checking.
|
|
|
|
|
|
- Erkennt nur Unterschiede auf Ebene der Feld-**Definition** im Code
|
|
|
|
|
|
(Typ/Relation/required). Laufzeitverhalten wie Constraints, Defaults oder
|
|
|
|
|
|
DB-Spaltenpräzision kann trotzdem abweichen, ohne hier aufzufallen - dafür
|
|
|
|
|
|
bleibt der eigentliche Testlauf über `create_dataset.py`/
|
|
|
|
|
|
`compare_snapshots.py` nötig.
|