You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

168 lines
5.2KB

  1. # Copyright (C) 2020 - Today: Iván Todorovich
  2. # Copyright (C) 2020 - Today: Simone Rubino
  3. # @author: Iván Todorovich (https://twitter.com/ivantodorovich)
  4. # @author: Simone Rubino (https://github.com/Daemo00)
  5. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  6. import re
  7. from pathlib import Path
  8. import lxml.etree as et
  9. from odoo_module_migrate.base_migration_script import BaseMigrationScript
  10. def src_model_new_value(field_elem, model_dot_name):
  11. """
  12. Particular behavior for new binding_model_id.
  13. Apply some heuristic to change
  14. old attribute `src_model=account.move`
  15. to `<field name="binding_model_id" ref="account.model_account_move"/>`.
  16. """
  17. module_name = model_dot_name.split('.')[0]
  18. model_und_name = model_dot_name.replace('.', '_')
  19. ref_value = f'{module_name}.model_{model_und_name}'
  20. field_elem.set('ref', ref_value)
  21. def value_to_text(field_elem, attr_value):
  22. """Standard behavior: an attribute value is the new fields's text."""
  23. field_elem.text = attr_value
  24. TAG_ATTR_RENAMING = {
  25. 'report': {
  26. 'name': ('report_name', value_to_text),
  27. 'string': ('name', value_to_text),
  28. },
  29. 'act_window': {
  30. 'src_model': ('binding_model_id', src_model_new_value),
  31. }
  32. }
  33. """
  34. Configuration for renaming particular attributes.
  35. The dictionary maps old tag names to their attributes' names.
  36. Each old attribute name is then mapped a tuple containing:
  37. - name of the corresponding new field
  38. - a function that applies the old attribute's value to the new field
  39. """
  40. def _reformat_file(file_path: Path):
  41. """Reformat `file_path`.
  42. Substitute `act_window` and `report` tag with `record` tag.
  43. Note that:
  44. - `id` attribute is kept in the `record` tag;
  45. - in `report` tag:
  46. - `string` has been renamed to `name`;
  47. - `name` has been renamed to `report_name`;
  48. - other attributes are assigned to respective fields
  49. """
  50. parser = et.XMLParser(remove_blank_text=True)
  51. tree = et.parse(str(file_path.resolve()), parser)
  52. root = tree.getroot()
  53. reformat_tags = (*root.findall("act_window"), *root.findall("report"))
  54. if not reformat_tags:
  55. return None
  56. regexp = r"(?P<indent>[ \t]*)" \
  57. r"(?P<tag><{tag_type}[^/]*id=\"{tag_id}\"[^/]*/>)"
  58. new_tags_dict = dict()
  59. for tag in reformat_tags:
  60. tag_regex = regexp.format(
  61. tag_type=tag.tag,
  62. tag_id=tag.attrib['id'],
  63. )
  64. for attrib, value in tag.attrib.items():
  65. if attrib == "id":
  66. continue
  67. tag.attrib.pop(attrib)
  68. attr_renames_dict = TAG_ATTR_RENAMING.get(tag.tag, dict())
  69. new_value_func = value_to_text
  70. if attrib in attr_renames_dict:
  71. new_attr_name, new_value_func = attr_renames_dict.get(attrib)
  72. attrib = new_attr_name
  73. field_elem = et.SubElement(tag, "field", {"name": attrib})
  74. new_value_func(field_elem, value)
  75. tag.attrib["model"] = "ir.actions." + tag.tag
  76. tag.tag = "record"
  77. new_tags_dict[tag_regex] = tag
  78. # Read in the file
  79. xml_file = file_path.read_text()
  80. # Replace the target string
  81. for tag_regex, tag in new_tags_dict.items():
  82. match = re.search(tag_regex, xml_file)
  83. if match:
  84. indent = match.group('indent')
  85. tag_match = match.group('tag')
  86. et.indent(tag, space=indent, level=1)
  87. # Remove trailing newline
  88. tag_string = et.tostring(tag, pretty_print=True)[:-1]
  89. xml_file = re.sub(tag_match, tag_string.decode(), xml_file)
  90. # Write the file out again
  91. file_path.write_text(xml_file)
  92. return file_path
  93. def _get_files(module_path, reformat_file_ext):
  94. """Get files to be reformatted."""
  95. file_paths = list()
  96. if not module_path.is_dir():
  97. raise Exception(f"'{module_path}' is not a directory")
  98. file_paths.extend(module_path.rglob("*" + reformat_file_ext))
  99. return file_paths
  100. def reformat_deprecated_tags(logger,
  101. module_path,
  102. module_name,
  103. manifest_path,
  104. migration_steps,
  105. tools):
  106. """Reformat deprecated tags in XML files.
  107. Deprecated tags are `act_window` and `report`:
  108. they have to be substituted by the `record` tag.
  109. """
  110. reformat_file_ext = ".xml"
  111. file_paths = _get_files(module_path, reformat_file_ext)
  112. logger.debug(f"{reformat_file_ext} files found:\n"
  113. f"{list(map(str, file_paths))}")
  114. reformatted_files = list()
  115. for file_path in file_paths:
  116. reformatted_file = _reformat_file(file_path)
  117. if reformatted_file:
  118. reformatted_files.append(reformatted_file)
  119. logger.debug("Reformatted files:\n"
  120. f"{list(reformatted_files)}")
  121. _TEXT_REPLACES = {
  122. ".js": {
  123. r"tour\.STEPS\.SHOW_APPS_MENU_ITEM":
  124. "tour.stepUtils.showAppsMenuItem()",
  125. r"tour\.STEPS\.TOGGLE_HOME_MENU":
  126. "tour.stepUtils.toggleHomeMenu()",
  127. },
  128. ".py": {
  129. r"\.phantom_js\(": ".browser_js(",
  130. }
  131. }
  132. class MigrationScript(BaseMigrationScript):
  133. _GLOBAL_FUNCTIONS = [reformat_deprecated_tags]
  134. _TEXT_REPLACES = _TEXT_REPLACES
上海开阖软件有限公司 沪ICP备12045867号-1