Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace)

This commit is contained in:
2026-04-27 08:15:56 +00:00
parent bbb71840c1
commit 364f37aa5a

View File

@@ -0,0 +1,474 @@
import logging
from odoo import _, api, fields, models
_logger = logging.getLogger(__name__)
class CxTowerJetTemplateInstall(models.Model):
"""
Used to track installation of Jet Templates on servers.
"""
_name = "cx.tower.jet.template.install"
_description = "Jet Template Install/Uninstall"
_order = "create_date desc"
_rec_name = "jet_template_id"
jet_template_id = fields.Many2one(
comodel_name="cx.tower.jet.template",
required=True,
help="Template to install/uninstall",
)
server_id = fields.Many2one(
comodel_name="cx.tower.server",
index=True,
ondelete="cascade",
required=True,
help="Server to install/uninstall the template on",
)
action = fields.Selection(
selection=[("install", "Install"), ("uninstall", "Uninstall")],
default="install",
)
date_done = fields.Datetime(string="Completed on", readonly=True)
line_ids = fields.One2many(
comodel_name="cx.tower.jet.template.install.line",
inverse_name="jet_template_install_id",
auto_join=True,
string="Templates to install",
help="Complete list of templates to install/uninstall including dependencies",
)
current_line_id = fields.Many2one(
comodel_name="cx.tower.jet.template.install.line",
string="Currently Installing",
help="Line that is currently being installed",
)
state = fields.Selection(
selection=[
("processing", "Processing"),
("done", "Done"),
("failed", "Failed"),
],
default="processing",
index=True,
)
@api.model
def install(self, server, template):
"""Install the template on the server.
Args:
server (cx.tower.server()): The server to install the template on.
template (cx.tower.jet.template()): The template to install.
Returns:
cx.tower.jet.template.install(): The installation record.
"""
server.ensure_one()
template.ensure_one()
# Compose the list of templates to install
# NB: templates will be installed later in reverse order
# to ensure that dependencies are satisfied
template_to_process = [template] + template._check_dependency_satisfaction(
server
)
# Prepare the template install lines
template_to_process_lines = []
order = 0
for t in template_to_process:
template_to_process_lines.append(
(0, 0, {"jet_template_id": t.id, "order": order})
)
order += 1
# Create a new install record
install_record = self.create(
{
"jet_template_id": template.id,
"server_id": server.id,
"line_ids": template_to_process_lines,
}
)
# Send notification
# Action for button
action = self.env["ir.actions.act_window"]._for_xml_id(
"cetmix_tower_server.cx_tower_jet_template_install_action"
)
context = self.env.context.copy()
params = dict(context.get("params") or {})
params["button_name"] = _("View Installation")
context["params"] = params
# Add record id and context to the action
action.update(
{
"context": context,
"res_id": install_record.id,
"views": [(False, "form")],
}
)
self.env.user.notify_info(
message=_(
"%(timestamp)s<br/>" "Installing template on server '%(server_name)s'",
server_name=server.name,
timestamp=fields.Datetime.context_timestamp(
self, fields.Datetime.now()
),
),
title=template.name,
sticky=False, # explicitly set to False to avoid blocking the user's screen
action=action,
)
# Launch the installation
install_record._process_install()
# Return the installation record
return install_record
@api.model
def uninstall(self, server, template):
"""Uninstall the template from the server.
NB: only one template can be uninstalled at a time.
Args:
server (cx.tower.server()): The server to uninstall the template from.
template (cx.tower.jet.template()): The template to uninstall.
"""
server.ensure_one()
template.ensure_one()
# Create a new install record
install_record = self.create(
{
"jet_template_id": template.id,
"server_id": server.id,
"line_ids": [(0, 0, {"jet_template_id": template.id, "order": 0})],
"action": "uninstall",
}
)
# Send notification
# Action for button
action = self.env["ir.actions.act_window"]._for_xml_id(
"cetmix_tower_server.cx_tower_jet_template_install_action"
)
context = self.env.context.copy()
params = dict(context.get("params") or {})
params["button_name"] = _("View Installation")
context["params"] = params
# Add record id and context to the action
action.update(
{
"context": context,
"res_id": install_record.id,
"views": [(False, "form")],
}
)
self.env.user.notify_info(
message=_(
"%(timestamp)s<br/>"
"Uninstalling template on server '%(server_name)s'",
server_name=server.name,
timestamp=fields.Datetime.context_timestamp(
self, fields.Datetime.now()
),
),
title=template.name,
sticky=False, # explicitly set to False to avoid blocking the user's screen
action=action,
)
# Launch the installation
install_record._process_install()
# Return the installation record
return install_record
def _process_install(self):
"""
Process the installation or uninstallation of the template.
"""
self.ensure_one()
# We are not using `while` because flight plans
# may run asynchronously and we don't want to
# block the execution of the function
# Continue only if the job is still processing
if self.state != "processing":
return
# Exit if there are some lines currently being installed
if self.current_line_id:
return
# Get the template to install
installation_tasks = self.line_ids.sorted("order", reverse=True)
for installation_task in installation_tasks:
# Pick the templates only in the "To Process" state
if installation_task.state != "to_process":
continue
# Get the flight plan to install the template
if self.action == "install":
flight_plan = installation_task.jet_template_id.plan_install_id # pylint: disable=no-member
else:
flight_plan = installation_task.jet_template_id.plan_uninstall_id # pylint: disable=no-member
# Run the corresponding flight plan
if flight_plan:
# Update the current template install line
self.write(
{
"current_line_id": installation_task.id,
}
)
# Add the install record to the flight plan params
plan_params = {
"jet_template_install_id": self.id, # pylint: disable=no-member
}
with self.env.cr.savepoint():
# Run the flight plan (exceptions handled inside the flight plan)
self.server_id.run_flight_plan(
flight_plan=flight_plan,
jet_template=installation_task.jet_template_id,
**{"plan_log": plan_params},
)
# Flight plan will trigger the `_process_install` function again
# if the flight plan is finished successfully.
# So we don't need continue the loop in this case.
return
# Mark the installation task as "Done"
# because nothing else is to be done here.
installation_task.write(
{
"state": "done",
}
)
# Add to the list of installed templates
if self.action == "install":
installation_task.jet_template_id.write(
{"server_ids": [(4, self.server_id.id)]}
)
else:
installation_task.jet_template_id.write(
{"server_ids": [(3, self.server_id.id)]}
)
# Refresh the frontend views
self.env.user.reload_views(
model="cx.tower.jet.template.install",
rec_ids=[self.id],
)
# Mark the installation as done
now = fields.Datetime.now()
self.write(
{
"state": "done",
"date_done": now,
}
)
# Refresh the frontend views
self.env.user.reload_views(
model="cx.tower.jet.template.install", rec_ids=[self.id]
)
self.env.user.reload_views(
model="cx.tower.server", view_types=["form"], rec_ids=[self.server_id.id]
)
self.env.user.reload_views(
model="cx.tower.jet.template",
view_types=["form"],
rec_ids=[self.jet_template_id.id],
)
# Check if notifications are enabled
ICP_sudo = self.env["ir.config_parameter"].sudo()
notification_type_success = ICP_sudo.get_param(
"cetmix_tower_server.notification_type_success"
)
# Send notification to the user
if notification_type_success:
# Action for button
action = self.env["ir.actions.act_window"]._for_xml_id(
"cetmix_tower_server.cx_tower_jet_template_install_action"
)
context = self.env.context.copy()
params = dict(context.get("params") or {})
params["button_name"] = _("View Installation")
context["params"] = params
# Add record id and context to the action
action.update(
{
"context": context,
"res_id": self.id,
"views": [(False, "form")],
}
)
# Send success notification
self.env.user.notify_success(
message=_(
"%(timestamp)s<br/>"
"%(action)s completed on server '%(server_name)s'",
action=_("Installation")
if self.action == "install"
else _("Uninstallation"),
server_name=self.server_id.name,
timestamp=fields.Datetime.context_timestamp(self, now),
),
title=self.jet_template_id.name, # pylint: disable=no-member
sticky=notification_type_success == "sticky",
action=action,
)
def _flight_plan_finished(self, plan_status):
"""
Triggered when a flight plan that is used for installing/uninstalling
a template is finished.
Args:
plan_status (int): The exit code of the flight plan.
"""
self.ensure_one()
# Validate callback state
if not self.current_line_id:
_logger.warning(
"Callback invoked with no current_line_id for install %s", self.id
)
return
if self.state != "processing":
_logger.warning(
"Callback invoked for install %s in state %s", self.id, self.state
)
return
# Flight plan finished successfully
if plan_status == 0:
# Mark current line as done
self.current_line_id.write( # pylint: disable=no-member
{
"state": "done",
}
)
# Add template to the list of installed templates
# or remove it from the list if it is being uninstalled
if self.action == "install":
self.current_line_id.jet_template_id.write( # pylint: disable=no-member
{"server_ids": [(4, self.server_id.id)]}
)
else:
self.current_line_id.jet_template_id.write( # pylint: disable=no-member
{"server_ids": [(3, self.server_id.id)]}
)
# Remove the link to the current line and continue
self.write({"current_line_id": False})
# Refresh the frontend views
self.env.user.reload_views(
model="cx.tower.jet.template.install",
rec_ids=[self.id],
)
self._process_install()
else:
# Mark current line as failed
self.current_line_id.write( # pylint: disable=no-member
{
"state": "failed",
}
)
# Clear the current line link
self.write(
{
"state": "failed",
"date_done": fields.Datetime.now(),
"current_line_id": False,
}
)
# Set all other 'to_process' lines as failed
self.line_ids.filtered(lambda line: line.state == "to_process").write(
{
"state": "failed",
}
)
# Refresh the frontend views
self.env.user.reload_views(
model="cx.tower.jet.template.install",
rec_ids=[self.id],
)
# Send notification to the user
# Check if notifications are enabled
ICP_sudo = self.env["ir.config_parameter"].sudo()
notification_type_error = ICP_sudo.get_param(
"cetmix_tower_server.notification_type_error"
)
if notification_type_error:
# Action for button
action = self.env["ir.actions.act_window"]._for_xml_id(
"cetmix_tower_server.cx_tower_jet_template_install_action"
)
context = self.env.context.copy()
params = dict(context.get("params") or {})
params["button_name"] = _("View Installation")
context["params"] = params
# Add record id and context to the action
action.update(
{
"context": context,
"res_id": self.id,
"views": [(False, "form")],
}
)
# Send error notification
self.env.user.notify_danger(
message=_(
"%(timestamp)s<br/>"
"%(action)s failed on server '%(server_name)s'",
action=_("Installation")
if self.action == "install"
else _("Uninstallation"),
server_name=self.server_id.name,
timestamp=fields.Datetime.context_timestamp(
self, fields.Datetime.now()
),
),
title=self.jet_template_id.name,
sticky=notification_type_error == "sticky",
action=action,
)
def action_view_flight_plan_logs(self):
"""Open flight plan logs related to this installation"""
self.ensure_one()
return {
"name": _(
"Flight Plan Logs - %(install_name)s",
install_name=self.jet_template_id.name,
),
"type": "ir.actions.act_window",
"res_model": "cx.tower.plan.log",
"view_mode": "tree,form",
"domain": [("jet_template_install_id", "=", self.id)], # pylint: disable=no-member
}