class QualityRule:
    def __init__(self, quality_rule_data):
        self.__quality_rule_data = quality_rule_data
        self.logger = Logger()
        self._rule_id = None
        self._rule_name = None
        self._error_table_name = None
        self._error_table_fields = None
        self._error_codes = None

        self._initialize_quality_rule()

    def _initialize_quality_rule(self):
        self.logger.debug(
            __name__, "Registering quality rule '{}'...".format(
                self.__quality_rule_data.get(QUALITY_RULE_ID)))
        common_fields = [
            QgsField(QCoreApplication.translate("QualityRule", "tipo_error"),
                     QVariant.String),
            QgsField(QCoreApplication.translate("QualityRule", "codigo_error"),
                     QVariant.String)
        ]

        if self.__quality_rule_data:
            self._rule_id = self.__quality_rule_data.get(QUALITY_RULE_ID)
            self._rule_name = self.__quality_rule_data.get(QUALITY_RULE_NAME)
            self._error_table_name = self.__quality_rule_data.get(
                QUALITY_RULE_TABLE_NAME)
            self._error_table_fields = self.__quality_rule_data.get(
                QUALITY_RULE_TABLE_FIELDS)

            if self._error_table_fields:
                self._error_table_fields.extend(common_fields)

            self._error_codes = self.__quality_rule_data.get(
                QUALITY_RULE_DOMAIN_ERROR_CODES)

    @property
    def rule_id(self):
        return self._rule_id

    @property
    def rule_name(self):
        return self._rule_name

    @property
    def error_table_name(self):
        return self._error_table_name

    @property
    def error_table_fields(self):
        return self._error_table_fields

    @property
    def error_codes(self):
        return self._error_codes
Exemple #2
0
class XTFModelConverterController(QObject):

    progress_changed = pyqtSignal(int)  # Percentage

    def __init__(self):
        QObject.__init__(self)

        self.logger = Logger()
        self.app = AppInterface()

        self._converter_registry = XTFModelConverterRegistry()

    def get_converters(self, source_xtf):
        converters = dict()  # {converter_key: name}

        if source_xtf:
            # Get models present in the source XTF
            models = get_models_from_xtf(source_xtf)
            self.logger.debug(__name__,
                              "Models found in source XTF: {}".format(models))

            # Ask the registry for converters that apply for those models
            if models:
                converters = self._converter_registry.get_converters_for_models(
                    models)
                self.logger.debug(
                    __name__,
                    "Converters found for source XTF models: {}".format(
                        list(converters.keys())))

        return converters

    #def get_converter_parameter_widgets(self, converter_key):
    #    return self._converter_registry.get_converter_parameter_widgets(converter_key)  # List of widgets

    def convert(self, converter_key, source_xtf, target_xtf, params):
        converter = self._converter_registry.get_converter(converter_key)
        converter.progress_changed.connect(self.progress_changed)
        res, msg = converter.convert(source_xtf, target_xtf, params)

        return res, msg
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
Exemple #4
0
class STTaskManager(QObject):
    """
    Retrieve tasks for a user from the Transitional System's Task Service and store them during the session.
    """
    task_started = pyqtSignal(int)  # task_id
    task_canceled = pyqtSignal(int)  # task_id
    task_closed = pyqtSignal(int)  # task_id

    def __init__(self):
        QObject.__init__(self)
        self.logger = Logger()
        self.__registered_tasks = dict()
        self.st_config = TransitionalSystemConfig()

    @_with_override_cursor
    def __retrieve_tasks(self, st_user, task_type=None, task_status=None):
        headers = {
            'Authorization': "Bearer {}".format(st_user.get_token()),
            # 'User-Agent': "PostmanRuntime/7.20.1",
            'Accept': "*/*",
            'Cache-Control': "no-cache",
            # 'Postman-Token': "987c7fbf-af4d-42e8-adee-687f35f4a4a0,0547120a-6f8e-42a8-b97f-f052602cc7ff",
            # 'Host': "st.local:8090",
            'Accept-Encoding': "gzip, deflate",
            'Connection': "keep-alive",
            'cache-control': "no-cache"
        }

        try:
            self.logger.debug(__name__, "Retrieving tasks from server...")
            response = requests.request("GET", self.st_config.ST_GET_TASKS_SERVICE_URL, headers=headers)
        except requests.ConnectionError as e:
            msg = self.st_config.ST_CONNECTION_ERROR_MSG.format(e)
            self.logger.warning(__name__, msg)
            return False, msg

        status_OK = response.status_code == 200
        if status_OK:
            # Parse, create and register tasks
            response_data = json.loads(response.text)
            for task_data in response_data:
                task = STTask(task_data)
                if task.is_valid():
                    self.__register_task(task)
        else:
             if response.status_code == 500:
                 self.logger.warning(__name__, self.st_config.ST_STATUS_500_MSG)
             elif response.status_code > 500 and response.status_code < 600:
                 self.logger.warning(__name__, self.st_config.ST_STATUS_GT_500_MSG)
             elif response.status_code == 401:
                 self.logger.warning(__name__, self.st_config.ST_STATUS_401_MSG)

    def get_tasks(self, st_user, task_type=None, task_status=None):
        """
        Go to server for current tasks per user
        :param st_user:
        :param task_type: To filter task types. Still unused.
        :param task_status: To filter task statuses. Still unused.
        :return: dict of task ids with the corresponding task object
        """
        # Each call refreshes the registered tasks.
        self.unregister_tasks()
        self.__retrieve_tasks(st_user, task_type, task_status)

        return self.__registered_tasks

    def get_task(self, task_id):
        task = self.__registered_tasks[task_id] if task_id in self.__registered_tasks else None
        if task is None:
            self.logger.warning(__name__, "Task {} not found!!!".format(task_id))
        else:
            self.logger.info(__name__, "Task {} found!!!".format(task_id))
        return task

    def __register_task(self, task):
        self.logger.debug(__name__, "Task {} registered!".format(task.get_id()))
        self.__registered_tasks[task.get_id()] = task

    def __unregister_task(self, task_id):
        self.logger.debug(__name__, "Task {} unregistered!".format(task_id))
        self.__registered_tasks[task_id] = None
        del self.__registered_tasks[task_id]

    def unregister_tasks(self):
        for k,v in self.__registered_tasks.items():
            self.__registered_tasks[k] = None

        self.__registered_tasks = dict()
        self.logger.info(__name__, "All tasks have been unregistered!")

    @_with_override_cursor
    def start_task(self, st_user, task_id):
        payload = {}
        headers = {
            'Authorization': "Bearer {}".format(st_user.get_token()),
        }

        try:
            self.logger.debug(__name__, "Telling the server to start a task...")
            response = requests.request("PUT", self.st_config.ST_START_TASK_SERVICE_URL.format(task_id), headers=headers, data=payload)
        except requests.ConnectionError as e:
            msg = self.st_config.ST_CONNECTION_ERROR_MSG.format(e)
            self.logger.warning(__name__, msg)
            return False, msg

        status_OK = response.status_code == 200
        if status_OK:
            # Parse response
            response_data = json.loads(response.text)
            self.logger.info(__name__, "Task id '{}' started in server!...".format(task_id))
            self.logger.info_msg(__name__, QCoreApplication.translate("TaskManager",
                                                                      "The task '{}' was successfully started!".format(
                                                                          self.get_task(task_id).get_name())))
            self.update_task_info(task_id, response_data)
            self.task_started.emit(task_id)
        else:
            if response.status_code == 500:
                self.logger.warning(__name__, self.st_config.ST_STATUS_500_MSG)
            elif response.status_code > 500 and response.status_code < 600:
                self.logger.warning(__name__, self.st_config.ST_STATUS_GT_500_MSG)
            elif response.status_code == 401:
                self.logger.warning(__name__, self.st_config.ST_STATUS_401_MSG)
            else:
                self.logger.warning(__name__, "Status code not handled: {}".format(response.status_code))

    @_with_override_cursor
    def cancel_task(self, st_user, task_id, reason):
        payload = json.dumps({"reason": reason})
        headers = {
            'Authorization': "Bearer {}".format(st_user.get_token()),
            'Content-Type': 'application/json'
        }

        try:
            self.logger.debug(__name__, "Telling the server to cancel a task...")
            response = requests.request("PUT", self.st_config.ST_CANCEL_TASK_SERVICE_URL.format(task_id), headers=headers, data=payload)
        except requests.ConnectionError as e:
            msg = self.st_config.ST_CONNECTION_ERROR_MSG.format(e)
            self.logger.warning(__name__, msg)
            return False, msg

        status_OK = response.status_code == 200
        if status_OK:
            # No need to parse response this time, we'll ask tasks from server again anyways
            self.logger.info(__name__, "Task id '{}' canceled in server!".format(task_id))
            self.logger.info_msg(__name__, QCoreApplication.translate("TaskManager", "The task '{}' was successfully canceled!".format(self.get_task(task_id).get_name())))
            self.task_canceled.emit(task_id)
        else:
            if response.status_code == 500:
                self.logger.warning(__name__, self.st_config.ST_STATUS_500_MSG)
            elif response.status_code > 500 and response.status_code < 600:
                self.logger.warning(__name__, self.st_config.ST_STATUS_GT_500_MSG)
            elif response.status_code == 401:
                self.logger.warning(__name__, self.st_config.ST_STATUS_401_MSG)
            else:
                self.logger.warning(__name__, "Status code not handled: {}, payload: {}".format(response.status_code, payload))

    @_with_override_cursor
    def close_task(self, st_user, task_id):
        payload = {}
        headers = {
            'Authorization': "Bearer {}".format(st_user.get_token()),
        }

        try:
            self.logger.debug(__name__, "Telling the server to close a task...")
            response = requests.request("PUT", self.st_config.ST_CLOSE_TASK_SERVICE_URL.format(task_id), headers=headers, data=payload)
        except requests.ConnectionError as e:
            msg = self.st_config.ST_CONNECTION_ERROR_MSG.format(e)
            self.logger.warning(__name__, msg)
            return False, msg

        status_OK = response.status_code == 200
        if status_OK:
            # No need to parse response this time, we'll ask tasks from server again anyways
            self.logger.success(__name__, "Task id '{}' closed in server!".format(task_id))
            self.logger.success_msg(__name__, QCoreApplication.translate("TaskManager",
                                                                      "The task '{}' was successfully closed!".format(
                                                                          self.get_task(task_id).get_name())))
            self.task_closed.emit(task_id)
        else:
            if response.status_code == 500:
                self.logger.warning(__name__, self.st_config.ST_STATUS_500_MSG)
            elif response.status_code > 500 and response.status_code < 600:
                self.logger.warning(__name__, self.st_config.ST_STATUS_GT_500_MSG)
            elif response.status_code == 401:
                self.logger.warning(__name__, self.st_config.ST_STATUS_401_MSG)
            elif response.status_code == 422:
                response_data = json.loads(response.text)
                msg = QCoreApplication.translate("STSession", QCoreApplication.translate("TaskManager",
                    "Task not closed! Details: {}").format(response_data['message'] if 'message' in response_data else "Unreadable response from server."))
                self.logger.warning_msg(__name__, msg)
            else:
                self.logger.warning(__name__, "Status code not handled: {}".format(response.status_code))

    def update_task_info(self, task_id, task_data):
        task = STTask(task_data)
        if task.is_valid():
            self.__unregister_task(task_id)
            self.__register_task(task)
Exemple #5
0
class DBConnector(QObject):
    """
    Superclass for all DB connectors.
    """
    _DEFAULT_VALUES = dict(
    )  # You should set it, so that testing empty parameters can be handled easily.

    def __init__(self, uri, conn_dict=dict()):
        QObject.__init__(self)
        self.logger = Logger()
        self.engine = ''
        self.provider = ''  # QGIS provider name. e.g., postgres
        self._uri = None
        self.schema = None
        self.conn = None
        self._dict_conn_params = None
        self.names = DBMappingRegistry()
        self.__db_mapping = None  # To cache query response from the DB getting table and field names.

        # Flag to control whether the DB connector should update the name registry. It should be True in two scenarios:
        # 1) when the DB connector is created, and 2) when the plugin's active role has changed.
        self._should_update_db_mapping_values = True

        # Table/field names in the DB. Should be read only once per connector. Note: Only a list of names. No structure.
        self.__flat_table_and_field_names_for_testing_names = list()

        if uri is not None:
            self.uri = uri
        else:
            self.dict_conn_params = conn_dict

        self.model_parser = None

        self.__ladmcol_models = LADMColModelRegistry()

    @property
    def dict_conn_params(self):
        return self._dict_conn_params.copy()

    @dict_conn_params.setter
    def dict_conn_params(self, dict_values):
        dict_values = {k: v
                       for k, v in dict_values.items() if v
                       }  # To avoid empty values to overwrite default values
        self._dict_conn_params = self._DEFAULT_VALUES.copy()
        self._dict_conn_params.update(dict_values)
        self._uri = self.get_connection_uri(self._dict_conn_params, level=1)

    @property
    def uri(self):
        return self._uri

    @uri.setter
    def uri(self, value):
        raise NotImplementedError

    def equals(self, db):
        return self.dict_conn_params == db.dict_conn_params

    def _table_exists(self, table_name):
        raise NotImplementedError

    def _metadata_exists(self):
        raise NotImplementedError

    def _has_basket_col(self):
        raise NotImplementedError

    def close_connection(self):
        raise NotImplementedError

    def get_description(self):
        return "Current connection details: '{}' -> {} {}".format(
            self.engine, self._uri,
            'schema:{}'.format(self.schema) if self.schema else '')

    def get_ladm_units(self):
        raise NotImplementedError

    def get_models(self, schema=None):
        raise NotImplementedError

    def get_display_conn_string(self):
        # Do not use to connect to a DB, only for display purposes
        tmp_dict_conn_params = self._dict_conn_params.copy()
        if 'password' in tmp_dict_conn_params:
            del tmp_dict_conn_params['password']

        return ' '.join(
            ["{}={}".format(k, v) for k, v in tmp_dict_conn_params.items()])

    def get_description_conn_string(self):
        raise NotImplementedError

    def get_connection_uri(self, dict_conn, level=1):
        """
        :param dict_conn: (dict) dictionary with the parameters to establish a connection
        :param level: (int) At what level the connection will be established
            0: server level
            1: database level
        :return: (str) string uri to establish a connection
        """
        raise NotImplementedError

    def survey_model_exists(self):
        if self.read_model_parser():
            return self.model_parser.survey_model_exists()

        return False

    def valuation_model_exists(self):
        if self.read_model_parser():
            return self.model_parser.valuation_model_exists()

        return False

    def ladm_model_exists(self):
        if self.read_model_parser():
            return self.model_parser.ladm_model_exists()

        return False

    def cadastral_cartography_model_exists(self):
        if self.read_model_parser():
            return self.model_parser.cadastral_cartography_model_exists()

        return False

    def snr_data_model_exists(self):
        if self.read_model_parser():
            return self.model_parser.snr_data_model_exists()

        return False

    def supplies_integration_model_exists(self):
        if self.read_model_parser():
            return self.model_parser.supplies_integration_model_exists()

        return False

    def supplies_model_exists(self):
        if self.read_model_parser():
            return self.model_parser.supplies_model_exists()

        return False

    def ladm_col_model_exists(self, model_prefix):
        if self.read_model_parser():
            return self.model_parser.ladm_col_model_exists(model_prefix)

        return False

    def at_least_one_ladm_col_model_exists(self):
        if self.read_model_parser():
            return self.model_parser.at_least_one_ladm_col_model_exists()

        return False

    def read_model_parser(self):
        if self.model_parser is None:
            try:
                self.model_parser = ModelParser(self)
            except psycopg2.ProgrammingError as e:
                # if it is not possible to access the schema due to lack of privileges
                return False

        return True

    def is_ladm_layer(self, layer):
        raise NotImplementedError

    def get_ladm_layer_name(self, layer, validate_is_ladm=False):
        raise NotImplementedError

    def get_ili2db_version(self):
        raise NotImplementedError

    def get_db_mapping(self):
        """
        Cache the get_db_mapping call, which should be called only once per db connector. Also fills the
        table_and_field_names variable, which is very related to __db_mapping, but flattened.

        :return: A dict with table ilinames as keys and dict as values. See __get_db_mapping for details.
        """
        if not self.__db_mapping:
            self.__db_mapping = self.__get_db_mapping()
            self.__set_flat_table_and_field_names_for_testing_names()

        return self.__db_mapping

    def __get_db_mapping(self):
        """
        Get table and field names from the DB. Should be called only once for a single connection.

        :return: dict with table ilinames as keys and dict as values. The dicts found in the value contain field
                 ilinames as keys and sqlnames as values. The table name itself is added with the key 'table_name'.
                 Example:

            "LADM_COL.LADM_Nucleo.col_masCcl": {
                'table_name': 'col_masccl',
                'LADM_COL.LADM_Nucleo.col_masCcl.ccl_mas..Levantamiento_Catastral.Levantamiento_Catastral.LC_Lindero': 'ccl_mas',
                'LADM_COL.LADM_Nucleo.col_masCcl.ue_mas..Levantamiento_Catastral.Levantamiento_Catastral.LC_Construccion': 'ue_mas_lc_construccion',
                'LADM_COL.LADM_Nucleo.col_masCcl.ue_mas..Levantamiento_Catastral.Levantamiento_Catastral.LC_ServidumbreTransito': 'ue_mas_lc_servidumbretransito',
                'LADM_COL.LADM_Nucleo.col_masCcl.another_ili_attr': 'corresponding_sql_name'
            }
        """
        # Get both table and field names. Only include field names that are not FKs, they will be added in a second step
        records = self._get_table_and_field_names()

        dict_names = dict()
        for record in records:
            if record[QueryNames.TABLE_ILINAME] is None:
                # Any t_ili2db_* tables (INTERLIS meta-attrs)
                continue

            table_iliname = normalize_iliname(record[QueryNames.TABLE_ILINAME])

            if not table_iliname in dict_names:
                dict_names[table_iliname] = dict()
                dict_names[table_iliname][QueryNames.TABLE_NAME] = record[
                    QueryNames.TABLE_NAME]

            if record[QueryNames.FIELD_ILINAME] is None:
                # Fields for domains, like 'description' (we map it in a custom way later in this class method)
                continue

            field_iliname = normalize_iliname(record[QueryNames.FIELD_ILINAME])
            dict_names[table_iliname][field_iliname] = record[
                QueryNames.FIELD_NAME]

        # Map FK ilinames (i.e., those whose t_ili2db_attrname target column is not NULL)
        # Spatial_Unit-->Ext_Address_ID (Ext_Address)
        #   Key: "LADM_COL_V1_2.LADM_Nucleo.COL_UnidadEspacial.Ext_Direccion_ID"
        #   Values: lc_construccion_ext_direccion_id and  lc_terreno_ext_direccion_id
        records = self._get_fk_fields()
        for record in records:
            composed_key = "{}{}{}".format(
                normalize_iliname(record['iliname']), COMPOSED_KEY_SEPARATOR,
                normalize_iliname(record['iliname2']))
            table_iliname = normalize_iliname(record[QueryNames.TABLE_ILINAME])
            if table_iliname in dict_names:
                dict_names[table_iliname][composed_key] = record['sqlname']
            else:
                colowner = normalize_iliname(record['colowner'])
                if colowner in dict_names:
                    dict_names[colowner][composed_key] = record['sqlname']

        dict_names.update(self._get_ili2db_names(
        ))  # Now add ili2db names like t_id, t_ili_id, t_basket, etc.

        return dict_names

    def _get_table_and_field_names(self):
        """Gets both table and field names from DB. Only includes field names that are not FKs.

        Execute below Sql statement (pseudo-SQL):
        SELECT
          IliName AS {QueryNames.TABLE_ILINAME}, -- (1)
          table_name AS {QueryNames.TABLE_NAME}, -- (2)
          IliName AS {QueryNames.FIELD_ILINAME}, -- (3)
          SqlName AS {QueryNames.FIELD_NAME}     -- (4)
        FROM Dbms_tbl_metadata INNER JOIN T_ILI2DB_CLASSNAME LEFT JOIN T_ILI2DB_ATTRNAME
        WHERE ilicol.Target IS NULL (because it does not include FKs)

        *Dbms_tbl_metadata="Metadata table of specific DBMS"

        +-----------------+       +------------------+       +-----------------+
        |Dbms_tbl_metadata|       |T_ILI2DB_CLASSNAME|       |T_ILI2DB_ATTRNAME|
        |-----------------|       |------------------|       |-----------------|
        |table_name (2)+  +---+   |IliName   (1)     |       |IliName   (3)    |
        +--------------|--+   +---+SqlName           |       |SqlName   (4)    |
                       |          +------------------+   +---+ColOwner         |
                       |                                 |   |Target           |
                       +---------------------------------+   +-----------------+

        :return: dict
        """
        raise NotImplementedError

    def _get_fk_fields(self):
        """Maps FK ilinames (i.e., those whose t_ili2db_attrname target column is not NULL)

        i.e.
        Spatial_Unit-->Ext_Address_ID (Ext_Address)
        Key: "LADM_COL_V1_2.LADM_Nucleo.COL_UnidadEspacial.Ext_Direccion_ID"
        Values: lc_construccion_ext_direccion_id and  lc_terreno_ext_direccion_id

        Execute below Sql statement (seudo-SQL):
        SELECT  "iliname before the last point" as QueryNames.TABLE_ILINAME,
          iliname, -- (2)
          sqlname, -- (3)
          iliname as iliname2, (4)
          iliname as colowner
        FROM T_ILI2DB_CLASSNAME AS main_class INNER_JOIN T_ILI2DB_ATTRNAME INNER JOIN T_ILI2DB_CLASSNAME as target class

        +------------------+     +-----------------+     +------------------+
        |T_ILI2DB_CLASSNAME|     |T_ILI2DB_ATTRNAME|     |T_ILI2DB_CLASSNAME|
        |   (main class)   |     |-----------------|     |  (target class)  |
        |------------------|     |IliName   (1,2)  |     |------------------|
        |IliName   (5)     |     |SqlName   (3)    |     |IliName           |
        |SqlName    <------------+ColOwner         |  +-->SqlName    (4)    |
        +------------------+     |Target       +------+  +------------------+
                                 +-----------------+
        :return: dict
        """
        raise NotImplementedError

    def _get_ili2db_names(self):
        """Returns field common names of databases, e.g., T_Id, T_Ili_Tid, dispName, t_basket, etc.

        :return: Dictionary with ili2db keys:
                 T_ID_KEY, T_ILI_TID_KEY, etc., from db_mapping_registry
        """
        raise NotImplementedError

    def _initialize_names(self):
        """
        Gets table and field names from the DB and initializes the db mapping values in the registry.

        Could be called more than once per connector, namely, when the role changes, the db mapping registry values
        should be updated.
        """
        self.logger.info(__name__,
                         "Resetting db mapping registry values from the DB!")
        self.names.initialize_table_and_field_names(self.get_db_mapping())
        self._should_update_db_mapping_values = False  # Since we just updated, avoid it for the next test_connection.

        # self.logger.debug(__name__, "DEBUG DICT: {}".format(dict_names["Operacion.Operacion.OP_Derecho"]))

    def reset_db_mapping_values(self):
        """
        Call it to let the connector know it has to update the DB mapping values in the DB Mapping Registry (e.g., when
        the active role has changed, since the new one could support other models).
        """
        self._should_update_db_mapping_values = True

    def _get_flat_table_and_field_names_for_testing_names(self):
        return self.__flat_table_and_field_names_for_testing_names

    def __set_flat_table_and_field_names_for_testing_names(self):
        """
        Fill table_and_field_names list. Unlike __db_mapping, this one has no hierarchical structure (it's a list).

        Should be called only once per db connector.
        """
        # Fill table names
        ili2db_keys = self.names.ili2db_names.values()
        for k, v in self.__db_mapping.items():
            if k not in ili2db_keys:  # ili2db names will be handled by Names class
                self.__flat_table_and_field_names_for_testing_names.append(
                    k)  # Table names
                for k1, v1 in v.items():
                    if k1 != QueryNames.TABLE_NAME:
                        self.__flat_table_and_field_names_for_testing_names.append(
                            k1)  # Field names

    def check_db_models(self, required_models):
        res = True
        code = EnumTestConnectionMsg.DB_MODELS_ARE_CORRECT
        msg = ""

        if required_models:
            res, msg = self.check_required_models(required_models)
            if not res:
                code = EnumTestConnectionMsg.REQUIRED_LADM_MODELS_NOT_FOUND
        else:
            res = self.at_least_one_ladm_col_model_exists()
            if not res:
                code = EnumTestConnectionMsg.NO_LADM_MODELS_FOUND_IN_SUPPORTED_VERSION
                msg = QCoreApplication.translate(
                    "DBConnector",
                    "At least one LADM-COL model should exist in the required version! Supported models are: '{}', but you have '{}'"
                ).format(
                    ', '.join([
                        m.full_alias()
                        for m in self.__ladmcol_models.supported_models()
                    ]), ', '.join(self.get_models()))

        return res, code, msg

    def check_required_models(self, models):
        msg = QCoreApplication.translate("DBConnector",
                                         "All required models are in the DB!")

        not_found = [
            model for model in models if not self.ladm_col_model_exists(model)
        ]

        if not_found:
            msg = QCoreApplication.translate(
                "SettingsDialog",
                "The following required model(s) could not be found in the DB: {}."
            ).format(', '.join(not_found))

        return not bool(not_found), msg

    def open_connection(self):
        """
        :return: Whether the connection is opened after calling this method or not
        """
        raise NotImplementedError

    def test_connection(self,
                        test_level=EnumTestLevel.LADM,
                        user_level=EnumUserLevel.CONNECT,
                        required_models=[]):
        """
        'Template method' subclasses should overwrite it, proposing their own way to test a connection.
        """
        raise NotImplementedError

    def _test_connection_to_db(self):
        raise NotImplementedError

    def _test_connection_to_ladm(self, required_models):
        raise NotImplementedError

    def _db_should_have_basket_support(self):
        """
        :return: Tuple: Whether the current DB should have baskets or not, name of the 1st model that requires baskets!
        """
        # Get models in the DB that are supported and not hidden
        model_names_in_db = self.get_models()
        if model_names_in_db:
            for model in self.__ladmcol_models.supported_models():
                if not model.hidden() and model.full_name(
                ) in model_names_in_db:
                    params = model.get_ili2db_params(
                    )  # Note: params depend on the model and on the active role
                    if ILI2DB_SCHEMAIMPORT in params:
                        for param in params[
                                ILI2DB_SCHEMAIMPORT]:  # List of tuples
                            if param[
                                    0] == ILI2DB_CREATE_BASKET_COL_KEY:  # param: (option, value)
                                self.logger.debug(
                                    __name__,
                                    "Model '{}' requires baskets...".format(
                                        model.alias()))
                                return True, model.alias()

        return False, ''
