# 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