Files
odoo-addons/addons/cetmix_tower_server/models/cx_tower_file_template.py

229 lines
7.9 KiB
Python

# 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