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,8 @@
import { fields, models } from "@web/../tests/web_test_helpers";
export class FakeUser extends models.Model {
_name = "fake.user";
name = fields.Char({ string: "Name" });
lang = fields.Char({ string: "Lang" });
}

View File

@@ -0,0 +1,31 @@
import { models, fields } from "@web/../tests/web_test_helpers";
export class HrDepartment extends models.ServerModel {
_name = "hr.department";
_rec_name = "complete_name";
name = fields.Char();
complete_name = fields.Char({
compute: "_compute_complete_name",
});
display_name = fields.Char({
compute: "_compute_display_name",
});
_compute_complete_name() {
for (const department of this) {
department.complete_name = department.name;
}
}
_compute_display_name() {
this._compute_complete_name();
for (const department of this) {
department.display_name = department.complete_name;
}
}
get _to_store_defaults() {
return ["name"];
}
}

View File

@@ -0,0 +1,29 @@
import { fields, models } from "@web/../tests/web_test_helpers";
import { mailDataHelpers } from "@mail/../tests/mock_server/mail_mock_server";
export class HrEmployee extends models.ServerModel {
_name = "hr.employee";
department_id = fields.Many2one({ relation: "hr.department" });
work_email = fields.Char();
work_phone = fields.Char();
work_location_type = fields.Char();
work_location_id = fields.Many2one({ relation: "hr.work.location" });
job_title = fields.Char();
_get_store_avatar_card_fields() {
return [
"company_id",
mailDataHelpers.Store.one("department_id", ["name"]),
"work_email",
mailDataHelpers.Store.one("work_location_id", ["location_type", "name"]),
"work_phone",
"job_title",
];
}
_views = {
search: `<search><field name="display_name" string="Name" /></search>`,
list: `<list><field name="display_name"/></list>`,
};
}

View File

@@ -0,0 +1,10 @@
import { models } from "@web/../tests/web_test_helpers";
export class HrEmployeePublic extends models.ServerModel {
_name = "hr.employee.public";
_views = {
search: `<search><field name="display_name" string="Name" /></search>`,
list: `<list><field name="display_name"/></list>`,
};
}

View File

@@ -0,0 +1,10 @@
import { models } from "@web/../tests/web_test_helpers";
export class HrJob extends models.ServerModel {
_name = "hr.job";
_views = {
search: `<search><field name="display_name" string="Name" /></search>`,
list: `<list><field name="display_name"/></list>`,
};
}

View File

@@ -0,0 +1,10 @@
import { models } from "@web/../tests/web_test_helpers";
export class HrVersion extends models.ServerModel {
_name = "hr.version";
_views = {
search: `<search><field name="display_name" string="Name" /></search>`,
list: `<list><field name="display_name"/></list>`,
};
}

View File

@@ -0,0 +1,19 @@
import { models, fields } from "@web/../tests/web_test_helpers";
export class HrWorkLocation extends models.ServerModel {
_name = "hr.work.location";
name = fields.Char();
location_type = fields.Selection({
selection: [
["office", "Office"],
["home", "Home"],
["other", "Other"],
],
});
_views = {
search: `<search><field name="display_name" string="Name" /></search>`,
list: `<list><field name="display_name"/></list>`,
};
}

View File

@@ -0,0 +1,8 @@
import { fields, models } from "@web/../tests/web_test_helpers";
export class M2xAvatarEmployee extends models.Model {
_name = "m2x.avatar.employee";
employee_id = fields.Many2one({ string: "Employee", relation: "hr.employee.public" });
employee_ids = fields.Many2many({ string: "Employees", relation: "hr.employee.public" });
}

View File

@@ -0,0 +1,23 @@
import { mailModels } from "@mail/../tests/mail_test_helpers";
import { fields, makeKwArgs } from "@web/../tests/web_test_helpers";
import { mailDataHelpers } from "@mail/../tests/mock_server/mail_mock_server";
export class ResPartner extends mailModels.ResPartner {
employee_ids = fields.One2many({
relation: "hr.employee",
inverse: "work_contact_id",
});
_get_store_avatar_card_fields() {
return [
...super._get_store_avatar_card_fields(),
mailDataHelpers.Store.many(
"employee_ids",
makeKwArgs({
fields: this.env["hr.employee"]._get_store_avatar_card_fields(),
mode: "ADD",
})
),
];
}
}

View File

@@ -0,0 +1,22 @@
import { mailModels } from "@mail/../tests/mail_test_helpers";
import { fields } from "@web/../tests/web_test_helpers";
export class ResUsers extends mailModels.ResUsers {
employee_id = fields.Many2one({ relation: "hr.employee" });
employee_ids = fields.One2many({
relation: "hr.employee",
inverse: "user_id",
});
department_id = fields.Many2one({
related: "employee_id.department_id",
relation: "hr.department",
});
work_email = fields.Char({ related: "employee_id.work_email" });
work_phone = fields.Char({ related: "employee_id.work_phone" });
work_location_type = fields.Char({ related: "employee_id.work_location_type" });
work_location_id = fields.Many2one({
related: "employee_id.work_location_id",
relation: "hr.work.location",
});
job_title = fields.Char({ related: "employee_id.job_title" });
}