class TaskPanelWidget(QgsPanelWidget, WIDGET_UI):
    trigger_action_emitted = pyqtSignal(str)  # action tag

    def __init__(self, task_id, parent):
        QgsPanelWidget.__init__(self, parent)
        self.setupUi(self)
        self.session = STSession()
        self._task = self.session.task_manager.get_task(task_id)
        self.parent = parent
        self.logger = Logger()
        self.st_config = TransitionalSystemConfig()

        self.setDockMode(True)
        self.setPanelTitle(
            QCoreApplication.translate("TaskPanelWidget", "Task details"))

        self.trw_task_steps.itemDoubleClicked.connect(self.trigger_action)
        self.trw_task_steps.itemChanged.connect(self.update_step_controls)
        self.session.task_manager.task_started.connect(self.update_task)
        self.session.task_manager.task_canceled.connect(self.acceptPanel)
        self.session.task_manager.task_closed.connect(self.acceptPanel)

        self.btn_start_task.clicked.connect(self.start_task)
        self.btn_cancel_task.clicked.connect(self.cancel_task)
        self.btn_close_task.clicked.connect(self.close_task)

        self.initialize_gui()

    def initialize_gui(self):
        self.show_task_description()
        self.show_task_steps()
        self.update_controls()

    def show_task_description(self):
        if self._task is not None:
            self.logger.debug(__name__,
                              "Setting task description in Task Panel...")
            self.lbl_name.setText(self._task.get_name())
            self.lbl_description.setText(self._task.get_description())
            self.lbl_created_at.setText(
                QCoreApplication.translate("TaskPanelWidget",
                                           "Created at: {}").format(
                                               self._task.get_creation_date()))
            if self._task.get_status() == EnumSTTaskStatus.STARTED.value:
                self.lbl_started_at.setVisible(True)
                self.lbl_started_at.setText(
                    QCoreApplication.translate(
                        "TaskPanelWidget", "Started at: {}").format(
                            self._task.get_started_date()))
            else:
                self.lbl_started_at.setVisible(False)
            self.lbl_deadline.setText(
                QCoreApplication.translate("TaskPanelWidget",
                                           "Deadline: {}").format(
                                               self._task.get_deadline()))
            self.lbl_status.setText(self._task.get_status())

            # Styles
            self.lbl_name.setStyleSheet(self.st_config.TASK_TITLE_BIG_TEXT_CSS)
            if self._task.get_status() == EnumSTTaskStatus.ASSIGNED.value:
                self.lbl_status.setStyleSheet(
                    self.st_config.TASK_ASSIGNED_STATUS_BIG_TEXT_CSS)
            elif self._task.get_status() == EnumSTTaskStatus.STARTED.value:
                self.lbl_status.setStyleSheet(
                    self.st_config.TASK_STARTED_STATUS_BIG_TEXT_CSS)

    def show_task_steps(self):
        self.trw_task_steps.clear()
        steps = self._task.get_steps()
        self.logger.debug(
            __name__,
            "Showing task steps in Task Panel. {} task steps found: {}.".
            format(len(steps), ", ".join([s.get_name() for s in steps])))

        for i, step in enumerate(steps):
            children = []
            step_item = QTreeWidgetItem([
                QCoreApplication.translate("TaskPanelWidget",
                                           "Step {}").format(i + 1)
            ])
            step_item.setData(0, Qt.BackgroundRole, QBrush(GRAY_COLOR))
            step_item.setToolTip(0, step.get_name())
            step_item.setCheckState(
                0, Qt.Checked if step.get_status() else Qt.Unchecked)

            action_item = QTreeWidgetItem([step.get_name()])
            action_item.setData(0, Qt.UserRole, step.get_id())
            action_item.setIcon(
                0, QIcon(":/Asistente-LADM-COL/resources/images/process.svg"))
            action_item.setToolTip(0, step.get_description())
            step_item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
            children.append(action_item)

            step_item.addChildren(children)
            self.trw_task_steps.addTopLevelItem(step_item)

        for i in range(self.trw_task_steps.topLevelItemCount()):
            self.trw_task_steps.topLevelItem(
                i).setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable
                            | Qt.ItemIsSelectable)
            self.trw_task_steps.topLevelItem(i).setExpanded(True)

    def trigger_action(self, item, column):
        """
        Triggers an action linked to the task step. Action config comes from TaskStepsConfig, and it can be either a
        default action slot or a customized action slot (even passing a task context).

        :param item: QTreeWidgetItem that is linked to an action, (i.e., a child item)
        :param column: 0
        """
        # Make sure we have a child item and only trigger if parent is not checked yet (i.e., step is not done yet)
        if not item.childCount() and item.parent().checkState(
                column) == Qt.Unchecked:
            step_id = item.data(column, Qt.UserRole)
            step = self._task.get_step(step_id)
            if step:
                slot = step.get_custom_action_slot()
                if slot:  # Custom action call
                    self.logger.info(
                        __name__,
                        "Executing step action with custom parameters...")
                    if SLOT_CONTEXT in slot and slot[SLOT_CONTEXT]:
                        slot[SLOT_CONTEXT].set_slot_on_result(
                            partial(self.set_item_enabled, item.parent()))
                        slot[SLOT_NAME](
                            slot[SLOT_CONTEXT],
                            **slot[SLOT_PARAMS])  # Call passing task context
                    else:
                        slot[SLOT_NAME](**slot[SLOT_PARAMS])
                else:  # Default action call
                    self.logger.info(__name__, "Executing default action...")
                    self.trigger_action_emitted.emit(step.get_action_tag())

    def set_item_enabled(self, item, enable):
        """
        Slot to react upon getting the result of an action (dialog) linked to a task step

        :param item: Checkable QTreeWidgetItem (i.e., it's the parent, not the child that triggers the action)
        :param enable: Whether the task step was successfully run or not
        """
        item.setCheckState(0, Qt.Checked if enable else Qt.Unchecked)

    def set_item_style(self, item, column):
        color = CHECKED_COLOR if item.checkState(
            column) == Qt.Checked else UNCHECKED_COLOR
        item.setData(column, Qt.BackgroundRole, QBrush(color))

        for index in range(item.childCount()):
            color = GRAY_COLOR if item.checkState(
                column) == Qt.Checked else Qt.black
            item.child(index).setData(column, Qt.ForegroundRole, QBrush(color))

    def update_step_controls(self, item, column):
        if item.childCount():  # Only do this for parents
            self.trw_task_steps.blockSignals(True)
            self.set_item_style(item, column)
            self.save_task_steps_status(column)
            self.trw_task_steps.blockSignals(False)

        self.update_close_control()

    def update_controls(self):
        # Steps panel
        self.trw_task_steps.setEnabled(
            self._task.get_status() == EnumSTTaskStatus.STARTED.value)

        # Start task button
        self.btn_start_task.setEnabled(
            self._task.get_status() == EnumSTTaskStatus.ASSIGNED.value)

        # Cancel task button
        self.btn_cancel_task.setEnabled(
            self._task.get_status() == EnumSTTaskStatus.STARTED.value)

        self.update_close_control()

    def update_close_control(self):
        # Can we close the task?
        self.btn_close_task.setEnabled(
            self._task.get_status() == EnumSTTaskStatus.STARTED.value
            and self.steps_complete())
        if self._task.get_status(
        ) == EnumSTTaskStatus.STARTED.value and self.steps_complete():
            self.btn_close_task.setToolTip("")
        elif self._task.get_status(
        ) != EnumSTTaskStatus.STARTED.value and self.steps_complete():
            self.btn_close_task.setToolTip(
                QCoreApplication.translate(
                    "TaskPanelWidget",
                    "The task is not started yet, hence, it cannot be closed.")
            )
        else:  # The remaining 2 cases: steps incomplete (whether the task is started or not)
            self.btn_close_task.setToolTip(
                QCoreApplication.translate(
                    "TaskPanelWidget",
                    "You should complete the steps before closing the task."))

    def steps_complete(self):
        """
        :return: boolean --> Can we close the task
        """
        return self._task.steps_complete()

    def save_task_steps_status(self, column):
        steps_status = dict()
        for i in range(self.trw_task_steps.topLevelItemCount()):
            steps_status[i + 1] = self.trw_task_steps.topLevelItem(
                i).checkState(column) == Qt.Checked

        # Don't save if not necessary
        status = QSettings().value(
            "Asistente-LADM-COL/transitional_system/tasks/{}/step_status".
            format(self._task.get_id()), "{}")
        if status != json.dumps(steps_status):
            self._task.save_steps_status(steps_status)

    def start_task(self):
        self.session.task_manager.start_task(self.session.get_logged_st_user(),
                                             self._task.get_id())
        self.update_controls()

    def cancel_task(self):
        reply = QMessageBox.question(
            self, QCoreApplication.translate("TaskPanelWidget", "Confirm"),
            QCoreApplication.translate(
                "TaskPanelWidget",
                "Are you sure you want to cancel the task '{}'?").format(
                    self._task.get_name()), QMessageBox.Yes, QMessageBox.No)
        if reply == QMessageBox.Yes:
            dlg = STCancelTaskDialog(self.parent)
            res = dlg.exec_()
            if res == QDialog.Accepted:
                if dlg.comments:
                    self.session.task_manager.cancel_task(
                        self.session.get_logged_st_user(), self._task.get_id(),
                        dlg.comments)
            else:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate("TaskPanelWidget",
                                               "The task was not canceled."))

    def close_task(self):
        reply = QMessageBox.question(
            self, QCoreApplication.translate("TaskPanelWidget", "Confirm"),
            QCoreApplication.translate(
                "TaskPanelWidget",
                "Are you sure you want to close the task '{}'?").format(
                    self._task.get_name()), QMessageBox.Yes, QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.session.task_manager.close_task(
                self.session.get_logged_st_user(), self._task.get_id())

    def update_task(self, task_id):
        """A task changed in the Task Manager, so, update the base task for the panel and update the panel itself"""
        self._task = self.session.task_manager.get_task(task_id)
        self.initialize_gui()
Exemple #7
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!
Exemple #8
0
class Dependency(QObject):
    download_dependency_completed = pyqtSignal()
    download_dependency_progress_changed = pyqtSignal(int)  # progress

    def __init__(self):
        QObject.__init__(self)
        self.logger = Logger()
        self._downloading = False
        self._show_cursor = True
        self.dependency_name = ""
        self.fetcher_task = None

    def download_dependency(self, uri):
        if not uri:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "Dependency", "Invalid URL to download dependency."))

        self.logger.clear_message_bar()
        is_valid = self.check_if_dependency_is_valid()

        if is_valid:
            self.logger.debug(
                __name__,
                QCoreApplication.translate(
                    "Dependency",
                    "The {} dependency is already valid, so it won't be downloaded! (Dev, why did you asked to download it :P?)"
                    .format(self.dependency_name)))
            return

        if not self._downloading:  # Already downloading dependency?
            if is_connected(TEST_SERVER):
                self._downloading = True
                self.logger.clear_message_bar()
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "Dependency",
                        "A {} dependency will be downloaded...".format(
                            self.dependency_name)))
                self.fetcher_task = QgsNetworkContentFetcherTask(QUrl(uri))
                self.fetcher_task.begun.connect(self._task_begun)
                self.fetcher_task.progressChanged.connect(
                    self._task_progress_changed)
                self.fetcher_task.fetched.connect(
                    partial(self._save_dependency_file, self.fetcher_task))
                self.fetcher_task.taskCompleted.connect(self._task_completed)
                QgsApplication.taskManager().addTask(self.fetcher_task)
            else:
                self.logger.clear_message_bar()
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "Dependency",
                        "There was a problem connecting to Internet."))
                self._downloading = False

    def check_if_dependency_is_valid(self):
        raise NotImplementedError

    def _task_begun(self):
        if self._show_cursor:
            QApplication.setOverrideCursor(Qt.WaitCursor)

    def _task_progress_changed(self, progress):
        self.download_dependency_progress_changed.emit(progress)

    def _save_dependency_file(self, fetcher_task):
        raise NotImplementedError

    def _task_completed(self):
        if self._show_cursor:
            QApplication.restoreOverrideCursor()
        self.download_dependency_completed.emit()
class DBEngineGUIConfig(QObject, metaclass=SingletonQObject):
    """
    Action configuration for each DB engine.

    These lists might be modified by add-ons. We expect add-ons to add action
    keys to the lists and don't expect them to remove elements from the lists.
    Action keys might live in these lists even if an add-on is uninstalled.
    The add-on should unregister all its actions in gui_builder and that's enough.
    """
    __COMMON_ACTIONS = [
        ACTION_SETTINGS,
        ACTION_HELP,
        ACTION_ABOUT,
        ACTION_DOWNLOAD_GUIDE,
        ACTION_FINALIZE_GEOMETRY_CREATION,
        ACTION_BUILD_BOUNDARY,
        ACTION_MOVE_NODES,
        ACTION_FILL_BFS,
        ACTION_FILL_MORE_BFS_AND_LESS,
        ACTION_FILL_RIGHT_OF_WAY_RELATIONS,
        ACTION_IMPORT_FROM_INTERMEDIATE_STRUCTURE,
        ACTION_ST_LOGIN,
        ACTION_ST_LOGOUT,
        ACTION_ST_UPLOAD_XTF,
        ACTION_CREATE_BOUNDARY,
        ACTION_CREATE_POINT,
        ACTION_CREATE_PLOT,
        ACTION_CREATE_BUILDING,
        ACTION_CREATE_BUILDING_UNIT,
        ACTION_CREATE_RIGHT_OF_WAY,
        ACTION_CREATE_EXT_ADDRESS,
        ACTION_CREATE_PARCEL,
        ACTION_CREATE_PARTY,
        ACTION_CREATE_GROUP_PARTY,
        ACTION_CREATE_RIGHT,
        ACTION_CREATE_RESTRICTION,
        ACTION_CREATE_ADMINISTRATIVE_SOURCE,
        ACTION_CREATE_SPATIAL_SOURCE,
        ACTION_UPLOAD_PENDING_SOURCE,
        ACTION_SCHEMA_IMPORT,
        ACTION_IMPORT_DATA,
        ACTION_EXPORT_DATA,
        ACTION_XTF_MODEL_CONVERTER,
        ACTION_LOAD_LAYERS,
        ACTION_FIX_LADM_COL_RELATIONS,
        ACTION_CHANGE_DETECTION_PER_PARCEL,
        ACTION_CHANGE_DETECTION_ALL_PARCELS,
        ACTION_PARCEL_QUERY,
        # ACTION_REPORT_ANNEX_17,
        # ACTION_REPORT_ANT,
        ACTION_CHANGE_DETECTION_SETTINGS,
        ACTION_CHECK_QUALITY_RULES,
        ACTION_ALLOCATE_PARCELS_FIELD_DATA_CAPTURE,
        ACTION_SYNCHRONIZE_FIELD_DATA
    ]

    __GPKG_ACTIONS = __COMMON_ACTIONS + [
        ACTION_RUN_ETL_SUPPLIES, ACTION_FIND_MISSING_COBOL_SUPPLIES,
        ACTION_FIND_MISSING_SNC_SUPPLIES, ACTION_INTEGRATE_SUPPLIES
    ]

    __PG_ACTIONS = [ALL_ACTIONS]

    __MSSQL_ACTIONS = __COMMON_ACTIONS

    def __init__(self):
        QObject.__init__(self)
        self.logger = Logger()

    def add_actions_to_db_engines(self, action_key_list, db_engine_key_list):
        """
        For add-ons that want to modify actions of supported DB engines.
        If a supported DB engine is missing in the list, the actions will
        be disabled for DB connections that correspond to such engine.

        Note: All actions should support at least PostgreSQL.

        :param action_key_list: List of action keys to add.
        :param db_engine_key_list: List of DB engines in which the action should work. Possible values: 'pg', 'gpkg',
                                   'myssql'.
        """
        for engine in db_engine_key_list:
            engine_actions = getattr(
                self, "_DBEngineGUIConfig__{}_ACTIONS".format(engine.upper()),
                None)
            if engine_actions:
                setattr(
                    self,
                    "_DBEngineGUIConfig__{}_ACTIONS".format(engine.upper()),
                    list(set(engine_actions + action_key_list)))
                self.logger.debug(
                    __name__, "{} actions added to DB engine '{}'!".format(
                        len(action_key_list), engine))

    def get_db_engine_actions(self, engine):
        """
        Gets a GUI config dict for both Toolbars and Menus.

        :param name: Either TEMPLATE_GUI or DEFAULT_GUI (or more if GUI_Congif() has more dict keys)
        :return: A deep copy (i.e., it's safe to alter it) of the GUI config dictionary
        """
        return getattr(self,
                       "_DBEngineGUIConfig__{}_ACTIONS".format(engine.upper()),
                       self.__COMMON_ACTIONS)
class SettingsDialog(QDialog, DIALOG_UI):
    db_connection_changed = pyqtSignal(DBConnector, bool,
                                       str)  # dbconn, ladm_col_db, source
    active_role_changed = pyqtSignal()

    def __init__(self, parent=None, qgis_utils=None, conn_manager=None):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        self.logger = Logger()
        self.conn_manager = conn_manager
        self._db = None
        self.qgis_utils = qgis_utils
        self.db_source = COLLECTED_DB_SOURCE  # default db source
        self._required_models = list()
        self._tab_pages_list = list()
        self.init_db_engine = None

        self._action_type = None
        self.dbs_supported = ConfigDbSupported()

        self.online_models_radio_button.setChecked(True)
        self.online_models_radio_button.toggled.connect(
            self.model_provider_toggle)
        self.custom_model_directories_line_edit.setText("")
        self.custom_models_dir_button.clicked.connect(
            self.show_custom_model_dir)
        self.custom_model_directories_line_edit.setVisible(False)
        self.custom_models_dir_button.setVisible(False)

        # Set connections
        self.buttonBox.accepted.disconnect()
        self.buttonBox.accepted.connect(self.accepted)
        self.buttonBox.helpRequested.connect(self.show_help)
        self.finished.connect(self.finished_slot)
        self.btn_test_connection.clicked.connect(self.test_connection)
        self.btn_test_ladm_col_structure.clicked.connect(
            self.test_ladm_col_structure)

        self.btn_test_service.clicked.connect(self.test_service)
        self.btn_test_service_transitional_system.clicked.connect(
            self.test_service_transitional_system)

        self.btn_default_value_sources.clicked.connect(
            self.set_default_value_source_service)
        self.btn_default_value_transitional_system.clicked.connect(
            self.set_default_value_transitional_system_service)

        self.chk_use_roads.toggled.connect(self.update_images_state)

        self.bar = QgsMessageBar()
        self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop)

        self.cbo_db_engine.clear()

        self._lst_db = self.dbs_supported.get_db_factories()
        self._lst_panel = dict()

        for key, value in self._lst_db.items():
            self.cbo_db_engine.addItem(value.get_name(), key)
            self._lst_panel[key] = value.get_config_panel(self)
            self._lst_panel[key].notify_message_requested.connect(
                self.show_message)
            self.db_layout.addWidget(self._lst_panel[key])

        self.db_engine_changed()

        # Trigger some default behaviours
        self.restore_db_source_settings(
        )  # restore settings with default db source
        self.restore_settings()

        self.roles = Role_Registry()
        self.load_roles()

        self.cbo_db_engine.currentIndexChanged.connect(self.db_engine_changed)
        self.rejected.connect(self.close_dialog)

    def set_db_source(self, db_source):
        self.db_source = db_source
        self.restore_db_source_settings()

    def set_tab_pages_list(self, tab_pages_list):
        self._tab_pages_list = tab_pages_list
        self.show_tabs(tab_pages_list)

    def set_required_models(self, required_models):
        self._required_models = required_models

    def show_tabs(self, tab_pages_list):
        """
        Show only those tabs that are listed in tab_pages_list, if any. If it's an empty list, show all tabs.
        """
        if tab_pages_list:
            for i in reversed(range(self.tabWidget.count())):
                if i not in tab_pages_list:
                    self.tabWidget.removeTab(i)

    def load_roles(self):
        """
        Initialize group box for selecting the active role
        """
        self.gbx_active_role_layout = QVBoxLayout()
        dict_roles = self.roles.get_roles_info()
        checked = False
        active_role = self.roles.get_active_role()

        # Initialize radio buttons
        for k, v in dict_roles.items():
            radio = QRadioButton(v)
            radio.setToolTip(self.roles.get_role_description(k))

            if not checked:
                if k == active_role:
                    radio.setChecked(True)
                    checked = True

            self.gbx_active_role_layout.addWidget(radio)

        self.gbx_active_role.setLayout(self.gbx_active_role_layout)

    def close_dialog(self):
        self.close()

    def showEvent(self, event):
        # It is necessary to reload the variables
        # to load the database and schema name
        self.restore_settings()

        self.btn_test_ladm_col_structure.setVisible(
            self._action_type != EnumDbActionType.SCHEMA_IMPORT)

    def model_provider_toggle(self):
        if self.offline_models_radio_button.isChecked():
            self.custom_model_directories_line_edit.setVisible(True)
            self.custom_models_dir_button.setVisible(True)
        else:
            self.custom_model_directories_line_edit.setVisible(False)
            self.custom_models_dir_button.setVisible(False)
            self.custom_model_directories_line_edit.setText("")

    def _get_db_connector_from_gui(self):
        current_db_engine = self.cbo_db_engine.currentData()
        params = self._lst_panel[current_db_engine].read_connection_parameters(
        )
        db = self._lst_db[current_db_engine].get_db_connector(params)
        return db

    def get_db_connection(self):
        if self._db is not None:
            self.logger.info(__name__, "Returning existing db connection...")
        else:
            self.logger.info(__name__, "Getting new db connection...")
            self._db = self._get_db_connector_from_gui()
            self._db.open_connection()

        return self._db

    def show_custom_model_dir(self):
        dlg = CustomModelDirDialog(
            self.custom_model_directories_line_edit.text(), self)
        dlg.exec_()

    def accepted(self):
        """
        First check if connection to DB/schema is valid, if not, block the dialog.
        If valid, check it complies with LADM. If not, block the dialog. If it complies, we have two options: To emit
        db_connection changed or not. Finally, we store options in QSettings.
        """
        ladm_col_schema = False

        db = self._get_db_connector_from_gui()

        test_level = EnumTestLevel.DB_SCHEMA

        if self._action_type == EnumDbActionType.SCHEMA_IMPORT:
            # Limit the validation (used in GeoPackage)
            test_level |= EnumTestLevel.SCHEMA_IMPORT

        res, code, msg = db.test_connection(
            test_level
        )  # No need to pass required_models, we don't test that much

        if res:
            if self._action_type != EnumDbActionType.SCHEMA_IMPORT:
                # Only check LADM-schema if we are not in an SCHEMA IMPORT.
                # We know in an SCHEMA IMPORT, at this point the schema is still not LADM.
                ladm_col_schema, code, msg = db.test_connection(
                    EnumTestLevel.LADM, required_models=self._required_models)

            if not ladm_col_schema and self._action_type != EnumDbActionType.SCHEMA_IMPORT:
                self.show_message(msg, Qgis.Warning)
                return  # Do not close the dialog

        else:
            self.show_message(msg, Qgis.Warning)
            return  # Do not close the dialog

        # Connection is valid and complies with LADM
        current_db_engine = self.cbo_db_engine.currentData()
        if self._lst_panel[current_db_engine].state_changed(
        ) or self.init_db_engine != current_db_engine:
            # Emit db_connection_changed
            if self._db is not None:
                self._db.close_connection()

            self._db = db

            # Update db connect with new db conn
            self.conn_manager.set_db_connector_for_source(
                self._db, self.db_source)

            # Emmit signal when db source changes
            self.db_connection_changed.emit(self._db, ladm_col_schema,
                                            self.db_source)
            self.logger.debug(
                __name__, "Settings dialog emitted a db_connection_changed.")

        # Save settings from tabs other than database connection
        self.save_settings()
        QDialog.accept(self)

        # If active role changed, refresh the GUI
        selected_role = self.get_selected_role()
        if self.roles.get_active_role() != selected_role:
            self.logger.info(
                __name__,
                "The active role has changed from '{}' to '{}'.".format(
                    self.roles.get_active_role(), selected_role))
            self.roles.set_active_role(selected_role)
            self.active_role_changed.emit()

        self.close()

    def get_selected_role(self):
        selected_role = None
        radio_checked = None
        for i in range(self.gbx_active_role_layout.count()):
            radio = self.gbx_active_role_layout.itemAt(i).widget()
            if radio.isChecked():
                radio_checked = radio.text()
                break

        for k, v in self.roles.get_roles_info().items():
            if v == radio_checked:
                selected_role = k
                break

        return selected_role  # Role key

    def reject(self):
        self.done(0)

    def finished_slot(self, result):
        self.bar.clearWidgets()

    def save_settings(self):
        settings = QSettings()
        current_db_engine = self.cbo_db_engine.currentData()
        settings.setValue(
            'Asistente-LADM_COL/db/{db_source}/db_connection_engine'.format(
                db_source=self.db_source), current_db_engine)
        dict_conn = self._lst_panel[
            current_db_engine].read_connection_parameters()

        self._lst_db[current_db_engine].save_parameters_conn(
            dict_conn=dict_conn, db_source=self.db_source)

        settings.setValue(
            'Asistente-LADM_COL/models/custom_model_directories_is_checked',
            self.offline_models_radio_button.isChecked())
        if self.offline_models_radio_button.isChecked():
            settings.setValue('Asistente-LADM_COL/models/custom_models',
                              self.custom_model_directories_line_edit.text())

        settings.setValue('Asistente-LADM_COL/quality/use_roads',
                          self.chk_use_roads.isChecked())

        settings.setValue(
            'Asistente-LADM_COL/automatic_values/automatic_values_in_batch_mode',
            self.chk_automatic_values_in_batch_mode.isChecked())
        settings.setValue('Asistente-LADM_COL/sources/document_repository',
                          self.connection_box.isChecked())

        settings.setValue(
            'Asistente-LADM_COL/advanced_settings/validate_data_importing_exporting',
            self.chk_validate_data_importing_exporting.isChecked())

        endpoint_transitional_system = self.txt_service_transitional_system.text(
        ).strip()
        settings.setValue(
            'Asistente-LADM_COL/sources/service_transitional_system',
            (endpoint_transitional_system[:-1]
             if endpoint_transitional_system.endswith('/') else
             endpoint_transitional_system)
            or TransitionalSystemConfig().ST_DEFAULT_DOMAIN)

        endpoint = self.txt_service_endpoint.text().strip()
        settings.setValue(
            'Asistente-LADM_COL/sources/service_endpoint',
            (endpoint[:-1] if endpoint.endswith('/') else endpoint)
            or DEFAULT_ENDPOINT_SOURCE_SERVICE)

        # Changes in automatic namespace or local_id configuration?
        current_namespace_enabled = settings.value(
            'Asistente-LADM_COL/automatic_values/namespace_enabled', True,
            bool)
        current_namespace_prefix = settings.value(
            'Asistente-LADM_COL/automatic_values/namespace_prefix', "")
        current_local_id_enabled = settings.value(
            'Asistente-LADM_COL/automatic_values/local_id_enabled', True, bool)

        settings.setValue(
            'Asistente-LADM_COL/automatic_values/namespace_enabled',
            self.namespace_collapsible_group_box.isChecked())
        if self.namespace_collapsible_group_box.isChecked():
            settings.setValue(
                'Asistente-LADM_COL/automatic_values/namespace_prefix',
                self.txt_namespace.text())

        settings.setValue(
            'Asistente-LADM_COL/automatic_values/local_id_enabled',
            self.chk_local_id.isChecked())

        if current_namespace_enabled != self.namespace_collapsible_group_box.isChecked() or \
           current_namespace_prefix != self.txt_namespace.text() or \
           current_local_id_enabled != self.chk_local_id.isChecked():
            if self._db is not None:
                self.qgis_utils.automatic_namespace_local_id_configuration_changed(
                    self._db)

    def restore_db_source_settings(self):
        settings = QSettings()
        default_db_engine = self.dbs_supported.id_default_db

        self.init_db_engine = settings.value(
            'Asistente-LADM_COL/db/{db_source}/db_connection_engine'.format(
                db_source=self.db_source), default_db_engine)
        index_db_engine = self.cbo_db_engine.findData(self.init_db_engine)

        if index_db_engine == -1:
            index_db_engine = self.cbo_db_engine.findData(default_db_engine)

        self.cbo_db_engine.setCurrentIndex(index_db_engine)
        self.db_engine_changed()

        # restore db settings for all panels
        for db_engine, db_factory in self._lst_db.items():
            dict_conn = db_factory.get_parameters_conn(self.db_source)
            self._lst_panel[db_engine].write_connection_parameters(dict_conn)
            self._lst_panel[db_engine].save_state()

    def restore_settings(self):
        # Restore QSettings
        settings = QSettings()

        custom_model_directories_is_checked = settings.value(
            'Asistente-LADM_COL/models/custom_model_directories_is_checked',
            DEFAULT_USE_CUSTOM_MODELS,
            type=bool)
        if custom_model_directories_is_checked:
            self.offline_models_radio_button.setChecked(True)
            self.custom_model_directories_line_edit.setText(
                settings.value('Asistente-LADM_COL/models/custom_models',
                               DEFAULT_MODELS_DIR))
            self.custom_model_directories_line_edit.setVisible(True)
            self.custom_models_dir_button.setVisible(True)
        else:
            self.online_models_radio_button.setChecked(True)
            self.custom_model_directories_line_edit.setText("")
            self.custom_model_directories_line_edit.setVisible(False)
            self.custom_models_dir_button.setVisible(False)

        use_roads = settings.value('Asistente-LADM_COL/quality/use_roads',
                                   True, bool)
        self.chk_use_roads.setChecked(use_roads)
        self.update_images_state(use_roads)

        self.chk_automatic_values_in_batch_mode.setChecked(
            settings.value(
                'Asistente-LADM_COL/automatic_values/automatic_values_in_batch_mode',
                True, bool))
        self.connection_box.setChecked(
            settings.value('Asistente-LADM_COL/sources/document_repository',
                           True, bool))
        self.namespace_collapsible_group_box.setChecked(
            settings.value(
                'Asistente-LADM_COL/automatic_values/namespace_enabled', True,
                bool))
        self.chk_local_id.setChecked(
            settings.value(
                'Asistente-LADM_COL/automatic_values/local_id_enabled', True,
                bool))
        self.txt_namespace.setText(
            str(
                settings.value(
                    'Asistente-LADM_COL/automatic_values/namespace_prefix',
                    "")))

        self.chk_validate_data_importing_exporting.setChecked(
            settings.value(
                'Asistente-LADM_COL/advanced_settings/validate_data_importing_exporting',
                True, bool))

        self.txt_service_transitional_system.setText(
            settings.value(
                'Asistente-LADM_COL/sources/service_transitional_system',
                TransitionalSystemConfig().ST_DEFAULT_DOMAIN))
        self.txt_service_endpoint.setText(
            settings.value('Asistente-LADM_COL/sources/service_endpoint',
                           DEFAULT_ENDPOINT_SOURCE_SERVICE))

    def db_engine_changed(self):
        if self._db is not None:
            self._db.close_connection()

        self._db = None  # Reset db connection

        for key, value in self._lst_panel.items():
            value.setVisible(False)

        current_db_engine = self.cbo_db_engine.currentData()

        self._lst_panel[current_db_engine].setVisible(True)

    def test_connection(self):
        db = self._get_db_connector_from_gui()
        test_level = EnumTestLevel.DB_SCHEMA

        if self._action_type == EnumDbActionType.SCHEMA_IMPORT:
            test_level |= EnumTestLevel.SCHEMA_IMPORT

        res, code, msg = db.test_connection(
            test_level
        )  # No need to pass required_models, we don't test that much

        if db is not None:
            db.close_connection()

        self.show_message(msg, Qgis.Info if res else Qgis.Warning)
        self.logger.info(__name__, "Test connection!")
        self.logger.debug(
            __name__,
            "Test connection ({}): {}, {}".format(test_level, res, msg))

    def test_ladm_col_structure(self):
        db = self._get_db_connector_from_gui()
        res, code, msg = db.test_connection(
            test_level=EnumTestLevel.LADM,
            required_models=self._required_models)

        if db is not None:
            db.close_connection()

        self.show_message(msg, Qgis.Info if res else Qgis.Warning)
        self.logger.info(__name__, "Test LADM structure!")
        self.logger.debug(
            __name__,
            "Test connection ({}): {}, {}".format(EnumTestLevel.LADM, res,
                                                  msg))

    def test_service(self):
        self.setEnabled(False)
        QCoreApplication.processEvents()
        res, msg = self.qgis_utils.is_source_service_valid(
            self.txt_service_endpoint.text().strip())
        self.setEnabled(True)
        self.show_message(msg['text'], msg['level'])

    def test_service_transitional_system(self):
        self.setEnabled(False)
        QCoreApplication.processEvents()
        res, msg = self.qgis_utils.is_transitional_system_service_valid(
            self.txt_service_transitional_system.text().strip())
        self.setEnabled(True)
        self.show_message(msg['text'], msg['level'])

    def set_default_value_source_service(self):
        self.txt_service_endpoint.setText(DEFAULT_ENDPOINT_SOURCE_SERVICE)

    def set_default_value_transitional_system_service(self):
        self.txt_service_transitional_system.setText(
            TransitionalSystemConfig().ST_DEFAULT_DOMAIN)

    def show_message(self, message, level):
        self.bar.clearWidgets(
        )  # Remove previous messages before showing a new one
        self.bar.pushMessage(message, level, 10)

    def update_images_state(self, checked):
        self.img_with_roads.setEnabled(checked)
        self.img_with_roads.setToolTip(
            QCoreApplication.translate(
                "SettingsDialog", "Missing roads will be marked as errors."
            ) if checked else '')
        self.img_without_roads.setEnabled(not checked)
        self.img_without_roads.setToolTip(
            '' if checked else QCoreApplication.translate(
                "SettingsDialog", "Missing roads will not be marked as errors."
            ))

    def show_help(self):
        self.qgis_utils.show_help("settings")

    def set_action_type(self, action_type):
        self._action_type = action_type

        for key, value in self._lst_panel.items():
            value.set_action(action_type)
class ModelParser(QObject):
    def __init__(self, db):
        QObject.__init__(self)
        self.logger = Logger()

        self.current_model_version = {
            LADMNames.OPERATION_MODEL_PREFIX: None,
            LADMNames.CADASTRAL_FORM_MODEL_PREFIX: None,
            LADMNames.VALUATION_MODEL_PREFIX: None,
            LADMNames.LADM_MODEL_PREFIX: None,
            LADMNames.ANT_MODEL_PREFIX: None,
            LADMNames.REFERENCE_CARTOGRAPHY_PREFIX: None,
            LADMNames.SNR_DATA_MODEL_PREFIX: None,
            LADMNames.SUPPLIES_INTEGRATION_MODEL_PREFIX: None,
            LADMNames.SUPPLIES_MODEL_PREFIX: None
        }

        self.model_version_is_supported = {
            LADMNames.OPERATION_MODEL_PREFIX: False,
            LADMNames.CADASTRAL_FORM_MODEL_PREFIX: False,
            LADMNames.VALUATION_MODEL_PREFIX: False,
            LADMNames.LADM_MODEL_PREFIX: False,
            LADMNames.ANT_MODEL_PREFIX: False,
            LADMNames.REFERENCE_CARTOGRAPHY_PREFIX: False,
            LADMNames.SNR_DATA_MODEL_PREFIX: False,
            LADMNames.SUPPLIES_INTEGRATION_MODEL_PREFIX: False,
            LADMNames.SUPPLIES_MODEL_PREFIX: False
        }

        self._db = db

        # Fill versions for each model found
        for current_model_name in self._get_models():
            for model_prefix, v in self.current_model_version.items():
                if current_model_name.startswith(model_prefix):
                    parts = current_model_name.split(model_prefix)
                    if len(parts) > 1:
                        current_version = self.parse_version(parts[1])
                        current_version_valid = is_version_valid(
                            current_version,
                            LADMNames.SUPPORTED_MODEL_VERSIONS[model_prefix],
                            True,  # Exact version required
                            QCoreApplication.translate("ModelParser",
                                                       model_prefix))
                        self.current_model_version[
                            model_prefix] = current_version
                        self.model_version_is_supported[
                            model_prefix] = current_version_valid
                        self.logger.debug(
                            __name__, "Model '{}' found! Valid: {}".format(
                                model_prefix, current_version_valid))
                        break

    def parse_version(self, str_version):
        """ E.g., _V2_9_6 -> 2.9.6 """
        return ".".join(str_version.replace("_V", "").split("_"))

    def operation_model_exists(self):
        return self.model_version_is_supported[
            LADMNames.OPERATION_MODEL_PREFIX]

    def cadastral_form_model_exists(self):
        return self.model_version_is_supported[
            LADMNames.CADASTRAL_FORM_MODEL_PREFIX]

    def valuation_model_exists(self):
        return self.model_version_is_supported[
            LADMNames.VALUATION_MODEL_PREFIX]

    def ant_model_exists(self):
        return self.model_version_is_supported[LADMNames.ANT_MODEL_PREFIX]

    def ladm_model_exists(self):
        return self.model_version_is_supported[LADMNames.LADM_MODEL_PREFIX]

    def reference_cartography_model_exists(self):
        return self.model_version_is_supported[
            LADMNames.REFERENCE_CARTOGRAPHY_PREFIX]

    def snr_data_model_exists(self):
        return self.model_version_is_supported[LADMNames.SNR_DATA_MODEL_PREFIX]

    def supplies_integration_model_exists(self):
        return self.model_version_is_supported[
            LADMNames.SUPPLIES_INTEGRATION_MODEL_PREFIX]

    def supplies_model_exists(self):
        return self.model_version_is_supported[LADMNames.SUPPLIES_MODEL_PREFIX]

    def ladm_col_model_exists(self, model_prefix):
        return self.model_version_is_supported[
            model_prefix] if model_prefix in self.model_version_is_supported else False

    def at_least_one_ladm_col_model_exists(self):
        return True in self.model_version_is_supported.values()

    def _get_models(self):
        return self._db.get_models()
Exemple #12
0
class ModelParser(QObject):
    def __init__(self, db):
        QObject.__init__(self)
        self.logger = Logger()

        ladmcol_models = LADMColModelRegistry()
        self.current_model_version = {
            model_id: None
            for model_id in ladmcol_models.model_ids()
        }
        self.model_version_is_supported = {
            model_id: False
            for model_id in ladmcol_models.model_ids()
        }

        self._db = db

        # Fill versions for each model found
        for current_model_name in self._get_models():
            for model_key, v in self.current_model_version.items():
                if current_model_name.startswith(model_key):
                    parts = current_model_name.split(model_key)
                    if len(parts) > 1:
                        current_version = self.parse_version(parts[1])
                        current_version_valid = is_version_valid(
                            current_version,
                            ladmcol_models.model(
                                model_key).supported_version(),
                            True,  # Exact version required
                            QCoreApplication.translate("ModelParser",
                                                       model_key))
                        self.current_model_version[model_key] = current_version
                        self.model_version_is_supported[
                            model_key] = current_version_valid
                        self.logger.debug(
                            __name__, "Model '{}' found! Valid: {}".format(
                                model_key, current_version_valid))
                        break

    def parse_version(self, str_version):
        """ E.g., _V2_9_6 -> 2.9.6 """
        return ".".join(str_version.replace("_V", "").split("_"))

    def survey_model_exists(self):
        return self.model_version_is_supported[LADMNames.SURVEY_MODEL_KEY]

    def valuation_model_exists(self):
        return self.model_version_is_supported[LADMNames.VALUATION_MODEL_KEY]

    def ladm_model_exists(self):
        return self.model_version_is_supported[LADMNames.LADM_COL_MODEL_KEY]

    def cadastral_cartography_model_exists(self):
        return self.model_version_is_supported[
            LADMNames.CADASTRAL_CARTOGRAPHY_MODEL_KEY]

    def snr_data_model_exists(self):
        return self.model_version_is_supported[
            LADMNames.SNR_DATA_SUPPLIES_MODEL_KEY]

    def supplies_integration_model_exists(self):
        return self.model_version_is_supported[
            LADMNames.SUPPLIES_INTEGRATION_MODEL_KEY]

    def supplies_model_exists(self):
        return self.model_version_is_supported[LADMNames.SUPPLIES_MODEL_KEY]

    def ladm_col_model_exists(self, model_prefix):
        return self.model_version_is_supported[
            model_prefix] if model_prefix in self.model_version_is_supported else False

    def at_least_one_ladm_col_model_exists(self):
        return True in self.model_version_is_supported.values()

    def _get_models(self):
        return self._db.get_models()
class ModelParser(QObject):
    """
    Assembles both plugin's registered models and models found in the DB to
    answer questions about the existence of a registered model in the DB.

    Note: Some info in the LADMColModelRegistry depends on the active role.
          Have a look at its documentation.
    """
    def __init__(self, db):
        QObject.__init__(self)
        self.logger = Logger()

        ladmcol_models = LADMColModelRegistry()
        self.current_model_version = {
            model_id: None
            for model_id in ladmcol_models.model_keys()
        }
        self.model_version_is_supported = {
            model_id: False
            for model_id in ladmcol_models.model_keys()
        }

        self._db = db

        # Fill versions for each model found
        for current_model_name in self._get_models():
            for model_key, v in self.current_model_version.items():
                if current_model_name.startswith(model_key):
                    parts = current_model_name.split(model_key)
                    if len(parts) > 1:
                        current_version = self.parse_version(parts[1])
                        current_version_valid = is_version_valid(
                            current_version,
                            ladmcol_models.model(
                                model_key).supported_version(),
                            True,  # Exact version required
                            QCoreApplication.translate("ModelParser",
                                                       model_key))
                        self.current_model_version[model_key] = current_version
                        self.model_version_is_supported[
                            model_key] = current_version_valid
                        self.logger.debug(
                            __name__, "Model '{}' found! Valid: {}".format(
                                model_key, current_version_valid))
                        break

    def parse_version(self, str_version):
        """ E.g., _V2_9_6 -> 2.9.6 """
        return ".".join(str_version.replace("_V", "").split("_"))

    def survey_model_exists(self):
        return self.model_version_is_supported[LADMNames.SURVEY_MODEL_KEY]

    def valuation_model_exists(self):
        return self.model_version_is_supported[LADMNames.VALUATION_MODEL_KEY]

    def ladm_model_exists(self):
        return self.model_version_is_supported[LADMNames.LADM_COL_MODEL_KEY]

    def cadastral_cartography_model_exists(self):
        return self.model_version_is_supported[
            LADMNames.CADASTRAL_CARTOGRAPHY_MODEL_KEY]

    def snr_data_model_exists(self):
        return self.model_version_is_supported[
            LADMNames.SNR_DATA_SUPPLIES_MODEL_KEY]

    def supplies_integration_model_exists(self):
        return self.model_version_is_supported[
            LADMNames.SUPPLIES_INTEGRATION_MODEL_KEY]

    def supplies_model_exists(self):
        return self.model_version_is_supported[LADMNames.SUPPLIES_MODEL_KEY]

    def ladm_col_model_exists(self, model_prefix):
        return self.model_version_is_supported.get(model_prefix, False)

    def at_least_one_ladm_col_model_exists(self):
        """
        Check that at least one hidden_and_supported model (hidden models are supposed to be the building blocks
        of extended ones) is also supported in the DB and that there is at least one non-hidden_and_supported
        model that is supported in the DB.

        Note: Both hidden/non hidden and supported models depend on the active role,
              which is already taken into account by LADMColModelRegistry().
        """
        hidden_model_ids = [
            model.id()
            for model in LADMColModelRegistry().hidden_and_supported_models()
        ]
        non_hidden_model_ids = [
            model.id() for model in
            LADMColModelRegistry().non_hidden_and_supported_models()
        ]

        hidden_models_supported = list()
        non_hidden_models_supported = list()

        for model_id, is_supported in self.model_version_is_supported.items():
            if model_id in hidden_model_ids:
                hidden_models_supported.append(is_supported)
            elif model_id in non_hidden_model_ids:
                non_hidden_models_supported.append(is_supported)

        return any(hidden_models_supported) and any(
            non_hidden_models_supported)

    def _get_models(self):
        return self._db.get_models()
class STTaskSteps(QObject):
    """
    Manage task steps
    """
    def __init__(self, task):
        QObject.__init__(self)
        self.task = task
        self.task_id = task.get_id()
        self.task_type = task.get_type()
        self.logger = Logger()

        self.__steps = list()
        self.task_steps_config = TaskStepsConfig()

        self.__initialize_steps()

    def __initialize_steps(self):
        """
        Get actions from task step config and create STTaskStep objects for the task
        :return: List of steps ready to use
        """
        steps_data = self.task_steps_config.get_steps_config(self.task)
        self.logger.info(
            __name__,
            "{} steps found for task id {}!".format(len(steps_data),
                                                    self.task_id))

        for step_data in steps_data:
            step = STTaskStep(step_data)
            if step.is_valid():
                self.__steps.append(step)
            else:
                self.logger.error_msg(
                    __name__,
                    QCoreApplication.translate(
                        "STTaskSteps",
                        "The step '{} ({})' for the task '{} ({})' is invalid!"
                    ).format(step.get_name(), step.get_id(),
                             self.task.get_name(), self.task.get_id()))

        self.load_status(self.task_id)  # Update status if found in QSettings

    def get_steps(self):
        return self.__steps

    def get_step(self, id):
        for step in self.__steps:
            if step.get_id() == id:
                return step

        self.logger.warning(__name__, "Step '{}' not found!".format(id))
        return None

    def steps_complete(self):
        """
        :return: boolean --> Are all steps done?
        """
        for step in self.__steps:
            if not step.get_status():
                return False

        return True

    def steps_started(self):
        """
        :return: boolean --> Whether at least one step is done or not
        """
        count = 0
        for step in self.__steps:
            if step.get_status():
                count += 1

        return count > 0  # and count < len(self.__steps)

    def save_status(self, task_id, steps_status):
        """
        Save status in QSettings

        :param task_id: Id of the task.
        :param steps_status: dict --> {step number: boolean status}
        """
        if steps_status:
            self.logger.debug(
                __name__, "Saving step status for task ({}): {}".format(
                    task_id, steps_status))
            QSettings().setValue(
                "Asistente-LADM-COL/transitional_system/tasks/{}/step_status".
                format(task_id), json.dumps(steps_status))

            for i, step in enumerate(self.__steps):
                index = i + 1
                if index in steps_status:
                    step.set_status(steps_status[index])

    def load_status(self, task_id):
        """
        Load status from QSettings
        """
        try:
            status = json.loads(QSettings().value(
                "Asistente-LADM-COL/transitional_system/tasks/{}/step_status".
                format(task_id), "{}"))
        except TypeError as e:
            # The QSettings value is not in the format we expect, just reset it
            QSettings().setValue(
                "Asistente-LADM-COL/transitional_system/tasks/{}/step_status".
                format(task_id), "{}")
            return

        if status:
            self.logger.debug(
                __name__, "Loading step status for task ({}): {}".format(
                    task_id, status))
            for i, step in enumerate(self.__steps):
                index = str(i + 1)
                if index in status:
                    step.set_status(status[index])
Exemple #15
0
class STTask(QObject):
    """
    Read and store task info
    """
    ID_KEY = 'id'
    NAME_KEY = 'name'
    DESCRIPTION_KEY = 'description'
    DEADLINE_KEY = 'deadline'
    CREATED_AT_KEY = 'createdAt'
    CLOSING_DATE_KEY = 'closingDate'
    TASK_STATE_KEY = 'taskState'
    MEMBERS_KEY = 'members'
    CATEGORIES_KEY = 'categories'
    DATA_KEY = 'data'

    def __init__(self, task_server_data):
        QObject.__init__(self)
        self.logger = Logger()
        self.__is_valid = True
        self.__id = None
        self.__name = None
        self.__description = None
        self.__deadline = None
        self.__created_at = None
        self.__closing_date = None
        self.__task_status = None
        self.__members = None
        self.__categories = None
        self.__data = None
        self.__task_steps = None

        self.__task_server_data = task_server_data

        self._initialize_task(task_server_data)

    def __get_mandatory_attributes(self):
        return {
            self.ID_KEY: self.__id,
            self.NAME_KEY: self.__name,
            self.CATEGORIES_KEY: self.__categories,
            self.TASK_STATE_KEY: self.__task_status,
            self.CREATED_AT_KEY: self.__created_at
        }

    def _initialize_task(self, task_server_data):
        self.logger.info(__name__, "Creating task...")
        if self.ID_KEY in task_server_data:
            self.__id = task_server_data[self.ID_KEY]
        if self.NAME_KEY in task_server_data:
            self.__name = task_server_data[self.NAME_KEY]
        if self.DESCRIPTION_KEY in task_server_data:
            self.__description = task_server_data[self.DESCRIPTION_KEY]
        if self.DEADLINE_KEY in task_server_data:
            self.__deadline = self.normalize_date(
                task_server_data[self.DEADLINE_KEY])
        if self.CREATED_AT_KEY in task_server_data:
            self.__created_at = self.normalize_date(
                task_server_data[self.CREATED_AT_KEY])
        if self.CLOSING_DATE_KEY in task_server_data:
            self.__closing_date = self.normalize_date(
                task_server_data[self.CLOSING_DATE_KEY])
        if self.TASK_STATE_KEY in task_server_data:
            self.__task_status = task_server_data[self.TASK_STATE_KEY]
        if self.MEMBERS_KEY in task_server_data:
            self.__members = task_server_data[self.MEMBERS_KEY]
        if self.CATEGORIES_KEY in task_server_data:
            self.__categories = task_server_data[self.CATEGORIES_KEY]
        if self.DATA_KEY in task_server_data:
            self.__data = task_server_data[self.DATA_KEY]

        for k, attribute in self.__get_mandatory_attributes().items():
            if attribute is None:
                self.logger.debug(
                    __name__, "Missing attribute to create task: {}".format(k))
                self.__is_valid = False

        if self.__is_valid:
            self.__task_steps = STTaskSteps(self)

        self.logger.debug(__name__,
                          "Task is valid? {}".format(self.is_valid()))

    def is_valid(self):
        return self.__is_valid

    def get_id(self):
        return self.__id

    def get_name(self):
        return self.__name

    def get_description(self):
        return self.__description

    def get_type(self):
        return self.__categories[0][
            'id'] if self.__categories is not None else None

    def get_deadline(self):
        return self.__deadline

    def get_creation_date(self):
        return self.__created_at

    def get_started_date(self):
        return self.__created_at

    def get_status(self):
        return self.__task_status[
            'name'] if self.__task_status is not None else None

    def get_connection(self):
        pass

    def get_steps(self):
        return self.__task_steps.get_steps(
        ) if self.__task_steps is not None else list()

    def get_step(self, id):
        return self.__task_steps.get_step(
            id) if self.__task_steps is not None else None

    def steps_complete(self):
        return self.__task_steps.steps_complete(
        ) if self.__task_steps is not None else False

    def task_started(self):
        return self.__task_steps.steps_started(
        ) if self.__task_steps is not None else False

    def save_steps_status(self, steps_status):
        if self.__task_steps is not None:
            self.__task_steps.save_status(self.get_id(), steps_status)

    def close_task(self):
        pass

    def validate_task(self):
        pass

    def load_task_status(self):
        pass

    def save_task_status(self):
        pass

    def get_data(self):
        return self.__data

    def normalize_date(self, date_str):
        if date_str:
            date = datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%S.%f%z'
                                     )  # e.g., '2019-02-01T08:01:31.664+0000'
            return date.strftime("%Y-%m-%d %H:%M")

        return ''
Exemple #16
0
class AppGUIInterface(QObject):
    add_indicators_requested = pyqtSignal(
        str, QgsLayerTreeNode.NodeType)  # node name, node type

    def __init__(self, iface):
        QObject.__init__(self)
        self.iface = iface

        self.logger = Logger()

    def trigger_add_feature(self):
        self.iface.actionAddFeature().trigger()

    def trigger_vertex_tool(self):
        self.iface.actionVertexTool().trigger()

    def create_progress_message_bar(self, text, progress):
        progressMessageBar = self.iface.messageBar().createMessage(
            PLUGIN_NAME, text)
        progressMessageBar.layout().addWidget(progress)
        self.iface.messageBar().pushWidget(progressMessageBar, Qgis.Info)

    def refresh_layer_symbology(self, layer_id):
        self.iface.layerTreeView().refreshLayerSymbology(layer_id)

    def refresh_map(self):
        self.iface.mapCanvas().refresh()

    def redraw_all_layers(self):
        self.iface.mapCanvas().redrawAllLayers()

    def freeze_map(self, frozen):
        self.iface.mapCanvas().freeze(frozen)

    def activate_layer(self, layer):
        self.iface.layerTreeView().setCurrentLayer(layer)

    def set_node_visibility(self, node, visible=True):
        # Modes may eventually be layer_id, group_name, layer, group
        if node is not None:
            node.setItemVisibilityChecked(visible)

    def remove_error_group(self):
        group = self.get_error_layers_group()
        parent = group.parent()
        parent.removeChildNode(group)

    def clear_status_bar(self):
        self.iface.statusBarIface().clearMessage()

    def add_error_layer(self, db, error_layer):
        group = self.get_error_layers_group()

        # Check if layer is loaded and remove it
        layers = group.findLayers()
        for layer in layers:
            if layer.name() == error_layer.name():
                group.removeLayer(layer.layer())
                break

        added_layer = QgsProject.instance().addMapLayer(error_layer, False)
        index = QgisModelBakerUtils().get_suggested_index_for_layer(
            added_layer, group)
        added_layer = group.insertLayer(index, added_layer).layer()
        if added_layer.isSpatial():
            # db connection is none because we are using a memory layer
            SymbologyUtils().set_layer_style_from_qml(db,
                                                      added_layer,
                                                      is_error_layer=True)

            if isinstance(added_layer.renderer(),
                          QgsCategorizedSymbolRenderer):
                # Remove empty style categories as they just make difficult to understand validation errors
                unique_values = added_layer.uniqueValues(
                    added_layer.fields().indexOf(
                        QCoreApplication.translate("QualityRule",
                                                   "codigo_error")))
                renderer = added_layer.renderer()
                for cat in reversed(renderer.categories()
                                    ):  # To be safe while removing categories
                    if cat.value() not in unique_values:
                        renderer.deleteCategory(
                            renderer.categoryIndexForValue(cat.value()))

                added_layer.setRenderer(added_layer.renderer().clone())

        return added_layer

    def get_error_layers_group(self):
        """
        Get the topology errors group. If it exists but is placed in another
        position rather than the top, it moves the group to the top.
        """
        root = QgsProject.instance().layerTreeRoot()
        translated_strings = TranslatableConfigStrings.get_translatable_config_strings(
        )
        group = root.findGroup(translated_strings[ERROR_LAYER_GROUP])
        if group is None:
            group = root.insertGroup(0, translated_strings[ERROR_LAYER_GROUP])
            self.add_indicators_requested.emit(
                translated_strings[ERROR_LAYER_GROUP],
                QgsLayerTreeNode.NodeGroup)
        elif not self.iface.layerTreeView().layerTreeModel().node2index(
                group).row() == 0 or type(group.parent()) is QgsLayerTreeGroup:
            group_clone = group.clone()
            root.insertChildNode(0, group_clone)
            parent = group.parent()
            parent.removeChildNode(group)
            group = group_clone
        return group

    def add_indicators(self, node_name, node_type, payload):
        """
        Adds all indicators for a node in layer tree. It searches for the proper node and its config.

        :param node_name: Key to get the config and possibly, the node (see payload)
        :param node_type: QgsLayerTreeNode.NodeType
        :param payload: If the node is a LADM layer, we need the layer object, as the name is not enough to disambiguate
                        between layers from different connections
        """
        # First get the node
        node = None
        root = QgsProject.instance().layerTreeRoot()
        if node_type == QgsLayerTreeNode.NodeGroup:
            node = root.findGroup(node_name)
        elif node_type == QgsLayerTreeNode.NodeLayer:
            if payload:
                node = root.findLayer(payload)  # Search by QgsMapLayer
            else:  # Get the first layer matching the node name
                layers = QgsProject.instance().mapLayersByName(node_name)
                if layers:
                    node = root.findLayer(layers[0])

        if not node:
            self.logger.warning(
                __name__,
                "Node not found for adding indicators! ({}, {})".format(
                    node_name, node_type))
            return  # No node, no party

        # Then, get the config
        indicators_config = LayerTreeIndicatorConfig().get_indicators_config(
            node_name, node_type)
        if not indicators_config:
            self.logger.warning(
                __name__,
                "Configuration for indicators not found for node '{}'!".format(
                    node_name))

        # And finally...
        for config in indicators_config:
            self.add_indicator(node, config)

    def add_indicator(self, node, config):
        """
        Adds a single indicator for the node, based on a config dict

        :param node: Layer tree node
        :param config: Dictionary with required data to set the indicator
        """
        indicator = QgsLayerTreeViewIndicator(self.iface.layerTreeView())
        indicator.setToolTip(config[INDICATOR_TOOLTIP])
        indicator.setIcon(config[INDICATOR_ICON])
        indicator.clicked.connect(config[INDICATOR_SLOT])
        self.iface.layerTreeView().addIndicator(node, indicator)

    def export_error_group(self):
        """Exports the error group to GeoPackage"""
        group = self.get_error_layers_group()
        if group:
            layers = group.findLayerIds()
            if not layers:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "AppGUIInterface",
                        "There are no error layers to export!"))
                return

            filename, matched_filter = QFileDialog.getSaveFileName(
                self.iface.mainWindow(),
                QCoreApplication.translate(
                    "AppGUIInterface",
                    "Where do you want to save your GeoPackage?"), ".",
                QCoreApplication.translate("AppGUIInterface",
                                           "GeoPackage (*.gpkg)"))

            if filename:
                if not filename.endswith(".gpkg") and filename:
                    filename = filename + ".gpkg"

                feedback = CustomFeedbackWithErrors()
                try:
                    msg = QCoreApplication.translate(
                        "AppGUIInterface",
                        "Exporting quality errors to GeoPackage...")
                    with ProcessWithStatus(msg):
                        processing.run("native:package", {
                            'LAYERS': layers,
                            'OUTPUT': filename,
                            'OVERWRITE': False,
                            'SAVE_STYLES': True
                        },
                                       feedback=feedback)
                except QgsProcessingException as e:
                    self.logger.warning_msg(
                        __name__,
                        QCoreApplication.translate(
                            "AppGUIInterface",
                            "The quality errors could not be exported. Details: {}"
                            .format(feedback.msg)))
                    return

                self.logger.success_msg(
                    __name__,
                    QCoreApplication.translate(
                        "AppGUIInterface",
                        "The quality errors have been exported to GeoPackage!")
                )
            else:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "AppGUIInterface",
                        "Export to GeoPackage was cancelled. No output file was selected."
                    ), 5)
        else:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "AppGUIInterface",
                    "There is no quality error group to export!"), 5)

    def set_error_group_visibility(self, visible):
        self.set_node_visibility(self.get_error_layers_group(), visible)

    def set_layer_visibility(self, layer, visible):
        node = QgsProject.instance().layerTreeRoot().findLayer(layer.id())
        self.set_node_visibility(node, visible)

    def error_group_exists(self):
        root = QgsProject.instance().layerTreeRoot()
        translated_strings = TranslatableConfigStrings.get_translatable_config_strings(
        )
        return root.findGroup(
            translated_strings[ERROR_LAYER_GROUP]) is not None

    @pyqtSlot()
    def clear_message_bar(self):
        self.iface.messageBar().clearWidgets()

    def zoom_full(self):
        self.iface.zoomFull()

    def zoom_to_active_layer(self):
        self.iface.zoomToActiveLayer()

    def zoom_to_selected(self):
        self.iface.actionZoomToSelected().trigger()

    def show_message(self, msg, level, duration=5):
        self.clear_message_bar(
        )  # Remove previous messages before showing a new one
        self.iface.messageBar().pushMessage("Asistente LADM-COL", msg, level,
                                            duration)

    def show_status_bar_message(self, msg, duration):
        self.iface.statusBarIface().showMessage(msg, duration)

    def add_tabified_dock_widget(self, area, dock_widget):
        """
        Adds the dock_widget to the given area, making sure it is tabified if other dock widgets exist.
        :param area: Value of the Qt.DockWidgetArea enum
        :param dock_widget: QDockWidget object
        """
        if Qgis.QGIS_VERSION_INT >= 31300:  # Use native addTabifiedDockWidget
            self.iface.addTabifiedDockWidget(area, dock_widget, raiseTab=True)
        else:  # Use plugin's addTabifiedDockWidget, which does not raise the new tab
            dock_widgets = list()
            for dw in self.iface.mainWindow().findChildren(QDockWidget):
                if dw.isVisible() and self.iface.mainWindow().dockWidgetArea(
                        dw) == area:
                    dock_widgets.append(dw)

            self.iface.mainWindow().addDockWidget(
                area,
                dock_widget)  # We add the dock widget, then attempt to tabify
            if dock_widgets:
                self.logger.debug(
                    __name__, "Tabifying dock widget {}...".format(
                        dock_widget.windowTitle()))
                self.iface.mainWindow().tabifyDockWidget(
                    dock_widgets[0],
                    dock_widget)  # No way to prefer one Dock Widget
class SettingsDialog(QDialog, DIALOG_UI):
    """
    Customizable dialog to configure LADM-COL Assistant.

    It can be created passing a SettingsContext with specific params
    or it can be instantiated and then set params one by one.
    """
    db_connection_changed = pyqtSignal(DBConnector, bool,
                                       str)  # dbconn, ladm_col_db, source
    open_dlg_import_schema = pyqtSignal(
        Context)  # Context for the import schema dialog

    def __init__(self, conn_manager=None, context=None, parent=None):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        self.parent = parent
        self.logger = Logger()
        self.conn_manager = conn_manager
        self.app = AppInterface()

        self.sbx_tolerance.setMaximum(TOLERANCE_MAX_VALUE)
        self._valid_document_repository = False  # Needs to be True if users want to enable doc repo (using test button)

        context = context if context else SettingsContext()

        self.db_source = context.db_source  # default db source is COLLECTED_DB_SOURCE
        self._required_models = context.required_models
        self._tab_pages_list = context.tab_pages_list
        self._blocking_mode = context.blocking_mode  # Whether the dialog can only be accepted on valid DB connections or not
        self._action_type = context.action_type  # By default "config"
        self.setWindowTitle(context.title)

        self._db = None
        self.init_db_engine = None
        self.dbs_supported = ConfigDBsSupported()
        self._open_dlg_import_schema = False  # After accepting, if non-valid DB is configured, we can go to import schema

        self.online_models_radio_button.setEnabled(
            False)  # This option is disabled until we have online models back!
        self.online_models_radio_button.setChecked(True)
        self.online_models_radio_button.toggled.connect(
            self.model_provider_toggle)
        self.custom_model_directories_line_edit.setText("")
        self.custom_models_dir_button.clicked.connect(
            self.show_custom_model_dir)
        self.custom_model_directories_line_edit.setVisible(False)
        self.custom_models_dir_button.setVisible(False)

        # Set connections
        self.buttonBox.accepted.disconnect()
        self.buttonBox.accepted.connect(self.accepted)
        self.buttonBox.helpRequested.connect(self.show_help)
        self.finished.connect(self.finished_slot)
        self.btn_test_connection.clicked.connect(self.test_connection)
        self.btn_test_ladm_col_structure.clicked.connect(
            self.test_ladm_col_structure)

        self.btn_test_service.clicked.connect(self.test_service)
        self.btn_test_service_transitional_system.clicked.connect(
            self.test_service_transitional_system)
        self.txt_service_endpoint.textEdited.connect(
            self.source_service_endpoint_changed)  # For manual changes only

        self.btn_default_value_sources.clicked.connect(
            self.set_default_value_source_service)
        self.btn_default_value_transitional_system.clicked.connect(
            self.set_default_value_transitional_system_service)

        self.chk_use_roads.toggled.connect(self.update_images_state)

        self.bar = QgsMessageBar()
        self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop)

        self.cbo_db_engine.clear()

        self._lst_db = self.dbs_supported.get_db_factories()
        self._lst_panel = dict()

        for key, value in self._lst_db.items():
            self.cbo_db_engine.addItem(value.get_name(), key)
            self._lst_panel[key] = value.get_config_panel(self)
            self._lst_panel[key].notify_message_requested.connect(
                self.show_message)
            self.db_layout.addWidget(self._lst_panel[key])

        self.db_engine_changed()

        # Trigger some default behaviours
        self.restore_db_source_settings(
        )  # restore settings with default db source
        self.restore_settings()

        self.roles = RoleRegistry()
        self.load_roles()

        self.cbo_db_engine.currentIndexChanged.connect(self.db_engine_changed)
        self.rejected.connect(self.close_dialog)

        self._update_tabs()

        if context.tip:
            self.show_tip(context.tip)

    def set_db_source(self, db_source):
        self.db_source = db_source
        self.restore_db_source_settings()

    def set_tab_pages_list(self, tab_pages_list):
        self._tab_pages_list = tab_pages_list
        self._update_tabs()

    def set_required_models(self, required_models):
        self._required_models = required_models

    def set_blocking_mode(self, block):
        self._blocking_mode = block

    def _update_tabs(self):
        """
        Show only those tabs that are listed in tab_pages_list, if any. If it's an empty list, show all tabs.
        """
        if self._tab_pages_list:
            for i in reversed(range(self.tabWidget.count())):
                if i not in self._tab_pages_list:
                    self.tabWidget.removeTab(i)

    def load_roles(self):
        """
        Initialize group box for selecting the active role
        """
        self.gbx_active_role_layout = QVBoxLayout()
        dict_roles = self.roles.get_roles_info()
        checked = False
        active_role = self.roles.get_active_role()

        # Initialize radio buttons
        for k, v in dict_roles.items():
            radio = QRadioButton(v)
            radio.setToolTip(self.roles.get_role_description(k))

            if not checked:
                if k == active_role:
                    radio.setChecked(True)
                    checked = True

            self.gbx_active_role_layout.addWidget(radio)

        self.gbx_active_role.setLayout(self.gbx_active_role_layout)

    def close_dialog(self):
        self.close()

    def showEvent(self, event):
        # It is necessary to reload the variables
        # to load the database and schema name
        self.restore_settings()

        self.btn_test_ladm_col_structure.setVisible(
            self._action_type != EnumDbActionType.SCHEMA_IMPORT)

    def model_provider_toggle(self):
        if self.offline_models_radio_button.isChecked():
            self.custom_model_directories_line_edit.setVisible(True)
            self.custom_models_dir_button.setVisible(True)
        else:
            self.custom_model_directories_line_edit.setVisible(False)
            self.custom_models_dir_button.setVisible(False)
            self.custom_model_directories_line_edit.setText("")

    def _get_db_connector_from_gui(self):
        current_db_engine = self.cbo_db_engine.currentData()
        params = self._lst_panel[current_db_engine].read_connection_parameters(
        )
        db = self._lst_db[current_db_engine].get_db_connector(params)
        return db

    def get_db_connection(self):
        if self._db is not None:
            self.logger.info(__name__, "Returning existing db connection...")
        else:
            self.logger.info(__name__, "Getting new db connection...")
            self._db = self._get_db_connector_from_gui()
            self._db.open_connection()

        return self._db

    def show_custom_model_dir(self):
        dlg = CustomModelDirDialog(
            self.custom_model_directories_line_edit.text(), self)
        dlg.exec_()

    def accepted(self):
        """
        We start checking the document repository configuration and only allow to continue if we have a valid repo or
        if the repo won't be used.

        Then, check if connection to DB/schema is valid, if not, block the dialog.
        If valid, check it complies with LADM. If not, block the dialog. If it complies, we have two options: To emit
        db_connection changed or not. Finally, we store options in QSettings.
        """
        res_doc_repo, msg_doc_repo = self.check_document_repository_before_saving_settings(
        )
        if not res_doc_repo:
            self.show_message(msg_doc_repo, Qgis.Warning, 0)
            return  # Do not close the dialog

        ladm_col_schema = False

        db = self._get_db_connector_from_gui()

        test_level = EnumTestLevel.DB_SCHEMA

        if self._action_type == EnumDbActionType.SCHEMA_IMPORT:
            # Limit the validation (used in GeoPackage)
            test_level |= EnumTestLevel.SCHEMA_IMPORT

        res, code, msg = db.test_connection(
            test_level
        )  # No need to pass required_models, we don't test that much

        if res:
            if self._action_type != EnumDbActionType.SCHEMA_IMPORT:
                # Only check LADM-schema if we are not in an SCHEMA IMPORT.
                # We know in an SCHEMA IMPORT, at this point the schema is still not LADM.
                ladm_col_schema, code, msg = db.test_connection(
                    EnumTestLevel.LADM, required_models=self._required_models)

            if not ladm_col_schema and self._action_type != EnumDbActionType.SCHEMA_IMPORT:
                if self._blocking_mode:
                    self.show_message(msg, Qgis.Warning)
                    return  # Do not close the dialog

        else:
            if self._blocking_mode:
                self.show_message(msg, Qgis.Warning)
                return  # Do not close the dialog

        # Connection is valid and complies with LADM
        current_db_engine = self.cbo_db_engine.currentData()
        if self._lst_panel[current_db_engine].state_changed(
        ) or self.init_db_engine != current_db_engine:
            # Emit db_connection_changed
            if self._db is not None:
                self._db.close_connection()

            self._db = db

            # Update db connect with new db conn
            self.conn_manager.set_db_connector_for_source(
                self._db, self.db_source)

            # Emmit signal when db source changes
            self.db_connection_changed.emit(self._db, ladm_col_schema,
                                            self.db_source)
            self.logger.debug(
                __name__, "Settings dialog emitted a db_connection_changed.")

        if not ladm_col_schema and self._action_type == EnumDbActionType.CONFIG:
            msg_box = QMessageBox(self)
            msg_box.setIcon(QMessageBox.Question)
            msg_box.setText(
                QCoreApplication.translate(
                    "SettingsDialog",
                    "No LADM-COL DB has been configured! You'll continue with limited functionality until you configure a LADM-COL DB.\n\nDo you want to go to 'Create LADM-COL structure' dialog?"
                ))
            msg_box.setWindowTitle(
                QCoreApplication.translate("SettingsDialog", "Important"))
            msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.Ignore)
            msg_box.setDefaultButton(QMessageBox.Ignore)
            msg_box.button(QMessageBox.Yes).setText(
                QCoreApplication.translate("SettingsDialog",
                                           "Yes, go to create structure"))
            msg_box.button(QMessageBox.Ignore).setText(
                QCoreApplication.translate("SettingsDialog",
                                           "No, I'll do it later"))
            reply = msg_box.exec_()

            if reply == QMessageBox.Yes:
                self._open_dlg_import_schema = True  # We will open it when we've closed this Settings dialog

        # If active role is changed (a check and confirmation may be needed), refresh the GUI
        # Note: Better to leave this check as the last one in the accepted() method.
        selected_role = self.get_selected_role()
        if self.roles.get_active_role() != selected_role:
            b_change_role = True
            if STSession().is_user_logged():
                reply = QMessageBox.question(
                    self.parent,
                    QCoreApplication.translate("SettingsDialog", "Warning"),
                    QCoreApplication.translate(
                        "SettingsDialog",
                        "You have a ST connection opened and you want to change your role.\nIf you confirm that you want to change your role, you'll be logged out from the ST.\n\nDo you really want to change your role?"
                    ), QMessageBox.Yes | QMessageBox.Cancel,
                    QMessageBox.Cancel)
                if reply == QMessageBox.Yes:
                    STSession().logout()
                elif reply == QMessageBox.Cancel:
                    # No need to switch back selected role, the Settings Dialog gets it from role registry
                    b_change_role = False

            if b_change_role:
                self.logger.info(
                    __name__,
                    "The active role has changed from '{}' to '{}'.".format(
                        self.roles.get_active_role(), selected_role))
                self.roles.set_active_role(
                    selected_role
                )  # Emits signal that refreshed the plugin for this role

        self.save_settings(db)

        QDialog.accept(self)
        self.close()

        if self._open_dlg_import_schema:
            # After Settings dialog has been closed, we could call Import Schema depending on user's answer above
            self.open_dlg_import_schema.emit(Context())
            self.logger.debug(
                __name__,
                "Settings dialog emitted a show Import Schema dialog.")

    def check_document_repository_before_saving_settings(self):
        # Check if source service is checked (active). If so, check if either status or endpoint changed. If so,
        # check if self._valid_document_repository is False. If so, we need to test the service and only allow to save
        # if such test is successful.
        res, msg = True, ''
        if self.connection_box.isChecked(
        ):  # The user wants to have the source service enabled
            initial_config = QSettings().value(
                'Asistente-LADM-COL/sources/use_service',
                DEFAULT_USE_SOURCE_SERVICE_SETTING, bool)
            initial_endpoint = QSettings().value(
                'Asistente-LADM-COL/sources/service_endpoint',
                DEFAULT_ENDPOINT_SOURCE_SERVICE)

            # Config changed or Endpoint changed?
            if initial_config != self.connection_box.isChecked(
            ) or initial_endpoint.strip() != self.txt_service_endpoint.text(
            ).strip():
                if not self._valid_document_repository:  # A test service has not been run, so we need to do it now
                    self.logger.debug(
                        __name__,
                        "The user wants to enable the source service but the 'test service' has not been run on the current URL. Testing it..."
                    )
                    res_test, msg_test = self.test_service()
                    if not res_test:
                        res = False
                        msg = QCoreApplication.translate(
                            "SettingsDialog",
                            "The source service is not valid, so it cannot be activated! Adjust such configuration before saving settings."
                        )

        return res, msg

    def source_service_endpoint_changed(self, new_text):
        # Source service endpoint was changed, so a test_service is required to make the valid variable True
        self._valid_document_repository = False

    def get_selected_role(self):
        selected_role = None
        radio_checked = None
        for i in range(self.gbx_active_role_layout.count()):
            radio = self.gbx_active_role_layout.itemAt(i).widget()
            if radio.isChecked():
                radio_checked = radio.text()
                break

        for k, v in self.roles.get_roles_info().items():
            if v == radio_checked:
                selected_role = k
                break

        return selected_role  # Role key

    def reject(self):
        self.done(0)

    def finished_slot(self, result):
        self.bar.clearWidgets()

    def save_settings(self, db):
        settings = QSettings()
        current_db_engine = self.cbo_db_engine.currentData()
        settings.setValue(
            'Asistente-LADM-COL/db/{db_source}/db_connection_engine'.format(
                db_source=self.db_source), current_db_engine)
        dict_conn = self._lst_panel[
            current_db_engine].read_connection_parameters()

        self._lst_db[current_db_engine].save_parameters_conn(
            dict_conn=dict_conn, db_source=self.db_source)

        settings.setValue(
            'Asistente-LADM-COL/models/custom_model_directories_is_checked',
            self.offline_models_radio_button.isChecked())
        if self.offline_models_radio_button.isChecked():
            settings.setValue('Asistente-LADM-COL/models/custom_models',
                              self.custom_model_directories_line_edit.text())

        self.app.settings.tolerance = self.sbx_tolerance.value()
        settings.setValue('Asistente-LADM-COL/quality/use_roads',
                          self.chk_use_roads.isChecked())

        settings.setValue(
            'Asistente-LADM-COL/models/validate_data_importing_exporting',
            self.chk_validate_data_importing_exporting.isChecked())

        endpoint_transitional_system = self.txt_service_transitional_system.text(
        ).strip()
        settings.setValue(
            'Asistente-LADM-COL/sources/service_transitional_system',
            (endpoint_transitional_system[:-1]
             if endpoint_transitional_system.endswith('/') else
             endpoint_transitional_system)
            or TransitionalSystemConfig().ST_DEFAULT_DOMAIN)

        settings.setValue('Asistente-LADM-COL/sources/use_service',
                          self.connection_box.isChecked())
        endpoint = self.txt_service_endpoint.text().strip()
        settings.setValue(
            'Asistente-LADM-COL/sources/service_endpoint',
            (endpoint[:-1] if endpoint.endswith('/') else endpoint)
            or DEFAULT_ENDPOINT_SOURCE_SERVICE)

        settings.setValue(
            'Asistente-LADM-COL/automatic_values/automatic_values_in_batch_mode',
            self.chk_automatic_values_in_batch_mode.isChecked())

        # Changes in automatic namespace, local_id or t_ili_tid configuration?
        current_namespace_enabled = settings.value(
            'Asistente-LADM-COL/automatic_values/namespace_enabled', True,
            bool)
        current_namespace_prefix = settings.value(
            'Asistente-LADM-COL/automatic_values/namespace_prefix', "")
        current_local_id_enabled = settings.value(
            'Asistente-LADM-COL/automatic_values/local_id_enabled', True, bool)
        current_t_ili_tid_enabled = settings.value(
            'Asistente-LADM-COL/automatic_values/t_ili_tid_enabled', True,
            bool)

        settings.setValue(
            'Asistente-LADM-COL/automatic_values/namespace_enabled',
            self.namespace_collapsible_group_box.isChecked())
        if self.namespace_collapsible_group_box.isChecked():
            settings.setValue(
                'Asistente-LADM-COL/automatic_values/namespace_prefix',
                self.txt_namespace.text())

        settings.setValue(
            'Asistente-LADM-COL/automatic_values/local_id_enabled',
            self.chk_local_id.isChecked())
        settings.setValue(
            'Asistente-LADM-COL/automatic_values/t_ili_tid_enabled',
            self.chk_t_ili_tid.isChecked())

        if current_namespace_enabled != self.namespace_collapsible_group_box.isChecked() or \
           current_namespace_prefix != self.txt_namespace.text() or \
           current_local_id_enabled != self.chk_local_id.isChecked() or \
           current_t_ili_tid_enabled != self.chk_t_ili_tid.isChecked():
            if db is not None:
                self.logger.info(
                    __name__,
                    "Automatic values changed in Settings dialog. All LADM-COL layers are being updated..."
                )
                self.app.core.automatic_fields_settings_changed(db)

    def restore_db_source_settings(self):
        settings = QSettings()
        default_db_engine = self.dbs_supported.id_default_db

        self.init_db_engine = settings.value(
            'Asistente-LADM-COL/db/{db_source}/db_connection_engine'.format(
                db_source=self.db_source), default_db_engine)
        index_db_engine = self.cbo_db_engine.findData(self.init_db_engine)

        if index_db_engine == -1:
            index_db_engine = self.cbo_db_engine.findData(default_db_engine)

        self.cbo_db_engine.setCurrentIndex(index_db_engine)
        self.db_engine_changed()

        # restore db settings for all panels
        for db_engine, db_factory in self._lst_db.items():
            dict_conn = db_factory.get_parameters_conn(self.db_source)
            self._lst_panel[db_engine].write_connection_parameters(dict_conn)
            self._lst_panel[db_engine].save_state()

    def restore_settings(self):
        # Restore QSettings
        settings = QSettings()

        custom_model_directories_is_checked = settings.value(
            'Asistente-LADM-COL/models/custom_model_directories_is_checked',
            DEFAULT_USE_CUSTOM_MODELS,
            type=bool)
        if custom_model_directories_is_checked:
            self.offline_models_radio_button.setChecked(True)
            self.custom_model_directories_line_edit.setText(
                settings.value('Asistente-LADM-COL/models/custom_models',
                               DEFAULT_MODELS_DIR))
            self.custom_model_directories_line_edit.setVisible(True)
            self.custom_models_dir_button.setVisible(True)
        else:
            self.online_models_radio_button.setChecked(True)
            self.custom_model_directories_line_edit.setText("")
            self.custom_model_directories_line_edit.setVisible(False)
            self.custom_models_dir_button.setVisible(False)

        self.sbx_tolerance.setValue(self.app.settings.tolerance)
        use_roads = settings.value('Asistente-LADM-COL/quality/use_roads',
                                   True, bool)
        self.chk_use_roads.setChecked(use_roads)
        self.update_images_state(use_roads)

        self.chk_automatic_values_in_batch_mode.setChecked(
            settings.value(
                'Asistente-LADM-COL/automatic_values/automatic_values_in_batch_mode',
                DEFAULT_AUTOMATIC_VALUES_IN_BATCH_MODE, bool))
        self.connection_box.setChecked(
            settings.value('Asistente-LADM-COL/sources/use_service',
                           DEFAULT_USE_SOURCE_SERVICE_SETTING, bool))
        self.namespace_collapsible_group_box.setChecked(
            settings.value(
                'Asistente-LADM-COL/automatic_values/namespace_enabled', True,
                bool))
        self.chk_local_id.setChecked(
            settings.value(
                'Asistente-LADM-COL/automatic_values/local_id_enabled', True,
                bool))
        self.chk_t_ili_tid.setChecked(
            settings.value(
                'Asistente-LADM-COL/automatic_values/t_ili_tid_enabled', True,
                bool))
        self.txt_namespace.setText(
            str(
                settings.value(
                    'Asistente-LADM-COL/automatic_values/namespace_prefix',
                    "")))

        self.chk_validate_data_importing_exporting.setChecked(
            settings.value(
                'Asistente-LADM-COL/models/validate_data_importing_exporting',
                True, bool))

        self.txt_service_transitional_system.setText(
            settings.value(
                'Asistente-LADM-COL/sources/service_transitional_system',
                TransitionalSystemConfig().ST_DEFAULT_DOMAIN))
        self.txt_service_endpoint.setText(
            settings.value('Asistente-LADM-COL/sources/service_endpoint',
                           DEFAULT_ENDPOINT_SOURCE_SERVICE))

    def db_engine_changed(self):
        if self._db is not None:
            self._db.close_connection()

        self._db = None  # Reset db connection

        for key, value in self._lst_panel.items():
            value.setVisible(False)

        current_db_engine = self.cbo_db_engine.currentData()

        self._lst_panel[current_db_engine].setVisible(True)

    def test_connection(self):
        db = self._get_db_connector_from_gui()
        test_level = EnumTestLevel.DB_SCHEMA

        if self._action_type == EnumDbActionType.SCHEMA_IMPORT:
            test_level |= EnumTestLevel.SCHEMA_IMPORT

        res, code, msg = db.test_connection(
            test_level
        )  # No need to pass required_models, we don't test that much

        if db is not None:
            db.close_connection()

        self.show_message(msg, Qgis.Info if res else Qgis.Warning)
        self.logger.info(__name__, "Test connection!")
        self.logger.debug(
            __name__,
            "Test connection ({}): {}, {}".format(test_level, res, msg))

    def test_ladm_col_structure(self):
        db = self._get_db_connector_from_gui()
        res, code, msg = db.test_connection(
            test_level=EnumTestLevel.LADM,
            required_models=self._required_models)

        if db is not None:
            db.close_connection()

        self.show_message(msg, Qgis.Info if res else Qgis.Warning)
        self.logger.info(__name__, "Test LADM structure!")
        self.logger.debug(
            __name__,
            "Test connection ({}): {}, {}".format(EnumTestLevel.LADM, res,
                                                  msg))

    def test_service(self):
        self.setEnabled(False)
        QCoreApplication.processEvents()
        res, msg = self.app.core.is_source_service_valid(
            self.txt_service_endpoint.text().strip())
        self._valid_document_repository = res  # Required to be True if the user wants to enable the source service
        self.setEnabled(True)
        self.show_message(msg['text'], msg['level'], 0)
        return res, msg

    def test_service_transitional_system(self):
        self.setEnabled(False)
        QCoreApplication.processEvents()
        res, msg = self.app.core.is_transitional_system_service_valid(
            self.txt_service_transitional_system.text().strip())
        self.setEnabled(True)
        self.show_message(msg['text'], msg['level'])

    def set_default_value_source_service(self):
        self.txt_service_endpoint.setText(DEFAULT_ENDPOINT_SOURCE_SERVICE)

    def set_default_value_transitional_system_service(self):
        self.txt_service_transitional_system.setText(
            TransitionalSystemConfig().ST_DEFAULT_DOMAIN)

    def show_message(self, message, level, duration=10):
        self.bar.clearWidgets(
        )  # Remove previous messages before showing a new one
        self.bar.pushMessage(message, level, duration)

    def show_tip(self, tip):
        self.show_message(tip, Qgis.Info,
                          0)  # Don't show counter for the tip message

    def update_images_state(self, checked):
        self.img_with_roads.setEnabled(checked)
        self.img_with_roads.setToolTip(
            QCoreApplication.translate(
                "SettingsDialog", "Missing roads will be marked as errors."
            ) if checked else '')
        self.img_without_roads.setEnabled(not checked)
        self.img_without_roads.setToolTip(
            '' if checked else QCoreApplication.translate(
                "SettingsDialog", "Missing roads will not be marked as errors."
            ))

    def show_help(self):
        show_plugin_help("settings")

    def set_action_type(self, action_type):
        self._action_type = action_type

        for key, value in self._lst_panel.items():
            value.set_action(action_type)
class DialogImportSchema(QDialog, DIALOG_UI):
    open_dlg_import_data = pyqtSignal(dict)  # dict with key-value params
    on_result = pyqtSignal(
        bool)  # whether the tool was run successfully or not

    BUTTON_NAME_CREATE_STRUCTURE = QCoreApplication.translate(
        "DialogImportSchema", "Create LADM-COL structure")
    BUTTON_NAME_GO_TO_IMPORT_DATA = QCoreApplication.translate(
        "DialogImportData", "Go to Import Data...")

    def __init__(self,
                 iface,
                 conn_manager,
                 context,
                 selected_models=list(),
                 link_to_import_data=True,
                 parent=None):
        QDialog.__init__(self, parent)
        self.iface = iface
        self.conn_manager = conn_manager
        self.selected_models = selected_models
        self.link_to_import_data = link_to_import_data
        self.logger = Logger()
        self.app = AppInterface()
        self.__ladmcol_models = LADMColModelRegistry()

        self.java_dependency = JavaDependency()
        self.java_dependency.download_dependency_completed.connect(
            self.download_java_complete)
        self.java_dependency.download_dependency_progress_changed.connect(
            self.download_java_progress_change)

        self.db_source = context.get_db_sources()[0]
        self.db = self.conn_manager.get_db_connector_from_source(
            self.db_source)
        self.base_configuration = BaseConfiguration()
        self.ilicache = IliCache(self.base_configuration)
        self._dbs_supported = ConfigDBsSupported()
        self._running_tool = False

        # There may be two cases where we need to emit a db_connection_changed from the Schema Import dialog:
        #   1) Connection Settings was opened and the DB conn was changed.
        #   2) Connection Settings was never opened but the Schema Import ran successfully, in a way that new models may
        #      convert a db/schema LADM-COL compliant.
        self._db_was_changed = False  # To postpone calling refresh gui until we close this dialog instead of settings

        # Similarly, we could call a refresh on layers and relations cache in two cases:
        #   1) If the SI dialog was called for the COLLECTED source: opening Connection Settings and changing the DB
        #      connection.
        #   2) Not opening the Connection Settings, but running a successful Schema Import on the COLLECTED DB, which
        #      invalidates the cache as models change.
        self._schedule_layers_and_relations_refresh = False

        self.setupUi(self)

        self.validators = Validators()

        self.update_import_models()
        self.previous_item = QListWidgetItem()

        self.connection_setting_button.clicked.connect(self.show_settings)
        self.connection_setting_button.setText(
            QCoreApplication.translate("DialogImportSchema",
                                       "Connection Settings"))

        # CRS Setting
        self.srs_auth = DEFAULT_SRS_AUTH
        self.srs_code = DEFAULT_SRS_CODE
        self.crsSelector.crsChanged.connect(self.crs_changed)

        # LOG
        self.log_config.setTitle(
            QCoreApplication.translate("DialogImportSchema", "Show log"))

        self.buttonBox.accepted.disconnect()
        self.buttonBox.clicked.connect(self.accepted_import_schema)
        self.buttonBox.clear()
        self.buttonBox.addButton(QDialogButtonBox.Cancel)
        self._accept_button = self.buttonBox.addButton(
            self.BUTTON_NAME_CREATE_STRUCTURE, QDialogButtonBox.AcceptRole)
        self.buttonBox.addButton(QDialogButtonBox.Help)
        self.buttonBox.helpRequested.connect(self.show_help)

        self.import_models_list_widget.setDisabled(bool(
            selected_models))  # If we got models from params, disable panel

        self.update_connection_info()
        self.restore_configuration()

        self.bar = QgsMessageBar()
        self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop)

    def accepted_import_schema(self, button):
        if self.buttonBox.buttonRole(button) == QDialogButtonBox.AcceptRole:
            if button.text() == self.BUTTON_NAME_CREATE_STRUCTURE:
                self.accepted()
            elif button.text() == self.BUTTON_NAME_GO_TO_IMPORT_DATA:
                self.close(
                )  # Close import schema dialog and open import open dialog
                self.open_dlg_import_data.emit({"db_source": self.db_source})

    def reject(self):
        if self._running_tool:
            QMessageBox.information(
                self,
                QCoreApplication.translate("DialogImportSchema", "Warning"),
                QCoreApplication.translate(
                    "DialogImportSchema",
                    "The Import Schema tool is still running. Please wait until it finishes."
                ))
        else:
            self.close_dialog()

    def close_dialog(self):
        """
        We use this method to be safe when emitting the db_connection_changed, otherwise we could trigger slots that
        unload the plugin, destroying dialogs and thus, leading to crashes.
        """
        if self._schedule_layers_and_relations_refresh:
            self.conn_manager.db_connection_changed.connect(
                self.app.core.cache_layers_and_relations)

        if self._db_was_changed:
            self.conn_manager.db_connection_changed.emit(
                self.db,
                self.db.test_connection()[0], self.db_source)

        if self._schedule_layers_and_relations_refresh:
            self.conn_manager.db_connection_changed.disconnect(
                self.app.core.cache_layers_and_relations)

        self.logger.info(__name__, "Dialog closed.")
        self.done(QDialog.Accepted)

    def update_connection_info(self):
        db_description = self.db.get_description_conn_string()
        if db_description:
            self.db_connect_label.setText(db_description)
            self.db_connect_label.setToolTip(self.db.get_display_conn_string())
            self._accept_button.setEnabled(True)
        else:
            self.db_connect_label.setText(
                QCoreApplication.translate("DialogImportSchema",
                                           "The database is not defined!"))
            self.db_connect_label.setToolTip('')
            self._accept_button.setEnabled(False)

    def update_import_models(self):
        for model in self.__ladmcol_models.supported_models():
            if not model.hidden():
                item = QListWidgetItem(model.full_alias())
                item.setData(Qt.UserRole, model.full_name())
                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
                if self.selected_models:  # From parameters
                    item.setCheckState(Qt.Checked if model.id() in
                                       self.selected_models else Qt.Unchecked)
                else:  # By default
                    item.setCheckState(
                        Qt.Checked if model.checked() else Qt.Unchecked)
                self.import_models_list_widget.addItem(item)

        self.import_models_list_widget.itemClicked.connect(
            self.on_item_clicked_import_model)
        self.import_models_list_widget.itemChanged.connect(
            self.on_itemchanged_import_model)

    def on_item_clicked_import_model(self, item):
        # disconnect signal to do changes in the items
        self.import_models_list_widget.itemChanged.disconnect(
            self.on_itemchanged_import_model)
        if self.previous_item.text() != item.text():
            item.setCheckState(Qt.Unchecked if item.checkState() ==
                               Qt.Checked else Qt.Checked)

        # connect signal to check when the items change
        self.import_models_list_widget.itemChanged.connect(
            self.on_itemchanged_import_model)
        self.previous_item = item

    def on_itemchanged_import_model(self, item):
        if self.previous_item.text() != item.text():
            item.setSelected(True)
        self.previous_item = item

    def get_checked_models(self):
        checked_models = list()
        for index in range(self.import_models_list_widget.count()):
            item = self.import_models_list_widget.item(index)
            if item.checkState() == Qt.Checked:
                checked_models.append(item.data(Qt.UserRole))

        return checked_models

    def show_settings(self):
        # We only need those tabs related to Model Baker/ili2db operations
        dlg = SettingsDialog(self.conn_manager, parent=self)
        dlg.setWindowTitle(
            QCoreApplication.translate("DialogImportSchema",
                                       "Target DB Connection Settings"))
        dlg.show_tip(
            QCoreApplication.translate(
                "DialogImportSchema",
                "Configure where do you want the LADM-COL structure to be created."
            ))
        dlg.set_db_source(self.db_source)
        dlg.set_tab_pages_list(
            [SETTINGS_CONNECTION_TAB_INDEX, SETTINGS_MODELS_TAB_INDEX])

        # Connect signals (DBUtils, Core)
        dlg.db_connection_changed.connect(self.db_connection_changed)
        if self.db_source == COLLECTED_DB_SOURCE:
            self._schedule_layers_and_relations_refresh = True

        dlg.set_action_type(EnumDbActionType.SCHEMA_IMPORT)

        if dlg.exec_():
            self.db = dlg.get_db_connection()
            self.update_connection_info()

    def db_connection_changed(self, db, ladm_col_db, db_source):
        # We dismiss parameters here, after all, we already have the db, and the ladm_col_db may change from this moment
        # until we close the import schema dialog
        self._db_was_changed = True
        self.clear_messages()  # Clean GUI messages if db connection changed

    def accepted(self):
        self._running_tool = True
        self.txtStdout.clear()
        self.progress_bar.setValue(0)
        self.bar.clearWidgets()

        java_home_set = self.java_dependency.set_java_home()
        if not java_home_set:
            message_java = QCoreApplication.translate(
                "DialogImportSchema",
                """Configuring Java {}...""").format(JAVA_REQUIRED_VERSION)
            self.txtStdout.setTextColor(QColor('#000000'))
            self.txtStdout.clear()
            self.txtStdout.setText(message_java)
            self.java_dependency.get_java_on_demand()
            self.disable()
            return

        configuration = self.update_configuration()
        configuration = self.apply_role_model_configuration(configuration)

        if not self.get_checked_models():
            self._running_tool = False
            message_error = QCoreApplication.translate(
                "DialogImportSchema",
                "You should select a valid model(s) before creating the LADM-COL structure."
            )
            self.txtStdout.setText(message_error)
            self.show_message(message_error, Qgis.Warning)
            self.import_models_list_widget.setFocus()
            return

        self.save_configuration(configuration)

        with OverrideCursor(Qt.WaitCursor):
            self.progress_bar.show()
            self.disable()
            self.txtStdout.setTextColor(QColor('#000000'))
            self.txtStdout.clear()

            importer = iliimporter.Importer()

            db_factory = self._dbs_supported.get_db_factory(self.db.engine)

            importer.tool = db_factory.get_model_baker_db_ili_mode()
            importer.configuration = configuration
            importer.stdout.connect(self.print_info)
            importer.stderr.connect(self.on_stderr)
            importer.process_started.connect(self.on_process_started)
            importer.process_finished.connect(self.on_process_finished)

            try:
                if importer.run() != iliimporter.Importer.SUCCESS:
                    self._running_tool = False
                    self.show_message(
                        QCoreApplication.translate(
                            "DialogImportSchema",
                            "An error occurred when creating the LADM-COL structure. For more information see the log..."
                        ), Qgis.Warning)
                    self.on_result.emit(
                        False
                    )  # Inform other classes that the execution was not successful
                    return
            except JavaNotFoundError:
                self._running_tool = False
                message_error_java = QCoreApplication.translate(
                    "DialogImportSchema",
                    "Java {} could not be found. You can configure the JAVA_HOME environment variable manually, restart QGIS and try again."
                ).format(JAVA_REQUIRED_VERSION)
                self.txtStdout.setTextColor(QColor('#000000'))
                self.txtStdout.clear()
                self.txtStdout.setText(message_error_java)
                self.show_message(message_error_java, Qgis.Warning)
                return

            self._running_tool = False
            self.buttonBox.clear()
            if self.link_to_import_data:
                self.buttonBox.addButton(
                    self.BUTTON_NAME_GO_TO_IMPORT_DATA,
                    QDialogButtonBox.AcceptRole).setStyleSheet(
                        "color: #007208;")
            self.buttonBox.setEnabled(True)
            self.buttonBox.addButton(QDialogButtonBox.Close)
            self.progress_bar.setValue(100)
            self.print_info(
                QCoreApplication.translate("DialogImportSchema", "\nDone!"),
                '#004905')
            self.show_message(
                QCoreApplication.translate(
                    "DialogImportSchema",
                    "LADM-COL structure was successfully created!"),
                Qgis.Success)
            self.on_result.emit(
                True)  # Inform other classes that the execution was successful
            self._db_was_changed = True  # Schema could become LADM compliant after a schema import

            if self.db_source == COLLECTED_DB_SOURCE:
                self.logger.info(
                    __name__,
                    "Schedule a call to refresh db relations cache since a Schema Import was run on the current 'collected' DB."
                )
                self._schedule_layers_and_relations_refresh = True

    def download_java_complete(self):
        self.accepted()

    def download_java_progress_change(self, progress):
        self.progress_bar.setValue(progress / 2)
        if (progress % 20) == 0:
            self.txtStdout.append('...')

    def save_configuration(self, configuration):
        settings = QSettings()
        settings.setValue('Asistente-LADM-COL/QgisModelBaker/show_log',
                          not self.log_config.isCollapsed())
        settings.setValue('Asistente-LADM-COL/QgisModelBaker/srs_auth',
                          self.srs_auth)
        settings.setValue('Asistente-LADM-COL/QgisModelBaker/srs_code',
                          self.srs_code)

    def restore_configuration(self):
        settings = QSettings()

        # CRS
        srs_auth = settings.value('Asistente-LADM-COL/QgisModelBaker/srs_auth',
                                  DEFAULT_SRS_AUTH, str)
        srs_code = settings.value('Asistente-LADM-COL/QgisModelBaker/srs_code',
                                  int(DEFAULT_SRS_CODE), int)
        self.crsSelector.setCrs(get_crs_from_auth_and_code(srs_auth, srs_code))
        self.crs_changed()

        # Show log
        value_show_log = settings.value(
            'Asistente-LADM-COL/QgisModelBaker/show_log', False, type=bool)
        self.log_config.setCollapsed(not value_show_log)

        # set model repository
        # if there is no option  by default use online model repository
        self.use_local_models = settings.value(
            'Asistente-LADM-COL/models/custom_model_directories_is_checked',
            DEFAULT_USE_CUSTOM_MODELS,
            type=bool)
        if self.use_local_models:
            self.custom_model_directories = settings.value(
                'Asistente-LADM-COL/models/custom_models', DEFAULT_MODELS_DIR)

    def crs_changed(self):
        self.srs_auth, self.srs_code = get_crs_authid(
            self.crsSelector.crs()).split(":")
        if self.srs_code != DEFAULT_SRS_CODE or self.srs_auth != DEFAULT_SRS_AUTH:
            self.crs_label.setStyleSheet('color: orange')
            self.crs_label.setToolTip(
                QCoreApplication.translate(
                    "DialogImportSchema",
                    "The {} (Colombian National Origin) is recommended,<br>since official models were created for that projection."
                ).format(DEFAULT_SRS_AUTHID))
        else:
            self.crs_label.setStyleSheet('')
            self.crs_label.setToolTip(
                QCoreApplication.translate("DialogImportSchema",
                                           "Coordinate Reference System"))

    def update_configuration(self):
        db_factory = self._dbs_supported.get_db_factory(self.db.engine)

        configuration = SchemaImportConfiguration()
        db_factory.set_ili2db_configuration_params(self.db.dict_conn_params,
                                                   configuration)

        # set custom toml file
        configuration.tomlfile = TOML_FILE_DIR
        configuration.inheritance = ILI2DBNames.DEFAULT_INHERITANCE
        configuration.create_basket_col = ILI2DBNames.CREATE_BASKET_COL
        configuration.create_import_tid = ILI2DBNames.CREATE_IMPORT_TID
        configuration.stroke_arcs = ILI2DBNames.STROKE_ARCS

        # CTM12 support
        configuration.srs_auth = self.srs_auth
        configuration.srs_code = self.srs_code
        if self.srs_auth == DEFAULT_SRS_AUTH and self.srs_code == DEFAULT_SRS_CODE:
            if self.db.engine == 'pg':
                configuration.pre_script = CTM12_PG_SCRIPT_PATH
            elif self.db.engine == 'gpkg':
                # (Ugly, I know) We need to send known parameters, we'll fix this in the post_script
                configuration.srs_auth = 'EPSG'
                configuration.srs_code = 3116
                configuration.post_script = CTM12_GPKG_SCRIPT_PATH

        full_java_exe_path = JavaDependency.get_full_java_exe_path()
        if full_java_exe_path:
            self.base_configuration.java_path = full_java_exe_path

        # User could have changed the default values
        self.use_local_models = QSettings().value(
            'Asistente-LADM-COL/models/custom_model_directories_is_checked',
            DEFAULT_USE_CUSTOM_MODELS,
            type=bool)
        self.custom_model_directories = QSettings().value(
            'Asistente-LADM-COL/models/custom_models', DEFAULT_MODELS_DIR)

        # Check custom model directories
        if self.use_local_models:
            if not self.custom_model_directories:
                self.base_configuration.custom_model_directories_enabled = False
            else:
                self.base_configuration.custom_model_directories = self.custom_model_directories
                self.base_configuration.custom_model_directories_enabled = True

        configuration.base_configuration = self.base_configuration
        if self.get_checked_models():
            configuration.ilimodels = ';'.join(self.get_checked_models())

        return configuration

    def apply_role_model_configuration(self, configuration):
        """
        Applies the configuration that the active role has set over checked models.

        Important:
        Note that this works better if the checked models are limited to one (e.g. Field Data Capture) or limited to
        a group of related models (e.g., the 3 supplies models). When the checked models are heterogeneous, results
        start to be unpredictable, as the configuration for a single model may affect the others.

        :param configuration: SchemaImportConfiguration object
        :return: configuration object updated
        """
        for checked_model in self.get_checked_models():
            model = self.__ladmcol_models.model_by_full_name(checked_model)
            params = model.get_ili2db_params()
            if ILI2DB_SCHEMAIMPORT in params:
                for param in params[ILI2DB_SCHEMAIMPORT]:  # List of tuples
                    if param[
                            0] == ILI2DB_CREATE_BASKET_COL_KEY:  # param: (option, value)
                        configuration.create_basket_col = True
                        self.logger.debug(
                            __name__,
                            "Schema Import createBasketCol enabled! (taken from role config)"
                        )

        return configuration

    def print_info(self, text, text_color='#000000', clear=False):
        self.txtStdout.setTextColor(QColor(text_color))
        self.txtStdout.append(text)
        QCoreApplication.processEvents()

    def on_stderr(self, text):
        color_log_text(text, self.txtStdout)
        self.advance_progress_bar_by_text(text)

    def on_process_started(self, command):
        self.txtStdout.setText(command)
        QCoreApplication.processEvents()

    def on_process_finished(self, exit_code, result):
        if exit_code == 0:
            color = '#004905'
            message = QCoreApplication.translate(
                "DialogImportSchema",
                "Model(s) successfully imported into the database!")
        else:
            color = '#aa2222'
            message = QCoreApplication.translate("DialogImportSchema",
                                                 "Finished with errors!")

            # Open log
            if self.log_config.isCollapsed():
                self.log_config.setCollapsed(False)

        self.txtStdout.setTextColor(QColor(color))
        self.txtStdout.append(message)

    def advance_progress_bar_by_text(self, text):
        if text.strip() == 'Info: compile models…':
            self.progress_bar.setValue(20)
            QCoreApplication.processEvents()
        elif text.strip() == 'Info: create table structure…':
            self.progress_bar.setValue(70)
            QCoreApplication.processEvents()

    def clear_messages(self):
        self.bar.clearWidgets(
        )  # Remove previous messages before showing a new one
        self.txtStdout.clear()  # Clear previous log messages
        self.progress_bar.setValue(0)  # Initialize progress bar

    def show_help(self):
        show_plugin_help("import_schema")

    def disable(self):
        self.db_config.setEnabled(False)
        self.ili_config.setEnabled(False)
        self.buttonBox.setEnabled(False)

    def enable(self):
        self.db_config.setEnabled(True)
        self.ili_config.setEnabled(True)
        self.buttonBox.setEnabled(True)

    def show_message(self, message, level):
        if level == Qgis.Warning:
            self.enable()

        self.bar.clearWidgets(
        )  # Remove previous messages before showing a new one
        self.bar.pushMessage("Asistente LADM-COL", message, level, duration=0)
