Tower: upload cetmix_tower_server 16.0.2.2.9 (via marketplace)
This commit is contained in:
209
addons/cetmix_tower_server/models/cx_tower_template_mixin.py
Normal file
209
addons/cetmix_tower_server/models/cx_tower_template_mixin.py
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
# Copyright (C) 2024 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
from jinja2 import Environment, Template, meta
|
||||||
|
from jinja2.exceptions import TemplateSyntaxError
|
||||||
|
|
||||||
|
from odoo import _, api, fields, models
|
||||||
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class CxTowerTemplateMixin(models.AbstractModel):
|
||||||
|
"""Used to implement template rendering functions.
|
||||||
|
Inherit in your model in case you want to render variable values in it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_name = "cx.tower.template.mixin"
|
||||||
|
_description = "Cetmix Tower template rendering mixin"
|
||||||
|
|
||||||
|
code = fields.Text(help="This field will be rendered using variables")
|
||||||
|
variable_ids = fields.Many2many(
|
||||||
|
string="Variables",
|
||||||
|
comodel_name="cx.tower.variable",
|
||||||
|
compute="_compute_variable_ids",
|
||||||
|
store=True,
|
||||||
|
copy=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _get_depends_fields(cls):
|
||||||
|
"""
|
||||||
|
Define dependent fields for the `variable_ids` computation.
|
||||||
|
|
||||||
|
This method should be overridden in inheriting models to provide
|
||||||
|
a list of fields that influence the computation of `variable_ids`.
|
||||||
|
These fields are used in the `@api.depends` decorator to trigger
|
||||||
|
recomputation when their values change.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: A list of field names (str) that are dependencies for
|
||||||
|
the `variable_ids` computation. Default is an empty list.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
In a subclass, override as follows:
|
||||||
|
>>> @classmethod
|
||||||
|
>>> def _get_depends_fields(cls):
|
||||||
|
>>> return ["code", "path"]
|
||||||
|
"""
|
||||||
|
return []
|
||||||
|
|
||||||
|
@api.depends(lambda self: self._get_depends_fields())
|
||||||
|
def _compute_variable_ids(self):
|
||||||
|
"""
|
||||||
|
Compute the values of the `variable_ids`
|
||||||
|
field based on model-specific dependencies.
|
||||||
|
|
||||||
|
This method retrieves the dependent fields using `_get_depends_fields`
|
||||||
|
and dynamically calculates the values of `variable_ids` using the
|
||||||
|
`_prepare_variable_commands` method.
|
||||||
|
|
||||||
|
If no dependent fields or relation parameters are defined, the field
|
||||||
|
is reset to an empty list.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
If dependent fields include `code` and `path`, and the model-specific
|
||||||
|
logic links them to variables, this method will update the `variable_ids`
|
||||||
|
field accordingly.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValidationError: If the field metadata is incorrectly defined or
|
||||||
|
missing required attributes.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None: The field `variable_ids` is updated in-place for each record.
|
||||||
|
"""
|
||||||
|
depends_fields = self._get_depends_fields()
|
||||||
|
|
||||||
|
for record in self:
|
||||||
|
if depends_fields:
|
||||||
|
record.variable_ids = record._prepare_variable_commands(depends_fields)
|
||||||
|
else:
|
||||||
|
record.variable_ids = [(5, 0, 0)]
|
||||||
|
|
||||||
|
def render_code(self, pythonic_mode=False, **kwargs):
|
||||||
|
"""Render record 'code' field using variables from kwargs
|
||||||
|
Call to render recordset of the inheriting models
|
||||||
|
Args:
|
||||||
|
pythonic_mode (Bool): If True, all variables in kwargs are converted to
|
||||||
|
strings and wrapped in double quotes.
|
||||||
|
Default is False.
|
||||||
|
**kwargs (dict): {variable: value, ...}
|
||||||
|
Returns:
|
||||||
|
dict {record_id: rendered_code, ...}
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
rec.id: self.render_code_custom(rec.code, pythonic_mode, **kwargs)
|
||||||
|
for rec in self
|
||||||
|
}
|
||||||
|
|
||||||
|
def render_code_custom(self, code, pythonic_mode=False, **kwargs):
|
||||||
|
"""
|
||||||
|
Render custom code using variables from kwargs
|
||||||
|
|
||||||
|
This method renders a template string (code) using the variables provided
|
||||||
|
in kwargs. If pythonic_mode is enabled, all variables are automatically
|
||||||
|
converted to strings and enclosed in double quotes before rendering.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
code (Text): code to render (eg 'some {{ custom }} text')
|
||||||
|
pythonic_mode (Bool): If True, all variables in kwargs are converted to
|
||||||
|
strings and wrapped in double quotes.
|
||||||
|
Default is False.
|
||||||
|
**kwargs (dict): {variable: value, ...}
|
||||||
|
Returns:
|
||||||
|
rendered_code (text): The resulting string after rendering the template with
|
||||||
|
the provided variables.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if pythonic_mode:
|
||||||
|
kwargs = {
|
||||||
|
key: self._make_value_pythonic(value)
|
||||||
|
for key, value in kwargs.items()
|
||||||
|
}
|
||||||
|
return Template(code, trim_blocks=True).render(kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
raise UserError(str(e)) from e
|
||||||
|
|
||||||
|
def get_variables(self):
|
||||||
|
"""Get the list of variables for templates
|
||||||
|
Call to get variables for recordset of the inheriting models
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict {'record_id': {variables}...}
|
||||||
|
NB: 'record_id' is String
|
||||||
|
"""
|
||||||
|
res = {}
|
||||||
|
for rec in self:
|
||||||
|
res[str(rec.id)] = self.get_variables_from_code(rec.code)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def get_variables_from_code(self, code):
|
||||||
|
"""Get the list of variables for templates
|
||||||
|
Call to get variables from custom code string
|
||||||
|
|
||||||
|
Args:
|
||||||
|
code (Text) custom code (eg 'Custom {{ var }} {{ var2 }} ...')
|
||||||
|
Returns:
|
||||||
|
variables (List) variables (eg ['var','var2',..])
|
||||||
|
"""
|
||||||
|
env = Environment()
|
||||||
|
try:
|
||||||
|
ast = env.parse(code)
|
||||||
|
undeclared_variables = meta.find_undeclared_variables(ast)
|
||||||
|
return list(undeclared_variables) if undeclared_variables else []
|
||||||
|
except TemplateSyntaxError as e:
|
||||||
|
raise ValidationError(_("Variable syntax error: %s", e)) from e
|
||||||
|
|
||||||
|
def _prepare_variable_commands(self, field_names, force_record=None):
|
||||||
|
"""
|
||||||
|
Prepares commands to set variable references from the given fields.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
field_names (list): List of field names to extract variable references from.
|
||||||
|
force_record (record, optional): A record to use instead of the current one.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: An Odoo command to assign or clear variable references.
|
||||||
|
"""
|
||||||
|
record = force_record or self
|
||||||
|
record.ensure_one()
|
||||||
|
|
||||||
|
all_references = set()
|
||||||
|
for field_name in field_names:
|
||||||
|
value = getattr(record, field_name, None)
|
||||||
|
if value:
|
||||||
|
all_references.update(self.get_variables_from_code(value))
|
||||||
|
|
||||||
|
if all_references:
|
||||||
|
variables = self.env["cx.tower.variable"].search(
|
||||||
|
[("reference", "in", list(all_references))]
|
||||||
|
)
|
||||||
|
command = [(6, 0, variables.ids)]
|
||||||
|
else:
|
||||||
|
command = [(5, 0, 0)]
|
||||||
|
|
||||||
|
return command
|
||||||
|
|
||||||
|
def _make_value_pythonic(self, value):
|
||||||
|
"""Prepares value for use in 'pythonic' mode
|
||||||
|
by enclosing strings into double quotes
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (Char): value to process
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Char: processed value
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Nothing to do here
|
||||||
|
if isinstance(value, bool) or value is None:
|
||||||
|
result = value
|
||||||
|
|
||||||
|
# Handle nested dicts such as system variables
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
result = {}
|
||||||
|
for key, val in value.items():
|
||||||
|
result.update({key: self._make_value_pythonic(val)})
|
||||||
|
else:
|
||||||
|
# Enclose in double quotes
|
||||||
|
result = f'"{value}"'
|
||||||
|
return result
|
||||||
Reference in New Issue
Block a user