diff --git a/cetmix_tower_server/models/cx_tower_server_template.py b/cetmix_tower_server/models/cx_tower_server_template.py index 88066fb0..4f0bb837 100644 --- a/cetmix_tower_server/models/cx_tower_server_template.py +++ b/cetmix_tower_server/models/cx_tower_server_template.py @@ -169,6 +169,10 @@ def create_server_from_template(self, template_reference, server_name, **kwargs) `variable_reference`: `variable_value_char` eg: {'branch': 'prod', 'odoo_version': '16.0'} + pick_all_template_variables (bool): This parameter ensures that the server + being created considers existing variables from the template. + If enabled, the template variables will also be included in the server + variables. The default value is True. Returns: cx.tower.server: newly created server record @@ -195,6 +199,10 @@ def _create_new_server(self, name, **kwargs): `variable_reference`: `variable_value_char` eg: {'branch': 'prod', 'odoo_version': '16.0'} + pick_all_template_variables (bool): This parameter ensures that the server + being created considers existing variables from the template. + If enabled, the template variables will also be included in the server + variables. The default value is True. Returns: cx.tower.server: newly created server record @@ -247,7 +255,7 @@ def _get_fields_tower_server(self): "server_log_ids", ] - def _prepare_server_values(self, **kwargs): + def _prepare_server_values(self, pick_all_template_variables=True, **kwargs): """ Prepare the server values to create a new server based on the current template. It reads all fields from the template, copies them, @@ -256,6 +264,10 @@ def _prepare_server_values(self, **kwargs): data. Args: + pick_all_template_variables (bool): This parameter ensures that the server + being created considers existing variables from the template. + If enabled, the template variables will also be included in the server + variables. The default value is True. **kwargs: Additional values to update in the final server record. Returns: @@ -298,76 +310,136 @@ def _prepare_server_values(self, **kwargs): values[field] = new_records - # custom specific variable values + # Handle configuration variables if provided. configuration_variables = kwargs.pop("configuration_variables", None) - line_ids_variables = kwargs.pop("line_ids_variables", None) + configuration_variable_options = kwargs.pop( + "configuration_variable_options", {} + ) + if configuration_variables: # Validate required variables self._validate_required_variables(configuration_variables) - variable_vals_list = [] - variable_obj = self.env["cx.tower.variable"] + # Search for existing variable options. + option_references = list(configuration_variable_options.values()) + existing_options = option_references and self.env[ + "cx.tower.variable.option" + ].search([("reference", "in", option_references)]) + missing_options = list( + set(option_references) + - {option.reference for option in existing_options} + ) - for ( - variable_reference, - variable_value, - ) in configuration_variables.items(): - variable = variable_obj.search( - [("reference", "=", variable_reference)] - ) - if not variable: - variable = variable_obj.create( - { - "name": variable_reference, - } + if missing_options: + # Map variable references to their corresponding + # invalid option references. + missing_options_to_variables = { + var_ref: opt_ref + for var_ref, opt_ref in configuration_variable_options.items() + if opt_ref in missing_options + } + # Generate a detailed error message for invalid variable options. + detailed_message = "\n".join( + _( + "Variable reference '%(var_ref)s' has an invalid " + "option reference '%(opt_ref)s'.", + var_ref=var_ref, + opt_ref=opt_ref, ) - variable_option_id = variable_id = False - - if not variable_value and line_ids_variables: - val_found = next( - ( - v - for v in line_ids_variables.values() - if v.get("variable_reference") == variable_reference - ), - None, + for var_ref, opt_ref in missing_options_to_variables.items() + ) + raise ValidationError( + _( + "Some variable options are invalid:\n%(detailed_message)s", + detailed_message=detailed_message, ) - if val_found: - variable_value = val_found.get("value_char") - variable_option_id = val_found.get("option_id", False) - variable_id = val_found.get("variable_id", False) + ) + + # Map variable options to their IDs. + configuration_variable_options_dict = { + option.variable_id.id: option for option in existing_options + } + + variable_obj = self.env["cx.tower.variable"] + variable_references = list(configuration_variables.keys()) + + # Search for existing variables or create new ones if missing. + exist_variables = variable_obj.search( + [("reference", "in", variable_references)] + ) + missing_references = list( + set(variable_references) + - {variable.reference for variable in exist_variables} + ) + variable_vals_list = [ + {"name": reference} for reference in missing_references + ] + new_variables = variable_obj.create(variable_vals_list) + all_variables = exist_variables | new_variables + + # Build a dictionary {variable: variable_value}. + configuration_variable_dict = { + variable: configuration_variables[variable.reference] + for variable in all_variables + } + + server_variable_vals_list = [] + for variable, variable_value in configuration_variable_dict.items(): + variable_option = configuration_variable_options_dict.get( + variable.id + ) - variable_vals_list.append( + server_variable_vals_list.append( ( 0, 0, { - "variable_id": variable.id or variable_id, - "value_char": variable_value or "", - "option_id": variable_option_id, + "variable_id": variable.id, + "value_char": variable_option + and variable_option.value_char + or variable_value, + "option_id": variable_option and variable_option.id, }, ) ) - # update or add variable values - existing_variable_values = values.get("variable_value_ids", []) - variable_id_to_index = { - cmd[2]["variable_id"]: idx - for idx, cmd in enumerate(existing_variable_values) - if cmd[0] == 0 and "variable_id" in cmd[2] - } + if pick_all_template_variables: + # update or add variable values + existing_variable_values = values.get("variable_value_ids", []) + variable_id_to_index = { + cmd[2]["variable_id"]: idx + for idx, cmd in enumerate(existing_variable_values) + if cmd[0] == 0 and "variable_id" in cmd[2] + } + + # Update exist variable options + for exist_variable_id, index in variable_id_to_index.items(): + option = configuration_variable_options_dict.get( + exist_variable_id + ) + if not option: + continue + existing_variable_values[index][2].update( + { + "option_id": option.id, + "value_char": option.value_char, + } + ) - for new_command in variable_vals_list: - variable_id = new_command[2]["variable_id"] - if variable_id in variable_id_to_index: - idx = variable_id_to_index[variable_id] - # update exist command - existing_variable_values[idx] = new_command - else: - # add new command - existing_variable_values.append(new_command) - - values["variable_value_ids"] = existing_variable_values + # Prepare new command values for server variables + for new_command in server_variable_vals_list: + variable_id = new_command[2]["variable_id"] + if variable_id in variable_id_to_index: + idx = variable_id_to_index[variable_id] + # update exist command + existing_variable_values[idx] = new_command + else: + # add new command + existing_variable_values.append(new_command) + + values["variable_value_ids"] = existing_variable_values + else: + values["variable_value_ids"] = server_variable_vals_list # remove the `id` field to ensure a new record is created # instead of updating the existing one diff --git a/cetmix_tower_server/readme/USAGE.md b/cetmix_tower_server/readme/USAGE.md index 6d738e45..7ddd6e8e 100644 --- a/cetmix_tower_server/readme/USAGE.md +++ b/cetmix_tower_server/readme/USAGE.md @@ -24,6 +24,11 @@ This function takes the following arguments: 'variable_reference': 'variable_value_char' eg: {'branch': 'prod', 'odoo_version': '16.0'} + - configuration_variable_options (Dict, optional): Custom configuration variable options. + Following format is used: + 'variable_reference': 'option_reference' + eg: + {'language': 'language_variable_option_1'} ``` Here is a short example of an Odoo automated action that creates a new server when a Sales Order is confirmed: diff --git a/cetmix_tower_server/tests/test_server_template.py b/cetmix_tower_server/tests/test_server_template.py index 40523ee3..6c5ffa4f 100644 --- a/cetmix_tower_server/tests/test_server_template.py +++ b/cetmix_tower_server/tests/test_server_template.py @@ -153,6 +153,14 @@ def test_create_server_from_template_action(self): "value_char": "test template url", } ) + # add variable option + variable_url_option = self.VariableOption.create( + { + "name": "localhost", + "value_char": "localhost", + "variable_id": self.variable_url.id, + } + ) # create new server with new variable self.ServerTemplate.create_server_from_template( @@ -165,6 +173,9 @@ def test_create_server_from_template_action(self): self.variable_version.reference: "test server version", "new_variable": "new_value", }, + configuration_variable_options={ + self.variable_url.reference: variable_url_option.reference, + }, ) new_server = self.Server.search([("name", "=", name)]) @@ -189,8 +200,8 @@ def test_create_server_from_template_action(self): ) self.assertEqual( var_url_value.value_char, - "test template url", - "Url variable values should be same as in the template", + variable_url_option.value_char, + "Url variable values should be same as option value", ) var_new_value = new_server.variable_value_ids.filtered( @@ -798,3 +809,70 @@ def test_server_template_variable_values_manager_access_rights(self): self.server_template_sample.message_unsubscribe([self.user_bob.partner_id.id]) with self.assertRaises(AccessError): other_own_variable_value.unlink() + + def test_with_partial_removed_variables_from_wizard(self): + """ + Test that server creation only with specified + variables from wizard and option + """ + # create new variable option + option = self.VariableOption.create( + { + "name": "test", + "value_char": "test", + "variable_id": self.variable_dir.id, + } + ) + + # template with variables + self.server_template_sample.variable_value_ids = [ + ( + 0, + 0, + { + "variable_id": self.variable_path.id, + "value_char": "/var/log", + "required": False, + }, + ), + ( + 0, + 0, + { + "variable_id": self.variable_dir.id, + "option_id": option.id, + "required": False, + }, + ), + ] + + action = self.server_template_sample.action_create_server() + + # Open the wizard and fill in the data + wizard = ( + self.env["cx.tower.server.template.create.wizard"] + .with_context(**action["context"]) + .create( + { + "name": "Server from Template", + "ip_v4_address": "localhost", + "server_template_id": self.server_template_sample.id, + } + ) + ) + + with Form(wizard) as wizard_form: + wizard_form.line_ids.remove(0) + wizard_form.save() + + wizard.action_confirm() + + server = self.server_template_sample.server_ids + self.assertEqual( + len(server.variable_value_ids), 1, "Server variable must be 1!" + ) + self.assertEqual( + server.variable_value_ids.value_char, + option.value_char, + "The variable value must be equal to the value from the option", + ) diff --git a/cetmix_tower_server/wizards/cx_tower_server_template_create_wizard.py b/cetmix_tower_server/wizards/cx_tower_server_template_create_wizard.py index 5ae6a12f..a195b0ce 100644 --- a/cetmix_tower_server/wizards/cx_tower_server_template_create_wizard.py +++ b/cetmix_tower_server/wizards/cx_tower_server_template_create_wizard.py @@ -110,7 +110,12 @@ def _prepare_server_parameters(self): "configuration_variables": { line.variable_reference: line.value_char for line in self.line_ids - } + }, + "configuration_variable_options": { + line.variable_reference: line.option_id.reference + for line in self.line_ids + if line.option_id + }, } ) return res @@ -125,19 +130,9 @@ def action_confirm(self): raise ValidationError(self.missing_required_variables_message) kwargs = self._prepare_server_parameters() - kwargs["line_ids_variables"] = { - line.id: { - "variable_id": line.id, - "variable_reference": line.variable_reference, - "value_char": line.option_id.value_char - if line.option_id - else line.value_char, - "option_id": line.option_id.id if line.option_id else None, - "variable_type": line.variable_type, - } - for line in self.line_ids.wizard_id.line_ids - } - server = self.server_template_id._create_new_server(self.name, **kwargs) + server = self.server_template_id._create_new_server( + self.name, pick_all_template_variables=False, **kwargs + ) action = self.env["ir.actions.actions"]._for_xml_id( "cetmix_tower_server.action_cx_tower_server" )