Files
goodie/tools/compile_templates.py
Marc 0ed29fe2fd Odoo ERP ported to Go — complete backend + original OWL frontend
Full port of Odoo's ERP system from Python to Go, with the original
Odoo JavaScript frontend (OWL framework) running against the Go server.

Backend (10,691 LoC Go):
- Custom ORM: CRUD, domains→SQL with JOINs, computed fields, sequences
- 93 models across 14 modules (base, account, sale, stock, purchase, hr,
  project, crm, fleet, product, l10n_de, google_address/translate/calendar)
- Auth with bcrypt + session cookies
- Setup wizard (company, SKR03 chart, admin, demo data)
- Double-entry bookkeeping constraint
- Sale→Invoice workflow (confirm SO → generate invoice → post)
- SKR03 chart of accounts (110 accounts) + German taxes (USt/VSt)
- Record rules (multi-company filter)
- Google integrations as opt-in modules (Maps, Translate, Calendar)

Frontend:
- Odoo's original OWL webclient (503 JS modules, 378 XML templates)
- JS transpiled via Odoo's js_transpiler (ES modules → odoo.define)
- SCSS compiled to CSS (675KB) via dart-sass
- XML templates compiled to registerTemplate() JS calls
- Static file serving from Odoo source addons
- Login page, session management, menu navigation
- Contacts list view renders with real data from PostgreSQL

Infrastructure:
- 14MB single binary (CGO_ENABLED=0)
- Docker Compose (Go server + PostgreSQL 16)
- Zero phone-home (no outbound calls to odoo.com)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 01:45:09 +02:00

103 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""
Compile Odoo XML templates to a JS bundle with registerTemplate() calls.
Mirrors: odoo/addons/base/models/assetsbundle.py generate_xml_bundle()
Usage:
python3 tools/compile_templates.py <odoo_source_path> <output_file>
"""
import os
import sys
import json
from lxml import etree
def main():
if len(sys.argv) < 3:
print("Usage: compile_templates.py <odoo_source_path> <output_file>")
sys.exit(1)
odoo_path = sys.argv[1]
output_file = sys.argv[2]
# Read XML file list
bundle_file = os.path.join(os.path.dirname(__file__), '..', 'pkg', 'server', 'assets_xml.txt')
with open(bundle_file) as f:
xml_urls = [line.strip() for line in f if line.strip()]
addons_dirs = [
os.path.join(odoo_path, 'addons'),
os.path.join(odoo_path, 'odoo', 'addons'),
]
content = []
count = 0
errors = 0
for url_path in xml_urls:
rel_path = url_path.lstrip('/')
source_file = None
for addons_dir in addons_dirs:
candidate = os.path.join(addons_dir, rel_path)
if os.path.isfile(candidate):
source_file = candidate
break
if not source_file:
continue
try:
tree = etree.parse(source_file)
root = tree.getroot()
# Process each <templates> block
if root.tag == 'templates':
templates_el = root
else:
templates_el = root
for template in templates_el.iter():
t_name = template.get('t-name')
if not t_name:
continue
inherit_from = template.get('t-inherit')
template.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve')
xml_string = etree.tostring(template, encoding='unicode')
# Escape for JS template literal
xml_string = xml_string.replace('\\', '\\\\').replace('`', '\\`').replace('${', '\\${')
# Templates with a t-name are always registered as primary templates.
# If they have t-inherit, the templates.js _getTemplate function
# handles the inheritance (cloning parent + applying xpath modifications).
# registerTemplateExtension is ONLY for anonymous patches without t-name.
content.append(f'registerTemplate("{t_name}", `{url_path}`, `{xml_string}`);')
count += 1
except Exception as e:
print(f" ERROR parsing {url_path}: {e}")
errors += 1
# Wrap in odoo.define with new-format module name so the loader resolves it
js_output = f'''odoo.define("@web/bundle_xml", ["@web/core/templates"], function(require) {{
"use strict";
const {{ checkPrimaryTemplateParents, registerTemplate, registerTemplateExtension }} = require("@web/core/templates");
{chr(10).join(content)}
}});
// Trigger the module in case the loader hasn't started it yet
if (odoo.loader && odoo.loader.modules.has("@web/bundle_xml") === false) {{
odoo.loader.startModule("@web/bundle_xml");
}}
'''
os.makedirs(os.path.dirname(output_file), exist_ok=True)
with open(output_file, 'w', encoding='utf-8') as f:
f.write(js_output)
print(f"Done: {count} templates compiled, {errors} errors")
print(f"Output: {output_file}")
if __name__ == '__main__':
main()