class STUtils(QObject):
    def __init__(self):
        QObject.__init__(self)
        self.logger = Logger()
        self.st_session = STSession()
        self.st_config = TransitionalSystemConfig()

    def upload_file(self, request_id, supply_type, file_path, comments):
        url = self.st_config.ST_UPLOAD_FILE_SERVICE_URL.format(request_id)

        payload = {'typeSupplyId': supply_type, 'observations': comments}
        files = [('files[]', open(file_path, 'rb'))]
        headers = {
            'Authorization':
            "Bearer {}".format(
                self.st_session.get_logged_st_user().get_token())
        }

        msg = ""
        try:
            self.logger.debug(__name__,
                              "Uploading file to transitional system...")
            response = requests.request("PUT",
                                        url,
                                        headers=headers,
                                        data=payload,
                                        files=files)
        except requests.ConnectionError as e:
            msg = self.st_config.ST_CONNECTION_ERROR_MSG.format(e)
            self.logger.warning(__name__, msg)
            return False, msg

        status_OK = response.status_code == 200
        if status_OK:
            msg = QCoreApplication.translate(
                "STUtils",
                "The file was successfully uploaded to the Transitional System!"
            )
            self.logger.success(__name__, msg)
        else:
            if response.status_code == 500:
                msg = self.st_config.ST_STATUS_500_MSG
                self.logger.warning(__name__, self.st_config.ST_STATUS_500_MSG)
            elif response.status_code > 500 and response.status_code < 600:
                msg = self.st_config.ST_STATUS_GT_500_MSG
                self.logger.warning(__name__,
                                    self.st_config.ST_STATUS_GT_500_MSG)
            elif response.status_code == 401:
                msg = self.st_config.ST_STATUS_401_MSG
                self.logger.warning(__name__, self.st_config.ST_STATUS_401_MSG)
            elif response.status_code == 422:
                response_data = json.loads(response.text)
                msg = QCoreApplication.translate(
                    "STUtils", "File was not uploaded! Details: {}").format(
                        response_data['message'])
                self.logger.warning(__name__, msg)
            else:
                msg = QCoreApplication.translate(
                    "STUtils",
                    "Status code not handled: {}").format(response.status_code)
                self.logger.warning(__name__, msg)

        return status_OK, msg
Exemple #20
0
class AppProcessingInterface(QObject):
    def __init__(self):
        QObject.__init__(self)

        self.logger = Logger()

        self.ladm_col_provider = LADMCOLAlgorithmProvider()
        self.__processing_resources_installed = list()
        self.__processing_model_dirs_to_register = [PROCESSING_MODELS_DIR]
        self.__processing_script_dirs_to_register = [PROCESSING_SCRIPTS_DIR]

    def initialize_processing_resources(self):
        """
        Add custom provider, models and scripts to QGIS
        """
        QgsApplication.processingRegistry().addProvider(self.ladm_col_provider)

        connect_provider_added = False
        if QgsApplication.processingRegistry().providerById('model'):
            self.__add_processing_resources_by_provider('model')
        else:
            connect_provider_added = True

        if QgsApplication.processingRegistry().providerById('script'):
            self.__add_processing_resources_by_provider('script')
        else:
            connect_provider_added = True

        if connect_provider_added:  # We need to wait until processing is initialized
            QgsApplication.processingRegistry().providerAdded.connect(
                self.__add_processing_resources_by_provider)

    def __add_processing_resources_by_provider(self, provider_id):
        if provider_id not in ['model', 'script']:
            return

        if sorted(self.__processing_resources_installed) == [
                "models", "script"
        ]:  # We are done, disconnect.
            try:
                QgsApplication.processingRegistry().providerAdded.disconnect(
                    self.__add_processing_resources_by_provider)
            except:
                pass  # Disconnect throws an error if the SLOT is already disconnected

        if provider_id == 'model':
            for models_dir in self.__processing_model_dirs_to_register:
                self.__register_processing_models(models_dir)

            self.__processing_resources_installed.append('model')
        elif provider_id == 'script':
            for scripts_dir in self.__processing_script_dirs_to_register:
                self.__register_processing_scripts(scripts_dir)

            self.__processing_resources_installed.append('script')

    def __register_processing_models(self, models_dir):
        # First get model file names from the model root folder
        filenames = list()
        for filename in glob.glob(os.path.join(models_dir,
                                               '*.model3')):  # Non-recursive
            filenames.append(filename)

        # Now, go for subfolders.
        # We store models that depend on QGIS versions in folders like "314" (for QGIS 3.14.x)
        # This was initially needed for the FieldMapper input, which was migrated to C++ in QGIS 3.14
        qgis_major_version = str(Qgis.QGIS_VERSION_INT)[:3]
        qgis_major_version_path = os.path.join(models_dir, qgis_major_version)

        if not os.path.isdir(qgis_major_version_path):
            # No folder for this version (e.g., unit tests on QGIS-dev), so let's find the most recent version
            subfolders = [
                sf.name for sf in os.scandir(models_dir) if sf.is_dir()
            ]
            if subfolders:
                qgis_major_version_path = os.path.join(models_dir,
                                                       max(subfolders))

        for filename in glob.glob(
                os.path.join(qgis_major_version_path, '*.model3')):
            filenames.append(filename)

        # Finally, do load the models!
        count = 0
        registered_models = list()
        for filename in filenames:
            alg = QgsProcessingModelAlgorithm()
            if not alg.fromFile(filename):
                self.logger.critical(
                    __name__,
                    "Couldn't load model from '{}'!".format(filename))
                return

            registered_models.append(os.path.basename(filename))
            destFilename = os.path.join(ModelerUtils.modelsFolders()[0],
                                        os.path.basename(filename))
            shutil.copyfile(filename, destFilename)
            count += 1

        if count:
            QgsApplication.processingRegistry().providerById(
                'model').refreshAlgorithms()
            if DEFAULT_LOG_MODE == EnumLogMode.DEV:
                self.logger.debug(
                    __name__,
                    "{} LADM-COL Processing models were installed! {}".format(
                        count, registered_models))
            else:
                self.logger.debug(
                    __name__,
                    "{} LADM-COL Processing models were installed!".format(
                        count))

    def __register_processing_scripts(self, scripts_dir):
        count = 0
        registered_scripts = list()
        qgis_scripts_dir = ScriptUtils.defaultScriptsFolder()
        for filename in glob.glob(os.path.join(scripts_dir, '*.py')):
            try:
                shutil.copy(filename, qgis_scripts_dir)
                count += 1
                registered_scripts.append(os.path.basename(filename))
            except OSError as e:
                self.logger.critical(
                    __name__,
                    "Couldn't install LADM-COL script '{}'!".format(filename))

        if count:
            QgsApplication.processingRegistry().providerById(
                "script").refreshAlgorithms()
            if DEFAULT_LOG_MODE == EnumLogMode.DEV:
                self.logger.debug(
                    __name__,
                    "{} LADM-COL Processing scripts were installed! {}".format(
                        count, registered_scripts))
            else:
                self.logger.debug(
                    __name__,
                    "{} LADM-COL Processing scripts were installed!".format(
                        count))

    def register_add_on_processing_models(self, models_dir):
        """
        For add-ons to delegate the registration of their own Processing models.

        :param models_dir: Path to the directory containing Processing models
        """
        if 'model' in self.__processing_resources_installed:
            # The plugin already registered its models, so register right away.
            self.__register_processing_models(models_dir)
        else:
            # The plugin has not yet registered its models (waiting for Processing
            # to setup the 'model' provider). Pass the add-on's model dir to a list
            # of paths to be registered by the plugin, when Processing is ready.
            self.__processing_model_dirs_to_register.append(models_dir)

    def register_add_on_processing_scripts(self, scripts_dir):
        """
        For add-ons to delegate the registration of their own Processing scripts.

        :param models_dir: Path to the directory containing Processing scripts
        """
        if 'script' in self.__processing_resources_installed:
            # The plugin already registered its scripts, so register right away.
            self.__register_processing_scripts(scripts_dir)
        else:
            # The plugin has not yet registered its scripts (waiting for Processing
            # to setup the 'script' provider). Pass the add-on's script dir to a list
            # of paths to be registered by the plugin, when Processing is ready.
            self.__processing_script_dirs_to_register.append(scripts_dir)

    def unload_resources(self):
        # TODO: Also unregister models and scripts
        QgsApplication.processingRegistry().removeProvider(
            self.ladm_col_provider)
Exemple #21
0
class GUIBuilder(QObject):
    """
    Build plugin GUI according to roles and LADM-COL models present in the current db connection
    """
    def __init__(self, iface):
        QObject.__init__(self)
        self.iface = iface
        self.logger = Logger()
        self._registered_actions = dict()
        self._registered_dock_widgets = dict()

        self.menus = list()
        self.toolbar_menus = list()
        self.toolbars = list()

        # When building the GUI we rely on info from the DB connection
        self._db = None
        self._test_conn_result = None
        self._db_engine_actions = list()
        self._engine_name = ""

    def register_action(self, key, action):
        self._registered_actions[key] = {
            ACTION: action,
            DEFAULT_ACTION_TEXT: action.text(),
            DEFAULT_ACTION_STATUS: action.isEnabled()
        }

    def register_actions(self, dict_key_action):
        new_dict = dict()
        for k, v in dict_key_action.items():
            new_dict[k] = {
                ACTION: v,
                DEFAULT_ACTION_TEXT: v.text(),
                DEFAULT_ACTION_STATUS: v.isEnabled()
            }

        self._registered_actions.update(new_dict)
        del new_dict

    def unregister_actions(self, list_keys):
        """
        Mainly for plugins that add functionalities as add-ons
        :param list_keys: List of action keys
        """
        b_any_change = False
        for action_key in list_keys:
            if action_key in self._registered_actions:
                b_any_change = True
                self.logger.info(
                    __name__,
                    "Unregistering action '{}'...".format(action_key))
                # del self._registered_actions[action_key][ACTION]  # Leave this to the add-on
                del self._registered_actions[action_key]

        if b_any_change:
            self.build_gui(
            )  # Refresh the GUI, since removing actions might leave some menus unnecessary

    def get_action(self, action_key):
        return self._get_and_configure_action(action_key)

    def register_dock_widget(self, key, dock_widget):
        self._registered_dock_widgets[key] = dock_widget

    def set_db_connection(self, db, test_conn_result=None):
        """
        Set the DB connection info this class will use to build the GUI.

        :param db: DBConnector object
        :param test_conn_result: Can be True or False if test_connection was called, or None if we should call it.
        :return:
        """
        self._db = db

        if test_conn_result is not None:
            self._test_conn_result = test_conn_result
        else:
            self._test_conn_result, code, msg = db.test_connection()
            if not self._test_conn_result:
                self.logger.warning(
                    __name__,
                    "Test connection is False! Details: {}".format(msg))

        db_factory = ConfigDBsSupported().get_db_factory(db.engine)
        self._db_engine_actions = db_factory.get_db_engine_actions()
        self._engine_name = db_factory.get_name()

    def build_gui(self):
        """
        Build the plugin gui according to configurations.
        We first check if the DB is LADM, if not, we use a default gui configuration. Otherwise we ask the role_key for
        a gui configuration. If he/she has it, we use it, otherwise we use a template gui configuration.
        """
        if self._db is None or self._test_conn_result is None:
            self.logger.warning(
                __name__,
                QCoreApplication.translate(
                    "AsistenteLADMCOLPlugin",
                    "You should first set the db connection in the GUI_Builder to build the GUI!"
                ))
            return

        self.unload_gui(final_unload=False)  # First clear everything

        # Filter menus and actions and get a gui_config with the proper structure ready to build the GUI (e.g., with no
        # empty Menus)
        gui_config = self._get_filtered_gui_config()

        for component, values in gui_config.items():
            if component == MAIN_MENU:
                for menu_def in values:
                    menu = self._build_menu(menu_def)

                    # Try to add the menu in the second to last position of the QGIS menus
                    existent_actions = self.iface.mainWindow().menuBar(
                    ).actions()
                    if len(existent_actions) > 0:
                        last_action = existent_actions[-1]
                        self.iface.mainWindow().menuBar().insertMenu(
                            last_action, menu)
                    else:
                        self.iface.mainWindow().menuBar().addMenu(menu)
                    self.menus.append(menu)
            elif component == TOOLBAR:
                for toolbar_def in values:  # We expect a list of dicts here...
                    toolbar = self._build_toolbar(toolbar_def)

                    self.toolbars.append(toolbar)

    def _get_filtered_gui_config(self):
        """
        Rebuilds a gui_config dict removing not allowed actions.

        :return: Dictionary in the form of a gui_config dict, but only with allowed actions for the role_key passed.
        """
        role_key = RoleRegistry().get_active_role()
        self.logger.info(
            __name__,
            "Active role: {}".format(RoleRegistry().get_role_name(role_key)))

        gui_config = self._get_gui_config(role_key)
        # self.logger.debug(__name__, "Filtered gui_config: {}".format(gui_config))
        role_actions = self._get_role_actions(role_key)
        model_actions = self._get_model_actions(
        ) if self._test_conn_result else list()

        # If you want to take models into account, combine role_actions and model_actions as you like, and store the
        # result in allowed_actions.
        #
        # Here we define how to deal with actions, role permissions and models present
        # We decided to prefer always the rol's actions. Like this (R: Role, M: Model, Res: Result):
        # R  M   Res
        # V  V    V
        # V  F    V
        # F  V    F
        # F  F    F
        #
        # Therefore:
        allowed_actions = role_actions  # It's safe to make use of this list, no need to copy it, as it's a sum of lists
        self.logger.debug(
            __name__,
            "Allowed actions for role '{}': {}".format(role_key,
                                                       allowed_actions))

        # Now, use only allowed actions and remove other actions from gui_config
        filtered_gui_config = dict()
        for k, v in gui_config.items():
            if k == MAIN_MENU or k == TOOLBAR:
                for menu_def in v:
                    actions = self._get_filtered_actions(
                        menu_def[ACTIONS], allowed_actions)
                    if actions:
                        menu_def[ACTIONS] = actions
                        if not k in filtered_gui_config:
                            filtered_gui_config[k] = [menu_def]
                        else:
                            filtered_gui_config[k].append(menu_def)

        return filtered_gui_config

    def _get_filtered_actions(self, action_list, allowed_actions):
        """
        Filters out not allowed actions from an action list. It removes menus if no actions are allowed inside that
        menu, and it also removes separators if they are in a wrong position (e.e., two consecutive separators, a
        trailing separator, etc.)

        :param action_list: List of all actions defined in a gui_config dict.
        :param allowed_actions: List of allowed actions. Actions that are not here are not returned by this function.
        :return: List of actions with actions not allowed removed.
        """
        filtered_actions = list()
        for item in action_list:
            if type(item) is dict:  # Menu
                menu_actions = self._get_filtered_actions(
                    item[ACTIONS], allowed_actions)
                if [
                        menu_action for menu_action in menu_actions
                        if menu_action != SEPARATOR
                ]:
                    item[ACTIONS] = menu_actions
                    filtered_actions.append(item)
            elif item == SEPARATOR:
                if filtered_actions and filtered_actions[-1] != SEPARATOR:
                    filtered_actions.append(SEPARATOR)
            else:  # Action
                if (item in allowed_actions or ALL_ACTIONS in allowed_actions
                    ) and item in self._registered_actions:
                    # The action must be registered, otherwise we don't continue
                    # If the action is registered, we check if the action is allowed, either by finding ALL_ACTIONS
                    # or by finding the action in the allowed actions list
                    filtered_actions.append(item)

        self._remove_trailing_separators(filtered_actions)

        return filtered_actions

    def _remove_trailing_separators(self, action_list):
        """
        Remove unnecessary trailing separators, both in menus and in the current action_list. Modifies the input list.

        :param action_list: list of actions, separators and other widgets
        """
        for item in action_list[:]:
            if type(item) is dict:
                # We don't expect empty ACTION lists, so it should be safe a [-1]
                if item[ACTIONS][-1] == SEPARATOR:
                    del item[ACTIONS][-1]

        if action_list and action_list[-1] == SEPARATOR:
            del action_list[-1]

    def _get_gui_config(self, role_key):
        """
        Get a basic GUI config (still unfiltered).

        :param role_key: Active role key to whom we will ask for its GUI config. Normally, it should be the active one.
        :return: Dictionary in the form of a gui_config dict (still unfiltered).
        """
        if self._test_conn_result:
            self.logger.info(__name__,
                             "Using template gui_config from the role.")
            return RoleRegistry().get_role_gui_config(role_key)
        else:
            default_gui = RoleRegistry().get_role_gui_config(
                role_key, DEFAULT_GUI)
            if default_gui:
                self.logger.info(
                    __name__,
                    "Using default gui_config (minimal) from the role.")
                return default_gui
            else:
                self.logger.info(
                    __name__,
                    "Using gui_config from the default GUI (minimal).")
                return GUI_Config().get_gui_dict(
                    DEFAULT_GUI
                )  # Use a default gui config given by the plugin

    def _get_role_actions(self, role_key):
        """
        Get actions a given role has access to.

        :param role_key: Role key.
        :return: List of actions a role has access to.
        """
        return RoleRegistry().get_role_actions(role_key)

    def _get_model_actions(self):
        """
        Gets a list of actions that models in the DB enable. E.g., if we have valuation model, we add to this list
        valuation actions, otherwise we don't.

        :return: List of actions without duplicate elements.
        """
        actions = list()
        if self._db.survey_model_exists():
            actions.extend(MODELS_GUI_DICT[LADMNames.SURVEY_MODEL_KEY])
        if self._db.valuation_model_exists():
            actions.extend(MODELS_GUI_DICT[LADMNames.VALUATION_MODEL_KEY])

        return list(set(actions))

    def unload_gui(self, final_unload=True):
        """
        Destroys the GUI (Menus and toolbars)

        :param final_unload: True if the plugin is closing. False if we just destroy the GUI to rebuild it once more.
        """
        if final_unload:
            self.logger.info(__name__,
                             "Unloading completely the GUI (final_unload)")
            for action_info in self._registered_actions.values():
                del action_info[ACTION]
            self._registered_actions = dict()

        for menu in self.menus:
            menu.clear()
            menu.deleteLater()

        for menu in self.toolbar_menus:  # Basically, a push button who has received a menu
            menu.deleteLater()

        for toolbar in self.toolbars:
            self.iface.mainWindow().removeToolBar(toolbar)
            del toolbar

        self.menus = list()
        self.toolbar_menus = list()
        self.toolbars = list()

        # Make sure dock widgets are deleted properly
        self.close_dock_widgets(list(self._registered_dock_widgets.keys()))

        self.logger.info(__name__, "GUI unloaded (not a final_unload)")

    def close_dock_widgets(self, dock_widget_keys):
        """
        Deletes properly registered dock widgets by key
        :param dock_widget_keys: List of dock widget keys to delete
        """
        for dock_widget_key in dock_widget_keys:
            if dock_widget_key in self._registered_dock_widgets:
                if self._registered_dock_widgets[dock_widget_key] is not None:
                    self.logger.info(
                        __name__,
                        "Deleting dock widget '{}'...".format(dock_widget_key))
                    self._registered_dock_widgets[dock_widget_key].close()
                    self._registered_dock_widgets[dock_widget_key] = None

    def _build_menu(self, menu_def):
        menu = self.iface.mainWindow().findChild(QMenu, menu_def[OBJECT_NAME])
        if menu is None:
            menu = QMenu(menu_def[WIDGET_NAME],
                         self.iface.mainWindow().menuBar())
            if ICON in menu_def:
                menu.setIcon(QIcon(menu_def[ICON]))
            menu.setObjectName(menu_def[OBJECT_NAME])

        self._build_actions(menu_def[ACTIONS], menu)

        return menu

    def _build_toolbar_menu(self, menu_def):
        # Menus for toolbars are created differently...
        widget = self.iface.mainWindow().findChild(QPushButton,
                                                   menu_def[OBJECT_NAME])
        if widget is None:
            widget = QPushButton(menu_def[WIDGET_NAME])
            menu = QMenu()
            if ICON in menu_def:
                widget.setIcon(QIcon(menu_def[ICON]))
            widget.setMenu(menu)
            self.menus.append(
                menu
            )  # Because menu ownership is not transferred to the push button!

        self._build_actions(
            menu_def[ACTIONS],
            menu)  # Now we have a normal menu, build actions on it

        return widget

    def _build_toolbar(self, toolbar_def):
        toolbar = self.iface.mainWindow().findChild(QToolBar,
                                                    toolbar_def[OBJECT_NAME])
        if toolbar is None:
            toolbar = self.iface.addToolBar(
                QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                           toolbar_def[WIDGET_NAME]))
            toolbar.setObjectName(toolbar_def[OBJECT_NAME])
            toolbar.setToolTip(toolbar_def[WIDGET_NAME])

        self._build_toolbar_actions(toolbar_def[ACTIONS], toolbar)

        return toolbar

    def _build_actions(self, actions_list, base_menu):
        for item in actions_list:
            if type(item) is dict:  # Menu
                menu = self._build_menu(item)
                base_menu.addMenu(menu)
                self.menus.append(menu)
            elif item == SEPARATOR:
                base_menu.addSeparator()
            else:  # Action
                if item in self._registered_actions:
                    base_menu.addAction(self._get_and_configure_action(item))

    def _build_toolbar_actions(self, actions_list, toolbar):
        for item in actions_list:
            if type(item) is dict:  # Menu
                widget = self._build_toolbar_menu(item)
                toolbar.addWidget(widget)
                self.toolbar_menus.append(widget)
            elif item == SEPARATOR:
                toolbar.addSeparator()
            else:  # Action
                if item in self._registered_actions:
                    toolbar.addAction(self._get_and_configure_action(item))

    def _get_and_configure_action(self, action_key):
        """
        Get and configure actions. Configuration means to enable/disable them, and set text and tooltip, among others.

        :param action_key:
        :return: Configured QAction
        """
        action = self._registered_actions[action_key][
            ACTION] if action_key in self._registered_actions else None
        if action is None:
            return action

        # Default properties
        action_text = self._registered_actions[action_key][DEFAULT_ACTION_TEXT]
        action.setEnabled(
            self._registered_actions[action_key][DEFAULT_ACTION_STATUS])
        action.setText(action_text)
        action.setToolTip(action_text)

        # If not supported by current DB engine...
        if not ALL_ACTIONS in self._db_engine_actions and not action_key in self._db_engine_actions:
            action.setEnabled(False)
            action.setText(
                QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                           "{} (not for {})").format(
                                               action_text, self._engine_name))
            action.setToolTip(
                QCoreApplication.translate(
                    "AsistenteLADMCOLPlugin",
                    "Not supported by {}".format(self._engine_name)))

        return action

    def show_welcome_screen(self):
        return not RoleRegistry().active_role_already_set()

    def add_actions_to_db_engine(self, action_key_list):
        """
        For add-ons to add a set of actions to current DB engine.
        After a call to this method, it is expected a build_gui to refresh the GUI.

        :param action_key_list: List of action keys that should be added to current db engine
        """
        self._db_engine_actions = list(
            set(self._db_engine_actions + action_key_list))
