mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-18 05:32:03 +02:00
18.0 vanilla
This commit is contained in:
parent
d72e748793
commit
0a7ae8db93
337 changed files with 399651 additions and 232598 deletions
|
|
@ -1,13 +1,13 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# ruff: noqa: E402, F401
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
""" OpenERP core library."""
|
""" OpenERP core library."""
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
#----------------------------------------------------------
|
|
||||||
# odoo must be a namespace package for odoo.addons to become one too
|
# odoo must be a namespace package for odoo.addons to become one too
|
||||||
# https://packaging.python.org/guides/packaging-namespace-packages/
|
# https://packaging.python.org/guides/packaging-namespace-packages/
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import os.path
|
import os.path
|
||||||
__path__ = [
|
__path__ = [
|
||||||
|
|
@ -20,76 +20,9 @@ MIN_PY_VERSION = (3, 10)
|
||||||
MAX_PY_VERSION = (3, 13)
|
MAX_PY_VERSION = (3, 13)
|
||||||
assert sys.version_info > MIN_PY_VERSION, f"Outdated python version detected, Odoo requires Python >= {'.'.join(map(str, MIN_PY_VERSION))} to run."
|
assert sys.version_info > MIN_PY_VERSION, f"Outdated python version detected, Odoo requires Python >= {'.'.join(map(str, MIN_PY_VERSION))} to run."
|
||||||
|
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
# Running mode flags (gevent, prefork)
|
|
||||||
#----------------------------------------------------------
|
|
||||||
# Is the server running with gevent.
|
|
||||||
evented = False
|
|
||||||
if len(sys.argv) > 1 and sys.argv[1] == 'gevent':
|
|
||||||
sys.argv.remove('gevent')
|
|
||||||
import gevent.monkey
|
|
||||||
import psycopg2
|
|
||||||
from gevent.socket import wait_read, wait_write
|
|
||||||
gevent.monkey.patch_all()
|
|
||||||
|
|
||||||
def gevent_wait_callback(conn, timeout=None):
|
|
||||||
"""A wait callback useful to allow gevent to work with Psycopg."""
|
|
||||||
# Copyright (C) 2010-2012 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
|
||||||
# This function is borrowed from psycogreen module which is licensed
|
|
||||||
# under the BSD license (see in odoo/debian/copyright)
|
|
||||||
while 1:
|
|
||||||
state = conn.poll()
|
|
||||||
if state == psycopg2.extensions.POLL_OK:
|
|
||||||
break
|
|
||||||
elif state == psycopg2.extensions.POLL_READ:
|
|
||||||
wait_read(conn.fileno(), timeout=timeout)
|
|
||||||
elif state == psycopg2.extensions.POLL_WRITE:
|
|
||||||
wait_write(conn.fileno(), timeout=timeout)
|
|
||||||
else:
|
|
||||||
raise psycopg2.OperationalError(
|
|
||||||
"Bad result from poll: %r" % state)
|
|
||||||
psycopg2.extensions.set_wait_callback(gevent_wait_callback)
|
|
||||||
evented = True
|
|
||||||
|
|
||||||
# Is the server running in prefork mode (e.g. behind Gunicorn).
|
|
||||||
# If this is True, the processes have to communicate some events,
|
|
||||||
# e.g. database update or cache invalidation. Each process has also
|
|
||||||
# its own copy of the data structure and we don't need to care about
|
|
||||||
# locks between threads.
|
|
||||||
multi_process = False
|
|
||||||
|
|
||||||
#----------------------------------------------------------
|
|
||||||
# libc UTC hack
|
|
||||||
#----------------------------------------------------------
|
|
||||||
# Make sure the OpenERP server runs in UTC.
|
|
||||||
import os
|
|
||||||
os.environ['TZ'] = 'UTC' # Set the timezone
|
|
||||||
import time
|
|
||||||
if hasattr(time, 'tzset'):
|
|
||||||
time.tzset()
|
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
|
||||||
# some charset are known by Python under a different name
|
|
||||||
# ---------------------------------------------------------
|
|
||||||
import encodings.aliases # noqa: E402
|
|
||||||
|
|
||||||
encodings.aliases.aliases['874'] = 'cp874'
|
|
||||||
encodings.aliases.aliases['windows_874'] = 'cp874'
|
|
||||||
|
|
||||||
#----------------------------------------------------------
|
|
||||||
# alias hebrew iso-8859-8-i and iso-8859-8-e on iso-8859-8
|
|
||||||
# https://bugs.python.org/issue18624
|
|
||||||
#----------------------------------------------------------
|
|
||||||
import codecs
|
|
||||||
import re
|
|
||||||
|
|
||||||
iso8859_8 = codecs.lookup('iso8859_8')
|
|
||||||
iso8859_8ie_re = re.compile(r'iso[-_]?8859[-_]8[-_]?[ei]', re.IGNORECASE)
|
|
||||||
codecs.register(lambda charset: iso8859_8 if iso8859_8ie_re.match(charset) else None)
|
|
||||||
|
|
||||||
#----------------------------------------------------------
|
|
||||||
# Shortcuts
|
# Shortcuts
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
# The hard-coded super-user id (a.k.a. administrator, or root user).
|
# The hard-coded super-user id (a.k.a. administrator, or root user).
|
||||||
SUPERUSER_ID = 1
|
SUPERUSER_ID = 1
|
||||||
|
|
||||||
|
|
@ -100,14 +33,25 @@ def registry(database_name=None):
|
||||||
on the current thread. If the registry does not exist yet, it is created on
|
on the current thread. If the registry does not exist yet, it is created on
|
||||||
the fly.
|
the fly.
|
||||||
"""
|
"""
|
||||||
|
import warnings # noqa: PLC0415
|
||||||
|
warnings.warn("Use directly odoo.modules.registry.Registry", DeprecationWarning, 2)
|
||||||
if database_name is None:
|
if database_name is None:
|
||||||
import threading
|
import threading
|
||||||
database_name = threading.current_thread().dbname
|
database_name = threading.current_thread().dbname
|
||||||
return modules.registry.Registry(database_name)
|
return modules.registry.Registry(database_name)
|
||||||
|
|
||||||
#----------------------------------------------------------
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# Import tools to patch code and libraries
|
||||||
|
# required to do as early as possible for evented and timezone
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
from . import _monkeypatches
|
||||||
|
_monkeypatches.patch_all()
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
# Imports
|
# Imports
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
from . import upgrade # this namespace must be imported first
|
from . import upgrade # this namespace must be imported first
|
||||||
from . import addons
|
from . import addons
|
||||||
from . import conf
|
from . import conf
|
||||||
|
|
@ -120,17 +64,17 @@ from . import service
|
||||||
from . import sql_db
|
from . import sql_db
|
||||||
from . import tools
|
from . import tools
|
||||||
|
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
# Model classes, fields, api decorators, and translations
|
# Model classes, fields, api decorators, and translations
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
from . import models
|
from . import models
|
||||||
from . import fields
|
from . import fields
|
||||||
from . import api
|
from . import api
|
||||||
from odoo.tools.translate import _, _lt
|
from odoo.tools.translate import _, _lt
|
||||||
from odoo.fields import Command
|
from odoo.fields import Command
|
||||||
|
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
# Other imports, which may require stuff from above
|
# Other imports, which may require stuff from above
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
from . import cli
|
from . import cli
|
||||||
from . import http
|
from . import http
|
||||||
|
|
|
||||||
39
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/__init__.py
Normal file
39
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/__init__.py
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# ruff: noqa: F401, PLC0415
|
||||||
|
# ignore import not at top of the file
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from .evented import patch_evented
|
||||||
|
|
||||||
|
|
||||||
|
def set_timezone_utc():
|
||||||
|
os.environ['TZ'] = 'UTC' # Set the timezone
|
||||||
|
if hasattr(time, 'tzset'):
|
||||||
|
time.tzset()
|
||||||
|
|
||||||
|
|
||||||
|
def patch_all():
|
||||||
|
patch_evented()
|
||||||
|
set_timezone_utc()
|
||||||
|
|
||||||
|
from .codecs import patch_codecs
|
||||||
|
patch_codecs()
|
||||||
|
from .email import patch_email
|
||||||
|
patch_email()
|
||||||
|
from .mimetypes import patch_mimetypes
|
||||||
|
patch_mimetypes()
|
||||||
|
from .pytz import patch_pytz
|
||||||
|
patch_pytz()
|
||||||
|
from .literal_eval import patch_literal_eval
|
||||||
|
patch_literal_eval()
|
||||||
|
from .lxml import patch_lxml
|
||||||
|
patch_lxml()
|
||||||
|
from .num2words import patch_num2words
|
||||||
|
patch_num2words()
|
||||||
|
from .stdnum import patch_stdnum
|
||||||
|
patch_stdnum()
|
||||||
|
from .urllib3 import patch_urllib3
|
||||||
|
patch_urllib3()
|
||||||
|
from .werkzeug_urls import patch_werkzeug
|
||||||
|
patch_werkzeug()
|
||||||
|
from .zeep import patch_zeep
|
||||||
|
patch_zeep()
|
||||||
26
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/codecs.py
Normal file
26
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/codecs.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import codecs
|
||||||
|
import encodings.aliases
|
||||||
|
import re
|
||||||
|
|
||||||
|
import babel.core
|
||||||
|
|
||||||
|
|
||||||
|
def patch_codecs():
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# some charset are known by Python under a different name
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
encodings.aliases.aliases['874'] = 'cp874'
|
||||||
|
encodings.aliases.aliases['windows_874'] = 'cp874'
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# alias hebrew iso-8859-8-i and iso-8859-8-e on iso-8859-8
|
||||||
|
# https://bugs.python.org/issue18624
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
iso8859_8 = codecs.lookup('iso8859_8')
|
||||||
|
iso8859_8ie_re = re.compile(r'iso[-_]?8859[-_]8[-_]?[ei]', re.IGNORECASE)
|
||||||
|
codecs.register(lambda charset: iso8859_8 if iso8859_8ie_re.match(charset) else None)
|
||||||
|
|
||||||
|
# To remove when corrected in Babel
|
||||||
|
babel.core.LOCALE_ALIASES['nb'] = 'nb_NO'
|
||||||
12
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/email.py
Normal file
12
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/email.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
from email._policybase import _PolicyBase
|
||||||
|
|
||||||
|
|
||||||
|
def patch_email():
|
||||||
|
def policy_clone(self, **kwargs):
|
||||||
|
for arg in kwargs:
|
||||||
|
if arg.startswith("_") or "__" in arg:
|
||||||
|
raise AttributeError(f"{self.__class__.__name__!r} object has no attribute {arg!r}")
|
||||||
|
return orig_policy_clone(self, **kwargs)
|
||||||
|
|
||||||
|
orig_policy_clone = _PolicyBase.clone
|
||||||
|
_PolicyBase.clone = policy_clone
|
||||||
39
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/evented.py
Normal file
39
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/evented.py
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
"""
|
||||||
|
Running mode flags (gevent, prefork)
|
||||||
|
|
||||||
|
This should be imported as early as possible.
|
||||||
|
It will initialize the `odoo.evented` variable.
|
||||||
|
"""
|
||||||
|
import odoo
|
||||||
|
import sys
|
||||||
|
|
||||||
|
odoo.evented = False
|
||||||
|
|
||||||
|
|
||||||
|
def patch_evented():
|
||||||
|
if odoo.evented or not (len(sys.argv) > 1 and sys.argv[1] == 'gevent'):
|
||||||
|
return
|
||||||
|
sys.argv.remove('gevent')
|
||||||
|
import gevent.monkey # noqa: PLC0415
|
||||||
|
import psycopg2 # noqa: PLC0415
|
||||||
|
from gevent.socket import wait_read, wait_write # noqa: PLC0415
|
||||||
|
gevent.monkey.patch_all()
|
||||||
|
|
||||||
|
def gevent_wait_callback(conn, timeout=None):
|
||||||
|
"""A wait callback useful to allow gevent to work with Psycopg."""
|
||||||
|
# Copyright (C) 2010-2012 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||||
|
# This function is borrowed from psycogreen module which is licensed
|
||||||
|
# under the BSD license (see in odoo/debian/copyright)
|
||||||
|
while 1:
|
||||||
|
state = conn.poll()
|
||||||
|
if state == psycopg2.extensions.POLL_OK:
|
||||||
|
break
|
||||||
|
elif state == psycopg2.extensions.POLL_READ:
|
||||||
|
wait_read(conn.fileno(), timeout=timeout)
|
||||||
|
elif state == psycopg2.extensions.POLL_WRITE:
|
||||||
|
wait_write(conn.fileno(), timeout=timeout)
|
||||||
|
else:
|
||||||
|
raise psycopg2.OperationalError(
|
||||||
|
"Bad result from poll: %r" % state)
|
||||||
|
psycopg2.extensions.set_wait_callback(gevent_wait_callback)
|
||||||
|
odoo.evented = True
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
# ruff: noqa: E402, PLC0415
|
||||||
|
# ignore import not at top of the file
|
||||||
|
import ast
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
orig_literal_eval = ast.literal_eval
|
||||||
|
|
||||||
|
|
||||||
|
def literal_eval(expr):
|
||||||
|
# limit the size of the expression to avoid segmentation faults
|
||||||
|
# the default limit is set to 100KiB
|
||||||
|
# can be overridden by setting the ODOO_LIMIT_LITEVAL_BUFFER buffer_size_environment variable
|
||||||
|
|
||||||
|
buffer_size = 102400
|
||||||
|
buffer_size_env = os.getenv("ODOO_LIMIT_LITEVAL_BUFFER")
|
||||||
|
|
||||||
|
if buffer_size_env:
|
||||||
|
if buffer_size_env.isdigit():
|
||||||
|
buffer_size = int(buffer_size_env)
|
||||||
|
else:
|
||||||
|
_logger.error("ODOO_LIMIT_LITEVAL_BUFFER has to be an integer, defaulting to 100KiB")
|
||||||
|
|
||||||
|
if isinstance(expr, str) and len(expr) > buffer_size:
|
||||||
|
raise ValueError("expression can't exceed buffer limit")
|
||||||
|
|
||||||
|
return orig_literal_eval(expr)
|
||||||
|
|
||||||
|
|
||||||
|
def patch_literal_eval():
|
||||||
|
ast.literal_eval = literal_eval
|
||||||
13
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/lxml.py
Normal file
13
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/lxml.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import lxml.html.clean
|
||||||
|
import re
|
||||||
|
|
||||||
|
from importlib.metadata import version
|
||||||
|
|
||||||
|
from odoo.tools import parse_version
|
||||||
|
|
||||||
|
|
||||||
|
def patch_lxml():
|
||||||
|
# between these versions having a couple data urls in a style attribute
|
||||||
|
# or style node removes the attribute or node erroneously
|
||||||
|
if parse_version("4.6.0") <= parse_version(version('lxml')) < parse_version("5.2.0"):
|
||||||
|
lxml.html.clean._find_image_dataurls = re.compile(r'data:image/(.+?);base64,').findall
|
||||||
15
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/mimetypes.py
Normal file
15
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/mimetypes.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
|
|
||||||
|
def patch_mimetypes():
|
||||||
|
# if extension is already knows, the new definition will remplace the existing one
|
||||||
|
# Add potentially missing (older ubuntu) font mime types
|
||||||
|
mimetypes.add_type('application/font-woff', '.woff')
|
||||||
|
mimetypes.add_type('application/vnd.ms-fontobject', '.eot')
|
||||||
|
mimetypes.add_type('application/x-font-ttf', '.ttf')
|
||||||
|
mimetypes.add_type('image/webp', '.webp')
|
||||||
|
# Add potentially wrong (detected on windows) svg mime types
|
||||||
|
mimetypes.add_type('image/svg+xml', '.svg')
|
||||||
|
# this one can be present on windows with the value 'text/plain' which
|
||||||
|
# breaks loading js files from an addon's static folder
|
||||||
|
mimetypes.add_type('text/javascript', '.js')
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
import decimal
|
import decimal
|
||||||
|
import logging
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from decimal import ROUND_HALF_UP, Decimal
|
from decimal import ROUND_HALF_UP, Decimal
|
||||||
from math import floor
|
from math import floor
|
||||||
|
|
||||||
|
from odoo import MIN_PY_VERSION
|
||||||
|
|
||||||
# The following section of the code is used to monkey patch
|
# The following section of the code is used to monkey patch
|
||||||
# the Arabic class of num2words package as there are some problems
|
# the Arabic class of num2words package as there are some problems
|
||||||
# upgrading the package to the newer version that fixed the bugs
|
# upgrading the package to the newer version that fixed the bugs
|
||||||
|
|
@ -708,3 +711,274 @@ def to_s(val):
|
||||||
return unicode(val)
|
return unicode(val)
|
||||||
except NameError:
|
except NameError:
|
||||||
return str(val)
|
return str(val)
|
||||||
|
|
||||||
|
|
||||||
|
# Derived from num2cyrillic licensed under LGPL-3.0-only
|
||||||
|
# Copyright 2018 ClaimCompass, Inc (num2cyrillic authored by Velizar Shulev) https://github.com/ClaimCompass/num2cyrillic
|
||||||
|
# Copyright 1997 The PHP Group (PEAR::Numbers_Words, authored by Kouber Saparev) https://github.com/pear/Numbers_Words/blob/master/Numbers/Words/Locale/bg.php
|
||||||
|
|
||||||
|
|
||||||
|
class NumberToWords_BG(Num2Word_Base):
|
||||||
|
locale = 'bg'
|
||||||
|
lang = 'Bulgarian'
|
||||||
|
lang_native = 'Български'
|
||||||
|
_misc_strings = {
|
||||||
|
'deset': 'десет',
|
||||||
|
'edinadeset': 'единадесет',
|
||||||
|
'na': 'на',
|
||||||
|
'sto': 'сто',
|
||||||
|
'sta': 'ста',
|
||||||
|
'stotin': 'стотин',
|
||||||
|
'hiliadi': 'хиляди',
|
||||||
|
}
|
||||||
|
_digits = {
|
||||||
|
0: [None, 'едно', 'две', 'три', 'четири', 'пет', 'шест', 'седем', 'осем', 'девет'],
|
||||||
|
}
|
||||||
|
_digits[1] = [None, 'един', 'два'] + _digits[0][3:]
|
||||||
|
_digits[-1] = [None, 'една'] + _digits[0][2:]
|
||||||
|
_last_and = False
|
||||||
|
_zero = 'нула'
|
||||||
|
_infinity = 'безкрайност'
|
||||||
|
_and = 'и'
|
||||||
|
_sep = ' '
|
||||||
|
_minus = 'минус'
|
||||||
|
_plural = 'а'
|
||||||
|
_exponent = {
|
||||||
|
0: '',
|
||||||
|
3: 'хиляда',
|
||||||
|
6: 'милион',
|
||||||
|
9: 'милиард',
|
||||||
|
12: 'трилион',
|
||||||
|
15: 'квадрилион',
|
||||||
|
18: 'квинтилион',
|
||||||
|
21: 'секстилион',
|
||||||
|
24: 'септилион',
|
||||||
|
27: 'октилион',
|
||||||
|
30: 'ноналион',
|
||||||
|
33: 'декалион',
|
||||||
|
36: 'ундекалион',
|
||||||
|
39: 'дуодекалион',
|
||||||
|
42: 'тредекалион',
|
||||||
|
45: 'кватордекалион',
|
||||||
|
48: 'квинтдекалион',
|
||||||
|
51: 'сексдекалион',
|
||||||
|
54: 'септдекалион',
|
||||||
|
57: 'октодекалион',
|
||||||
|
60: 'новемдекалион',
|
||||||
|
63: 'вигинтилион',
|
||||||
|
66: 'унвигинтилион',
|
||||||
|
69: 'дуовигинтилион',
|
||||||
|
72: 'тревигинтилион',
|
||||||
|
75: 'кваторвигинтилион',
|
||||||
|
78: 'квинвигинтилион',
|
||||||
|
81: 'сексвигинтилион',
|
||||||
|
84: 'септенвигинтилион',
|
||||||
|
87: 'октовигинтилион',
|
||||||
|
90: 'новемвигинтилион',
|
||||||
|
93: 'тригинтилион',
|
||||||
|
96: 'унтригинтилион',
|
||||||
|
99: 'дуотригинтилион',
|
||||||
|
102: 'третригинтилион',
|
||||||
|
105: 'кватортригинтилион',
|
||||||
|
108: 'квинтригинтилион',
|
||||||
|
111: 'секстригинтилион',
|
||||||
|
114: 'септентригинтилион',
|
||||||
|
117: 'октотригинтилион',
|
||||||
|
120: 'новемтригинтилион',
|
||||||
|
123: 'квадрагинтилион',
|
||||||
|
126: 'унквадрагинтилион',
|
||||||
|
129: 'дуоквадрагинтилион',
|
||||||
|
132: 'треквадрагинтилион',
|
||||||
|
135: 'кваторквадрагинтилион',
|
||||||
|
138: 'квинквадрагинтилион',
|
||||||
|
141: 'сексквадрагинтилион',
|
||||||
|
144: 'септенквадрагинтилион',
|
||||||
|
147: 'октоквадрагинтилион',
|
||||||
|
150: 'новемквадрагинтилион',
|
||||||
|
153: 'квинквагинтилион',
|
||||||
|
156: 'унквинкагинтилион',
|
||||||
|
159: 'дуоквинкагинтилион',
|
||||||
|
162: 'треквинкагинтилион',
|
||||||
|
165: 'кваторквинкагинтилион',
|
||||||
|
168: 'квинквинкагинтилион',
|
||||||
|
171: 'сексквинкагинтилион',
|
||||||
|
174: 'септенквинкагинтилион',
|
||||||
|
177: 'октоквинкагинтилион',
|
||||||
|
180: 'новемквинкагинтилион',
|
||||||
|
183: 'сексагинтилион',
|
||||||
|
186: 'унсексагинтилион',
|
||||||
|
189: 'дуосексагинтилион',
|
||||||
|
192: 'тресексагинтилион',
|
||||||
|
195: 'кваторсексагинтилион',
|
||||||
|
198: 'квинсексагинтилион',
|
||||||
|
201: 'секссексагинтилион',
|
||||||
|
204: 'септенсексагинтилион',
|
||||||
|
207: 'октосексагинтилион',
|
||||||
|
210: 'новемсексагинтилион',
|
||||||
|
213: 'септагинтилион',
|
||||||
|
216: 'унсептагинтилион',
|
||||||
|
219: 'дуосептагинтилион',
|
||||||
|
222: 'тресептагинтилион',
|
||||||
|
225: 'кваторсептагинтилион',
|
||||||
|
228: 'квинсептагинтилион',
|
||||||
|
231: 'секссептагинтилион',
|
||||||
|
234: 'септенсептагинтилион',
|
||||||
|
237: 'октосептагинтилион',
|
||||||
|
240: 'новемсептагинтилион',
|
||||||
|
243: 'октогинтилион',
|
||||||
|
246: 'уноктогинтилион',
|
||||||
|
249: 'дуооктогинтилион',
|
||||||
|
252: 'треоктогинтилион',
|
||||||
|
255: 'кватороктогинтилион',
|
||||||
|
258: 'квиноктогинтилион',
|
||||||
|
261: 'сексоктогинтилион',
|
||||||
|
264: 'септоктогинтилион',
|
||||||
|
267: 'октооктогинтилион',
|
||||||
|
270: 'новемоктогинтилион',
|
||||||
|
273: 'нонагинтилион',
|
||||||
|
276: 'уннонагинтилион',
|
||||||
|
279: 'дуононагинтилион',
|
||||||
|
282: 'тренонагинтилион',
|
||||||
|
285: 'кваторнонагинтилион',
|
||||||
|
288: 'квиннонагинтилион',
|
||||||
|
291: 'секснонагинтилион',
|
||||||
|
294: 'септеннонагинтилион',
|
||||||
|
297: 'октононагинтилион',
|
||||||
|
300: 'новемнонагинтилион',
|
||||||
|
303: 'центилион',
|
||||||
|
}
|
||||||
|
|
||||||
|
def to_cardinal(self, value):
|
||||||
|
return '' if value is None else self._to_words(value).strip()
|
||||||
|
|
||||||
|
def to_ordinal(self, _):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def to_ordinal_num(self, _):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def to_year(self, _):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def to_currency(self, _):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _split_number(self, num):
|
||||||
|
if isinstance(num, int):
|
||||||
|
num = str(num)
|
||||||
|
first = []
|
||||||
|
if len(num) % 3 != 0:
|
||||||
|
if len(num[1:]) % 3 == 0:
|
||||||
|
first = [num[0:1]]
|
||||||
|
num = num[1:]
|
||||||
|
elif len(num[2:]) % 3 == 0:
|
||||||
|
first = [num[0:2]]
|
||||||
|
num = num[2:]
|
||||||
|
ret = [num[i:i + 3] for i in range(0, len(num), 3)]
|
||||||
|
return first + ret
|
||||||
|
|
||||||
|
def _discard_empties(self, ls):
|
||||||
|
return list(filter(lambda x: x is not None, ls))
|
||||||
|
|
||||||
|
def _show_digits_group(self, num, gender=0, last=False):
|
||||||
|
num = int(num)
|
||||||
|
e = int(num % 10) # ones
|
||||||
|
d = int((num - e) % 100 / 10) # tens
|
||||||
|
s = int((num - d * 10 - e) % 1000 / 100) # hundreds
|
||||||
|
ret = [None] * 6
|
||||||
|
|
||||||
|
if s:
|
||||||
|
if s == 1:
|
||||||
|
ret[1] = self._misc_strings['sto']
|
||||||
|
elif s == 2 or s == 3:
|
||||||
|
ret[1] = self._digits[0][s] + self._misc_strings['sta']
|
||||||
|
else:
|
||||||
|
ret[1] = self._digits[0][s] + self._misc_strings['stotin']
|
||||||
|
|
||||||
|
if d:
|
||||||
|
if d == 1:
|
||||||
|
if not e:
|
||||||
|
ret[3] = self._misc_strings['deset']
|
||||||
|
else:
|
||||||
|
if e == 1:
|
||||||
|
ret[3] = self._misc_strings['edinadeset']
|
||||||
|
else:
|
||||||
|
ret[3] = self._digits[1][e] + self._misc_strings['na'] + self._misc_strings['deset']
|
||||||
|
e = 0
|
||||||
|
else:
|
||||||
|
ret[3] = self._digits[1][d] + self._misc_strings['deset']
|
||||||
|
|
||||||
|
if e:
|
||||||
|
ret[5] = self._digits[gender][e]
|
||||||
|
|
||||||
|
if len(self._discard_empties(ret)) > 1:
|
||||||
|
if e:
|
||||||
|
ret[4] = self._and
|
||||||
|
else:
|
||||||
|
ret[2] = self._and
|
||||||
|
|
||||||
|
if last:
|
||||||
|
if not s or len(self._discard_empties(ret)) == 1:
|
||||||
|
ret[0] = self._and
|
||||||
|
self._last_and = True
|
||||||
|
|
||||||
|
return self._sep.join(self._discard_empties(ret))
|
||||||
|
|
||||||
|
def _to_words(self, num=0):
|
||||||
|
num_groups = self._split_number(num)
|
||||||
|
sizeof_num_groups = len(num_groups)
|
||||||
|
|
||||||
|
ret = [None] * (sizeof_num_groups + 1)
|
||||||
|
ret_minus = ''
|
||||||
|
|
||||||
|
if num < 0:
|
||||||
|
ret_minus = self._minus + self._sep
|
||||||
|
elif num == 0:
|
||||||
|
return self._zero
|
||||||
|
|
||||||
|
i = sizeof_num_groups - 1
|
||||||
|
j = 1
|
||||||
|
while i >= 0:
|
||||||
|
if ret[j] is None:
|
||||||
|
ret[j] = ''
|
||||||
|
|
||||||
|
_pow = sizeof_num_groups - i
|
||||||
|
|
||||||
|
if num_groups[i] != '000':
|
||||||
|
if int(num_groups[i]) > 1:
|
||||||
|
if _pow == 1:
|
||||||
|
ret[j] += self._show_digits_group(num_groups[i], 0, not self._last_and and i) + self._sep
|
||||||
|
ret[j] += self._exponent[(_pow - 1) * 3]
|
||||||
|
elif _pow == 2:
|
||||||
|
ret[j] += self._show_digits_group(num_groups[i], -1, not self._last_and and i) + self._sep
|
||||||
|
ret[j] += self._misc_strings['hiliadi'] + self._sep
|
||||||
|
else:
|
||||||
|
ret[j] += self._show_digits_group(num_groups[i], 1, not self._last_and and i) + self._sep
|
||||||
|
ret[j] += self._exponent[(_pow - 1) * 3] + self._plural + self._sep
|
||||||
|
else:
|
||||||
|
if _pow == 1:
|
||||||
|
ret[j] += self._show_digits_group(num_groups[i], 0, not self._last_and and i) + self._sep
|
||||||
|
elif _pow == 2:
|
||||||
|
ret[j] += self._exponent[(_pow - 1) * 3] + self._sep
|
||||||
|
else:
|
||||||
|
ret[j] += self._digits[1][1] + self._sep + self._exponent[(_pow - 1) * 3] + self._sep
|
||||||
|
|
||||||
|
i -= 1
|
||||||
|
j += 1
|
||||||
|
|
||||||
|
ret = self._discard_empties(ret)
|
||||||
|
ret.reverse()
|
||||||
|
return ret_minus + ''.join(ret)
|
||||||
|
|
||||||
|
|
||||||
|
def patch_num2words():
|
||||||
|
try:
|
||||||
|
import num2words # noqa: PLC0415
|
||||||
|
except ImportError:
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
_logger.warning("num2words is not available, Arabic number to words conversion will not work")
|
||||||
|
return
|
||||||
|
if MIN_PY_VERSION >= (3, 12):
|
||||||
|
raise RuntimeError("The num2words monkey patch is obsolete. Bump the version of the library to the latest available in the official package repository, if it hasn't already been done, and remove the patch.")
|
||||||
|
num2words.CONVERTER_CLASSES["ar"] = Num2Word_AR_Fixed()
|
||||||
|
num2words.CONVERTER_CLASSES["bg"] = NumberToWords_BG()
|
||||||
|
|
@ -119,14 +119,13 @@ _tz_mapping = {
|
||||||
"Zulu": "Etc/UTC",
|
"Zulu": "Etc/UTC",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
original_pytz_timezone = pytz.timezone
|
original_pytz_timezone = pytz.timezone
|
||||||
|
|
||||||
|
|
||||||
def timezone(name):
|
def patch_pytz():
|
||||||
if name not in pytz.all_timezones_set and name in _tz_mapping:
|
def timezone(name):
|
||||||
name = _tz_mapping[name]
|
if name not in pytz.all_timezones_set and name in _tz_mapping:
|
||||||
return original_pytz_timezone(name)
|
name = _tz_mapping[name]
|
||||||
|
return original_pytz_timezone(name)
|
||||||
|
|
||||||
|
pytz.timezone = timezone
|
||||||
pytz.timezone = timezone
|
|
||||||
57
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/stdnum.py
Normal file
57
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/stdnum.py
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# ruff: noqa: PLC0415
|
||||||
|
|
||||||
|
_soap_clients = {}
|
||||||
|
|
||||||
|
|
||||||
|
def new_get_soap_client(wsdlurl, timeout=30):
|
||||||
|
# stdnum library does not set the timeout for the zeep Transport class correctly
|
||||||
|
# (timeout is to fetch the wsdl and operation_timeout is to perform the call),
|
||||||
|
# requiring us to monkey patch the get_soap_client function.
|
||||||
|
# Can be removed when https://github.com/arthurdejong/python-stdnum/issues/444 is
|
||||||
|
# resolved and the version of the dependency is updated.
|
||||||
|
# The code is a copy of the original apart for the line related to the Transport class.
|
||||||
|
# This was done to keep the code as similar to the original and to reduce the possibility
|
||||||
|
# of introducing import errors, even though some imports are not in the requirements.
|
||||||
|
# See https://github.com/odoo/odoo/pull/173359 for a more thorough explanation.
|
||||||
|
if (wsdlurl, timeout) not in _soap_clients:
|
||||||
|
try:
|
||||||
|
from zeep.transports import Transport
|
||||||
|
transport = Transport(operation_timeout=timeout, timeout=timeout) # operational_timeout added here
|
||||||
|
from zeep import CachingClient
|
||||||
|
client = CachingClient(wsdlurl, transport=transport).service
|
||||||
|
except ImportError:
|
||||||
|
# fall back to non-caching zeep client
|
||||||
|
try:
|
||||||
|
from zeep import Client
|
||||||
|
client = Client(wsdlurl, transport=transport).service
|
||||||
|
except ImportError:
|
||||||
|
# other implementations require passing the proxy config
|
||||||
|
try:
|
||||||
|
from urllib import getproxies
|
||||||
|
except ImportError:
|
||||||
|
from urllib.request import getproxies
|
||||||
|
# fall back to suds
|
||||||
|
try:
|
||||||
|
from suds.client import Client
|
||||||
|
client = Client(
|
||||||
|
wsdlurl, proxy=getproxies(), timeout=timeout).service
|
||||||
|
except ImportError:
|
||||||
|
# use pysimplesoap as last resort
|
||||||
|
try:
|
||||||
|
from pysimplesoap.client import SoapClient
|
||||||
|
client = SoapClient(
|
||||||
|
wsdl=wsdlurl, proxy=getproxies(), timeout=timeout)
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError(
|
||||||
|
'No SOAP library (such as zeep) found')
|
||||||
|
_soap_clients[(wsdlurl, timeout)] = client
|
||||||
|
return _soap_clients[(wsdlurl, timeout)]
|
||||||
|
|
||||||
|
|
||||||
|
def patch_stdnum():
|
||||||
|
try:
|
||||||
|
from stdnum import util
|
||||||
|
except ImportError:
|
||||||
|
return # nothing to patch
|
||||||
|
|
||||||
|
util.get_soap_client = new_get_soap_client
|
||||||
12
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/urllib3.py
Normal file
12
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/urllib3.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
from urllib3 import PoolManager
|
||||||
|
|
||||||
|
orig_pool_init = PoolManager.__init__
|
||||||
|
|
||||||
|
|
||||||
|
def pool_init(self, *args, **kwargs):
|
||||||
|
orig_pool_init(self, *args, **kwargs)
|
||||||
|
self.pool_classes_by_scheme = {**self.pool_classes_by_scheme}
|
||||||
|
|
||||||
|
|
||||||
|
def patch_urllib3():
|
||||||
|
PoolManager.__init__ = pool_init
|
||||||
|
|
@ -1,14 +1,24 @@
|
||||||
|
# ruff: noqa: PLC0415 (import in function not at top-level)
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import operator
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
import typing as t
|
import typing as t
|
||||||
import warnings
|
import warnings
|
||||||
from werkzeug.datastructures import iter_multi_items
|
from shutil import copyfileobj
|
||||||
from werkzeug.urls import _decode_idna
|
from types import CodeType
|
||||||
|
|
||||||
|
from werkzeug import urls
|
||||||
|
from werkzeug.datastructures import FileStorage, MultiDict
|
||||||
|
from werkzeug.routing import Rule
|
||||||
|
from werkzeug.urls import _decode_idna
|
||||||
|
from werkzeug.wrappers import Request, Response
|
||||||
|
|
||||||
|
Rule_get_func_code = hasattr(Rule, '_get_func_code') and Rule._get_func_code
|
||||||
|
|
||||||
import operator
|
|
||||||
|
|
||||||
def _check_str_tuple(value: t.Tuple[t.AnyStr, ...]) -> None:
|
def _check_str_tuple(value: t.Tuple[t.AnyStr, ...]) -> None:
|
||||||
"""Ensure tuple items are all strings or all bytes."""
|
"""Ensure tuple items are all strings or all bytes."""
|
||||||
|
|
@ -30,8 +40,10 @@ def _make_encode_wrapper(reference: t.AnyStr) -> t.Callable[[str], t.AnyStr]:
|
||||||
|
|
||||||
return operator.methodcaller("encode", "latin1")
|
return operator.methodcaller("encode", "latin1")
|
||||||
|
|
||||||
|
|
||||||
_default_encoding = sys.getdefaultencoding()
|
_default_encoding = sys.getdefaultencoding()
|
||||||
|
|
||||||
|
|
||||||
def _to_str(
|
def _to_str(
|
||||||
x: t.Optional[t.Any],
|
x: t.Optional[t.Any],
|
||||||
charset: t.Optional[str] = _default_encoding,
|
charset: t.Optional[str] = _default_encoding,
|
||||||
|
|
@ -51,7 +63,6 @@ def _to_str(
|
||||||
return x.decode(charset, errors) # type: ignore
|
return x.decode(charset, errors) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
from werkzeug import datastructures as ds
|
from werkzeug import datastructures as ds
|
||||||
|
|
||||||
|
|
@ -98,7 +109,7 @@ class BaseURL(_URLTuple):
|
||||||
_lbracket: str
|
_lbracket: str
|
||||||
_rbracket: str
|
_rbracket: str
|
||||||
|
|
||||||
def __new__(cls, *args: t.Any, **kwargs: t.Any) -> BaseURL:
|
def __new__(cls, *args: t.Any, **kwargs: t.Any) -> BaseURL: # noqa: PYI034
|
||||||
return super().__new__(cls, *args, **kwargs)
|
return super().__new__(cls, *args, **kwargs)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
|
@ -126,10 +137,8 @@ class BaseURL(_URLTuple):
|
||||||
"""
|
"""
|
||||||
rv = self.host
|
rv = self.host
|
||||||
if rv is not None and isinstance(rv, str):
|
if rv is not None and isinstance(rv, str):
|
||||||
try:
|
with contextlib.suppress(UnicodeError):
|
||||||
rv = rv.encode("idna").decode("ascii")
|
rv = rv.encode("idna").decode("ascii")
|
||||||
except UnicodeError:
|
|
||||||
pass
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -985,7 +994,7 @@ def url_join(
|
||||||
if not url:
|
if not url:
|
||||||
return base
|
return base
|
||||||
|
|
||||||
bscheme, bnetloc, bpath, bquery, bfragment = url_parse(
|
bscheme, bnetloc, bpath, bquery, _bfragment = url_parse(
|
||||||
base, allow_fragments=allow_fragments
|
base, allow_fragments=allow_fragments
|
||||||
)
|
)
|
||||||
scheme, netloc, path, query, fragment = url_parse(url, bscheme, allow_fragments)
|
scheme, netloc, path, query, fragment = url_parse(url, bscheme, allow_fragments)
|
||||||
|
|
@ -1031,16 +1040,37 @@ def url_join(
|
||||||
return url_unparse((scheme, netloc, path, query, fragment))
|
return url_unparse((scheme, netloc, path, query, fragment))
|
||||||
|
|
||||||
|
|
||||||
from werkzeug import urls
|
def patch_werkzeug():
|
||||||
# see https://github.com/pallets/werkzeug/compare/2.3.0..3.0.0
|
from ..tools.json import scriptsafe # noqa: PLC0415
|
||||||
# see https://github.com/pallets/werkzeug/blob/2.3.0/src/werkzeug/urls.py for replacement
|
Request.json_module = Response.json_module = scriptsafe
|
||||||
urls.url_decode = url_decode
|
|
||||||
urls.url_encode = url_encode
|
FileStorage.save = lambda self, dst, buffer_size=(1 << 20): copyfileobj(self.stream, dst, buffer_size)
|
||||||
urls.url_join = url_join
|
|
||||||
urls.url_parse = url_parse
|
def _multidict_deepcopy(self, memo=None):
|
||||||
urls.url_quote = url_quote
|
return orig_deepcopy(self)
|
||||||
urls.url_unquote = url_unquote
|
|
||||||
urls.url_quote_plus = url_quote_plus
|
orig_deepcopy = MultiDict.deepcopy
|
||||||
urls.url_unquote_plus = url_unquote_plus
|
MultiDict.deepcopy = _multidict_deepcopy
|
||||||
urls.url_unparse = url_unparse
|
|
||||||
urls.URL = URL
|
if Rule_get_func_code:
|
||||||
|
@staticmethod
|
||||||
|
def _get_func_code(code, name):
|
||||||
|
assert isinstance(code, CodeType)
|
||||||
|
return Rule_get_func_code(code, name)
|
||||||
|
Rule._get_func_code = _get_func_code
|
||||||
|
|
||||||
|
if hasattr(urls, 'url_join'):
|
||||||
|
# URLs are already patched
|
||||||
|
return
|
||||||
|
# see https://github.com/pallets/werkzeug/compare/2.3.0..3.0.0
|
||||||
|
# see https://github.com/pallets/werkzeug/blob/2.3.0/src/werkzeug/urls.py for replacement
|
||||||
|
urls.url_decode = url_decode
|
||||||
|
urls.url_encode = url_encode
|
||||||
|
urls.url_join = url_join
|
||||||
|
urls.url_parse = url_parse
|
||||||
|
urls.url_quote = url_quote
|
||||||
|
urls.url_unquote = url_unquote
|
||||||
|
urls.url_quote_plus = url_quote_plus
|
||||||
|
urls.url_unquote_plus = url_unquote_plus
|
||||||
|
urls.url_unparse = url_unparse
|
||||||
|
urls.URL = URL
|
||||||
9
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/zeep.py
Normal file
9
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/zeep.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from zeep.xsd import visitor
|
||||||
|
from zeep.xsd.const import xsd_ns
|
||||||
|
|
||||||
|
|
||||||
|
def patch_zeep():
|
||||||
|
# see https://github.com/mvantellingen/python-zeep/issues/1185
|
||||||
|
if visitor.tags.notation.localname != 'notation':
|
||||||
|
visitor.tags.notation = xsd_ns('notation')
|
||||||
|
visitor.SchemaVisitor.visitors[visitor.tags.notation] = visitor.SchemaVisitor.visit_notation
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
from . import controllers
|
from . import controllers
|
||||||
from . import models
|
from . import models
|
||||||
from . import populate
|
|
||||||
from . import report
|
from . import report
|
||||||
from . import wizard
|
from . import wizard
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ The kernel of Odoo, needed for all installation.
|
||||||
'views/ir_ui_menu_views.xml',
|
'views/ir_ui_menu_views.xml',
|
||||||
'views/ir_ui_view_views.xml',
|
'views/ir_ui_view_views.xml',
|
||||||
'views/ir_default_views.xml',
|
'views/ir_default_views.xml',
|
||||||
|
'data/ir_config_parameter_data.xml',
|
||||||
'data/ir_cron_data.xml',
|
'data/ir_cron_data.xml',
|
||||||
'report/ir_model_report.xml',
|
'report/ir_model_report.xml',
|
||||||
'report/ir_model_templates.xml',
|
'report/ir_model_templates.xml',
|
||||||
|
|
@ -67,8 +68,8 @@ The kernel of Odoo, needed for all installation.
|
||||||
'views/res_country_views.xml',
|
'views/res_country_views.xml',
|
||||||
'views/res_currency_views.xml',
|
'views/res_currency_views.xml',
|
||||||
'views/res_users_views.xml',
|
'views/res_users_views.xml',
|
||||||
|
'views/res_device_views.xml',
|
||||||
'views/res_users_identitycheck_views.xml',
|
'views/res_users_identitycheck_views.xml',
|
||||||
'views/ir_property_views.xml',
|
|
||||||
'views/res_config_settings_views.xml',
|
'views/res_config_settings_views.xml',
|
||||||
'views/report_paperformat_views.xml',
|
'views/report_paperformat_views.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from markupsafe import Markup
|
||||||
import odoo
|
import odoo
|
||||||
from odoo.http import Controller, route, dispatch_rpc, request, Response
|
from odoo.http import Controller, route, dispatch_rpc, request, Response
|
||||||
from odoo.fields import Date, Datetime, Command
|
from odoo.fields import Date, Datetime, Command
|
||||||
from odoo.tools import lazy, ustr
|
from odoo.tools import lazy
|
||||||
from odoo.tools.misc import frozendict
|
from odoo.tools.misc import frozendict
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
@ -45,7 +45,7 @@ def xmlrpc_handle_exception_int(e):
|
||||||
formatted_info = "".join(traceback.format_exception(*info))
|
formatted_info = "".join(traceback.format_exception(*info))
|
||||||
fault = xmlrpc.client.Fault(RPC_FAULT_CODE_APPLICATION_ERROR, formatted_info)
|
fault = xmlrpc.client.Fault(RPC_FAULT_CODE_APPLICATION_ERROR, formatted_info)
|
||||||
|
|
||||||
return xmlrpc.client.dumps(fault, allow_none=None)
|
return dumps(fault)
|
||||||
|
|
||||||
|
|
||||||
def xmlrpc_handle_exception_string(e):
|
def xmlrpc_handle_exception_string(e):
|
||||||
|
|
@ -65,7 +65,7 @@ def xmlrpc_handle_exception_string(e):
|
||||||
formatted_info = "".join(traceback.format_exception(*info))
|
formatted_info = "".join(traceback.format_exception(*info))
|
||||||
fault = xmlrpc.client.Fault(odoo.tools.exception_to_unicode(e), formatted_info)
|
fault = xmlrpc.client.Fault(odoo.tools.exception_to_unicode(e), formatted_info)
|
||||||
|
|
||||||
return xmlrpc.client.dumps(fault, allow_none=None, encoding=None)
|
return dumps(fault)
|
||||||
|
|
||||||
|
|
||||||
class OdooMarshaller(xmlrpc.client.Marshaller):
|
class OdooMarshaller(xmlrpc.client.Marshaller):
|
||||||
|
|
@ -78,9 +78,8 @@ class OdooMarshaller(xmlrpc.client.Marshaller):
|
||||||
# By default, in xmlrpc, bytes are converted to xmlrpc.client.Binary object.
|
# By default, in xmlrpc, bytes are converted to xmlrpc.client.Binary object.
|
||||||
# Historically, odoo is sending binary as base64 string.
|
# Historically, odoo is sending binary as base64 string.
|
||||||
# In python 3, base64.b64{de,en}code() methods now works on bytes.
|
# In python 3, base64.b64{de,en}code() methods now works on bytes.
|
||||||
# Convert them to str to have a consistent behavior between python 2 and python 3.
|
|
||||||
def dump_bytes(self, value, write):
|
def dump_bytes(self, value, write):
|
||||||
self.dump_unicode(ustr(value), write)
|
self.dump_unicode(value.decode(), write)
|
||||||
|
|
||||||
def dump_datetime(self, value, write):
|
def dump_datetime(self, value, write):
|
||||||
# override to marshall as a string for backwards compatibility
|
# override to marshall as a string for backwards compatibility
|
||||||
|
|
@ -111,21 +110,34 @@ class OdooMarshaller(xmlrpc.client.Marshaller):
|
||||||
dispatch[Markup] = lambda self, value, write: self.dispatch[str](self, str(value), write)
|
dispatch[Markup] = lambda self, value, write: self.dispatch[str](self, str(value), write)
|
||||||
|
|
||||||
|
|
||||||
# monkey-patch xmlrpc.client's marshaller
|
def dumps(params: list | tuple | xmlrpc.client.Fault) -> str:
|
||||||
xmlrpc.client.Marshaller = OdooMarshaller
|
response = OdooMarshaller(allow_none=False).dumps(params)
|
||||||
|
return f"""\
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<methodResponse>
|
||||||
|
{response}
|
||||||
|
</methodResponse>
|
||||||
|
"""
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# RPC Controller
|
# RPC Controller
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
|
|
||||||
|
def _check_request():
|
||||||
|
if request.db:
|
||||||
|
request.env.cr.close()
|
||||||
|
|
||||||
class RPC(Controller):
|
class RPC(Controller):
|
||||||
"""Handle RPC connections."""
|
"""Handle RPC connections."""
|
||||||
|
|
||||||
def _xmlrpc(self, service):
|
def _xmlrpc(self, service):
|
||||||
"""Common method to handle an XML-RPC request."""
|
"""Common method to handle an XML-RPC request."""
|
||||||
|
_check_request()
|
||||||
data = request.httprequest.get_data()
|
data = request.httprequest.get_data()
|
||||||
params, method = xmlrpc.client.loads(data)
|
params, method = xmlrpc.client.loads(data, use_datetime=True)
|
||||||
result = dispatch_rpc(service, method, params)
|
result = dispatch_rpc(service, method, params)
|
||||||
return xmlrpc.client.dumps((result,), methodresponse=1, allow_none=False)
|
return dumps((result,))
|
||||||
|
|
||||||
@route("/xmlrpc/<service>", auth="none", methods=["POST"], csrf=False, save_session=False)
|
@route("/xmlrpc/<service>", auth="none", methods=["POST"], csrf=False, save_session=False)
|
||||||
def xmlrpc_1(self, service):
|
def xmlrpc_1(self, service):
|
||||||
|
|
@ -134,6 +146,7 @@ class RPC(Controller):
|
||||||
This entrypoint is historical and non-compliant, but kept for
|
This entrypoint is historical and non-compliant, but kept for
|
||||||
backwards-compatibility.
|
backwards-compatibility.
|
||||||
"""
|
"""
|
||||||
|
_check_request()
|
||||||
try:
|
try:
|
||||||
response = self._xmlrpc(service)
|
response = self._xmlrpc(service)
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
|
|
@ -147,6 +160,7 @@ class RPC(Controller):
|
||||||
@route("/xmlrpc/2/<service>", auth="none", methods=["POST"], csrf=False, save_session=False)
|
@route("/xmlrpc/2/<service>", auth="none", methods=["POST"], csrf=False, save_session=False)
|
||||||
def xmlrpc_2(self, service):
|
def xmlrpc_2(self, service):
|
||||||
"""XML-RPC service that returns faultCode as int."""
|
"""XML-RPC service that returns faultCode as int."""
|
||||||
|
_check_request()
|
||||||
try:
|
try:
|
||||||
response = self._xmlrpc(service)
|
response = self._xmlrpc(service)
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
|
|
@ -160,4 +174,5 @@ class RPC(Controller):
|
||||||
@route('/jsonrpc', type='json', auth="none", save_session=False)
|
@route('/jsonrpc', type='json', auth="none", save_session=False)
|
||||||
def jsonrpc(self, service, method, args):
|
def jsonrpc(self, service, method, args):
|
||||||
""" Method used by client APIs to contact OpenERP. """
|
""" Method used by client APIs to contact OpenERP. """
|
||||||
|
_check_request()
|
||||||
return dispatch_rpc(service, method, args)
|
return dispatch_rpc(service, method, args)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
|
<record id="default_max_email_size" model="ir.config_parameter">
|
||||||
|
<field name="key">base.default_max_email_size</field>
|
||||||
|
<field name="value">10</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
<field name="code">model._run_vacuum_cleaner()</field>
|
<field name="code">model._run_vacuum_cleaner()</field>
|
||||||
<field name='interval_number'>1</field>
|
<field name='interval_number'>1</field>
|
||||||
<field name='interval_type'>days</field>
|
<field name='interval_type'>days</field>
|
||||||
<field name="numbercall">-1</field>
|
|
||||||
<field name="priority">3</field>
|
<field name="priority">3</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
@ -15,9 +14,9 @@
|
||||||
<field name="name">Base: Portal Users Deletion</field>
|
<field name="name">Base: Portal Users Deletion</field>
|
||||||
<field name="model_id" ref="base.model_res_users_deletion"/>
|
<field name="model_id" ref="base.model_res_users_deletion"/>
|
||||||
<field name="state">code</field>
|
<field name="state">code</field>
|
||||||
<field name="code">model._gc_portal_users()</field>
|
<field name="code">model._gc_portal_users(batch_size=50)</field>
|
||||||
<field name='interval_number'>1</field>
|
<field name='interval_number'>1</field>
|
||||||
<field name='interval_type'>days</field>
|
<field name='interval_type'>days</field>
|
||||||
<field name="numbercall">-1</field>
|
<field name="priority">8</field>
|
||||||
</record>
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,10 @@ action = {
|
||||||
module(s) failed to install and were disabled
|
module(s) failed to install and were disabled
|
||||||
</h3>
|
</h3>
|
||||||
<field name="failure_ids">
|
<field name="failure_ids">
|
||||||
<tree>
|
<list>
|
||||||
<field name="module_id"/>
|
<field name="module_id"/>
|
||||||
<field name="error"/>
|
<field name="error"/>
|
||||||
</tree>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
<footer>
|
<footer>
|
||||||
<button string="Ok" class="oe_highlight" type="object" name="done" data-hotkey="q"/>
|
<button string="Ok" class="oe_highlight" type="object" name="done" data-hotkey="q"/>
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@
|
||||||
|
|
||||||
<record model="ir.module.category" id="module_category_services_helpdesk">
|
<record model="ir.module.category" id="module_category_services_helpdesk">
|
||||||
<field name="name">Helpdesk</field>
|
<field name="name">Helpdesk</field>
|
||||||
<field name="description">After-sales services</field>
|
<field name="description" />
|
||||||
<field name="sequence">14</field>
|
<field name="sequence">14</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@
|
||||||
<field name="website">https://www.odoo.com/app/timesheet?utm_source=db&utm_medium=module</field>
|
<field name="website">https://www.odoo.com/app/timesheet?utm_source=db&utm_medium=module</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record model="ir.module.module" id="base.module_account_accountant">
|
<record model="ir.module.module" id="base.module_accountant">
|
||||||
<field name="name">account_accountant</field>
|
<field name="name">accountant</field>
|
||||||
<field name="sequence">30</field>
|
<field name="sequence">30</field>
|
||||||
<field name="shortdesc">Accounting</field>
|
<field name="shortdesc">Accounting</field>
|
||||||
<field name="category_id" ref="base.module_category_accounting_accounting"/>
|
<field name="category_id" ref="base.module_category_accounting_accounting"/>
|
||||||
|
|
@ -156,20 +156,6 @@
|
||||||
<field name="website">https://www.odoo.com/app/amazon-connector?utm_source=db&utm_medium=module</field>
|
<field name="website">https://www.odoo.com/app/amazon-connector?utm_source=db&utm_medium=module</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record model="ir.module.module" id="base.module_sale_ebay">
|
|
||||||
<field name="name">sale_ebay</field>
|
|
||||||
<field name="shortdesc">eBay Connector</field>
|
|
||||||
<field name="sequence">325</field>
|
|
||||||
<field name="category_id" ref="base.module_category_sales_sales"/>
|
|
||||||
<field name="application" eval="True"/>
|
|
||||||
<field name="summary">Sell on eBay easily</field>
|
|
||||||
<field name="license">OEEL-1</field>
|
|
||||||
<field name="author">Odoo S.A.</field>
|
|
||||||
<field name="to_buy" eval="True"/>
|
|
||||||
<field name="icon">/base/static/img/icons/sale_ebay.png</field>
|
|
||||||
<field name="website">https://www.odoo.com/app/sales?utm_source=db&utm_medium=module</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record model="ir.module.module" id="base.module_planning">
|
<record model="ir.module.module" id="base.module_planning">
|
||||||
<field name="name">planning</field>
|
<field name="name">planning</field>
|
||||||
<field name="shortdesc">Planning</field>
|
<field name="shortdesc">Planning</field>
|
||||||
|
|
@ -296,17 +282,6 @@
|
||||||
<field name="website">https://play.google.com/store/apps/details?id=com.odoo.mobile</field>
|
<field name="website">https://play.google.com/store/apps/details?id=com.odoo.mobile</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record model="ir.module.module" id="base.module_website_twitter_wall">
|
|
||||||
<field name="name">website_twitter_wall</field>
|
|
||||||
<field name="shortdesc">Twitter Wall</field>
|
|
||||||
<field name="summary">Interactive twitter wall for events</field>
|
|
||||||
<field name="category_id" ref="base.module_category_website_website"/>
|
|
||||||
<field name="license">OEEL-1</field>
|
|
||||||
<field name="author">Odoo S.A.</field>
|
|
||||||
<field name="to_buy" eval="True"/>
|
|
||||||
<field name="icon">/base/static/img/icons/website_twitter_wall.png</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record model="ir.module.module" id="base.module_payment_sepa_direct_debit">
|
<record model="ir.module.module" id="base.module_payment_sepa_direct_debit">
|
||||||
<field name="name">payment_sepa_direct_debit</field>
|
<field name="name">payment_sepa_direct_debit</field>
|
||||||
<field name="shortdesc">Sepa Direct Debit Payment Provider</field>
|
<field name="shortdesc">Sepa Direct Debit Payment Provider</field>
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,14 @@
|
||||||
<field name="page_height">0</field>
|
<field name="page_height">0</field>
|
||||||
<field name="page_width">0</field>
|
<field name="page_width">0</field>
|
||||||
<field name="orientation">Portrait</field>
|
<field name="orientation">Portrait</field>
|
||||||
<field name="margin_top">40</field>
|
<field name="margin_top">52</field>
|
||||||
<field name="margin_bottom">32</field>
|
<field name="margin_bottom">32</field>
|
||||||
<field name="margin_left">7</field>
|
<field name="margin_left">0</field>
|
||||||
<field name="margin_right">7</field>
|
<field name="margin_right">0</field>
|
||||||
<field name="header_line" eval="False" />
|
<field name="header_line" eval="False" />
|
||||||
<field name="header_spacing">35</field>
|
<field name="header_spacing">52</field>
|
||||||
<field name="dpi">90</field>
|
<field name="dpi">90</field>
|
||||||
|
<field name="css_margins" eval="True" />
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="paperformat_us" model="report.paperformat">
|
<record id="paperformat_us" model="report.paperformat">
|
||||||
|
|
@ -24,13 +25,14 @@
|
||||||
<field name="page_height">0</field>
|
<field name="page_height">0</field>
|
||||||
<field name="page_width">0</field>
|
<field name="page_width">0</field>
|
||||||
<field name="orientation">Portrait</field>
|
<field name="orientation">Portrait</field>
|
||||||
<field name="margin_top">40</field>
|
<field name="margin_top">52</field>
|
||||||
<field name="margin_bottom">30</field>
|
<field name="margin_bottom">32</field>
|
||||||
<field name="margin_left">7</field>
|
<field name="margin_left">0</field>
|
||||||
<field name="margin_right">7</field>
|
<field name="margin_right">0</field>
|
||||||
<field name="header_line" eval="False" />
|
<field name="header_line" eval="False" />
|
||||||
<field name="header_spacing">35</field>
|
<field name="header_spacing">52</field>
|
||||||
<field name="dpi">90</field>
|
<field name="dpi">90</field>
|
||||||
|
<field name="css_margins" eval="True" />
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="paperformat_batch_deposit" model="report.paperformat">
|
<record id="paperformat_batch_deposit" model="report.paperformat">
|
||||||
|
|
|
||||||
|
|
@ -915,7 +915,7 @@ state_et_5,et,"Dire Dawa","DR"
|
||||||
state_et_6,et,"Gambella Peoples","GM"
|
state_et_6,et,"Gambella Peoples","GM"
|
||||||
state_et_7,et,"Harrari Peoples","HR"
|
state_et_7,et,"Harrari Peoples","HR"
|
||||||
state_et_8,et,"Oromia","OR"
|
state_et_8,et,"Oromia","OR"
|
||||||
state_et_9,et,"Somalia","SM"
|
state_et_9,et,"Somali","SM"
|
||||||
state_et_10,et,"Southern Peoples, Nations, and Nationalities","SP"
|
state_et_10,et,"Southern Peoples, Nations, and Nationalities","SP"
|
||||||
state_et_11,et,"Tigray","TG"
|
state_et_11,et,"Tigray","TG"
|
||||||
state_ie_1,ie,"Carlow","CW"
|
state_ie_1,ie,"Carlow","CW"
|
||||||
|
|
@ -1748,3 +1748,34 @@ state_jo_ka,jo,"Karak",JO-KA
|
||||||
state_jo_ma,jo,"Mafraq",JO-MA
|
state_jo_ma,jo,"Mafraq",JO-MA
|
||||||
state_jo_md,jo,"Madaba",JO-MD
|
state_jo_md,jo,"Madaba",JO-MD
|
||||||
state_jo_mn,jo,"Maan",JO-MN
|
state_jo_mn,jo,"Maan",JO-MN
|
||||||
|
state_kr_11,kr,"서울특별시","KR-11"
|
||||||
|
state_kr_26,kr,"부산광역시","KR-26"
|
||||||
|
state_kr_27,kr,"대구광역시","KR-27"
|
||||||
|
state_kr_28,kr,"인천광역시","KR-28"
|
||||||
|
state_kr_29,kr,"광주광역시","KR-29"
|
||||||
|
state_kr_30,kr,"대전광역시","KR-30"
|
||||||
|
state_kr_31,kr,"울산광역시","KR-31"
|
||||||
|
state_kr_41,kr,"경기도","KR-41"
|
||||||
|
state_kr_42,kr,"강원도","KR-42"
|
||||||
|
state_kr_43,kr,"충청북도","KR-43"
|
||||||
|
state_kr_44,kr,"충청남도","KR-44"
|
||||||
|
state_kr_45,kr,"전라북도","KR-45"
|
||||||
|
state_kr_46,kr,"전라남도","KR-46"
|
||||||
|
state_kr_47,kr,"경상북도","KR-47"
|
||||||
|
state_kr_48,kr,"경상남도","KR-48"
|
||||||
|
state_kr_49,kr,"제주특별자치도","KR-49"
|
||||||
|
state_kr_50,kr,"세종특별자치시","KR-50"
|
||||||
|
state_be_1,be,"Antwerp","VAN"
|
||||||
|
state_be_2,be,"Limburg","VLI"
|
||||||
|
state_be_3,be,"East Flanders","VOV"
|
||||||
|
state_be_4,be,"Flemish Brabant","VBR"
|
||||||
|
state_be_5,be,"West Flanders","VWV"
|
||||||
|
state_be_6,be,"Walloon Brabant","WBR"
|
||||||
|
state_be_7,be,"Hainaut","WHT"
|
||||||
|
state_be_8,be,"Liège","WLG"
|
||||||
|
state_be_9,be,"Luxembourg","WLX"
|
||||||
|
state_be_10,be,"Namur","WNA"
|
||||||
|
state_bn_b,bn,"Brunei-Muara","B"
|
||||||
|
state_bn_k,bn,"Belait","K"
|
||||||
|
state_bn_t,bn,"Tutong","T"
|
||||||
|
state_bn_p,bn,"Temburong","P"
|
||||||
|
|
|
||||||
|
|
|
@ -1,92 +1,93 @@
|
||||||
"id","name","code","iso_code","direction","grouping","decimal_point","thousands_sep","date_format","time_format","week_start"
|
"id","name","code","iso_code","direction","grouping","decimal_point","thousands_sep","date_format","time_format","short_time_format","week_start"
|
||||||
"base.lang_en","English (US)","en_US","en","Left-to-Right","[3,0]",".",",","%m/%d/%Y","%H:%M:%S","7"
|
"base.lang_en","English (US)","en_US","en","Left-to-Right","[3,0]",".",",","%m/%d/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_am_ET","Amharic / አምሃርኛ","am_ET","am_ET","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%I:%M:%S","7"
|
"base.lang_am_ET","Amharic / አምሃርኛ","am_ET","am_ET","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%I:%M:%S","%I:%M","7"
|
||||||
"base.lang_ar","Arabic / الْعَرَبيّة","ar_001","ar","Right-to-Left","[3,0]",".",",","%d %b, %Y","%I:%M:%S %p","6"
|
"base.lang_ar","Arabic / الْعَرَبيّة","ar_001","ar","Right-to-Left","[3,0]",".",",","%d %b, %Y","%I:%M:%S %p","%I:%M","6"
|
||||||
"base.lang_ar_SY","Arabic (Syria) / الْعَرَبيّة","ar_SY","ar_SY","Right-to-Left","[3,0]",".",",","%d %b, %Y","%I:%M:%S %p","6"
|
"base.lang_ar_SY","Arabic (Syria) / الْعَرَبيّة","ar_SY","ar_SY","Right-to-Left","[3,0]",".",",","%d %b, %Y","%I:%M:%S %p","%I:%M","6"
|
||||||
"base.lang_az","Azerbaijani / Azərbaycanca","az_AZ","az","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","1"
|
"base.lang_az","Azerbaijani / Azərbaycanca","az_AZ","az","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_eu_ES","Basque / Euskara","eu_ES","eu_ES","Left-to-Right","[]",",",,"%a, %Y.eko %bren %da","%H:%M:%S","1"
|
"base.lang_eu_ES","Basque / Euskara","eu_ES","eu_ES","Left-to-Right","[]",",",,"%a, %Y.eko %bren %da","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_bn_IN","Bengali / বাংলা","bn_IN","bn_IN","Left-to-Right","[]",",",,"%A %d %b %Y","%I:%M:%S","1"
|
"base.lang_be","Belarusian / Беларуская мова","be_BY","be","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_bs_BA","Bosnian / bosanski jezik","bs_BA","bs","Left-to-Right","[3,0]",",",".","%d.%m.%Y","%H:%M:%S","1"
|
"base.lang_bn_IN","Bengali / বাংলা","bn_IN","bn_IN","Left-to-Right","[]",",",,"%A %d %b %Y","%I:%M:%S","%I:%M","1"
|
||||||
"base.lang_bg","Bulgarian / български език","bg_BG","bg","Left-to-Right","[3,0]",",",,"%d.%m.%Y","%H,%M,%S","1"
|
"base.lang_bs_BA","Bosnian / bosanski jezik","bs_BA","bs","Left-to-Right","[3,0]",",",".","%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_ca_ES","Catalan / Català","ca_ES","ca_ES","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_bg","Bulgarian / български език","bg_BG","bg","Left-to-Right","[3,0]",",",,"%d.%m.%Y","%H,%M,%S","%H,%M","1"
|
||||||
"base.lang_zh_CN","Chinese (Simplified) / 简体中文","zh_CN","zh_CN","Left-to-Right","[3,0]",".",",","%Y年%m月%d日","%H时%M分%S秒","7"
|
"base.lang_ca_ES","Catalan / Català","ca_ES","ca_ES","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_zh_HK","Chinese (HK)","zh_HK","zh_HK","Left-to-Right","[3,0]",".",",","%Y年%m月%d日 %A","%I時%M分%S秒","7"
|
"base.lang_zh_CN","Chinese (Simplified) / 简体中文","zh_CN","zh_CN","Left-to-Right","[3,0]",".",",","%Y年%m月%d日","%H时%M分%S秒","%H时%M分","7"
|
||||||
"base.lang_zh_TW","Chinese (Traditional) / 繁體中文","zh_TW","zh_TW","Left-to-Right","[3,0]",".",",","%Y年%m月%d日","%H時%M分%S秒","7"
|
"base.lang_zh_HK","Chinese (HK)","zh_HK","zh_HK","Left-to-Right","[3,0]",".",",","%Y年%m月%d日 %A","%I時%M分%S秒","%I時%M分","7"
|
||||||
"base.lang_hr","Croatian / hrvatski jezik","hr_HR","hr","Left-to-Right","[3,0]",",",".","%d.%m.%Y","%H:%M:%S","1"
|
"base.lang_zh_TW","Chinese (Traditional) / 繁體中文","zh_TW","zh_TW","Left-to-Right","[3,0]",".",",","%Y年%m月%d日","%H時%M分%S秒","%H時%M分","7"
|
||||||
"base.lang_cs_CZ","Czech / Čeština","cs_CZ","cs_CZ","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","1"
|
"base.lang_hr","Croatian / hrvatski jezik","hr_HR","hr","Left-to-Right","[3,0]",",",".","%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_da_DK","Danish / Dansk","da_DK","da_DK","Left-to-Right","[3,0]",",",".","%d-%m-%Y","%H:%M:%S","1"
|
"base.lang_cs_CZ","Czech / Čeština","cs_CZ","cs_CZ","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_nl_BE","Dutch (BE) / Nederlands (BE)","nl_BE","nl_BE","Left-to-Right","[3,0]",",",".","%d-%m-%Y","%H:%M:%S","1"
|
"base.lang_da_DK","Danish / Dansk","da_DK","da_DK","Left-to-Right","[3,0]",",",".","%d-%m-%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_nl","Dutch / Nederlands","nl_NL","nl","Left-to-Right","[3,0]",",",".","%d-%m-%Y","%H:%M:%S","1"
|
"base.lang_nl_BE","Dutch (BE) / Nederlands (BE)","nl_BE","nl_BE","Left-to-Right","[3,0]",",",".","%d-%m-%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_en_AU","English (AU)","en_AU","en_AU","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","7"
|
"base.lang_nl","Dutch / Nederlands","nl_NL","nl","Left-to-Right","[3,0]",",",".","%d-%m-%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_en_CA","English (CA)","en_CA","en_CA","Left-to-Right","[3,0]",".",",","%Y-%m-%d","%H:%M:%S","7"
|
"base.lang_en_AU","English (AU)","en_AU","en_AU","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_en_GB","English (UK)","en_GB","en_GB","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_en_CA","English (CA)","en_CA","en_CA","Left-to-Right","[3,0]",".",",","%Y-%m-%d","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_en_IN","English (IN)","en_IN","en_IN","Left-to-Right","[3,2,0]",".",",","%d/%m/%Y","%H:%M:%S","7"
|
"base.lang_en_GB","English (UK)","en_GB","en_GB","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_en_NZ","English (NZ)","en_NZ","en_NZ","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","7"
|
"base.lang_en_IN","English (IN)","en_IN","en_IN","Left-to-Right","[3,2,0]",".",",","%d/%m/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_et_EE","Estonian / Eesti keel","et_EE","et","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","1"
|
"base.lang_en_NZ","English (NZ)","en_NZ","en_NZ","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_fi","Finnish / Suomi","fi_FI","fi","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H.%M.%S","1"
|
"base.lang_et_EE","Estonian / Eesti keel","et_EE","et","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_fr_BE","French (BE) / Français (BE)","fr_BE","fr_BE","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_fi","Finnish / Suomi","fi_FI","fi","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H.%M.%S","%H.%M","1"
|
||||||
"base.lang_fr_CA","French (CA) / Français (CA)","fr_CA","fr_CA","Left-to-Right","[3,0]",","," ","%Y-%m-%d","%H:%M:%S","7"
|
"base.lang_fr_BE","French (BE) / Français (BE)","fr_BE","fr_BE","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_fr_CH","French (CH) / Français (CH)","fr_CH","fr_CH","Left-to-Right","[3,0]",".","'","%d.%m.%Y","%H:%M:%S","1"
|
"base.lang_fr_CA","French (CA) / Français (CA)","fr_CA","fr_CA","Left-to-Right","[3,0]",","," ","%Y-%m-%d","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_fr","French / Français","fr_FR","fr","Left-to-Right","[3,0]",","," ","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_fr_CH","French (CH) / Français (CH)","fr_CH","fr_CH","Left-to-Right","[3,0]",".","'","%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_gl_ES","Galician / Galego","gl_ES","gl","Left-to-Right","[]",",",,"%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_fr","French / Français","fr_FR","fr","Left-to-Right","[3,0]",","," ","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_ka_GE","Georgian / ქართული ენა","ka_GE","ka","Left-to-Right","[3,0]",",",".","%m/%d/%Y","%H:%M:%S","1"
|
"base.lang_gl_ES","Galician / Galego","gl_ES","gl","Left-to-Right","[]",",",,"%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_de","German / Deutsch","de_DE","de","Left-to-Right","[3,0]",",",".","%d.%m.%Y","%H:%M:%S","1"
|
"base.lang_ka_GE","Georgian / ქართული ენა","ka_GE","ka","Left-to-Right","[3,0]",",",".","%m/%d/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_de_CH","German (CH) / Deutsch (CH)","de_CH","de_CH","Left-to-Right","[3,0]",".","'","%d.%m.%Y","%H:%M:%S","1"
|
"base.lang_de","German / Deutsch","de_DE","de","Left-to-Right","[3,0]",",",".","%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_el_GR","Greek / Ελληνικά","el_GR","el_GR","Left-to-Right","[]",",",".","%d/%m/%Y","%I:%M:%S %p","1"
|
"base.lang_de_CH","German (CH) / Deutsch (CH)","de_CH","de_CH","Left-to-Right","[3,0]",".","'","%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_gu_IN","Gujarati / ગુજરાતી","gu_IN","gu","Left-to-Right","[]",".",",","%A %d %b %Y","%I:%M:%S","7"
|
"base.lang_el_GR","Greek / Ελληνικά","el_GR","el_GR","Left-to-Right","[]",",",".","%d/%m/%Y","%I:%M:%S %p","%I:%M %p","1"
|
||||||
"base.lang_he_IL","Hebrew / עברית","he_IL","he","Right-to-Left","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","7"
|
"base.lang_gu_IN","Gujarati / ગુજરાતી","gu_IN","gu","Left-to-Right","[]",".",",","%A %d %b %Y","%I:%M:%S","%I:%M","7"
|
||||||
"base.lang_hi_IN","Hindi / हिंदी","hi_IN","hi","Left-to-Right","[]",".",",","%A %d %b %Y","%I:%M:%S","7"
|
"base.lang_he_IL","Hebrew / עברית","he_IL","he","Right-to-Left","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_hu","Hungarian / Magyar","hu_HU","hu","Left-to-Right","[3,0]",",",".","%Y-%m-%d","%H:%M:%S","1"
|
"base.lang_hi_IN","Hindi / हिंदी","hi_IN","hi","Left-to-Right","[]",".",",","%A %d %b %Y","%I:%M:%S","%I:%M","7"
|
||||||
"base.lang_id","Indonesian / Bahasa Indonesia","id_ID","id","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","7"
|
"base.lang_hu","Hungarian / Magyar","hu_HU","hu","Left-to-Right","[3,0]",",",".","%Y-%m-%d","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_it","Italian / Italiano","it_IT","it","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_id","Indonesian / Bahasa Indonesia","id_ID","id","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_ja_JP","Japanese / 日本語","ja_JP","ja","Left-to-Right","[3,0]",".",",","%Y年%m月%d日","%H時%M分%S秒","7"
|
"base.lang_it","Italian / Italiano","it_IT","it","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_kab_DZ","Kabyle / Taqbaylit","kab_DZ","kab","Left-to-Right","[]",".",",","%m/%d/%Y","%I:%M:%S %p","6"
|
"base.lang_ja_JP","Japanese / 日本語","ja_JP","ja","Left-to-Right","[3,0]",".",",","%Y年%m月%d日","%H時%M分%S秒","%H時%M分","7"
|
||||||
"base.lang_km","Khmer / ភាសាខ្មែរ","km_KH","km","Left-to-Right","[3,0]",".",",","%d %B %Y","%H:%M:%S","7"
|
"base.lang_kab_DZ","Kabyle / Taqbaylit","kab_DZ","kab","Left-to-Right","[]",".",",","%m/%d/%Y","%I:%M:%S %p","%I:%M %p","6"
|
||||||
"base.lang_ko_KP","Korean (KP) / 한국어 (KP)","ko_KP","ko_KP","Left-to-Right","[3,0]",".",",","%m/%d/%Y","%I:%M:%S %p","1"
|
"base.lang_km","Khmer / ភាសាខ្មែរ","km_KH","km","Left-to-Right","[3,0]",".",",","%d %B %Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_ko_KR","Korean (KR) / 한국어 (KR)","ko_KR","ko_KR","Left-to-Right","[3,0]",".",",","%Y년 %m월 %d일","%H시 %M분 %S초","7"
|
"base.lang_ko_KP","Korean (KP) / 한국어 (KP)","ko_KP","ko_KP","Left-to-Right","[3,0]",".",",","%m/%d/%Y","%I:%M:%S %p","%I:%M %p","1"
|
||||||
"base.lang_lo_LA","Lao / ພາສາລາວ","lo_LA","lo","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","7"
|
"base.lang_ko_KR","Korean (KR) / 한국어 (KR)","ko_KR","ko_KR","Left-to-Right","[3,0]",".",",","%Y년 %m월 %d일","%H시 %M분 %S초","%H시 %M분","7"
|
||||||
"base.lang_lv","Latvian / latviešu valoda","lv_LV","lv","Left-to-Right","[3,0]",","," ","%Y.%m.%d.","%H:%M:%S","1"
|
"base.lang_lo_LA","Lao / ພາສາລາວ","lo_LA","lo","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_lt","Lithuanian / Lietuvių kalba","lt_LT","lt","Left-to-Right","[3,0]",",",".","%Y-%m-%d","%H:%M:%S","1"
|
"base.lang_lv","Latvian / latviešu valoda","lv_LV","lv","Left-to-Right","[3,0]",","," ","%Y.%m.%d.","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_lb","Luxembourgish","lb_LU","lb","Left-to-Right","[3,0]",","," ","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_lt","Lithuanian / Lietuvių kalba","lt_LT","lt","Left-to-Right","[3,0]",",",".","%Y-%m-%d","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_mk","Macedonian / македонски јазик","mk_MK","mk","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","1"
|
"base.lang_lb","Luxembourgish","lb_LU","lb","Left-to-Right","[3,0]",","," ","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_ml","Malayalam / മലയാളം","ml_IN","ml","Left-to-Right","[3,0]",","," ","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_mk","Macedonian / македонски јазик","mk_MK","mk","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_mn","Mongolian / монгол","mn_MN","mn","Left-to-Right","[3,0]",".","'","%Y-%m-%d","%H:%M:%S","7"
|
"base.lang_ml","Malayalam / മലയാളം","ml_IN","ml","Left-to-Right","[3,0]",","," ","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_ms","Malay / Bahasa Melayu","ms_MY","ms","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_mn","Mongolian / монгол","mn_MN","mn","Left-to-Right","[3,0]",".","'","%Y-%m-%d","%H:%M:%S","%H:%M","7"
|
||||||
base.lang_my,"Burmese / ဗမာစာ",my_MM,my,"Left-to-Right","[3,3]",".",",","%Y %b %d %A","%I:%M:%S %p","7"
|
"base.lang_ms","Malay / Bahasa Melayu","ms_MY","ms","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_nb_NO","Norwegian Bokmål / Norsk bokmål","nb_NO","nb_NO","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","1"
|
base.lang_my,"Burmese / ဗမာစာ",my_MM,my,"Left-to-Right","[3,3]",".",",","%Y %b %d %A","%I:%M:%S %p","%I:%M %p","7"
|
||||||
"base.lang_fa_IR","Persian / فارسی","fa_IR","fa","Right-to-Left","[3,0]",".",",","%Y/%m/%d","%H:%M:%S","6"
|
"base.lang_nb_NO","Norwegian Bokmål / Norsk bokmål","nb_NO","nb_NO","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_pl","Polish / Język polski","pl_PL","pl","Left-to-Right","[]",",",,"%d.%m.%Y","%H:%M:%S","1"
|
"base.lang_fa_IR","Persian / فارسی","fa_IR","fa","Right-to-Left","[3,0]",".",",","%Y/%m/%d","%H:%M:%S","%H:%M","6"
|
||||||
"base.lang_pt_AO","Portuguese (AO) / Português (AO)","pt_AO","pt_AO","Left-to-Right","[]",",",,"%d-%m-%Y","%H:%M:%S","1"
|
"base.lang_pl","Polish / Język polski","pl_PL","pl","Left-to-Right","[]",",",,"%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_pt_BR","Portuguese (BR) / Português (BR)","pt_BR","pt_BR","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","7"
|
"base.lang_pt_AO","Portuguese (AO) / Português (AO)","pt_AO","pt_AO","Left-to-Right","[]",",",,"%d-%m-%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_pt","Portuguese / Português","pt_PT","pt","Left-to-Right","[]",",",,"%d-%m-%Y","%H:%M:%S","1"
|
"base.lang_pt_BR","Portuguese (BR) / Português (BR)","pt_BR","pt_BR","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_ro","Romanian / română","ro_RO","ro","Left-to-Right","[3,0]",",",".","%d.%m.%Y","%H:%M:%S","1"
|
"base.lang_pt","Portuguese / Português","pt_PT","pt","Left-to-Right","[]",",",,"%d-%m-%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_ru","Russian / русский язык","ru_RU","ru","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","1"
|
"base.lang_ro","Romanian / română","ro_RO","ro","Left-to-Right","[3,0]",",",".","%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_sr_RS","Serbian (Cyrillic) / српски","sr_RS","sr_RS","Left-to-Right","[]",",",,"%d.%m.%Y.","%H:%M:%S","7"
|
"base.lang_ru","Russian / русский язык","ru_RU","ru","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_sr@latin","Serbian (Latin) / srpski","sr@latin","sr@latin","Left-to-Right","[]",".",",","%m/%d/%Y","%I:%M:%S %p","7"
|
"base.lang_sr@Cyrl","Serbian (Cyrillic) / српски","sr@Cyrl","sr@Cyrl","Left-to-Right","[]",",",,"%d.%m.%Y.","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_sk","Slovak / Slovenský jazyk","sk_SK","sk","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","1"
|
"base.lang_sr@latin","Serbian (Latin) / srpski","sr@latin","sr@latin","Left-to-Right","[]",".",",","%m/%d/%Y","%I:%M:%S %p","%I:%M %p","7"
|
||||||
"base.lang_sl_SI","Slovenian / slovenščina","sl_SI","sl","Left-to-Right","[]",","," ","%d. %m. %Y","%H:%M:%S","1"
|
"base.lang_sk","Slovak / Slovenský jazyk","sk_SK","sk","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_es_419","Spanish (Latin America) / Español (América Latina)","es_419","es_419","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_sl_SI","Slovenian / slovenščina","sl_SI","sl","Left-to-Right","[]",","," ","%d. %m. %Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_es_AR","Spanish (AR) / Español (AR)","es_AR","es_AR","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","7"
|
"base.lang_es_419","Spanish (Latin America) / Español (América Latina)","es_419","es_419","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_es_BO","Spanish (BO) / Español (BO)","es_BO","es_BO","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_es_AR","Spanish (AR) / Español (AR)","es_AR","es_AR","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_es_CL","Spanish (CL) / Español (CL)","es_CL","es_CL","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_es_BO","Spanish (BO) / Español (BO)","es_BO","es_BO","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_es_CO","Spanish (CO) / Español (CO)","es_CO","es_CO","Left-to-Right","[3,0]",",",".","%d-%m-%Y","%H:%M:%S","7"
|
"base.lang_es_CL","Spanish (CL) / Español (CL)","es_CL","es_CL","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_es_CR","Spanish (CR) / Español (CR)","es_CR","es_CR","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_es_CO","Spanish (CO) / Español (CO)","es_CO","es_CO","Left-to-Right","[3,0]",",",".","%d-%m-%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_es_DO","Spanish (DO) / Español (DO)","es_DO","es_DO","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%I:%M:%S %p","1"
|
"base.lang_es_CR","Spanish (CR) / Español (CR)","es_CR","es_CR","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_es_EC","Spanish (EC) / Español (EC)","es_EC","es_EC","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_es_DO","Spanish (DO) / Español (DO)","es_DO","es_DO","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%I:%M:%S %p","%I:%M %p","1"
|
||||||
"base.lang_es_GT","Spanish (GT) / Español (GT)","es_GT","es_GT","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","7"
|
"base.lang_es_EC","Spanish (EC) / Español (EC)","es_EC","es_EC","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_es_MX","Spanish (MX) / Español (MX)","es_MX","es_MX","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","7"
|
"base.lang_es_GT","Spanish (GT) / Español (GT)","es_GT","es_GT","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_es_PA","Spanish (PA) / Español (PA)","es_PA","es_PA","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","7"
|
"base.lang_es_MX","Spanish (MX) / Español (MX)","es_MX","es_MX","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_es_PE","Spanish (PE) / Español (PE)","es_PE","es_PE","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","7"
|
"base.lang_es_PA","Spanish (PA) / Español (PA)","es_PA","es_PA","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_es_PY","Spanish (PY) / Español (PY)","es_PY","es_PY","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","7"
|
"base.lang_es_PE","Spanish (PE) / Español (PE)","es_PE","es_PE","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_es_UY","Spanish (UY) / Español (UY)","es_UY","es_UY","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_es_PY","Spanish (PY) / Español (PY)","es_PY","es_PY","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_es_VE","Spanish (VE) / Español (VE)","es_VE","es_VE","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","7"
|
"base.lang_es_UY","Spanish (UY) / Español (UY)","es_UY","es_UY","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_es","Spanish / Español","es_ES","es","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_es_VE","Spanish (VE) / Español (VE)","es_VE","es_VE","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_sw","Swahili / Kiswahili","sw","sw","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_es","Spanish / Español","es_ES","es","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_sv_SE","Swedish / Svenska","sv_SE","sv","Left-to-Right","[3,0]",","," ","%Y-%m-%d","%H:%M:%S","1"
|
"base.lang_sw","Swahili / Kiswahili","sw","sw","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_th","Thai / ภาษาไทย","th_TH","th","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","7"
|
"base.lang_sv_SE","Swedish / Svenska","sv_SE","sv","Left-to-Right","[3,0]",","," ","%Y-%m-%d","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_tl","Tagalog / Filipino","tl_PH","tl","Left-to-Right","[3,0]",".",",","%m/%d/%y","%H:%M:%S","1"
|
"base.lang_th","Thai / ภาษาไทย","th_TH","th","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%H:%M:%S","%H:%M","7"
|
||||||
"base.lang_tr","Turkish / Türkçe","tr_TR","tr","Left-to-Right","[3,0]",",",".","%d-%m-%Y","%H:%M:%S","1"
|
"base.lang_tl","Tagalog / Filipino","tl_PH","tl","Left-to-Right","[3,0]",".",",","%m/%d/%y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_uk_UA","Ukrainian / українська","uk_UA","uk","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","1"
|
"base.lang_tr","Turkish / Türkçe","tr_TR","tr","Left-to-Right","[3,0]",",",".","%d-%m-%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_vi_VN","Vietnamese / Tiếng Việt","vi_VN","vi","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","1"
|
"base.lang_uk_UA","Ukrainian / українська","uk_UA","uk","Left-to-Right","[3,0]",","," ","%d.%m.%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_sq_AL","Albanian / Shqip","sq_AL","sq","Left-to-Right","[3,0]",",",".","%Y-%b-%d","%I.%M.%S.","1"
|
"base.lang_vi_VN","Vietnamese / Tiếng Việt","vi_VN","vi","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","%H:%M","1"
|
||||||
"base.lang_te_IN","Telugu / తెలుగు","te_IN","te","Left-to-Right","[]",".",",","%B %d %A %Y","%p%I.%M.%S","7"
|
"base.lang_sq_AL","Albanian / Shqip","sq_AL","sq","Left-to-Right","[3,0]",",",".","%Y-%b-%d","%I.%M.%S.","%I.%M.","1"
|
||||||
|
"base.lang_te_IN","Telugu / తెలుగు","te_IN","te","Left-to-Right","[]",".",",","%B %d %A %Y","%p%I.%M.%S","%p%I.%M","7"
|
||||||
|
|
|
||||||
|
|
|
@ -180,6 +180,7 @@
|
||||||
<record id="bn" model="res.country">
|
<record id="bn" model="res.country">
|
||||||
<field name="name">Brunei Darussalam</field>
|
<field name="name">Brunei Darussalam</field>
|
||||||
<field name="code">bn</field>
|
<field name="code">bn</field>
|
||||||
|
<field eval="'%(street)s\n%(street2)s\n%(city)s %(zip)s\n%(country_name)s'" name="address_format" />
|
||||||
<field name="currency_id" ref="BND" />
|
<field name="currency_id" ref="BND" />
|
||||||
<field eval="673" name="phone_code" />
|
<field eval="673" name="phone_code" />
|
||||||
</record>
|
</record>
|
||||||
|
|
@ -1104,6 +1105,7 @@
|
||||||
<field name="code">om</field>
|
<field name="code">om</field>
|
||||||
<field name="currency_id" ref="OMR" />
|
<field name="currency_id" ref="OMR" />
|
||||||
<field eval="968" name="phone_code" />
|
<field eval="968" name="phone_code" />
|
||||||
|
<field name="address_format" eval="'%(street)s\n%(street2)s\n%(city)s %(state_name)s %(zip)s\n%(country_name)s'" />
|
||||||
</record>
|
</record>
|
||||||
<record id="pa" model="res.country">
|
<record id="pa" model="res.country">
|
||||||
<field name="name">Panama</field>
|
<field name="name">Panama</field>
|
||||||
|
|
@ -1111,6 +1113,7 @@
|
||||||
<field name="currency_id" ref="PAB" />
|
<field name="currency_id" ref="PAB" />
|
||||||
<field eval="507" name="phone_code" />
|
<field eval="507" name="phone_code" />
|
||||||
<field name="address_format" eval="'%(street)s\n%(street2)s\n%(city)s %(state_name)s %(zip)s\n%(country_name)s'" />
|
<field name="address_format" eval="'%(street)s\n%(street2)s\n%(city)s %(state_name)s %(zip)s\n%(country_name)s'" />
|
||||||
|
<field name="vat_label">RUC</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="pe" model="res.country">
|
<record id="pe" model="res.country">
|
||||||
<field name="name">Peru</field>
|
<field name="name">Peru</field>
|
||||||
|
|
@ -1143,6 +1146,7 @@
|
||||||
<field name="code">pk</field>
|
<field name="code">pk</field>
|
||||||
<field name="currency_id" ref="PKR" />
|
<field name="currency_id" ref="PKR" />
|
||||||
<field eval="92" name="phone_code" />
|
<field eval="92" name="phone_code" />
|
||||||
|
<field name="vat_label">NTN</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="pl" model="res.country">
|
<record id="pl" model="res.country">
|
||||||
<field name="name">Poland</field>
|
<field name="name">Poland</field>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -726,12 +726,6 @@
|
||||||
<field name="rate">1105.24376765</field>
|
<field name="rate">1105.24376765</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record forcecreate="0" id="rateZWL" model="res.currency.rate">
|
|
||||||
<field name="currency_id" ref="ZWL" />
|
|
||||||
<field name="name">2010-01-01</field>
|
|
||||||
<field name="rate">395.80</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record forcecreate="0" id="rateZIG" model="res.currency.rate">
|
<record forcecreate="0" id="rateZIG" model="res.currency.rate">
|
||||||
<field name="currency_id" ref="ZIG" />
|
<field name="currency_id" ref="ZIG" />
|
||||||
<field name="name">2024-04-08</field>
|
<field name="name">2024-04-08</field>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<data noupdate="1">
|
<data noupdate="1">
|
||||||
|
<!-- es_419 is the new "generic" spanish -->
|
||||||
|
<record id="base.lang_es" model="res.lang">
|
||||||
|
<field name="url_code">es_ES</field>
|
||||||
|
</record>
|
||||||
|
<record id="base.lang_es_419" model="res.lang">
|
||||||
|
<field name="url_code">es</field>
|
||||||
|
</record>
|
||||||
<function name="install_lang" model="res.lang"/>
|
<function name="install_lang" model="res.lang"/>
|
||||||
</data>
|
</data>
|
||||||
<data>
|
<data>
|
||||||
|
<!-- /my is for the portal -->
|
||||||
<record id="base.lang_my" model="res.lang">
|
<record id="base.lang_my" model="res.lang">
|
||||||
<field name="url_code">mya</field>
|
<field name="url_code">mya</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,20 @@
|
||||||
<field name="vat">US12345675</field>
|
<field name="vat">US12345675</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="res_partner_5" model="res.partner">
|
||||||
|
<field name="city">Florenville</field>
|
||||||
|
<field name="country_id" ref="base.be"/>
|
||||||
|
<field name="email">wow@example.com</field>
|
||||||
|
<field name="image_1920" file="base/static/img/partner_open_wood.png" type="base64"/>
|
||||||
|
<field name="is_company" eval="True"/>
|
||||||
|
<field name="mobile">+32 987 65 43 21</field>
|
||||||
|
<field name="name">OpenWood</field>
|
||||||
|
<field name="phone">+32 987 65 43 21</field>
|
||||||
|
<field name="street">Orval 1</field>
|
||||||
|
<field name="website">www.openwood.example.com</field>
|
||||||
|
<field name="zip">6823</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<record id="res_partner_10" model="res.partner">
|
<record id="res_partner_10" model="res.partner">
|
||||||
<field name="name">The Jackson Group</field>
|
<field name="name">The Jackson Group</field>
|
||||||
<field name="is_company">1</field>
|
<field name="is_company">1</field>
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@ System</span>]]></field>
|
||||||
Administrator</span>]]></field>
|
Administrator</span>]]></field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="user_admin_settings" model="res.users.settings" forcecreate="0">
|
||||||
|
<field name="user_id" ref="base.user_admin"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
<!-- Default user with full access rights for newly created users -->
|
<!-- Default user with full access rights for newly created users -->
|
||||||
<record id="default_user" model="res.users">
|
<record id="default_user" model="res.users">
|
||||||
<field name="name">Default User Template</field>
|
<field name="name">Default User Template</field>
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,10 @@
|
||||||
<field name="image_1920" type="base64" file="base/static/img/user_demo-image.png"/>
|
<field name="image_1920" type="base64" file="base/static/img/user_demo-image.png"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="partner_demo" model="res.partner">
|
||||||
|
<field name="user_id" ref="base.user_demo"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
<record model="res.partner" id="base.partner_root">
|
<record model="res.partner" id="base.partner_root">
|
||||||
<field name="tz">Europe/Brussels</field>
|
<field name="tz">Europe/Brussels</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
@ -57,6 +61,7 @@
|
||||||
<field name="phone">+1 555-555-5555</field>
|
<field name="phone">+1 555-555-5555</field>
|
||||||
<field name="email">admin@yourcompany.example.com</field>
|
<field name="email">admin@yourcompany.example.com</field>
|
||||||
<field name="tz">Europe/Brussels</field>
|
<field name="tz">Europe/Brussels</field>
|
||||||
|
<field name="user_id" ref="base.user_admin"/>
|
||||||
<field name="image_1920" type="base64" file="base/static/img/partner_root-image.png"/>
|
<field name="image_1920" type="base64" file="base/static/img/partner_root-image.png"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -8,6 +8,7 @@ from . import ir_ui_menu
|
||||||
from . import ir_ui_view
|
from . import ir_ui_view
|
||||||
from . import ir_asset
|
from . import ir_asset
|
||||||
from . import ir_actions
|
from . import ir_actions
|
||||||
|
from . import ir_embedded_actions
|
||||||
from . import ir_actions_report
|
from . import ir_actions_report
|
||||||
from . import ir_attachment
|
from . import ir_attachment
|
||||||
from . import ir_binary
|
from . import ir_binary
|
||||||
|
|
@ -24,7 +25,6 @@ from . import ir_qweb
|
||||||
from . import ir_qweb_fields
|
from . import ir_qweb_fields
|
||||||
from . import ir_http
|
from . import ir_http
|
||||||
from . import ir_logging
|
from . import ir_logging
|
||||||
from . import ir_property
|
|
||||||
from . import ir_module
|
from . import ir_module
|
||||||
from . import ir_demo
|
from . import ir_demo
|
||||||
from . import ir_demo_failure
|
from . import ir_demo_failure
|
||||||
|
|
@ -45,5 +45,6 @@ from . import res_company
|
||||||
from . import res_users
|
from . import res_users
|
||||||
from . import res_users_settings
|
from . import res_users_settings
|
||||||
from . import res_users_deletion
|
from . import res_users_deletion
|
||||||
|
from . import res_device
|
||||||
|
|
||||||
from . import decimal_precision
|
from . import decimal_precision
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,16 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from datetime import datetime
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
import base64
|
|
||||||
import copy
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import io
|
import io
|
||||||
import itertools
|
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import textwrap
|
import textwrap
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import psycopg2
|
|
||||||
try:
|
try:
|
||||||
import sass as libsass
|
import sass as libsass
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
@ -29,11 +23,10 @@ from rjsmin import jsmin as rjsmin
|
||||||
from odoo import release, SUPERUSER_ID, _
|
from odoo import release, SUPERUSER_ID, _
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
from odoo.tools import (func, misc, transpile_javascript,
|
from odoo.tools import (func, misc, transpile_javascript,
|
||||||
is_odoo_module, SourceMapGenerator, profiler,
|
is_odoo_module, SourceMapGenerator, profiler, OrderedSet)
|
||||||
apply_inheritance_specs)
|
from odoo.tools.json import scriptsafe as json
|
||||||
from odoo.tools.constants import SCRIPT_EXTENSIONS, STYLE_EXTENSIONS
|
from odoo.tools.constants import SCRIPT_EXTENSIONS, STYLE_EXTENSIONS
|
||||||
from odoo.tools.misc import file_open, file_path
|
from odoo.tools.misc import file_open, file_path
|
||||||
from odoo.tools.pycompat import to_text
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -49,6 +42,8 @@ class AssetError(Exception):
|
||||||
class AssetNotFound(AssetError):
|
class AssetNotFound(AssetError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class XMLAssetError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class AssetsBundle(object):
|
class AssetsBundle(object):
|
||||||
rx_css_import = re.compile("(@import[^;{]+;?)", re.M)
|
rx_css_import = re.compile("(@import[^;{]+;?)", re.M)
|
||||||
|
|
@ -326,22 +321,20 @@ class AssetsBundle(object):
|
||||||
if not js_attachment:
|
if not js_attachment:
|
||||||
template_bundle = ''
|
template_bundle = ''
|
||||||
if self.templates:
|
if self.templates:
|
||||||
content = ['<?xml version="1.0" encoding="UTF-8"?>']
|
templates = self.generate_xml_bundle()
|
||||||
content.append('<templates xml:space="preserve">')
|
|
||||||
content.append(self.xml(show_inherit_info=not is_minified))
|
|
||||||
content.append('</templates>')
|
|
||||||
templates = '\n'.join(content).replace("\\", "\\\\").replace("`", "\\`").replace("${", "\\${")
|
|
||||||
template_bundle = textwrap.dedent(f"""
|
template_bundle = textwrap.dedent(f"""
|
||||||
|
|
||||||
/*******************************************
|
/*******************************************
|
||||||
* Templates *
|
* Templates *
|
||||||
*******************************************/
|
*******************************************/
|
||||||
|
|
||||||
odoo.define('{self.name}.bundle.xml', ['@web/core/registry'], function(require){{
|
odoo.define("{self.name}.bundle.xml", ["@web/core/templates"], function(require) {{
|
||||||
'use strict';
|
"use strict";
|
||||||
const {{ registry }} = require('@web/core/registry');
|
const {{ checkPrimaryTemplateParents, registerTemplate, registerTemplateExtension }} = require("@web/core/templates");
|
||||||
registry.category(`xml_templates`).add(`{self.name}`, `{templates}`);
|
/* {self.name} */
|
||||||
}});""")
|
{templates}
|
||||||
|
}});
|
||||||
|
""")
|
||||||
|
|
||||||
if is_minified:
|
if is_minified:
|
||||||
content_bundle = ';\n'.join(asset.minify() for asset in self.javascripts)
|
content_bundle = ';\n'.join(asset.minify() for asset in self.javascripts)
|
||||||
|
|
@ -394,29 +387,64 @@ class AssetsBundle(object):
|
||||||
|
|
||||||
return js_attachment
|
return js_attachment
|
||||||
|
|
||||||
def xml(self, show_inherit_info=False):
|
def generate_xml_bundle(self):
|
||||||
|
content = []
|
||||||
|
blocks = []
|
||||||
|
try:
|
||||||
|
blocks = self.xml()
|
||||||
|
except XMLAssetError as e:
|
||||||
|
content.append(f'throw new Error({json.dumps(str(e))});')
|
||||||
|
|
||||||
|
def get_template(element):
|
||||||
|
element.set("{http://www.w3.org/XML/1998/namespace}space", "preserve")
|
||||||
|
string = etree.tostring(element, encoding='unicode')
|
||||||
|
return string.replace("\\", "\\\\").replace("`", "\\`").replace("${", "\\${")
|
||||||
|
|
||||||
|
names = OrderedSet()
|
||||||
|
primary_parents = OrderedSet()
|
||||||
|
extension_parents = OrderedSet()
|
||||||
|
for block in blocks:
|
||||||
|
if block["type"] == "templates":
|
||||||
|
for (element, url, inherit_from) in block["templates"]:
|
||||||
|
if inherit_from:
|
||||||
|
primary_parents.add(inherit_from)
|
||||||
|
name = element.get("t-name")
|
||||||
|
names.add(name)
|
||||||
|
template = get_template(element)
|
||||||
|
content.append(f'registerTemplate("{name}", `{url}`, `{template}`);')
|
||||||
|
else:
|
||||||
|
for inherit_from, elements in block["extensions"].items():
|
||||||
|
extension_parents.add(inherit_from)
|
||||||
|
for (element, url) in elements:
|
||||||
|
template = get_template(element)
|
||||||
|
content.append(f'registerTemplateExtension("{inherit_from}", `{url}`, `{template}`);')
|
||||||
|
|
||||||
|
missing_names_for_primary = primary_parents - names
|
||||||
|
if missing_names_for_primary:
|
||||||
|
content.append(f'checkPrimaryTemplateParents({json.dumps(list(missing_names_for_primary))});')
|
||||||
|
missing_names_for_extension = extension_parents - names
|
||||||
|
if missing_names_for_extension:
|
||||||
|
content.append(f'console.error("Missing (extension) parent templates: {", ".join(missing_names_for_extension)}");')
|
||||||
|
|
||||||
|
return '\n'.join(content)
|
||||||
|
|
||||||
|
def xml(self):
|
||||||
"""
|
"""
|
||||||
Create the ir.attachment representing the content of the bundle XML.
|
Create a list of blocks. A block can have one of the two types "templates" or "extensions".
|
||||||
The xml contents are loaded and parsed with etree. Inheritances are
|
A template with no parent or template with t-inherit-mode="primary" goes in a block of type "templates".
|
||||||
applied in the order of files and templates.
|
A template with t-inherit-mode="extension" goes in a block of type "extensions".
|
||||||
|
|
||||||
Used parsed attributes:
|
Used parsed attributes:
|
||||||
* `t-name`: template name
|
* `t-name`: template name
|
||||||
* `t-inherit`: inherited template name. The template use the
|
* `t-inherit`: inherited template name.
|
||||||
`apply_inheritance_specs` method from `ir.ui.view` to apply
|
* 't-inherit-mode': 'primary' or 'extension'.
|
||||||
inheritance (with xpath and position).
|
|
||||||
* 't-inherit-mode': 'primary' to create a new template with the
|
|
||||||
update, or 'extension' to apply the update on the inherited
|
|
||||||
template.
|
|
||||||
* `t-extend` deprecated attribute, used by the JavaScript Qweb.
|
|
||||||
|
|
||||||
:param show_inherit_info: if true add the file url and inherit
|
:return a list of blocks
|
||||||
information in the template.
|
|
||||||
:return ir.attachment representing the content of the bundle XML
|
|
||||||
"""
|
"""
|
||||||
template_dict = OrderedDict()
|
|
||||||
parser = etree.XMLParser(ns_clean=True, recover=True, remove_comments=True)
|
parser = etree.XMLParser(ns_clean=True, recover=True, remove_comments=True)
|
||||||
|
|
||||||
|
blocks = []
|
||||||
|
block = None
|
||||||
for asset in self.templates:
|
for asset in self.templates:
|
||||||
# Load content.
|
# Load content.
|
||||||
try:
|
try:
|
||||||
|
|
@ -425,106 +453,36 @@ class AssetsBundle(object):
|
||||||
io_content = io.BytesIO(template.encode('utf-8'))
|
io_content = io.BytesIO(template.encode('utf-8'))
|
||||||
content_templates_tree = etree.parse(io_content, parser=parser).getroot()
|
content_templates_tree = etree.parse(io_content, parser=parser).getroot()
|
||||||
except etree.ParseError as e:
|
except etree.ParseError as e:
|
||||||
_logger.error("Could not parse file %s: %s", asset.url, e.msg)
|
return asset.generate_error(f'Could not parse file: {e.msg}')
|
||||||
raise
|
|
||||||
addon = asset.url.split('/')[1]
|
|
||||||
template_dict.setdefault(addon, OrderedDict())
|
|
||||||
# Process every templates.
|
# Process every templates.
|
||||||
for template_tree in list(content_templates_tree):
|
for template_tree in list(content_templates_tree):
|
||||||
template_name = None
|
template_name = template_tree.get("t-name")
|
||||||
if 't-name' in template_tree.attrib:
|
inherit_from = template_tree.get("t-inherit")
|
||||||
template_name = template_tree.attrib['t-name']
|
inherit_mode = None
|
||||||
dotted_names = template_name.split('.', 1)
|
if inherit_from:
|
||||||
if len(dotted_names) > 1 and dotted_names[0] == addon:
|
inherit_mode = template_tree.get('t-inherit-mode', 'primary')
|
||||||
template_name = dotted_names[1]
|
|
||||||
|
|
||||||
if 't-inherit' in template_tree.attrib:
|
|
||||||
inherit_mode = template_tree.attrib.get('t-inherit-mode', 'primary')
|
|
||||||
if inherit_mode not in ['primary', 'extension']:
|
if inherit_mode not in ['primary', 'extension']:
|
||||||
raise ValueError(_("Invalid inherit mode. Module %r and template name %r", addon, template_name))
|
addon = asset.url.split('/')[1]
|
||||||
|
return asset.generate_error(_(
|
||||||
# Get inherited template, the identifier can be "addon.name", just "name" or (silly) "just.name.with.dots"
|
'Invalid inherit mode. Module "%(module)s" and template name "%(template_name)s"',
|
||||||
parent_dotted_name = template_tree.attrib['t-inherit']
|
module=addon,
|
||||||
split_name_attempt = parent_dotted_name.split('.', 1)
|
template_name=template_name,
|
||||||
parent_addon, parent_name = split_name_attempt if len(split_name_attempt) == 2 else (addon, parent_dotted_name)
|
))
|
||||||
if parent_addon not in template_dict:
|
if inherit_mode == "extension":
|
||||||
if parent_dotted_name in template_dict[addon]:
|
if block is None or block["type"] != "extensions":
|
||||||
parent_addon = addon
|
block = {"type": "extensions", "extensions": OrderedDict()}
|
||||||
parent_name = parent_dotted_name
|
blocks.append(block)
|
||||||
else:
|
block["extensions"].setdefault(inherit_from, [])
|
||||||
raise ValueError(_("Module %r not loaded or inexistent (try to inherit %r), or templates of addon being loaded %r are misordered (template %r)", parent_addon, parent_name, addon, template_name))
|
block["extensions"][inherit_from].append((template_tree, asset.url))
|
||||||
if parent_name not in template_dict[parent_addon]:
|
|
||||||
raise ValueError(_("Cannot create %r because the template to inherit %r is not found.", '%s.%s' % (addon, template_name), '%s.%s' % (parent_addon, parent_name)))
|
|
||||||
|
|
||||||
# After several performance tests, we found out that deepcopy is the most efficient
|
|
||||||
# solution in this case (compared with copy, xpath with '.' and stringifying).
|
|
||||||
parent_tree, parent_urls = template_dict[parent_addon][parent_name]
|
|
||||||
parent_tree = copy.deepcopy(parent_tree)
|
|
||||||
|
|
||||||
if show_inherit_info:
|
|
||||||
# Add inheritance information as xml comment for debugging.
|
|
||||||
xpaths = []
|
|
||||||
for item in template_tree:
|
|
||||||
position = item.get('position')
|
|
||||||
attrib = dict(**item.attrib)
|
|
||||||
attrib.pop('position', None)
|
|
||||||
comment = etree.Comment(f""" Filepath: {asset.url} ; position="{position}" ; {attrib} """)
|
|
||||||
if position == "attributes":
|
|
||||||
if item.get('expr'):
|
|
||||||
comment_node = etree.Element('xpath', {'expr': item.get('expr'), 'position': 'before'})
|
|
||||||
else:
|
|
||||||
comment_node = etree.Element(item.tag, item.attrib)
|
|
||||||
comment_node.attrib['position'] = 'before'
|
|
||||||
comment_node.append(comment)
|
|
||||||
xpaths.append(comment_node)
|
|
||||||
else:
|
|
||||||
if len(item) > 0:
|
|
||||||
item[0].addprevious(comment)
|
|
||||||
else:
|
|
||||||
item.append(comment)
|
|
||||||
xpaths.append(item)
|
|
||||||
else:
|
|
||||||
xpaths = list(template_tree)
|
|
||||||
|
|
||||||
# Apply inheritance.
|
|
||||||
if inherit_mode == 'primary':
|
|
||||||
parent_tree.tag = template_tree.tag
|
|
||||||
inherited_template = apply_inheritance_specs(parent_tree, xpaths)
|
|
||||||
if inherit_mode == 'primary': # New template_tree: A' = B(A)
|
|
||||||
for attr_name, attr_val in template_tree.attrib.items():
|
|
||||||
if attr_name not in ('t-inherit', 't-inherit-mode'):
|
|
||||||
inherited_template.set(attr_name, attr_val)
|
|
||||||
if not template_name:
|
|
||||||
raise ValueError(_("Template name is missing in file %r.", asset.url))
|
|
||||||
template_dict[addon][template_name] = (inherited_template, parent_urls + [asset.url])
|
|
||||||
else: # Modifies original: A = B(A)
|
|
||||||
template_dict[parent_addon][parent_name] = (inherited_template, parent_urls + [asset.url])
|
|
||||||
elif template_name:
|
elif template_name:
|
||||||
if template_name in template_dict[addon]:
|
if block is None or block["type"] != "templates":
|
||||||
raise ValueError(_("Template %r already exists in module %r", template_name, addon))
|
block = {"type": "templates", "templates": []}
|
||||||
template_dict[addon][template_name] = (template_tree, [asset.url])
|
blocks.append(block)
|
||||||
elif template_tree.attrib.get('t-extend'):
|
block["templates"].append((template_tree, asset.url, inherit_from))
|
||||||
template_name = '%s__extend_%s' % (template_tree.attrib.get('t-extend'), len(template_dict[addon]))
|
|
||||||
template_dict[addon][template_name] = (template_tree, [asset.url])
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(_("Template name is missing in file %r.", asset.url))
|
return asset.generate_error(_("Template name is missing."))
|
||||||
|
return blocks
|
||||||
|
|
||||||
# Concat and render inherited templates
|
|
||||||
root = etree.Element('root')
|
|
||||||
for addon in template_dict.values():
|
|
||||||
for template, urls in addon.values():
|
|
||||||
if show_inherit_info:
|
|
||||||
tail = "\n"
|
|
||||||
if len(root) > 0:
|
|
||||||
tail = root[-1].tail
|
|
||||||
root[-1].tail = "\n\n"
|
|
||||||
comment = etree.Comment(f""" Filepath: {' => '.join(urls)} """)
|
|
||||||
comment.tail = tail
|
|
||||||
root.append(comment)
|
|
||||||
root.append(template)
|
|
||||||
|
|
||||||
# Returns the string by removing the <root> tag.
|
|
||||||
return etree.tostring(root, encoding='unicode')[6:-7]
|
|
||||||
|
|
||||||
def css(self):
|
def css(self):
|
||||||
is_minified = not self.is_debug_assets
|
is_minified = not self.is_debug_assets
|
||||||
|
|
@ -652,7 +610,7 @@ css_error_message {
|
||||||
"""Sanitizes @import rules, remove duplicates @import rules, then compile"""
|
"""Sanitizes @import rules, remove duplicates @import rules, then compile"""
|
||||||
imports = []
|
imports = []
|
||||||
def handle_compile_error(e, source):
|
def handle_compile_error(e, source):
|
||||||
error = self.get_preprocessor_error(e, source=source)
|
error = self.get_preprocessor_error(str(e), source=source)
|
||||||
_logger.warning(error)
|
_logger.warning(error)
|
||||||
self.css_errors.append(error)
|
self.css_errors.append(error)
|
||||||
return ''
|
return ''
|
||||||
|
|
@ -668,7 +626,6 @@ css_error_message {
|
||||||
return ''
|
return ''
|
||||||
source = re.sub(self.rx_preprocess_imports, sanitize, source)
|
source = re.sub(self.rx_preprocess_imports, sanitize, source)
|
||||||
|
|
||||||
compiled = ''
|
|
||||||
try:
|
try:
|
||||||
compiled = compiler(source)
|
compiled = compiler(source)
|
||||||
except CompileError as e:
|
except CompileError as e:
|
||||||
|
|
@ -700,7 +657,7 @@ css_error_message {
|
||||||
cmd = [rtlcss, '-c', file_path("base/data/rtlcss.json"), '-']
|
cmd = [rtlcss, '-c', file_path("base/data/rtlcss.json"), '-']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rtlcss = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
rtlcss = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, encoding='utf-8')
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
||||||
# Check the presence of rtlcss, if rtlcss not available then we should return normal less file
|
# Check the presence of rtlcss, if rtlcss not available then we should return normal less file
|
||||||
|
|
@ -717,23 +674,20 @@ css_error_message {
|
||||||
self.css_errors.append(msg)
|
self.css_errors.append(msg)
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
stdout, stderr = rtlcss.communicate(input=source.encode('utf-8'))
|
out, err = rtlcss.communicate(input=source)
|
||||||
if rtlcss.returncode or (source and not stdout):
|
if rtlcss.returncode or (source and not out):
|
||||||
cmd_output = ''.join(misc.ustr(stderr))
|
if rtlcss.returncode:
|
||||||
if not cmd_output and rtlcss.returncode:
|
error = self.get_rtlcss_error(err or f"Process exited with return code {rtlcss.returncode}", source=source)
|
||||||
cmd_output = "Process exited with return code %d\n" % rtlcss.returncode
|
else:
|
||||||
elif not cmd_output:
|
error = "rtlcss: error processing payload\n"
|
||||||
cmd_output = "rtlcss: error processing payload\n"
|
_logger.warning("%s", error)
|
||||||
error = self.get_rtlcss_error(cmd_output, source=source)
|
|
||||||
_logger.warning(error)
|
|
||||||
self.css_errors.append(error)
|
self.css_errors.append(error)
|
||||||
return ''
|
return ''
|
||||||
rtlcss_result = stdout.strip().decode('utf8')
|
return out.strip()
|
||||||
return rtlcss_result
|
|
||||||
|
|
||||||
def get_preprocessor_error(self, stderr, source=None):
|
def get_preprocessor_error(self, stderr, source=None):
|
||||||
"""Improve and remove sensitive information from sass/less compilator error messages"""
|
"""Improve and remove sensitive information from sass/less compilator error messages"""
|
||||||
error = misc.ustr(stderr).split('Load paths')[0].replace(' Use --trace for backtrace.', '')
|
error = stderr.split('Load paths')[0].replace(' Use --trace for backtrace.', '')
|
||||||
if 'Cannot load compass' in error:
|
if 'Cannot load compass' in error:
|
||||||
error += "Maybe you should install the compass gem using this extra argument:\n\n" \
|
error += "Maybe you should install the compass gem using this extra argument:\n\n" \
|
||||||
" $ sudo gem install compass --pre\n"
|
" $ sudo gem install compass --pre\n"
|
||||||
|
|
@ -745,8 +699,8 @@ css_error_message {
|
||||||
|
|
||||||
def get_rtlcss_error(self, stderr, source=None):
|
def get_rtlcss_error(self, stderr, source=None):
|
||||||
"""Improve and remove sensitive information from sass/less compilator error messages"""
|
"""Improve and remove sensitive information from sass/less compilator error messages"""
|
||||||
error = misc.ustr(stderr).split('Load paths')[0].replace(' Use --trace for backtrace.', '')
|
error = stderr.split('Load paths')[0].replace(' Use --trace for backtrace.', '')
|
||||||
error += "This error occurred while compiling the bundle '%s' containing:" % self.name
|
error = f"{error}This error occurred while compiling the bundle {self.name!r} containing:"
|
||||||
return error
|
return error
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -765,6 +719,11 @@ class WebAsset(object):
|
||||||
if not inline and not url:
|
if not inline and not url:
|
||||||
raise Exception("An asset should either be inlined or url linked, defined in bundle '%s'" % bundle.name)
|
raise Exception("An asset should either be inlined or url linked, defined in bundle '%s'" % bundle.name)
|
||||||
|
|
||||||
|
def generate_error(self, msg):
|
||||||
|
msg = f'{msg!r} in file {self.url!r}'
|
||||||
|
_logger.error(msg) # log it in the python console in all cases.
|
||||||
|
return msg
|
||||||
|
|
||||||
@func.lazy_property
|
@func.lazy_property
|
||||||
def id(self):
|
def id(self):
|
||||||
if self._id is None: self._id = str(uuid.uuid4())
|
if self._id is None: self._id = str(uuid.uuid4())
|
||||||
|
|
@ -840,6 +799,10 @@ class JavascriptAsset(WebAsset):
|
||||||
self._is_transpiled = None
|
self._is_transpiled = None
|
||||||
self._converted_content = None
|
self._converted_content = None
|
||||||
|
|
||||||
|
def generate_error(self, msg):
|
||||||
|
msg = super().generate_error(msg)
|
||||||
|
return f'console.error({json.dumps(msg)});'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bundle_version(self):
|
def bundle_version(self):
|
||||||
return self.bundle.get_version('js')
|
return self.bundle.get_version('js')
|
||||||
|
|
@ -847,7 +810,7 @@ class JavascriptAsset(WebAsset):
|
||||||
@property
|
@property
|
||||||
def is_transpiled(self):
|
def is_transpiled(self):
|
||||||
if self._is_transpiled is None:
|
if self._is_transpiled is None:
|
||||||
self._is_transpiled = bool(is_odoo_module(super().content))
|
self._is_transpiled = bool(is_odoo_module(self.url, super().content))
|
||||||
return self._is_transpiled
|
return self._is_transpiled
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -866,7 +829,7 @@ class JavascriptAsset(WebAsset):
|
||||||
try:
|
try:
|
||||||
return super()._fetch_content()
|
return super()._fetch_content()
|
||||||
except AssetError as e:
|
except AssetError as e:
|
||||||
return u"console.error(%s);" % json.dumps(to_text(e))
|
return self.generate_error(str(e))
|
||||||
|
|
||||||
|
|
||||||
def with_header(self, content=None, minimal=True):
|
def with_header(self, content=None, minimal=True):
|
||||||
|
|
@ -898,17 +861,21 @@ class XMLAsset(WebAsset):
|
||||||
try:
|
try:
|
||||||
content = super()._fetch_content()
|
content = super()._fetch_content()
|
||||||
except AssetError as e:
|
except AssetError as e:
|
||||||
return u"console.error(%s);" % json.dumps(to_text(e))
|
return self.generate_error(str(e))
|
||||||
|
|
||||||
parser = etree.XMLParser(ns_clean=True, remove_comments=True, resolve_entities=False)
|
parser = etree.XMLParser(ns_clean=True, remove_comments=True, resolve_entities=False)
|
||||||
try:
|
try:
|
||||||
root = etree.fromstring(content.encode('utf-8'), parser=parser)
|
root = etree.fromstring(content.encode('utf-8'), parser=parser)
|
||||||
except etree.XMLSyntaxError as e:
|
except etree.XMLSyntaxError as e:
|
||||||
return f'<t t-name="parsing_error{self.url.replace("/","_")}"><parsererror>Invalid XML template: {self.url} \n {e.msg} </parsererror></t>'
|
return self.generate_error(f'Invalid XML template: {e.msg}')
|
||||||
if root.tag in ('templates', 'template'):
|
if root.tag in ('templates', 'template'):
|
||||||
return ''.join(etree.tostring(el, encoding='unicode') for el in root)
|
return ''.join(etree.tostring(el, encoding='unicode') for el in root)
|
||||||
return etree.tostring(root, encoding='unicode')
|
return etree.tostring(root, encoding='unicode')
|
||||||
|
|
||||||
|
def generate_error(self, msg):
|
||||||
|
msg = super().generate_error(msg)
|
||||||
|
raise XMLAssetError(msg)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bundle_version(self):
|
def bundle_version(self):
|
||||||
return self.bundle.get_version('js')
|
return self.bundle.get_version('js')
|
||||||
|
|
@ -1008,17 +975,17 @@ class PreprocessedCSS(StylesheetAsset):
|
||||||
command = self.get_command()
|
command = self.get_command()
|
||||||
try:
|
try:
|
||||||
compiler = Popen(command, stdin=PIPE, stdout=PIPE,
|
compiler = Popen(command, stdin=PIPE, stdout=PIPE,
|
||||||
stderr=PIPE)
|
stderr=PIPE, encoding='utf-8')
|
||||||
except Exception:
|
except Exception:
|
||||||
raise CompileError("Could not execute command %r" % command[0])
|
raise CompileError("Could not execute command %r" % command[0])
|
||||||
|
|
||||||
(out, err) = compiler.communicate(input=source.encode('utf-8'))
|
out, err = compiler.communicate(input=source)
|
||||||
if compiler.returncode:
|
if compiler.returncode:
|
||||||
cmd_output = misc.ustr(out) + misc.ustr(err)
|
cmd_output = out + err
|
||||||
if not cmd_output:
|
if not cmd_output:
|
||||||
cmd_output = u"Process exited with return code %d\n" % compiler.returncode
|
cmd_output = u"Process exited with return code %d\n" % compiler.returncode
|
||||||
raise CompileError(cmd_output)
|
raise CompileError(cmd_output)
|
||||||
return out.decode('utf8')
|
return out
|
||||||
|
|
||||||
class SassStylesheetAsset(PreprocessedCSS):
|
class SassStylesheetAsset(PreprocessedCSS):
|
||||||
rx_indent = re.compile(r'^( +|\t+)', re.M)
|
rx_indent = re.compile(r'^( +|\t+)', re.M)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import logging
|
||||||
from operator import getitem
|
from operator import getitem
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
|
|
@ -55,9 +56,12 @@ class IrActions(models.Model):
|
||||||
_order = 'name'
|
_order = 'name'
|
||||||
_allow_sudo_commands = False
|
_allow_sudo_commands = False
|
||||||
|
|
||||||
|
_sql_constraints = [('path_unique', 'unique(path)', "Path to show in the URL must be unique! Please choose another one.")]
|
||||||
|
|
||||||
name = fields.Char(string='Action Name', required=True, translate=True)
|
name = fields.Char(string='Action Name', required=True, translate=True)
|
||||||
type = fields.Char(string='Action Type', required=True)
|
type = fields.Char(string='Action Type', required=True)
|
||||||
xml_id = fields.Char(compute='_compute_xml_id', string="External ID")
|
xml_id = fields.Char(compute='_compute_xml_id', string="External ID")
|
||||||
|
path = fields.Char(string="Path to show in the URL")
|
||||||
help = fields.Html(string='Action Description',
|
help = fields.Html(string='Action Description',
|
||||||
help='Optional help text for the users with a description of the target view, such as its usage and purpose.',
|
help='Optional help text for the users with a description of the target view, such as its usage and purpose.',
|
||||||
translate=True)
|
translate=True)
|
||||||
|
|
@ -68,6 +72,30 @@ class IrActions(models.Model):
|
||||||
required=True, default='action')
|
required=True, default='action')
|
||||||
binding_view_types = fields.Char(default='list,form')
|
binding_view_types = fields.Char(default='list,form')
|
||||||
|
|
||||||
|
@api.constrains('path')
|
||||||
|
def _check_path(self):
|
||||||
|
for action in self:
|
||||||
|
if action.path:
|
||||||
|
if not re.fullmatch(r'[a-z][a-z0-9_-]*', action.path):
|
||||||
|
raise ValidationError(_('The path should contain only lowercase alphanumeric characters, underscore, and dash, and it should start with a letter.'))
|
||||||
|
if action.path.startswith("m-"):
|
||||||
|
raise ValidationError(_("'m-' is a reserved prefix."))
|
||||||
|
if action.path.startswith("action-"):
|
||||||
|
raise ValidationError(_("'action-' is a reserved prefix."))
|
||||||
|
if action.path == "new":
|
||||||
|
raise ValidationError(_("'new' is reserved, and can not be used as path."))
|
||||||
|
# Tables ir_act_window, ir_act_report_xml, ir_act_url, ir_act_server and ir_act_client
|
||||||
|
# inherit from table ir_actions (see base_data.sql). The path must be unique across
|
||||||
|
# all these tables. The unique constraint is not enough because a big limitation of
|
||||||
|
# the inheritance feature is that unique indexes only apply to single tables, and
|
||||||
|
# not accross all the tables. So we need to check the uniqueness of the path manually.
|
||||||
|
# For more information, see: https://www.postgresql.org/docs/14/ddl-inherit.html#DDL-INHERIT-CAVEATS
|
||||||
|
|
||||||
|
# Note that, we leave the unique constraint in place to check the uniqueness of the path
|
||||||
|
# within the same table before checking the uniqueness across all the tables.
|
||||||
|
if (self.env['ir.actions.actions'].search_count([('path', '=', action.path)]) > 1):
|
||||||
|
raise ValidationError(_("Path to show in the URL must be unique! Please choose another one."))
|
||||||
|
|
||||||
def _compute_xml_id(self):
|
def _compute_xml_id(self):
|
||||||
res = self.get_external_id()
|
res = self.get_external_id()
|
||||||
for record in self:
|
for record in self:
|
||||||
|
|
@ -132,7 +160,7 @@ class IrActions(models.Model):
|
||||||
for action in all_actions:
|
for action in all_actions:
|
||||||
action = dict(action)
|
action = dict(action)
|
||||||
groups = action.pop('groups_id', None)
|
groups = action.pop('groups_id', None)
|
||||||
if groups and not self.user_has_groups(groups):
|
if groups and not any(self.env.user.has_group(ext_id) for ext_id in groups):
|
||||||
# the user may not perform this action
|
# the user may not perform this action
|
||||||
continue
|
continue
|
||||||
res_model = action.pop('res_model', None)
|
res_model = action.pop('res_model', None)
|
||||||
|
|
@ -167,13 +195,16 @@ class IrActions(models.Model):
|
||||||
try:
|
try:
|
||||||
action = self.env[action_model].sudo().browse(action_id)
|
action = self.env[action_model].sudo().browse(action_id)
|
||||||
fields = ['name', 'binding_view_types']
|
fields = ['name', 'binding_view_types']
|
||||||
for field in ('groups_id', 'res_model', 'sequence'):
|
for field in ('groups_id', 'res_model', 'sequence', 'domain'):
|
||||||
if field in action._fields:
|
if field in action._fields:
|
||||||
fields.append(field)
|
fields.append(field)
|
||||||
action = action.read(fields)[0]
|
action = action.read(fields)[0]
|
||||||
if action.get('groups_id'):
|
if action.get('groups_id'):
|
||||||
|
# transform the list of ids into a list of xml ids
|
||||||
groups = self.env['res.groups'].browse(action['groups_id'])
|
groups = self.env['res.groups'].browse(action['groups_id'])
|
||||||
action['groups_id'] = ','.join(ext_id for ext_id in groups._ensure_xml_id().values())
|
action['groups_id'] = list(groups._ensure_xml_id().values())
|
||||||
|
if 'domain' in action and not action.get('domain'):
|
||||||
|
action.pop('domain')
|
||||||
result[binding_type].append(frozendict(action))
|
result[binding_type].append(frozendict(action))
|
||||||
except (MissingError):
|
except (MissingError):
|
||||||
continue
|
continue
|
||||||
|
|
@ -217,6 +248,7 @@ class IrActions(models.Model):
|
||||||
return {
|
return {
|
||||||
"binding_model_id", "binding_type", "binding_view_types",
|
"binding_model_id", "binding_type", "binding_view_types",
|
||||||
"display_name", "help", "id", "name", "type", "xml_id",
|
"display_name", "help", "id", "name", "type", "xml_id",
|
||||||
|
"path",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -232,9 +264,9 @@ class IrActionsActWindow(models.Model):
|
||||||
def _check_model(self):
|
def _check_model(self):
|
||||||
for action in self:
|
for action in self:
|
||||||
if action.res_model not in self.env:
|
if action.res_model not in self.env:
|
||||||
raise ValidationError(_('Invalid model name %r in action definition.', action.res_model))
|
raise ValidationError(_('Invalid model name “%s” in action definition.', action.res_model))
|
||||||
if action.binding_model_id and action.binding_model_id.model not in self.env:
|
if action.binding_model_id and action.binding_model_id.model not in self.env:
|
||||||
raise ValidationError(_('Invalid model name %r in action definition.', action.binding_model_id.model))
|
raise ValidationError(_('Invalid model name “%s” in action definition.', action.binding_model_id.model))
|
||||||
|
|
||||||
@api.depends('view_ids.view_mode', 'view_mode', 'view_id.type')
|
@api.depends('view_ids.view_mode', 'view_mode', 'view_id.type')
|
||||||
def _compute_views(self):
|
def _compute_views(self):
|
||||||
|
|
@ -265,7 +297,7 @@ class IrActionsActWindow(models.Model):
|
||||||
if len(modes) != len(set(modes)):
|
if len(modes) != len(set(modes)):
|
||||||
raise ValidationError(_('The modes in view_mode must not be duplicated: %s', modes))
|
raise ValidationError(_('The modes in view_mode must not be duplicated: %s', modes))
|
||||||
if ' ' in modes:
|
if ' ' in modes:
|
||||||
raise ValidationError(_('No spaces allowed in view_mode: %r', modes))
|
raise ValidationError(_('No spaces allowed in view_mode: “%s”', modes))
|
||||||
|
|
||||||
type = fields.Char(default="ir.actions.act_window")
|
type = fields.Char(default="ir.actions.act_window")
|
||||||
view_id = fields.Many2one('ir.ui.view', string='View Ref.', ondelete='set null')
|
view_id = fields.Many2one('ir.ui.view', string='View Ref.', ondelete='set null')
|
||||||
|
|
@ -277,8 +309,8 @@ class IrActionsActWindow(models.Model):
|
||||||
res_model = fields.Char(string='Destination Model', required=True,
|
res_model = fields.Char(string='Destination Model', required=True,
|
||||||
help="Model name of the object to open in the view window")
|
help="Model name of the object to open in the view window")
|
||||||
target = fields.Selection([('current', 'Current Window'), ('new', 'New Window'), ('inline', 'Inline Edit'), ('fullscreen', 'Full Screen'), ('main', 'Main action of Current Window')], default="current", string='Target Window')
|
target = fields.Selection([('current', 'Current Window'), ('new', 'New Window'), ('inline', 'Inline Edit'), ('fullscreen', 'Full Screen'), ('main', 'Main action of Current Window')], default="current", string='Target Window')
|
||||||
view_mode = fields.Char(required=True, default='tree,form',
|
view_mode = fields.Char(required=True, default='list,form',
|
||||||
help="Comma-separated list of allowed view modes, such as 'form', 'tree', 'calendar', etc. (Default: tree,form)")
|
help="Comma-separated list of allowed view modes, such as 'form', 'list', 'calendar', etc. (Default: list,form)")
|
||||||
mobile_view_mode = fields.Char(default="kanban", help="First view mode in mobile and small screen environments (default='kanban'). If it can't be found among available view modes, the same mode as for wider screens is used)")
|
mobile_view_mode = fields.Char(default="kanban", help="First view mode in mobile and small screen environments (default='kanban'). If it can't be found among available view modes, the same mode as for wider screens is used)")
|
||||||
usage = fields.Char(string='Action Usage',
|
usage = fields.Char(string='Action Usage',
|
||||||
help="Used to filter menu and home actions from the user form.")
|
help="Used to filter menu and home actions from the user form.")
|
||||||
|
|
@ -291,8 +323,14 @@ class IrActionsActWindow(models.Model):
|
||||||
groups_id = fields.Many2many('res.groups', 'ir_act_window_group_rel',
|
groups_id = fields.Many2many('res.groups', 'ir_act_window_group_rel',
|
||||||
'act_id', 'gid', string='Groups')
|
'act_id', 'gid', string='Groups')
|
||||||
search_view_id = fields.Many2one('ir.ui.view', string='Search View Ref.')
|
search_view_id = fields.Many2one('ir.ui.view', string='Search View Ref.')
|
||||||
|
embedded_action_ids = fields.One2many('ir.embedded.actions', compute="_compute_embedded_actions")
|
||||||
filter = fields.Boolean()
|
filter = fields.Boolean()
|
||||||
|
|
||||||
|
def _compute_embedded_actions(self):
|
||||||
|
embedded_actions = self.env["ir.embedded.actions"].search([('parent_action_id', 'in', self.ids)]).filtered(lambda x: x.is_visible)
|
||||||
|
for action in self:
|
||||||
|
action.embedded_action_ids = embedded_actions.filtered(lambda rec: rec.parent_action_id == action)
|
||||||
|
|
||||||
def read(self, fields=None, load='_classic_read'):
|
def read(self, fields=None, load='_classic_read'):
|
||||||
""" call the method get_empty_list_help of the model and set the window action help message
|
""" call the method get_empty_list_help of the model and set the window action help message
|
||||||
"""
|
"""
|
||||||
|
|
@ -336,20 +374,33 @@ class IrActionsActWindow(models.Model):
|
||||||
def _get_readable_fields(self):
|
def _get_readable_fields(self):
|
||||||
return super()._get_readable_fields() | {
|
return super()._get_readable_fields() | {
|
||||||
"context", "mobile_view_mode", "domain", "filter", "groups_id", "limit",
|
"context", "mobile_view_mode", "domain", "filter", "groups_id", "limit",
|
||||||
"res_id", "res_model", "search_view_id", "target", "view_id", "view_mode", "views",
|
"res_id", "res_model", "search_view_id", "target", "view_id", "view_mode", "views", "embedded_action_ids",
|
||||||
# `flags` is not a real field of ir.actions.act_window but is used
|
# `flags` is not a real field of ir.actions.act_window but is used
|
||||||
# to give the parameters to generate the action
|
# to give the parameters to generate the action
|
||||||
"flags"
|
"flags",
|
||||||
|
# this is used by frontend, with the document layout wizard before send and print
|
||||||
|
"close_on_report_download",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _get_action_dict(self):
|
||||||
|
""" Override to return action content with detailed embedded actions data if available.
|
||||||
|
|
||||||
|
:return: A dict with updated action dictionary including embedded actions information.
|
||||||
|
"""
|
||||||
|
result = super()._get_action_dict()
|
||||||
|
if embedded_action_ids := result["embedded_action_ids"]:
|
||||||
|
EmbeddedActions = self.env["ir.embedded.actions"]
|
||||||
|
embedded_fields = EmbeddedActions._get_readable_fields()
|
||||||
|
result["embedded_action_ids"] = EmbeddedActions.browse(embedded_action_ids).read(embedded_fields)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
VIEW_TYPES = [
|
VIEW_TYPES = [
|
||||||
('tree', 'Tree'),
|
('list', 'List'),
|
||||||
('form', 'Form'),
|
('form', 'Form'),
|
||||||
('graph', 'Graph'),
|
('graph', 'Graph'),
|
||||||
('pivot', 'Pivot'),
|
('pivot', 'Pivot'),
|
||||||
('calendar', 'Calendar'),
|
('calendar', 'Calendar'),
|
||||||
('gantt', 'Gantt'),
|
|
||||||
('kanban', 'Kanban'),
|
('kanban', 'Kanban'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -463,6 +514,7 @@ class IrActionsServer(models.Model):
|
||||||
# - records: recordset of all records on which the action is triggered in multi-mode; may be void
|
# - records: recordset of all records on which the action is triggered in multi-mode; may be void
|
||||||
# - time, datetime, dateutil, timezone: useful Python libraries
|
# - time, datetime, dateutil, timezone: useful Python libraries
|
||||||
# - float_compare: utility function to compare floats based on specific precision
|
# - float_compare: utility function to compare floats based on specific precision
|
||||||
|
# - b64encode, b64decode: functions to encode/decode binary data
|
||||||
# - log: log(message, level='info'): logging function to record debug information in ir.logging table
|
# - log: log(message, level='info'): logging function to record debug information in ir.logging table
|
||||||
# - _logger: _logger.info(message): logger to emit messages in server logs
|
# - _logger: _logger.info(message): logger to emit messages in server logs
|
||||||
# - UserError: exception class for raising user-facing warning messages
|
# - UserError: exception class for raising user-facing warning messages
|
||||||
|
|
@ -697,15 +749,15 @@ class IrActionsServer(models.Model):
|
||||||
action.webhook_sample_payload = False
|
action.webhook_sample_payload = False
|
||||||
continue
|
continue
|
||||||
payload = {
|
payload = {
|
||||||
'id': 1,
|
'_id': 1,
|
||||||
'_model': self.model_id.model,
|
'_model': self.model_id.model,
|
||||||
'_name': action.name,
|
'_action': f'{action.name}(#{action.id})',
|
||||||
}
|
}
|
||||||
if self.model_id:
|
if self.model_id:
|
||||||
sample_record = self.env[self.model_id.model].with_context(active_test=False).search([], limit=1)
|
sample_record = self.env[self.model_id.model].with_context(active_test=False).search([], limit=1)
|
||||||
for field in action.webhook_field_ids:
|
for field in action.webhook_field_ids:
|
||||||
if sample_record:
|
if sample_record:
|
||||||
payload['id'] = sample_record.id
|
payload['_id'] = sample_record.id
|
||||||
payload.update(sample_record.read(self.webhook_field_ids.mapped('name'), load=None)[0])
|
payload.update(sample_record.read(self.webhook_field_ids.mapped('name'), load=None)[0])
|
||||||
else:
|
else:
|
||||||
payload[field.name] = WEBHOOK_SAMPLE_VALUES[field.ttype] if field.ttype in WEBHOOK_SAMPLE_VALUES else WEBHOOK_SAMPLE_VALUES[None]
|
payload[field.name] = WEBHOOK_SAMPLE_VALUES[field.ttype] if field.ttype in WEBHOOK_SAMPLE_VALUES else WEBHOOK_SAMPLE_VALUES[None]
|
||||||
|
|
@ -725,8 +777,8 @@ class IrActionsServer(models.Model):
|
||||||
raise ValidationError(msg)
|
raise ValidationError(msg)
|
||||||
|
|
||||||
@api.constrains('child_ids')
|
@api.constrains('child_ids')
|
||||||
def _check_recursion(self):
|
def _check_child_recursion(self):
|
||||||
if not self._check_m2m_recursion('child_ids'):
|
if self._has_cycle('child_ids'):
|
||||||
raise ValidationError(_('Recursion found in child server actions'))
|
raise ValidationError(_('Recursion found in child server actions'))
|
||||||
|
|
||||||
def _get_readable_fields(self):
|
def _get_readable_fields(self):
|
||||||
|
|
@ -767,7 +819,7 @@ class IrActionsServer(models.Model):
|
||||||
|
|
||||||
def unlink_action(self):
|
def unlink_action(self):
|
||||||
""" Remove the contextual actions created for the server actions. """
|
""" Remove the contextual actions created for the server actions. """
|
||||||
self.check_access_rights('write', raise_exception=True)
|
self.check_access('write')
|
||||||
self.filtered('binding_model_id').write({'binding_model_id': False})
|
self.filtered('binding_model_id').write({'binding_model_id': False})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
@ -790,7 +842,7 @@ class IrActionsServer(models.Model):
|
||||||
record_cached = self._context['onchange_self']
|
record_cached = self._context['onchange_self']
|
||||||
for field, new_value in res.items():
|
for field, new_value in res.items():
|
||||||
record_cached[field] = new_value
|
record_cached[field] = new_value
|
||||||
else:
|
elif self.update_path:
|
||||||
starting_record = self.env[self.model_id.model].browse(self._context.get('active_id'))
|
starting_record = self.env[self.model_id.model].browse(self._context.get('active_id'))
|
||||||
_, _, target_records = self._traverse_path(record=starting_record)
|
_, _, target_records = self._traverse_path(record=starting_record)
|
||||||
target_records.write(res)
|
target_records.write(res)
|
||||||
|
|
@ -915,7 +967,7 @@ class IrActionsServer(models.Model):
|
||||||
else:
|
else:
|
||||||
model_name = action.model_id.model
|
model_name = action.model_id.model
|
||||||
try:
|
try:
|
||||||
self.env[model_name].check_access_rights("write")
|
self.env[model_name].check_access("write")
|
||||||
except AccessError:
|
except AccessError:
|
||||||
_logger.warning("Forbidden server action %r executed while the user %s does not have access to %s.",
|
_logger.warning("Forbidden server action %r executed while the user %s does not have access to %s.",
|
||||||
action.name, self.env.user.login, model_name,
|
action.name, self.env.user.login, model_name,
|
||||||
|
|
@ -925,11 +977,11 @@ class IrActionsServer(models.Model):
|
||||||
eval_context = self._get_eval_context(action)
|
eval_context = self._get_eval_context(action)
|
||||||
records = eval_context.get('record') or eval_context['model']
|
records = eval_context.get('record') or eval_context['model']
|
||||||
records |= eval_context.get('records') or eval_context['model']
|
records |= eval_context.get('records') or eval_context['model']
|
||||||
if records.ids:
|
if not action_groups and records.ids:
|
||||||
# check access rules on real records only; base automations of
|
# check access rules on real records only; base automations of
|
||||||
# type 'onchange' can run server actions on new records
|
# type 'onchange' can run server actions on new records
|
||||||
try:
|
try:
|
||||||
records.check_access_rule('write')
|
records.check_access('write')
|
||||||
except AccessError:
|
except AccessError:
|
||||||
_logger.warning("Forbidden server action %r executed while the user %s does not have access to %s.",
|
_logger.warning("Forbidden server action %r executed while the user %s does not have access to %s.",
|
||||||
action.name, self.env.user.login, records,
|
action.name, self.env.user.login, records,
|
||||||
|
|
@ -1016,6 +1068,8 @@ class IrActionsServer(models.Model):
|
||||||
elif action.update_field_id.ttype in ['many2one', 'integer']:
|
elif action.update_field_id.ttype in ['many2one', 'integer']:
|
||||||
try:
|
try:
|
||||||
expr = int(action.value)
|
expr = int(action.value)
|
||||||
|
if expr == 0 and action.update_field_id.ttype == 'many2one':
|
||||||
|
expr = False
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
elif action.update_field_id.ttype == 'float':
|
elif action.update_field_id.ttype == 'float':
|
||||||
|
|
@ -1026,9 +1080,11 @@ class IrActionsServer(models.Model):
|
||||||
|
|
||||||
def copy_data(self, default=None):
|
def copy_data(self, default=None):
|
||||||
default = default or {}
|
default = default or {}
|
||||||
|
vals_list = super().copy_data(default=default)
|
||||||
if not default.get('name'):
|
if not default.get('name'):
|
||||||
default['name'] = _('%s (copy)', self.name)
|
for vals in vals_list:
|
||||||
return super().copy_data(default=default)
|
vals['name'] = _('%s (copy)', vals.get('name', ''))
|
||||||
|
return vals_list
|
||||||
|
|
||||||
class IrActionsTodo(models.Model):
|
class IrActionsTodo(models.Model):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue