Zum Hauptinhalt springen
Entwicklung · Odoo 19, 18 und 17

Dasselbe Modul in Odoo 19, 18 und 17 pflegen: was sich ändert, was stillschweigend bricht und wie man es wirklich testet

Ein Branch pro Version, ein echter Build pro Version und ein Inventar der Unterschiede. Was zwischen 17, 18 und 19 Probleme macht, im Odoo-Quellcode verifiziert, und wie wir es mit 105 veröffentlichten Modulen testen.

Odoo-Apps-Bildschirm mit installierten Modulen in einem echten Backend

Ein Odoo-Modul gleichzeitig in 19, 18 und 17 zu pflegen heißt nicht, den Ordner in drei Branches zu kopieren und die Nummer im Manifest zu ändern. Es bedeutet einen Git-Branch pro Version, einen echten Installations-Build pro Version und ein Inventar der Unterschiede zwischen den Versionen, das bei jedem Release geprüft wird. Wir pflegen 105 veröffentlichte Module in allen drei Versionen; dies ist die Liste dessen, was bricht, einschließlich unserer eigenen Fehler.

Ein Eintrag, drei Branches (19.0, 18.0 und 17.0 mit demselben technischen Namen) wird in Ein Modul im Odoo App Store veröffentlichen erklärt. Hier geht es darum, was innerhalb jedes Branches passiert.

Was sich zwischen 17, 18 und 19 ändert und in den Release-Übersichten nicht steht

Die Neuerungen von Odoo 19 beschreiben Funktionen; diese Tabelle beschreibt, was bei der Installation fehlschlägt oder, schlimmer, nicht fehlschlägt. Sie ist gegen den Quellcode aller drei Versionen geprüft, nicht gegen ein Changelog.

WasOdoo 17Odoo 18Odoo 19Wie es fehlschlägt
SQL-Constraints_sql_constraints_sql_constraintsmodels.ConstraintStillschweigend: 19 warnt und macht weiter; die Constraint existiert nicht
Cron ohne Limitnumbercall=-1 erforderlichDas Feld existiert nichtDas Feld existiert nicht17: ohne läuft er einmal und schaltet sich ab. 18/19: mit lässt er sich nicht installieren
Gruppen des Benutzersgroups_idgroups_idgroup_idsUngültiges Feld bei Installation oder Ausführung
Benutzer der Gruppeusersusersuser_idsGleich; hat uns in 17 und 18 einen Benachrichtigungs-Cron zerschossen
Gruppenkategoriecategory_idcategory_idprivilege_id → res.groups.privilegeInvalid field 'category_id' in 'res.groups'
Listenansicht<tree><list><list>Ungültiger Ansichtstyp; die xpath-Ausdrücke auf //tree greifen nicht

Der gefährlichste: _sql_constraints

In Odoo 19 wird _sql_constraints ignoriert: Der Modell-Loader schreibt «no longer supported» ins Log und macht weiter. Das Modul wird installiert, die Tests laufen durch, und die Eindeutigkeits-Constraint ist nicht in der Datenbank; ein Konnektor, der sich auf unique(external_id) verlässt, um keine doppelten Aufträge anzulegen, verliert diese Garantie, ohne dass irgendetwas darauf hinweist. Die neue Form, models.Constraint('unique(...)', 'mensaje'), gibt es nur in 19. Odoo liefert einen Konverter mit, odoo/cli/upgrade_code.py --script 18.1-00-sql-constraint, mit einer Falle, die wir beim Migrieren unserer eigenen Module kennengelernt haben: Er gibt den Exit-Code 1 zurück, wenn er Dateien geändert hat, also ist 1 Erfolg. Den Beweis, dass die Constraint lebt, liefert nur ein Test, der das Duplikat versucht.

Der, der uns erwischt hat: numbercall

In Odoo 17 steht ir.cron.numbercall standardmäßig auf 1: Ein Cron, der ohne dieses Feld deklariert ist, läuft einmal und deaktiviert sich selbst, ohne Fehler und ohne Logzeile. Odoo 18 hat das Feld entfernt, und seitdem verhindert seine Angabe die Installation. Der Branch 17 braucht also <field name="numbercall">-1</field> in jedem Cron, die anderen beiden dürfen es nicht enthalten. Im September 2026 haben wir unsere eigenen Ports auf 17 geprüft und 14 Crons ohne dieses Feld in vier Konnektoren gefunden, zwei davon mit laufender Demo: Niemand testet eine periodische Synchronisation, indem er die zweite Ausführung abwartet. Die Korrektur sind zwei Zeilen pro Cron; der Preis war, dass es nicht im Inventar stand.

Was Odoo 17 validiert und 18 und 19 durchlassen

