diff --git a/odoo_module_migrate/base_migration_script.py b/odoo_module_migrate/base_migration_script.py index f42e792c..0ea4a486 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,34 @@ 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: + 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..a620f58c --- /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'_\((["\'].*?%[ds].*?["\'])\)\s*%\s*\(\s*(.+)\s*\)': format_parenthesis, + r'_\((["\'].*?%[ds].*?["\'])\)\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_150_allways.py b/odoo_module_migrate/migration_scripts/migrate_150_allways.py new file mode 100644 index 00000000..3c18eee2 --- /dev/null +++ b/odoo_module_migrate/migration_scripts/migrate_150_allways.py @@ -0,0 +1,28 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo_module_migrate.base_migration_script import BaseMigrationScript + + +def replace_toggle_button( + logger, module_path, module_name, manifest_path, migration_steps, tools +): + files_to_process = tools.get_files(module_path, (".xml",)) + replaces = { + r'widget="\s*toggle_button\s*"': 'widget="boolean_toggle"', + r"widget='\s*toggle_button\s*'": 'widget="boolean_toggle"', + r'\s*toggle_button\s*': 'boolean_toggle', + } + + for file in files_to_process: + try: + tools._replace_in_file( + file, + replaces, + log_message=f"Replace toggle_button widget to boolean_toggle widget in file: {file}", + ) + except Exception as e: + logger.error(f"Error processing file {file}: {str(e)}") + + +class MigrationScript(BaseMigrationScript): + _GLOBAL_FUNCTIONS = [replace_toggle_button] diff --git a/odoo_module_migrate/migration_scripts/migrate_170_180.py b/odoo_module_migrate/migration_scripts/migrate_170_180.py index 930ebf72..df74ba17 100644 --- a/odoo_module_migrate/migration_scripts/migrate_170_180.py +++ b/odoo_module_migrate/migration_scripts/migrate_170_180.py @@ -9,7 +9,7 @@ def replace_tree_with_list_in_views( logger, module_path, module_name, manifest_path, migration_steps, tools ): - files_to_process = tools.get_files(module_path, (".xml", ".js", ".py")) + files_to_process = tools.get_files(module_path, (".xml", ".js", ".py",)) reg_tree_to_list_xml_mode = re.compile( r"""(]* name=["'](view_mode|name|binding_view_types)["'][^>]*>([^<>]+[,.])?\s*)tree(\s*([,.][^<>]+)?)""" @@ -84,6 +84,26 @@ def replace_chatter_self_closing(match): logger.error(f"Error processing file {file}: {str(e)}") +def replace_deprecated_kanban_box_card_menu( + logger, module_path, module_name, manifest_path, migration_steps, tools +): + files_to_process = tools.get_files(module_path, (".xml", ".js", ".py",)) + replaces = { + "kanban-card": "card", + "kanban-box": "card", + "kanban-menu": "menu", + } + for file in files_to_process: + try: + tools._replace_in_file( + file, + replaces, + log_message=f"""Replace kanban-card and kanban-box with card, also change kanban-menu with menu" in file: {file}""", + ) + except Exception as e: + logger.error(f"Error processing file {file}: {str(e)}") + + def replace_user_has_groups( logger, module_path, module_name, manifest_path, migration_steps, tools ): @@ -100,9 +120,240 @@ def replace_user_has_groups( 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: