19.0 vanilla

This commit is contained in:
Ernad Husremovic 2025-10-03 18:07:25 +02:00
parent 0a7ae8db93
commit 991d2234ca
416 changed files with 646602 additions and 300844 deletions

View file

@ -0,0 +1,149 @@
import re
import warnings
from collections.abc import Set as AbstractSet
import dateutil.relativedelta
from odoo.exceptions import AccessError, ValidationError
from odoo.tools import SQL
regex_alphanumeric = re.compile(r'^[a-z0-9_]+$')
regex_object_name = re.compile(r'^[a-z0-9_.]+$')
regex_pg_name = re.compile(r'^[a-z_][a-z0-9_$]*$', re.IGNORECASE)
# match private methods, to prevent their remote invocation
regex_private = re.compile(r'^(_.*|init)$')
# types handled as collections
COLLECTION_TYPES = (list, tuple, AbstractSet)
# The hard-coded super-user id (a.k.a. root user, or OdooBot).
SUPERUSER_ID = 1
# _read_group stuff
READ_GROUP_TIME_GRANULARITY = {
'hour': dateutil.relativedelta.relativedelta(hours=1),
'day': dateutil.relativedelta.relativedelta(days=1),
'week': dateutil.relativedelta.relativedelta(days=7),
'month': dateutil.relativedelta.relativedelta(months=1),
'quarter': dateutil.relativedelta.relativedelta(months=3),
'year': dateutil.relativedelta.relativedelta(years=1)
}
READ_GROUP_NUMBER_GRANULARITY = {
'year_number': 'year',
'quarter_number': 'quarter',
'month_number': 'month',
'iso_week_number': 'week', # ISO week number because anything else than ISO is nonsense
'day_of_year': 'doy',
'day_of_month': 'day',
'day_of_week': 'dow',
'hour_number': 'hour',
'minute_number': 'minute',
'second_number': 'second',
}
READ_GROUP_ALL_TIME_GRANULARITY = READ_GROUP_TIME_GRANULARITY | READ_GROUP_NUMBER_GRANULARITY
# SQL operators with spaces around them
# hardcoded to avoid changing SQL injection linting
SQL_OPERATORS = {
"=": SQL(" = "),
"!=": SQL(" != "),
"in": SQL(" IN "),
"not in": SQL(" NOT IN "),
"<": SQL(" < "),
">": SQL(" > "),
"<=": SQL(" <= "),
">=": SQL(" >= "),
"like": SQL(" LIKE "),
"ilike": SQL(" ILIKE "),
"=like": SQL(" LIKE "),
"=ilike": SQL(" ILIKE "),
"not like": SQL(" NOT LIKE "),
"not ilike": SQL(" NOT ILIKE "),
"not =like": SQL(" NOT LIKE "),
"not =ilike": SQL(" NOT ILIKE "),
}
def check_method_name(name):
""" Raise an ``AccessError`` if ``name`` is a private method name. """
warnings.warn("Since 19.0, use odoo.service.model.get_public_method", DeprecationWarning)
if regex_private.match(name):
raise AccessError('Private methods (such as %s) cannot be called remotely.' % name)
def check_object_name(name):
""" Check if the given name is a valid model name.
The _name attribute in osv and osv_memory object is subject to
some restrictions. This function returns True or False whether
the given name is allowed or not.
TODO: this is an approximation. The goal in this approximation
is to disallow uppercase characters (in some places, we quote
table/column names and in other not, which leads to this kind
of errors:
psycopg2.ProgrammingError: relation "xxx" does not exist).
The same restriction should apply to both osv and osv_memory
objects for consistency.
"""
return regex_object_name.match(name) is not None
def check_pg_name(name):
""" Check whether the given name is a valid PostgreSQL identifier name. """
if not regex_pg_name.match(name):
raise ValidationError("Invalid characters in table name %r" % name)
if len(name) > 63:
raise ValidationError("Table name %r is too long" % name)
def parse_field_expr(field_expr: str) -> tuple[str, str | None]:
if (property_index := field_expr.find(".")) >= 0:
property_name = field_expr[property_index + 1:]
field_expr = field_expr[:property_index]
else:
property_name = None
if not field_expr:
raise ValueError(f"Invalid field expression {field_expr!r}")
return field_expr, property_name
def expand_ids(id0, ids):
""" Return an iterator of unique ids from the concatenation of ``[id0]`` and
``ids``, and of the same kind (all real or all new).
"""
yield id0
seen = {id0}
kind = bool(id0)
for id_ in ids:
if id_ not in seen and bool(id_) == kind:
yield id_
seen.add(id_)
class OriginIds:
""" A reversible iterable returning the origin ids of a collection of ``ids``.
Actual ids are returned as is, and ids without origin are not returned.
"""
__slots__ = ['ids']
def __init__(self, ids):
self.ids = ids
def __iter__(self):
for id_ in self.ids:
if id_ := id_ or getattr(id_, 'origin', None):
yield id_
def __reversed__(self):
for id_ in reversed(self.ids):
if id_ := id_ or getattr(id_, 'origin', None):
yield id_
origin_ids = OriginIds