mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 02:52:00 +02:00
17.0 vanilla
This commit is contained in:
parent
2e65bf056a
commit
df627a6bba
328 changed files with 578149 additions and 759311 deletions
|
|
@ -29,7 +29,7 @@ except ImportError:
|
|||
from decorator import decorator
|
||||
|
||||
from .exceptions import AccessError, CacheMiss
|
||||
from .tools import classproperty, frozendict, lazy_property, OrderedSet, Query, StackMap
|
||||
from .tools import clean_context, frozendict, lazy_property, OrderedSet, Query, SQL, StackMap
|
||||
from .tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
|
@ -498,22 +498,6 @@ class Environment(Mapping):
|
|||
names to models. It also holds a cache for records, and a data
|
||||
structure to manage recomputations.
|
||||
"""
|
||||
@classproperty
|
||||
def envs(cls):
|
||||
raise NotImplementedError(
|
||||
"Since Odoo 15.0, Environment.envs no longer works; "
|
||||
"use cr.transaction or env.transaction instead."
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@contextmanager
|
||||
def manage(cls):
|
||||
warnings.warn(
|
||||
"Since Odoo 15.0, Environment.manage() is useless.",
|
||||
DeprecationWarning, stacklevel=2,
|
||||
)
|
||||
yield
|
||||
|
||||
def reset(self):
|
||||
""" Reset the transaction, see :meth:`Transaction.reset`. """
|
||||
self.transaction.reset()
|
||||
|
|
@ -596,7 +580,8 @@ class Environment(Mapping):
|
|||
"""
|
||||
cr = self.cr if cr is None else cr
|
||||
uid = self.uid if user is None else int(user)
|
||||
context = self.context if context is None else context
|
||||
if context is None:
|
||||
context = clean_context(self.context) if su and not self.su else self.context
|
||||
su = (user is None and self.su) if su is None else su
|
||||
return Environment(cr, uid, context, su, self.uid_origin)
|
||||
|
||||
|
|
@ -655,7 +640,7 @@ class Environment(Mapping):
|
|||
|
||||
.. warning::
|
||||
|
||||
No sanity checks applied in sudo mode !
|
||||
No sanity checks applied in sudo mode!
|
||||
When in sudo mode, a user can access any company,
|
||||
even if not in his allowed companies.
|
||||
|
||||
|
|
@ -723,33 +708,14 @@ class Environment(Mapping):
|
|||
# because 'env.lang' may be injected in SQL queries
|
||||
return lang if lang and self['res.lang']._lang_get_id(lang) else None
|
||||
|
||||
@property
|
||||
def ocb(self):
|
||||
"""Allow to flag OCB environment so we can easily address compatibility issues
|
||||
when making backports or improvements that aren't present in the current Odoo
|
||||
version.
|
||||
|
||||
:rtype bool
|
||||
"""
|
||||
return True
|
||||
|
||||
def clear(self):
|
||||
""" Clear all record caches, and discard all fields to recompute.
|
||||
This may be useful when recovering from a failed ORM operation.
|
||||
"""
|
||||
lazy_property.reset_all(self)
|
||||
self._cache_key.clear()
|
||||
self.transaction.clear()
|
||||
|
||||
def clear_upon_failure(self):
|
||||
""" Context manager that rolls back the environments (caches and pending
|
||||
computations and updates) upon exception.
|
||||
"""
|
||||
warnings.warn(
|
||||
"Since Odoo 15.0, use cr.savepoint() instead of env.clear_upon_failure().",
|
||||
DeprecationWarning, stacklevel=2,
|
||||
)
|
||||
return self.cr.savepoint()
|
||||
|
||||
def invalidate_all(self, flush=True):
|
||||
""" Invalidate the cache of all records.
|
||||
|
||||
|
|
@ -846,7 +812,8 @@ class Environment(Mapping):
|
|||
|
||||
@contextmanager
|
||||
def norecompute(self):
|
||||
""" Delay recomputations (deprecated: this is not the default behavior). """
|
||||
""" Deprecated: It does nothing, recomputation is delayed by default. """
|
||||
warnings.warn("`norecompute` is useless. Deprecated since 17.0.", DeprecationWarning, 2)
|
||||
yield
|
||||
|
||||
def cache_key(self, field):
|
||||
|
|
@ -864,6 +831,8 @@ class Environment(Mapping):
|
|||
return get_context('lang') or None
|
||||
elif key == 'active_test':
|
||||
return get_context('active_test', field.context.get('active_test', True))
|
||||
elif key.startswith('bin_size'):
|
||||
return bool(get_context(key))
|
||||
else:
|
||||
val = get_context(key)
|
||||
if type(val) is list:
|
||||
|
|
@ -890,6 +859,7 @@ class Transaction:
|
|||
self.registry = registry
|
||||
# weak set of environments
|
||||
self.envs = WeakSet()
|
||||
self.envs.data = OrderedSet() # make the weakset OrderedWeakSet
|
||||
# cache for all records
|
||||
self.cache = Cache()
|
||||
# fields to protect {field: ids}
|
||||
|
|
@ -922,6 +892,7 @@ class Transaction:
|
|||
for env in self.envs:
|
||||
env.registry = self.registry
|
||||
lazy_property.reset_all(env)
|
||||
env._cache_key.clear()
|
||||
self.clear()
|
||||
|
||||
|
||||
|
|
@ -960,6 +931,10 @@ class Cache(object):
|
|||
# in `_data`
|
||||
self._dirty = defaultdict(OrderedSet)
|
||||
|
||||
# {field: {record_id: ids}} record ids to be added to the values of
|
||||
# x2many fields if they are not in cache yet
|
||||
self._patches = defaultdict(lambda: defaultdict(list))
|
||||
|
||||
def __repr__(self):
|
||||
# for debugging: show the cache content and dirty flags as stars
|
||||
data = {}
|
||||
|
|
@ -1001,7 +976,7 @@ class Cache(object):
|
|||
cache_value = field_cache.get(record.id, EMPTY_DICT)
|
||||
if cache_value is None:
|
||||
return True
|
||||
lang = record.env.lang or 'en_US'
|
||||
lang = field._lang(record.env)
|
||||
return lang in cache_value
|
||||
|
||||
return record.id in field_cache
|
||||
|
|
@ -1022,12 +997,14 @@ class Cache(object):
|
|||
field_cache = self._get_field_cache(record, field)
|
||||
cache_value = field_cache[record._ids[0]]
|
||||
if field.translate and cache_value is not None:
|
||||
lang = record.env.lang or 'en_US'
|
||||
lang = field._lang(record.env)
|
||||
if not (field.compute or field.store and record._origin):
|
||||
return cache_value.get(lang, cache_value.get('en_US'))
|
||||
return cache_value[lang]
|
||||
return cache_value
|
||||
except KeyError:
|
||||
if default is NOTHING:
|
||||
raise CacheMiss(record, field)
|
||||
raise CacheMiss(record, field) from None
|
||||
return default
|
||||
|
||||
def set(self, record, field, value, dirty=False, check_dirty=True):
|
||||
|
|
@ -1042,26 +1019,33 @@ class Cache(object):
|
|||
dirty must raise an exception
|
||||
"""
|
||||
field_cache = self._set_field_cache(record, field)
|
||||
record_id = record.id
|
||||
|
||||
if field.translate and value is not None:
|
||||
# only for model translated fields
|
||||
lang = record.env.lang or 'en_US'
|
||||
cache_value = field_cache.get(record._ids[0]) or {}
|
||||
cache_value = field_cache.get(record_id) or {}
|
||||
cache_value[lang] = value
|
||||
if not (field.compute or field.store and record._origin):
|
||||
cache_value.setdefault('en_US', value)
|
||||
value = cache_value
|
||||
field_cache[record._ids[0]] = value
|
||||
|
||||
field_cache[record_id] = value
|
||||
|
||||
if not check_dirty:
|
||||
return
|
||||
|
||||
if dirty:
|
||||
assert field.column_type and field.store and record.id
|
||||
self._dirty[field].add(record.id)
|
||||
assert field.column_type and field.store and record_id
|
||||
self._dirty[field].add(record_id)
|
||||
if record.pool.field_depends_context[field]:
|
||||
# put the values under conventional context key values {'context_key': None},
|
||||
# in order to ease the retrieval of those values to flush them
|
||||
context_none = dict.fromkeys(record.pool.field_depends_context[field])
|
||||
record = record.with_env(record.env(context=context_none))
|
||||
field_cache = self._set_field_cache(record, field)
|
||||
field_cache[record._ids[0]] = value
|
||||
elif record.id in self._dirty.get(field, ()):
|
||||
field_cache[record_id] = value
|
||||
elif record_id in self._dirty.get(field, ()):
|
||||
_logger.error("cache.set() removing flag dirty on %s.%s", record, field.name, stack_info=True)
|
||||
|
||||
def update(self, records, field, values, dirty=False, check_dirty=True):
|
||||
|
|
@ -1076,15 +1060,18 @@ class Cache(object):
|
|||
dirty must raise an exception
|
||||
"""
|
||||
if field.translate:
|
||||
lang = records.env.lang or 'en_US'
|
||||
# only for model translated fields
|
||||
lang = (records.env.lang or 'en_US') if dirty else field._lang(records.env)
|
||||
field_cache = self._get_field_cache(records, field)
|
||||
cache_values = []
|
||||
for id_, value in zip(records._ids, values):
|
||||
for record, value in zip(records, values):
|
||||
if value is None:
|
||||
cache_values.append(None)
|
||||
else:
|
||||
cache_value = field_cache.get(id_) or {}
|
||||
cache_value = field_cache.get(record.id) or {}
|
||||
cache_value[lang] = value
|
||||
if not (field.compute or field.store and record._origin):
|
||||
cache_value.setdefault('en_US', value)
|
||||
cache_values.append(cache_value)
|
||||
values = cache_values
|
||||
|
||||
|
|
@ -1120,18 +1107,61 @@ class Cache(object):
|
|||
"""
|
||||
field_cache = self._set_field_cache(records, field)
|
||||
if field.translate:
|
||||
lang = records.env.lang or 'en_US'
|
||||
for id_, val in zip(records._ids, values):
|
||||
if val is None:
|
||||
field_cache.setdefault(id_, None)
|
||||
else:
|
||||
cache_value = field_cache.setdefault(id_, {})
|
||||
if cache_value is not None:
|
||||
cache_value.setdefault(lang, val)
|
||||
if records.env.context.get('prefetch_langs'):
|
||||
langs = {lang for lang, _ in records.env['res.lang'].get_installed()} | {'en_US'}
|
||||
_langs = {f'_{l}' for l in langs} if field._lang(records.env).startswith('_') else set()
|
||||
for id_, val in zip(records._ids, values):
|
||||
if val is None:
|
||||
field_cache.setdefault(id_, None)
|
||||
else:
|
||||
if _langs: # fallback missing _lang to lang if exists
|
||||
val.update({f'_{k}': v for k, v in val.items() if k in langs and f'_{k}' not in val})
|
||||
field_cache[id_] = {
|
||||
**dict.fromkeys(langs, val['en_US']), # fallback missing lang to en_US
|
||||
**dict.fromkeys(_langs, val.get('_en_US')), # fallback missing _lang to _en_US
|
||||
**val
|
||||
}
|
||||
else:
|
||||
lang = field._lang(records.env)
|
||||
for id_, val in zip(records._ids, values):
|
||||
if val is None:
|
||||
field_cache.setdefault(id_, None)
|
||||
else:
|
||||
cache_value = field_cache.setdefault(id_, {})
|
||||
if cache_value is not None:
|
||||
cache_value.setdefault(lang, val)
|
||||
else:
|
||||
for id_, val in zip(records._ids, values):
|
||||
field_cache.setdefault(id_, val)
|
||||
|
||||
def patch(self, records, field, new_id):
|
||||
""" Apply a patch to an x2many field on new records. The patch consists
|
||||
in adding new_id to its value in cache. If the value is not in cache
|
||||
yet, it will be applied once the value is put in cache with method
|
||||
:meth:`patch_and_set`.
|
||||
"""
|
||||
assert not new_id, "Cache.patch can only be called with a new id"
|
||||
field_cache = self._set_field_cache(records, field)
|
||||
for id_ in records._ids:
|
||||
assert not id_, "Cache.patch can only be called with new records"
|
||||
if id_ in field_cache:
|
||||
field_cache[id_] = tuple(dict.fromkeys(field_cache[id_] + (new_id,)))
|
||||
else:
|
||||
self._patches[field][id_].append(new_id)
|
||||
|
||||
def patch_and_set(self, record, field, value):
|
||||
""" Set the value of ``field`` for ``record``, like :meth:`set`, but
|
||||
apply pending patches to ``value`` and return the value actually put
|
||||
in cache.
|
||||
"""
|
||||
field_patches = self._patches.get(field)
|
||||
if field_patches:
|
||||
ids = field_patches.pop(record.id, ())
|
||||
if ids:
|
||||
value = tuple(dict.fromkeys(value + tuple(ids)))
|
||||
self.set(record, field, value)
|
||||
return value
|
||||
|
||||
def remove(self, record, field):
|
||||
""" Remove the value of ``field`` for ``record``. """
|
||||
assert record.id not in self._dirty.get(field, ())
|
||||
|
|
@ -1154,7 +1184,7 @@ class Cache(object):
|
|||
""" Return the cached values of ``field`` for ``records`` until a value is not found. """
|
||||
field_cache = self._get_field_cache(records, field)
|
||||
if field.translate:
|
||||
lang = records.env.lang or 'en_US'
|
||||
lang = field._lang(records.env)
|
||||
|
||||
def get_value(id_):
|
||||
cache_value = field_cache[id_]
|
||||
|
|
@ -1174,7 +1204,7 @@ class Cache(object):
|
|||
""" Return the subset of ``records`` that has not ``value`` for ``field``. """
|
||||
field_cache = self._get_field_cache(records, field)
|
||||
if field.translate:
|
||||
lang = records.env.lang or 'en_US'
|
||||
lang = field._lang(records.env)
|
||||
|
||||
def get_value(id_):
|
||||
cache_value = field_cache[id_]
|
||||
|
|
@ -1217,7 +1247,7 @@ class Cache(object):
|
|||
""" Return the ids of ``records`` that have no value for ``field``. """
|
||||
field_cache = self._get_field_cache(records, field)
|
||||
if field.translate:
|
||||
lang = records.env.lang or 'en_US'
|
||||
lang = field._lang(records.env)
|
||||
for record_id in records._ids:
|
||||
cache_value = field_cache.get(record_id, False)
|
||||
if cache_value is False or not (cache_value is None or lang in cache_value):
|
||||
|
|
@ -1292,6 +1322,7 @@ class Cache(object):
|
|||
""" Invalidate the cache and its dirty flags. """
|
||||
self._data.clear()
|
||||
self._dirty.clear()
|
||||
self._patches.clear()
|
||||
|
||||
def check(self, env):
|
||||
""" Check the consistency of the cache for the given environment. """
|
||||
|
|
@ -1307,14 +1338,14 @@ class Cache(object):
|
|||
|
||||
# select the column for the given ids
|
||||
query = Query(env.cr, model._table, model._table_query)
|
||||
qname = model._inherits_join_calc(model._table, field.name, query)
|
||||
sql_id = SQL.identifier(model._table, 'id')
|
||||
sql_field = model._field_to_sql(model._table, field.name, query)
|
||||
if field.type == 'binary' and (
|
||||
model.env.context.get('bin_size') or model.env.context.get('bin_size_' + field.name)
|
||||
):
|
||||
qname = f'pg_size_pretty(length({qname})::bigint)'
|
||||
query.add_where(f'"{model._table}".id IN %s', [tuple(ids)])
|
||||
query_str, params = query.select(f'"{model._table}".id', qname)
|
||||
env.cr.execute(query_str, params)
|
||||
sql_field = SQL('pg_size_pretty(length(%s)::bigint)', sql_field)
|
||||
query.add_where(SQL("%s IN %s", sql_id, tuple(ids)))
|
||||
env.cr.execute(query.select(sql_id, sql_field))
|
||||
|
||||
# compare returned values with corresponding values in cache
|
||||
for id_, value in env.cr.fetchall():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue