# coding=utf-8 import traceback from contextlib import suppress from ppms import ppms from ppms.dimmer import Dimmer from ppms.migration import SQLPacket from ppms.text_constant import get_text_constant from .applicator import ApplicatorManager, PosRefresherApplicator from .conflict import ConflictManager from .constants import TableSuffixes from .constants import get_reference_suffix, get_customer_suffix, get_future_suffix from .difference import DiffManager, DifferenceRecorder, PosDifferenceRecorder from .modification import ModificationManager from .schema import get_update_relevant_tables from .venus_on_earth import copy_uuids_on_primary_key_match_from_table_name __all__ = ['UpdateManager'] class UpdateManager(object): def __init__(self): self.applicator_manager = ApplicatorManager() self.modification_manager = ModificationManager() self.conflict_manager = ConflictManager() self._all_modifications = None def generate_icou_data(self, venus_on_earth=False, dont_wipe=False): # Wipe any migration differences that could have come from previous updates self._wipe_migration_differences() # Determine changes made by the current update self._find_migration_differences(venus_on_earth) # Determine changes the customer made # comment this out to test pos only self._find_differences(venus_on_earth) self._find_pos_differences(venus_on_earth) if dont_wipe: return # Wipe the changes made by the current update self._wipe_migration_differences() self.cleanup_copied_tables() self._copy_all_fut_to_ref() def get_missing_tables_necessary_for_icou(self): tables = self.applicator_manager.get_tables_with_configured_applicators() suffixes = [TableSuffixes.REFERENCE, TableSuffixes.BEFORE_MIGRATION, TableSuffixes.BEFORE_DB, TableSuffixes.AFTER_DB, TableSuffixes.AFTER_MIGRATION] missing_tables = [] for table in tables: for suffix in suffixes: table_with_suffix = f'{table}{suffix}' if not SQLPacket.table_exists(table_with_suffix): missing_tables.append(table_with_suffix) return sorted(missing_tables) def apply_icou_data(self, conflicts): focused_module = ppms.get_focused_module() with Dimmer(text=get_text_constant('002191', default='Applying In Case Of Update rules')): self._all_modifications = [] for conflict in conflicts.values(): for modification in conflict.modifications: self._all_modifications.append(self.modification_manager.modifications[modification].record_uuid) for index, conflict in enumerate(conflicts.values()): modifications = [self.modification_manager.modifications.get(uuid) for uuid in conflict.modifications] if len(modifications) > 1: # get applicator and feed it with the conflicts - then get the uuids from the objects and you can run # TODO: Check if the applicators configured for all modifications and if it matches instantiate it raise NotImplementedError('Not yet!!') if focused_module is not None: statusbar_text = get_text_constant('002192', default='Applying rule {index} of {amount}.') focused_module.set_statusbar(statusbar_text.format(index=index + 1, amount=len(conflicts))) # No point in implementing a loop yet, as the multi-modification way has to work differently modification = modifications[0] self._apply_applicator_on_single_modification(conflict=conflict, modification=modification) if focused_module is not None: focused_module.clear_statusbar() def apply_pos_icou(self, conflicts): with Dimmer(text=get_text_constant('002191', default='Applying In Case Of Update rules')): for conflict in conflicts.values(): self.apply_single_pos_icou(conflict) def apply_single_pos_icou(self, conflict): conflict_uuid = conflict.uuid applicator = PosRefresherApplicator(conflict_uuid) success = applicator.apply() conflict.resolved = success conflict.save() def _apply_applicator_on_single_modification(self, conflict, modification): applicator_cls = self.applicator_manager.get_applicator(datatable=modification.datatable, dataitem=modification.dataitem) if applicator_cls is None: return conflict.description = '' applicator = applicator_cls(modification=modification, conflict=conflict) record = self._get_record_from_modification(datatable=modification.datatable, modifications=[modification]) if record is None: text = get_text_constant('002193', default='The record could not be found.') conflict.log(text) conflict.save() return try: success = applicator.apply(record=record) except Exception as exception: success = False # exception = traceback.format_exc().replace(r'\n', '\n').replace(r'\t', '\t') # ppms.ui_message_box(traceback.format_exc().replace(r'\n', '\n').replace(r'\t', '\t')) text = get_text_constant('002194', default='Unhandled exception:\n{exception}') conflict.log(text.format(exception=exception)) conflict.resolved = success conflict.save() def _get_record_from_modification(self, datatable, modifications): if self._all_modifications is None: uuid_to_pk = self.modification_manager.get_uuid_to_pk_mapping(datatable_id=datatable) else: uuid_to_pk = self.modification_manager.get_limited_uuid_to_pk_mapping(datatable_id=datatable, uuids=tuple(self._all_modifications)) dataitems = [int(modification.dataitem) for modification in modifications] # They should all be from the same record primary_key = uuid_to_pk.get(modifications[0].record_uuid, None) if primary_key is None: return None return ppms.search_record(int(datatable), primary_key, dataitems) def _find_migration_differences(self, venus_on_earth): # TODO: This doesn't even complain when a table is missing :-| text = get_text_constant('002195', default='Searching for modified customizing') focused_module = ppms.get_focused_module() with Dimmer(text=text): tables = self.applicator_manager.get_tables_with_configured_applicators() amount = len(tables) for index, table in enumerate(tables): if focused_module is not None: text = get_text_constant('002196', default='Table {index} of {amount} is being analyzed.') focused_module.set_statusbar(text.format(index=index + 1, amount=amount)) _find_and_record_migration_differences(table_name=table, reference_suffix=TableSuffixes.BEFORE_MIGRATION, customer_suffix=TableSuffixes.BEFORE_DB, venus_on_earth=venus_on_earth) _find_and_record_migration_differences(table_name=table, reference_suffix=TableSuffixes.AFTER_DB, customer_suffix=TableSuffixes.AFTER_MIGRATION, venus_on_earth=venus_on_earth) if focused_module is not None: focused_module.clear_statusbar() def _find_differences(self, venus_on_earth): """dont break old code""" self._find_default_differences(venus_on_earth) def _find_default_differences(self, venus_on_earth): self._find_differences_with_class(recorder_class=DifferenceRecorder, venus_on_earth=venus_on_earth) def _find_pos_differences(self, venus_on_earth): self._find_differences_with_class(recorder_class=PosDifferenceRecorder, venus_on_earth=venus_on_earth) def _find_differences_with_class(self, recorder_class, venus_on_earth): text = get_text_constant('002195', default='Searching for modified customizing') focused_module = ppms.get_focused_module() with Dimmer(text=text): recorder = recorder_class() # Delete all modifications that already exist that don't want the customer value # These are leftover unconfigured conflicts or conflicts where the user defined that he # wants the PLANTA value. No point in keeping them around recorder.delete_modifications_without_individual_conflict_solution() tables = self.applicator_manager.get_tables_with_configured_applicators() amount = len(tables) for index, table in enumerate(tables): if focused_module is not None: text = get_text_constant('002196', default='Table {index} of {amount} is being analyzed.') focused_module.set_statusbar(text.format(index=index + 1, amount=amount)) diff_manager = _get_diff_manager_and_fill_modified_rows(table=table, venus_on_earth=venus_on_earth) if diff_manager is None: continue # This will create both the modification and conflict recorder.record_differences(diff_manager) if focused_module is not None: focused_module.clear_statusbar() def _copy_all_fut_to_ref(self): update_relevant_tables = get_update_relevant_tables() with Dimmer("") as dimmer: for _, table_name in update_relevant_tables: dimmer.text = f"Now copying {table_name}" with suppress(ppms.DatabaseError): self._copy_fut_into_ref(table_name=table_name) def _copy_fut_into_ref(self, table_name): sql_statement = ppms.get_query('001155') suffix = TableSuffixes.REFERENCE ref_table = table_name + suffix sql_statement = sql_statement.format(source=table_name, dest=ref_table) ppms.db_modify(sql_statement) constraint_name = f'{table_name}{suffix}_UUID_PK' constraint_query = f"ALTER TABLE {ref_table} ADD CONSTRAINT {constraint_name} PRIMARY KEY (UUID)" ppms.db_modify(constraint_query) def _wipe_migration_differences(self): """Wipe all modifications that have been determined to come from the migration process""" query = "DELETE MODIFICATION WHERE CHANGED_BY_MIGRATION = 1" ppms.db_modify(query) def cleanup_copied_tables(self): """Drops all tables related to ICOU - uses the same query as server""" query = ppms.get_query('001157') ppms.db_modify(query) def _find_and_record_migration_differences(table_name, reference_suffix, customer_suffix, venus_on_earth=False): dt_id = table_name[2:] if venus_on_earth: copy_uuids_on_primary_key_match_from_table_name(source_table_name=table_name, target_table_sql_id=table_name + reference_suffix) copy_uuids_on_primary_key_match_from_table_name(source_table_name=table_name, target_table_sql_id=table_name + customer_suffix) diff_manager = DiffManager(datatable_id=dt_id, reference_table=table_name + reference_suffix, customer_table=table_name + customer_suffix, future_table=None) diff_manager.wipe_modified_rows_table() diff_manager.fill_modified_rows_table() recorder = DifferenceRecorder() recorder.record_differences(diff_manager, changed_by_migration=True) diff_manager.wipe_modified_rows_table() def _get_diff_manager_and_fill_modified_rows(table, venus_on_earth=False): dt_id = table[2:] table_record = ppms.search_record(415, [dt_id], ['variable_name']) if not table_record: return None sql_id = table_record.variable_name.get_value() reference_table = sql_id + get_reference_suffix() customer_table = sql_id + get_customer_suffix() future_table = sql_id + get_future_suffix() if not SQLPacket.table_exists(future_table): future_table = None if not SQLPacket.table_exists(reference_table) or not SQLPacket.table_exists(customer_table): return None if venus_on_earth and future_table is not None: copy_uuids_on_primary_key_match_from_table_name(source_table_name=future_table, target_table_sql_id=reference_table) copy_uuids_on_primary_key_match_from_table_name(source_table_name=future_table, target_table_sql_id=customer_table) diff_manager = DiffManager(datatable_id=dt_id, reference_table=reference_table, customer_table=customer_table, future_table=future_table) diff_manager.wipe_modified_rows_table() diff_manager.fill_modified_rows_table() return diff_manager