Skip to content

Commit

Permalink
[ADD] sixteen_in_fourteen
Browse files Browse the repository at this point in the history
  • Loading branch information
paradoxxxzero committed Jun 14, 2024
1 parent 2fb9ea3 commit e7ea631
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 0 deletions.
58 changes: 58 additions & 0 deletions sixteen_in_fourteen/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import sys
import importlib

MOVED_MODULES = {
"odoo.addons.sale.models.sale_order_line": "odoo.addons.sale.models.sale",
"odoo.addons.sale.models.sale_order": "odoo.addons.sale.models.sale",
}
EXTENDED_MODULES = ["odoo.tools.float_utils", "odoo.http"]


def extend(module, name):
extended_module = importlib.import_module(f"odoo.addons.sixteen_in_fourteen.{name}")
module.__dict__.update(
{
key: value
for key, value in extended_module.__dict__.items()
if key not in ["__file__", "__doc__", "__package__", "__loader__"]
}
)


class SixteenInFourteenMovedHook(object):
def find_module(self, name, path=None):
if name in MOVED_MODULES:
return self

def load_module(self, name):
assert name not in sys.modules
odoo_module = sys.modules.get(name)
if not odoo_module:
odoo_module = importlib.import_module(MOVED_MODULES[name])
sys.modules[name] = odoo_module
return odoo_module


class SixteenInFourteenExtendedHook(object):
def find_module(self, name, path=None):
if name in EXTENDED_MODULES:
return self

def load_module(self, name):
assert name not in sys.modules
odoo_module = sys.modules.get(name)
if not odoo_module:
odoo_module = importlib.import_module(name)
extend(odoo_module, name)

sys.modules[name] = odoo_module
return odoo_module


sys.meta_path.insert(0, SixteenInFourteenMovedHook())
sys.meta_path.insert(0, SixteenInFourteenExtendedHook())

# Also patch already imported modules
for mod in EXTENDED_MODULES:
if mod in sys.modules:
extend(sys.modules[mod], mod)
19 changes: 19 additions & 0 deletions sixteen_in_fourteen/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2024 Akretion (http://www.akretion.com).
# @author Florian Mounier <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
"name": "Sixteen in Fourteen",
"summary": "Layer of compat to run 16.0 modules in 14.0",
"version": "14.0.1.0.0",
"category": "Technical",
"website": "https://github.com/akretion/ak-odoo-incubator",
"author": " Akretion",
"license": "AGPL-3",
"application": False,
"installable": True,
"depends": [
"base",
],
"data": [],
}
84 changes: 84 additions & 0 deletions sixteen_in_fourteen/odoo/http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from abc import ABC, abstractmethod
import werkzeug.exceptions
import collections.abc

_dispatchers = {}
CORS_MAX_AGE = 60 * 60 * 24


class Dispatcher(ABC):
routing_type: str

@classmethod
def __init_subclass__(cls):
super().__init_subclass__()
_dispatchers[cls.routing_type] = cls

def __init__(self):
from odoo.http import request

self.request = request

@classmethod
@abstractmethod
def is_compatible_with(cls, request):
"""
Determine if the current request is compatible with this
dispatcher.
"""

def pre_dispatch(self, rule, args):
"""
Prepare the system before dispatching the request to its
controller. This method is often overridden in ir.http to
extract some info from the request query-string or headers and
to save them in the session or in the context.
"""
routing = rule.endpoint.routing
self.request.session.can_save = routing.get("save_session", True)

set_header = self.request.future_response.headers.set
cors = routing.get("cors")
if cors:
set_header("Access-Control-Allow-Origin", cors)
set_header(
"Access-Control-Allow-Methods",
(
"POST"
if routing["type"] == "json"
else ", ".join(routing["methods"] or ["GET", "POST"])
),
)

if cors and self.request.httprequest.method == "OPTIONS":
set_header("Access-Control-Max-Age", CORS_MAX_AGE)
set_header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization",
)
werkzeug.exceptions.abort(Response(status=204))

@abstractmethod
def dispatch(self, endpoint, args):
"""
Extract the params from the request's body and call the
endpoint. While it is preferred to override ir.http._pre_dispatch
and ir.http._post_dispatch, this method can be override to have
a tight control over the dispatching.
"""

def post_dispatch(self, response):
"""
Manipulate the HTTP response to inject various headers, also
save the session when it is dirty.
"""
self.request._save_session()
self.request._inject_future_response(response)
root.set_csp(response)

@abstractmethod
def handle_error(self, exc: Exception) -> collections.abc.Callable:
"""
Transform the exception into a valid HTTP response. Called upon
any exception while serving a request.
"""
30 changes: 30 additions & 0 deletions sixteen_in_fourteen/odoo/tools/float_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
def json_float_round(value, precision_digits, rounding_method="HALF-UP"):
"""Not suitable for float calculations! Similar to float_repr except that it
returns a float suitable for json dump
This may be necessary to produce "exact" representations of rounded float
values during serialization, such as what is done by `json.dumps()`.
Unfortunately `json.dumps` does not allow any form of custom float representation,
nor any custom types, everything is serialized from the basic JSON types.
:param int precision_digits: number of fractional digits to round to.
:param rounding_method: the rounding method used: 'HALF-UP', 'UP' or 'DOWN',
the first one rounding up to the closest number with the rule that
number>=0.5 is rounded up to 1, the second always rounding up and the
latest one always rounding down.
:return: a rounded float value that must not be used for calculations, but
is ready to be serialized in JSON with minimal chances of
representation errors.
"""
rounded_value = float_round(
value, precision_digits=precision_digits, rounding_method=rounding_method
)
rounded_repr = float_repr(rounded_value, precision_digits=precision_digits)
# As of Python 3.1, rounded_repr should be the shortest representation for our
# rounded float, so we create a new float whose repr is expected
# to be the same value, or a value that is semantically identical
# and will be used in the json serialization.
# e.g. if rounded_repr is '3.1750', the new float repr could be 3.175
# but not 3.174999999999322452.
# Cfr. bpo-1580: https://bugs.python.org/issue1580
return float(rounded_repr)

0 comments on commit e7ea631

Please sign in to comment.