diff --git a/odoo_module_migrate/base_migration_script.py b/odoo_module_migrate/base_migration_script.py index 2d225c4d..715216fd 100644 --- a/odoo_module_migrate/base_migration_script.py +++ b/odoo_module_migrate/base_migration_script.py @@ -208,10 +208,17 @@ def process_file( replaces.update(self._TEXT_REPLACES.get(extension, {})) replaces.update(renamed_models.get("replaces")) replaces.update(removed_models.get("replaces")) - new_text = tools._replace_in_file( absolute_file_path, replaces, "Change file content of %s" % filename ) + field_renames = renamed_fields.get("replaces") + # To be safe we only rename fields on files associated with the current replaces + if field_renames: + new_text = tools._replace_field_names( + absolute_file_path, + field_renames, + "Updated field names of %s" % filename, + ) # Display errors if the new content contains some obsolete # pattern @@ -260,17 +267,32 @@ def handle_renamed_fields(self, removed_fields): For now this handler is simple but the idea would be to improve it with deeper analysis and direct replaces if it is possible and secure. For that analysis model_name could be used + It also will add to the replaces key of the returned dictionary a key value pair + to be used in _replace_in_file """ - res = {} + res = {"warnings": {}, "replaces": {}} + res["replaces"] = {} for model_name, old_field_name, new_field_name, more_info in removed_fields: + # if model_name in res['replaces']: + # res['replaces'][model_name].update({old_field_name: new_field_name,}) + # else: + model_info = res["replaces"].get(model_name) + if model_info: + model_info.update({old_field_name: new_field_name}) + else: + res["replaces"].update({model_name: {old_field_name: new_field_name}}) msg = "On the model %s, the field %s was renamed to %s.%s" % ( model_name, old_field_name, new_field_name, " %s" % more_info if more_info else "", ) - res[r"""(['"]{0}['"]|\.{0}[\s,=])""".format(old_field_name)] = msg - return {"warnings": res} + res["warnings"].update( + { + r"""(['"]{0}['"]|\.{0}[\s,=])""".format(old_field_name): msg, + } + ) + return res def handle_deprecated_modules(self, manifest_path, deprecated_modules): current_manifest_text = tools._read_content(manifest_path) diff --git a/odoo_module_migrate/migration_scripts/migrate_130_140.py b/odoo_module_migrate/migration_scripts/migrate_130_140.py index e98c2e42..a442277d 100644 --- a/odoo_module_migrate/migration_scripts/migrate_130_140.py +++ b/odoo_module_migrate/migration_scripts/migrate_130_140.py @@ -142,6 +142,36 @@ def reformat_deprecated_tags( logger.debug("Reformatted files:\n" f"{list(reformatted_files)}") +def refactor_action_read(**kwargs): + """ + replace action.read() by _for_xml_id to avoid access rights issue + + ##### case 1: pattern for case action.read[0] right after self.env.ref + ## action = self.env.ref('sale.action_orders') + ## action = action.read()[0] + + ##### case 2: pattern for case having new line between action.read[0] and self.env.ref + ## action = self.env.ref('sale.action_orders') + ## ......... + ## ......... + ## action = action.read()[0] + """ + logger = kwargs["logger"] + tools = kwargs["tools"] + module_path = kwargs["module_path"] + file_paths = _get_files(module_path, ".py") + + old_term = r"action.*= self.env.ref\((.*)\)((\n.+)+?)?(\n.+)(action\.read\(\)\[0\])" + new_term = r'\2\4self.env["ir.actions.act_window"]._for_xml_id(\1)' + for file_path in file_paths: + logger.debug(f"refactor file {file_path}") + tools._replace_in_file( + file_path, + {old_term: new_term}, + log_message="refactor action.read[0] to _for_xml_id", + ) + + _TEXT_REPLACES = { ".js": { r"tour\.STEPS\.SHOW_APPS_MENU_ITEM": "tour.stepUtils.showAppsMenuItem()", @@ -155,5 +185,5 @@ def reformat_deprecated_tags( class MigrationScript(BaseMigrationScript): - _GLOBAL_FUNCTIONS = [reformat_deprecated_tags] + _GLOBAL_FUNCTIONS = [reformat_deprecated_tags, refactor_action_read] _TEXT_REPLACES = _TEXT_REPLACES diff --git a/odoo_module_migrate/migration_scripts/migrate_130_allways.py b/odoo_module_migrate/migration_scripts/migrate_130_allways.py new file mode 100644 index 00000000..99294ac9 --- /dev/null +++ b/odoo_module_migrate/migration_scripts/migrate_130_allways.py @@ -0,0 +1,83 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import re +from odoo_module_migrate.base_migration_script import BaseMigrationScript + + +def multi_value_translation_replacement_function(match, single_quote=True): + format_string = match.group(1) + dictionary_entries = match.group(2) + + formatted_entries = [] + for entry in dictionary_entries.split(","): + if ":" in entry: + [key, value] = entry.split(":") + formatted_entries.append( + "{}={}".format(key.strip().strip("'").strip('"'), value.strip()) + ) + + formatted_entries = ", ".join(formatted_entries) + + if single_quote: + return f"_('{format_string}', {formatted_entries})" + return f'_("{format_string}", {formatted_entries})' + + +def format_parenthesis(match): + format_string = match.group(1) + dictionary_entries = match.group(2) + + if dictionary_entries.endswith(","): + dictionary_entries = dictionary_entries[:-1] + + return f"_({format_string}, {dictionary_entries})" + + +def format_replacement_function(match, single_quote=True): + format_string = re.sub(r"\{\d*\}", "%s", match.group(1)) + format_string = re.sub(r"{(\w+)}", r"%(\1)s", format_string) + arguments = " ".join(match.group(2).split()) + + if arguments.endswith(","): + arguments = arguments[:-1] + + if single_quote: + return f"_('{format_string}', {arguments})" + return f'_("{format_string}", {arguments})' + + +def replace_translation_function( + logger, module_path, module_name, manifest_path, migration_steps, tools +): + files_to_process = tools.get_files(module_path, (".py",)) + + replaces = { + r'_\(\s*"([^"]+)"\s*\)\s*%\s*\{([^}]+)\}': lambda match: multi_value_translation_replacement_function( + match, single_quote=False + ), + r"_\(\s*'([^']+)'\s*\)\s*%\s*\{([^}]+)\}": lambda match: multi_value_translation_replacement_function( + match, single_quote=True + ), + r'_\(\s*(["\'].*?%[ds].*?["\'])\s*\)\s*%\s*\(\s*(.+)\s*\)': format_parenthesis, + r'_\(\s*(["\'].*?%[ds].*?["\'])\s*\)\s*?%\s*?([^\s]+)': r"_(\1, \2)", + r'_\(\s*"([^"]*)"\s*\)\.format\(\s*(\s*[^)]+)\)': lambda match: format_replacement_function( + match, single_quote=False + ), + r"_\(\s*'([^']*)'\s*\)\.format\(\s*(\s*[^)]+)\)": lambda match: format_replacement_function( + match, single_quote=True + ), + } + + for file in files_to_process: + try: + tools._replace_in_file( + file, + replaces, + log_message=f"""Improve _() function: {file}""", + ) + except Exception as e: + logger.error(f"Error processing file {file}: {str(e)}") + + +class MigrationScript(BaseMigrationScript): + + _GLOBAL_FUNCTIONS = [replace_translation_function] diff --git a/odoo_module_migrate/migration_scripts/migrate_160_170.py b/odoo_module_migrate/migration_scripts/migrate_160_170.py index ac0a8688..96421dab 100644 --- a/odoo_module_migrate/migration_scripts/migrate_160_170.py +++ b/odoo_module_migrate/migration_scripts/migrate_160_170.py @@ -261,6 +261,148 @@ def _check_open_form_view(logger, file_path: Path): ) +def _move_attrs_to_attributes_view(logger, file_path: Path): + """Transform + for node in arch.xpath("//*[@attrs]"): + attributes = attrs_to_attributes(node.attrib["attrs"]) + if not attributes: + continue + node.attrib.update(attributes) + del node.attrib["attrs"] + modified = True + # inherited views + for node in arch.xpath("//attribute[@name='attrs']"): + attributes = attrs_to_attributes(node.text) + if not attributes: + continue + parent = node.getparent() + for key, value in attributes.items(): + new_node = et.SubElement(parent, "attribute", name=key) + new_node.text = value + parent.remove(node) + modified = True + + if modified: + tree.write(file_path, xml_declaration=True) + with open(file_path, "r+") as xml_file: + xml_file.write('') + xml_file.seek(0, 2) + xml_file.write("\n") + + def _check_open_form( logger, module_path, module_name, manifest_path, migration_steps, tools ): @@ -272,6 +414,17 @@ def _check_open_form( _check_open_form_view(logger, file_path) +def _move_attrs_to_attributes( + logger, module_path, module_name, manifest_path, migration_steps, tools +): + reformat_file_ext = ".xml" + file_paths = _get_files(module_path, reformat_file_ext) + logger.debug(f"{reformat_file_ext} files found:\n" f"{list(map(str, file_paths))}") + + for file_path in file_paths: + _move_attrs_to_attributes_view(logger, file_path) + + def _reformat_read_group( logger, module_path, module_name, manifest_path, migration_steps, tools ): @@ -291,4 +444,8 @@ def _reformat_read_group( class MigrationScript(BaseMigrationScript): - _GLOBAL_FUNCTIONS = [_check_open_form, _reformat_read_group] + _GLOBAL_FUNCTIONS = [ + _check_open_form, + _reformat_read_group, + _move_attrs_to_attributes, + ] diff --git a/odoo_module_migrate/migration_scripts/migrate_170_180.py b/odoo_module_migrate/migration_scripts/migrate_170_180.py index 330ce75f..96192843 100644 --- a/odoo_module_migrate/migration_scripts/migrate_170_180.py +++ b/odoo_module_migrate/migration_scripts/migrate_170_180.py @@ -169,12 +169,191 @@ def replace_ustr( logger.error(f"Error processing file {file}: {str(e)}") +def replace_slugify( + logger, module_path, module_name, manifest_path, migration_steps, tools +): + files_to_process = tools.get_files(module_path, (".py",)) + + for file in files_to_process: + try: + content = tools._read_content(file) + content = re.sub( + r"from\s+odoo\.addons\.http_routing\.models\.ir_http\s+import\s+slugify\b.*\n", + "", + content, + ) + # process in controller (*.py) file are using request + has_request = "request" in content + if has_request: + content = re.sub( + r"""(?1': 'bottom', + } + for file in files_to_process: + try: + tools._replace_in_file( + file, + replaces, + log_message=f"""Replace editable="1" by "bottom" in file: {file}""", + ) + except Exception as e: + logger.error(f"Error processing file {file}: {str(e)}") + + +def replace_odoo_module_from_js_assets( + logger, module_path, module_name, manifest_path, migration_steps, tools +): + files_to_process = tools.get_files(module_path, (".js",)) + replaces = { + r"/\*\*\s*@odoo-module\s*\*\*/\s*\n?": "", + r"/\*\s*@odoo-module\s*\*/\s*\n?": "", + r"/\*\*\s*@odoo-module\s*\*/\s*\n?": "", + r"/\*\s*@odoo-module\s*\*\*/\s*\n?": "", + } + for file in files_to_process: + file_str = str(file) + if not ("/static/src" in file_str or "/static/tests" in file_str): + continue + try: + tools._replace_in_file( + file, + replaces, + log_message=f"Remove @odoo-module from js assets in file: {file}", + ) + except Exception as e: + logger.error(f"Error processing file {file}: {str(e)}") + + +def remove_deprecated_kanban_click_classes( + logger, module_path, module_name, manifest_path, migration_steps, tools +): + files_to_process = tools.get_files(module_path, (".xml",)) + + replaces = { + "oe_kanban_global_click_edit": "", + "oe_kanban_global_click": "", + } + + for file in files_to_process: + try: + tools._replace_in_file( + file, + replaces, + log_message=f"Remove deprecated kanban click classes in file: {file}", + ) + except Exception as e: + logger.error(f"Error processing file {file}: {str(e)}") + + +def replace_kanban_color_picker_widget( + logger, module_path, module_name, manifest_path, migration_steps, tools +): + files_to_process = tools.get_files(module_path, (".xml",)) + + replaces = { + # Case 1: Match any ul tag containing both oe_kanban_colorpicker class and data-field + # Example: