mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 07:32:08 +02:00
19.0 vanilla
This commit is contained in:
parent
0a7ae8db93
commit
991d2234ca
416 changed files with 646602 additions and 300844 deletions
205
odoo-bringout-oca-ocb-base/odoo/orm/table_objects.py
Normal file
205
odoo-bringout-oca-ocb-base/odoo/orm/table_objects.py
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
from odoo.tools import sql
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
import psycopg2.extensions
|
||||
|
||||
from .environments import Environment
|
||||
from .models import BaseModel
|
||||
from .registry import Registry
|
||||
|
||||
ConstraintMessageType = (
|
||||
str
|
||||
| Callable[[Environment, psycopg2.extensions.Diagnostics | None], str]
|
||||
)
|
||||
IndexDefinitionType = (
|
||||
str
|
||||
| Callable[[Registry], str]
|
||||
)
|
||||
|
||||
|
||||
class TableObject:
|
||||
""" Declares a SQL object related to the model.
|
||||
|
||||
The identifier of the SQL object will be "{model._table}_{name}".
|
||||
"""
|
||||
name: str
|
||||
message: ConstraintMessageType = ''
|
||||
_module: str = ''
|
||||
|
||||
def __init__(self):
|
||||
"""Abstract SQL object"""
|
||||
# to avoid confusion: name is unique inside the model, full_name is in the database
|
||||
self.name = ''
|
||||
|
||||
def __set_name__(self, owner, name):
|
||||
# database objects should be private member fo the class:
|
||||
# first of all, you should not need to access them from any model
|
||||
# and this avoid having them in the middle of the fields when listing members
|
||||
assert name.startswith('_'), "Names of SQL objects in a model must start with '_'"
|
||||
assert not name.startswith(f"_{owner.__name__}__"), "Names of SQL objects must not be mangled"
|
||||
self.name = name[1:]
|
||||
if getattr(owner, 'pool', None) is None: # models.is_model_definition(owner)
|
||||
# only for fields on definition classes, not registry classes
|
||||
self._module = owner._module
|
||||
owner._table_object_definitions.append(self)
|
||||
|
||||
def get_definition(self, registry: Registry) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def full_name(self, model: BaseModel) -> str:
|
||||
assert self.name, f"The table object is not named ({self.definition})"
|
||||
name = f"{model._table}_{self.name}"
|
||||
return sql.make_identifier(name)
|
||||
|
||||
def get_error_message(self, model: BaseModel, diagnostics=None) -> str:
|
||||
"""Build an error message for the object/constraint.
|
||||
|
||||
:param model: Optional model on which the constraint is defined
|
||||
:param diagnostics: Optional diagnostics from the raised exception
|
||||
:return: Translated error for the user
|
||||
"""
|
||||
message = self.message
|
||||
if callable(message):
|
||||
return message(model.env, diagnostics)
|
||||
return message
|
||||
|
||||
def apply_to_database(self, model: BaseModel):
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"({self.name!r}={self.definition!r}, {self.message!r})"
|
||||
|
||||
|
||||
class Constraint(TableObject):
|
||||
""" SQL table constraint.
|
||||
|
||||
The definition of the constraint is used to `ADD CONSTRAINT` on the table.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
definition: str,
|
||||
message: ConstraintMessageType = '',
|
||||
) -> None:
|
||||
""" SQL table containt.
|
||||
|
||||
The definition is the SQL that will be used to add the constraint.
|
||||
If the constraint is violated, we will show the message to the user
|
||||
or an empty string to get a default message.
|
||||
|
||||
Examples of constraint definitions:
|
||||
- CHECK (x > 0)
|
||||
- FOREIGN KEY (abc) REFERENCES some_table(id)
|
||||
- UNIQUE (user_id)
|
||||
"""
|
||||
super().__init__()
|
||||
self._definition = definition
|
||||
if message:
|
||||
self.message = message
|
||||
|
||||
def get_definition(self, registry: Registry):
|
||||
return self._definition
|
||||
|
||||
def apply_to_database(self, model: BaseModel):
|
||||
cr = model.env.cr
|
||||
conname = self.full_name(model)
|
||||
definition = self.get_definition(model.pool)
|
||||
current_definition = sql.constraint_definition(cr, model._table, conname)
|
||||
if current_definition == definition:
|
||||
return
|
||||
|
||||
if current_definition:
|
||||
# constraint exists but its definition may have changed
|
||||
sql.drop_constraint(cr, model._table, conname)
|
||||
|
||||
model.pool.post_constraint(
|
||||
cr, lambda cr: sql.add_constraint(cr, model._table, conname, definition), conname)
|
||||
|
||||
|
||||
class Index(TableObject):
|
||||
""" Index on the table.
|
||||
|
||||
``CREATE INDEX ... ON model_table <your definition>``.
|
||||
"""
|
||||
unique: bool = False
|
||||
|
||||
def __init__(self, definition: IndexDefinitionType):
|
||||
""" Index in SQL.
|
||||
|
||||
The name of the SQL object will be "{model._table}_{key}". The definition
|
||||
is the SQL that will be used to create the constraint.
|
||||
|
||||
Example of definition:
|
||||
- (group_id, active) WHERE active IS TRUE
|
||||
- USING btree (group_id, user_id)
|
||||
"""
|
||||
super().__init__()
|
||||
self._index_definition = definition
|
||||
|
||||
def get_definition(self, registry: Registry):
|
||||
if callable(self._index_definition):
|
||||
definition = self._index_definition(registry)
|
||||
else:
|
||||
definition = self._index_definition
|
||||
if not definition:
|
||||
return ''
|
||||
return f"{'UNIQUE ' if self.unique else ''}INDEX {definition}"
|
||||
|
||||
def apply_to_database(self, model: BaseModel):
|
||||
cr = model.env.cr
|
||||
conname = self.full_name(model)
|
||||
definition = self.get_definition(model.pool)
|
||||
db_definition, db_comment = sql.index_definition(cr, conname)
|
||||
if db_comment == definition or (not db_comment and db_definition):
|
||||
# keep when the definition matches the comment in the database
|
||||
# or if we have an index without a comment (this is used by support to tweak indexes)
|
||||
return
|
||||
|
||||
if db_definition:
|
||||
# constraint exists but its definition may have changed
|
||||
sql.drop_index(cr, conname, model._table)
|
||||
|
||||
if callable(self._index_definition):
|
||||
definition_clause = self._index_definition(model.pool)
|
||||
else:
|
||||
definition_clause = self._index_definition
|
||||
if not definition_clause:
|
||||
# Don't create index with an empty definition
|
||||
return
|
||||
model.pool.post_constraint(cr, lambda cr: sql.add_index(
|
||||
cr,
|
||||
conname,
|
||||
model._table,
|
||||
comment=definition,
|
||||
definition=definition_clause,
|
||||
unique=self.unique,
|
||||
), conname)
|
||||
|
||||
|
||||
class UniqueIndex(Index):
|
||||
""" Unique index on the table.
|
||||
|
||||
``CREATE UNIQUE INDEX ... ON model_table <your definition>``.
|
||||
"""
|
||||
unique = True
|
||||
|
||||
def __init__(self, definition: IndexDefinitionType, message: ConstraintMessageType = ''):
|
||||
""" Unique index in SQL.
|
||||
|
||||
The name of the SQL object will be "{model._table}_{key}". The definition
|
||||
is the SQL that will be used to create the constraint.
|
||||
You can also specify a message to be used when constraint is violated.
|
||||
|
||||
Example of definition:
|
||||
- (group_id, active) WHERE active IS TRUE
|
||||
- USING btree (group_id, user_id)
|
||||
"""
|
||||
super().__init__(definition)
|
||||
if message:
|
||||
self.message = message
|
||||
Loading…
Add table
Add a link
Reference in a new issue