Der Verkehr läuft nicht nur nach oben. Odoo 17 verlangt, dass ein Feld, das in einem View-Modifier (invisible, readonly) verwendet wird, in derselben Ansicht vorkommt; 18 und 19 prüfen das nicht. Ein in 19 geschriebenes und rückportiertes Modul scheitert in 17 mit Field 'state' used in modifier 'invisible' (state == 'draft') must be present in view but is missing. Deshalb gilt ein Port erst als gut, wenn er in der ältesten Version installiert wurde. Weitere Fälle in Ein Odoo-19-Modul kompiliert, lässt sich aber nicht installieren und Fehler bei der Migration auf Odoo 19.

Branch-Strategie: einer pro Version und keine Bedingungen

Der Hauptbranch gehört zur neuesten Version, heute 19.0. Jede Änderung wird dort gemacht und getestet und dann per Cherry-Pick nach 18.0 und 17.0 zurückgeführt, wobei angepasst wird, was die Tabelle zu ändern zwingt. Was wir nie tun, ist ein einziger Code mit if version >= 18 darin: Es entsteht ein Modul mit allen drei APIs gleichzeitig, bei dem niemand weiß, welchen der drei Branches er gerade testet. Für neue Konnektoren erzeugen wir die Branches 18 und 17 aus dem Quellcode von 19 mit einem Skript, das die obige Tabelle abbildet; es ersetzt nicht den Test, sondern das Copy-and-paste.

Zwei Ausnahmen, und beide fragen nach einer Fähigkeit, nicht nach einer Versionsnummer:

  • Erkennung von Feld oder Modell zur Laufzeit. Schreibt das Modul in ein Feld, das nicht in allen Versionen existiert, fragt man die Registry: 'mobile' in partner._fields oder 'hr.expense.sheet' in self.env. Sie schaut, was da ist, nicht, was da sein sollte.
  • Tests, die übersprungen werden, wenn die Fähigkeit fehlt, mit einem skipTest, der an das Vorhandensein des Felds gebunden ist. Nicht akzeptabel ist, mit 0 von 0 zu bestehen: Ein Modul von uns hatte in allen drei Branches null Tests; als wir 25 schrieben, kamen drei echte Fehler zutage, einer davon der Benachrichtigungs-Cron, der in 17 und 18 user_ids verwendete.

Der Test, der zählt: eine echte Installation in allen dreien

Weder der Linter noch import noch eine grüne Suite auf Ihrem Rechner beweisen, dass sich ein Modul installieren lässt: Ein XML mit numbercall ist gültiges XML, und ein xpath auf //tree ist ein gültiger xpath. Der Fehler zeigt sich erst, wenn Odoo das Modul gegen eine Datenbank dieser Version lädt. Unsere Regel: Eine Änderung gibt es erst, wenn sie sauber in einem echten Odoo jeder Version installiert wurde und, falls das Modul bei jemandem bereits läuft, auch als Update von der Vorversion.

Das Upgrade ist aus einem konkreten Grund wichtig: Die Gruppen von base tragen noupdate="1", ein Modul, das seine Manager-Gruppe mit implied_ids zu base.group_system hinzufügt, erreicht das also bei einer Neuinstallation, während Odoo es beim Update stillschweigend ignoriert. Wir haben das in einer Odoo.sh-Produktivumgebung erlebt: Die Gruppe blieb ohne Mitglieder, und nicht einmal der Administrator konnte das davon abhängige Zugangsdatenfeld lesen. Neben dem XML braucht es eine Migration in migrations/ und einen Test, der fehlschlägt, wenn die Gruppe leer bleibt.

Wir nutzen zwei Arten von Umgebung. Eine Testumgebung, ein Odoo pro Version (lokal oder in Containern), das jedes Modul installiert und seine Suite gegen alle drei ausführt: Dort liefen die 37 Apps unserer ersten Kampagne, dort besteht der Mirakl-Konnektor in jeder Version 163 Tests und der Amazon-Konnektor 786 in 19 und 685 in 18 und in 17. Und Odoo.sh, ein Build pro Branch, für die echte Installation. Ein Durchlauf des Gates dauert etwa zehn Minuten, und jede davon lohnt sich: Die beiden Module, die uns dieses Jahr am teuersten zu stehen kamen, waren lokal komplett grün und ließen sich nicht installieren (grüne Tests, und der Konnektor scheitert in Produktion).

Eigener Fehler: Die erste Kampagne mit 37 Apps haben wir mit grüner Installation und grünen Tests für abgeschlossen erklärt. Vier Tage später blieben bei einem zweiten Durchgang über 47 Apps in Odoo 17 und 18 Community, bei dem jede Ansicht mit Daten ausgeführt wurde, 61 von 94 Läufen sauber: 28 Installationsfehler und 4 Fehler bei der Ausführung, etwa elf echte Fehler. xpath auf //list in 17, Anker auf Felder, die in 17 nicht existieren. Rückportierter Code, der in der Zielversion nicht installiert wurde.

