您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

176 行
5.2 KiB

  1. # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
  2. # License: MIT. See LICENSE
  3. import json
  4. import os
  5. from textwrap import dedent
  6. import frappe
  7. import frappe.model.sync
  8. import frappe.modules.patch_handler
  9. import frappe.translate
  10. from frappe.cache_manager import clear_global_cache
  11. from frappe.core.doctype.language.language import sync_languages
  12. from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs
  13. from frappe.database.schema import add_column
  14. from frappe.desk.notifications import clear_notifications
  15. from frappe.modules.patch_handler import PatchType
  16. from frappe.modules.utils import sync_customizations
  17. from frappe.search.website_search import build_index_for_all_routes
  18. from frappe.utils.connections import check_connection
  19. from frappe.utils.dashboard import sync_dashboards
  20. from frappe.utils.fixtures import sync_fixtures
  21. from frappe.website.utils import clear_website_cache
  22. BENCH_START_MESSAGE = dedent(
  23. """
  24. Cannot run bench migrate without the services running.
  25. If you are running bench in development mode, make sure that bench is running:
  26. $ bench start
  27. Otherwise, check the server logs and ensure that all the required services are running.
  28. """
  29. )
  30. def atomic(method):
  31. def wrapper(*args, **kwargs):
  32. try:
  33. ret = method(*args, **kwargs)
  34. frappe.db.commit()
  35. return ret
  36. except Exception:
  37. frappe.db.rollback()
  38. raise
  39. return wrapper
  40. class SiteMigration:
  41. """Migrate all apps to the current version, will:
  42. - run before migrate hooks
  43. - run patches
  44. - sync doctypes (schema)
  45. - sync dashboards
  46. - sync jobs
  47. - sync fixtures
  48. - sync customizations
  49. - sync languages
  50. - sync web pages (from /www)
  51. - run after migrate hooks
  52. """
  53. def __init__(self, skip_failing: bool = False, skip_search_index: bool = False) -> None:
  54. self.skip_failing = skip_failing
  55. self.skip_search_index = skip_search_index
  56. def setUp(self):
  57. """Complete setup required for site migration
  58. """
  59. frappe.flags.touched_tables = set()
  60. self.touched_tables_file = frappe.get_site_path("touched_tables.json")
  61. add_column(doctype="DocType", column_name="migration_hash", fieldtype="Data")
  62. clear_global_cache()
  63. if os.path.exists(self.touched_tables_file):
  64. os.remove(self.touched_tables_file)
  65. frappe.flags.in_migrate = True
  66. def tearDown(self):
  67. """Run operations that should be run post schema updation processes
  68. This should be executed irrespective of outcome
  69. """
  70. frappe.translate.clear_cache()
  71. clear_website_cache()
  72. clear_notifications()
  73. with open(self.touched_tables_file, "w") as f:
  74. json.dump(list(frappe.flags.touched_tables), f, sort_keys=True, indent=4)
  75. if not self.skip_search_index:
  76. print(f"Building search index for {frappe.local.site}")
  77. build_index_for_all_routes()
  78. frappe.publish_realtime("version-update")
  79. frappe.flags.touched_tables.clear()
  80. frappe.flags.in_migrate = False
  81. @atomic
  82. def pre_schema_updates(self):
  83. """Executes `before_migrate` hooks
  84. """
  85. for app in frappe.get_installed_apps():
  86. for fn in frappe.get_hooks("before_migrate", app_name=app):
  87. frappe.get_attr(fn)()
  88. @atomic
  89. def run_schema_updates(self):
  90. """Run patches as defined in patches.txt, sync schema changes as defined in the {doctype}.json files
  91. """
  92. frappe.modules.patch_handler.run_all(skip_failing=self.skip_failing, patch_type=PatchType.pre_model_sync)
  93. frappe.model.sync.sync_all()
  94. frappe.modules.patch_handler.run_all(skip_failing=self.skip_failing, patch_type=PatchType.post_model_sync)
  95. @atomic
  96. def post_schema_updates(self):
  97. """Execute pending migration tasks post patches execution & schema sync
  98. This includes:
  99. * Sync `Scheduled Job Type` and scheduler events defined in hooks
  100. * Sync fixtures & custom scripts
  101. * Sync in-Desk Module Dashboards
  102. * Sync customizations: Custom Fields, Property Setters, Custom Permissions
  103. * Sync Frappe's internal language master
  104. * Sync Portal Menu Items
  105. * Sync Installed Applications Version History
  106. * Execute `after_migrate` hooks
  107. """
  108. sync_jobs()
  109. sync_fixtures()
  110. sync_dashboards()
  111. sync_customizations()
  112. sync_languages()
  113. frappe.get_single("Portal Settings").sync_menu()
  114. frappe.get_single("Installed Applications").update_versions()
  115. for app in frappe.get_installed_apps():
  116. for fn in frappe.get_hooks("after_migrate", app_name=app):
  117. frappe.get_attr(fn)()
  118. def required_services_running(self) -> bool:
  119. """Returns True if all required services are running. Returns False and prints
  120. instructions to stdout when required services are not available.
  121. """
  122. service_status = check_connection(redis_services=["redis_cache"])
  123. are_services_running = all(service_status.values())
  124. if not are_services_running:
  125. for service in service_status:
  126. if not service_status.get(service, True):
  127. print(f"Service {service} is not running.")
  128. print(BENCH_START_MESSAGE)
  129. return are_services_running
  130. def run(self, site: str):
  131. """Run Migrate operation on site specified. This method initializes
  132. and destroys connections to the site database.
  133. """
  134. if not self.required_services_running():
  135. raise SystemExit(1)
  136. if site:
  137. frappe.init(site=site)
  138. frappe.connect()
  139. self.setUp()
  140. try:
  141. self.pre_schema_updates()
  142. self.run_schema_updates()
  143. finally:
  144. self.post_schema_updates()
  145. self.tearDown()
  146. frappe.destroy()