Exemple #22
0
class DialogImportData(QDialog, DIALOG_UI):
    open_dlg_import_schema = pyqtSignal(dict)  # dict with key-value params
    BUTTON_NAME_IMPORT_DATA = QCoreApplication.translate("DialogImportData", "Import data")
    BUTTON_NAME_GO_TO_CREATE_STRUCTURE = QCoreApplication.translate("DialogImportData",  "Go to Create Structure...")

    def __init__(self, iface, conn_manager, context, link_to_import_schema=True, parent=None):
        QDialog.__init__(self, parent)
        self.setupUi(self)

        QgsGui.instance().enableAutoGeometryRestore(self)
        self.iface = iface
        self.conn_manager = conn_manager
        self.db_source = context.get_db_sources()[0]
        self.link_to_import_schema = link_to_import_schema
        self.db = self.conn_manager.get_db_connector_from_source(self.db_source)
        self.logger = Logger()
        self.app = AppInterface()
        self.__ladmcol_models = LADMColModelRegistry()

        self.java_dependency = JavaDependency()
        self.java_dependency.download_dependency_completed.connect(self.download_java_complete)
        self.java_dependency.download_dependency_progress_changed.connect(self.download_java_progress_change)

        self._dbs_supported = ConfigDBsSupported()
        self._running_tool = False

        # There may be 1 case where we need to emit a db_connection_changed from the Import Data dialog:
        #   1) Connection Settings was opened and the DB conn was changed.
        self._db_was_changed = False  # To postpone calling refresh gui until we close this dialog instead of settings

        # Similarly, we could call a refresh on layers and relations cache in 1 case:
        #   1) If the ID dialog was called for the COLLECTED source: opening Connection Settings and changing the DB
        #      connection.
        self._schedule_layers_and_relations_refresh = False

        # We need bar definition above calling clear_messages
        self.bar = QgsMessageBar()
        self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop)

        self.xtf_file_browse_button.clicked.connect(
            make_file_selector(self.xtf_file_line_edit, title=QCoreApplication.translate("DialogImportData", "Open Transfer or Catalog File"),
                               file_filter=QCoreApplication.translate("DialogImportData",'Transfer File (*.xtf *.itf);;Catalog File (*.xml *.xls *.xlsx)')))

        self.validators = Validators()
        self.xtf_file_line_edit.setPlaceholderText(QCoreApplication.translate("DialogImportData", "[Name of the XTF to be imported]"))
        fileValidator = FileValidator(pattern=['*.xtf', '*.itf', '*.xml'])
        self.xtf_file_line_edit.setValidator(fileValidator)
        self.xtf_file_line_edit.textChanged.connect(self.update_import_models)
        self.xtf_file_line_edit.textChanged.emit(self.xtf_file_line_edit.text())

        # db
        self.connection_setting_button.clicked.connect(self.show_settings)
        self.connection_setting_button.setText(QCoreApplication.translate("DialogImportData", "Connection Settings"))

        # LOG
        self.log_config.setTitle(QCoreApplication.translate("DialogImportData", "Show log"))

        self.buttonBox.accepted.disconnect()
        self.buttonBox.clicked.connect(self.accepted_import_data)
        self.buttonBox.clear()
        self.buttonBox.addButton(QDialogButtonBox.Cancel)
        self._accept_button = self.buttonBox.addButton(self.BUTTON_NAME_IMPORT_DATA, QDialogButtonBox.AcceptRole)
        self.buttonBox.addButton(QDialogButtonBox.Help)
        self.buttonBox.helpRequested.connect(self.show_help)

        self.update_connection_info()
        self.restore_configuration()

    def accepted_import_data(self, button):
        if self.buttonBox.buttonRole(button) == QDialogButtonBox.AcceptRole:
            if button.text() == self.BUTTON_NAME_IMPORT_DATA:
                self.accepted()
            elif button.text() == self.BUTTON_NAME_GO_TO_CREATE_STRUCTURE:
                self.close()  # Close import data dialog
                self.open_dlg_import_schema.emit({'selected_models': self.get_ili_models()})  # Emit signal to open import schema dialog

    def reject(self):
        if self._running_tool:
            QMessageBox.information(self,
                                    QCoreApplication.translate("DialogImportData", "Warning"),
                                    QCoreApplication.translate("DialogImportData", "The Import Data tool is still running. Please wait until it finishes."))
        else:
            self.close_dialog()

    def close_dialog(self):
        """
        We use this method to be safe when emitting the db_connection_changed, otherwise we could trigger slots that
        unload the plugin, destroying dialogs and thus, leading to crashes.
        """
        if self._schedule_layers_and_relations_refresh:
            self.conn_manager.db_connection_changed.connect(self.app.core.cache_layers_and_relations)

        if self._db_was_changed:
            # If the db was changed, it implies it complies with ladm_col, hence the second parameter
            self.conn_manager.db_connection_changed.emit(self.db, True, self.db_source)

        if self._schedule_layers_and_relations_refresh:
            self.conn_manager.db_connection_changed.disconnect(self.app.core.cache_layers_and_relations)

        self.logger.info(__name__, "Dialog closed.")
        self.done(QDialog.Accepted)

    def update_connection_info(self):
        db_description = self.db.get_description_conn_string()
        if db_description:
            self.db_connect_label.setText(db_description)
            self.db_connect_label.setToolTip(self.db.get_display_conn_string())
            self._accept_button.setEnabled(True)
        else:
            self.db_connect_label.setText(QCoreApplication.translate("DialogImportData", "The database is not defined!"))
            self.db_connect_label.setToolTip('')
            self._accept_button.setEnabled(False)

    def update_import_models(self):
        self.clear_messages()
        error_msg = None

        if not self.xtf_file_line_edit.text().strip():
            color = '#ffd356'  # Light orange
            self.import_models_qmodel = QStandardItemModel()
            self.import_models_list_view.setModel(self.import_models_qmodel)
        else:
            if os.path.isfile(self.xtf_file_line_edit.text().strip()):
                color = '#fff'  # White

                self.import_models_qmodel = QStandardItemModel()
                model_names = get_models_from_xtf(self.xtf_file_line_edit.text().strip())

                for model in self.__ladmcol_models.non_hidden_and_supported_models():
                    if model.full_name() in model_names:
                        item = QStandardItem(model.full_alias())
                        item.setData(model.full_name(), Qt.UserRole)
                        item.setCheckable(False)
                        item.setEditable(False)
                        self.import_models_qmodel.appendRow(item)

                if self.import_models_qmodel.rowCount() > 0:
                    self.import_models_list_view.setModel(self.import_models_qmodel)
                else:
                    error_msg = QCoreApplication.translate("DialogImportData",
                                                               "No models were found in the XTF. Is it a valid file?")
                    color = '#ffd356'  # Light orange
                    self.import_models_qmodel = QStandardItemModel()
                    self.import_models_list_view.setModel(self.import_models_qmodel)
            else:
                error_msg = QCoreApplication.translate("DialogImportData", "Please set a valid XTF file")
                color = '#ffd356'  # Light orange
                self.import_models_qmodel = QStandardItemModel()
                self.import_models_list_view.setModel(self.import_models_qmodel)
        self.xtf_file_line_edit.setStyleSheet('QLineEdit {{ background-color: {} }}'.format(color))

        if error_msg:
            self.txtStdout.setText(error_msg)
            self.show_message(error_msg, Qgis.Warning)
            self.import_models_list_view.setFocus()
            return

    def get_ili_models(self):
        ili_models = list()
        for index in range(self.import_models_qmodel.rowCount()):
            item = self.import_models_qmodel.item(index)
            ili_models.append(item.data(Qt.UserRole))
        return ili_models

    def show_settings(self):
        # We only need those tabs related to Model Baker/ili2db operations
        dlg = SettingsDialog(self.conn_manager, parent=self)
        dlg.setWindowTitle(QCoreApplication.translate("DialogImportData", "Target DB Connection Settings"))
        dlg.show_tip(QCoreApplication.translate("DialogImportData", "Configure where do you want the XTF data to be imported."))
        dlg.set_db_source(self.db_source)
        dlg.set_tab_pages_list([SETTINGS_CONNECTION_TAB_INDEX, SETTINGS_MODELS_TAB_INDEX])

        # Connect signals (DBUtils, Core)
        dlg.db_connection_changed.connect(self.db_connection_changed)
        if self.db_source == COLLECTED_DB_SOURCE:
            self._schedule_layers_and_relations_refresh = True

        dlg.set_action_type(EnumDbActionType.IMPORT)

        if dlg.exec_():
            self.db = dlg.get_db_connection()
            self.update_connection_info()

    def db_connection_changed(self, db, ladm_col_db, db_source):
        self._db_was_changed = True
        self.clear_messages()

    def accepted(self):
        self._running_tool = True
        self.txtStdout.clear()
        self.progress_bar.setValue(0)
        self.bar.clearWidgets()

        if not os.path.isfile(self.xtf_file_line_edit.text().strip()):
            self._running_tool = False
            error_msg = QCoreApplication.translate("DialogImportData", "Please set a valid XTF file before importing data. XTF file does not exist.")
            self.txtStdout.setText(error_msg)
            self.show_message(error_msg, Qgis.Warning)
            self.xtf_file_line_edit.setFocus()
            return

        java_home_set = self.java_dependency.set_java_home()
        if not java_home_set:
            message_java = QCoreApplication.translate("DialogImportData", """Configuring Java {}...""").format(JAVA_REQUIRED_VERSION)
            self.txtStdout.setTextColor(QColor('#000000'))
            self.txtStdout.clear()
            self.txtStdout.setText(message_java)
            self.java_dependency.get_java_on_demand()
            self.disable()
            return

        ili2db = Ili2DB()

        configuration = self.update_configuration(ili2db)
        configuration = self.apply_role_model_configuration(configuration)

        if configuration.disable_validation:  # If data validation at import is disabled, we ask for confirmation
            self.msg = QMessageBox()
            self.msg.setIcon(QMessageBox.Question)
            self.msg.setText(QCoreApplication.translate("DialogImportData",
                                                        "Are you sure you want to import your data without validation?"))
            self.msg.setWindowTitle(QCoreApplication.translate("DialogImportData", "Import XTF without validation?"))
            self.msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            res = self.msg.exec_()
            if res == QMessageBox.No:
                self._running_tool = False
                return

        if not self.xtf_file_line_edit.validator().validate(configuration.xtffile, 0)[0] == QValidator.Acceptable:
            self._running_tool = False
            error_msg = QCoreApplication.translate("DialogImportData", "Please set a valid XTF before importing data.")
            self.txtStdout.setText(error_msg)
            self.show_message(error_msg, Qgis.Warning)
            self.xtf_file_line_edit.setFocus()
            return

        if not self.get_ili_models():
            self._running_tool = False
            error_msg = QCoreApplication.translate("DialogImportData", "The selected XTF file does not have information according to the LADM-COL model to import.")
            self.txtStdout.setText(error_msg)
            self.show_message(error_msg, Qgis.Warning)
            self.import_models_list_view.setFocus()
            return

        # Get list of models present in the XTF file, in the DB and in the list of required models (by the plugin)
        ili_models = set([ili_model for ili_model in self.get_ili_models()])

        supported_models_in_ili = set([m.full_name() for m in self.__ladmcol_models.supported_models()]).intersection(ili_models)

        if not supported_models_in_ili:
            self._running_tool = False
            error_msg = QCoreApplication.translate("DialogImportData",
                                                   "The selected XTF file does not have data from any LADM-COL model supported by the LADM-COL Assistant. " \
                                                   "Therefore, you cannot import it! These are the models supported:\n\n * {}").format(" \n * ".join([m.full_alias() for m in self.__ladmcol_models.supported_models()]))
            self.txtStdout.setText(error_msg)
            self.show_message(error_msg, Qgis.Warning)
            self.import_models_list_view.setFocus()
            return

        db_models = set(self.db.get_models())
        suggested_models = sorted(ili_models.difference(db_models))

        if not ili_models.issubset(db_models):
            self._running_tool = False
            error_msg = QCoreApplication.translate("DialogImportData",
                                                   "IMPORT ERROR: The XTF file to import does not have the same models as the target database schema. " \
                                                   "Please create a schema that also includes the following missing modules:\n\n * {}").format(
                " \n * ".join(suggested_models))
            self.txtStdout.clear()
            self.txtStdout.setTextColor(QColor('#000000'))
            self.txtStdout.setText(error_msg)
            self.show_message(error_msg, Qgis.Warning)
            self.xtf_file_line_edit.setFocus()

            # button is removed to define order in GUI
            for button in self.buttonBox.buttons():
                if button.text() == self.BUTTON_NAME_IMPORT_DATA:
                    self.buttonBox.removeButton(button)

            # Check if button was previously added
            self.remove_create_structure_button()

            if self.link_to_import_schema:
                self.buttonBox.addButton(self.BUTTON_NAME_GO_TO_CREATE_STRUCTURE,
                                         QDialogButtonBox.AcceptRole).setStyleSheet("color: #aa2222;")
            self.buttonBox.addButton(self.BUTTON_NAME_IMPORT_DATA,
                                     QDialogButtonBox.AcceptRole)

            return

        self.progress_bar.show()

        self.disable()
        self.txtStdout.setTextColor(QColor('#000000'))
        self.txtStdout.clear()

        self._connect_ili2db_signals(ili2db)

        self.save_configuration(configuration)

        res, msg = ili2db.import_data(self.db, configuration)

        self._disconnect_ili2db_signals(ili2db)

        self._running_tool = False

        self.progress_bar.setValue(25)

        if res:
            self.buttonBox.clear()
            self.buttonBox.setEnabled(True)
            self.buttonBox.addButton(QDialogButtonBox.Close)
            self.progress_bar.setValue(100)

        message_type = Qgis.Success if res else Qgis.Warning
        self.show_message(msg, message_type)

    def download_java_complete(self):
        self.accepted()

    def download_java_progress_change(self, progress):
        self.progress_bar.setValue(progress/2)
        if (progress % 20) == 0:
            self.txtStdout.append('...')

    def remove_create_structure_button(self):
        for button in self.buttonBox.buttons():
            if button.text() == self.BUTTON_NAME_GO_TO_CREATE_STRUCTURE:
                self.buttonBox.removeButton(button)

    def save_configuration(self, configuration):
        settings = QSettings()
        settings.setValue('Asistente-LADM-COL/QgisModelBaker/ili2pg/xtffile_import', configuration.xtffile)
        settings.setValue('Asistente-LADM-COL/QgisModelBaker/show_log', not self.log_config.isCollapsed())

    def restore_configuration(self):
        settings = QSettings()
        self.xtf_file_line_edit.setText(settings.value('Asistente-LADM-COL/QgisModelBaker/ili2pg/xtffile_import'))

        # Show log
        value_show_log = settings.value('Asistente-LADM-COL/QgisModelBaker/show_log', False, type=bool)
        self.log_config.setCollapsed(not value_show_log)

        # set model repository
        # if there is no option  by default use online model repository
        self.use_local_models = self.app.settings.custom_models
        if self.use_local_models:
            self.custom_model_directories = self.app.settings.custom_model_dirs

    def update_configuration(self, ili2db: Ili2DB):
        """
        Get the configuration that is updated with the user configuration changes on the dialog.
        :return: Configuration
        """
        disable_validation = \
            not QSettings().value('Asistente-LADM-COL/models/validate_data_importing_exporting', True, bool)

        configuration = ili2db.get_import_data_configuration(self.db, self.xtf_file_line_edit.text().strip(),
                                                             disable_validation=disable_validation)

        configuration.delete_data = False

        # TODO this is different to ili2db.py
        if self.get_ili_models():
            configuration.ilimodels = ';'.join(self.get_ili_models())

        return configuration

    def apply_role_model_configuration(self, configuration):
        """
        Applies the configuration that the active role has set over models that are in both the DB and the XTF.

        Important:
        Note that this works better if the checked models are limited to one (e.g. Field Data Capture) or limited to
        a group of related models (e.g., the 3 supplies models). When the checked models are heterogeneous, results
        start to be unpredictable, as the configuration for a single model may affect the others.

        :param configuration: SchemaImportConfiguration object
        :return: configuration object updated
        """
        model_names = get_models_from_xtf(self.xtf_file_line_edit.text().strip())

        for model in self.__ladmcol_models.non_hidden_and_supported_models():
            if model.full_name() in model_names:  # We'll check models in the DB that are also in the XTF
                params = model.get_ili2db_params()
                if ILI2DB_IMPORT in params:
                    for param in params[ILI2DB_IMPORT]:  # List of tuples
                        if param[0] == ILI2DB_DATASET:  # param: (option, value)
                            configuration.dataset = param[1]
                            self.logger.debug(__name__, "Import XTF data into dataset '{}'! (taken from role config)".format(param[1]))

        return configuration

    def print_info(self, text, text_color='#000000', clear=False):
        self.txtStdout.setTextColor(QColor(text_color))
        self.txtStdout.append(text)
        QCoreApplication.processEvents()

    def on_stderr(self, text):
        color_log_text(text, self.txtStdout)
        self.advance_progress_bar_by_text(text)

    def on_process_started(self, command):
        self.disable()
        self.txtStdout.setTextColor(QColor('#000000'))
        self.txtStdout.clear()
        self.txtStdout.setText(command)
        QCoreApplication.processEvents()

    def on_process_finished(self, exit_code, result):
        color = '#004905' if exit_code == 0 else '#aa2222'
        self.txtStdout.setTextColor(QColor(color))
        self.txtStdout.append('Finished ({})'.format(exit_code))
        if result == iliimporter.Importer.SUCCESS:
            self.buttonBox.clear()
            self.buttonBox.setEnabled(True)
            self.buttonBox.addButton(QDialogButtonBox.Close)
        else:
            self.show_message(QCoreApplication.translate("DialogImportData", "Error when importing data"), Qgis.Warning)

            # Open log
            if self.log_config.isCollapsed():
                self.log_config.setCollapsed(False)

    def advance_progress_bar_by_text(self, text):
        if text.strip() == 'Info: compile models...':
            self.progress_bar.setValue(50)
            QCoreApplication.processEvents()
        elif text.strip() == 'Info: create table structure...':
            self.progress_bar.setValue(55)
            QCoreApplication.processEvents()
        elif text.strip() == 'Info: first validation pass...':
            self.progress_bar.setValue(60)
            QCoreApplication.processEvents()
        elif text.strip() == 'Info: second validation pass...':
            self.progress_bar.setValue(80)
            QCoreApplication.processEvents()

    def clear_messages(self):
        self.bar.clearWidgets()  # Remove previous messages before showing a new one
        self.txtStdout.clear()  # Clear previous log messages
        self.progress_bar.setValue(0)  # Initialize progress bar

    def show_help(self):
        show_plugin_help("import_data")

    def disable(self):
        self.source_config.setEnabled(False)
        self.target_config.setEnabled(False)
        self.buttonBox.setEnabled(False)

    def enable(self):
        self.source_config.setEnabled(True)
        self.target_config.setEnabled(True)
        self.buttonBox.setEnabled(True)

    def show_message(self, message, level):
        if level == Qgis.Warning:
            self.enable()

        self.bar.clearWidgets()  # Remove previous messages before showing a new one
        self.bar.pushMessage("Asistente LADM-COL", message, level, duration=0)

    def _connect_ili2db_signals(self, ili2db):
        ili2db.process_started.connect(self.on_process_started)
        ili2db.stderr.connect(self.on_stderr)
        ili2db.stdout.connect(self.print_info)
        ili2db.process_finished.connect(self.on_process_finished)

    def _disconnect_ili2db_signals(self, ili2db):
        ili2db.process_started.disconnect(self.on_process_started)
        ili2db.stderr.disconnect(self.on_stderr)
        ili2db.stdout.disconnect(self.print_info)
        ili2db.process_finished.disconnect(self.on_process_finished)
Exemple #23
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()
Exemple #24
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 ReportGenerator(QObject):
    LOG_TAB = 'LADM-COL Reports'

    enable_action_requested = pyqtSignal(str, bool)

    def __init__(self, ladm_data):
        QObject.__init__(self)
        self.ladm_data = ladm_data
        self.logger = Logger()
        self.app = AppInterface()
        self.java_dependency = JavaDependency()
        self.java_dependency.download_dependency_completed.connect(
            self.download_java_complete)

        self.report_dependency = ReportDependency()
        self.report_dependency.download_dependency_completed.connect(
            self.download_report_complete)

        self.encoding = locale.getlocale()[1]
        # This might be unset
        if not self.encoding:
            self.encoding = 'UTF8'

        self._downloading = False

    def stderr_ready(self, proc):
        text = bytes(proc.readAllStandardError()).decode(self.encoding)
        self.logger.critical(__name__, text, tab=self.LOG_TAB)

    def stdout_ready(self, proc):
        text = bytes(proc.readAllStandardOutput()).decode(self.encoding)
        self.logger.info(__name__, text, tab=self.LOG_TAB)

    def update_yaml_config(self, db, config_path):
        text = ''
        qgs_uri = QgsDataSourceUri(db.uri)

        with open(os.path.join(config_path, 'config_template.yaml')) as f:
            text = f.read()
            text = text.format('{}',
                               DB_USER=qgs_uri.username(),
                               DB_PASSWORD=qgs_uri.password(),
                               DB_HOST=qgs_uri.host(),
                               DB_PORT=qgs_uri.port(),
                               DB_NAME=qgs_uri.database())
        new_file_path = os.path.join(
            config_path, self.get_tmp_filename('yaml_config', 'yaml'))

        with open(new_file_path, 'w') as new_yaml:
            new_yaml.write(text)

        return new_file_path

    def get_layer_geojson(self, db, layer_name, plot_id, report_type):
        if report_type == ANNEX_17_REPORT:
            if layer_name == 'terreno':
                return db.get_annex17_plot_data(plot_id, 'only_id')
            elif layer_name == 'terrenos':
                return db.get_annex17_plot_data(plot_id, 'all_but_id')
            elif layer_name == 'terrenos_all':
                return db.get_annex17_plot_data(plot_id, 'all')
            elif layer_name == 'construcciones':
                return db.get_annex17_building_data()
            else:
                return db.get_annex17_point_data(plot_id)
        else:  #report_type == ANT_MAP_REPORT:
            if layer_name == 'terreno':
                return db.get_ant_map_plot_data(plot_id, 'only_id')
            elif layer_name == 'terrenos':
                return db.get_ant_map_plot_data(plot_id, 'all_but_id')
            elif layer_name == 'terrenos_all':
                return db.get_annex17_plot_data(plot_id, 'all')
            elif layer_name == 'construcciones':
                return db.get_annex17_building_data()
            elif layer_name == 'puntoLindero':
                return db.get_annex17_point_data(plot_id)
            else:  #layer_name == 'cambio_colindancia':
                return db.get_ant_map_neighbouring_change_data(plot_id)

    def update_json_data(self, db, json_spec_file, plot_id, tmp_dir,
                         report_type):
        json_data = dict()
        with open(json_spec_file) as f:
            json_data = json.load(f)

        json_data['attributes']['id'] = plot_id
        json_data['attributes']['datasetName'] = db.schema
        layers = json_data['attributes']['map']['layers']
        for layer in layers:
            layer['geoJson'] = self.get_layer_geojson(db, layer['name'],
                                                      plot_id, report_type)

        overview_layers = json_data['attributes']['overviewMap']['layers']
        for layer in overview_layers:
            layer['geoJson'] = self.get_layer_geojson(db, layer['name'],
                                                      plot_id, report_type)

        new_json_file_path = os.path.join(
            tmp_dir,
            self.get_tmp_filename('json_data_{}'.format(plot_id), 'json'))
        with open(new_json_file_path, 'w') as new_json:
            new_json.write(json.dumps(json_data))

        return new_json_file_path

    def get_tmp_dir(self, create_random=True):
        if create_random:
            return tempfile.mkdtemp()

        return tempfile.gettempdir()

    def get_tmp_filename(self, basename, extension='gpkg'):
        return "{}_{}.{}".format(basename,
                                 str(time.time()).replace(".", ""), extension)

    def generate_report(self, db, report_type):
        # Check if mapfish and Jasper are installed, otherwise show where to
        # download them from and return
        if not self.report_dependency.check_if_dependency_is_valid():
            self.report_dependency.download_dependency(URL_REPORTS_LIBRARIES)
            return

        java_home_set = self.java_dependency.set_java_home()
        if not java_home_set:
            self.java_dependency.get_java_on_demand()
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ReportGenerator",
                    "Java is a prerequisite. Since it was not found, it is being configured..."
                ))
            return

        plot_layer = self.app.core.get_layer(db, db.names.LC_PLOT_T, load=True)
        if not plot_layer:
            return

        selected_plots = plot_layer.selectedFeatures()
        if not selected_plots:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "ReportGenerator",
                    "To generate reports, first select at least a plot!"))
            return

        # Where to store the reports?
        previous_folder = QSettings().value(
            "Asistente-LADM-COL/reports/save_into_dir", ".")
        save_into_folder = QFileDialog.getExistingDirectory(
            None,
            QCoreApplication.translate(
                "ReportGenerator",
                "Select a folder to save the reports to be generated"),
            previous_folder)
        if not save_into_folder:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "ReportGenerator",
                    "You need to select a folder where to save the reports before continuing."
                ))
            return
        QSettings().setValue("Asistente-LADM-COL/reports/save_into_dir",
                             save_into_folder)

        config_path = os.path.join(DEPENDENCY_REPORTS_DIR_NAME, report_type)
        json_spec_file = os.path.join(config_path, 'spec_json_file.json')

        script_name = ''
        if os.name == 'posix':
            script_name = 'print'
        elif os.name == 'nt':
            script_name = 'print.bat'

        script_path = os.path.join(DEPENDENCY_REPORTS_DIR_NAME, 'bin',
                                   script_name)
        if not os.path.isfile(script_path):
            self.logger.warning(
                __name__,
                "Script file for reports wasn't found! {}".format(script_path))
            return

        self.enable_action_requested.emit(report_type, False)

        # Update config file
        yaml_config_path = self.update_yaml_config(db, config_path)
        self.logger.debug(
            __name__, "Config file for reports: {}".format(yaml_config_path))

        total = len(selected_plots)
        step = 0
        count = 0
        tmp_dir = self.get_tmp_dir()

        # Progress bar setup
        progress = QProgressBar()
        if total == 1:
            progress.setRange(0, 0)
        else:
            progress.setRange(0, 100)
        progress.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        self.app.gui.create_progress_message_bar(
            QCoreApplication.translate("ReportGenerator",
                                       "Generating {} report{}...").format(
                                           total, '' if total == 1 else 's'),
            progress)

        polygons_with_holes = []
        multi_polygons = []

        for selected_plot in selected_plots:
            plot_id = selected_plot[db.names.T_ID_F]

            geometry = selected_plot.geometry()
            abstract_geometry = geometry.get()
            if abstract_geometry.ringCount() > 1:
                polygons_with_holes.append(str(plot_id))
                self.logger.warning(
                    __name__,
                    QCoreApplication.translate(
                        "ReportGenerator",
                        "Skipping Annex 17 for plot with {}={} because it has holes. The reporter module does not support such polygons."
                    ).format(db.names.T_ID_F, plot_id))
                continue
            if abstract_geometry.numGeometries() > 1:
                multi_polygons.append(str(plot_id))
                self.logger.warning(
                    __name__,
                    QCoreApplication.translate(
                        "ReportGenerator",
                        "Skipping Annex 17 for plot with {}={} because it is a multi-polygon. The reporter module does not support such polygons."
                    ).format(db.names.T_ID_F, plot_id))
                continue

            # Generate data file
            json_file = self.update_json_data(db, json_spec_file, plot_id,
                                              tmp_dir, report_type)
            self.logger.debug(__name__,
                              "JSON file for reports: {}".format(json_file))

            # Run sh/bat passing config and data files
            proc = QProcess()
            proc.readyReadStandardError.connect(
                functools.partial(self.stderr_ready, proc=proc))
            proc.readyReadStandardOutput.connect(
                functools.partial(self.stdout_ready, proc=proc))

            parcel_number = self.ladm_data.get_parcels_related_to_plots(
                db, [plot_id], db.names.LC_PARCEL_T_PARCEL_NUMBER_F) or ['']
            file_name = '{}_{}_{}.pdf'.format(report_type, plot_id,
                                              parcel_number[0])

            current_report_path = os.path.join(save_into_folder, file_name)
            proc.start(script_path, [
                '-config', yaml_config_path, '-spec', json_file, '-output',
                current_report_path
            ])

            if not proc.waitForStarted():
                # Grant execution permissions
                os.chmod(
                    script_path, stat.S_IXOTH | stat.S_IXGRP | stat.S_IXUSR
                    | stat.S_IRUSR | stat.S_IRGRP)
                proc.start(script_path, [
                    '-config', yaml_config_path, '-spec', json_file, '-output',
                    current_report_path
                ])

            if not proc.waitForStarted():
                proc = None
                self.logger.warning(
                    __name__, "Couldn't execute script to generate report...")
            else:
                loop = QEventLoop()
                proc.finished.connect(loop.exit)
                loop.exec()

                self.logger.debug(__name__,
                                  "{}:{}".format(plot_id, proc.exitCode()))
                if proc.exitCode() == 0:
                    count += 1

                step += 1
                progress.setValue(step * 100 / total)

        os.remove(yaml_config_path)

        self.enable_action_requested.emit(report_type, True)
        self.logger.clear_message_bar()

        if total == count:
            if total == 1:
                msg = QCoreApplication.translate(
                    "ReportGenerator",
                    "The report <a href='file:///{}'>{}</a> was successfully generated!"
                ).format(normalize_local_url(save_into_folder), file_name)
            else:
                msg = QCoreApplication.translate(
                    "ReportGenerator",
                    "All reports were successfully generated in folder <a href='file:///{path}'>{path}</a>!"
                ).format(path=normalize_local_url(save_into_folder))

            self.logger.success_msg(__name__, msg)
        else:
            details_msg = ''
            if polygons_with_holes:
                details_msg += QCoreApplication.translate(
                    "ReportGenerator",
                    " The following polygons were skipped because they have holes and are not supported: {}."
                ).format(", ".join(polygons_with_holes))
            if multi_polygons:
                details_msg += QCoreApplication.translate(
                    "ReportGenerator",
                    " The following polygons were skipped because they are multi-polygons and are not supported: {}."
                ).format(", ".join(multi_polygons))

            if total == 1:
                msg = QCoreApplication.translate(
                    "ReportGenerator",
                    "The report for plot {} couldn't be generated!{} See QGIS log (tab '{}') for details."
                ).format(plot_id, details_msg, self.LOG_TAB)
            else:
                if count == 0:
                    msg = QCoreApplication.translate(
                        "ReportGenerator",
                        "No report could be generated!{} See QGIS log (tab '{}') for details."
                    ).format(details_msg, self.LOG_TAB)
                else:
                    msg = QCoreApplication.translate(
                        "ReportGenerator",
                        "At least one report couldn't be generated!{details_msg} See QGIS log (tab '{log_tab}') for details. Go to <a href='file:///{path}'>{path}</a> to see the reports that were generated."
                    ).format(details_msg=details_msg,
                             path=normalize_local_url(save_into_folder),
                             log_tab=self.LOG_TAB)

            self.logger.warning_msg(__name__, msg)

    def download_java_complete(self):
        if self.java_dependency.fetcher_task and not self.java_dependency.fetcher_task.isCanceled(
        ):
            if self.java_dependency.check_if_dependency_is_valid():
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "ReportGenerator",
                        "Java was successfully configured!"), 5)
        else:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "ReportGenerator",
                    "You have just canceled the Java dependency download."), 5)

    def download_report_complete(self):
        if self.report_dependency.fetcher_task and not self.report_dependency.fetcher_task.isCanceled(
        ):
            if self.report_dependency.check_if_dependency_is_valid():
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "ReportGenerator",
                        "Report dependency was successfully configured!"), 5)
        else:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "ReportGenerator",
                    "You have just canceled the report dependency download."),
                5)
