Skip to content

Commit d47a0c5

Browse files
committed
[IMP] usability_webhooks: call api external data with table
1 parent ff42aff commit d47a0c5

File tree

6 files changed

+252
-0
lines changed

6 files changed

+252
-0
lines changed

usability_webhooks/__manifest__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@
1414
"data/config_parameter.xml",
1515
"data/ir_cron.xml",
1616
"views/api_log.xml",
17+
"views/base_api_data_views.xml",
1718
],
1819
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
22

33
from . import api_log
4+
from . import base_api_data
5+
from . import common_base_api
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright 2025 Ecosoft Co., Ltd. (https://ecosoft.co.th)
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
3+
4+
from odoo import fields, models
5+
6+
7+
class BaseApiData(models.Model):
8+
_name = "base.api.data"
9+
_description = "Base API Data"
10+
11+
name = fields.Char(required=True, translate=True)
12+
active = fields.Boolean(default=True)
13+
description = fields.Char()
14+
model_id = fields.Many2one(
15+
comodel_name="ir.model",
16+
required=True,
17+
ondelete="cascade",
18+
)
19+
api_data_line = fields.One2many(
20+
comodel_name="base.api.data.line",
21+
inverse_name="api_data_id",
22+
)
23+
endpoint = fields.Char()
24+
endpoint_method = fields.Char()
25+
special_command = fields.Selection(
26+
selection=[
27+
("none", "None"),
28+
("create", "Create (0, 0, )"),
29+
("replace", "Replace (6, 0, )"),
30+
],
31+
default="none",
32+
help="This fields add special command in each line because some API "
33+
"need command to create like XML-RPC",
34+
)
35+
36+
37+
class BaseApiDataLine(models.Model):
38+
_name = "base.api.data.line"
39+
_description = "Base API Data Line"
40+
_order = "sequence, id"
41+
42+
api_data_id = fields.Many2one(
43+
comodel_name="base.api.data",
44+
required=True,
45+
string="API Data",
46+
ondelete="restrict",
47+
)
48+
sequence = fields.Integer(default=10)
49+
name = fields.Char(
50+
required=True,
51+
)
52+
field_type = fields.Selection(
53+
selection=[
54+
("string", "String"),
55+
("int", "Integer"),
56+
("float", "Float"),
57+
],
58+
string="Type",
59+
required=True,
60+
)
61+
is_dynamic = fields.Boolean(string="Dynamic", default=False)
62+
value = fields.Char(
63+
required=True,
64+
)
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Copyright 2025 Ecosoft Co., Ltd. (https://ecosoft.co.th)
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
3+
4+
from odoo import fields, models
5+
from odoo.exceptions import UserError
6+
from odoo.tools.safe_eval import safe_eval
7+
8+
9+
class CommonBaseApi(models.AbstractModel):
10+
_name = "common.base.api"
11+
_description = "Common Base API"
12+
13+
def get_api_data(self, api_data):
14+
data_dict = {}
15+
standard_field_map = {}
16+
nested_field_map = {}
17+
18+
for api_line in api_data.api_data_line:
19+
if "/" in api_line.name:
20+
relational_field, sub_field = api_line.name.split("/", 1)
21+
nested_field_map.setdefault(relational_field, []).append(
22+
(sub_field, api_line)
23+
)
24+
else:
25+
# This is a standard field
26+
standard_field_map[api_line.name] = api_line
27+
28+
for field_name, api_line in standard_field_map.items():
29+
value = self._get_field_value(api_line, self)
30+
data_dict[field_name] = value
31+
32+
for relational_field, sub_fields in nested_field_map.items():
33+
if relational_field not in self._fields:
34+
raise UserError(
35+
f"Relational field '{relational_field}' not found on the record."
36+
)
37+
38+
related_recordset = self[relational_field]
39+
data_dict[relational_field] = []
40+
41+
sub_field_names = [
42+
sub_field
43+
for sub_field, _ in sub_fields
44+
if sub_field in related_recordset._fields
45+
]
46+
related_values = (
47+
related_recordset.read(sub_field_names) if sub_field_names else []
48+
)
49+
50+
for record, values_dict in zip(
51+
related_recordset, related_values, strict=False
52+
):
53+
record_dict = {}
54+
for sub_field, api_line in sub_fields:
55+
try:
56+
value = self._get_field_value(
57+
api_line,
58+
record,
59+
field_to_access=sub_field,
60+
prefetch_dict=values_dict,
61+
)
62+
record_dict[sub_field] = value
63+
except Exception as e:
64+
raise UserError(
65+
f"Error processing nested field '{api_line.name}': {str(e)}"
66+
) from None
67+
68+
if api_data.special_command == "create":
69+
data_dict[relational_field].append((0, 0, record_dict))
70+
elif api_data.special_command == "replace":
71+
data_dict[relational_field].append((6, 0, record_dict))
72+
else:
73+
data_dict[relational_field].append(record_dict)
74+
75+
return data_dict
76+
77+
def _get_field_value(
78+
self, api_line, record, field_to_access=None, prefetch_dict=None
79+
):
80+
field_name = field_to_access or api_line.name
81+
82+
if api_line.is_dynamic:
83+
return safe_eval(
84+
api_line.value,
85+
{
86+
"record": record,
87+
"env": record.env,
88+
"user": record.env.user,
89+
"datetime": fields.Datetime,
90+
},
91+
)
92+
93+
if prefetch_dict and field_name in prefetch_dict:
94+
value = prefetch_dict[field_name]
95+
elif field_name in record._fields:
96+
value = record[field_name]
97+
else:
98+
value = api_line.value # fallback static
99+
100+
try:
101+
if api_line.field_type == "float":
102+
return float(value)
103+
elif api_line.field_type == "int":
104+
return int(value)
105+
else:
106+
return value
107+
except (ValueError, TypeError):
108+
raise UserError(
109+
f"Field '{api_line.name}' must be of type {api_line.field_type}, "
110+
f"but got: {value}"
111+
) from None
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
22
access_api_log,access_api_log,model_api_log,base.group_user,1,1,1,1
3+
access_base_api_data_user,access_base_api_data_user,model_base_api_data,base.group_erp_manager,1,1,1,1
4+
access_base_api_data_line_user,access_base_api_data_line_user,model_base_api_data_line,base.group_erp_manager,1,1,1,1
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<odoo>
3+
<record id="base_api_data_list_view" model="ir.ui.view">
4+
<field name="name">base.api.data.list.view</field>
5+
<field name="model">base.api.data</field>
6+
<field name="arch" type="xml">
7+
<list>
8+
<field name="name" />
9+
<field name="model_id" />
10+
</list>
11+
</field>
12+
</record>
13+
<record id="base_api_data_form_view" model="ir.ui.view">
14+
<field name="name">base.api.data.form.view</field>
15+
<field name="model">base.api.data</field>
16+
<field name="arch" type="xml">
17+
<form>
18+
<sheet>
19+
<widget
20+
name="web_ribbon"
21+
title="Archived"
22+
bg_color="text-bg-danger"
23+
invisible="active"
24+
/>
25+
<div class="oe_title">
26+
<label class="oe_edit_only" for="name" string="API Data Name" />
27+
<h1>
28+
<field name="name" placeholder="API Data Name" />
29+
</h1>
30+
</div>
31+
<group>
32+
<group>
33+
<field name="model_id" />
34+
<field name="endpoint" />
35+
<field name="endpoint_method" />
36+
<field name="special_command" />
37+
</group>
38+
<group>
39+
<field name="description" />
40+
</group>
41+
</group>
42+
<notebook>
43+
<page name="api_line" string="API Line">
44+
<field name="api_data_line">
45+
<list editable="bottom">
46+
<field name="sequence" widget="handle" />
47+
<field name="name" />
48+
<field name="field_type" />
49+
<field name="value" />
50+
<field name="is_dynamic" />
51+
</list>
52+
</field>
53+
</page>
54+
</notebook>
55+
</sheet>
56+
</form>
57+
</field>
58+
</record>
59+
60+
<record id="action_api_export" model="ir.actions.act_window">
61+
<field name="name">API Exports</field>
62+
<field name="res_model">base.api.data</field>
63+
<field name="view_mode">list,form</field>
64+
</record>
65+
<menuitem
66+
id="menu_action_api_export"
67+
parent="usability_webhooks.menu_action_api_configuration"
68+
name="API Exports"
69+
action="action_api_export"
70+
sequence="20"
71+
/>
72+
</odoo>

0 commit comments

Comments
 (0)