From 5fd192356fbe9abe982b39bb217087f7f71b405c Mon Sep 17 00:00:00 2001 From: git_admin Date: Mon, 27 Apr 2026 08:43:45 +0000 Subject: [PATCH] Tower: upload cetmix_tower_server 16.0.2.2.9 (via marketplace) --- .../models/cx_tower_file_template.py | 228 ++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 addons/cetmix_tower_server/models/cx_tower_file_template.py diff --git a/addons/cetmix_tower_server/models/cx_tower_file_template.py b/addons/cetmix_tower_server/models/cx_tower_file_template.py new file mode 100644 index 0000000..94b147a --- /dev/null +++ b/addons/cetmix_tower_server/models/cx_tower_file_template.py @@ -0,0 +1,228 @@ +# Copyright (C) 2024 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + +from .cx_tower_file import TEMPLATE_FILE_FIELD_MAPPING + + +class CxTowerFileTemplate(models.Model): + """File template to manage multiple files at once""" + + _name = "cx.tower.file.template" + _inherit = [ + "cx.tower.reference.mixin", + "cx.tower.key.mixin", + "cx.tower.template.mixin", + "cx.tower.access.role.mixin", + "cx.tower.tag.mixin", + ] + _description = "Cetmix Tower File Template" + _order = "name" + + active = fields.Boolean(default=True) + file_name = fields.Char( + help="Default full file name with file type for example: test.txt", + ) + code = fields.Text(string="File content") + server_dir = fields.Char(string="Directory on server") + file_ids = fields.One2many("cx.tower.file", "template_id") + file_count = fields.Integer( + "File(s)", + compute="_compute_file_count", + ) + tag_ids = fields.Many2many( + relation="cx_tower_file_template_tag_rel", + column1="file_template_id", + column2="tag_id", + ) + note = fields.Text(help="This field is used to put some notes regarding template.") + keep_when_deleted = fields.Boolean( + help="File will be kept on server when deleted in Tower", + ) + auto_sync = fields.Boolean( + help="If enabled, files created from this template will have " + "Auto Sync enabled by default. Used only with 'Tower' source.", + ) + file_type = fields.Selection( + selection=lambda self: self.env["cx.tower.file"]._selection_file_type(), + default=lambda self: self.env["cx.tower.file"]._default_file_type(), + required=True, + ) + source = fields.Selection( + [ + ("tower", "Tower"), + ("server", "Server"), + ], + required=True, + default="tower", + ) + variable_ids = fields.Many2many( + comodel_name="cx.tower.variable", + relation="cx_tower_file_template_variable_rel", + column1="file_template_id", + column2="variable_id", + ) + + # ---- Access. Add relation for mixin fields + user_ids = fields.Many2many( + relation="cx_tower_file_template_user_rel", + domain=lambda self: [ + ("groups_id", "in", [self.env.ref("cetmix_tower_server.group_manager").id]) + ], + ) + manager_ids = fields.Many2many( + relation="cx_tower_file_template_manager_rel", + ) + + @classmethod + def _get_depends_fields(cls): + """ + Define dependent fields for computing + `variable_ids` in file template-related models. + + This implementation specifies that the fields `code`, `server_dir`, + and `file_name` are used to compute the + variables associated with a file template. + + Returns: + list: A list of field names (str) representing the dependencies. + + Example: + The following fields trigger recomputation + of `variable_ids`: + - `code`: The template content for the file. + - `server_dir`: The target directory on the + server where the template is applied. + - `file_name`: The name of the generated file. + """ + return ["code", "server_dir", "file_name"] + + # -- Computes + @api.depends("file_ids") + def _compute_file_count(self): + """ + Compute total template files + """ + for template in self: + template.file_count = len(template.file_ids) + + # -- Create/Write/Unlink + def write(self, vals): + """ + Override to update files related with the templates + """ + result = super().write(vals) + if any([field_ in vals for field_ in TEMPLATE_FILE_FIELD_MAPPING]): + for file in self.mapped("file_ids"): + file.write(file._get_file_values_from_related_template()) + return result + + # -- Actions + def action_open_files(self): + """ + Open current template files + """ + action = self.env["ir.actions.actions"]._for_xml_id( + "cetmix_tower_server.cx_tower_file_action" + ) + action["domain"] = [("id", "in", self.file_ids.ids)] + return action + + # -- Business logic + def create_file(self, server, server_dir="", if_file_exists="raise"): + """ + Create a new file using the current template for the selected server. + If the same file already exists, just ignore it or raise an error based on the + parameter. + + :param server: recordset + The server (cx.tower.server) on which the file should be created. This is a + required parameter. + :param if_file_exists: str, optional + Defines the behavior if the file already exists on the server. + :param server_dir: str, optional + The directory on the server where the file should be created. If not set, + the server_dir field of the template will be used. + + :return: cx.tower.file + Returns the newly created file record (cx.tower.file) if the file was + created successfully or if_file_exists is set to "overwrite". + Returns the existing file record if the file already exists + and if_file_exists is set to "skip". + + :raises ValidationError: + If the file already exists on the server if_file_exists is set to "raise". + """ + self.ensure_one() + # Explicit guard against invalid behavior values + valid_behaviors = {"skip", "raise", "overwrite"} + if if_file_exists not in valid_behaviors: + raise ValidationError( + f"Invalid if_file_exists value: {if_file_exists}. " + f"Expected one of {valid_behaviors}." + ) + file_model = self.env["cx.tower.file"] + existing_files = file_model.search( + [ + ("server_id", "=", server.id), + ("source", "=", self.source), + ], + order="id DESC", + ) + existing_dir = server_dir or self.server_dir + + # Render the server directory and file name from the template + variables = list( + set( + self.get_variables_from_code(self.file_name) + + self.get_variables_from_code(existing_dir) + ) + ) + var_vals = server.get_variable_values(variables).get(server.id) or {} + + unrendered_path = ( + f"{existing_dir}/{self.file_name}" if existing_dir else self.file_name + ) + rendered_path = self.render_code_custom(unrendered_path, **var_vals) + + # Filter existing files by rendered path + existing_files = existing_files.filtered( + lambda f: f.full_server_path == rendered_path + ) + + # Filter existing files by template if it exists, otherwise take the first one + existing_file = ( + existing_files.filtered(lambda f: f.template_id == self)[:1] + or existing_files[:1] + ) + + if existing_file and if_file_exists == "skip": + return existing_file.with_context(file_creation_skipped=True) + + if existing_file and if_file_exists == "raise": + raise ValidationError(_("File already exists on server.")) + + if existing_file and if_file_exists == "overwrite": + existing_file.with_context(is_custom_server_dir=True).write( + { + "template_id": self.id, + } + ) + return existing_file + + vals = { + "name": self.file_name, + "server_id": server.id, + "server_dir": existing_dir, + "template_id": self.id, + "code": self.code, + "file_type": self.file_type, + "source": self.source, + "auto_sync": self.auto_sync, + } + + new_file = file_model.with_context(is_custom_server_dir=True).create(vals) + # Return new_file if no file exists + return new_file