Die realen Kosten und wann man 17 aufgibt

Drei Branches verdreifachen nicht die Entwicklung, denn die Logik wird einmal geschrieben. Sie verdreifachen Test und Veröffentlichung: drei Installationen pro Änderung, drei Suiten, drei Einträge zu prüfen und ein Inventar, das mit jeder neuen Version komplett neu gelesen werden muss, etwa mit 20 (September 2026), die der Tabelle Zeilen hinzufügt. Odoo bringt jährlich eine Hauptversion heraus und unterstützt die letzten drei; an dem Tag, an dem 20 erscheint, fällt 17 aus diesem Fenster und wird nicht mehr gepflegt. Bis dahin erhält der Branch 17 jede Korrektur: Wer noch auf 17 ist, plant eine Migration und wird migrieren, mit Ihrem Modul oder ohne.

Was die Kosten tatsächlich senkt, ist das Inventar als Werkzeug: ein schreibgeschützter Scanner, der je nach Manifest-Version anzeigt, welche Zeile auf welchen Unterschied tritt. Unserer hat einmal 67 Module markiert, weil sie category_id in eine Gruppe schreiben, und alle 67 waren richtig: Diese Zeile stand in einem <record> von res.groups.privilege, genau die korrekte Lösung für 19. Die Regel sah die Zeile und nicht den Datensatz, der sie umgibt. Eine Zählung, ohne den Code zu öffnen, ist kein Datum, und eine Regel, die die richtige Lösung markiert, lehrt, das Werkzeug zu ignorieren.

Checkliste vor jedem Push

  1. Constraints: models.Constraint in 19, _sql_constraints in 18 und 17, und ein Test, der das Duplikat versucht.
  2. Crons: numbercall=-1 in allen Crons des Branches 17; keines in 18 und 19.
  3. Gruppen: group_ids, user_ids und privilege_id (Modell res.groups.privilege) in 19; groups_id, users und category_id in 18 und 17.
  4. Ansichten: <list> in 19 und 18, <tree> in 17; in 17 steht jedes in einem Modifier verwendete Feld in der Ansicht.
  5. Saubere Installation in allen dreien und Update von der Vorversion, wenn das Modul schon im Einsatz ist.
  6. Komplette Suite grün in allen dreien und nicht leer: 0 von 0 ist nicht grün.
  7. Kein __pycache__ im Commit. Ein git add -A nach den Tests brachte uns 1.078 .pyc-Dateien in 35 von 37 Repositories.

Das tun wir, wenn uns ein Odoo-Partner beauftragt, ein eigenes Modul in mehreren Versionen zu pflegen, oder wenn eine Beratung eine Entwicklung, die bei ihr in 17 funktioniert, nach 19 bringen muss, ohne 17 zu verlieren. Die Abkürzung ist das, was der erste Käufer des Eintrags entdeckt.

Häufig gestellte Fragen

Kann ich einen einzigen Code mit Versionsbedingungen pflegen?

Der App Store veröffentlicht aus den Branches 17.0, 18.0 und 19.0, Sie haben also ohnehin drei Branches, nur mit allen drei APIs vermischt in jedem. Ein Branch pro Version bedeutet mehr wiederholten Code und viel weniger Fehler. Die einzigen akzeptablen Bedingungen fragen nach einer Fähigkeit, nie nach der Versionsnummer.

Warum reicht es nicht, dass es kompiliert und die Tests lokal bestehen?

Weil Versionsfehler beim Laden des Moduls gegen eine Datenbank dieser Version auftreten, nicht beim Import des Codes; und manche, wie _sql_constraints in 19, schlagen nicht einmal fehl: Man muss beweisen, dass die Constraint existiert.

Nützliche Links bei FlexigoTech

Ein Modul im Odoo App Store veröffentlichenEin Eintrag, drei Branches: Repository, Manifest und erneuter ScanEin Odoo-19-Modul kompiliert, lässt sich aber nicht installierenDie API-Fallen von 19, die die statische Analyse nicht siehtGrüne Tests, und der Konnektor scheitert in ProduktionWarum der Test, der zählt, die echte Installation istMigration auf Odoo 19Migrationsservice inklusive Ihrer eigenen Module

Haben Sie ein Modul in einer Version und brauchen es in allen dreien?

Wir installieren es in 19, 18 und 17, sagen Ihnen, was in jeder Version bricht, und übergeben es mit einem Branch pro Version und grünem Build. Schreiben Sie an comercial@flexigobe.com oder rufen Sie an unter +34 616 809 504.

Mit einem Ingenieur sprechen