Skip to content

Commit e7ea631

Browse files
committed
[ADD] sixteen_in_fourteen
1 parent 2fb9ea3 commit e7ea631

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed

sixteen_in_fourteen/__init__.py

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import sys
2+
import importlib
3+
4+
MOVED_MODULES = {
5+
"odoo.addons.sale.models.sale_order_line": "odoo.addons.sale.models.sale",
6+
"odoo.addons.sale.models.sale_order": "odoo.addons.sale.models.sale",
7+
}
8+
EXTENDED_MODULES = ["odoo.tools.float_utils", "odoo.http"]
9+
10+
11+
def extend(module, name):
12+
extended_module = importlib.import_module(f"odoo.addons.sixteen_in_fourteen.{name}")
13+
module.__dict__.update(
14+
{
15+
key: value
16+
for key, value in extended_module.__dict__.items()
17+
if key not in ["__file__", "__doc__", "__package__", "__loader__"]
18+
}
19+
)
20+
21+
22+
class SixteenInFourteenMovedHook(object):
23+
def find_module(self, name, path=None):
24+
if name in MOVED_MODULES:
25+
return self
26+
27+
def load_module(self, name):
28+
assert name not in sys.modules
29+
odoo_module = sys.modules.get(name)
30+
if not odoo_module:
31+
odoo_module = importlib.import_module(MOVED_MODULES[name])
32+
sys.modules[name] = odoo_module
33+
return odoo_module
34+
35+
36+
class SixteenInFourteenExtendedHook(object):
37+
def find_module(self, name, path=None):
38+
if name in EXTENDED_MODULES:
39+
return self
40+
41+
def load_module(self, name):
42+
assert name not in sys.modules
43+
odoo_module = sys.modules.get(name)
44+
if not odoo_module:
45+
odoo_module = importlib.import_module(name)
46+
extend(odoo_module, name)
47+
48+
sys.modules[name] = odoo_module
49+
return odoo_module
50+
51+
52+
sys.meta_path.insert(0, SixteenInFourteenMovedHook())
53+
sys.meta_path.insert(0, SixteenInFourteenExtendedHook())
54+
55+
# Also patch already imported modules
56+
for mod in EXTENDED_MODULES:
57+
if mod in sys.modules:
58+
extend(sys.modules[mod], mod)

sixteen_in_fourteen/__manifest__.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright 2024 Akretion (http://www.akretion.com).
2+
# @author Florian Mounier <florian.mounier@akretion.com>
3+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
4+
5+
{
6+
"name": "Sixteen in Fourteen",
7+
"summary": "Layer of compat to run 16.0 modules in 14.0",
8+
"version": "14.0.1.0.0",
9+
"category": "Technical",
10+
"website": "https://github.com/akretion/ak-odoo-incubator",
11+
"author": " Akretion",
12+
"license": "AGPL-3",
13+
"application": False,
14+
"installable": True,
15+
"depends": [
16+
"base",
17+
],
18+
"data": [],
19+
}

sixteen_in_fourteen/odoo/http.py

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from abc import ABC, abstractmethod
2+
import werkzeug.exceptions
3+
import collections.abc
4+
5+
_dispatchers = {}
6+
CORS_MAX_AGE = 60 * 60 * 24
7+
8+
9+
class Dispatcher(ABC):
10+
routing_type: str
11+
12+
@classmethod
13+
def __init_subclass__(cls):
14+
super().__init_subclass__()
15+
_dispatchers[cls.routing_type] = cls
16+
17+
def __init__(self):
18+
from odoo.http import request
19+
20+
self.request = request
21+
22+
@classmethod
23+
@abstractmethod
24+
def is_compatible_with(cls, request):
25+
"""
26+
Determine if the current request is compatible with this
27+
dispatcher.
28+
"""
29+
30+
def pre_dispatch(self, rule, args):
31+
"""
32+
Prepare the system before dispatching the request to its
33+
controller. This method is often overridden in ir.http to
34+
extract some info from the request query-string or headers and
35+
to save them in the session or in the context.
36+
"""
37+
routing = rule.endpoint.routing
38+
self.request.session.can_save = routing.get("save_session", True)
39+
40+
set_header = self.request.future_response.headers.set
41+
cors = routing.get("cors")
42+
if cors:
43+
set_header("Access-Control-Allow-Origin", cors)
44+
set_header(
45+
"Access-Control-Allow-Methods",
46+
(
47+
"POST"
48+
if routing["type"] == "json"
49+
else ", ".join(routing["methods"] or ["GET", "POST"])
50+
),
51+
)
52+
53+
if cors and self.request.httprequest.method == "OPTIONS":
54+
set_header("Access-Control-Max-Age", CORS_MAX_AGE)
55+
set_header(
56+
"Access-Control-Allow-Headers",
57+
"Origin, X-Requested-With, Content-Type, Accept, Authorization",
58+
)
59+
werkzeug.exceptions.abort(Response(status=204))
60+
61+
@abstractmethod
62+
def dispatch(self, endpoint, args):
63+
"""
64+
Extract the params from the request's body and call the
65+
endpoint. While it is preferred to override ir.http._pre_dispatch
66+
and ir.http._post_dispatch, this method can be override to have
67+
a tight control over the dispatching.
68+
"""
69+
70+
def post_dispatch(self, response):
71+
"""
72+
Manipulate the HTTP response to inject various headers, also
73+
save the session when it is dirty.
74+
"""
75+
self.request._save_session()
76+
self.request._inject_future_response(response)
77+
root.set_csp(response)
78+
79+
@abstractmethod
80+
def handle_error(self, exc: Exception) -> collections.abc.Callable:
81+
"""
82+
Transform the exception into a valid HTTP response. Called upon
83+
any exception while serving a request.
84+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
def json_float_round(value, precision_digits, rounding_method="HALF-UP"):
2+
"""Not suitable for float calculations! Similar to float_repr except that it
3+
returns a float suitable for json dump
4+
5+
This may be necessary to produce "exact" representations of rounded float
6+
values during serialization, such as what is done by `json.dumps()`.
7+
Unfortunately `json.dumps` does not allow any form of custom float representation,
8+
nor any custom types, everything is serialized from the basic JSON types.
9+
10+
:param int precision_digits: number of fractional digits to round to.
11+
:param rounding_method: the rounding method used: 'HALF-UP', 'UP' or 'DOWN',
12+
the first one rounding up to the closest number with the rule that
13+
number>=0.5 is rounded up to 1, the second always rounding up and the
14+
latest one always rounding down.
15+
:return: a rounded float value that must not be used for calculations, but
16+
is ready to be serialized in JSON with minimal chances of
17+
representation errors.
18+
"""
19+
rounded_value = float_round(
20+
value, precision_digits=precision_digits, rounding_method=rounding_method
21+
)
22+
rounded_repr = float_repr(rounded_value, precision_digits=precision_digits)
23+
# As of Python 3.1, rounded_repr should be the shortest representation for our
24+
# rounded float, so we create a new float whose repr is expected
25+
# to be the same value, or a value that is semantically identical
26+
# and will be used in the json serialization.
27+
# e.g. if rounded_repr is '3.1750', the new float repr could be 3.175
28+
# but not 3.174999999999322452.
29+
# Cfr. bpo-1580: https://bugs.python.org/issue1580
30+
return float(rounded_repr)

0 commit comments

Comments
 (0)