class Role_Registry(metaclass=Singleton): """ Manage all role information. Current role can also be got/set from this class. Roles can set their own GUI configuration even using and overwriting the template gui config. """ COMMON_ACTIONS = [ # Common actions for all roles ACTION_LOAD_LAYERS, ACTION_SCHEMA_IMPORT, ACTION_IMPORT_DATA, ACTION_EXPORT_DATA, ACTION_SETTINGS, ACTION_HELP, ACTION_ABOUT ] def __init__(self): self.logger = Logger() self._registered_roles = dict() self._default_role = BASIC_ROLE role = BASIC_ROLE template_gui = GUI_Config().get_gui_dict(TEMPLATE_GUI) template_gui[TOOLBAR] = [{ # Overwrite list of toolbars WIDGET_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "LADM-COL tools"), OBJECT_NAME: 'ladm_col_toolbar', ACTIONS: [ { # List of toolbars WIDGET_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "Data management"), OBJECT_NAME: 'ladm_col_data_management_toolbar', ICON: DATA_MANAGEMENT_ICON, ACTIONS: [ ACTION_SCHEMA_IMPORT, ACTION_IMPORT_DATA, ACTION_EXPORT_DATA ] }, SEPARATOR, { WIDGET_TYPE: MENU, WIDGET_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "Create Operation objects"), OBJECT_NAME: "ladm_col_operation_toolbar", ICON: OPERATION_ICON, ACTIONS: [ ACTION_CREATE_POINT, ACTION_CREATE_BOUNDARY, SEPARATOR, ACTION_CREATE_PLOT, ACTION_CREATE_BUILDING, ACTION_CREATE_BUILDING_UNIT, ACTION_CREATE_RIGHT_OF_WAY, ACTION_FILL_RIGHT_OF_WAY_RELATIONS, SEPARATOR, ACTION_CREATE_EXT_ADDRESS, SEPARATOR, ACTION_CREATE_PARCEL, SEPARATOR, ACTION_CREATE_PARTY, ACTION_CREATE_GROUP_PARTY, SEPARATOR, ACTION_CREATE_RIGHT, ACTION_CREATE_RESTRICTION, SEPARATOR, ACTION_CREATE_ADMINISTRATIVE_SOURCE, ACTION_CREATE_SPATIAL_SOURCE, ACTION_UPLOAD_PENDING_SOURCE ] }, SEPARATOR, ACTION_FINALIZE_GEOMETRY_CREATION, { WIDGET_TYPE: MENU, WIDGET_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "Structuring tools"), OBJECT_NAME: "ladm_col_structuring_tools_toolbar", ICON: STRUCTURING_TOOLS_ICON, ACTIONS: [ ACTION_BUILD_BOUNDARY, ACTION_MOVE_NODES, ACTION_FILL_BFS, ACTION_FILL_MORE_BFS_AND_LESS ] }, SEPARATOR, ACTION_LOAD_LAYERS, ACTION_PARCEL_QUERY ] }] role_dict = { ROLE_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "Basic"), ROLE_DESCRIPTION: QCoreApplication.translate( "AsistenteLADMCOLPlugin", "The basic role helps you to explore the LADM_COL assistant main functionalities." ), ROLE_ACTIONS: [ ACTION_DOWNLOAD_GUIDE, ACTION_CREATE_POINT, ACTION_CREATE_BOUNDARY, ACTION_CREATE_PLOT, ACTION_CREATE_BUILDING, ACTION_CREATE_BUILDING_UNIT, ACTION_CREATE_RIGHT_OF_WAY, ACTION_CREATE_EXT_ADDRESS, ACTION_CREATE_PARCEL, ACTION_CREATE_RIGHT, ACTION_CREATE_RESTRICTION, ACTION_CREATE_PARTY, ACTION_CREATE_GROUP_PARTY, ACTION_CREATE_ADMINISTRATIVE_SOURCE, ACTION_CREATE_SPATIAL_SOURCE, ACTION_UPLOAD_PENDING_SOURCE, ACTION_IMPORT_FROM_INTERMEDIATE_STRUCTURE, ACTION_BUILD_BOUNDARY, ACTION_MOVE_NODES, ACTION_FINALIZE_GEOMETRY_CREATION, ACTION_FILL_BFS, ACTION_FILL_MORE_BFS_AND_LESS, ACTION_FILL_RIGHT_OF_WAY_RELATIONS, ACTION_PARCEL_QUERY, ACTION_CHECK_QUALITY_RULES ], ROLE_GUI_CONFIG: template_gui } self.register_role(role, role_dict) role = SUPPLIES_PROVIDER_ROLE template_gui = GUI_Config().get_gui_dict(TEMPLATE_GUI) template_gui[TOOLBAR] = [{ # Overwrite list of toolbars WIDGET_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "LADM-COL tools"), OBJECT_NAME: 'ladm_col_toolbar', ACTIONS: [ { # List of toolbars WIDGET_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "Transitional System"), OBJECT_NAME: 'ladm_col_toolbar_st', ICON: ST_ICON, ACTIONS: [ACTION_ST_LOGIN, ACTION_ST_LOGOUT] }, SEPARATOR, ACTION_SCHEMA_IMPORT, ACTION_RUN_ETL_COBOL, ACTION_RUN_ETL_SNC, ACTION_FIND_MISSING_COBOL_SUPPLIES, ACTION_LOAD_LAYERS, ACTION_EXPORT_DATA ] }] role_dict = { ROLE_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "Supplies Provider"), ROLE_DESCRIPTION: QCoreApplication.translate( "AsistenteLADMCOLPlugin", "The Supplies Provider role generates a XTF file with supplies data for the Manager role." ), ROLE_ACTIONS: [ ACTION_RUN_ETL_COBOL, ACTION_RUN_ETL_SNC, ACTION_FIND_MISSING_COBOL_SUPPLIES, ACTION_ST_LOGIN, ACTION_ST_LOGOUT ], ROLE_GUI_CONFIG: template_gui } self.register_role(role, role_dict) role = OPERATOR_ROLE role_dict = { ROLE_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "Operator"), ROLE_DESCRIPTION: QCoreApplication.translate( "AsistenteLADMCOLPlugin", "The operator is in charge of capturing current cadastral data." ), ROLE_ACTIONS: [ ACTION_CREATE_POINT, ACTION_CREATE_BOUNDARY, ACTION_CREATE_PLOT, ACTION_CREATE_BUILDING, ACTION_CREATE_BUILDING_UNIT, ACTION_CREATE_RIGHT_OF_WAY, ACTION_CREATE_EXT_ADDRESS, ACTION_CREATE_PARCEL, ACTION_CREATE_RIGHT, ACTION_CREATE_RESTRICTION, ACTION_CREATE_PARTY, ACTION_CREATE_GROUP_PARTY, ACTION_CREATE_ADMINISTRATIVE_SOURCE, ACTION_CREATE_SPATIAL_SOURCE, ACTION_UPLOAD_PENDING_SOURCE, ACTION_IMPORT_FROM_INTERMEDIATE_STRUCTURE, ACTION_BUILD_BOUNDARY, ACTION_MOVE_NODES, ACTION_FINALIZE_GEOMETRY_CREATION, ACTION_FILL_BFS, ACTION_FILL_MORE_BFS_AND_LESS, ACTION_FILL_RIGHT_OF_WAY_RELATIONS, ACTION_CHANGE_DETECTION_SETTINGS, ACTION_CHANGE_DETECTION_ALL_PARCELS, ACTION_CHANGE_DETECTION_PER_PARCEL, ACTION_ST_LOGIN, ACTION_ST_LOGOUT, ACTION_PARCEL_QUERY, ACTION_CHECK_QUALITY_RULES ], ROLE_GUI_CONFIG: {} # Let the gui builder use the template GUI config. } self.register_role(role, role_dict) role = MANAGER_ROLE template_gui = GUI_Config().get_gui_dict(TEMPLATE_GUI) template_gui[TOOLBAR] = [{ # Overwrite list of toolbars WIDGET_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "LADM-COL tools"), OBJECT_NAME: 'ladm_col_toolbar', ACTIONS: [ { # List of toolbars WIDGET_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "Transitional System"), OBJECT_NAME: 'ladm_col_toolbar_st', ICON: ST_ICON, ACTIONS: [ACTION_ST_LOGIN, ACTION_ST_LOGOUT] }, SEPARATOR, ACTION_LOAD_LAYERS, ACTION_INTEGRATE_SUPPLIES, SEPARATOR, ACTION_CHECK_QUALITY_RULES, ACTION_PARCEL_QUERY, SEPARATOR, { # List of toolbars WIDGET_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "Change Detection"), OBJECT_NAME: 'ladm_col_change_detection_toolbar', ICON: CHANGE_DETECTION_ICON, ACTIONS: [ ACTION_CHANGE_DETECTION_SETTINGS, SEPARATOR, ACTION_CHANGE_DETECTION_PER_PARCEL, ACTION_CHANGE_DETECTION_ALL_PARCELS ] }, SEPARATOR, { # List of toolbars WIDGET_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "Reports"), OBJECT_NAME: 'ladm_col_reports_toolbar', ICON: REPORTS_ICON, ACTIONS: [ACTION_REPORT_ANNEX_17, ACTION_REPORT_ANT] } ] }] role_dict = { ROLE_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "Manager"), ROLE_DESCRIPTION: QCoreApplication.translate( "AsistenteLADMCOLPlugin", "The manager is in charge of preparing supplies for operators as well as validating and managing the data provided by operators." ), ROLE_ACTIONS: [ ACTION_CHANGE_DETECTION_SETTINGS, ACTION_CHANGE_DETECTION_ALL_PARCELS, ACTION_CHANGE_DETECTION_PER_PARCEL, ACTION_ST_LOGIN, ACTION_ST_LOGOUT, ACTION_REPORT_ANNEX_17, ACTION_REPORT_ANT, ACTION_INTEGRATE_SUPPLIES, ACTION_PARCEL_QUERY, ACTION_CHECK_QUALITY_RULES ], ROLE_GUI_CONFIG: template_gui } self.register_role(role, role_dict) role = ADVANCED_ROLE template_gui = GUI_Config().get_gui_dict(TEMPLATE_GUI) template_gui[TOOLBAR] = [{ # List of toolbars WIDGET_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "LADM-COL tools"), OBJECT_NAME: 'ladm_col_toolbar', ACTIONS: [ { # List of toolbars WIDGET_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "Transitional System"), OBJECT_NAME: 'ladm_col_st_toolbar', ICON: ST_ICON, ACTIONS: [ACTION_ST_LOGIN, ACTION_ST_LOGOUT] }, SEPARATOR, { WIDGET_TYPE: MENU, WIDGET_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "Create Operation objects"), OBJECT_NAME: "ladm_col_operation_toolbar", ICON: OPERATION_ICON, ACTIONS: [ ACTION_CREATE_POINT, ACTION_CREATE_BOUNDARY, SEPARATOR, ACTION_CREATE_PLOT, ACTION_CREATE_BUILDING, ACTION_CREATE_BUILDING_UNIT, ACTION_CREATE_RIGHT_OF_WAY, ACTION_FILL_RIGHT_OF_WAY_RELATIONS, SEPARATOR, ACTION_CREATE_EXT_ADDRESS, SEPARATOR, ACTION_CREATE_PARCEL, SEPARATOR, ACTION_CREATE_PARTY, ACTION_CREATE_GROUP_PARTY, SEPARATOR, ACTION_CREATE_RIGHT, ACTION_CREATE_RESTRICTION, SEPARATOR, ACTION_CREATE_ADMINISTRATIVE_SOURCE, ACTION_CREATE_SPATIAL_SOURCE, ACTION_UPLOAD_PENDING_SOURCE ] }, SEPARATOR, ACTION_LOAD_LAYERS, SEPARATOR, ACTION_FINALIZE_GEOMETRY_CREATION, ACTION_BUILD_BOUNDARY, ACTION_MOVE_NODES, SEPARATOR, ACTION_FILL_BFS, ACTION_FILL_MORE_BFS_AND_LESS, SEPARATOR, ACTION_SETTINGS ] }] role_dict = { ROLE_NAME: QCoreApplication.translate("AsistenteLADMCOLPlugin", "Advanced"), ROLE_DESCRIPTION: QCoreApplication.translate( "AsistenteLADMCOLPlugin", "The advanced role has access to all the functionality."), ROLE_ACTIONS: [ALL_ACTIONS], ROLE_GUI_CONFIG: template_gui } self.register_role(role, role_dict) def register_role(self, role_key, role_dict): """ Register roles for the LADM_COL assistant. Roles have access only to certain GUI controls. :param role_key: Role unique identifier :param role_dict: Dictionary with the following information: ROLE_NAME: Name of the role ROLE_DESCRIPTION: Explains what this role is about ROLE_ACTIONS: List of actions a role has access to :return: Whether the role was successfully registered or not. """ valid = False if ROLE_NAME in role_dict and ROLE_DESCRIPTION in role_dict and ROLE_ACTIONS in role_dict and ROLE_GUI_CONFIG in role_dict: self._registered_roles[role_key] = deepcopy(role_dict) valid = True else: self.logger.error( __name__, "Role '{}' is not defined correctly and could not be registered! Check the role_dict parameter." .format(role_key)) return valid def get_active_role(self): return QSettings().value("Asistente-LADM_COL/roles/current_role_key", self._default_role) def active_role_already_set(self): """ Whether we have set an active role already or not. :return: True if the current_role_key variable is stored in QSettings. False otherwise. """ return QSettings().value("Asistente-LADM_COL/roles/current_role_key", False) is not False def set_active_role(self, role_key): res = False if role_key in self._registered_roles: res = True else: self.logger.warning( __name__, "Role '{}' was not found, the default role is now active.". format(role_key)) role_key = self._default_role QSettings().setValue("Asistente-LADM_COL/roles/current_role_key", role_key) self.logger.info(__name__, "Role '{}' is now active!".format(role_key)) return res def set_active_default_role(self): QSettings().setValue("Asistente-LADM_COL/roles/current_role_key", self._default_role) self.logger.info( __name__, "Default role '{}' is now active!".format(self._default_role)) return True def get_roles_info(self): return {k: v[ROLE_NAME] for k, v in self._registered_roles.items()} def get_role_name(self, role_key): if role_key not in self._registered_roles: self.logger.error( __name__, "Role '{}' was not found, returning default role's name". format(role_key)) role_key = self._default_role return self._registered_roles[role_key][ROLE_NAME] def get_role_description(self, role_key): if role_key not in self._registered_roles: self.logger.error( __name__, "Role '{}' was not found, returning default role's decription". format(role_key)) role_key = self._default_role return self._registered_roles[role_key][ROLE_DESCRIPTION] def get_role_actions(self, role_key): if role_key not in self._registered_roles: self.logger.error( __name__, "Role '{}' was not found, returning default role's actions.". format(role_key)) role_key = self._default_role return list( set(self._registered_roles[role_key][ROLE_ACTIONS] + self.COMMON_ACTIONS)) def get_role_gui_config(self, role_key): if role_key not in self._registered_roles: self.logger.error( __name__, "Role '{}' was not found, returning default role's GUI configuration." .format(role_key)) role_key = self._default_role return self._registered_roles[role_key][ROLE_GUI_CONFIG]
class LADMColModelRegistry(metaclass=Singleton): """ Registry of supported models. The following info of registered models is updated each time the current role changes: is_supported, hidden, checked and get_ili2db_params. """ def __init__(self): self.logger = Logger() self.app = AppInterface() self.__models = dict() # {model_key1: LADMColModel1, ...} self.__model_config = ModelConfig() # Register default models for model_key, model_config in self.__model_config.get_models_config( ).items(): self.register_model(LADMColModel(model_key, model_config), False) def register_model(self, model, refresh_model_for_active_role=True): """ Registers an INTERLIS model to be accessible for registered roles. :param model: LADMColModel instance. :return: True if the model was registered, False otherwise. """ if not isinstance(model, LADMColModel) or model.id() in self.__models: return False self.__models[model.id()] = model self.logger.info(__name__, "Model '{}' has been registered!".format(model.id())) if model.model_dir(): self.app.settings.add_custom_model_dir(model.model_dir()) self.logger.info( __name__, "Model dir '{}' has been registered!".format( model.model_dir())) # Finally, update model.is_supported() data according to the active role. # # Note we don't want to call this while initializing the plugin, # as it calls back and forth registered_roles and registered_models, # or, in other words, it requires to have both models and roles # registered before calling the method (or we end up with max recursion). if refresh_model_for_active_role: self.refresh_models_for_active_role(model.id()) return True def unregister_model(self, model_key, unregister_model_dir=False): """ Unregisters an INTERLIS model. :param model_key: Id of the model to unregister. :param unregister_model_dir: If True, we'll search the paths associated to the model_key. If any path is found, it will be removed, so we won't search for models in that path anymore. This is False by default because it might affect other registered models, so only set it as True if removing such path won't affect discovering other models. :return: True if the model was unregistered, False otherwise. """ if model_key not in self.__models: self.logger.error( __name__, "Model '{}' was not found in registered models, therefore, it cannot be unregistered!" .format(model_key)) return False if unregister_model_dir: model_dir = self.__models[model_key].model_dir() if model_dir: self.app.settings.remove_custom_model_dir(model_dir) self.logger.info( __name__, "Model dir '{}' has been unregistered!".format(model_dir)) self.__models[model_key] = None del self.__models[model_key] self.logger.info(__name__, "Model '{}' has been unregistered!".format(model_key)) return True def supported_models(self): return [ model for model in self.__models.values() if model.is_supported() ] def supported_model_keys(self): return [ model.id() for model in self.__models.values() if model.is_supported() ] def model(self, model_key): return self.__models.get(model_key, LADMColModel("foo", dict())) # To avoid exceptions def model_by_full_name(self, full_name): for model in self.__models.values(): if model.full_name() == full_name: return model return LADMColModel("foo", dict()) # To avoid exceptions def model_keys(self): return list(self.__models.keys()) def hidden_and_supported_models(self): return [ model for model in self.__models.values() if model.hidden() and model.is_supported() ] def non_hidden_and_supported_models(self): return [ model for model in self.__models.values() if not model.hidden() and model.is_supported() ] def refresh_models_for_active_role(self, only_for_model=''): role_key = RoleRegistry().get_active_role() role_models = RoleRegistry().get_role_models(role_key) # ili2db params may come from the model config itself or overwritten by the current user. # If the user does not have such config, we grab it from MODEL_CONFIG. ili2db_params = role_models.get(ROLE_MODEL_ILI2DB_PARAMETERS, dict()) for model_key, model in self.__models.items(): if only_for_model and model_key != only_for_model: continue # Avoid overwriting data of the other models (useful for refreshing a just-registered model) model.set_is_supported( model_key in role_models[ROLE_SUPPORTED_MODELS]) model.set_is_hidden(model_key in role_models[ROLE_HIDDEN_MODELS]) model.set_is_checked(model_key in role_models[ROLE_CHECKED_MODELS]) # First attempt to get ili2db parameters from role, otherwise from model config model_ili2db_params = ili2db_params.get( model_key, dict()) or model.get_default_ili2db_params() model.set_ili2db_params(model_ili2db_params) if model_ili2db_params: self.logger.debug( __name__, "Model ili2db params are: {}".format(model_ili2db_params)) self.logger.debug( __name__, "Supported models for active role '{}': {}".format( role_key, role_models[ROLE_SUPPORTED_MODELS])) def get_model_mapping(self, model_key): return self.model(model_key).get_mapping() def register_catalog_for_model(self, model_key, catalog): res = False model = self.__models.get(model_key, None) if model: model.add_catalog(catalog) res = True return res def unregister_catalog_from_model(self, model_key, catalog_key): return self.__models[model_key].remove_catalog( catalog_key) if model_key in self.__models else False def get_model_catalogs(self, model_key): return self.__models[model_key].get_catalogs( ) if model_key in self.__models else dict()
class XTFModelConverterRegistry(metaclass=Singleton): """ Registry of supported model converters. """ def __init__(self): self.logger = Logger() self.app = AppInterface() self.__converters = dict() # {converter_key1: LADMColModelConverter1, ...} # Register default models self.register_model_converter(Survey10To11Converter()) self.register_model_converter(Survey11To10Converter()) def register_model_converter(self, converter): """ :param converter: LADMColModelConverter instance. :return: True if the converter was registered, False otherwise. """ if not isinstance(converter, AbstractLADMColModelConverter): self.logger.warning(__name__, "The converter '{}' is not a 'LADMColModelConverter' instance!".format(converter.id())) return False if not converter.is_valid(): self.logger.warning(__name__, "The converter '{}' is not valid! Check the converter definition!".format(converter.id())) return False if converter.id() in self.__converters: self.logger.warning(__name__, "The converter '{}' is already registered.".format(converter.id())) return False self.__converters[converter.id()] = converter self.logger.info(__name__, "Model converter '{}' has been registered!".format(converter.id())) return True def unregister_model_converter(self, converter_key): """ Unregisters a model converter. :param converter_key: Id of the converter to unregister. :return: True if the converter was unregistered, False otherwise. """ if converter_key not in self.__converters: self.logger.error(__name__, "Converter '{}' was not found in registered model converters, therefore, it cannot be unregistered!".format(converter_key)) return False self.__converters[converter_key] = None del self.__converters[converter_key] self.logger.info(__name__, "Model converter '{}' has been unregistered!".format(converter_key)) return True def get_converter(self, converter_key): converter = self.__converters.get(converter_key, None) # To avoid exceptions if not converter: self.logger.critical(__name__, "No model converter found with key '{}'".format(converter_key)) return converter def get_converters_for_models(self, models): converters = dict() # {converter_key_1: converter_display_name_1, ...] for converter_key, converter in self.__converters.items(): for model in models: if converter.supports_source_model(model): converters[converter_key] = converter.display_name() return converters
class RoleRegistry(QObject, metaclass=SingletonQObject): """ Manage all role information. Current role can also be got/set from this class. Roles can set their own GUI configuration, their own LADM-COL supported models, their own quality rules, etc. """ active_role_changed = pyqtSignal(str) # New active role key COMMON_ACTIONS = [ # Common actions for all roles ACTION_LOAD_LAYERS, ACTION_SCHEMA_IMPORT, ACTION_IMPORT_DATA, ACTION_EXPORT_DATA, ACTION_SETTINGS, ACTION_HELP, ACTION_ABOUT ] def __init__(self): QObject.__init__(self) self.logger = Logger() self.app = AppInterface() self._registered_roles = dict() self._default_role = BASIC_ROLE # Register default roles for role_key, role_config in get_role_config().items(): if ROLE_ENABLED in role_config and role_config[ROLE_ENABLED]: self.register_role(role_key, role_config) def register_role(self, role_key, role_dict, activate_role=False): """ Register roles for the LADM-COL assistant. Roles have access only to certain GUI controls, to certain LADM-COL models and to certain quality rules. Warning: this class will modify the role_dict, so better pass a deepcopy of the configuration dict. :param role_key: Role unique identifier :param role_dict: Dictionary with the following information: ROLE_NAME: Name of the role ROLE_DESCRIPTION: Explains what this role is about ROLE_ENABLED: Whether this role is enabled or not ROLE_ACTIONS: List of actions a role has access to ROLE_MODELS: List of models and their configuration for the current role ROLE_QUALITY_RULES: List of quality rule keys this role has access to ROLE_GUI_CONFIG: Dict with the GUI config (menus and toolbars) :return: Whether the role was successfully registered or not. """ valid = False if ROLE_NAME in role_dict and ROLE_DESCRIPTION in role_dict and ROLE_ACTIONS in role_dict and \ ROLE_GUI_CONFIG in role_dict and TEMPLATE_GUI in role_dict[ROLE_GUI_CONFIG] \ and ROLE_MODELS in role_dict: if role_dict[ROLE_GUI_CONFIG]: # It's mandatory to provide a GUI config for the role self._registered_roles[role_key] = role_dict valid = True else: self.logger.error(__name__, "Role '{}' has no GUI config and could not be registered!".format(role_key)) else: self.logger.error(__name__, "Role '{}' is not defined correctly and could not be registered! Check the role_dict parameter.".format(role_key)) if activate_role: self.set_active_role(role_key) return valid def unregister_role(self, role_key): res = False if role_key in self._registered_roles: # You cannot unregister the default role if role_key != self._default_role: # First change active role to default if role_key is active if role_key == self.get_active_role(): self.set_active_role(self._default_role) # Then unregister the role self._registered_roles[role_key] = None del self._registered_roles[role_key] res = False else: self.logger.warning(__name__, "You cannot unregister the default role!") else: self.logger.warning(__name__, "The role ('{}') you're trying to unregister is not registered!".format(role_key)) return res def get_active_role(self): # We make sure the active role we return is in fact registered. # Otherwise, we set the default role as active. active_role = self.app.settings.active_role if not active_role in self._registered_roles: self.set_active_role(self._default_role) return self.app.settings.active_role def get_active_role_name(self): return self.get_role_name(self.get_active_role()) def active_role_already_set(self): """ Whether we have set an active role already or not. :return: True if the current_role_key variable is stored in QSettings. False otherwise. """ return self.app.settings.active_role is not None def set_active_role(self, role_key, emit_signal=True): """ Set the active role for the plugin. :param role_key: Key to identify the role. :param emit_signal: Whether the active_role_changed should be emitted or not. A False argument should be passed if the plugin config refresh will be called manually, for instance, because it is safer to call a GUI refresh after closing some plugin dialogs. :return: Whether the role was successfully changed or not in the role registry. """ res = False if role_key in self._registered_roles: res = True else: self.logger.warning(__name__, "Role '{}' was not found, the default role is now active.".format(role_key)) role_key = self._default_role self.app.settings.active_role = role_key self.logger.info(__name__, "Role '{}' is now active!".format(role_key)) if emit_signal: self.active_role_changed.emit(role_key) return res def set_active_default_role(self, emit_signal=True): return self.set_active_role(self._default_role, emit_signal) def get_roles_info(self): return {k: v[ROLE_NAME] for k,v in self._registered_roles.items()} def get_role_name(self, role_key): if role_key not in self._registered_roles: self.logger.error(__name__, "Role '{}' was not found, returning default role's name".format(role_key)) role_key = self._default_role return self._registered_roles[role_key][ROLE_NAME] def get_role_description(self, role_key): if role_key not in self._registered_roles: self.logger.error(__name__, "Role '{}' was not found, returning default role's decription".format(role_key)) role_key = self._default_role return self._registered_roles[role_key][ROLE_DESCRIPTION] def get_role_actions(self, role_key): if role_key not in self._registered_roles: self.logger.error(__name__, "Role '{}' was not found, returning default role's actions.".format(role_key)) role_key = self._default_role return list(set(self._registered_roles[role_key][ROLE_ACTIONS] + self.COMMON_ACTIONS)) def get_role_gui_config(self, role_key, gui_type=TEMPLATE_GUI): """ Return the role GUI config. :param role_key: Role id. :param gui_type: Either TEMPLATE_GUI or DEFAULT_GUI (the one for wrong db connections). :return: Dict with the GUI config for the role. """ if role_key not in self._registered_roles: self.logger.error(__name__, "Role '{}' was not found, returning default role's GUI configuration.".format(role_key)) role_key = self._default_role # Return a deepcopy, since we don't want external classes to modify a role's GUI config gui_conf = self._registered_roles[role_key][ROLE_GUI_CONFIG].get(gui_type, dict()) return deepcopy(gui_conf) # The plugin knows what to do if the role has no DEFAULT_GUI def get_role_models(self, role_key): """ Normally you wouldn't need this but LADMColModelRegistry, which is anyway updated when the role changes. """ if role_key not in self._registered_roles: self.logger.error(__name__, "Role '{}' was not found, returning default role's models.".format(role_key)) role_key = self._default_role return self._registered_roles[role_key][ROLE_MODELS] def get_active_role_supported_models(self): return self.get_role_supported_models(self.get_active_role()) def get_role_supported_models(self, role_key): return self.get_role_models(role_key)[ROLE_SUPPORTED_MODELS] def active_role_needs_automatic_expression_for_baskets(self): return self._registered_roles[self.get_active_role()].get(ROLE_NEEDS_AUTOMATIC_VALUE_FOR_BASKETS, False) def get_role_quality_rules(self, role_key): if role_key not in self._registered_roles: self.logger.error(__name__, "Role '{}' was not found, returning default role's quality rules.".format(role_key)) role_key = self._default_role return self._registered_roles[role_key][ROLE_QUALITY_RULES] def get_role_db_source(self, role_key): if role_key not in self._registered_roles: self.logger.error(__name__, "Role '{}' was not found, returning default role's db source.".format(role_key)) role_key = self._default_role return self._registered_roles[role_key].get(ROLE_DB_SOURCE, None) def add_actions_to_roles(self, action_keys, role_keys=None): """ For add-ons that want to modify actions of already registered roles. This first adds each action_key to allowed role actions, and then it adds each action key to the menu Add-ons that is empty by default in the template GUI Config. After calling this method, it is necessary to call gui_builder.build_gui() to refresh the GUI with these changes. Otherwise, the user won't see changes until build_gui() is called from the Asistente LADM-COL. :param action_keys: List of action keys. :param role_keys: List of role keys. This param is optional. If it's not passed, we'll use all registered roles. """ if not role_keys: role_keys = list(self._registered_roles.keys()) for role_key in role_keys: if role_key in self._registered_roles: self.__add_actions_to_allowed_role_actions(action_keys, role_key) self.__add_actions_to_role_add_on_menu(action_keys, role_key) self.logger.debug(__name__, "{} actions added to role '{}'!".format(len(action_keys), role_key)) def __add_actions_to_allowed_role_actions(self, action_keys, role_key): # Add action keys to the list of allowed actions for a given role role_actions = self._registered_roles[role_key][ROLE_ACTIONS] self._registered_roles[role_key][ROLE_ACTIONS] = list(set(role_actions + action_keys)) del role_actions def __add_actions_to_role_add_on_menu(self, action_keys, role_key): # Go for the Menu with object_name LADM_COL_ADD_ON_MENU and add the action keys gui_config = self._registered_roles[role_key][ROLE_GUI_CONFIG] for main_menu in gui_config.get(MAIN_MENU, dict()): # Since MAIN_MENU is a list of menus for action in main_menu.get(ACTIONS, list()): if isinstance(action, dict): # We know this is a menu if action.get(OBJECT_NAME, "") == LADM_COL_ADD_ON_MENU: # This is the Add-ons menu action[ACTIONS] = list(set(action[ACTIONS] + action_keys)) # Add actions and avoid dup. break # Go to other menus, because in this one we are done!
class RoleRegistry(QObject, metaclass=SingletonQObject): """ Manage all role information. Current role can also be got/set from this class. Roles can set their own GUI configuration, their own LADM-COL supported models, their own quality rules, etc. """ active_role_changed = pyqtSignal(str) # New active role key COMMON_ACTIONS = [ # Common actions for all roles ACTION_LOAD_LAYERS, ACTION_SCHEMA_IMPORT, ACTION_IMPORT_DATA, ACTION_EXPORT_DATA, ACTION_SETTINGS, ACTION_HELP, ACTION_ABOUT ] def __init__(self): QObject.__init__(self) self.logger = Logger() self.app = AppInterface() self._registered_roles = dict() self._default_role = BASIC_ROLE def register_role(self, role_key, role_dict): """ Register roles for the LADM-COL assistant. Roles have access only to certain GUI controls, to certain LADM-COL models and to certain quality rules. :param role_key: Role unique identifier :param role_dict: Dictionary with the following information: ROLE_NAME: Name of the role ROLE_DESCRIPTION: Explains what this role is about ROLE_ACTIONS: List of actions a role has access to ROLE_MODELS: List of models and their configuration for the current role :return: Whether the role was successfully registered or not. """ valid = False if ROLE_NAME in role_dict and ROLE_DESCRIPTION in role_dict and ROLE_ACTIONS in role_dict and \ ROLE_GUI_CONFIG in role_dict and ROLE_MODELS in role_dict: self._registered_roles[role_key] = deepcopy(role_dict) valid = True else: self.logger.error(__name__, "Role '{}' is not defined correctly and could not be registered! Check the role_dict parameter.".format(role_key)) return valid def get_active_role(self): return self.app.settings.active_role or self._default_role def get_active_role_name(self): return self.get_role_name(self.get_active_role()) def active_role_already_set(self): """ Whether we have set an active role already or not. :return: True if the current_role_key variable is stored in QSettings. False otherwise. """ return self.app.settings.active_role is not None def set_active_role(self, role_key, emit_signal=True): """ Set the active role for the plugin. :param role_key: Key to identify the role. :param emit_signal: Whether the active_role_changed should be emitted or not. A False argument should be passed if the plugin config refresh will be called manually, for instance, because it is safer to call a GUI refresh after closing some plugin dialogs. :return: Whether the role was successfully changed or not in the role registry. """ res = False if role_key in self._registered_roles: res = True else: self.logger.warning(__name__, "Role '{}' was not found, the default role is now active.".format(role_key)) role_key = self._default_role self.app.settings.active_role = role_key self.logger.info(__name__, "Role '{}' is now active!".format(role_key)) if emit_signal: self.active_role_changed.emit(role_key) return res def set_active_default_role(self, emit_signal=True): return self.set_active_role(self._default_role, emit_signal) def get_roles_info(self): return {k: v[ROLE_NAME] for k,v in self._registered_roles.items()} def get_role_name(self, role_key): if role_key not in self._registered_roles: self.logger.error(__name__, "Role '{}' was not found, returning default role's name".format(role_key)) role_key = self._default_role return self._registered_roles[role_key][ROLE_NAME] def get_role_description(self, role_key): if role_key not in self._registered_roles: self.logger.error(__name__, "Role '{}' was not found, returning default role's decription".format(role_key)) role_key = self._default_role return self._registered_roles[role_key][ROLE_DESCRIPTION] def get_role_actions(self, role_key): if role_key not in self._registered_roles: self.logger.error(__name__, "Role '{}' was not found, returning default role's actions.".format(role_key)) role_key = self._default_role return list(set(self._registered_roles[role_key][ROLE_ACTIONS] + self.COMMON_ACTIONS)) def get_role_gui_config(self, role_key): if role_key not in self._registered_roles: self.logger.error(__name__, "Role '{}' was not found, returning default role's GUI configuration.".format(role_key)) role_key = self._default_role return self._registered_roles[role_key][ROLE_GUI_CONFIG] def get_role_models(self, role_key): """ Normally you wouldn't need this but LADMColModelRegistry, which is anyway updated when the role changes """ if role_key not in self._registered_roles: self.logger.error(__name__, "Role '{}' was not found, returning default role's models.".format(role_key)) role_key = self._default_role return self._registered_roles[role_key][ROLE_MODELS] def get_active_role_supported_models(self): role_key = self.get_active_role() role_models = self.get_role_models(role_key) return role_models[ROLE_SUPPORTED_MODELS] def get_role_quality_rules(self, role_key): if role_key not in self._registered_roles: self.logger.error(__name__, "Role '{}' was not found, returning default role's quality rules.".format(role_key)) role_key = self._default_role return self._registered_roles[role_key][ROLE_QUALITY_RULES] def get_role_db_source(self, role_key): if role_key not in self._registered_roles: self.logger.error(__name__, "Role '{}' was not found, returning default role's db source.".format(role_key)) role_key = self._default_role return self._registered_roles[role_key][ROLE_DB_SOURCE] if ROLE_DB_SOURCE in self._registered_roles[role_key] else None
class DBMappingRegistry: """ Names are dynamic because different DB engines handle different names, and because even in a single DB engine, one could shorten table and field names via ili2db. Therefore, each DB connector has its own DBMappingRegistry. At any time, the DBMapping Registry has all table and field names that are both in the models the active user has access to and those which are present in the DB. That is, variable members in DBMapping Registry can be seen as the intersection of current active role model objects and current DB connection objects. """ def __init__(self): self.id = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') self.logger = Logger() self._cached_domain_values = dict( ) # Right cache: queries that actually return a domain value/code self._cached_wrong_domain_queries = { # Wrong cache: queries that do not return anything from the domain QueryNames.VALUE_KEY: dict(), QueryNames.CODE_KEY: dict() } self._cached_default_basket_t_id = None # To ease addition of new ili2db names (which must be done in several classes), # we keep them together in a dict {variable_name: variable_key} self.__ili2db_names = { "T_ID_F": T_ID_KEY, "T_ILI_TID_F": T_ILI_TID_KEY, "ILICODE_F": ILICODE_KEY, "DESCRIPTION_F": DESCRIPTION_KEY, "DISPLAY_NAME_F": DISPLAY_NAME_KEY, "THIS_CLASS_F": THIS_CLASS_KEY, "T_BASKET_F": T_BASKET_KEY, "T_ILI2DB_BASKET_T": T_ILI2DB_BASKET_KEY, "T_ILI2DB_DATASET_T": T_ILI2DB_DATASET_KEY, "DATASET_T_DATASETNAME_F": DATASET_T_DATASETNAME_KEY, "BASKET_T_DATASET_F": BASKET_T_DATASET_KEY, "BASKET_T_TOPIC_F": BASKET_T_TOPIC_KEY, "BASKET_T_ATTACHMENT_KEY_F": BASKET_T_ATTACHMENT_KEY } # Main mapping dictionary: # {table_key: {variable: 'table_variable_name', field_dict:{field_key: 'field_variable'}}} # It only gets mappings for models that are both, supported by the active rol, and present in the DB. # This dict is reset each time the active role changes. # # It's used to: # 1) Set variables that are present in both, the dict itself and the DB. Note: The dict doesn't change # after it has been registered via register_db_mapping(). # 2) Traverse the expected DB-model variables so that we can test they are set. self.__table_field_dict = dict() def __register_db_mapping(self, model_key, mapping): self.__table_field_dict[model_key] = mapping.copy() def __refresh_mapping_for_role(self, db_models): model_registry = LADMColModelRegistry() for model in model_registry.supported_models(): if model.full_name() in db_models: self.__register_db_mapping( model.id(), model_registry.get_model_mapping(model.id())) def initialize_table_and_field_names(self, db_mapping, db_models): """ Update class variables (table and field names) according to a dictionary of names coming from a DB connection. This function should be called when a new DB connection is established for making all classes in the plugin able to access current DB connection names. :param db_mapping: Expected dict with key as iliname (fully qualified object name in the model) with no version info, and value as sqlname (produced by ili2db). :param db_models: Models present in the DB. :return: True if anything is updated, False otherwise. """ self.__reset_table_and_field_names( ) # We will start mapping from scratch, so reset any previous mapping self.__refresh_mapping_for_role( db_models ) # Now, for the active role, register the db mappings per allowed model any_update = False table_names_count = 0 field_names_count = 0 if db_mapping: for key in self.__ili2db_names.values(): if key not in db_mapping: self.logger.error( __name__, "dict_names is not properly built, this required field was not found: {}" ).format(key) return False for model_key, registered_mapping in self.__table_field_dict.items( ): for table_key, attrs in registered_mapping.items(): if table_key in db_mapping: setattr(self, attrs[QueryNames.VARIABLE_NAME], db_mapping[table_key][QueryNames.TABLE_NAME]) table_names_count += 1 any_update = True for field_key, field_variable in attrs[ QueryNames.FIELDS_DICT].items(): if field_key in db_mapping[table_key]: setattr(self, field_variable, db_mapping[table_key][field_key]) field_names_count += 1 # Required fields coming from ili2db (T_ID_F, T_ILI_TID, etc.) for k, v in self.__ili2db_names.items(): setattr(self, k, db_mapping[v]) self.logger.info(__name__, "Table and field names have been set!") self.logger.debug( __name__, "Number of table names set: {}".format(table_names_count)) self.logger.debug( __name__, "Number of field names set: {}".format(field_names_count)) self.logger.debug( __name__, "Number of common ili2db names set: {}".format( len(self.__ili2db_names))) return any_update def __reset_table_and_field_names(self): """ Start __table_field_dict from scratch to prepare the next mapping. The other vars are set to None for the same reason. """ self.__table_field_dict = dict() for k, v in self.__ili2db_names.items(): setattr(self, k, None) # Clear caches self._cached_domain_values = dict() self._cached_wrong_domain_queries = { QueryNames.VALUE_KEY: dict(), QueryNames.CODE_KEY: dict() } self._cached_default_basket_t_id = None self.logger.info( __name__, "Names (DB mapping) have been reset to prepare the next mapping.") def test_names(self): """ Test whether required table/field names are present. Required names are all those that are in the __table_field_dict variable (names of supported models by the active role and at the same time present in the DB) and the ones in ili2db_names variable. :return: Tuple bool: Names are valid or not, string: Message to indicate what exactly failed """ # Get required names (registered names) from the __table_field_dict required_names = list() for model_key, registered_mapping in self.__table_field_dict.items(): self.logger.debug( __name__, "Names to test in model '{}': {}".format( model_key, len(registered_mapping))) for k, v in registered_mapping.items(): required_names.append(v[QueryNames.VARIABLE_NAME]) for k1, v1 in v[QueryNames.FIELDS_DICT].items(): required_names.append(v1) required_names = list( set(required_names) ) # Cause tables from base models might be registered by submodels required_ili2db_names = list(self.__ili2db_names.keys()) count_required_names_before = len(required_names) required_names.extend(required_ili2db_names) self.logger.debug( __name__, "Testing names... Number of required names: {} ({} + {})".format( len(required_names), count_required_names_before, len(required_ili2db_names))) names_not_found = list() for required_name in required_names: if getattr(self, required_name, None) is None: names_not_found.append(required_name) if names_not_found: self.logger.debug( __name__, "Variable names not properly set: {}".format(names_not_found)) return False, "Name '{}' was not found!".format(names_not_found[0]) return True, "" def cache_domain_value(self, domain_table, t_id, value, value_is_ilicode, child_domain_table=''): key = "{}..{}".format('ilicode' if value_is_ilicode else 'dispname', value) if child_domain_table: key = "{}..{}".format(key, child_domain_table) if domain_table in self._cached_domain_values: self._cached_domain_values[domain_table][key] = t_id else: self._cached_domain_values[domain_table] = {key: t_id} def cache_wrong_query(self, query_type, domain_table, code, value, value_is_ilicode, child_domain_table=''): """ If query was by value, then use value in key and code in the corresponding value pair, and viceversa :param query_type: QueryNames.VALUE_KEY (search by value) or QueryNames.CODE_KEY (search by code) :param domain_table: name of the table being searched :param code: t_id :param value: iliCode or dispName value :param value_is_ilicode: whether the value to be searched is iliCode or not :param child_domain_table: (Optional) Name of the child domain table (may be required to disambiguate duplicate ilicodes, which occurs when the DB has multiple child domains). """ key = "{}..{}".format( 'ilicode' if value_is_ilicode else 'dispname', value if query_type == QueryNames.VALUE_KEY else code) if child_domain_table: key = "{}..{}".format(key, child_domain_table) if domain_table in self._cached_wrong_domain_queries[query_type]: self._cached_wrong_domain_queries[query_type][domain_table][ key] = code if query_type == QueryNames.VALUE_KEY else value else: self._cached_wrong_domain_queries[query_type][domain_table] = { key: code if query_type == QueryNames.VALUE_KEY else value } def get_domain_value(self, domain_table, t_id, value_is_ilicode): """ Get a domain value from the cache. First, attempt to get it from the 'right' cache, then from the 'wrong' cache. Note: Here we don't need a child_domain_table (like we do in get_domain_code), because the t_id is enough to get a single domain record, even if the DB has multiple child domains. :param domain_table: Domain table name. :param t_id: t_id to be searched. :param value_is_ilicode: Whether the value is iliCode (True) or dispName (False) :return: iliCode of the corresponding t_id. """ # Search in 'right' cache field_name = 'ilicode' if value_is_ilicode else 'dispname' if domain_table in self._cached_domain_values: for k, v in self._cached_domain_values[domain_table].items(): if v == t_id: # In this case, we compare by value and we're interested in the value included in the key key = k.split("..") if key[0] == field_name: # Compound key: ilicode..value/dispname..value/ilicode..value..child/dispname..value..child # Note: if the value was stored with a key that includes child_domain_table, we still have # the field_name in key[0] and the value in key[1]. We also have the child_domain_table # in key[2], but we don't care about it, since the t_id is enough to get the value (i.e., # we won't need to disambiguate anything). return True, key[1] # Search in 'wrong' cache (in this case, we'll never find anything that has child_domain_table included in key) if domain_table in self._cached_wrong_domain_queries[ QueryNames.CODE_KEY]: key = "{}..{}".format( 'ilicode' if value_is_ilicode else 'dispname', t_id) if key in self._cached_wrong_domain_queries[ QueryNames.CODE_KEY][domain_table]: return True, self._cached_wrong_domain_queries[ QueryNames.CODE_KEY][domain_table][key] return False, None def get_domain_code(self, domain_table, value, value_is_ilicode, child_domain_table=''): """ Get a domain code (t_id) from the cache. First, attempt from the 'right' cache, then from the 'wrong' cache. :param domain_table: Domain table name. :param value: value to be searched. :param value_is_ilicode: Whether the value is iliCode (True) or dispName (False) :param child_domain_table: (Optional) Name of the child domain table (may be required to disambiguate duplicate ilicodes, which occurs when the DB has multiple child domains). :return: tuple (found, t_id) found: boolean, whether the value was found in cache or not t_id: t_id of the corresponding ilicode """ # Search in 'right' cache key = "{}..{}".format('ilicode' if value_is_ilicode else 'dispname', value) if child_domain_table: key = "{}..{}".format(key, child_domain_table) if domain_table in self._cached_domain_values: if key in self._cached_domain_values[domain_table]: return True, self._cached_domain_values[domain_table][key] # Search in 'wrong' cache if domain_table in self._cached_wrong_domain_queries[ QueryNames.VALUE_KEY]: if key in self._cached_wrong_domain_queries[ QueryNames.VALUE_KEY][domain_table]: return True, self._cached_wrong_domain_queries[ QueryNames.VALUE_KEY][domain_table][key] return False, None def cache_default_basket(self, default_basket_t_id): self._cached_default_basket_t_id = default_basket_t_id def get_default_basket(self): return True if self._cached_default_basket_t_id else False, self._cached_default_basket_t_id
class DBMappingRegistry: """ Names are dynamic because different DB engines handle different names, and because even in a single DB engine, one could shorten table and field names via ili2db. Therefore, each DB connector has its own DBMappingRegistry. At any time, the DBMapping Registry has all table and field names that are both in the models the active user has access to and those which are present in the DB. That is, variable members in DBMapping Registry can be seen as the intersection of current active role model objects and current DB connection objects. """ def __init__(self): self.id = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') self.logger = Logger() self._cached_domain_values = dict( ) # Right cache: queries that actually return a domain value/code self._cached_wrong_domain_queries = { # Wrong cache: queries that do not return anything from the domain QueryNames.VALUE_KEY: dict(), QueryNames.CODE_KEY: dict() } # To ease addition of new ili2db names (which must be done in several classes), # we keep them together in a dict {variable_name: variable_key} self.ili2db_names = { "T_ID_F": T_ID_KEY, "T_ILI_TID_F": T_ILI_TID_KEY, "ILICODE_F": ILICODE_KEY, "DESCRIPTION_F": DESCRIPTION_KEY, "DISPLAY_NAME_F": DISPLAY_NAME_KEY, "T_BASKET_F": T_BASKET_KEY, "T_ILI2DB_BASKET_T": T_ILI2DB_BASKET_KEY, "T_ILI2DB_DATASET_T": T_ILI2DB_DATASET_KEY, "DATASET_T_DATASETNAME_F": DATASET_T_DATASETNAME_KEY, "BASKET_T_DATASET_F": BASKET_T_DATASET_KEY, "BASKET_T_TOPIC_F": BASKET_T_TOPIC_KEY, "BASKET_T_ATTACHMENT_KEY_F": BASKET_T_ATTACHMENT_KEY } # Main mapping dictionary: {table_key: {variable: 'table_variable_name', field_dict:{field_key: 'field_variable'}}} self.TABLE_DICT = dict() def register_db_mapping(self, mapping): self.TABLE_DICT.update(mapping.copy()) def refresh_mapping_for_role(self): for model_key in RoleRegistry().get_active_role_supported_models(): if model_key in DB_MAPPING_CONFIG: self.register_db_mapping(DB_MAPPING_CONFIG[model_key]) def initialize_table_and_field_names(self, db_mapping): """ Update class variables (table and field names) according to a dictionary of names coming from a DB connection. This function should be called when a new DB connection is established for making all classes in the plugin able to access current DB connection names. :param db_mapping: Expected dict with key as iliname (fully qualified object name in the model) with no version info, and value as sqlname (produced by ili2db). :return: True if anything is updated, False otherwise. """ self.reset_table_and_field_names( ) # We will start mapping from scratch, so reset any previous mapping self.refresh_mapping_for_role( ) # Now, for the active role, register his/her db mapping per allowed model any_update = False table_names_count = 0 field_names_count = 0 if db_mapping: for key in self.ili2db_names.values(): if key not in db_mapping: self.logger.error( __name__, "dict_names is not properly built, this required fields was not found: {}" ).format(key) return False for table_key, attrs in self.TABLE_DICT.items(): if table_key in db_mapping: setattr(self, attrs[QueryNames.VARIABLE_NAME], db_mapping[table_key][QueryNames.TABLE_NAME]) table_names_count += 1 any_update = True for field_key, field_variable in attrs[ QueryNames.FIELDS_DICT].items(): if field_key in db_mapping[table_key]: setattr(self, field_variable, db_mapping[table_key][field_key]) field_names_count += 1 # Required fields coming from ili2db (T_ID_F, T_ILI_TID, etc.) for k, v in self.ili2db_names.items(): setattr(self, k, db_mapping[v]) self.logger.info(__name__, "Table and field names have been set!") self.logger.debug( __name__, "Number of table names set: {}".format(table_names_count)) self.logger.debug( __name__, "Number of field names set: {}".format(field_names_count)) return any_update def reset_table_and_field_names(self): """ Start TABLE_DICT from scratch to prepare the next mapping. The other varis are set to None for the same reason. """ self.TABLE_DICT = dict() for k, v in self.ili2db_names.items(): setattr(self, k, None) # Clear cache self._cached_domain_values = dict() self.logger.info( __name__, "Names (DB mapping) have been reset to prepare the next mapping.") def test_names(self, table_and_field_names): """ Test whether required table/field names are present. :param table_and_field_names: Flat list (no structure) of table and field names present in the db :return: Tuple bool: Names are valid or not, string: Message to indicate what exactly failed """ # Names that are mapped in the code mapped_names = dict() for k, v in self.TABLE_DICT.items(): mapped_names[k] = v[QueryNames.VARIABLE_NAME] for k1, v1 in v[QueryNames.FIELDS_DICT].items(): mapped_names[k1] = v1 # Iterate names from DB and add to a list to check only those that coming from the DB are also mapped in code required_names = list( set([ mapped_names[name] for name in table_and_field_names if name in mapped_names ])) if not required_names: return False, "The DB has no table or field names to check! As is, the plugin cannot get tables or fields from it!" not_mapped = list( set([ name for name in table_and_field_names if not name in mapped_names ])) self.logger.debug( __name__, "DB names not mapped in code ({}): First 10 --> {}".format( len(not_mapped), not_mapped[:10])) self.logger.debug( __name__, "Number of required names: {}".format(len(required_names))) required_names.extend(list(self.ili2db_names.keys())) names_not_found = list() for required_name in required_names: if getattr(self, required_name) is None: names_not_found.append(required_name) self.logger.debug( __name__, "Variable names not properly set: {}".format(names_not_found)) if names_not_found: return False, "Name '{}' was not found!".format(names_not_found[0]) return True, "" def cache_domain_value(self, domain_table, t_id, value, value_is_ilicode): key = "{}..{}".format('ilicode' if value_is_ilicode else 'dispname', value) if domain_table in self._cached_domain_values: self._cached_domain_values[domain_table][key] = t_id else: self._cached_domain_values[domain_table] = {key: t_id} def cache_wrong_query(self, query_type, domain_table, code, value, value_is_ilicode): """ If query was by value, then use value in key and code in the corresponding value pair, and viceversa :param query_type: QueryNames.VALUE_KEY (search by value) or QueryNames.CODE_KEY (search by code) :param domain_table: name of the table being searched :param code: t_id :param value: iliCode or dispName value :param value_is_ilicode: whether the value to be searched is iliCode or not """ key = "{}..{}".format( 'ilicode' if value_is_ilicode else 'dispname', value if query_type == QueryNames.VALUE_KEY else code) if domain_table in self._cached_wrong_domain_queries[query_type]: self._cached_wrong_domain_queries[query_type][domain_table][ key] = code if query_type == QueryNames.VALUE_KEY else value else: self._cached_wrong_domain_queries[query_type][domain_table] = { key: code if query_type == QueryNames.VALUE_KEY else value } def get_domain_value(self, domain_table, t_id, value_is_ilicode): """ Get a domain value from the cache. First, attempt to get it from the 'right' cache, then from the 'wrong' cache. :param domain_table: Domain table name. :param t_id: t_id to be searched. :param value_is_ilicode: Whether the value is iliCode (True) or dispName (False) :return: iliCode of the corresponding t_id. """ # Search in 'right' cache field_name = 'ilicode' if value_is_ilicode else 'dispname' if domain_table in self._cached_domain_values: for k, v in self._cached_domain_values[domain_table].items(): if v == t_id: key = k.split("..") if key[0] == field_name: return True, key[ 1] # Compound key: ilicode..value or dispname..value # Search in 'wrong' cache if domain_table in self._cached_wrong_domain_queries[ QueryNames.CODE_KEY]: key = "{}..{}".format( 'ilicode' if value_is_ilicode else 'dispname', t_id) if key in self._cached_wrong_domain_queries[ QueryNames.CODE_KEY][domain_table]: return True, self._cached_wrong_domain_queries[ QueryNames.CODE_KEY][domain_table][key] return False, None def get_domain_code(self, domain_table, value, value_is_ilicode): """ Get a domain code from the cache. First, attempt to get it from the 'right' cache, then from the 'wrong' cache. :param domain_table: Domain table name. :param value: value to be searched. :param value_is_ilicode: Whether the value is iliCode (True) or dispName (False) :return: tuple (found, t_id) found: boolean, whether the value was found in cache or not t_id: t_id of the corresponding ilicode """ # Search in 'right' cache key = "{}..{}".format('ilicode' if value_is_ilicode else 'dispname', value) if domain_table in self._cached_domain_values: if key in self._cached_domain_values[domain_table]: return True, self._cached_domain_values[domain_table][key] # Search in 'wrong' cache if domain_table in self._cached_wrong_domain_queries[ QueryNames.VALUE_KEY]: if key in self._cached_wrong_domain_queries[ QueryNames.VALUE_KEY][domain_table]: return True, self._cached_wrong_domain_queries[ QueryNames.VALUE_KEY][domain_table][key] return False, None
class QualityRuleRegistry(metaclass=Singleton): """ Registry of supported quality rules. """ def __init__(self): self.logger = Logger() self.app = AppInterface() self.__quality_rules = dict() # {quality_rule_key1: QualityRule1, ...} # Register default quality rules self.register_quality_rule(QRValidateDataAgainstModel()) # QR_ILIVALIDATORR0001 self.register_quality_rule(QROverlappingBoundaryPoints()) # QR_IGACR1001 self.register_quality_rule(QRBoundaryPointsNotCoveredByBoundaryNodes()) # QR_IGACR1003 self.register_quality_rule(QROverlappingBoundaries()) # QR_IGACR2001 self.register_quality_rule(QROverlappingBuildings()) # QR_IGACR3002 self.register_quality_rule(QRGapsInPlots()) # QR_IGACR3006 self.register_quality_rule(QRMultiPartsInRightOfWay()) # QR_IGACR3007 self.register_quality_rule(QRParcelRightRelationship()) # QR_IGACR4001 self.register_quality_rule(QRParcelWithInvalidDepartmentCode()) # QR_IGACR4003 self.register_quality_rule(QRParcelWithInvalidMunicipalityCode()) # QR_IGACR4004 self.register_quality_rule(QRParcelWithInvalidParcelNumber()) # QR_IGACR4005 self.register_quality_rule(QRParcelWithInvalidPreviousParcelNumber()) # QR_IGACR4006 self.register_quality_rule(QRValidateNaturalParty()) # QR_IGACR4007 self.register_quality_rule(QRValidateLegalParty()) # QR_IGACR4008 self.register_quality_rule(QRDuplicateBoundaryPointRecords()) # QR_IGACR4011 self.register_quality_rule(QRDuplicateSurveyPointRecords()) # QR_IGACR4012 self.register_quality_rule(QRDuplicateControlPointRecords()) # QR_IGACR4013 self.register_quality_rule(QRDuplicateBoundaryRecords()) # QR_IGACR4014 self.register_quality_rule(QRDuplicateBuildingRecords()) # QR_IGACR4016 self.register_quality_rule(QRDuplicateBuildingUnitRecords()) # QR_IGACR4017 self.register_quality_rule(QRDuplicatePartyRecords()) # QR_IGACR4019 self.register_quality_rule(QRDuplicateRightRecords()) # QR_IGACR4020 self.register_quality_rule(QRDuplicateRestrictionRecords()) # QR_IGACR4021 self.register_quality_rule(QRDuplicateAdministrativeSourceRecords()) # QR_IGACR4022 def register_quality_rule(self, quality_rule): """ Registers a quality rule. :param quality_rule: QualityRule instance. :return: True if the quality rule was registered, False otherwise. """ if not isinstance(quality_rule, AbstractQualityRule): self.logger.warning(__name__, "The quality rule '{}' is not an 'AbstractQualityRule' instance!".format(quality_rule.id())) return False if not quality_rule.is_valid(): self.logger.warning(__name__, "The quality rule '{}' is not valid! Check the quality rule definition!".format( quality_rule.id())) return False if quality_rule.id() in self.__quality_rules: self.logger.warning(__name__, "The quality rule '{}' is already registered.".format(quality_rule.id())) return False self.__quality_rules[quality_rule.id()] = quality_rule self.logger.info(__name__, "Quality rule '{}' has been registered!".format(quality_rule.id())) return True def unregister_quality_rule(self, quality_rule_id): """ Unregisters a quality rule by id. :param quality_rule_id: Id of the quality rule to unregister. :return: True if the quality rule was unregistered, False otherwise. """ if quality_rule_id not in self.__quality_rules: self.logger.error(__name__, "Quality rule '{}' was not found in registered quality rules, therefore, it cannot be unregistered!".format(quality_rule_id)) return False self.__quality_rules[quality_rule_id] = None del self.__quality_rules[quality_rule_id] self.logger.info(__name__, "Quality rule '{}' has been unregistered!".format(quality_rule_id)) return True def get_quality_rule(self, quality_rule_id): qr = self.__quality_rules.get(quality_rule_id, None) if not qr: self.logger.warning(__name__, "Quality rule '{}' is not registered, therefore it cannot be obtained!".format(quality_rule_id)) return qr def get_qrs_per_role_and_models(self, db, as_dict=True): """ :param as_dict: Boolean. If False, the result is returned as a list or rule keys """ qrs = dict() role_registry = RoleRegistry() role_qrs = role_registry.get_role_quality_rules(role_registry.get_active_role()) if role_qrs == ALL_QUALITY_RULES: role_qrs = self.__quality_rules if role_qrs: db_models = db.get_models() model_registry = LADMColModelRegistry() for qr in role_qrs: # First check if the role QR is registered if qr in self.__quality_rules: # Then check if the models required by the QR are in the DB req_models = self.__quality_rules[qr].models() num_models = len(req_models) all_models_found = True if num_models: # We don't check models if a QR has no required models (e.g., iliValidator) for req_model in req_models: model = model_registry.model(req_model) model_key = model.full_name() if model_key and model_key not in db_models: all_models_found = False self.logger.debug(__name__, "Model '{}' not found in the DB. QR '{}' cannot be listed.".format( model_key, qr )) break if all_models_found: qrs[qr] = self.__quality_rules[qr] return qrs if as_dict else list(qrs.keys())