Skip to content

Commit df3a350

Browse files
committed
Execute SQL queries through API
1 parent 2ace352 commit df3a350

File tree

4 files changed

+51
-149
lines changed

4 files changed

+51
-149
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Changelog
77

88
* Colorize interactive console with Python 3.13+.
99

10+
* Add method :meth:`Env.sql` to execute SQL queries as an administrator.
11+
1012
* Drop support for Python 3.6 and 3.7.
1113

1214
* Drop support for OpenERP and for Odoo 8.

docs/api.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ Please refer to `the Odoo documentation`_ for details.
204204

205205
.. automethod:: Env.execute(obj, method, *params, **kwargs)
206206

207+
.. automethod:: Env.sql
208+
207209
.. method:: Env._call_kw(obj, method, params, kw=None)
208210

209211
Expose the ``/web/dataset/call_kw`` endpoint.

odooly.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66
import _ast
77
import atexit
8+
import datetime
89
import functools
910
import json
1011
import optparse
@@ -111,6 +112,26 @@
111112
('unlink', ['ids']),
112113
('write', ['ids', 'vals']),
113114
]
115+
_sql_action_code = """\
116+
sql_queries = env.context.get("__sql") or []
117+
result = env.cr.connection.notices
118+
119+
if not env.is_system():
120+
raise UserError("Not allowed")
121+
122+
for query in sql_queries:
123+
env.cr.execute(query)
124+
if not env.cr.description:
125+
result.append(env.cr.statusmessage)
126+
elif not env.cr.rowcount:
127+
result.append({c.name: () for c in env.cr.description})
128+
else:
129+
columns = [c.name for c in env.cr.description]
130+
result.extend(dict(zip(columns, values)) for values in env.cr.fetchall())
131+
132+
log(str({'queries': sql_queries, 'result': result}))
133+
result[:] = []
134+
"""
114135
colorize, color_comment, http_context = str, str, None
115136

116137
if os.getenv('ODOOLY_SSL_UNVERIFIED'):
@@ -1097,6 +1118,32 @@ def generate_api_key(self):
10971118
assert res['res_model'] == "res.users.apikeys.show"
10981119
return self.set_api_key(res['context']['default_key'])
10991120

1121+
def _get_sql_action(self, _external_id="__odooly__.sql"):
1122+
act_model = self._get('ir.actions.server', False)
1123+
if not (action := act_model.get(_external_id)):
1124+
logg_model = self._get('ir.model', False).get('base.model_ir_logging')
1125+
values = {'name': 'SQL Execute', 'state': 'code', 'model_id': logg_model.id}
1126+
(action := act_model.create(values).ensure_one())._set_external_id(_external_id)
1127+
return action.with_context(lang=None)
1128+
1129+
def sql(self, queries):
1130+
"""Execute SQL commands on the PostgreSQL server."""
1131+
qlist = []
1132+
for query in queries.split(";"):
1133+
if any(li.split('--', 1)[0].strip() for li in query.splitlines()):
1134+
qlist.append(query)
1135+
if not qlist:
1136+
return None
1137+
1138+
vals = {"name": f"SQL Execute - {datetime.datetime.now()}"}
1139+
if (sql_action := self._get_sql_action()).code != _sql_action_code:
1140+
vals["code"] = _sql_action_code
1141+
sql_action.write(vals)
1142+
sql_action.with_context(__sql=qlist).run()
1143+
1144+
logg = self._get('ir.logging', False).get([f"func = {vals['name']}"])
1145+
return eval(logg.message, {"datetime": datetime}) if logg else None
1146+
11001147

11011148
class Client:
11021149
"""Connection to an Odoo instance.

scripts/odooly+sql.py

Lines changed: 0 additions & 149 deletions
This file was deleted.

0 commit comments

Comments
 (0)