Eliminate Python dependency: embed frontend assets in odoo-go

- Copy all OWL frontend assets (JS/CSS/XML/fonts/images) into frontend/
  directory (2925 files, 43MB) — no more runtime reads from Python Odoo
- Replace OdooAddonsPath config with FrontendDir pointing to local frontend/
- Rewire bundle.go, static.go, templates.go, webclient.go to read from
  frontend/ instead of external Python Odoo addons directory
- Auto-detect frontend/ and build/ dirs relative to binary in main.go
- Delete obsolete Python helper scripts (tools/*.py)

The Go server is now fully self-contained: single binary + frontend/ folder.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc
2026-03-31 23:09:12 +02:00
parent 0ed29fe2fd
commit 8741282322
2933 changed files with 280644 additions and 264 deletions

View File

@@ -0,0 +1,30 @@
import { models } from '@web/../tests/web_test_helpers';
import { ProductProduct as ProductModel } from './product_product';
export class ProductProduct extends ProductModel {
_records = [
{ id: 1, name: "Black chair", type: 'goods', list_price: 50.0 },
{ id: 2, name: "Blue chair", type: 'goods', list_price: 60.0 },
{ id: 3, name: "Black table", type: 'goods', list_price: 70.0 },
{ id: 4, name: "Blue table", type: 'goods', list_price: 80.0 },
{ id: 5, name: "Test Combo", type: 'combo', combo_ids: [1, 2] },
];
}
export class ProductComboItem extends models.ServerModel {
_name = 'product.combo.item';
_records = [
{ id: 1, product_id: 1 },
{ id: 2, product_id: 2 },
{ id: 3, product_id: 3 },
{ id: 4, product_id: 4 },
];
}
export class ProductCombo extends models.ServerModel {
_name = 'product.combo';
_records = [
{ id: 1, name: "Chair combo", combo_item_ids: [1, 2] },
{ id: 2, name: "Table combo", combo_item_ids: [3, 4] },
];
}

View File

@@ -0,0 +1,12 @@
import { models } from "@web/../tests/web_test_helpers";
export class ProductProduct extends models.ServerModel {
_name = "product.product";
_records = [
{id: 1, name: "Test Product", type: "consu", list_price: 20.0},
{id: 2, name: "Test Service Product", type: "service", list_price: 50.0},
{id: 14, name: "desk"},
];
}

View File

@@ -0,0 +1,12 @@
import { models } from "@web/../tests/web_test_helpers";
export class ProductTemplate extends models.ServerModel {
_name = "product.template";
get_single_product_variant() {
return { product_id: 14, product_name: "desk" };
}
_records = [{ id: 12, name: "desk" }];
}

View File

@@ -0,0 +1,16 @@
import { defineModels } from '@web/../tests/web_test_helpers';
import {
ProductCombo,
ProductComboItem,
ProductProduct,
} from './mock_server/mock_models/product_combo';
export const comboModels = {
ProductCombo,
ProductComboItem,
ProductProduct,
}
export function defineComboModels() {
defineModels(comboModels);
}

View File

@@ -0,0 +1,88 @@
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
import { expect, test } from "@odoo/hoot";
import { queryAllTexts } from "@odoo/hoot-dom";
import { contains, defineModels, fields, getService, models, mountWebClient, onRpc } from "@web/../tests/web_test_helpers";
class ProductProduct extends models.Model {
_records = [{ id: 42, name: "Customizable Desk" }];
name = fields.Char();
}
class ProductPricelist extends models.Model {
_records = [
{ id: 1, name: "Public Pricelist" },
{ id: 2, name: "Test" },
];
name = fields.Char();
}
defineModels([ProductProduct, ProductPricelist]);
defineMailModels();
test(`Pricelist Client Action`, async () => {
onRpc("report.product.report_pricelist", "get_html", async () => "");
await mountWebClient();
await getService("action").doAction({
id: 1,
name: "Generate Pricelist Report",
tag: "generate_pricelist_report",
type: "ir.actions.client",
context: {
active_ids: [42],
active_model: "product.product",
},
});
// checking default pricelist
expect(`select#pricelists > option:eq(0)`).toHaveText("Public Pricelist", {
message: "should have default pricelist",
});
// changing pricelist
await contains(`select#pricelists`).select("2");
// check whether pricelist value has been updated or not
expect(`select#pricelists > option:eq(0)`).toHaveText("Test", {
message: "After pricelist change, the pricelist_id field should be updated",
});
// check default quantities should be there
expect(queryAllTexts(`.o_badges_list .badge`)).toEqual(["1", "5", "10"]);
// existing quantity can not be added.
await contains(`.o_add_qty`).click();
expect(queryAllTexts(`.o_badges_list .badge`)).toEqual(["1", "5", "10"]);
expect(`.o_notification`).toHaveCount(1);
expect(`.o_notification .o_notification_content`).toHaveText(
"Quantity already present (1).",
{ message: "Existing Quantity can not be added" }
);
expect(`.o_notification .o_notification_bar`).toHaveClass("bg-info");
await contains(`.o_notification_close`).click();
expect(`.o_notification`).toHaveCount(0);
// adding few more quantities to check.
await contains(`.add-quantity-input`).edit("2", { confirm: false });
await contains(`.o_add_qty`).click();
expect(queryAllTexts(`.o_badges_list .badge`)).toEqual(["1", "2", "5", "10"]);
expect(`.o_notification`).toHaveCount(0);
await contains(`.add-quantity-input`).edit("3", { confirm: false });
await contains(`.o_add_qty`).click();
expect(queryAllTexts(`.o_badges_list .badge`)).toEqual(["1", "2", "3", "5", "10"]);
expect(`.o_notification`).toHaveCount(0);
// no more than 5 quantities can be used at a time
await contains(`.add-quantity-input`).edit("4", { confirm: false });
await contains(`.o_add_qty`).click();
expect(queryAllTexts(`.o_badges_list .badge`)).toEqual(["1", "2", "3", "5", "10"]);
expect(`.o_notification`).toHaveCount(1);
expect(`.o_notification .o_notification_content`).toHaveText(
"At most 5 quantities can be displayed simultaneously. Remove a selected quantity to add others.",
{ message: "Can not add more then 5 quantities" }
);
expect(`.o_notification .o_notification_bar`).toHaveClass("bg-warning");
});

View File

@@ -0,0 +1,13 @@
import { defineModels } from '@web/../tests/web_test_helpers';
import { ProductProduct } from './mock_server/mock_models/product_product';
import { ProductTemplate } from './mock_server/mock_models/product_template';
export const productModels = {
ProductProduct,
ProductTemplate,
};
export function defineProductModels() {
defineModels(productModels);
}