Exemple #26
0
class AppGUIInterface(QObject):
    add_indicators_requested = pyqtSignal(
        str, QgsLayerTreeNode.NodeType)  # node name, node type

    def __init__(self, iface):
        QObject.__init__(self)
        self.iface = iface

        self.logger = Logger()

    def trigger_add_feature(self):
        self.iface.actionAddFeature().trigger()

    def trigger_vertex_tool(self):
        self.iface.actionVertexTool().trigger()

    def create_progress_message_bar(self, text, progress):
        progressMessageBar = self.iface.messageBar().createMessage(
            PLUGIN_NAME, text)
        progressMessageBar.layout().addWidget(progress)
        self.iface.messageBar().pushWidget(progressMessageBar, Qgis.Info)

    def refresh_layer_symbology(self, layer_id):
        self.iface.layerTreeView().refreshLayerSymbology(layer_id)

    def trigger_repaint_on_layer(self, layer):
        layer.triggerRepaint()

    def refresh_map(self):
        self.iface.mapCanvas().refresh()

    def redraw_all_layers(self):
        self.iface.mapCanvas().redrawAllLayers()

    def freeze_map(self, frozen):
        self.iface.mapCanvas().freeze(frozen)

    def activate_layer(self, layer):
        self.iface.layerTreeView().setCurrentLayer(layer)

    def set_node_visibility(self, node, visible=True):
        # Modes may eventually be layer_id, group_name, layer, group
        if node is not None:
            node.setItemVisibilityChecked(visible)

    def clear_status_bar(self):
        self.iface.statusBarIface().clearMessage()

    def add_indicators(self, node_name, node_type, payload, names):
        """
        Adds all indicators for a node in layer tree. It searches for the proper node and its config.

        :param node_name: Key to get the config and possibly, the node (see payload)
        :param node_type: QgsLayerTreeNode.NodeType
        :param payload: If the node is a LADM layer, we need the layer object, as the name is not enough to disambiguate
                        between layers from different connections
        :param names: DBMappingRegistry instance to read layer names from
        """
        # First get the node
        node = None
        root = QgsProject.instance().layerTreeRoot()
        if node_type == QgsLayerTreeNode.NodeGroup:
            node = root.findGroup(node_name)
        elif node_type == QgsLayerTreeNode.NodeLayer:
            if payload:
                node = root.findLayer(payload)  # Search by QgsMapLayer
            else:  # Get the first layer matching the node name
                layers = QgsProject.instance().mapLayersByName(node_name)
                if layers:
                    node = root.findLayer(layers[0])

        if not node:
            self.logger.warning(
                __name__,
                "Node not found for adding indicators! ({}, {})".format(
                    node_name, node_type))
            return  # No node, no party

        # Then, get the config
        indicators_config = LayerTreeIndicatorConfig().get_indicators_config(
            node_name, node_type, names)
        if not indicators_config:
            self.logger.warning(
                __name__,
                "Configuration for indicators not found for node '{}'!".format(
                    node_name))

        # And finally...
        for config in indicators_config:
            self.logger.debug(
                __name__, "Adding indicator for {} node '{}'...".format(
                    'group' if node_type == QgsLayerTreeNode.NodeGroup else
                    'layer', node_name))
            self.add_indicator(node, config)

    def add_indicator(self, node, config):
        """
        Adds a single indicator for the node, based on a config dict

        :param node: Layer tree node
        :param config: Dictionary with required data to set the indicator
        """
        indicator = QgsLayerTreeViewIndicator(self.iface.layerTreeView())
        indicator.setToolTip(config[INDICATOR_TOOLTIP])
        indicator.setIcon(config[INDICATOR_ICON])
        indicator.clicked.connect(config[INDICATOR_SLOT])
        self.iface.layerTreeView().addIndicator(node, indicator)

    def set_layer_visibility(self, layer, visible):
        node = QgsProject.instance().layerTreeRoot().findLayer(layer.id())
        self.set_node_visibility(node, visible)

    @pyqtSlot()
    def clear_message_bar(self):
        self.iface.messageBar().clearWidgets()

    def zoom_full(self):
        self.iface.zoomFull()

    def zoom_to_active_layer(self):
        self.iface.zoomToActiveLayer()

    def zoom_to_selected(self):
        self.iface.actionZoomToSelected().trigger()

    def zoom_to_feature_ids(self, layer, fids):
        self.iface.mapCanvas().zoomToFeatureIds(layer, fids)

    def zoom_to_extent(self, extent):
        self.iface.mapCanvas().zoomToFeatureExtent(extent)

    def show_message(self, msg, level, duration=5):
        self.clear_message_bar(
        )  # Remove previous messages before showing a new one
        self.iface.messageBar().pushMessage("Asistente LADM-COL", msg, level,
                                            duration)

    def show_status_bar_message(self, msg, duration):
        self.iface.statusBarIface().showMessage(msg, duration)

    def add_tabified_dock_widget(self, area, dock_widget):
        """
        Adds the dock_widget to the given area, making sure it is tabified if other dock widgets exist.
        :param area: Value of the Qt.DockWidgetArea enum
        :param dock_widget: QDockWidget object
        """
        self.iface.addTabifiedDockWidget(area, dock_widget, raiseTab=True)

    def open_feature_form(self, layer, feature):
        self.iface.openFeatureForm(layer, feature)

    def flash_features(self, layer, fids, flashes=1, duration=500):
        self.iface.mapCanvas().flashFeatureIds(layer,
                                               fids,
                                               QColor(255, 0, 0, 255),
                                               QColor(255, 0, 0, 0),
                                               flashes=flashes,
                                               duration=duration)
Exemple #27
0
class QgisModelBakerUtils(QObject):
    def __init__(self):
        QObject.__init__(self)
        self.logger = Logger()
        from asistente_ladm_col.config.config_db_supported import ConfigDBsSupported
        self._dbs_supported = ConfigDBsSupported()
        self.translatable_config_strings = TranslatableConfigStrings()

    def get_generator(self, db):
        if 'QgisModelBaker' in qgis.utils.plugins:
            tool = self._dbs_supported.get_db_factory(
                db.engine).get_model_baker_db_ili_mode()

            QgisModelBaker = qgis.utils.plugins["QgisModelBaker"]
            generator = QgisModelBaker.get_generator()(
                tool, db.uri, "smart2", db.schema, pg_estimated_metadata=False)
            return generator
        else:
            self.logger.critical(
                __name__,
                QCoreApplication.translate(
                    "AsistenteLADMCOLPlugin",
                    "The QGIS Model Baker plugin is a prerequisite, install it before using LADM-COL Assistant."
                ))
            return None

    def get_model_baker_db_connection(self, db):
        generator = self.get_generator(db)
        if generator is not None:
            return generator._db_connector

        return None

    def load_layers(self, db, layer_list):
        """
        Load a selected list of layers from qgis model baker.
        This call should configure relations and bag of enums
        between layers being loaded, but not when a layer already
        loaded has a relation or is part of a bag of enum. For
        that case, we use a cached set of relations and bags of
        enums that we get only once per session and configure in
        the Asistente LADM-COL.
        """
        translated_strings = self.translatable_config_strings.get_translatable_config_strings(
        )

        if 'QgisModelBaker' in qgis.utils.plugins:
            QgisModelBaker = qgis.utils.plugins["QgisModelBaker"]

            tool = self._dbs_supported.get_db_factory(
                db.engine).get_model_baker_db_ili_mode()

            generator = QgisModelBaker.get_generator()(
                tool, db.uri, "smart2", db.schema, pg_estimated_metadata=False)
            layers = generator.layers(layer_list)
            relations, bags_of_enum = generator.relations(layers, layer_list)
            legend = generator.legend(
                layers,
                ignore_node_names=[translated_strings[ERROR_LAYER_GROUP]])
            QgisModelBaker.create_project(layers,
                                          relations,
                                          bags_of_enum,
                                          legend,
                                          auto_transaction=False)
        else:
            self.logger.critical(
                __name__,
                QCoreApplication.translate(
                    "AsistenteLADMCOLPlugin",
                    "The QGIS Model Baker plugin is a prerequisite, install it before using LADM-COL Assistant."
                ))

    def get_required_layers_without_load(self, layer_list, db):
        """
        Gets a list of layers from a list of layer names using QGIS Model Baker.
        Layers are register in QgsProject, but not loaded to the canvas!
        :param layer_list: list of layers names (e.g., ['lc_terreno', 'lc_lindero'])
        :param db: db connection
        :return: list of QgsVectorLayers registered in the project
        """
        layers = list()
        if 'QgisModelBaker' in qgis.utils.plugins:
            QgisModelBaker = qgis.utils.plugins["QgisModelBaker"]

            tool = self._dbs_supported.get_db_factory(
                db.engine).get_model_baker_db_ili_mode()
            generator = QgisModelBaker.get_generator()(
                tool, db.uri, "smart2", db.schema, pg_estimated_metadata=False)
            model_baker_layers = generator.layers(layer_list)

            for model_baker_layer in model_baker_layers:
                layer = model_baker_layer.create(
                )  # Convert Model Baker layer to QGIS layer
                QgsProject.instance().addMapLayer(
                    layer, False)  # Do not load it to canvas
                layers.append(layer)
        else:
            self.logger.critical(
                __name__,
                QCoreApplication.translate(
                    "AsistenteLADMCOLPlugin",
                    "The QGIS Model Baker plugin is a prerequisite, install it before using LADM-COL Assistant."
                ))

        return layers

    def get_layers_and_relations_info(self, db):
        """
        Called once per session, this is used to get information
        of all relations and bags of enums in the DB and cache it
        in the Asistente LADM-COL.
        """
        if 'QgisModelBaker' in qgis.utils.plugins:
            generator = self.get_generator(db)

            layers = generator.get_tables_info_without_ignored_tables()
            relations = [
                relation for relation in generator.get_relations_info()
            ]
            self.logger.debug(
                __name__,
                "Relationships before filter: {}".format(len(relations)))
            self.filter_relations(relations)
            self.logger.debug(
                __name__,
                "Relationships after filter: {}".format(len(relations)))
            return (layers, relations, {})
        else:
            self.logger.critical(
                __name__,
                QCoreApplication.translate(
                    "AsistenteLADMCOLPlugin",
                    "The QGIS Model Baker plugin is a prerequisite, install it before using LADM-COL Assistant."
                ))
            return (list(), list(), dict())

    def filter_relations(self, relations):
        """
        Modifies the input list of relations, removing elements that meet a condition.

        :param relations: List of a dict of relations.
        :return: Nothing, changes the input list of relations.
        """
        to_delete = list()
        for relation in relations:
            if relation[QueryNames.REFERENCING_FIELD].startswith(
                    'uej2_') or relation[
                        QueryNames.REFERENCING_FIELD].startswith('ue_'):
                to_delete.append(relation)

        for idx in to_delete:
            relations.remove(idx)

    def get_tables_info_without_ignored_tables(self, db):
        if 'QgisModelBaker' in qgis.utils.plugins:
            generator = self.get_generator(db)
            return generator.get_tables_info_without_ignored_tables()
        else:
            self.logger.critical(
                __name__,
                QCoreApplication.translate(
                    "AsistenteLADMCOLPlugin",
                    "The QGIS Model Baker plugin is a prerequisite, install it before using LADM-COL Assistant."
                ))

    def get_first_index_for_layer_type(
        self, layer_type, group=QgsProject.instance().layerTreeRoot()):
        if 'QgisModelBaker' in qgis.utils.plugins:
            import QgisModelBaker
            return QgisModelBaker.utils.qgis_utils.get_first_index_for_layer_type(
                layer_type, group)
        return None

    @staticmethod
    def get_suggested_index_for_layer(layer, group):
        if 'QgisModelBaker' in qgis.utils.plugins:
            import QgisModelBaker
            return QgisModelBaker.utils.qgis_utils.get_suggested_index_for_layer(
                layer, group)
        return None
Exemple #28
0
class QualityRuleManager(QObject, metaclass=SingletonQObject):
    def __init__(self):
        self.logger = Logger()
        self.__quality_rules_data = QualityRuleConfig.get_quality_rules_config(
        )
        self.__translated_strings = TranslatableConfigStrings(
        ).get_translatable_config_strings()
        self._quality_rule_groups = dict()
        self.__quality_rules = dict()

        self.role_registry = RoleRegistry()

        self._initialize_quality_rule_manager()

    def _initialize_quality_rule_manager(self):
        for group_k, group_v in self.__quality_rules_data.items():
            self._quality_rule_groups[group_k] = group_v[QUALITY_GROUP_NAME]

            for rule_k, rule_v in group_v[QUALITY_RULES].items():
                self.__quality_rules[rule_k] = QualityRule(rule_v)
        self.logger.info(
            __name__,
            "{} quality rules registered!".format(len(self.__quality_rules)))

    def get_quality_rule(self, rule_key):
        """
        Returns the QualityRule object corresponding to a rule code.

        :param rule_key: rule key
        :return: QualityRule
        """
        return self.__quality_rules.get(rule_key)

    def get_quality_rule_group_name(self, group_key):
        """
        Returns a quality rule group name.

        :param group_key: Group key
        :return: Group name if the group key is found. Otherwise, None.
        """
        return self._quality_rule_groups.get(group_key)

    def get_quality_rules_by_group(self, enum_group=None):
        """
        Returns all rules in a given group. If no enum_group is given,
        it returns the whole set of rules classified by group.

        :param enum_group:  EnumQualityRule.Point|Line|Polygon|Logic
        :return: dict of rules
        """
        quality_rules_group = dict()
        role_key = self.role_registry.get_active_role()
        role_quality_rules = self.role_registry.get_role_quality_rules(
            role_key)

        if enum_group:
            quality_rules_group = {
                k_rule: v_rule
                for k_rule, v_rule in self.__quality_rules.items()
                if k_rule in enum_group and k_rule in role_quality_rules
            }
        else:
            quality_rules_group[EnumQualityRule.Point] = dict()
            quality_rules_group[EnumQualityRule.Line] = dict()
            quality_rules_group[EnumQualityRule.Polygon] = dict()
            quality_rules_group[EnumQualityRule.Logic] = dict()

            for k_quality_rule, v_quality_rule in self.__quality_rules.items():
                if k_quality_rule in role_quality_rules:
                    if k_quality_rule in EnumQualityRule.Point:
                        quality_rules_group[EnumQualityRule.Point][
                            k_quality_rule] = v_quality_rule
                    elif k_quality_rule in EnumQualityRule.Line:
                        quality_rules_group[EnumQualityRule.Line][
                            k_quality_rule] = v_quality_rule
                    elif k_quality_rule in EnumQualityRule.Polygon:
                        quality_rules_group[EnumQualityRule.Polygon][
                            k_quality_rule] = v_quality_rule
                    elif k_quality_rule in EnumQualityRule.Logic:
                        quality_rules_group[EnumQualityRule.Logic][
                            k_quality_rule] = v_quality_rule

            self.logger.debug(
                __name__, "Quality Rules for role '{}': {}".format(
                    role_key,
                    ", ".join([str(rqr.value) for rqr in role_quality_rules])))

        return quality_rules_group

    def get_error_message(self, error_code):
        return self.__translated_strings.get(error_code)
Exemple #29
0
class QualityRuleLayerManager(QObject):
    """
    Responsible for managing all layers during a Quality Rule execution
    session. It goes for LADM-COL layers only once and also manages
    intermediate layers (after snapping).
    """
    def __init__(self, db, rule_keys, tolerance):
        QObject.__init__(self)
        self.logger = Logger()
        self.app = AppInterface()

        self.__db = db
        self.__rule_keys = rule_keys
        self.__tolerance = tolerance

        self.__quality_rule_layers_config = QualityRuleConfig.get_quality_rules_layer_config(
            self.__db.names)

        # {rule_key: {QUALITY_RULE_LAYERS: {layer_name: layer},
        #             QUALITY_RULE_LADM_COL_LAYERS: {layer_name: layer}}
        self.__layers = dict()

        self.__adjusted_layers_cache = dict()

    def initialize(self, rule_keys):
        """
        Objects of this class are reusable calling initialize()
        """
        self.__rule_keys = rule_keys
        self.__layers = dict()

    def __prepare_layers(self):
        """
        Get layers from DB and prepare snapped layers for all rules
        """
        self.logger.info(
            __name__,
            QCoreApplication.translate("QualityRuleLayerManager",
                                       "Preparing layers..."))
        # First go for ladm-col layers
        ladm_layers = dict()
        for rule_key, rule_layers_config in self.__quality_rule_layers_config.items(
        ):
            if rule_key in self.__rule_keys:  # Only get selected rules' layers
                for layer_name in rule_layers_config[
                        QUALITY_RULE_LADM_COL_LAYERS]:
                    ladm_layers[layer_name] = None

        self.logger.debug(
            __name__,
            QCoreApplication.translate("QualityRuleLayerManager",
                                       "Getting {} LADM-COL layers...").format(
                                           len(ladm_layers)))
        self.app.core.get_layers(self.__db, ladm_layers, load=True)
        if ladm_layers is None:  # If there are errors with get_layers, ladm_layers is None
            self.logger.critical(
                __name__,
                QCoreApplication.translate(
                    "QualityRuleLayerManager",
                    "Couldn't finish preparing required layers!"))
            return False

        # If tolerance > 0, prepare adjusted layers
        #   We create an adjusted_layers dict to override ladm_layers per rule.
        #   For that, we need to read the config and, if not yet calculated,
        #   adjust the layers and store them in temporary cache.

        # {rule_key: {layer_name: layer}}, because each rule might need
        # different adjustments for the same layer, compared to other rules
        adjusted_layers = {rule_key: dict() for rule_key in self.__rule_keys}

        if self.__tolerance:
            self.logger.debug(
                __name__,
                QCoreApplication.translate(
                    "QualityRuleLayerManager",
                    "Tolerance > 0, adjusting layers..."))
            self.__adjusted_layers_cache = dict()  # adjusted_layers_key: layer
            count_rules = 0
            total_rules = len([
                rk for rk in self.__rule_keys
                if rk in self.__quality_rule_layers_config
            ])

            with ProcessWithStatus(
                    QCoreApplication.translate(
                        "QualityRuleLayerManager",
                        "Preparing tolerance on layers...")):
                for rule_key, rule_layers_config in self.__quality_rule_layers_config.items(
                ):
                    if rule_key in self.__rule_keys:  # Only get selected rules' layers
                        count_rules += 1
                        self.logger.status(
                            QCoreApplication.translate(
                                "QualityRuleLayerManager",
                                "Preparing tolerance on layers... {}%").format(
                                    int(count_rules / total_rules * 100)))
                        if QUALITY_RULE_ADJUSTED_LAYERS in rule_layers_config:

                            for layer_name, snap_config in rule_layers_config[
                                    QUALITY_RULE_ADJUSTED_LAYERS].items():
                                # Read from config
                                input_name = snap_config[
                                    ADJUSTED_INPUT_LAYER]  # input layer name
                                reference_name = snap_config[
                                    ADJUSTED_REFERENCE_LAYER]  # reference layer name
                                fix = snap_config[
                                    FIX_ADJUSTED_LAYER] if FIX_ADJUSTED_LAYER in snap_config else False

                                # Get input and reference layers (note that they could be adjusted layers)
                                input = self.__adjusted_layers_cache[
                                    input_name] if input_name in self.__adjusted_layers_cache else ladm_layers[
                                        input_name]
                                reference = self.__adjusted_layers_cache[
                                    reference_name] if reference_name in self.__adjusted_layers_cache else ladm_layers[
                                        reference_name]

                                # Try to reuse if already calculated!
                                adjusted_layers_key = get_key_for_quality_rule_adjusted_layer(
                                    input_name, reference_name, fix)
                                if adjusted_layers_key not in self.__adjusted_layers_cache:
                                    self.__adjusted_layers_cache[
                                        adjusted_layers_key] = self.app.core.adjust_layer(
                                            input, reference, self.__tolerance,
                                            fix)

                                adjusted_layers[rule_key][
                                    layer_name] = self.__adjusted_layers_cache[
                                        adjusted_layers_key]

            self.logger.debug(
                __name__,
                QCoreApplication.translate("QualityRuleLayerManager",
                                           "Layers adjusted..."))

        # Now that we have both ladm_layers and adjusted_layers, use them
        # in a single member dict of layers per rule (preserving original LADM-COL layers)
        self.__layers = {
            rule_key: {
                QUALITY_RULE_LAYERS: dict(),
                QUALITY_RULE_LADM_COL_LAYERS: dict()
            }
            for rule_key in self.__rule_keys
        }
        for rule_key, rule_layers_config in self.__quality_rule_layers_config.items(
        ):
            if rule_key in self.__rule_keys:  # Only get selected rules' layers
                for layer_name in rule_layers_config[
                        QUALITY_RULE_LADM_COL_LAYERS]:
                    # Fill both subdicts
                    # In LADM-COL layers we send all original layers
                    self.__layers[rule_key][QUALITY_RULE_LADM_COL_LAYERS][
                        layer_name] = ladm_layers[
                            layer_name] if layer_name in ladm_layers else None

                    # In QR_Layers we store the best layer we have available (preferring adjusted over ladm-col)
                    if layer_name in adjusted_layers[rule_key]:
                        self.__layers[rule_key][QUALITY_RULE_LAYERS][
                            layer_name] = adjusted_layers[rule_key][layer_name]
                    elif layer_name in ladm_layers:
                        self.__layers[rule_key][QUALITY_RULE_LAYERS][
                            layer_name] = ladm_layers[layer_name]

                # Let QRs know if they should switch between dicts looking for original geometries
                self.__layers[rule_key][HAS_ADJUSTED_LAYERS] = bool(
                    self.__tolerance)

        # Register adjusted layers so that Processing can properly find them
        if self.__adjusted_layers_cache:
            load_to_registry = [
                layer for key, layer in self.__adjusted_layers_cache.items()
                if layer is not None
            ]
            self.logger.debug(
                __name__,
                "{} adjusted layers loaded to QGIS registry...".format(
                    len(load_to_registry)))
            QgsProject.instance().addMapLayers(load_to_registry, False)

        return True

    def get_layer(self, layer_name, rule_key):
        return self.get_layers([layer_name], rule_key)

    def get_layers(self, rule_key):
        """
        Gets the layers a quality rule requires to run. This is based on the quality rule layer config.

        :param rule_key: Key of the quality rule.
        :return: Dict of layers for the given rule_key. This dict has both a 'layers' dict which has the best available
                 layer (which means, if an adjusted layer is required, it will be preferred, and if no adjusted layer is
                 required, just pass the LADM-COL layer) and a 'ladm-col' dict with the original LADM-COL layers,
                 because the quality rule might need to refer to the original object (or geometry) to build its result.
        """
        # Make sure we only call Prepare layers once for each call to run quality validations.
        if not self.__layers:
            if not self.__prepare_layers():
                return None

        return self.__layers[rule_key]

    def clean_temporary_layers(self):
        # Removes adjusted layers from registry
        unload_from_registry = [
            layer.id() for key, layer in self.__adjusted_layers_cache.items()
            if layer is not None
        ]
        self.logger.debug(
            __name__,
            "{} adjusted layers removed from QGIS registry...".format(
                len(unload_from_registry)))
        QgsProject.instance().removeMapLayers(unload_from_registry)
Exemple #30
0
class LADMColModelRegistry(metaclass=Singleton):
    """
    Registry of supported models.
    The model in the registry is updated each time the current role changes.
    """
    def __init__(self):
        self.logger = Logger()
        self.__models = dict()

    def register_model(self, model):
        if not isinstance(model, LADMColModel) or model.id() in self.__models:
            return False

        self.__models[model.id()] = model
        return True

    def supported_models(self):
        return [
            model for model in self.__models.values() if model.is_supported()
        ]

    def hidden_models(self):
        return [
            model.full_name() for model in self.__models.values()
            if model.hidden()
        ]

    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_ids(self):
        return list(self.__models.keys())

    def refresh_models_for_role(self):
        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.
        # It the user does not have such config, we grab it from MODEL_CONFIG.
        ili2db_params = role_models[
            ROLE_MODEL_ILI2DB_PARAMETERS] if ROLE_MODEL_ILI2DB_PARAMETERS in role_models else dict(
            )

        for model_key, model in self.__models.items():
            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])

            if model_key in ili2db_params and ili2db_params[model_key]:
                model_ili2db_params = ili2db_params[model_key]
            else:
                model_ili2db_params = self.__get_model_iili2db_params_from_config(
                    model_key)
            if model_ili2db_params:
                self.logger.debug(
                    __name__,
                    "Model ili2db params are: {}".format(model_ili2db_params))
            model.set_ili2db_params(model_ili2db_params)

        self.logger.debug(
            __name__, "Supported models for role '{}': {}".format(
                role_key, role_models[ROLE_SUPPORTED_MODELS]))

    @staticmethod
    def __get_model_iili2db_params_from_config(model_key):
        model_config = MODEL_CONFIG[model_key]
        return model_config[
            MODEL_ILI2DB_PARAMETERS] if MODEL_ILI2DB_PARAMETERS in model_config else dict(
            )