Esempio n. 1
0
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]
Esempio n. 2
0
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
Esempio n. 4
0
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!
Esempio n. 5
0
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
Esempio n. 6
0
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
Esempio n. 8
0
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())