コード例 #1
0
class QualityRuleEngine(QObject):
    """
    Engine that executes Quality Rules
    """
    def __init__(self, db, rules):
        QObject.__init__(self)
        self.logger = Logger()
        self.app = AppInterface()

        self.__tolerance = self.app.settings.tolerance
        self.__layer_manager = QualityRuleLayerManager(db, rules.keys(), self.__tolerance)
        self.__quality_rules = QualityRules()
        self.quality_rule_logger = QualityRuleLogger(self.__tolerance)

        self.__db = db
        self.__rules = rules
        self.__result_layers = list()

    def initialize(self, db, rules):
        """
        Objects of this class are reusable calling initialize()
        """
        self.__result_layers = list()
        self.__db = db
        self.__rules = rules
        self.__tolerance = self.app.settings.tolerance
        self.__layer_manager.initialize(rules.keys(), self.__tolerance)
        self.quality_rule_logger.initialize(self.__tolerance)

    def validate_quality_rules(self):
        if self.__rules:
            self.quality_rule_logger.set_count_topology_rules(len(self.__rules))
            self.logger.info(__name__,
                             QCoreApplication.translate("QualityRuleEngine",
                                "Validating {} quality rules (tolerance: {}).").format(len(self.__rules), self.__tolerance))

            for rule_key, rule_name in self.__rules.items():
                layers = self.__layer_manager.get_layers(rule_key)
                if layers:
                    self.__validate_quality_rule(rule_key, layers, rule_name=rule_name)
                else:
                    self.logger.warning(__name__, QCoreApplication.translate("QualityRuleEngine",
                            "Couldn't execute '{}' quality rule! Required layers are not available. Skipping...").format(rule_name))

            self.quality_rule_logger.generate_log_button()
            self.__layer_manager.clean_temporary_layers()
        else:
            self.logger.warning(__name__, QCoreApplication.translate("QualityRuleEngine", "No rules to validate!"))

    @_log_quality_rule_validations
    def __validate_quality_rule(self, rule_key, layers, rule_name):
        """
        Intermediate function to log quality rule execution.

        :param rule_key: rule key
        :param rule_name: Rule name (needed for the logging decorator)
        :return: tuple (msg, level), where level indicates whether the rule was successful,
                 couldn't be validated (warning), or was not successful (critical)
        """
        return self.__quality_rules.validate_quality_rule(self.__db, rule_key, layers)
コード例 #2
0
class LoginSTDialog(QDialog, DIALOG_UI):
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        self.session = STSession()

        self.logger = Logger()
        self.help_strings = HelpStrings()

        #self.txt_help_page.setHtml(self.help_strings.DLG_WELCOME_SCREEN)
        #self.txt_help_page.anchorClicked.connect(self.save_template)

        self.buttonBox.accepted.disconnect()
        self.buttonBox.accepted.connect(self.login)
        self.buttonBox.helpRequested.connect(self.show_help)

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

    def login(self):
        if not self.txt_login_user.text().strip(
        ) or not self.txt_login_password.text().strip():
            msg = QCoreApplication.translate(
                "LoginSTDialog", "First enter user and password data.")
            self.show_message(msg, Qgis.Warning)
            return

        msg = self.logger.status(
            QCoreApplication.translate("LoginSTDialog",
                                       "Connecting to login service..."))
        with ProcessWithStatus(msg):
            res, msg = self.session.login(self.txt_login_user.text(),
                                          self.txt_login_password.text())

        if res:
            self.logger.info(__name__, msg, LogHandlerEnum.MESSAGE_BAR, 15)
            self.close()
        else:
            self.show_message(msg, Qgis.Warning, 0)

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

    def show_help(self):
        self.qgis_utils.show_help("import_from_excel")
コード例 #3
0
class LoginSTDialog(QDialog, DIALOG_UI):
    active_role_changed = pyqtSignal()

    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        self.session = STSession()

        self.logger = Logger()
        self.help_strings = HelpStrings()

        self.should_emit_role_changed = False

        self.buttonBox.accepted.disconnect()
        self.buttonBox.accepted.connect(self.login)
        self.buttonBox.helpRequested.connect(self.show_help)

        self.txt_login_user.setFocus()

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

    def login(self):
        if not self.txt_login_user.text().strip(
        ) or not self.txt_login_password.text().strip():
            msg = QCoreApplication.translate(
                "LoginSTDialog", "First enter user and password data.")
            self.show_message(msg, Qgis.Warning)
            return

        msg = self.logger.status(
            QCoreApplication.translate("LoginSTDialog",
                                       "Connecting to login service..."))
        with ProcessWithStatus(msg):
            res, msg, change_role = self.session.login(
                self.txt_login_user.text(), self.txt_login_password.text())

        if res:
            self.should_emit_role_changed = change_role
            self.logger.info(__name__, msg, EnumLogHandler.MESSAGE_BAR, 15)
            self.close()
        else:
            self.show_message(msg, Qgis.Warning, 0)

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

    def show_help(self):
        show_plugin_help('transitional_system_login')

    def reject(self):
        if self.should_emit_role_changed:
            self.logger.info(__name__, "Emit active_role_changed.")
            self.active_role_changed.emit()

        self.logger.info(__name__, "Dialog closed.")
        self.done(QDialog.Accepted)  # Any code, we don't use it anyways
コード例 #4
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, ''
コード例 #5
0
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)
コード例 #6
0
class Ili2DB(QObject):
    """
    Execute ili2db operations via Model Baker API
    """
    stdout = pyqtSignal(str)
    stderr = pyqtSignal(str)
    process_started = pyqtSignal(str)
    process_finished = pyqtSignal(int, int)

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

        self.logger = Logger()

        self._java_path = ''
        self._java_dependency = JavaDependency()

        self.dbs_supported = ConfigDBsSupported()

        self._base_configuration = None
        self._ilicache = None
        self._log = ''

    def get_full_java_exe_path(self):
        if not self._java_path:
            self._java_path = JavaDependency.get_full_java_exe_path()

        return self._java_path

    def configure_java(self):
        if not self._java_dependency.set_java_home():
            message_java = QCoreApplication.translate(
                "Ili2DB",
                """Configuring Java {}...""").format(JAVA_REQUIRED_VERSION)
            self.logger.status(message_java)
            self._java_dependency.get_java_on_demand(asynchronous=False)

        res = True
        msg = 'Success!'

        java_path = self.get_full_java_exe_path()
        if not java_path:
            res = False
            msg = QCoreApplication.translate(
                "Ili2DB",
                "Java {} could not be confiured for you. You can configure the JAVA_HOME environment variable manually, restart QGIS and try again."
            ).format(JAVA_REQUIRED_VERSION)

        return res, msg

    def _get_base_configuration(self):
        """
        :return: BaseConfiguration object. If it's already configured, it returns the existing object, so that it can
                 be shared among chained operations (e.g., export DB1-->schema import DB2-->import DB2).
        """
        if not self._base_configuration:
            self._base_configuration = BaseConfiguration()
            self._ilicache = IliCache(self._base_configuration)

            self._base_configuration.java_path = self.get_full_java_exe_path(
            )  # It is well configured at this point!

            # Check custom model directories
            if QSettings().value(
                    'Asistente-LADM-COL/models/custom_model_directories_is_checked',
                    DEFAULT_USE_CUSTOM_MODELS,
                    type=bool):
                custom_model_directories = QSettings().value(
                    'Asistente-LADM-COL/models/custom_models',
                    DEFAULT_MODELS_DIR)
                if not custom_model_directories:
                    self._base_configuration.custom_model_directories_enabled = False
                else:
                    self._base_configuration.custom_model_directories = custom_model_directories
                    self._base_configuration.custom_model_directories_enabled = True

            # Debug mode
            self._base_configuration.debugging_enabled = \
                QSettings().value('Asistente-LADM-COL/models/debug', DEFAULT_ILI2DB_DEBUG_MODE, type=bool)

            self._base_configuration.logfile_path = QSettings().value(
                'Asistente-LADM-COL/models/log_file_path', '')

            self._ilicache.refresh(
            )  # Always call it after setting custom_model_directories

        return self._base_configuration

    def _get_ili_models(self, db):
        ili_models = list()
        model_names = db.get_models()
        if model_names:
            for model in LADMColModelRegistry().supported_models():
                if not model.hidden() and model.full_name() in model_names:
                    ili_models.append(model.full_name())

        return ili_models

    def get_import_schema_configuration(self,
                                        db,
                                        ili_models=list(),
                                        create_basket_col=False):
        db_factory = self.dbs_supported.get_db_factory(db.engine)

        configuration = SchemaImportConfiguration()
        db_factory.set_ili2db_configuration_params(db.dict_conn_params,
                                                   configuration)
        configuration.inheritance = ILI2DBNames.DEFAULT_INHERITANCE
        configuration.create_basket_col = create_basket_col
        configuration.create_import_tid = ILI2DBNames.CREATE_IMPORT_TID
        configuration.stroke_arcs = ILI2DBNames.STROKE_ARCS
        configuration.tomlfile = TOML_FILE_DIR

        configuration.base_configuration = self._get_base_configuration()
        configuration.ilimodels = ';'.join(ili_models)

        if db.engine == 'gpkg':
            # EPSG:9377 support for 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
        elif db.engine == 'pg':
            configuration.srs_auth = 'EPSG'
            configuration.srs_code = 9377
            configuration.pre_script = CTM12_PG_SCRIPT_PATH

        return configuration

    def get_import_data_configuration(self,
                                      db,
                                      xtf_path,
                                      dataset='',
                                      baskets=list(),
                                      disable_validation=False):
        db_factory = self.dbs_supported.get_db_factory(db.engine)
        configuration = ImportDataConfiguration()
        db_factory.set_ili2db_configuration_params(db.dict_conn_params,
                                                   configuration)
        configuration.with_importtid = True
        configuration.xtffile = xtf_path
        configuration.disable_validation = disable_validation
        configuration.dataset = dataset
        configuration.baskets = baskets  # list with basket UUIDs

        configuration.base_configuration = self._get_base_configuration()
        ili_models = self._get_ili_models(db)
        if ili_models:
            configuration.ilimodels = ';'.join(ili_models)

        return configuration

    def get_export_configuration(self,
                                 db,
                                 xtf_path,
                                 dataset='',
                                 baskets=list(),
                                 disable_validation=False):
        db_factory = self.dbs_supported.get_db_factory(db.engine)
        configuration = ExportConfiguration()
        db_factory.set_ili2db_configuration_params(db.dict_conn_params,
                                                   configuration)
        configuration.with_exporttid = True
        configuration.xtffile = xtf_path
        configuration.disable_validation = disable_validation
        configuration.dataset = dataset
        configuration.baskets = baskets  # List with basket UUIDs

        configuration.base_configuration = self._get_base_configuration()
        ili_models = self._get_ili_models(db)
        if ili_models:
            configuration.ilimodels = ';'.join(ili_models)

        return configuration

    def get_update_configuration(self, db_factory, db, xtf_path, dataset_name):
        configuration = UpdateDataConfiguration()
        db_factory.set_ili2db_configuration_params(db.dict_conn_params,
                                                   configuration)

        configuration.base_configuration = self._get_base_configuration()
        ili_models = self._get_ili_models(db)
        if ili_models:
            configuration.ilimodels = ';'.join(ili_models)

        configuration.dataset = dataset_name
        configuration.with_importbid = True
        configuration.with_importtid = True
        configuration.xtffile = xtf_path

        return configuration

    def get_validate_configuration(self, db_factory, db, model_names,
                                   xtflog_path, configfile_path):
        configuration = ValidateDataConfiguration()
        db_factory.set_ili2db_configuration_params(db.dict_conn_params,
                                                   configuration)

        configuration.base_configuration = self._get_base_configuration()
        # Since BaseConfiguration can be shared, avoid a --trace in --validate operation (not supported by ili2db)
        configuration.base_configuration.debugging_enabled = False

        if model_names:
            configuration.ilimodels = ';'.join(model_names)
        if xtflog_path:
            configuration.xtflogfile = xtflog_path
        if configfile_path:
            configuration.configfile = configfile_path

        return configuration

    @with_override_cursor
    def import_schema(self, db, configuration: SchemaImportConfiguration):
        # Check prerequisite
        if not self.get_full_java_exe_path():
            res_java, msg_java = self.configure_java()
            if not res_java:
                return res_java, msg_java

        # Configure command parameters
        db_factory = self.dbs_supported.get_db_factory(db.engine)

        # Configure run
        importer = iliimporter.Importer()
        importer.tool = db_factory.get_model_baker_db_ili_mode()
        importer.configuration = configuration

        self._connect_ili_executable_signals(importer)

        # Run!
        res = True
        msg = QCoreApplication.translate("Ili2DB",
                                         "Schema import ran successfully!")
        self._log = ''
        self.logger.status(
            QCoreApplication.translate(
                "Ili2Db", "Creating LADM-COL structure into {}...").format(
                    db.engine.upper()))
        try:
            if importer.run() != iliimporter.Importer.SUCCESS:
                msg = QCoreApplication.translate(
                    "Ili2DB",
                    "An error occurred when importing a schema into a DB (check the QGIS log panel)."
                )
                res = False
                QgsMessageLog.logMessage(
                    self._log,
                    QCoreApplication.translate("Ili2DB", "DB Schema Import"),
                    Qgis.Critical)

        except JavaNotFoundError:
            msg = QCoreApplication.translate(
                "Ili2DB",
                "Java {} could not be found. You can configure the JAVA_HOME environment variable manually, restart QGIS and try again."
            ).format(JAVA_REQUIRED_VERSION)
            res = False

        self._disconnect_ili_executable_signals(importer)
        self.logger.clear_status()
        return res, msg

    @with_override_cursor
    def import_data(self, db, configuration: ImportDataConfiguration):
        # Check prerequisite
        if not self.get_full_java_exe_path():
            res_java, msg_java = self.configure_java()
            if not res_java:
                return res_java, msg_java

        # Configure command parameters
        db_factory = self.dbs_supported.get_db_factory(db.engine)

        # Configure run
        importer = iliimporter.Importer(dataImport=True)
        importer.tool = db_factory.get_model_baker_db_ili_mode()
        importer.configuration = configuration

        self._connect_ili_executable_signals(importer)

        # Run!
        res = True
        msg = QCoreApplication.translate(
            "Ili2DB",
            "XTF '{}' imported successfully!").format(configuration.xtffile)
        self._log = ''
        self.logger.status(
            QCoreApplication.translate("Ili2Db",
                                       "Importing XTF into {}...").format(
                                           db.engine.upper()))
        try:
            if importer.run() != iliimporter.Importer.SUCCESS:
                msg = QCoreApplication.translate(
                    "Ili2DB",
                    "An error occurred when importing from XTF (check the QGIS log panel)."
                )
                res = False
                QgsMessageLog.logMessage(
                    self._log,
                    QCoreApplication.translate("Ili2DB", "Import from XTF"),
                    Qgis.Critical)

        except JavaNotFoundError:
            msg = QCoreApplication.translate(
                "Ili2DB",
                "Java {} could not be found. You can configure the JAVA_HOME environment variable manually, restart QGIS and try again."
            ).format(JAVA_REQUIRED_VERSION)
            res = False

        self._disconnect_ili_executable_signals(importer)
        self.logger.clear_status()
        return res, msg

    @with_override_cursor
    def export(self, db, configuration: ExportConfiguration):
        # Check prerequisite
        if not self.get_full_java_exe_path():
            res_java, msg_java = self.configure_java()
            if not res_java:
                return res_java, msg_java

        # Configure command parameters
        db_factory = self.dbs_supported.get_db_factory(db.engine)

        # Configure run
        exporter = iliexporter.Exporter()
        exporter.tool = db_factory.get_model_baker_db_ili_mode()
        exporter.configuration = configuration

        self._connect_ili_executable_signals(exporter)

        # Run!
        res = True
        msg = QCoreApplication.translate(
            "Ili2DB",
            "XTF '{}' exported successfully!").format(configuration.xtffile)
        self._log = ''
        self.logger.status(
            QCoreApplication.translate("Ili2Db",
                                       "Exporting from {} to XTF...").format(
                                           db.engine.upper()))
        try:
            if exporter.run() != iliexporter.Exporter.SUCCESS:
                msg = QCoreApplication.translate(
                    "Ili2DB",
                    "An error occurred when exporting data to XTF (check the QGIS log panel)."
                )
                res = False
                QgsMessageLog.logMessage(
                    self._log,
                    QCoreApplication.translate("Ili2DB", "Export to XTF"),
                    Qgis.Critical)
            else:
                self.logger.info(__name__, msg)

        except JavaNotFoundError:
            msg = QCoreApplication.translate(
                "Ili2DB",
                "Java {} could not be found. You can configure the JAVA_HOME environment variable manually, restart QGIS and try again."
            ).format(JAVA_REQUIRED_VERSION)
            res = False

        self._disconnect_ili_executable_signals(exporter)
        self.logger.clear_status()
        return res, msg

    @with_override_cursor
    def update(self, db, xtf_path, dataset_name):
        # Check prerequisite
        if not self.get_full_java_exe_path():
            res_java, msg_java = self.configure_java()
            if not res_java:
                return res_java, msg_java

        # Configure command parameters
        db_factory = self.dbs_supported.get_db_factory(db.engine)
        configuration = self.get_update_configuration(db_factory, db, xtf_path,
                                                      dataset_name)

        # Configure run
        updater = iliupdater.Updater()
        updater.tool = db_factory.get_model_baker_db_ili_mode()
        updater.configuration = configuration

        self._connect_ili_executable_signals(updater)

        # Run!
        res = True
        msg = QCoreApplication.translate(
            "Ili2DB",
            "DB updated successfully from XTF file '{}'!").format(xtf_path)
        self._log = ''
        self.logger.status(
            QCoreApplication.translate(
                "Ili2Db", "Updating {} DB from XTF '{}'...").format(
                    db.engine.upper(), xtf_path))
        try:
            if updater.run() != iliupdater.Updater.SUCCESS:
                msg = QCoreApplication.translate(
                    "Ili2DB",
                    "An error occurred when updating the DB from an XTF (check the QGIS log panel)."
                )
                res = False
                QgsMessageLog.logMessage(
                    self._log,
                    QCoreApplication.translate("Ili2DB", "Update DB from XTF"),
                    Qgis.Critical)
            else:
                self.logger.info(__name__, msg)

        except JavaNotFoundError:
            msg = QCoreApplication.translate(
                "Ili2DB",
                "Java {} could not be found. You can configure the JAVA_HOME environment variable manually, restart QGIS and try again."
            ).format(JAVA_REQUIRED_VERSION)
            res = False

        self._disconnect_ili_executable_signals(updater)
        self.logger.clear_status()
        return res, msg

    @with_override_cursor
    def validate(self,
                 db,
                 model_names=list(),
                 xtflog_path='',
                 configfile_path=''):
        # Check prerequisite
        if not self.get_full_java_exe_path():
            res_java, msg_java = self.configure_java()
            if not res_java:
                return res_java, msg_java

        # Configure command parameters
        db_factory = self.dbs_supported.get_db_factory(db.engine)
        configuration = self.get_validate_configuration(
            db_factory, db, model_names, xtflog_path, configfile_path)

        # Configure run
        validator = ili2dbvalidator.Ili2DBValidator()
        validator.tool = db_factory.get_model_baker_db_ili_mode()
        validator.process_started.connect(self.on_process_started)
        validator.stderr.connect(self.on_stderr)
        validator.configuration = configuration

        # Run!
        res = True
        msg = QCoreApplication.translate(
            "Ili2DB", "Data successfully validated from DB '{}'!").format(
                db.get_description_conn_string())
        self._log = ''
        self.logger.status(
            QCoreApplication.translate(
                "Ili2Db", "Validating data from '{}' DB...").format(
                    db.get_description_conn_string()))
        try:
            res_validation = validator.run()
            if (res_validation != ili2dbvalidator.Ili2DBValidator.SUCCESS
                    and res_validation != ili2dbvalidator.Ili2DBValidator.
                    SUCCESS_WITH_VALIDATION_ERRORS):
                msg = QCoreApplication.translate(
                    "Ili2DB",
                    "An error occurred when validating data from a DB (check the QGIS log panel)."
                )
                res = False
                QgsMessageLog.logMessage(
                    self._log,
                    QCoreApplication.translate("Ili2DB",
                                               "Validate data from DB"),
                    Qgis.Critical)
            else:
                self.logger.info(__name__, msg)
        except JavaNotFoundError:
            msg = QCoreApplication.translate(
                "Ili2DB",
                "Java {} could not be found. You can configure the JAVA_HOME environment variable manually, restart QGIS and try again."
            ).format(JAVA_REQUIRED_VERSION)
            res = False

        self.logger.clear_status()
        return res, msg

    def on_process_started(self, command):
        self._log += command + '\n'

    def on_stderr(self, text):
        self._log += text

    def _connect_ili_executable_signals(self, ili_executable: IliExecutable):
        ili_executable.process_started.connect(self.process_started)
        ili_executable.stderr.connect(self.stderr)
        ili_executable.stdout.connect(self.stdout)
        ili_executable.process_finished.connect(self.process_finished)

        ili_executable.process_started.connect(self.on_process_started)
        ili_executable.stderr.connect(self.on_stderr)

    def _disconnect_ili_executable_signals(self,
                                           ili_executable: IliExecutable):
        ili_executable.process_started.disconnect(self.process_started)
        ili_executable.stderr.disconnect(self.stderr)
        ili_executable.stdout.disconnect(self.stdout)
        ili_executable.process_finished.disconnect(self.process_finished)

        ili_executable.process_started.disconnect(self.on_process_started)
        ili_executable.stderr.disconnect(self.on_stderr)
コード例 #7
0
class ChangeDetectionSettingsDialog(QDialog, DIALOG_UI):
    CHANGE_DETECTIONS_MODE_SUPPLIES_MODEL = "CHANGE_DETECTIONS_MODE_SUPPLIES_MODEL"
    CHANGE_DETECTIONS_MODES = {CHANGE_DETECTIONS_MODE_SUPPLIES_MODEL: QCoreApplication.translate("ChangeDetectionSettingsDialog", "Change detection supplies model")}

    def __init__(self, parent=None, qgis_utils=None, conn_manager=None):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        self.logger = Logger()
        self.help_strings = HelpStrings()
        self.txt_help_page.setHtml(self.help_strings.CHANGE_DETECTION_SETTING_DIALOG_HELP)

        self.conn_manager = conn_manager
        self.qgis_utils = qgis_utils

        # we will use a unique instance of setting dialog
        self.settings_dialog = SettingsDialog(qgis_utils=self.qgis_utils, conn_manager=self.conn_manager)
        # The database configuration is saved if it becomes necessary
        # to restore the configuration when the user rejects the dialog
        self.init_db_collected = None
        self.init_db_supplies = None
        self.set_init_db_config()  # Always call after the settings_dialog variable is set

        self._db_collected = self.conn_manager.get_db_connector_from_source()
        self._db_supplies = self.conn_manager.get_db_connector_from_source(SUPPLIES_DB_SOURCE)

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

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

        for mode, label_mode in self.CHANGE_DETECTIONS_MODES.items():
            self.cbo_change_detection_modes.addItem(label_mode, mode)

        self.radio_button_other_db.setChecked(True)  # Default option
        self.radio_button_same_db.setEnabled(True)
        if not self._db_collected.supplies_model_exists():
            self.radio_button_same_db.setEnabled(False)

        self.radio_button_same_db.toggled.connect(self.update_supplies_db_options)
        self.update_supplies_db_options()

        self.btn_collected_db.clicked.connect(self.show_settings_collected_db)
        self.btn_supplies_db.clicked.connect(self.show_settings_supplies_db)

        # Default color error labels
        self.lbl_msg_collected.setStyleSheet('color: orange')
        self.lbl_msg_supplies.setStyleSheet('color: orange')

        # Set connections
        self.buttonBox.accepted.disconnect()
        self.buttonBox.accepted.connect(self.accepted)
        self.buttonBox.helpRequested.connect(self.show_help)

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

        self.update_connection_info()

        self.logger.clear_message_bar()  # Close any existing message in QGIS message bar

    def set_init_db_config(self):
        """
         A copy of the initial connections to the database is made,
         User can change the initial connections and then cancel the changes.
         Initial connections need to be re-established
        """
        self.init_db_collected = self.conn_manager.get_db_connector_from_source(COLLECTED_DB_SOURCE)
        self.init_db_supplies = self.conn_manager.get_db_connector_from_source(SUPPLIES_DB_SOURCE)

    def update_supplies_db_options(self):
        if self.radio_button_same_db.isChecked():
            self.btn_supplies_db.setEnabled(False)
        else:
            self.btn_supplies_db.setEnabled(True)
        self.update_connection_info()

    def show_settings_collected_db(self):
        self.settings_dialog.set_db_source(COLLECTED_DB_SOURCE)
        self.settings_dialog.set_tab_pages_list([SETTINGS_CONNECTION_TAB_INDEX])
        self.settings_dialog.set_required_models([LADMNames.OPERATION_MODEL_PREFIX])
        self.settings_dialog.db_connection_changed.connect(self.db_connection_changed)

        if self.settings_dialog.exec_():
            self._db_collected = self.settings_dialog.get_db_connection()
            self.update_connection_info()
        self.settings_dialog.db_connection_changed.disconnect(self.db_connection_changed)

    def show_settings_supplies_db(self):
        self.settings_dialog.set_db_source(SUPPLIES_DB_SOURCE)
        self.settings_dialog.set_tab_pages_list([SETTINGS_CONNECTION_TAB_INDEX])
        self.settings_dialog.set_required_models([LADMNames.SUPPLIES_MODEL_PREFIX])
        self.settings_dialog.db_connection_changed.connect(self.db_connection_changed)

        if self.settings_dialog.exec_():
            self._db_supplies = self.settings_dialog.get_db_connection()
            self.update_connection_info()
        self.settings_dialog.db_connection_changed.disconnect(self.db_connection_changed)

    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
        if db_source == COLLECTED_DB_SOURCE:
            self._db_collected_was_changed = True
            self._schedule_layers_and_relations_refresh = True
        else:
            self._db_supplies_was_changed = True

    def update_connection_info(self):
        # Validate db connections
        self.lbl_msg_collected.setText("")
        self.lbl_msg_supplies.setText("")
        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)

        # First, update status of same_db button according to collected db connection
        res_collected, code_collected, msg_collected = self._db_collected.test_connection(required_models=[LADMNames.OPERATION_MODEL_PREFIX])
        res_supplies, code_supplies, msg_supplies = self._db_collected.test_connection(required_models=[LADMNames.SUPPLIES_MODEL_PREFIX])

        if res_supplies:
            self.radio_button_same_db.setEnabled(True)
        else:
            self.radio_button_same_db.setChecked(False)  # signal update the label

        if not self.radio_button_same_db.isChecked():
            res_supplies, code_supplies, msg_supplies = self._db_supplies.test_connection(required_models=[LADMNames.SUPPLIES_MODEL_PREFIX])

        # Update collected db connection label
        db_description = self._db_collected.get_description_conn_string()
        if db_description:
            self.db_collected_connect_label.setText(db_description)
            self.db_collected_connect_label.setToolTip(self._db_collected.get_display_conn_string())
        else:
            self.db_collected_connect_label.setText(QCoreApplication.translate("ChangeDetectionSettingsDialog", "The database is not defined!"))
            self.db_collected_connect_label.setToolTip('')

        # Update supplies db connection label
        if self.radio_button_same_db.isChecked():
            self.db_supplies_connect_label.setText(self.db_collected_connect_label.text())
            self.db_supplies_connect_label.setToolTip(self.db_collected_connect_label.toolTip())
        else:
            db_description = self._db_supplies.get_description_conn_string()
            if db_description:
                self.db_supplies_connect_label.setText(db_description)
                self.db_supplies_connect_label.setToolTip(self._db_supplies.get_display_conn_string())
            else:
                self.db_supplies_connect_label.setText(QCoreApplication.translate("ChangeDetectionSettingsDialog", "The database is not defined!"))
                self.db_supplies_connect_label.setToolTip('')

        # Update error message labels
        if not res_collected:
            self.lbl_msg_collected.setText(QCoreApplication.translate("ChangeDetectionSettingsDialog", "Warning: DB connection is not valid"))
            self.lbl_msg_collected.setToolTip(msg_collected)

        if not res_supplies:
            self.lbl_msg_supplies.setText(QCoreApplication.translate("ChangeDetectionSettingsDialog", "Warning: DB connection is not valid"))
            self.lbl_msg_supplies.setToolTip(msg_supplies)

        if res_collected and res_supplies:
            self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True)

    def accepted(self):
        """
        Confirm changes in db connections.
        If user select collected db as supplies db we update supplies db connection with collected db connection.

        If there are layers loaded in canvas from a previous connection that changed, we ask users
        if they want to clean the canvas or preserve the layers.

        If none of the connections changed, the dialog is closed without asking anything to users, and an info message
        is displayed.
        """
        if self.radio_button_same_db.isChecked():
            # Set supplies db connector from collected db connector
            self.conn_manager.save_parameters_conn(self._db_collected, SUPPLIES_DB_SOURCE)
            self._db_supplies = self._db_collected
            self.conn_manager.db_connection_changed.emit(self._db_supplies, self._db_supplies.test_connection()[0], SUPPLIES_DB_SOURCE)

        # Show messages when closing dialog
        if self._db_collected_was_changed or self._db_supplies_was_changed:
            if list(QgsProject.instance().mapLayers().values()):
                message = ""
                if self._db_collected_was_changed and self._db_supplies_was_changed:
                    message = "The connection of the collected and supplies databases has changed,"
                elif self._db_collected_was_changed:
                    message = "The collected database connection has changed,"
                elif self._db_supplies_was_changed:
                    message = "The supplies database connection has changed,"

                message += " do you want to remove the layers that are currently loaded in QGIS?"
                self.show_message_clean_layers_panel(message)
                self.show_message_change_detection_settings_status()  # Show information message indicating if setting is OK
            else:
                self.close_dialog(QDialog.Accepted)
        else:
            # Connections have not changed
            self.close_dialog(QDialog.Accepted)

    def show_message_change_detection_settings_status(self):
        if not self.collected_db_is_valid() and not self.supplies_db_is_valid():
            message = QCoreApplication.translate("ChangeDetectionSettingsDialog", "Neither Collected nor Supplies database connections is valid, you should first configure them before proceeding to detect changes.")
            self.logger.warning_msg(__name__, message, 5)
        elif not self.collected_db_is_valid() or not self.supplies_db_is_valid():

            if not self.collected_db_is_valid():
                message = QCoreApplication.translate("ChangeDetectionSettingsDialog", "Collected database connection is not valid, you should first configure it before proceeding to detect changes.")
                self.logger.warning_msg(__name__, message, 5)

            if not self.supplies_db_is_valid():
                message = QCoreApplication.translate("ChangeDetectionSettingsDialog", "Supplies database connection is not valid, you should first configure it before proceeding to detect changes.")
                self.logger.warning_msg(__name__, message, 5)
        else:
            message = QCoreApplication.translate("ChangeDetectionSettingsDialog", "Both Collected and Supplies database connections are valid, now you can proceed to detect changes.")
            self.logger.message_with_buttons_change_detection_all_and_per_parcel_emitted.emit(message)

    def show_message_clean_layers_panel(self, message):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Question)
        msg.setText(message)
        msg.setWindowTitle(QCoreApplication.translate("ChangeDetectionSettingsDialog", "Remove layers?"))
        msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        msg.button(QMessageBox.Yes).setText(QCoreApplication.translate("ChangeDetectionSettingsDialog", "Yes, remove layers"))
        msg.button(QMessageBox.No).setText(QCoreApplication.translate("ChangeDetectionSettingsDialog", "No, don't remove"))
        reply = msg.exec_()

        if reply == QMessageBox.Yes:
            QgsProject.instance().layerTreeRoot().removeAllChildren()
            self.close_dialog(QDialog.Accepted)
        elif reply == QMessageBox.No:
            self.close_dialog(QDialog.Accepted)
        elif reply == QMessageBox.Cancel:
            pass  # Continue config db connections

    def collected_db_is_valid(self):
        res, foo, bar = self._db_collected.test_connection(required_models=[LADMNames.OPERATION_MODEL_PREFIX])
        return res

    def supplies_db_is_valid(self):
        res, foo, bar = self._db_supplies.test_connection(required_models=[LADMNames.SUPPLIES_MODEL_PREFIX])
        return res

    def reject(self):
        self.close_dialog(QDialog.Rejected)

    def close_dialog(self, result):
        """
        We use this slot to be safe when emitting the db_connection_changed (should be done at the end), otherwise we
        could trigger slots that unload the plugin, destroying dialogs and thus, leading to crashes.
        """
        if result == QDialog.Accepted:
            if self._schedule_layers_and_relations_refresh:
                self.conn_manager.db_connection_changed.connect(self.qgis_utils.cache_layers_and_relations)

            if self._db_collected_was_changed:
                self.conn_manager.db_connection_changed.emit(self._db_collected,
                                                             self._db_collected.test_connection()[0],
                                                             COLLECTED_DB_SOURCE)

            if self._db_supplies_was_changed:
                self.conn_manager.db_connection_changed.emit(self._db_supplies,
                                                             self._db_supplies.test_connection()[0],
                                                             SUPPLIES_DB_SOURCE)

            if self._schedule_layers_and_relations_refresh:
                self.conn_manager.db_connection_changed.disconnect(self.qgis_utils.cache_layers_and_relations)

        elif result == QDialog.Rejected:
            # Go back to initial connections and don't emit db_connection_changed
            if self._db_collected_was_changed:
                self.conn_manager.save_parameters_conn(self.init_db_collected, COLLECTED_DB_SOURCE)

            if self._db_supplies_was_changed:
                self.conn_manager.save_parameters_conn(self.init_db_supplies, SUPPLIES_DB_SOURCE)

        self.show_message_change_detection_settings_status()  # Show information message indicating whether setting is OK
        self.logger.info(__name__, "Dialog closed.")
        self.done(result)

    def show_help(self):
        self.qgis_utils.show_help("change_detection_settings")
コード例 #8
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)
コード例 #9
0
class QualityRuleEngine(QObject):
    """
    Engine that executes Quality Rules

    :param db: DBConnector object
    :param rules: Either a dict {rule_key:rule_name} or a list [rule_key1, rule_key2]
    :param tolerance: Tolerance to be used when running the QRs, in millimeters
    """
    progress_changed = pyqtSignal(int)  # Progress value

    def __init__(self, db, rules, tolerance, output_path=''):
        QObject.__init__(self)
        self.logger = Logger()
        self.app = AppInterface()

        self.__qr_registry = QualityRuleRegistry()

        self.__db = db
        self.__db_qr = None
        self.__rules = self.__get_dict_rules(rules)
        self.__result_layers = list()
        self.__with_gui = self.app.settings.with_gui
        self.__timestamp = ""

        self.__output_path = output_path

        self.app.settings.tolerance = tolerance  # Tolerance must be given, we don't want anything implicit about it
        self.__tolerance = self.app.settings.tolerance  # Tolerance input might be altered (e.g., if it comes negative)
        self.__layer_manager = QualityRuleLayerManager(db, self.__rules,
                                                       self.__tolerance)
        self.__layer_manager.progress_changed.connect(
            self.__emit_prepare_layers_progress)
        self.qr_logger = QualityRuleLogger(self.__db, self.__tolerance)
        self.__current_progress = 0
        self.__error_db_utils = QualityErrorDBUtils()
        self.__error_db_utils.progress_changed.connect(
            self.__emit_error_db_progress)

        # Clear informality cache before executing QRs.
        # Note: between creating an object of this class and calling validate_quality_rules() a lot
        # of things could happen (like new caches being built!). It is your responsibility to create
        # an instance of this class or initialize() a QREngine object just before calling validate_quality_rules().
        self.app.core.clear_cached_informal_spatial_units()

    def initialize(self,
                   db,
                   rules,
                   tolerance,
                   output_path='',
                   clear_informality_cache=True):
        """
        Objects of this class are reusable calling initialize()
        """
        self.__result_layers = list()
        self.__db = db
        self.__db_qr = None
        self.__rules = self.__get_dict_rules(rules)
        self.__with_gui = self.app.settings.with_gui
        self.__timestamp = ""

        self.__output_path = output_path

        self.app.settings.tolerance = tolerance
        self.__tolerance = self.app.settings.tolerance  # Tolerance input might be altered (e.g., if it comes negative)
        self.__layer_manager.initialize(self.__rules, self.__tolerance)
        self.qr_logger.initialize(self.__db, self.__tolerance)
        self.__current_progress = 0

        # This time, (initializing an existing object) we give you the chance to avoid
        # rebuilding the informality cache. It is handy if you're executing validations
        # consecutively and you're sure that reusing a previous cache does make sense.
        if clear_informality_cache:
            self.app.core.clear_cached_informal_spatial_units()

    def __get_dict_rules(self, rules):
        if isinstance(rules, dict):
            return rules  # We have everything ready

        # If rules is a list, we need to retrieve the quality rule names from the QRRegistry
        return {
            rule_key: self.__qr_registry.get_quality_rule(rule_key)
            for rule_key in rules
        }

    def validate_quality_rules(self, options=dict()):
        """
        :param options: Dict of dicts with of options per rule. {qr1_key: {qr1_opt1: value1, ...}, ...}
        """
        res = False
        msg = ""
        qr_res = dict()  # {rule_key: QualityRuleExecutionResult}

        if self.__rules:
            self.__emit_progress_changed(1)

            # First, create the error db and fill its metadata...
            self.__timestamp = time.strftime('%Y%m%d_%H%M%S')
            res_db, msg_db, self.__db_qr = self.__error_db_utils.get_quality_error_connector(
                self.__output_path, self.__timestamp, True)

            self.__emit_progress_changed(5)

            if not res_db:
                msg_db = QCoreApplication.translate(
                    "QualityRuleEngine",
                    "There was a problem creating the quality error DB! Details: {}"
                ).format(msg_db)
                self.logger.warning(__name__, msg_db)
                return False, msg_db, None

            self.qr_logger.set_count_topology_rules(len(self.__rules))
            self.logger.info(
                __name__,
                QCoreApplication.translate(
                    "QualityRuleEngine",
                    "Validating {} quality rules (tolerance: {}).").format(
                        len(self.__rules), self.__tolerance))

            first_pass = True
            count = 0
            for rule_key, rule in self.__rules.items():
                count += 1

                if rule is not None:
                    layers = self.__layer_manager.get_layers(
                        rule_key)  # Fist pass might be long if tolerance > 0
                    if first_pass:
                        first_pass = False
                        self.__emit_progress_changed(
                            25 if self.__tolerance else 15)

                    if layers:
                        connect_obj = rule.progress_changed.connect(
                            partial(self.__emit_qr_progress, count))
                        qr_res[rule_key] = self.__validate_quality_rule(
                            rule, layers, options.get(rule_key, dict()))
                        rule.progress_changed.disconnect(
                            connect_obj
                        )  # We no longer need the connection, so delete it
                    else:
                        qr_msg = QCoreApplication.translate(
                            "QualityRuleEngine",
                            "Couldn't execute '{}' quality rule! Required layers are not available. Skipping..."
                        ).format(rule.name())
                        qr_res[rule_key] = QualityRuleExecutionResult(
                            EnumQualityRuleResult.CRITICAL, qr_msg)
                        self.logger.warning(__name__, qr_msg)
                else:
                    qr_msg = QCoreApplication.translate(
                        "QualityRuleEngine",
                        "Quality rule with key '{}' does not exist or is not registered! Skipping..."
                    ).format(rule_key)
                    qr_res[rule_key] = QualityRuleExecutionResult(
                        EnumQualityRuleResult.CRITICAL, qr_msg)
                    self.logger.warning(__name__, qr_msg)

            self.__emit_progress_changed(95)

            metadata = {
                QR_METADATA_TOOL: QR_METADATA_TOOL_NAME,
                QR_METADATA_DATA_SOURCE:
                self.__db.get_description_conn_string(),
                QR_METADATA_TOLERANCE: self.__tolerance / 1000,
                QR_METADATA_TIMESTAMP: self.__timestamp,
                QR_METADATA_RULES: list(self.__rules.keys()),  # QR keys
                QR_METADATA_OPTIONS: self.__normalize_options(options),
                QR_METADATA_PERSON: getpass.getuser()
            }
            QualityErrorDBUtils.save_metadata(self.__db_qr, metadata)

            self.export_result_to_pdf()

            self.__emit_progress_changed(99)

            res = True
            msg = "Success!"
            self.__layer_manager.clean_temporary_layers()

        else:
            self.logger.warning(
                __name__,
                QCoreApplication.translate("QualityRuleEngine",
                                           "No rules to validate!"))

        self.__emit_progress_changed(100)

        return res, msg, QualityRulesExecutionResult(qr_res)

    @_log_quality_rule_validations
    def __validate_quality_rule(self, rule, layers, options):
        """
        Intermediate function to log quality rule execution.

        :param rule: Quality rule instance
        :param layers: Layer dict with the layers the quality rule needs (ready to use for tolerance > 0 scenarios)
        :param options: Dict of options per rule. {qr1_opt1: value1, ...}
        :return: An instance of QualityRuleExecutionResult
        """
        return rule.validate(self.__db,
                             self.__db_qr,
                             layers,
                             self.__tolerance,
                             options=options)

    def __emit_progress_changed(self, value, save_value=True):
        if value != self.__current_progress:  # Avoid emitting the same value twice
            if save_value:
                self.__current_progress = value
            self.progress_changed.emit(value)

    def __emit_error_db_progress(self, progress_value):
        """
        Add the normalized error db progress value to what we have already in the overall progress
        """
        value = self.__current_progress + (progress_value *
                                           TOTAL_PROGRESS_ERROR_DB / 100)
        # print("...DB", self.__current_progress, progress_value, value)
        self.__emit_progress_changed(
            int(value),
            progress_value == 100)  # Only save when the subprocess is finished

    def __emit_prepare_layers_progress(self, progress_value):
        """
        Add the normalized prepare layers' progress value to what we have already in the overall progress
        """
        range = TOTAL_PROGRESS_PREPARE_LAYERS if self.__tolerance else TOTAL_PROGRESS_PREPARE_LAYERS_NO_TOLERANCE
        value = self.__current_progress + (progress_value * range / 100)
        # print("...PL", self.__current_progress, progress_value, value)
        self.__emit_progress_changed(
            int(value),
            progress_value == 100)  # Only save when the subprocess is finished

    def __emit_qr_progress(self, count, qr_progress_value):
        """
        Add the normalized current QR progress value to what we have already in the overall progress
        """
        num_rules = len(self.__rules)
        step = (TOTAL_PROGRESS_QR if self.__tolerance else
                TOTAL_PROGRESS_QR_NO_TOLERANCE) / num_rules
        value = self.__current_progress + (qr_progress_value * step / 100)
        # print("...QR", self.__current_progress, qr_progress_value, value)
        self.__emit_progress_changed(
            int(value),
            qr_progress_value == 100)  # Only save when the QR is finished

    def get_db_quality(self):
        return self.__db_qr

    def get_timestamp(self):
        # Last timestamp used to validate QRs.
        # Note the timestamp is persisted in QR DB's metadata table
        return self.__timestamp

    def export_result_to_pdf(self):
        res, msg, output_path = QualityErrorDBUtils.get_quality_validation_output_path(
            self.__output_path, self.__timestamp)
        if not res:
            self.logger.critical(
                __name__,
                QCoreApplication.translate(
                    "QualityRuleEngine",
                    "PDF report could not be exported, there were problems with the output path '{}'!"
                ).format(self.__output_path))
            return

        pdf_path = os.path.join(
            output_path, "Reglas_de_Calidad_{}.pdf".format(self.__timestamp))
        log = self.qr_logger.get_log_result()
        export_title_text_to_pdf(pdf_path, log.title, log.text)

    def __normalize_options(self, options):
        # Get rid of options that do not correspond to validated QRs
        normalized_options = dict()
        for k, v in options.items():
            for kv, vv in v.items():
                if k in list(self.__rules.keys()):
                    if k in normalized_options:
                        normalized_options[k][kv] = vv
                    else:
                        normalized_options[k] = {kv: vv}
        return normalized_options
コード例 #10
0
class Role_Registry(metaclass=Singleton):
    """
    Manage all role information. Current role can also be got/set from this class.

    Roles can set their own GUI configuration even using and overwriting the template gui config.
    """
    COMMON_ACTIONS = [  # Common actions for all roles
        ACTION_LOAD_LAYERS, ACTION_SCHEMA_IMPORT, ACTION_IMPORT_DATA,
        ACTION_EXPORT_DATA, ACTION_SETTINGS, ACTION_HELP, ACTION_ABOUT
    ]

    def __init__(self):
        self.logger = Logger()
        self._registered_roles = dict()
        self._default_role = BASIC_ROLE

        role = BASIC_ROLE
        template_gui = GUI_Config().get_gui_dict(TEMPLATE_GUI)
        template_gui[TOOLBAR] = [{  # Overwrite list of toolbars
            WIDGET_NAME:
            QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                       "LADM-COL tools"),
            OBJECT_NAME:
            'ladm_col_toolbar',
            ACTIONS: [
                {  # List of toolbars
                    WIDGET_NAME:
                    QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                               "Data management"),
                    OBJECT_NAME:
                    'ladm_col_data_management_toolbar',
                    ICON:
                    DATA_MANAGEMENT_ICON,
                    ACTIONS: [
                        ACTION_SCHEMA_IMPORT, ACTION_IMPORT_DATA,
                        ACTION_EXPORT_DATA
                    ]
                },
                SEPARATOR,
                {
                    WIDGET_TYPE:
                    MENU,
                    WIDGET_NAME:
                    QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                               "Create Operation objects"),
                    OBJECT_NAME:
                    "ladm_col_operation_toolbar",
                    ICON:
                    OPERATION_ICON,
                    ACTIONS: [
                        ACTION_CREATE_POINT, ACTION_CREATE_BOUNDARY, SEPARATOR,
                        ACTION_CREATE_PLOT, ACTION_CREATE_BUILDING,
                        ACTION_CREATE_BUILDING_UNIT,
                        ACTION_CREATE_RIGHT_OF_WAY,
                        ACTION_FILL_RIGHT_OF_WAY_RELATIONS, SEPARATOR,
                        ACTION_CREATE_EXT_ADDRESS, SEPARATOR,
                        ACTION_CREATE_PARCEL, SEPARATOR, ACTION_CREATE_PARTY,
                        ACTION_CREATE_GROUP_PARTY, SEPARATOR,
                        ACTION_CREATE_RIGHT, ACTION_CREATE_RESTRICTION,
                        SEPARATOR, ACTION_CREATE_ADMINISTRATIVE_SOURCE,
                        ACTION_CREATE_SPATIAL_SOURCE,
                        ACTION_UPLOAD_PENDING_SOURCE
                    ]
                },
                SEPARATOR,
                ACTION_FINALIZE_GEOMETRY_CREATION,
                {
                    WIDGET_TYPE:
                    MENU,
                    WIDGET_NAME:
                    QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                               "Structuring tools"),
                    OBJECT_NAME:
                    "ladm_col_structuring_tools_toolbar",
                    ICON:
                    STRUCTURING_TOOLS_ICON,
                    ACTIONS: [
                        ACTION_BUILD_BOUNDARY, ACTION_MOVE_NODES,
                        ACTION_FILL_BFS, ACTION_FILL_MORE_BFS_AND_LESS
                    ]
                },
                SEPARATOR,
                ACTION_LOAD_LAYERS,
                ACTION_PARCEL_QUERY
            ]
        }]
        role_dict = {
            ROLE_NAME:
            QCoreApplication.translate("AsistenteLADMCOLPlugin", "Basic"),
            ROLE_DESCRIPTION:
            QCoreApplication.translate(
                "AsistenteLADMCOLPlugin",
                "The basic role helps you to explore the LADM_COL assistant main functionalities."
            ),
            ROLE_ACTIONS: [
                ACTION_DOWNLOAD_GUIDE, ACTION_CREATE_POINT,
                ACTION_CREATE_BOUNDARY, ACTION_CREATE_PLOT,
                ACTION_CREATE_BUILDING, ACTION_CREATE_BUILDING_UNIT,
                ACTION_CREATE_RIGHT_OF_WAY, ACTION_CREATE_EXT_ADDRESS,
                ACTION_CREATE_PARCEL, ACTION_CREATE_RIGHT,
                ACTION_CREATE_RESTRICTION, ACTION_CREATE_PARTY,
                ACTION_CREATE_GROUP_PARTY, ACTION_CREATE_ADMINISTRATIVE_SOURCE,
                ACTION_CREATE_SPATIAL_SOURCE, ACTION_UPLOAD_PENDING_SOURCE,
                ACTION_IMPORT_FROM_INTERMEDIATE_STRUCTURE,
                ACTION_BUILD_BOUNDARY, ACTION_MOVE_NODES,
                ACTION_FINALIZE_GEOMETRY_CREATION, ACTION_FILL_BFS,
                ACTION_FILL_MORE_BFS_AND_LESS,
                ACTION_FILL_RIGHT_OF_WAY_RELATIONS, ACTION_PARCEL_QUERY,
                ACTION_CHECK_QUALITY_RULES
            ],
            ROLE_GUI_CONFIG:
            template_gui
        }
        self.register_role(role, role_dict)

        role = SUPPLIES_PROVIDER_ROLE
        template_gui = GUI_Config().get_gui_dict(TEMPLATE_GUI)
        template_gui[TOOLBAR] = [{  # Overwrite list of toolbars
            WIDGET_NAME:
            QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                       "LADM-COL tools"),
            OBJECT_NAME:
            'ladm_col_toolbar',
            ACTIONS: [
                {  # List of toolbars
                    WIDGET_NAME:
                    QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                               "Transitional System"),
                    OBJECT_NAME:
                    'ladm_col_toolbar_st',
                    ICON:
                    ST_ICON,
                    ACTIONS: [ACTION_ST_LOGIN, ACTION_ST_LOGOUT]
                },
                SEPARATOR,
                ACTION_SCHEMA_IMPORT,
                ACTION_RUN_ETL_COBOL,
                ACTION_RUN_ETL_SNC,
                ACTION_FIND_MISSING_COBOL_SUPPLIES,
                ACTION_LOAD_LAYERS,
                ACTION_EXPORT_DATA
            ]
        }]
        role_dict = {
            ROLE_NAME:
            QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                       "Supplies Provider"),
            ROLE_DESCRIPTION:
            QCoreApplication.translate(
                "AsistenteLADMCOLPlugin",
                "The Supplies Provider role generates a XTF file with supplies data for the Manager role."
            ),
            ROLE_ACTIONS: [
                ACTION_RUN_ETL_COBOL, ACTION_RUN_ETL_SNC,
                ACTION_FIND_MISSING_COBOL_SUPPLIES, ACTION_ST_LOGIN,
                ACTION_ST_LOGOUT
            ],
            ROLE_GUI_CONFIG:
            template_gui
        }
        self.register_role(role, role_dict)

        role = OPERATOR_ROLE
        role_dict = {
            ROLE_NAME:
            QCoreApplication.translate("AsistenteLADMCOLPlugin", "Operator"),
            ROLE_DESCRIPTION:
            QCoreApplication.translate(
                "AsistenteLADMCOLPlugin",
                "The operator is in charge of capturing current cadastral data."
            ),
            ROLE_ACTIONS: [
                ACTION_CREATE_POINT, ACTION_CREATE_BOUNDARY,
                ACTION_CREATE_PLOT, ACTION_CREATE_BUILDING,
                ACTION_CREATE_BUILDING_UNIT, ACTION_CREATE_RIGHT_OF_WAY,
                ACTION_CREATE_EXT_ADDRESS, ACTION_CREATE_PARCEL,
                ACTION_CREATE_RIGHT, ACTION_CREATE_RESTRICTION,
                ACTION_CREATE_PARTY, ACTION_CREATE_GROUP_PARTY,
                ACTION_CREATE_ADMINISTRATIVE_SOURCE,
                ACTION_CREATE_SPATIAL_SOURCE, ACTION_UPLOAD_PENDING_SOURCE,
                ACTION_IMPORT_FROM_INTERMEDIATE_STRUCTURE,
                ACTION_BUILD_BOUNDARY, ACTION_MOVE_NODES,
                ACTION_FINALIZE_GEOMETRY_CREATION, ACTION_FILL_BFS,
                ACTION_FILL_MORE_BFS_AND_LESS,
                ACTION_FILL_RIGHT_OF_WAY_RELATIONS,
                ACTION_CHANGE_DETECTION_SETTINGS,
                ACTION_CHANGE_DETECTION_ALL_PARCELS,
                ACTION_CHANGE_DETECTION_PER_PARCEL, ACTION_ST_LOGIN,
                ACTION_ST_LOGOUT, ACTION_PARCEL_QUERY,
                ACTION_CHECK_QUALITY_RULES
            ],
            ROLE_GUI_CONFIG:
            {}  # Let the gui builder use the template GUI config.
        }
        self.register_role(role, role_dict)

        role = MANAGER_ROLE
        template_gui = GUI_Config().get_gui_dict(TEMPLATE_GUI)
        template_gui[TOOLBAR] = [{  # Overwrite list of toolbars
            WIDGET_NAME:
            QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                       "LADM-COL tools"),
            OBJECT_NAME:
            'ladm_col_toolbar',
            ACTIONS: [
                {  # List of toolbars
                    WIDGET_NAME:
                    QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                               "Transitional System"),
                    OBJECT_NAME:
                    'ladm_col_toolbar_st',
                    ICON:
                    ST_ICON,
                    ACTIONS: [ACTION_ST_LOGIN, ACTION_ST_LOGOUT]
                },
                SEPARATOR,
                ACTION_LOAD_LAYERS,
                ACTION_INTEGRATE_SUPPLIES,
                SEPARATOR,
                ACTION_CHECK_QUALITY_RULES,
                ACTION_PARCEL_QUERY,
                SEPARATOR,
                {  # List of toolbars
                    WIDGET_NAME:
                    QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                               "Change Detection"),
                    OBJECT_NAME:
                    'ladm_col_change_detection_toolbar',
                    ICON:
                    CHANGE_DETECTION_ICON,
                    ACTIONS: [
                        ACTION_CHANGE_DETECTION_SETTINGS, SEPARATOR,
                        ACTION_CHANGE_DETECTION_PER_PARCEL,
                        ACTION_CHANGE_DETECTION_ALL_PARCELS
                    ]
                },
                SEPARATOR,
                {  # List of toolbars
                    WIDGET_NAME:
                    QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                               "Reports"),
                    OBJECT_NAME:
                    'ladm_col_reports_toolbar',
                    ICON:
                    REPORTS_ICON,
                    ACTIONS: [ACTION_REPORT_ANNEX_17, ACTION_REPORT_ANT]
                }
            ]
        }]
        role_dict = {
            ROLE_NAME:
            QCoreApplication.translate("AsistenteLADMCOLPlugin", "Manager"),
            ROLE_DESCRIPTION:
            QCoreApplication.translate(
                "AsistenteLADMCOLPlugin",
                "The manager is in charge of preparing supplies for operators as well as validating and managing the data provided by operators."
            ),
            ROLE_ACTIONS: [
                ACTION_CHANGE_DETECTION_SETTINGS,
                ACTION_CHANGE_DETECTION_ALL_PARCELS,
                ACTION_CHANGE_DETECTION_PER_PARCEL, ACTION_ST_LOGIN,
                ACTION_ST_LOGOUT, ACTION_REPORT_ANNEX_17, ACTION_REPORT_ANT,
                ACTION_INTEGRATE_SUPPLIES, ACTION_PARCEL_QUERY,
                ACTION_CHECK_QUALITY_RULES
            ],
            ROLE_GUI_CONFIG:
            template_gui
        }
        self.register_role(role, role_dict)

        role = ADVANCED_ROLE
        template_gui = GUI_Config().get_gui_dict(TEMPLATE_GUI)
        template_gui[TOOLBAR] = [{  # List of toolbars
            WIDGET_NAME:
            QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                       "LADM-COL tools"),
            OBJECT_NAME:
            'ladm_col_toolbar',
            ACTIONS: [
                {  # List of toolbars
                    WIDGET_NAME:
                    QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                               "Transitional System"),
                    OBJECT_NAME:
                    'ladm_col_st_toolbar',
                    ICON:
                    ST_ICON,
                    ACTIONS: [ACTION_ST_LOGIN, ACTION_ST_LOGOUT]
                },
                SEPARATOR,
                {
                    WIDGET_TYPE:
                    MENU,
                    WIDGET_NAME:
                    QCoreApplication.translate("AsistenteLADMCOLPlugin",
                                               "Create Operation objects"),
                    OBJECT_NAME:
                    "ladm_col_operation_toolbar",
                    ICON:
                    OPERATION_ICON,
                    ACTIONS: [
                        ACTION_CREATE_POINT, ACTION_CREATE_BOUNDARY, SEPARATOR,
                        ACTION_CREATE_PLOT, ACTION_CREATE_BUILDING,
                        ACTION_CREATE_BUILDING_UNIT,
                        ACTION_CREATE_RIGHT_OF_WAY,
                        ACTION_FILL_RIGHT_OF_WAY_RELATIONS, SEPARATOR,
                        ACTION_CREATE_EXT_ADDRESS, SEPARATOR,
                        ACTION_CREATE_PARCEL, SEPARATOR, ACTION_CREATE_PARTY,
                        ACTION_CREATE_GROUP_PARTY, SEPARATOR,
                        ACTION_CREATE_RIGHT, ACTION_CREATE_RESTRICTION,
                        SEPARATOR, ACTION_CREATE_ADMINISTRATIVE_SOURCE,
                        ACTION_CREATE_SPATIAL_SOURCE,
                        ACTION_UPLOAD_PENDING_SOURCE
                    ]
                },
                SEPARATOR,
                ACTION_LOAD_LAYERS,
                SEPARATOR,
                ACTION_FINALIZE_GEOMETRY_CREATION,
                ACTION_BUILD_BOUNDARY,
                ACTION_MOVE_NODES,
                SEPARATOR,
                ACTION_FILL_BFS,
                ACTION_FILL_MORE_BFS_AND_LESS,
                SEPARATOR,
                ACTION_SETTINGS
            ]
        }]
        role_dict = {
            ROLE_NAME:
            QCoreApplication.translate("AsistenteLADMCOLPlugin", "Advanced"),
            ROLE_DESCRIPTION:
            QCoreApplication.translate(
                "AsistenteLADMCOLPlugin",
                "The advanced role has access to all the functionality."),
            ROLE_ACTIONS: [ALL_ACTIONS],
            ROLE_GUI_CONFIG:
            template_gui
        }
        self.register_role(role, role_dict)

    def register_role(self, role_key, role_dict):
        """
        Register roles for the LADM_COL assistant. Roles have access only to certain GUI controls.

        :param role_key: Role unique identifier
        :param role_dict: Dictionary with the following information:
                ROLE_NAME: Name of the role
                ROLE_DESCRIPTION: Explains what this role is about
                ROLE_ACTIONS: List of actions a role has access to
        :return: Whether the role was successfully registered or not.
        """
        valid = False
        if ROLE_NAME in role_dict and ROLE_DESCRIPTION in role_dict and ROLE_ACTIONS in role_dict and ROLE_GUI_CONFIG in role_dict:
            self._registered_roles[role_key] = deepcopy(role_dict)
            valid = True
        else:
            self.logger.error(
                __name__,
                "Role '{}' is not defined correctly and could not be registered! Check the role_dict parameter."
                .format(role_key))

        return valid

    def get_active_role(self):
        return QSettings().value("Asistente-LADM_COL/roles/current_role_key",
                                 self._default_role)

    def active_role_already_set(self):
        """
        Whether we have set an active role already or not.

        :return: True if the current_role_key variable is stored in QSettings. False otherwise.
        """
        return QSettings().value("Asistente-LADM_COL/roles/current_role_key",
                                 False) is not False

    def set_active_role(self, role_key):
        res = False
        if role_key in self._registered_roles:
            res = True
        else:
            self.logger.warning(
                __name__,
                "Role '{}' was not found, the default role is now active.".
                format(role_key))
            role_key = self._default_role

        QSettings().setValue("Asistente-LADM_COL/roles/current_role_key",
                             role_key)
        self.logger.info(__name__, "Role '{}' is now active!".format(role_key))

        return res

    def set_active_default_role(self):
        QSettings().setValue("Asistente-LADM_COL/roles/current_role_key",
                             self._default_role)
        self.logger.info(
            __name__,
            "Default role '{}' is now active!".format(self._default_role))
        return True

    def get_roles_info(self):
        return {k: v[ROLE_NAME] for k, v in self._registered_roles.items()}

    def get_role_name(self, role_key):
        if role_key not in self._registered_roles:
            self.logger.error(
                __name__,
                "Role '{}' was not found, returning default role's name".
                format(role_key))
            role_key = self._default_role

        return self._registered_roles[role_key][ROLE_NAME]

    def get_role_description(self, role_key):
        if role_key not in self._registered_roles:
            self.logger.error(
                __name__,
                "Role '{}' was not found, returning default role's decription".
                format(role_key))
            role_key = self._default_role

        return self._registered_roles[role_key][ROLE_DESCRIPTION]

    def get_role_actions(self, role_key):
        if role_key not in self._registered_roles:
            self.logger.error(
                __name__,
                "Role '{}' was not found, returning default role's actions.".
                format(role_key))
            role_key = self._default_role

        return list(
            set(self._registered_roles[role_key][ROLE_ACTIONS] +
                self.COMMON_ACTIONS))

    def get_role_gui_config(self, role_key):
        if role_key not in self._registered_roles:
            self.logger.error(
                __name__,
                "Role '{}' was not found, returning default role's GUI configuration."
                .format(role_key))
            role_key = self._default_role

        return self._registered_roles[role_key][ROLE_GUI_CONFIG]
コード例 #11
0
class DockWidgetQueries(QgsDockWidget, DOCKWIDGET_UI):

    zoom_to_features_requested = pyqtSignal(QgsVectorLayer, list, dict,
                                            int)  # layer, ids, t_ids, duration

    def __init__(self, iface, db, qgis_utils, ladm_data, parent=None):
        super(DockWidgetQueries, self).__init__(None)
        self.setupUi(self)
        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
        self.iface = iface
        self._db = db
        self.qgis_utils = qgis_utils
        self.ladm_data = ladm_data
        self.logger = Logger()
        self.canvas = iface.mapCanvas()
        self.active_map_tool_before_custom = None
        self.names = self._db.names

        self.clipboard = QApplication.clipboard()

        # Required layers
        self.restart_dict_of_layers()

        self._identify_tool = None

        self.add_layers()
        self.fill_combos()

        self.btn_identify_plot.setIcon(
            QIcon(":/Asistente-LADM_COL/resources/images/spatial_unit.png"))

        # Set connections
        self.btn_alphanumeric_query.clicked.connect(self.alphanumeric_query)
        self.cbo_parcel_fields.currentIndexChanged.connect(
            self.search_field_updated)
        self.btn_identify_plot.clicked.connect(self.btn_plot_toggled)

        # Context menu
        self._set_context_menus()

        # Create maptool
        self.maptool_identify = QgsMapToolIdentifyFeature(self.canvas)

        self.initialize_field_values_line_edit()

    def search_field_updated(self, index=None):
        self.initialize_field_values_line_edit()

    def initialize_field_values_line_edit(self):
        self.txt_alphanumeric_query.setLayer(
            self._layers[self.names.OP_PARCEL_T][LAYER])
        idx = self._layers[self.names.OP_PARCEL_T][LAYER].fields().indexOf(
            self.cbo_parcel_fields.currentData())
        self.txt_alphanumeric_query.setAttributeIndex(idx)

    def _set_context_menus(self):
        self.tree_view_basic.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree_view_basic.customContextMenuRequested.connect(
            self.show_context_menu)

        self.tree_view_legal.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree_view_legal.customContextMenuRequested.connect(
            self.show_context_menu)

        self.tree_view_property_record_card.setContextMenuPolicy(
            Qt.CustomContextMenu)
        self.tree_view_property_record_card.customContextMenuRequested.connect(
            self.show_context_menu)

        self.tree_view_physical.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree_view_physical.customContextMenuRequested.connect(
            self.show_context_menu)

        self.tree_view_economic.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree_view_economic.customContextMenuRequested.connect(
            self.show_context_menu)

    def restart_dict_of_layers(self):
        self._layers = {
            self.names.OP_PLOT_T: {
                'name': self.names.OP_PLOT_T,
                'geometry': QgsWkbTypes.PolygonGeometry,
                LAYER: None
            },
            self.names.OP_PARCEL_T: {
                'name': self.names.OP_PARCEL_T,
                'geometry': None,
                LAYER: None
            },
            self.names.COL_UE_BAUNIT_T: {
                'name': self.names.COL_UE_BAUNIT_T,
                'geometry': None,
                LAYER: None
            }
        }

    def add_layers(self):
        self.qgis_utils.get_layers(self._db, self._layers, load=True)
        if not self._layers:
            self.restart_dict_of_layers()  # Let it ready for the next call
            return None

        # Layer was found, listen to its removal so that we can deactivate the custom tool when that occurs
        try:
            self._layers[self.names.OP_PLOT_T][LAYER].willBeDeleted.disconnect(
                self.layer_removed)
        except TypeError as e:
            pass
        self._layers[self.names.OP_PLOT_T][LAYER].willBeDeleted.connect(
            self.layer_removed)

        # Layer was found, listen to its removal so that we can update the variable properly
        try:
            self._layers[
                self.names.OP_PARCEL_T][LAYER].willBeDeleted.disconnect(
                    self.parcel_layer_removed)
        except TypeError as e:
            pass
        self._layers[self.names.OP_PARCEL_T][LAYER].willBeDeleted.connect(
            self.parcel_layer_removed)

        # Layer was found, listen to its removal so that we can update the variable properly
        try:
            self._layers[
                self.names.COL_UE_BAUNIT_T][LAYER].willBeDeleted.disconnect(
                    self.uebaunit_table_removed)
        except TypeError as e:
            pass
        self._layers[self.names.COL_UE_BAUNIT_T][LAYER].willBeDeleted.connect(
            self.uebaunit_table_removed)

    def initialize_tool(self):
        self._layers[self.names.OP_PLOT_T][LAYER] = None
        self.initialize_tools(new_tool=None, old_tool=self.maptool_identify)
        self.btn_plot_toggled()

    def update_db_connection(self, db, ladm_col_db, db_source):
        self._db = db
        self.initialize_tool()

        if not ladm_col_db:
            self.setVisible(False)

    def layer_removed(self):
        # The required layer was removed, deactivate custom tool
        self.initialize_tool()

    def parcel_layer_removed(self):
        self._layers[self.names.OP_PARCEL_T][LAYER] = None

    def uebaunit_table_removed(self):
        self._layers[self.names.COL_UE_BAUNIT_T][LAYER] = None

    def fill_combos(self):
        self.cbo_parcel_fields.clear()

        self.cbo_parcel_fields.addItem(
            QCoreApplication.translate("DockWidgetQueries", "Parcel Number"),
            self.names.OP_PARCEL_T_PARCEL_NUMBER_F)
        self.cbo_parcel_fields.addItem(
            QCoreApplication.translate("DockWidgetQueries",
                                       "Previous Parcel Number"),
            self.names.OP_PARCEL_T_PREVIOUS_PARCEL_NUMBER_F)
        self.cbo_parcel_fields.addItem(
            QCoreApplication.translate("DockWidgetQueries",
                                       "Folio de Matrícula Inmobiliaria"),
            self.names.OP_PARCEL_T_FMI_F)

    def initialize_tools(self, new_tool, old_tool):
        if self.maptool_identify == old_tool:
            # custom identify was deactivated
            try:
                self.canvas.mapToolSet.disconnect(self.initialize_tools)
            except TypeError as e:
                pass
            self.btn_identify_plot.setChecked(False)
        else:
            # custom identify was activated
            pass

    def btn_plot_toggled(self):
        if self.btn_identify_plot.isChecked():
            self.prepare_identify_plot()
        else:
            # The button was toggled and deactivated, go back to the previous tool
            self.canvas.setMapTool(self.active_map_tool_before_custom)

    def prepare_identify_plot(self):
        """
            Custom Identify tool was activated, prepare everything for identifying plots
        """
        self.active_map_tool_before_custom = self.canvas.mapTool()

        self.btn_identify_plot.setChecked(True)

        self.canvas.mapToolSet.connect(self.initialize_tools)

        if self._layers[self.names.OP_PLOT_T][LAYER] is None:
            self.add_layers()

        self.maptool_identify.setLayer(
            self._layers[self.names.OP_PLOT_T][LAYER])
        cursor = QCursor()
        cursor.setShape(Qt.PointingHandCursor)
        self.maptool_identify.setCursor(cursor)
        self.canvas.setMapTool(self.maptool_identify)

        try:
            self.maptool_identify.featureIdentified.disconnect()
        except TypeError as e:
            pass
        self.maptool_identify.featureIdentified.connect(self.get_info_by_plot)

    def get_info_by_plot(self, plot_feature):
        plot_t_id = plot_feature[self.names.T_ID_F]
        self.canvas.flashFeatureIds(self._layers[self.names.OP_PLOT_T][LAYER],
                                    [plot_feature.id()],
                                    QColor(255, 0, 0, 255),
                                    QColor(255, 0, 0, 0),
                                    flashes=1,
                                    duration=500)

        with OverrideCursor(Qt.WaitCursor):
            if not self.isVisible():
                self.show()

            self.search_data_by_component(plot_t_id=plot_t_id,
                                          zoom_and_select=False)
            self._layers[self.names.OP_PLOT_T][LAYER].selectByIds(
                [plot_feature.id()])

    def search_data_by_component(self, **kwargs):
        self._layers[self.names.OP_PLOT_T][LAYER].removeSelection()

        # Read zoom_and_select parameter and remove it from kwargs
        bZoom = False
        if 'zoom_and_select' in kwargs:
            bZoom = kwargs['zoom_and_select']
            del kwargs['zoom_and_select']

        records = self._db.get_igac_basic_info(**kwargs)
        self.setup_tree_view(self.tree_view_basic, records)

        if bZoom:
            # Zoom to resulting plots
            plot_t_ids = self.get_plot_t_ids_from_basic_info(records)
            if plot_t_ids:
                features = self.ladm_data.get_features_from_t_ids(
                    self._layers[self.names.OP_PLOT_T][LAYER],
                    self.names.T_ID_F, plot_t_ids, True, True)
                plot_ids = [feature.id() for feature in features]
                self.zoom_to_features_requested.emit(
                    self._layers[self.names.OP_PLOT_T][LAYER], plot_ids,
                    dict(), 500)
                self._layers[self.names.OP_PLOT_T][LAYER].selectByIds(plot_ids)

        records = self._db.get_igac_legal_info(**kwargs)
        self.setup_tree_view(self.tree_view_legal, records)

        records = self._db.get_igac_property_record_card_info(**kwargs)
        self.setup_tree_view(self.tree_view_property_record_card, records)

        records = self._db.get_igac_physical_info(**kwargs)
        self.setup_tree_view(self.tree_view_physical, records)

        records = self._db.get_igac_economic_info(**kwargs)
        self.setup_tree_view(self.tree_view_economic, records)

    def setup_tree_view(self, tree_view, records):
        """
        Configure result tree views

        :param tree_view:
        :return:
        """
        tree_view.setModel(TreeModel(self.names, data=records))
        tree_view.expandAll()
        self.add_thumbnails_to_tree_view(tree_view)

    def add_thumbnails_to_tree_view(self, tree_view):
        """
        Gets a list of model indexes corresponding to extFiles objects to show a preview

        :param model:
        :return:
        """
        model = tree_view.model()
        indexes = model.getPixmapIndexList()
        for idx in indexes:
            url = model.data(idx, Qt.UserRole)['url']
            res, image = self.download_image("{}{}".format(
                url, SUFFIX_GET_THUMBNAIL))
            if res:
                pixmap = QPixmap()
                pixmap.loadFromData(image)
                label = QLabel()
                label.setPixmap(pixmap)
                tree_view.setIndexWidget(idx, label)

    def get_plot_t_ids_from_basic_info(self, records):
        res = []
        if records:
            for record in records:
                if self.names.OP_PLOT_T in record:
                    for element in record[self.names.OP_PLOT_T]:
                        res.append(element['id'])

        return res

    def alphanumeric_query(self):
        """
        Alphanumeric query
        """
        option = self.cbo_parcel_fields.currentData()
        query = self.txt_alphanumeric_query.value()
        if query:
            if option == self.names.OP_PARCEL_T_FMI_F:
                self.search_data_by_component(parcel_fmi=query,
                                              zoom_and_select=True)
            elif option == self.names.OP_PARCEL_T_PARCEL_NUMBER_F:
                self.search_data_by_component(parcel_number=query,
                                              zoom_and_select=True)
            else:  # previous_parcel_number
                self.search_data_by_component(previous_parcel_number=query,
                                              zoom_and_select=True)
        else:
            self.iface.messageBar().pushMessage(
                "Asistente LADM_COL",
                QCoreApplication.translate("DockWidgetQueries",
                                           "First enter a query"))

    def show_context_menu(self, point):
        tree_view = self.sender()
        index = tree_view.indexAt(point)

        context_menu = QMenu("Context menu")

        index_data = index.data(Qt.UserRole)

        if index_data is None:
            return

        if "value" in index_data:
            action_copy = QAction(
                QCoreApplication.translate("DockWidgetQueries", "Copy value"))
            action_copy.triggered.connect(
                partial(self.copy_value, index_data["value"]))
            context_menu.addAction(action_copy)
            context_menu.addSeparator()

        if "url" in index_data:
            action_open_url = QAction(
                QCoreApplication.translate("DockWidgetQueries", "Open URL"))
            action_open_url.triggered.connect(
                partial(self.open_url, index_data["url"]))
            context_menu.addAction(action_open_url)
            context_menu.addSeparator()

        # Configure actions for tables/layers
        if "type" in index_data and "id" in index_data:
            table_name = index_data["type"]
            table_package = LayerConfig.get_dict_table_package(self.names)
            t_id = index_data["id"]
            geometry_type = None
            if table_name in table_package and table_package[
                    table_name] == LADMNames.SPATIAL_UNIT_PACKAGE:
                # Layers in Spatial Unit package have double geometry, we need the polygon one
                geometry_type = QgsWkbTypes.PolygonGeometry

            if table_name == self.names.OP_PARCEL_T:
                if self._layers[
                        self.names.OP_PARCEL_T][LAYER] is None or self._layers[
                            self.names.
                            OP_PLOT_T][LAYER] is None or self._layers[
                                self.names.COL_UE_BAUNIT_T][LAYER] is None:
                    self.add_layers()
                layer = self._layers[self.names.OP_PARCEL_T][LAYER]
                self.iface.layerTreeView().setCurrentLayer(layer)
            else:
                layer = self.qgis_utils.get_layer(self._db, table_name,
                                                  geometry_type, True)

            if layer is not None:
                if layer.isSpatial():
                    action_zoom_to_feature = QAction(
                        QCoreApplication.translate(
                            "DockWidgetQueries",
                            "Zoom to {} with {}={}").format(
                                table_name, self.names.T_ID_F, t_id))
                    action_zoom_to_feature.triggered.connect(
                        partial(self.zoom_to_feature, layer, t_id))
                    context_menu.addAction(action_zoom_to_feature)

                if table_name == self.names.OP_PARCEL_T:
                    # We show a handy option to zoom to related plots
                    plot_ids = self.ladm_data.get_plots_related_to_parcels(
                        self._db, [t_id], None,
                        self._layers[self.names.OP_PLOT_T][LAYER],
                        self._layers[self.names.COL_UE_BAUNIT_T][LAYER])
                    if plot_ids:
                        action_zoom_to_plots = QAction(
                            QCoreApplication.translate(
                                "DockWidgetQueries",
                                "Zoom to related plot(s)"))
                        action_zoom_to_plots.triggered.connect(
                            partial(self.zoom_to_plots, plot_ids))
                        context_menu.addAction(action_zoom_to_plots)

                action_open_feature_form = QAction(
                    QCoreApplication.translate(
                        "DockWidgetQueries",
                        "Open form for {} with {}={}").format(
                            table_name, self.names.T_ID_F, t_id))
                action_open_feature_form.triggered.connect(
                    partial(self.open_feature_form, layer, t_id))
                context_menu.addAction(action_open_feature_form)

        if context_menu.actions():
            context_menu.exec_(tree_view.mapToGlobal(point))

    def copy_value(self, value):
        self.clipboard.setText(str(value))

    def open_url(self, url):
        webbrowser.open(url)

    def zoom_to_feature(self, layer, t_id):
        feature = self.get_feature_from_t_id(layer, t_id)
        self.iface.mapCanvas().zoomToFeatureIds(layer, [feature.id()])
        self.canvas.flashFeatureIds(layer, [feature.id()],
                                    QColor(255, 0, 0, 255),
                                    QColor(255, 0, 0, 0),
                                    flashes=1,
                                    duration=500)

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

    def get_feature_from_t_id(self, layer, t_id):
        field_idx = layer.fields().indexFromName(self.names.T_ID_F)
        request = QgsFeatureRequest(
            QgsExpression("{}={}".format(self.names.T_ID_F, t_id)))
        request.setFlags(QgsFeatureRequest.NoGeometry)

        iterator = layer.getFeatures(request)
        feature = QgsFeature()
        res = iterator.nextFeature(feature)
        if res:
            return feature

        return None

    def zoom_to_plots(self, plot_ids):
        self.iface.mapCanvas().zoomToFeatureIds(
            self._layers[self.names.OP_PLOT_T][LAYER], plot_ids)
        self.canvas.flashFeatureIds(self._layers[self.names.OP_PLOT_T][LAYER],
                                    plot_ids,
                                    QColor(255, 0, 0, 255),
                                    QColor(255, 0, 0, 0),
                                    flashes=1,
                                    duration=500)

    def closeEvent(self, event):
        try:
            self.canvas.mapToolSet.disconnect(self.initialize_tools)
        except TypeError as e:
            pass
        self.canvas.setMapTool(self.active_map_tool_before_custom)

    def download_image(self, url):
        res = False
        img = None
        msg = {'text': '', 'level': Qgis.Warning}
        if url:
            self.logger.info(__name__, "Downloading file from {}".format(url))
            msg = "Downloading image from document repository (this might take a while)..."
            with ProcessWithStatus(msg):
                if self.qgis_utils.is_connected(TEST_SERVER):

                    nam = QNetworkAccessManager()
                    request = QNetworkRequest(QUrl(url))
                    reply = nam.get(request)

                    loop = QEventLoop()
                    reply.finished.connect(loop.quit)
                    loop.exec_()

                    status = reply.attribute(
                        QNetworkRequest.HttpStatusCodeAttribute)
                    if status == 200:
                        res = True
                        img = reply.readAll()
                    else:
                        res = False
                        msg['text'] = QCoreApplication.translate(
                            "SettingsDialog",
                            "There was a problem connecting to the server. The server might be down or the service cannot be reached at the given URL."
                        )
                else:
                    res = False
                    msg['text'] = QCoreApplication.translate(
                        "SettingsDialog",
                        "There was a problem connecting to Internet.")

        else:
            res = False
            msg['text'] = QCoreApplication.translate("SettingsDialog",
                                                     "Not valid URL")

        if not res:
            self.logger.log_message(__name__, msg['text'], msg['level'])
        return (res, img)
class XTFModelConverterRegistry(metaclass=Singleton):
    """
    Registry of supported model converters.
    """
    def __init__(self):
        self.logger = Logger()
        self.app = AppInterface()
        self.__converters = dict()  # {converter_key1: LADMColModelConverter1, ...}

        # Register default models
        self.register_model_converter(Survey10To11Converter())
        self.register_model_converter(Survey11To10Converter())

    def register_model_converter(self, converter):
        """
        :param converter: LADMColModelConverter instance.
        :return: True if the converter was registered, False otherwise.
        """
        if not isinstance(converter, AbstractLADMColModelConverter):
            self.logger.warning(__name__, "The converter '{}' is not a 'LADMColModelConverter' instance!".format(converter.id()))
            return False

        if not converter.is_valid():
            self.logger.warning(__name__, "The converter '{}' is not valid! Check the converter definition!".format(converter.id()))
            return False

        if converter.id() in self.__converters:
            self.logger.warning(__name__, "The converter '{}' is already registered.".format(converter.id()))
            return False

        self.__converters[converter.id()] = converter
        self.logger.info(__name__, "Model converter '{}' has been registered!".format(converter.id()))

        return True

    def unregister_model_converter(self, converter_key):
        """
        Unregisters a model converter.

        :param converter_key: Id of the converter to unregister.
        :return: True if the converter was unregistered, False otherwise.
        """
        if converter_key not in self.__converters:
            self.logger.error(__name__, "Converter '{}' was not found in registered model converters, therefore, it cannot be unregistered!".format(converter_key))
            return False

        self.__converters[converter_key] = None
        del self.__converters[converter_key]
        self.logger.info(__name__, "Model converter '{}' has been unregistered!".format(converter_key))

        return True

    def get_converter(self, converter_key):
        converter = self.__converters.get(converter_key, None)  # To avoid exceptions
        if not converter:
            self.logger.critical(__name__, "No model converter found with key '{}'".format(converter_key))

        return converter

    def get_converters_for_models(self, models):
        converters = dict()  # {converter_key_1: converter_display_name_1, ...]
        for converter_key, converter in self.__converters.items():
            for model in models:
                if converter.supports_source_model(model):
                    converters[converter_key] = converter.display_name()

        return converters
コード例 #13
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()
コード例 #14
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!
コード例 #15
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))
コード例 #16
0
class ConnectionManager(QObject):
    """
    Access point to get and set DB Connectors used by the plugin.

    The plugin uses a DB Connector for Cadastral data collection (barrido) and one for the Supplies cadastral data.
    Other connections might be needed (e.g., while retrieving databases for the server in the settings dialog, but they
    are not handled by this class).
    """
    db_connection_changed = pyqtSignal(DBConnector, bool,
                                       str)  # dbconn, ladm_col_db, db_source

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

        self._db_sources = {  # Values are DB Connectors
            COLLECTED_DB_SOURCE: None,
            SUPPLIES_DB_SOURCE: None
        }
        self.encrypter_decrypter = EncrypterDecrypter()

    def update_db_connector_for_source(self, db_source=COLLECTED_DB_SOURCE):
        db_connection_engine = QSettings().value(
            'Asistente-LADM_COL/db/{db_source}/db_connection_engine'.format(
                db_source=db_source))

        if db_connection_engine:
            db_factory = self.dbs_supported.get_db_factory(
                db_connection_engine)
            dict_conn = db_factory.get_parameters_conn(db_source)
            db = db_factory.get_db_connector(dict_conn)
            db.open_connection()  # Open db connection
        else:
            # Use the default connector
            db_factory = self.dbs_supported.get_db_factory(
                self.dbs_supported.id_default_db)
            db = db_factory.get_db_connector(
            )  # When the connection parameters are not filled we use empty values

        self.set_db_connector_for_source(db, db_source)

    def get_db_connector_from_source(self, db_source=COLLECTED_DB_SOURCE):
        if self._db_sources[db_source] is None:
            # Obtain the connection of the database on demand
            self.update_db_connector_for_source(db_source)
        return self._db_sources[db_source]

    def set_db_connector_for_source(self,
                                    db_connector,
                                    db_source=COLLECTED_DB_SOURCE):
        try:
            if self._db_sources[db_source]:
                self._db_sources[db_source].close_connection()
            self._db_sources[db_source] = db_connector
        except:
            self.logger.info(
                __name__,
                QCoreApplication.translate(
                    "ConnectionManager",
                    "An error occurred while trying to close the connection."))

    def close_db_connections(self):
        for _db_source in self._db_sources:
            if self._db_sources[_db_source]:
                self._db_sources[_db_source].close_connection()

    def get_opened_db_connector_for_tests(self, db_engine, conn_dict):
        """
        This function is implemented for tests. Connection to non-LADM databases.
        """
        db_factory = self.dbs_supported.get_db_factory(db_engine)
        db = db_factory.get_db_connector(conn_dict)
        db.open_connection()

        return db

    def get_encrypted_db_connector(self, db_engine, conn_dict):
        """
        Receives encrypted connection parameters and returns a DB connector from them.

        :param db_engine: Example: 'pg' or 'gpkg'
        :param conn_dict: Connection dict with Encrypted values.
        :return: DB Connector object
        """
        decrypted_conn_dict = {}
        for k, v in conn_dict.items():
            decrypted_conn_dict[k] = self.encrypter_decrypter.decrypt_with_AES(
                v)

        db_factory = self.dbs_supported.get_db_factory(db_engine)
        db = db_factory.get_db_connector(decrypted_conn_dict)

        return db

    def save_parameters_conn(self, db, db_source):
        """
        Save db connection parameters from a DB connector to QSettings
        :param db: DB Connector
        :param db_source:
        """
        self.dbs_supported.get_db_factory(db.engine).save_parameters_conn(
            db.dict_conn_params, db_source)
        self.update_db_connector_for_source(
            db_source)  # Update db connection with new parameters
コード例 #17
0
class ImportFromExcelDialog(QDialog, DIALOG_UI):
    log_excel_show_message_emitted = pyqtSignal(str)

    def __init__(self, iface, db, qgis_utils, parent=None):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        self.iface = iface
        self._db = db
        self.qgis_utils = qgis_utils
        self.logger = Logger()
        self.help_strings = HelpStrings()
        self.log_dialog_excel_text_content = ""
        self.group_parties_exists = False
        self.names = self._db.names
        self._running_tool = False
        self.tool_name = QCoreApplication.translate(
            "ImportFromExcelDialog", "Import intermediate structure")

        self.fields = {
            EXCEL_SHEET_NAME_PLOT: [
                EXCEL_SHEET_TITLE_DEPARTMENT, EXCEL_SHEET_TITLE_MUNICIPALITY,
                EXCEL_SHEET_TITLE_ZONE, EXCEL_SHEET_TITLE_REGISTRATION_PLOT,
                EXCEL_SHEET_TITLE_NPN, EXCEL_SHEET_TITLE_NPV,
                EXCEL_SHEET_TITLE_PLOT_NAME, EXCEL_SHEET_TITLE_VALUATION,
                EXCEL_SHEET_TITLE_PLOT_CONDITION, EXCEL_SHEET_TITLE_PLOT_TYPE,
                EXCEL_SHEET_TITLE_ADDRESS
            ],
            EXCEL_SHEET_NAME_PARTY: [
                EXCEL_SHEET_TITLE_FIRST_NAME, EXCEL_SHEET_TITLE_MIDDLE,
                EXCEL_SHEET_TITLE_FIRST_SURNAME,
                EXCEL_SHEET_TITLE_SECOND_SURNAME,
                EXCEL_SHEET_TITLE_BUSINESS_NAME, EXCEL_SHEET_TITLE_SEX,
                EXCEL_SHEET_TITLE_DOCUMENT_TYPE,
                EXCEL_SHEET_TITLE_DOCUMENT_NUMBER,
                EXCEL_SHEET_TITLE_KIND_PERSON,
                EXCEL_SHEET_TITLE_ISSUING_ENTITY, EXCEL_SHEET_TITLE_DATE_ISSUE,
                EXCEL_SHEET_TITLE_NPN
            ],
            EXCEL_SHEET_NAME_GROUP: [
                EXCEL_SHEET_TITLE_NPN, EXCEL_SHEET_TITLE_DOCUMENT_TYPE,
                EXCEL_SHEET_TITLE_DOCUMENT_NUMBER, EXCEL_SHEET_TITLE_ID_GROUP
            ],
            EXCEL_SHEET_NAME_RIGHT: [
                EXCEL_SHEET_TITLE_TYPE,
                EXCEL_SHEET_TITLE_PARTY_DOCUMENT_NUMBER,
                EXCEL_SHEET_TITLE_GROUP, EXCEL_SHEET_TITLE_NPN,
                EXCEL_SHEET_TITLE_SOURCE_TYPE,
                EXCEL_SHEET_TITLE_DESCRIPTION_SOURCE,
                EXCEL_SHEET_TITLE_STATE_SOURCE,
                EXCEL_SHEET_TITLE_OFFICIALITY_SOURCE,
                EXCEL_SHEET_TITLE_STORAGE_PATH
            ]
        }

        self.txt_help_page.setHtml(self.help_strings.DLG_IMPORT_FROM_EXCEL)
        self.txt_help_page.anchorClicked.connect(self.save_template)

        self.buttonBox.accepted.disconnect()
        self.buttonBox.accepted.connect(self.accepted)
        #self.buttonBox.rejected.connect(self.rejected)
        self.buttonBox.helpRequested.connect(self.show_help)
        self.btn_browse_file.clicked.connect(
            make_file_selector(
                self.txt_excel_path,
                QCoreApplication.translate(
                    "ImportFromExcelDialog",
                    "Select the Excel file with data in the intermediate structure"
                ),
                QCoreApplication.translate("ImportFromExcelDialog",
                                           'Excel File (*.xlsx *.xls)')))
        self.buttonBox.button(QDialogButtonBox.Ok).setText(
            QCoreApplication.translate("ImportFromExcelDialog", "Import"))

        self.initialize_feedback()
        self.restore_settings()

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

    def accepted(self):
        self.save_settings()
        self.import_from_excel()

    def import_from_excel(self):
        self._running_tool = True
        steps = 18
        step = 0
        self.progress.setVisible(True)
        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)

        # Where to store the reports?
        excel_path = self.txt_excel_path.text()

        if not excel_path:
            self.show_message(
                QCoreApplication.translate(
                    "ImportFromExcelDialog",
                    "You need to select an Excel file before continuing with the import."
                ), Qgis.Warning)
            self.progress.setVisible(False)
            self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True)
            return

        if not os.path.exists(excel_path):
            self.show_message(
                QCoreApplication.translate(
                    "ImportFromExcelDialog",
                    "The specified Excel file does not exist!"), Qgis.Warning)
            self.progress.setVisible(False)
            self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True)
            return

        self.progress.setVisible(True)
        self.txt_log.setText(
            QCoreApplication.translate(
                "ImportFromExcelDialog",
                "Loading tables from the Excel file..."))

        # Now that we have the Excel file, build vrts to load its sheets appropriately
        # Also validate each layer against a number of rules
        layer_parcel = self.check_layer_from_excel_sheet(
            excel_path, EXCEL_SHEET_NAME_PLOT)
        layer_party = self.check_layer_from_excel_sheet(
            excel_path, EXCEL_SHEET_NAME_PARTY)
        layer_group_party = self.check_layer_from_excel_sheet(
            excel_path, EXCEL_SHEET_NAME_GROUP)
        layer_right = self.check_layer_from_excel_sheet(
            excel_path, EXCEL_SHEET_NAME_RIGHT)

        if layer_parcel is None or layer_party is None or layer_group_party is None or layer_right is None:
            # A layer is None if at least an error was found
            self.group_parties_exists = False
            self.log_excel_show_message_emitted.emit(
                self.log_dialog_excel_text_content)
            self.done(0)
            return

        if not layer_group_party.isValid() or not layer_party.isValid(
        ) or not layer_parcel.isValid() or not layer_right.isValid():
            self.show_message(
                QCoreApplication.translate(
                    "ImportFromExcelDialog",
                    "One of the sheets of the Excel file couldn't be loaded! Check the format again."
                ), Qgis.Warning)
            self.progress.setVisible(False)
            self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True)
            return

        QgsProject.instance().addMapLayers(
            [layer_group_party, layer_party, layer_parcel, layer_right])

        # GET LADM LAYERS
        layers = {
            self.names.OP_PARTY_T: {
                'name': self.names.OP_PARTY_T,
                'geometry': None,
                LAYER: None
            },
            self.names.OP_PARCEL_T: {
                'name': self.names.OP_PARCEL_T,
                'geometry': None,
                LAYER: None
            },
            self.names.OP_RIGHT_T: {
                'name': self.names.OP_RIGHT_T,
                'geometry': None,
                LAYER: None
            },
            self.names.EXT_ARCHIVE_S: {
                'name': self.names.EXT_ARCHIVE_S,
                'geometry': None,
                LAYER: None
            },
            self.names.COL_RRR_SOURCE_T: {
                'name': self.names.COL_RRR_SOURCE_T,
                'geometry': None,
                LAYER: None
            },
            self.names.OP_GROUP_PARTY_T: {
                'name': self.names.OP_GROUP_PARTY_T,
                'geometry': None,
                LAYER: None
            },
            self.names.MEMBERS_T: {
                'name': self.names.MEMBERS_T,
                'geometry': None,
                LAYER: None
            },
            self.names.OP_ADMINISTRATIVE_SOURCE_T: {
                'name': self.names.OP_ADMINISTRATIVE_SOURCE_T,
                'geometry': None,
                LAYER: None
            }
        }

        self.qgis_utils.get_layers(self._db, layers, load=True)
        if not layers:
            return None

        # Get feature counts to compare after the ETL and know how many records were imported to each ladm_col table
        ladm_tables = [
            layers[self.names.OP_PARCEL_T][LAYER],
            layers[self.names.OP_PARTY_T][LAYER],
            layers[self.names.OP_RIGHT_T][LAYER],
            layers[self.names.OP_ADMINISTRATIVE_SOURCE_T][LAYER],
            layers[self.names.COL_RRR_SOURCE_T][LAYER],
            layers[self.names.OP_GROUP_PARTY_T][LAYER],
            layers[self.names.MEMBERS_T][LAYER]
        ]
        ladm_tables_feature_count_before = {
            t.name(): t.featureCount()
            for t in ladm_tables
        }

        # Run the ETL
        params = {
            'agrupacion':
            layers[self.names.OP_GROUP_PARTY_T][LAYER],
            'colmiembros':
            layers[self.names.MEMBERS_T][LAYER],
            'colrrrsourcet':
            layers[self.names.COL_RRR_SOURCE_T][LAYER],
            'extarchivo':
            layers[self.names.EXT_ARCHIVE_S][LAYER],
            'interesado':
            layers[self.names.OP_PARTY_T][LAYER],
            'layergroupparty':
            layer_group_party,
            'layerparcel':
            layer_parcel,
            'layerparty':
            layer_party,
            'layerright':
            layer_right,
            'opderecho':
            layers[self.names.OP_RIGHT_T][LAYER],
            'opfuenteadministrativatipo':
            layers[self.names.OP_ADMINISTRATIVE_SOURCE_T][LAYER],
            'parcel':
            layers[self.names.OP_PARCEL_T][LAYER]
        }

        self.qgis_utils.disable_automatic_fields(self._db,
                                                 self.names.OP_GROUP_PARTY_T)
        self.qgis_utils.disable_automatic_fields(self._db,
                                                 self.names.OP_RIGHT_T)
        self.qgis_utils.disable_automatic_fields(
            self._db, self.names.OP_ADMINISTRATIVE_SOURCE_T)

        processing.run("model:ETL_intermediate_structure",
                       params,
                       feedback=self.feedback)

        if not self.feedback.isCanceled():
            self.progress.setValue(100)
            self.buttonBox.clear()
            self.buttonBox.setEnabled(True)
            self.buttonBox.addButton(QDialogButtonBox.Close)
        else:
            self.initialize_feedback()

        # Print summary getting feature count in involved LADM_COL tables...
        summary = """<html><head/><body><p>"""
        summary += QCoreApplication.translate("ImportFromExcelDialog",
                                              "Import done!!!<br/>")
        for table in ladm_tables:
            summary += QCoreApplication.translate(
                "ImportFromExcelDialog",
                "<br/><b>{count}</b> records loaded into table <b>{table}</b>"
            ).format(count=table.featureCount() -
                     ladm_tables_feature_count_before[table.name()],
                     table=table.name())

        summary += """</body></html>"""
        self.txt_log.setText(summary)
        self.logger.success_msg(
            __name__,
            QCoreApplication.translate(
                "QGISUtils",
                "Data successfully imported to LADM_COL from intermediate structure (Excel file: '{}')!!!"
            ).format(excel_path))
        self._running_tool = False

    def check_layer_from_excel_sheet(self, excel_path, sheetname):
        layer = self.get_layer_from_excel_sheet(excel_path, sheetname)
        error_counter = 0

        if layer is None and sheetname != EXCEL_SHEET_NAME_GROUP:  # optional sheet
            self.generate_message_excel_error(
                QCoreApplication.translate(
                    "ImportFromExcelDialog",
                    "The {} sheet has not information or has another name.").
                format(sheetname))
            error_counter += 1
        else:
            title_validator = layer.fields().toList()

        if sheetname == EXCEL_SHEET_NAME_PLOT and layer is not None:
            if not title_validator:
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The title does not match the format in the sheet {}."
                    ).format(sheetname))
                error_counter += 1
            if list(layer.getFeatures('"numero predial nuevo" is Null')):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column numero predial nuevo has empty values in sheet {}."
                    ).format(sheetname))
                error_counter += 1
            if not self.check_field_numeric_layer(layer, 'departamento'):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column departamento has non-numeric values in sheet {}."
                    ).format(sheetname))
                error_counter += 1
            if not self.check_field_numeric_layer(layer, 'municipio'):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column municipio has non-numeric values in sheet {}."
                    ).format(sheetname))
                error_counter += 1
            if not self.check_field_numeric_layer(layer,
                                                  'numero predial nuevo'):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column numero predial nuevo has non-numeric values in sheet {}."
                    ).format(sheetname))
                error_counter += 1

        if sheetname == EXCEL_SHEET_NAME_PARTY and layer is not None:
            if not title_validator:
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The title does not match the format in sheet {}.").
                    format(sheetname))
                error_counter += 1
            if list(layer.getFeatures('"tipo documento" is Null')):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column tipo documento has empty values in sheet {}."
                    ).format(sheetname))
                error_counter += 1
            if list(layer.getFeatures('"numero de documento" is Null')):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column numero de documento has empty values in sheet {}."
                    ).format(sheetname))
                error_counter += 1
            if not self.check_length_attribute_value(
                    layer, 'numero de documento', 12):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column numero de documento has more characters than expected in sheet {}."
                    ).format(sheetname))
                error_counter += 1
            if list(layer.getFeatures('"tipo persona" is Null')):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column tipo persona has empty values in sheet {}."
                    ).format(sheetname))
                error_counter += 1

        if sheetname == EXCEL_SHEET_NAME_GROUP and layer is not None:
            if not title_validator:
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The title does not match the format in the sheet {}."
                    ).format(sheetname))
                error_counter += 1
            self.group_parties_exists = True
            if list(layer.getFeatures('"numero predial nuevo" is Null')):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column numero predial nuevo has empty values in sheet {}."
                    ).format(sheetname))
                error_counter += 1
            if list(layer.getFeatures('"tipo documento" is Null')):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column tipo documento has empty values in sheet {}."
                    ).format(sheetname))
                error_counter += 1
            if list(layer.getFeatures('"numero de documento" is Null')):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column numero de documento has empty values in sheet {}."
                    ).format(sheetname))
                error_counter += 1
            if list(layer.getFeatures('"id agrupación" is Null')):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column id agrupación has empty values in sheet {}."
                    ).format(sheetname))
                error_counter += 1
            if not self.check_length_attribute_value(
                    layer, 'numero de documento', 12):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column numero de documento has more characters of the permitted in sheet {}."
                    ).format(sheetname))
                error_counter += 1

        if sheetname == EXCEL_SHEET_NAME_RIGHT and layer is not None:
            if not title_validator:
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The title does not match the format in sheet {}.").
                    format(sheetname))
                error_counter += 1
            if list(layer.getFeatures('"tipo" is Null')):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column tipo has empty values in sheet {}.").
                    format(sheetname))
                error_counter += 1
            if list(layer.getFeatures('"tipo de fuente" is Null')):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column tipo de fuente has empty values in sheet {}."
                    ).format(sheetname))
                error_counter += 1
            if list(
                    layer.getFeatures(
                        '"estado_disponibilidad de la fuente" is Null')):
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "The column estado_disponibilidad de la fuente has empty values in sheet {}."
                    ).format(sheetname))
                error_counter += 1
            #if list(layer.getFeatures('"Ruta de Almacenamiento de la fuente" is Null')):
            #    self.generate_message_excel_error(QCoreApplication.translate("ImportFromExcelDialog",
            #            "The column Ruta de Almacenamiento de la fuente has empty values in sheet {}.").format(sheetname))
            #    error_counter += 1
            if len(
                    list(
                        layer.getFeatures(
                            '"número documento Interesado" is Null'))) + len(
                                list(layer.getFeatures('"agrupación" is Null'))
                            ) != layer.featureCount():
                self.generate_message_excel_error(
                    QCoreApplication.translate(
                        "ImportFromExcelDialog",
                        "Number of non-null parties plus number of non-null group parties is not equal to number of records in sheet {}. There might be rights without party or group party associated."
                    ).format(sheetname))
                error_counter += 1
            if not self.group_parties_exists:
                if list(
                        layer.getFeatures(
                            '"número documento Interesado" is Null')):
                    self.generate_message_excel_error(
                        QCoreApplication.translate(
                            "ImportFromExcelDialog",
                            "The column número documento Interesado has empty values in sheet {}."
                        ).format(sheetname))
                    error_counter += 1
                if len(list(layer.getFeatures(
                        '"agrupacion" is Null'))) != layer.featureCount():
                    self.generate_message_excel_error(
                        QCoreApplication.translate(
                            "ImportFromExcelDialog",
                            "The column agrupacion has data but the sheet does not exist in sheet {}."
                        ).format(sheetname))
                    error_counter += 1

        return layer if error_counter == 0 else None

    def check_field_numeric_layer(self, layer, name):
        id_field_idx = layer.fields().indexFromName(name)
        request = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx])
        features = layer.getFeatures(request)
        is_numeric = True

        for feature in features:
            try:
                int(feature[name])
            except:
                is_numeric = False
                break

        return is_numeric

    def check_length_attribute_value(self, layer, name, size):
        id_field_idx = layer.fields().indexFromName(name)
        request = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx])
        features = layer.getFeatures(request)
        right_length = True

        for feature in features:
            if len(str(feature[name])) > size:
                right_length = False
                break

        return right_length

    def generate_message_excel_error(self, msg):
        self.log_dialog_excel_text_content += "{}{}{}{}{}{}".format(
            LOG_QUALITY_LIST_CONTAINER_OPEN, LOG_QUALITY_LIST_ITEM_ERROR_OPEN,
            msg, LOG_QUALITY_LIST_ITEM_ERROR_CLOSE,
            LOG_QUALITY_LIST_CONTAINER_CLOSE, LOG_QUALITY_CONTENT_SEPARATOR)

    def get_layer_from_excel_sheet(self, excel_path, sheetname):
        basename = os.path.basename(excel_path)
        filename = os.path.splitext(basename)[0]
        dirname = os.path.dirname(excel_path)

        header_in_first_row, count = self.get_excel_info(excel_path, sheetname)
        if header_in_first_row is None and count is None:
            return None

        layer_definition = "<SrcLayer>{sheetname}</SrcLayer>".format(
            sheetname=sheetname)
        if header_in_first_row:
            layer_definition = """<SrcSql dialect="sqlite">SELECT * FROM '{sheetname}' LIMIT {count} OFFSET 1</SrcSql>""".format(
                sheetname=sheetname, count=count)
        xml_text_group_party = """<?xml version="1.0" encoding="UTF-8"?>
                    <OGRVRTDataSource>
                        <OGRVRTLayer name="{filename}-{sheetname}">
                            <SrcDataSource relativeToVRT="1">{basename}</SrcDataSource>
                            <!--Header={header}-->
                            {layer_definition}
                            {fields}
                        </OGRVRTLayer>            
                    </OGRVRTDataSource>
                """.format(filename=filename,
                           basename=basename,
                           header=header_in_first_row,
                           layer_definition=layer_definition,
                           sheetname=sheetname,
                           fields=self.get_vrt_fields(sheetname,
                                                      header_in_first_row))

        group_party_file_path = os.path.join(
            dirname, '{}.{}.vrt'.format(basename, sheetname))
        with open(group_party_file_path, 'w') as sheet:
            sheet.write(xml_text_group_party)

        uri = '{vrtfilepath}|layername={filename}-{sheetname}'.format(
            vrtfilepath=group_party_file_path,
            sheetname=sheetname,
            filename=filename)

        self.logger.info(__name__,
                         "Loading layer from excel with uri='{}'".format(uri))
        layer = QgsVectorLayer(uri, '{}-{}'.format('excel', sheetname), 'ogr')
        layer.setProviderEncoding('UTF-8')
        return layer

    def get_excel_info(self, path, sheetname):
        data_source = ogr.Open(path, 0)
        layer = data_source.GetLayerByName(sheetname)

        if layer is None:
            # A sheetname couldn't be found
            return None, None

        feature = layer.GetNextFeature()

        # If ogr recognizes the header, the first row will contain data, otherwise it'll contain field names
        header_in_first_row = True
        for field in self.fields[sheetname]:
            if feature.GetField(self.fields[sheetname].index(field)) != field:
                header_in_first_row = False

        num_rows = layer.GetFeatureCount()
        return header_in_first_row, num_rows - 1 if header_in_first_row else num_rows

    def get_vrt_fields(self, sheetname, header_in_first_row):
        vrt_fields = ""
        for index, field in enumerate(self.fields[sheetname]):
            vrt_fields += """<Field name="{field}" src="{src}" type="String"/>\n""".format(
                field=field,
                src='Field{}'.format(index +
                                     1) if header_in_first_row else field)

        return vrt_fields.strip()

    def save_template(self, url):
        link = url.url()
        if link == '#template':
            self.download_excel_file('plantilla_estructura_excel.xlsx')
        elif link == '#data':
            self.download_excel_file('datos_estructura_excel.xlsx')

    def download_excel_file(self, filename):
        settings = QSettings()

        new_filename, filter = QFileDialog.getSaveFileName(
            self,
            QCoreApplication.translate("ImportFromExcelDialog", "Save File"),
            os.path.join(
                settings.value(
                    'Asistente-LADM_COL/import_from_excel_dialog/template_save_path',
                    '.'), filename),
            QCoreApplication.translate("ImportFromExcelDialog",
                                       "Excel File (*.xlsx *.xls)"))

        if new_filename:
            settings.setValue(
                'Asistente-LADM_COL/import_from_excel_dialog/template_save_path',
                os.path.dirname(new_filename))
            template_file = QFile(":/Asistente-LADM_COL/resources/excel/" +
                                  filename)

            if not template_file.exists():
                self.logger.critical(
                    __name__,
                    "Excel doesn't exist! Probably due to a missing 'make' execution to generate resources..."
                )
                msg = QCoreApplication.translate(
                    "ImportFromExcelDialog",
                    "Excel file not found. Update your plugin. For details see log."
                )
                self.show_message(msg, Qgis.Warning)
                return

            if os.path.isfile(new_filename):
                self.logger.info(
                    __name__,
                    'Removing existing file {}...'.format(new_filename))
                os.chmod(new_filename, 0o777)
                os.remove(new_filename)

            if template_file.copy(new_filename):
                os.chmod(
                    new_filename,
                    stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
                msg = QCoreApplication.translate(
                    "ImportFromExcelDialog",
                    """The file <a href="file:///{}">{}</a> was successfully saved!"""
                ).format(normalize_local_url(new_filename),
                         os.path.basename(new_filename))
                self.show_message(msg, Qgis.Info)
            else:
                self.logger.info(
                    __name__,
                    'There was an error copying the CSV file {}!'.format(
                        new_filename))
                msg = QCoreApplication.translate(
                    "ImportFromExcelDialog", "The file couldn\'t be saved.")
                self.show_message(msg, Qgis.Warning)

    def reject(self):
        self.selected_items_dict = dict()

        if self._running_tool:
            reply = QMessageBox.question(
                self, QCoreApplication.translate("import_from_excel",
                                                 "Warning"),
                QCoreApplication.translate(
                    "import_from_excel",
                    "The '{}' tool is still running. Do you want to cancel it? If you cancel, the data might be incomplete in the target database."
                ).format(self.tool_name), QMessageBox.Yes, QMessageBox.No)

            if reply == QMessageBox.Yes:
                self.feedback.cancel()
                self._running_tool = False
                msg = QCoreApplication.translate(
                    "import_from_excel",
                    "The '{}' tool was cancelled.").format(self.tool_name)
                self.logger.info(__name__, msg)
                self.show_message(msg, Qgis.Info)
        else:
            self.logger.info(__name__, "Dialog closed.")
            self.done(1)

    def save_settings(self):
        settings = QSettings()
        settings.setValue(
            'Asistente-LADM_COL/import_from_excel_dialog/excel_path',
            self.txt_excel_path.text())

    def restore_settings(self):
        settings = QSettings()
        self.txt_excel_path.setText(
            settings.value(
                'Asistente-LADM_COL/import_from_excel_dialog/excel_path', ''))

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

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

    def progress_changed(self):
        QCoreApplication.processEvents()  # Listen to cancel from the user
        self.progress.setValue(self.feedback.progress())

    def initialize_feedback(self):
        self.progress.setValue(0)
        self.progress.setVisible(False)
        self.feedback = QgsProcessingFeedback()
        self.feedback.progressChanged.connect(self.progress_changed)
        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True)
コード例 #18
0
class TasksWidget(QWidget, WIDGET_UI):
    task_panel_requested = pyqtSignal(int)  # Selected task_id

    def __init__(self, user):
        QWidget.__init__(self)
        self.setupUi(self)
        self.logger = Logger()
        self.session = STSession()
        self._user = user
        self.st_config = TransitionalSystemConfig()

        self.lvw_tasks.itemSelectionChanged.connect(self.update_controls)
        self.lvw_tasks.itemDoubleClicked.connect(self.call_task_panel)
        self.btn_view_task.clicked.connect(self.view_task)
        self.btn_close_task.clicked.connect(self.close_task)

        self.update_controls()  # Initialize controls

    def show_tasks(self):
        self.clear_task_widget()
        tasks = self._get_user_tasks()
        self.update_task_count_label(len(tasks))
        for k, task in tasks.items():
            self.add_task_widget_item_to_view(task)

    def _get_user_tasks(self):
        return self.session.task_manager.get_tasks(self._user)

    def update_controls(self):
        selected_items = self.lvw_tasks.selectedItems()
        enable = len(selected_items) == 1
        self.btn_view_task.setEnabled(enable)

        if enable:
            # Enable Close Task button?
            task = self.session.task_manager.get_task(selected_items[0].data(
                Qt.UserRole))
            if task:
                enable = task.get_status(
                ) == EnumSTTaskStatus.STARTED.value and task.steps_complete()

        self.btn_close_task.setEnabled(enable)

        for index in range(self.lvw_tasks.count()):
            # Update every single task item with a proper style
            item = self.lvw_tasks.item(index)
            self.set_task_item_style(self.lvw_tasks.itemWidget(item),
                                     item.isSelected())

    def update_task_count_label(self, count):
        self.lbl_task_count.setText(
            QCoreApplication.translate("TasksWidget",
                                       "{} pending task{}").format(
                                           count, "s" if count > 1 else ""))

    def view_task(self):
        items = self.lvw_tasks.selectedItems()
        if items:
            self.call_task_panel(items[0])

    def call_task_panel(self, item):
        task_id = item.data(Qt.UserRole)
        self.logger.info(__name__, "View task (id:{})".format(task_id))
        self.task_panel_requested.emit(task_id)

    def close_task(self):
        items = self.lvw_tasks.selectedItems()
        if items:
            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:
                task_id = items[0].data(Qt.UserRole)
                self.session.task_manager.close_task(self._user, task_id)

    def add_task_widget_item_to_view(self, task):
        widget_item = loadUi(
            get_ui_file_path('transitional_system/task_widget_item.ui'),
            QWidget())
        widget_item.lbl_name.setText(task.get_name())
        widget_item.lbl_description.setText(task.get_description())
        widget_item.lbl_status.setText(task.get_status())
        widget_item.lbl_created_at.setText(
            QCoreApplication.translate("TaskPanelWidget",
                                       "Created at: {}").format(
                                           task.get_creation_date()))
        widget_item.lbl_deadline.setText(
            QCoreApplication.translate(
                "TaskPanelWidget", "Deadline: {}").format(task.get_deadline()))

        self.set_task_item_style(widget_item)
        item = QListWidgetItem(self.lvw_tasks)
        self.lvw_tasks.setItemWidget(item, widget_item)
        item.setSizeHint(QSize(widget_item.width(), widget_item.height()))
        item.setData(Qt.UserRole, task.get_id())
        self.lvw_tasks.addItem(item)

    def set_task_item_style(self, widget, selected=False):
        title_text = self.st_config.TASK_TITLE_TEXT_CSS
        normal_text = self.st_config.TASK_NORMAL_TEXT_CSS
        date_text = self.st_config.TASK_DATE_TEXT_CSS
        if widget.lbl_status.text() == EnumSTTaskStatus.ASSIGNED.value:
            status_text = self.st_config.TASK_ASSIGNED_STATUS_TEXT_CSS
        elif widget.lbl_status.text() == EnumSTTaskStatus.STARTED.value:
            status_text = self.st_config.TASK_STARTED_STATUS_SELECTED_TEXT_CSS if selected else self.st_config.TASK_STARTED_STATUS_TEXT_CSS

        if selected:
            title_text = self.st_config.TASK_TITLE_SELECTED_TEXT_CSS
            normal_text = self.st_config.TASK_NORMAL_SELECTED_TEXT_CSS
            date_text = self.st_config.TASK_DATE_SELECTED_TEXT_CSS

        widget.lbl_name.setStyleSheet(title_text)
        widget.lbl_description.setStyleSheet(normal_text)
        widget.lbl_status.setStyleSheet(status_text)
        widget.lbl_created_at.setStyleSheet(date_text)
        widget.lbl_deadline.setStyleSheet(date_text)

    def clear_task_widget(self):
        self.lvw_tasks.clear()

    def logout_user(self):
        """
        If the user logged out, clear everything
        """
        self.clear_task_widget()
        self._user = None
        self.setDisabled(True)
コード例 #19
0
class STSession(QObject, metaclass=SingletonQObject):
    TOKEN_KEY = "Asistente-LADM_COL/transitional_system/token"

    login_status_changed = pyqtSignal(
        bool
    )  # Status of the login: True if a user is logged in, False otherwise
    logout_finished = pyqtSignal()

    def __init__(self):
        QObject.__init__(self)
        self.logger = Logger()
        self.task_manager = STTaskManager()
        self.__logged_user = None

    def login(self, user, password):
        msg = ""
        st_config = TransitionalSystemConfig()
        payload = st_config.ST_LOGIN_SERVICE_PAYLOAD.format(user, password)
        headers = {
            'Content-Type': "application/x-www-form-urlencoded",
            'Authorization': st_config.ST_LOGIN_AUTHORIZATION_CLIENT,
            'Accept': "*/*",
            'Cache-Control': "no-cache",
            'Accept-Encoding': "gzip, deflate",
            'Connection': "keep-alive",
            'cache-control': "no-cache"
        }
        s = requests.Session()
        s.mount(st_config.ST_LOGIN_SERVICE_URL, HTTPAdapter(max_retries=0))

        try:
            response = s.request("POST",
                                 st_config.ST_LOGIN_SERVICE_URL,
                                 data=payload,
                                 headers=headers)
        except requests.ConnectionError as e:
            msg = QCoreApplication.translate(
                "STSession",
                "There was an error accessing the login service. Details: {}"
            ).format(e)
            self.logger.warning(__name__, msg)
            return False, msg

        status_OK = response.status_code == 200
        self.logger.info(
            __name__,
            "Login response status code: {}".format(response.status_code))
        if status_OK:
            msg = QCoreApplication.translate(
                "STSession",
                "User logged in successfully in the Transitional System!")
            logged_data = json.loads(response.text)
            self.__logged_user = STLoggedUser(
                "{} {}".format(logged_data['first_name'],
                               logged_data['last_name']), logged_data['email'],
                logged_data['roles'][0]['name'], logged_data['access_token'])
            QSettings().setValue(
                self.TOKEN_KEY,
                logged_data['access_token'])  # Register (login) the user
            self.login_status_changed.emit(True)
            self.logger.info(__name__, msg)
        else:
            if response.status_code == 400:
                msg = QCoreApplication.translate(
                    "STSession",
                    "Wrong user name or password, change credentials and try again."
                )
            elif response.status_code == 500:
                msg = QCoreApplication.translate(
                    "STSession", "There is an error in the login server!")
            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 = QCoreApplication.translate(
                    "STSession",
                    "Unauthorized client! The server won't allow requests from this client."
                )
            self.logger.warning(__name__, msg)

        return status_OK, msg

    def logout(self):
        msg = ""
        logged_out = False
        if self.is_user_logged():
            QSettings().setValue(self.TOKEN_KEY,
                                 "")  # Unregister (logout) the user
            self.__logged_user = None
            logged_out = True
            self.login_status_changed.emit(False)
            self.logout_finished.emit()
            self.task_manager.unregister_tasks()
            msg = QCoreApplication.translate(
                "STSession", "User was logged out successfully!")
        else:
            msg = QCoreApplication.translate(
                "STSession",
                "There was not logged in user! Therefore, no logout.")

        self.logger.info(__name__, msg)
        return logged_out, msg

    def get_logged_st_user(self):
        return self.__logged_user

    def get_logged_role(self):
        return self.__logged_user.get_role(
        ) if self.__logged_user is not None else None

    def is_user_logged(self):
        return self.__logged_user is not None
コード例 #20
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.base_configuration = BaseConfiguration()
        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.ilicache = IliCache(self.base_configuration)
        self.ilicache.refresh()

        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);;Catalogue 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.supported_models():
                    if not model.hidden() and 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

        configuration = self.update_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

        with OverrideCursor(Qt.WaitCursor):
            self.progress_bar.show()

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

            dataImporter = iliimporter.Importer(dataImport=True)

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

            dataImporter.tool = db_factory.get_model_baker_db_ili_mode()
            dataImporter.configuration = configuration

            self.save_configuration(configuration)

            dataImporter.stdout.connect(self.print_info)
            dataImporter.stderr.connect(self.on_stderr)
            dataImporter.process_started.connect(self.on_process_started)
            dataImporter.process_finished.connect(self.on_process_finished)

            self.progress_bar.setValue(25)

            try:
                if dataImporter.run() != iliimporter.Importer.SUCCESS:
                    self._running_tool = False
                    self.show_message(
                        QCoreApplication.translate(
                            "DialogImportData",
                            "An error occurred when importing the data. For more information see the log..."
                        ), Qgis.Warning)
                    return
            except JavaNotFoundError:
                self._running_tool = False
                error_msg_java = QCoreApplication.translate(
                    "DialogImportData",
                    "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(error_msg_java)
                self.show_message(error_msg_java, Qgis.Warning)
                return

            self._running_tool = False
            self.buttonBox.clear()
            self.buttonBox.setEnabled(True)
            self.buttonBox.addButton(QDialogButtonBox.Close)
            self.progress_bar.setValue(100)
            self.show_message(
                QCoreApplication.translate(
                    "DialogImportData",
                    "Import of the data was successfully completed"),
                Qgis.Success)

    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 = 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 update_configuration(self):
        """
        Get the configuration that is updated with the user configuration changes on the dialog.
        :return: Configuration
        """
        db_factory = self._dbs_supported.get_db_factory(self.db.engine)

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

        configuration.xtffile = self.xtf_file_line_edit.text().strip()
        configuration.delete_data = False

        configuration.srs_auth = QSettings().value(
            'Asistente-LADM-COL/QgisModelBaker/srs_auth', DEFAULT_SRS_AUTH,
            str)
        configuration.srs_code = QSettings().value(
            'Asistente-LADM-COL/QgisModelBaker/srs_code',
            int(DEFAULT_SRS_CODE), int)
        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
        configuration.with_importtid = True

        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_ili_models():
            configuration.ilimodels = ';'.join(self.get_ili_models())

        configuration.disable_validation = not QSettings().value(
            'Asistente-LADM-COL/models/validate_data_importing_exporting',
            True, bool)

        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)
コード例 #21
0
class SuppliesETLWizard(QWizard, WIZARD_UI):
    on_result = pyqtSignal(
        bool)  # whether the tool was run successfully or not

    def __init__(self, db, conn_manager, parent=None):
        QWizard.__init__(self, parent)
        self.setupUi(self)
        self._db = db
        self.conn_manager = conn_manager
        self.parent = parent

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

        self.names = self._db.names
        self.help_strings = HelpStrings()
        self._data_source_widget = None
        self.db_source = SUPPLIES_DB_SOURCE
        self.tool_name = ""
        self._running_tool = False
        self._db_was_changed = False  # To postpone calling refresh gui until we close this dialog instead of settings
        self.progress_configuration(0, 1)  # start from: 0, number of steps: 1

        self.wizardPage2.setButtonText(
            QWizard.CustomButton1,
            QCoreApplication.translate("SuppliesETLWizard", "Run ETL"))
        self.wizardPage1.setButtonText(
            QWizard.CancelButton,
            QCoreApplication.translate("SuppliesETLWizard", "Close"))
        self.wizardPage2.setButtonText(
            QWizard.CancelButton,
            QCoreApplication.translate("SuppliesETLWizard", "Close"))

        # Auxiliary data to set nonlinear next pages
        self.pages = [self.wizardPage1, self.wizardPage2, self.wizardPage3]
        self.dict_pages_ids = {
            self.pages[idx]: pid
            for idx, pid in enumerate(self.pageIds())
        }

        # Set connections
        self.rad_snc_data.toggled.connect(self.etl_option_changed)
        self.etl_option_changed()  # Initialize it
        self.button(QWizard.CustomButton1).clicked.connect(
            self.import_button_clicked)
        self.button(QWizard.HelpButton).clicked.connect(self.show_help)
        self.currentIdChanged.connect(self.current_page_changed)
        self.finished.connect(self.finished_slot)
        self.btn_browse_connection.clicked.connect(self.show_settings)

        # Initialize
        self.current_page_changed(1)
        self.update_connection_info()
        self.restore_settings()
        self.initialize_feedback()

        # Set MessageBar for QWizard
        self.bar = QgsMessageBar()
        self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.setLayout(QGridLayout())
        self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop)

    def current_page_changed(self, id):
        """
        Reset the Next button. Needed because Next might have been disabled by a
        condition in a another SLOT.
        """
        #enable_next_wizard(self)
        button_list = [
            QWizard.HelpButton, QWizard.Stretch, QWizard.BackButton,
            QWizard.CustomButton1, QWizard.NextButton, QWizard.FinishButton,
            QWizard.CancelButton
        ]
        not_visible = []

        if id == self.dict_pages_ids[self.wizardPage1]:
            self.setWindowTitle(
                QCoreApplication.translate("SuppliesETLWizard",
                                           "Run supplies ETL"))
            button_list.remove(QWizard.BackButton)
            button_list.remove(QWizard.CustomButton1)
            button_list.remove(QWizard.FinishButton)
        elif id == self.dict_pages_ids[self.wizardPage2]:
            button_list.remove(QWizard.FinishButton)
            not_visible.append(self.NextButton)
            self.load_data_source_controls()
            if self.rad_snc_data.isChecked():
                self.setWindowTitle(
                    QCoreApplication.translate("SuppliesETLWizard",
                                               "ETL: SNC to Supplies model"))
            else:
                self.setWindowTitle(
                    QCoreApplication.translate("SuppliesETLWizard",
                                               "ETL: Cobol to Supplies model"))
        elif id == self.dict_pages_ids[self.wizardPage3]:
            self.bar.clearWidgets()
            button_list.remove(QWizard.CustomButton1)
            button_list.remove(QWizard.NextButton)
            button_list.remove(QWizard.BackButton)
            self.wizardPage3.setFinalPage(True)

        self.setButtonLayout(button_list)
        for button in not_visible:
            self.button(button).setVisible(False)

    def etl_option_changed(self):
        """
        Adjust help, names and titles according to the selected option
        """
        if self.rad_snc_data.isChecked():
            self.tool_name = QCoreApplication.translate(
                "SuppliesETLWizard", "ETL-SNC")
            self.txt_help_page_2.setHtml(
                self.help_strings.WIZ_SUPPLIES_ETL_PAGE_2.format("del SNC"))
        elif self.rad_cobol_data.isChecked():  # self.rad_cobol_data is checked
            self.tool_name = QCoreApplication.translate(
                "SuppliesETLWizard", "ETL-Cobol")
            self.txt_help_page_2.setHtml(
                self.help_strings.WIZ_SUPPLIES_ETL_PAGE_2.format("de Cobol"))

    def load_data_source_controls(self):
        self.clear_data_source_widget()
        if self.rad_snc_data.isChecked():
            self._data_source_widget = SNCDataSourceWidget()
        else:  # Cobol
            self._data_source_widget = CobolDataSourceWidget()

        self._data_source_widget.input_data_changed.connect(
            self.set_import_button_enabled)
        self._data_source_widget.emit_input_data_changed(
        )  # Initialize input validation
        self.data_source_layout.addWidget(self._data_source_widget)
        self._data_source_widget.setVisible(True)

    def clear_data_source_widget(self):
        while self.data_source_layout.count():
            child = self.data_source_layout.takeAt(0)
            if child.widget():
                child.widget().setVisible(False)

    def initialize_feedback(self):
        self.progress.setValue(0)
        self.progress.setVisible(False)
        self.custom_feedback = CustomFeedback()
        self.custom_feedback.progressChanged.connect(self.progress_changed)
        self.set_gui_controls_enabled(True)

    def progress_configuration(self, base, num_process):
        """
        :param base: Where to start counting from
        :param num_process: Number of steps
        """
        self.progress_base = base
        self.progress_maximum = 100 * num_process
        self.progress.setMaximum(self.progress_maximum)

    def progress_changed(self):
        QCoreApplication.processEvents()  # Listen to cancel from the user
        self.progress.setValue(self.progress_base +
                               self.custom_feedback.progress())

    def set_gui_controls_enabled(self, enable):
        self.gbx_data_source.setEnabled(enable)
        self.target_data.setEnabled(enable)
        self.set_import_button_enabled(enable)

    def set_import_button_enabled(self, enable):
        self.button(QWizard.CustomButton1).setEnabled(enable)

    def import_button_clicked(self):
        self.bar.clearWidgets()
        self.save_settings()
        etl_result = False

        if self.rad_snc_data.isChecked():
            etl = ETLSNC(self.names, self._data_source_widget)
        else:  # Cobol
            etl = ETLCobol(self.names, self._data_source_widget)

        if self._db.test_connection()[0]:
            reply = QMessageBox.question(
                self, QCoreApplication.translate("SuppliesETLWizard",
                                                 "Warning"),
                QCoreApplication.translate(
                    "SuppliesETLWizard",
                    "The database <i>{}</i> already has a valid LADM-COL structure.<br/><br/>If such database has any data, loading data into it might cause invalid data.<br/><br/>Do you still want to continue?"
                ).format(self._db.get_description_conn_string()),
                QMessageBox.Yes, QMessageBox.No)

            if reply == QMessageBox.Yes:
                self.set_gui_controls_enabled(False)
                self.button(self.BackButton).setEnabled(False)
                self.button(self.CustomButton1).setEnabled(False)
                self.button(self.CancelButton).setText(
                    QCoreApplication.translate("SuppliesETLWizard", "Cancel"))

                with OverrideCursor(Qt.WaitCursor):
                    res_alpha, msg_alpha = etl.load_alphanumeric_layers()

                    if res_alpha:
                        res_spatial, msg_spatial = etl.load_spatial_layers()

                        if res_spatial:
                            res_model, msg_model = self.load_model_layers(
                                etl.layers)

                            if res_model:
                                layers_feature_count_before = {
                                    name: layer.featureCount()
                                    for name, layer in etl.layers.items()
                                }
                                self._running_tool = True
                                self.progress.setVisible(True)
                                res_etl_model = etl.run_etl_model(
                                    self.custom_feedback)
                                if not self.custom_feedback.isCanceled(
                                ) and res_etl_model:
                                    self.progress.setValue(100)

                                    self.button(
                                        self.NextButton).setVisible(True)
                                    self.button(
                                        self.CustomButton1).setVisible(False)
                                    self.button(self.CancelButton).setText(
                                        QCoreApplication.translate(
                                            "SuppliesETLWizard", "Close"))
                                    self.show_message(
                                        QCoreApplication.translate(
                                            "SuppliesETLWizard",
                                            "The {} has finished successfully!"
                                        ).format(self.tool_name), Qgis.Success,
                                        0)

                                    self.logger.clear_status()
                                    self.fill_summary(
                                        layers_feature_count_before,
                                        etl.layers)
                                    etl_result = True
                                else:
                                    self.initialize_feedback(
                                    )  # Get ready for an eventual new execution
                                    self.logger.clear_status()
                                self._running_tool = False
                            else:
                                self.show_message(msg_model, Qgis.Warning)
                        else:
                            self.show_message(msg_spatial, Qgis.Warning)
                    else:
                        self.show_message(msg_alpha, Qgis.Warning)
        else:
            with OverrideCursor(Qt.WaitCursor):
                # TODO: if an empty schema was selected, do the magic under the hood
                # self.create_model_into_database()
                # Now execute "accepted()"
                msg = QCoreApplication.translate(
                    "SuppliesETLWizard",
                    "To run the ETL, the database (schema) should have the Supplies LADM-COL structure. Choose a proper database (schema) and try again."
                )
                self.show_message(msg, Qgis.Warning)
                self.logger.warning(__name__, msg)

        self.on_result.emit(
            etl_result)  # Inform other classes if the execution was successful

    def reject(self):
        if self._running_tool:
            reply = QMessageBox.question(
                self, QCoreApplication.translate("SuppliesETLWizard",
                                                 "Warning"),
                QCoreApplication.translate(
                    "SuppliesETLWizard",
                    "The '{}' tool is still running. Do you want to cancel it? If you cancel, the data might be incomplete in the target database."
                ).format(self.tool_name), QMessageBox.Yes, QMessageBox.No)

            if reply == QMessageBox.Yes:
                self.custom_feedback.cancel()
                self._running_tool = False
                msg = QCoreApplication.translate(
                    "SuppliesETLWizard",
                    "The '{}' tool was cancelled.").format(self.tool_name)
                self.logger.info(__name__, msg)
                self.show_message(msg, Qgis.Info)
        else:
            if self._db_was_changed:
                self.conn_manager.db_connection_changed.emit(
                    self._db,
                    self._db.test_connection()[0], self.db_source)
            self.logger.info(__name__, "Dialog closed.")
            self.app.settings.set_setting(
                self.app.settings.COBOL_FILES_DIR_KEY, '')
            self.app.settings.set_setting(self.app.settings.SNC_FILES_DIR_KEY,
                                          '')
            self.done(1)

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

    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 fill_summary(self, layers_feature_count_before, etl_layers):
        layers_feature_count_after = {
            name: layer.featureCount()
            for name, layer in etl_layers.items()
        }
        summary = """<html><head/><body><p>"""
        summary += QCoreApplication.translate(
            "SuppliesETLWizard", "<h4>{} report</h4>").format(self.tool_name)
        summary += QCoreApplication.translate(
            "SuppliesETLWizard",
            "Number of features loaded to the LADM-COL cadastral supplies model:<br/>"
        )

        for name, before_count in layers_feature_count_before.items():
            summary += QCoreApplication.translate(
                "SuppliesETLWizard", '<br/><b>{}</b> : {}'.format(
                    name, layers_feature_count_after[name] - before_count))

        summary += """<hr>"""
        summary += """</body></html>"""
        self.txt_log.setText(summary)

    def save_settings(self):
        settings = QSettings()
        etl_source = "snc"
        if self.rad_snc_data.isChecked():
            etl_source = "snc"
        elif self.rad_cobol_data.isChecked():
            etl_source = "cobol"

        settings.setValue('Asistente-LADM-COL/supplies/etl_source', etl_source)
        self._data_source_widget.save_settings()

        # In the main page (source-target configuration), save if splitter is closed
        self.app.settings.etl_splitter_collapsed = self.splitter_2.sizes(
        )[1] == 0

    def restore_settings(self):
        settings = QSettings()
        etl_source = settings.value(
            'Asistente-LADM-COL/supplies/etl_source') or 'snc'
        if etl_source == 'snc':
            self.rad_snc_data.setChecked(True)
        elif etl_source == 'cobol':
            self.rad_cobol_data.setChecked(True)

        # If splitter in the main page was closed before, set it as closed again
        if self.app.settings.etl_splitter_collapsed:
            sizes = self.splitter_2.sizes()
            self.splitter_2.setSizes([sizes[0], 0])

    def show_help(self):
        show_plugin_help('supplies')

    def show_settings(self):
        dlg = SettingsDialog(self.conn_manager, parent=self)
        dlg.setWindowTitle(
            QCoreApplication.translate("SuppliesETLWizard",
                                       "Target DB Connection Settings"))
        dlg.show_tip(
            QCoreApplication.translate(
                "SuppliesETLWizard",
                "Configure where do you want the data to be imported."))
        dlg.set_db_source(self.db_source)

        dlg.set_required_models([LADMNames.SUPPLIES_MODEL_KEY])
        dlg.set_tab_pages_list(
            [SETTINGS_CONNECTION_TAB_INDEX, SETTINGS_MODELS_TAB_INDEX])
        dlg.set_action_type(EnumDbActionType.IMPORT_FROM_ETL)

        dlg.db_connection_changed.connect(self.db_connection_changed)
        if self.db_source == COLLECTED_DB_SOURCE:
            dlg.db_connection_changed.connect(
                self.app.core.cache_layers_and_relations)

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

    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())
        else:
            self.db_connect_label.setText(
                QCoreApplication.translate("SuppliesETLWizard",
                                           "The database is not defined!"))
            self.db_connect_label.setToolTip('')

    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 supplies dialog (e.g., we might run an import schema before under the hood)
        self._db_was_changed = True

    def load_model_layers(self, layers):
        self.app.core.get_layers(self._db, layers, load=True)
        if not layers:
            return False, QCoreApplication.translate(
                "SuppliesETLWizard",
                "There was a problem loading layers from the 'Supplies' model!"
            )

        return True, ''
コード例 #22
0
class DialogExportData(QDialog, DIALOG_UI):
    on_result = pyqtSignal(
        bool)  # whether the tool was run successfully or not

    ValidExtensions = ['xtf', 'itf', 'gml', 'xml']
    current_row_schema = 0

    def __init__(self, iface, qgis_utils, conn_manager, context):
        QDialog.__init__(self)
        self.setupUi(self)

        QgsGui.instance().enableAutoGeometryRestore(self)
        self.iface = iface
        self.conn_manager = conn_manager
        self.db_source = context.get_db_sources()[0]
        self.db = self.conn_manager.get_db_connector_from_source(
            self.db_source)
        self.qgis_utils = qgis_utils
        self.logger = Logger()

        self.java_utils = JavaUtils()
        self.java_utils.download_java_completed.connect(
            self.download_java_complete)
        self.java_utils.download_java_progress_changed.connect(
            self.download_java_progress_change)

        self.base_configuration = BaseConfiguration()
        self.ilicache = IliCache(self.base_configuration)
        self.ilicache.refresh()

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

        # There may be 1 case where we need to emit a db_connection_changed from the Export 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 ED 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_save_file_selector(
                self.xtf_file_line_edit,
                title=QCoreApplication.translate("DialogExportData",
                                                 "Save in XTF Transfer File"),
                file_filter=QCoreApplication.translate(
                    "DialogExportData",
                    "XTF Transfer File (*.xtf);;Interlis 1 Transfer File (*.itf);;XML (*.xml);;GML (*.gml)"
                ),
                extension='.xtf',
                extensions=['.' + ext for ext in self.ValidExtensions]))
        self.xtf_file_browse_button.clicked.connect(
            self.xtf_browser_opened_to_true)
        self.xtf_browser_was_opened = False

        self.validators = Validators()
        fileValidator = FileValidator(
            pattern=['*.' + ext for ext in self.ValidExtensions],
            allow_non_existing=True)
        self.xtf_file_line_edit.setPlaceholderText(
            QCoreApplication.translate("DialogExportData",
                                       "[Name of the XTF to be created]"))
        self.xtf_file_line_edit.setValidator(fileValidator)
        self.xtf_file_line_edit.textChanged.connect(
            self.validators.validate_line_edits)
        self.xtf_file_line_edit.textChanged.connect(
            self.xtf_browser_opened_to_false)
        self.xtf_file_line_edit.textChanged.emit(
            self.xtf_file_line_edit.text())

        self.connection_setting_button.clicked.connect(self.show_settings)

        self.connection_setting_button.setText(
            QCoreApplication.translate("DialogExportData",
                                       "Connection Settings"))

        # LOG
        self.log_config.setTitle(
            QCoreApplication.translate("DialogExportData", "Show log"))
        self.log_config.setFlat(True)

        self.buttonBox.accepted.disconnect()
        self.buttonBox.accepted.connect(self.accepted)
        self.buttonBox.clear()
        self.buttonBox.addButton(QDialogButtonBox.Cancel)
        self._accept_button = self.buttonBox.addButton(
            QCoreApplication.translate("DialogExportData", "Export data"),
            QDialogButtonBox.AcceptRole)
        self.buttonBox.addButton(QDialogButtonBox.Help)
        self.buttonBox.helpRequested.connect(self.show_help)

        self.update_connection_info()
        self.update_model_names()
        self.restore_configuration()

    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("DialogExportData",
                                           "The database is not defined!"))
            self.db_connect_label.setToolTip('')
            self._accept_button.setEnabled(False)

    def update_model_names(self):
        self.export_models_qmodel = QStandardItemModel()

        model_names = self.db.get_models()

        if model_names:
            for model_name in model_names:
                if model_name not in LADMNames.DEFAULT_HIDDEN_MODELS:
                    item = QStandardItem(model_name)
                    item.setCheckable(False)
                    item.setEditable(False)
                    self.export_models_qmodel.appendRow(item)

        self.export_models_list_view.setModel(self.export_models_qmodel)

    def reject(self):
        if self._running_tool:
            QMessageBox.information(
                self, QCoreApplication.translate("DialogExportData",
                                                 "Warning"),
                QCoreApplication.translate(
                    "DialogExportData",
                    "The Export 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.qgis_utils.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.qgis_utils.cache_layers_and_relations)

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

    def get_ili_models(self):
        ili_models = list()
        for index in range(self.export_models_qmodel.rowCount()):
            item = self.export_models_qmodel.item(index)
            ili_models.append(item.text())
        return ili_models

    def show_settings(self):
        # We only need those tabs related to Model Baker/ili2db operations
        dlg = SettingsDialog(qgis_utils=self.qgis_utils,
                             conn_manager=self.conn_manager)
        dlg.set_db_source(self.db_source)
        dlg.set_tab_pages_list(
            [SETTINGS_CONNECTION_TAB_INDEX, SETTINGS_MODELS_TAB_INDEX])

        # Connect signals (DBUtils, QgisUtils)
        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.EXPORT)

        if dlg.exec_():
            self.db = dlg.get_db_connection()
            self.update_model_names()
            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.bar.clearWidgets()

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

        configuration = self.update_configuration()

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

        if not self.get_ili_models():
            self._running_tool = False
            message_error = QCoreApplication.translate(
                "DialogExportData",
                "Please set a valid schema to export. This schema does not have information to export."
            )
            self.txtStdout.setText(message_error)
            self.show_message(message_error, Qgis.Warning)
            self.export_models_list_view.setFocus()
            return

        if not configuration.iliexportmodels:
            self._running_tool = False
            message_error = QCoreApplication.translate(
                "DialogExportData",
                "Please set a model before exporting data.")
            self.txtStdout.setText(message_error)
            self.show_message(message_error, Qgis.Warning)
            self.export_models_list_view.setFocus()
            return

        # If xtf browser was opened and the file exists, the user already chose
        # to overwrite the file
        if os.path.isfile(self.xtf_file_line_edit.text().strip()
                          ) and not self.xtf_browser_was_opened:
            self.msg = QMessageBox()
            self.msg.setIcon(QMessageBox.Warning)
            self.msg.setText(
                QCoreApplication.translate(
                    "DialogExportData",
                    "{filename} already exists.\nDo you want to replace it?").
                format(filename=os.path.basename(
                    self.xtf_file_line_edit.text().strip())))
            self.msg.setWindowTitle(
                QCoreApplication.translate("DialogExportData",
                                           "Save in XTF Transfer File"))
            self.msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            msg_box = self.msg.exec_()
            if msg_box == QMessageBox.No:
                self._running_tool = False
                return

        with OverrideCursor(Qt.WaitCursor):
            self.progress_bar.show()

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

            exporter = iliexporter.Exporter()

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

            exporter.tool = db_factory.get_mbaker_db_ili_mode()
            exporter.configuration = configuration

            self.save_configuration(configuration)

            exporter.stdout.connect(self.print_info)
            exporter.stderr.connect(self.on_stderr)
            exporter.process_started.connect(self.on_process_started)
            exporter.process_finished.connect(self.on_process_finished)

            self.progress_bar.setValue(25)

            try:
                if exporter.run() != iliexporter.Exporter.SUCCESS:
                    self._running_tool = False
                    self.show_message(
                        QCoreApplication.translate(
                            "DialogExportData",
                            "An error occurred when exporting the data. 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(
                    "DialogExportData",
                    "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()
            self.buttonBox.setEnabled(True)
            self.buttonBox.addButton(QDialogButtonBox.Close)
            self.progress_bar.setValue(100)
            self.show_message(
                QCoreApplication.translate(
                    "DialogExportData",
                    "Export of the data was successfully completed."),
                Qgis.Success)
            self.on_result.emit(
                True)  # Inform other classes that the execution was successful

    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/ili2pg/xtffile_export',
            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_export'))

        # 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
        custom_model_is_checked = settings.value(
            'Asistente-LADM_COL/models/custom_model_directories_is_checked',
            DEFAULT_USE_CUSTOM_MODELS,
            type=bool)
        if custom_model_is_checked:
            self.custom_model_directories = settings.value(
                'Asistente-LADM_COL/models/custom_models', DEFAULT_MODELS_DIR)

    def update_configuration(self):
        """
        Get the configuration that is updated with the user configuration changes on the dialog.
        :return: Configuration
        """
        db_factory = self._dbs_supported.get_db_factory(self.db.engine)

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

        configuration.xtffile = self.xtf_file_line_edit.text().strip()
        full_java_exe_path = JavaUtils.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_ili_models():
            configuration.iliexportmodels = ';'.join(self.get_ili_models())
            configuration.ilimodels = ';'.join(self.get_ili_models())

        configuration.disable_validation = not QSettings().value(
            'Asistente-LADM_COL/advanced_settings/validate_data_importing_exporting',
            True, bool)

        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(
            QCoreApplication.translate("DialogExportData",
                                       "Finished ({})").format(exit_code))
        if result == iliexporter.Exporter.SUCCESS:
            self.buttonBox.clear()
            self.buttonBox.setEnabled(True)
            self.buttonBox.addButton(QDialogButtonBox.Close)
        else:
            self.enable()

            # 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: process data...':
            self.progress_bar.setValue(55)
            QCoreApplication.processEvents()
        elif text.strip() == 'Info: first validation pass...':
            self.progress_bar.setValue(70)
            QCoreApplication.processEvents()
        elif text.strip() == 'Info: second validation pass...':
            self.progress_bar.setValue(85)
            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):
        self.qgis_utils.show_help("export_data")

    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)

    def xtf_browser_opened_to_true(self):
        """
        Slot. Sets a flag to true to eventually avoid asking a user whether to overwrite a file.
        """
        self.xtf_browser_was_opened = True

    def xtf_browser_opened_to_false(self):
        """
        Slot. Sets a flag to false to eventually ask a user whether to overwrite a file.
        """
        self.xtf_browser_was_opened = False
        self.clear_messages()
コード例 #23
0
class RightOfWay(QObject):
    def __init__(self):
        QObject.__init__(self)
        self.logger = Logger()
        self.app = AppInterface()

        self._right_of_way_line_layer = None
        self.addedFeatures = None

    def fill_right_of_way_relations(self, db):
        layers = {
            db.names.LC_ADMINISTRATIVE_SOURCE_T: None,
            db.names.LC_PARCEL_T: None,
            db.names.LC_PLOT_T: None,
            db.names.LC_RESTRICTION_T: None,
            db.names.LC_RESTRICTION_TYPE_D: None,
            db.names.LC_RIGHT_OF_WAY_T: None,
            db.names.COL_RRR_SOURCE_T: None,
            db.names.LC_SURVEY_POINT_T: None,
            db.names.COL_UE_BAUNIT_T: None
        }

        # Load layers
        self.app.core.get_layers(db, layers, load=True)
        if not layers:
            return None

        exp = "\"{}\" = '{}'".format(
            db.names.ILICODE_F,
            LADMNames.RESTRICTION_TYPE_D_RIGHT_OF_WAY_ILICODE_VALUE)
        restriction_right_of_way_t_id = [
            feature for feature in layers[
                db.names.LC_RESTRICTION_TYPE_D].getFeatures(exp)
        ][0][db.names.T_ID_F]

        if layers[db.names.LC_PLOT_T].selectedFeatureCount() == 0 or layers[
                db.names.LC_RIGHT_OF_WAY_T].selectedFeatureCount(
                ) == 0 or layers[
                    db.names.LC_ADMINISTRATIVE_SOURCE_T].selectedFeatureCount(
                    ) == 0:
            if self.app.core.get_ladm_layer_from_qgis(
                    db, db.names.LC_PLOT_T,
                    EnumLayerRegistryType.IN_LAYER_TREE) is None:
                self.logger.message_with_button_load_layer_emitted.emit(
                    QCoreApplication.translate(
                        "RightOfWay",
                        "First load the layer {} into QGIS and select at least one plot!"
                    ).format(db.names.LC_PLOT_T),
                    QCoreApplication.translate("RightOfWay",
                                               "Load layer {} now").format(
                                                   db.names.LC_PLOT_T),
                    db.names.LC_PLOT_T, Qgis.Warning)
            else:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "RightOfWay",
                        "Select at least one benefited plot, one right of way and at least one administrative source to create relations!"
                    ))
                return
        else:
            ue_baunit_features = layers[db.names.COL_UE_BAUNIT_T].getFeatures()
            # Get unique pairs id_right_of_way-id_parcel
            existing_pairs = [
                (ue_baunit_feature[db.names.COL_UE_BAUNIT_T_PARCEL_F],
                 ue_baunit_feature[db.names.COL_UE_BAUNIT_T_LC_RIGHT_OF_WAY_F])
                for ue_baunit_feature in ue_baunit_features
            ]
            existing_pairs = set(existing_pairs)

            plot_ids = [
                f[db.names.T_ID_F]
                for f in layers[db.names.LC_PLOT_T].selectedFeatures()
            ]

            right_of_way_id = layers[
                db.names.LC_RIGHT_OF_WAY_T].selectedFeatures()[0].attribute(
                    db.names.T_ID_F)
            id_pairs = list()
            for plot in plot_ids:
                exp = "\"{uebaunit}\" = {plot}".format(
                    uebaunit=db.names.COL_UE_BAUNIT_T_LC_PLOT_F, plot=plot)
                parcels = layers[db.names.COL_UE_BAUNIT_T].getFeatures(exp)
                for parcel in parcels:
                    id_pair = (parcel.attribute(
                        db.names.COL_UE_BAUNIT_T_PARCEL_F), right_of_way_id)
                    id_pairs.append(id_pair)

            if len(id_pairs) < len(plot_ids):
                # If any relationship plot-parcel is not found, we don't need to continue
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "RightOfWay",
                        "One or more pairs id_plot-id_parcel weren't found, this is needed to create benefited and restriction relations."
                    ))
                return

            if id_pairs:
                new_features = list()
                for id_pair in id_pairs:
                    if not id_pair in existing_pairs:
                        #Create feature
                        new_feature = QgsVectorLayerUtils().createFeature(
                            layers[db.names.COL_UE_BAUNIT_T])
                        new_feature.setAttribute(
                            db.names.COL_UE_BAUNIT_T_PARCEL_F, id_pair[0])
                        new_feature.setAttribute(
                            db.names.COL_UE_BAUNIT_T_LC_RIGHT_OF_WAY_F,
                            id_pair[1])
                        self.logger.info(
                            __name__, "Saving RightOfWay-Parcel: {}-{}".format(
                                id_pair[1], id_pair[0]))
                        new_features.append(new_feature)

                layers[db.names.COL_UE_BAUNIT_T].dataProvider().addFeatures(
                    new_features)
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "RightOfWay",
                        "{} out of {} records were saved into {}! {} out of {} records already existed in the database."
                    ).format(len(new_features), len(id_pairs),
                             db.names.COL_UE_BAUNIT_T,
                             len(id_pairs) - len(new_features), len(id_pairs)))

            spatial_join_layer = processing.run(
                "qgis:joinattributesbylocation", {
                    'INPUT':
                    layers[db.names.LC_PLOT_T],
                    'JOIN':
                    QgsProcessingFeatureSourceDefinition(
                        layers[db.names.LC_RIGHT_OF_WAY_T].id(), True),
                    'PREDICATE': [0],
                    'JOIN_FIELDS': [db.names.T_ID_F],
                    'METHOD':
                    0,
                    'DISCARD_NONMATCHING':
                    True,
                    'PREFIX':
                    '',
                    'OUTPUT':
                    'memory:'
                })['OUTPUT']

            restriction_features = layers[
                db.names.LC_RESTRICTION_T].getFeatures()
            existing_restriction_pairs = [
                (restriction_feature[db.names.COL_BAUNIT_RRR_T_UNIT_F],
                 restriction_feature[db.names.COL_RRR_T_DESCRIPTION_F])
                for restriction_feature in restriction_features
            ]
            existing_restriction_pairs = set(existing_restriction_pairs)
            id_pairs_restriction = list()
            plot_ids = spatial_join_layer.getFeatures()

            for plot in plot_ids:
                exp = "\"uebaunit\" = {plot}".format(
                    uebaunit=db.names.COL_UE_BAUNIT_T_LC_PLOT_F,
                    plot=plot.attribute(db.names.T_ID_F))
                parcels = layers[db.names.COL_UE_BAUNIT_T].getFeatures(exp)
                for parcel in parcels:
                    id_pair_restriction = (parcel.attribute(
                        db.names.COL_UE_BAUNIT_T_PARCEL_F),
                                           QCoreApplication.translate(
                                               "RightOfWay", "Right of way"))
                    id_pairs_restriction.append(id_pair_restriction)

            new_restriction_features = list()
            if id_pairs_restriction:
                for id_pair in id_pairs_restriction:
                    if not id_pair in existing_restriction_pairs:
                        #Create feature
                        new_feature = QgsVectorLayerUtils().createFeature(
                            layers[db.names.LC_RESTRICTION_T])
                        new_feature.setAttribute(
                            db.names.COL_BAUNIT_RRR_T_UNIT_F, id_pair[0])
                        new_feature.setAttribute(
                            db.names.COL_RRR_T_DESCRIPTION_F, id_pair[1])
                        new_feature.setAttribute(
                            db.names.LC_RESTRICTION_T_TYPE_F,
                            restriction_right_of_way_t_id)
                        self.logger.info(
                            __name__, "Saving RightOfWay-Parcel: {}-{}".format(
                                id_pair[1], id_pair[0]))
                        new_restriction_features.append(new_feature)

                layers[db.names.LC_RESTRICTION_T].dataProvider().addFeatures(
                    new_restriction_features)
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "RightOfWay",
                        "{} out of {} records were saved into {}! {} out of {} records already existed in the database."
                    ).format(
                        len(new_restriction_features),
                        len(id_pairs_restriction), db.names.LC_RESTRICTION_T,
                        len(id_pairs_restriction) -
                        len(new_restriction_features),
                        len(id_pairs_restriction)))

            administrative_source_ids = [
                f[db.names.T_ID_F] for f in layers[
                    db.names.LC_ADMINISTRATIVE_SOURCE_T].selectedFeatures()
            ]

            source_relation_features = layers[
                db.names.COL_RRR_SOURCE_T].getFeatures()

            existing_source_pairs = [
                (source_relation_feature[db.names.COL_RRR_SOURCE_T_SOURCE_F],
                 source_relation_feature[
                     db.names.COL_RRR_SOURCE_T_LC_RESTRICTION_F])
                for source_relation_feature in source_relation_features
            ]
            existing_source_pairs = set(existing_source_pairs)

            rrr_source_relation_pairs = list()

            for administrative_source_id in administrative_source_ids:
                for restriction_feature in new_restriction_features:
                    rrr_source_relation_pair = (administrative_source_id,
                                                restriction_feature.attribute(
                                                    db.names.T_ID_F))
                    rrr_source_relation_pairs.append(rrr_source_relation_pair)

            new_rrr_source_relation_features = list()
            if rrr_source_relation_pairs:
                for id_pair in rrr_source_relation_pairs:
                    if not id_pair in existing_source_pairs:
                        new_feature = QgsVectorLayerUtils().createFeature(
                            layers[db.names.COL_RRR_SOURCE_T])
                        new_feature.setAttribute(
                            db.names.COL_RRR_SOURCE_T_SOURCE_F, id_pair[0])
                        new_feature.setAttribute(
                            db.names.COL_RRR_SOURCE_T_LC_RESTRICTION_F,
                            id_pair[1])
                        self.logger.info(
                            __name__,
                            "Saving Restriction-Source: {}-{}".format(
                                id_pair[1], id_pair[0]))
                        new_rrr_source_relation_features.append(new_feature)

                layers[db.names.COL_RRR_SOURCE_T].dataProvider().addFeatures(
                    new_rrr_source_relation_features)
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "RightOfWay",
                        "{} out of {} records were saved into {}! {} out of {} records already existed in the database."
                    ).format(
                        len(new_rrr_source_relation_features),
                        len(rrr_source_relation_pairs),
                        db.names.COL_RRR_SOURCE_T,
                        len(rrr_source_relation_pairs) -
                        len(new_rrr_source_relation_features),
                        len(rrr_source_relation_pairs)))
コード例 #24
0
class AbsWizardFactory(QWizard):
    update_wizard_is_open_flag = pyqtSignal(bool)
    set_finalize_geometry_creation_enabled_emitted = pyqtSignal(bool)

    def __init__(self, iface, db, qgis_utils, wizard_settings):
        super(AbsWizardFactory, self).__init__()
        self.iface = iface
        self._db = db
        self.qgis_utils = qgis_utils
        self.wizard_config = wizard_settings
        self.logger = Logger()
        self.names = self._db.names
        self.help_strings = HelpStrings()
        self.translatable_config_strings = TranslatableConfigStrings()
        load_ui(self.wizard_config[WIZARD_UI], self)

        self.WIZARD_FEATURE_NAME = self.wizard_config[WIZARD_FEATURE_NAME]
        self.WIZARD_TOOL_NAME = self.wizard_config[WIZARD_TOOL_NAME]
        self.EDITING_LAYER_NAME = self.wizard_config[WIZARD_EDITING_LAYER_NAME]
        self._layers = self.wizard_config[WIZARD_LAYERS]
        self.set_ready_only_field()

        self.init_gui()

    def init_gui(self):
        raise NotImplementedError

    def adjust_page_1_controls(self):
        raise NotImplementedError

    def finished_dialog(self):
        raise NotImplementedError

    def prepare_feature_creation(self):
        result = self.prepare_feature_creation_layers()
        if result:
            self.edit_feature()
        else:
            self.close_wizard(show_message=False)

    def prepare_feature_creation_layers(self):
        raise NotImplementedError

    def close_wizard(self, message=None, show_message=True):
        raise NotImplementedError

    def rollback_in_layers_with_empty_editing_buffer(self):
        for layer_name in self._layers:
            if self._layers[layer_name][
                    LAYER] is not None:  # If the layer was removed, this becomes None
                if self._layers[layer_name][LAYER].isEditable():
                    if not self._layers[layer_name][LAYER].editBuffer(
                    ).isModified():
                        self._layers[layer_name][LAYER].rollBack()

    def disconnect_signals(self):
        raise NotImplementedError

    def edit_feature(self):
        raise NotImplementedError

    def finish_feature_creation(self, layerId, features):
        message = self.post_save(features)

        self._layers[
            self.EDITING_LAYER_NAME][LAYER].committedFeaturesAdded.disconnect(
                self.finish_feature_creation)
        self.logger.info(
            __name__, "{} committedFeaturesAdded SIGNAL disconnected".format(
                self.WIZARD_FEATURE_NAME))
        self.close_wizard(message)

    def post_save(self, features):
        raise NotImplementedError

    def open_form(self, layer):
        raise NotImplementedError

    def exec_form(self, layer):
        feature = self.get_feature_exec_form(layer)
        dialog = self.iface.getFeatureForm(layer, feature)
        dialog.rejected.connect(self.form_rejected)
        dialog.setModal(True)

        if dialog.exec_():
            self.exec_form_advanced(layer)
            saved = layer.commitChanges()

            if not saved:
                layer.rollBack()
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "WizardTranslations",
                        "Error while saving changes. {} could not be created."
                    ).format(self.WIZARD_FEATURE_NAME))
                for e in layer.commitErrors():
                    self.logger.warning(__name__, "Commit error: {}".format(e))
        else:
            layer.rollBack()
        self.iface.mapCanvas().refresh()

    def get_feature_exec_form(self, layer):
        raise NotImplementedError

    def exec_form_advanced(self, layer):
        raise NotImplementedError

    def form_rejected(self):
        message = QCoreApplication.translate(
            "WizardTranslations",
            "'{}' tool has been closed because you just closed the form."
        ).format(self.WIZARD_TOOL_NAME)
        self.close_wizard(message)

    def save_settings(self):
        settings = QSettings()
        settings.setValue(
            self.wizard_config[WIZARD_QSETTINGS]
            [WIZARD_QSETTINGS_LOAD_DATA_TYPE], 'create_manually'
            if self.rad_create_manually.isChecked() else 'refactor')

    def restore_settings(self):
        settings = QSettings()

        load_data_type = settings.value(
            self.wizard_config[WIZARD_QSETTINGS]
            [WIZARD_QSETTINGS_LOAD_DATA_TYPE]) or 'create_manually'
        if load_data_type == 'refactor':
            self.rad_refactor.setChecked(True)
        else:
            self.rad_create_manually.setChecked(True)

    def show_help(self):
        self.qgis_utils.show_help(self.wizard_config[WIZARD_HELP])

    def set_ready_only_field(self, read_only=True):
        if self._layers[self.EDITING_LAYER_NAME][LAYER] is not None:
            for field in self.wizard_config[WIZARD_READ_ONLY_FIELDS]:
                # Not validate field that are read only
                QGISUtils.set_read_only_field(
                    self._layers[self.EDITING_LAYER_NAME][LAYER], field,
                    read_only)
コード例 #25
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)
コード例 #26
0
class ChangesPerParcelPanelWidget(QgsPanelWidget, WIDGET_UI):
    def __init__(self,
                 parent,
                 utils,
                 parcel_number=None,
                 collected_parcel_t_id=None):
        QgsPanelWidget.__init__(self, None)
        self.setupUi(self)
        self.parent = parent
        self.utils = utils
        self.logger = Logger()

        self.setDockMode(True)
        self.setPanelTitle(
            QCoreApplication.translate("ChangesPerParcelPanelWidget",
                                       "Change detection per parcel"))

        self._current_supplies_substring = ""
        self._current_substring = ""

        self.utils.add_layers()
        self.fill_combos()

        # Remove selection in plot layers
        self.utils._layers[self.utils._db.names.LC_PLOT_T].removeSelection()
        self.utils._supplies_layers[
            self.utils._supplies_db.names.GC_PLOT_T].removeSelection()

        # Map tool before activate map swipe tool
        self.init_map_tool = self.utils.canvas.mapTool()

        self.active_map_tool_before_custom = None
        self.btn_identify_plot.setIcon(
            QIcon(":/Asistente-LADM-COL/resources/images/spatial_unit.png"))
        self.btn_identify_plot.clicked.connect(self.btn_plot_toggled)

        # Create maptool
        self.maptool_identify = QgsMapToolIdentifyFeature(self.utils.canvas)

        # Set connections
        self.btn_alphanumeric_query.clicked.connect(self.alphanumeric_query)
        self.chk_show_all_plots.toggled.connect(self.show_all_plots)
        self.cbo_parcel_fields.currentIndexChanged.connect(
            self.search_field_updated)
        self.panelAccepted.connect(self.initialize_tools_and_layers)
        self.tbl_changes_per_parcel.itemDoubleClicked.connect(
            self.call_party_panel)

        self.initialize_field_values_line_edit()
        self.initialize_tools_and_layers()

        if parcel_number is not None:  # Do a search!
            self.txt_alphanumeric_query.setValue(parcel_number)
            if collected_parcel_t_id is not None:  # Search data for a duplicated parcel_number, so, take the t_id into account!
                self.search_data(parcel_number=parcel_number,
                                 collected_parcel_t_id=collected_parcel_t_id)
            else:
                self.search_data(parcel_number=parcel_number)

    def btn_plot_toggled(self):
        self.clear_result_table()

        if self.btn_identify_plot.isChecked():
            self.prepare_identify_plot()
        else:
            # The button was toggled and deactivated, go back to the previous tool
            self.utils.canvas.setMapTool(self.active_map_tool_before_custom)

    def clear_result_table(self):
        self.tbl_changes_per_parcel.clearContents()
        self.tbl_changes_per_parcel.setRowCount(0)

    def prepare_identify_plot(self):
        """
            Custom Identify tool was activated, prepare everything for identifying plots
        """
        self.active_map_tool_before_custom = self.utils.canvas.mapTool()

        self.btn_identify_plot.setChecked(True)

        self.utils.canvas.mapToolSet.connect(self.initialize_maptool)

        if self.utils._supplies_layers[
                self.utils._supplies_db.names.GC_PLOT_T] is None:
            self.utils.add_layers()

        self.maptool_identify.setLayer(self.utils._supplies_layers[
            self.utils._supplies_db.names.GC_PLOT_T])
        cursor = QCursor()
        cursor.setShape(Qt.PointingHandCursor)
        self.maptool_identify.setCursor(cursor)
        self.utils.canvas.setMapTool(self.maptool_identify)

        try:
            self.maptool_identify.featureIdentified.disconnect()
        except TypeError as e:
            pass
        self.maptool_identify.featureIdentified.connect(self.get_info_by_plot)

    def get_info_by_plot(self, plot_feature):
        """
        :param plot_feature: from supplies db
        """
        plot_t_id = plot_feature[self.utils._supplies_db.names.T_ID_F]

        self.utils.canvas.flashFeatureIds(self.utils._supplies_layers[
            self.utils._supplies_db.names.GC_PLOT_T], [plot_feature.id()],
                                          QColor(255, 0, 0, 255),
                                          QColor(255, 0, 0, 0),
                                          flashes=1,
                                          duration=500)

        if not self.isVisible():
            self.show()

        self.spatial_query(plot_t_id)
        self.utils._supplies_layers[
            self.utils._supplies_db.names.GC_PLOT_T].selectByIds(
                [plot_feature.id()])

    def spatial_query(self, plot_id):
        if plot_id:
            parcel_number = self.utils.ladm_data.get_parcels_related_to_plots_supplies(
                self.utils._supplies_db, [plot_id],
                self.utils._supplies_db.names.GC_PARCEL_T_PARCEL_NUMBER_F)
            if parcel_number:  # Delegate handling of duplicates to search_data() method
                self.search_data(parcel_number=parcel_number[0])

    def call_party_panel(self, item):
        row = item.row()
        if self.tbl_changes_per_parcel.item(row, 0).text(
        ) == DICT_ALIAS_KEYS_CHANGE_DETECTION[DICT_KEY_PARTIES]:
            data = {
                SUPPLIES_DB_SOURCE:
                self.tbl_changes_per_parcel.item(row, 1).data(Qt.UserRole),
                COLLECTED_DB_SOURCE:
                self.tbl_changes_per_parcel.item(row, 2).data(Qt.UserRole)
            }
            self.parent.show_party_panel(data)

    def search_field_updated(self, index=None):
        self.initialize_field_values_line_edit()

    def initialize_field_values_line_edit(self):
        # We search for alphanumeric data in supplies data source
        self.txt_alphanumeric_query.setLayer(self.utils._supplies_layers[
            self.utils._supplies_db.names.GC_PARCEL_T])
        search_option = self.cbo_parcel_fields.currentData()
        search_field_supplies = get_supplies_search_options(
            self.utils._supplies_db.names)[search_option]
        idx = self.utils._supplies_layers[
            self.utils._supplies_db.names.GC_PARCEL_T].fields().indexOf(
                search_field_supplies)
        self.txt_alphanumeric_query.setAttributeIndex(idx)

    def fill_combos(self):
        self.cbo_parcel_fields.clear()
        self.cbo_parcel_fields.addItem(
            QCoreApplication.translate("DockWidgetChanges", "Parcel Number"),
            PARCEL_NUMBER_SEARCH_KEY)
        self.cbo_parcel_fields.addItem(
            QCoreApplication.translate("DockWidgetChanges",
                                       "Previous Parcel Number"),
            PREVIOUS_PARCEL_NUMBER_SEARCH_KEY)
        self.cbo_parcel_fields.addItem(
            QCoreApplication.translate("DockWidgetChanges",
                                       "Folio de Matrícula Inmobiliaria"),
            FMI_PARCEL_SEARCH_KEY)

    @_with_override_cursor
    def search_data(self, **kwargs):
        """
        Get plot geometries associated with parcels, both collected and supplies, zoom to them, fill comparison table
        and activate map swipe tool.

        To fill the comparison table we build two search dicts, one for supplies (already given because the alphanumeric
        search is on supplies db source), and another one for collected. For the latter, we have 3 cases. We specify
        them below (inline).

        :param kwargs: key-value (field name-field value) to search in parcel tables, both collected and supplies
                       Normally, keys are parcel_number, old_parcel_number or FMI, but if duplicates are found, an
                       additional t_id disambiguates only for the collected source. In the supplies source we assume
                       we will not find duplicates, if there are, we will choose the first record found an will not deal
                       with letting the user choose one of the duplicates by hand (as we do for the collected source).
        """
        self.chk_show_all_plots.setEnabled(False)
        self.chk_show_all_plots.setChecked(True)
        self.initialize_tools_and_layers()  # Reset any filter on layers

        plots_supplies = list()
        plots_collected = list()
        self.clear_result_table()

        search_option = self.cbo_parcel_fields.currentData()
        search_field_supplies = get_supplies_search_options(
            self.utils._supplies_db.names)[search_option]
        search_field_collected = get_collected_search_options(
            self.utils._db.names)[search_option]
        search_value = list(kwargs.values())[0]

        # Build search criterion for both supplies and collected
        search_criterion_supplies = {search_field_supplies: search_value}

        # Get supplies parcel's t_id and get related plot(s)
        expression_supplies = QgsExpression("{}='{}'".format(
            search_field_supplies, search_value))
        request = QgsFeatureRequest(expression_supplies)
        field_idx = self.utils._supplies_layers[
            self.utils._supplies_db.names.GC_PARCEL_T].fields().indexFromName(
                self.utils._supplies_db.names.T_ID_F)
        request.setFlags(QgsFeatureRequest.NoGeometry)
        request.setSubsetOfAttributes([field_idx
                                       ])  # Note: this adds a new flag
        supplies_parcels = [
            feature for feature in self.utils._supplies_layers[
                self.utils._supplies_db.names.GC_PARCEL_T].getFeatures(request)
        ]

        if len(supplies_parcels) > 1:
            # We do not expect duplicates in the supplies source!
            pass  # We'll choose the first one anyways
        elif len(supplies_parcels) == 0:
            self.logger.info(
                __name__, "No supplies parcel found! Search: {}={}".format(
                    search_field_supplies, search_value))

        supplies_plot_t_ids = []
        if supplies_parcels:
            supplies_plot_t_ids = self.utils.ladm_data.get_plots_related_to_parcels_supplies(
                self.utils._supplies_db,
                [supplies_parcels[0][self.utils._supplies_db.names.T_ID_F]],
                self.utils._supplies_db.names.T_ID_F,
                self.utils._supplies_layers[
                    self.utils._supplies_db.names.GC_PLOT_T])

            if supplies_plot_t_ids:
                self._current_supplies_substring = "\"{}\" IN ('{}')".format(
                    self.utils._supplies_db.names.T_ID_F,
                    "','".join([str(t_id) for t_id in supplies_plot_t_ids]))
                plots_supplies = self.utils.ladm_data.get_features_from_t_ids(
                    self.utils._supplies_layers[
                        self.utils._supplies_db.names.GC_PLOT_T],
                    self.utils._supplies_db.names.T_ID_F, supplies_plot_t_ids,
                    True)

        # Now get COLLECTED parcel's t_id to build the search dict for collected
        collected_parcel_t_id = None
        if 'collected_parcel_t_id' in kwargs:
            # This is the case when this panel is called and we already know the parcel number is duplicated
            collected_parcel_t_id = kwargs['collected_parcel_t_id']
            search_criterion_collected = {
                self.utils._db.names.T_ID_F: collected_parcel_t_id
            }  # As there are duplicates, we need to use t_ids
        else:
            # This is the case when:
            #   + Either this panel was called and we know the parcel number is not duplicated, or
            #   + This panel was shown without knowing about duplicates (e.g., individual parcel search) and we still
            #     need to discover whether we have duplicates for this search criterion
            search_criterion_collected = {search_field_collected: search_value}

            expression_collected = QgsExpression("{}='{}'".format(
                search_field_collected, search_value))
            request = QgsFeatureRequest(expression_collected)
            request.setFlags(QgsFeatureRequest.NoGeometry)
            request.setSubsetOfAttributes(
                [self.utils._db.names.T_ID_F],
                self.utils._layers[self.utils._db.names.LC_PARCEL_T].fields(
                ))  # Note this adds a new flag
            collected_parcels = self.utils._layers[
                self.utils._db.names.LC_PARCEL_T].getFeatures(request)
            collected_parcels_t_ids = [
                feature[self.utils._db.names.T_ID_F]
                for feature in collected_parcels
            ]

            if collected_parcels_t_ids:
                collected_parcel_t_id = collected_parcels_t_ids[0]
                if len(collected_parcels_t_ids
                       ) > 1:  # Duplicates in collected source after a search
                    QApplication.restoreOverrideCursor(
                    )  # Make sure cursor is not waiting (it is if on an identify)
                    QCoreApplication.processEvents()
                    dlg_select_parcel = SelectDuplicateParcelDialog(
                        self.utils, collected_parcels_t_ids, self.parent)
                    dlg_select_parcel.exec_()

                    if dlg_select_parcel.parcel_t_id:  # User selected one of the duplicated parcels
                        collected_parcel_t_id = dlg_select_parcel.parcel_t_id
                        search_criterion_collected = {
                            self.utils._db.names.T_ID_F: collected_parcel_t_id
                        }
                    else:
                        return  # User just cancelled the dialog, there is nothing more to do

        self.fill_table(search_criterion_supplies, search_criterion_collected)

        # Now get related plot(s) for both collected and supplies,
        if collected_parcel_t_id is not None:
            plot_t_ids = self.utils.ladm_data.get_plots_related_to_parcels(
                self.utils._db, [collected_parcel_t_id],
                self.utils._db.names.T_ID_F,
                plot_layer=self.utils._layers[self.utils._db.names.LC_PLOT_T],
                uebaunit_table=self.utils._layers[
                    self.utils._db.names.COL_UE_BAUNIT_T])

            if plot_t_ids:
                self._current_substring = "{} IN ('{}')".format(
                    self.utils._db.names.T_ID_F,
                    "','".join([str(t_id) for t_id in plot_t_ids]))
                plots_collected = self.utils.ladm_data.get_features_from_t_ids(
                    self.utils._layers[self.utils._db.names.LC_PLOT_T],
                    self.utils._db.names.T_ID_F, plot_t_ids, True)

        # Zoom to combined extent
        plot_features = plots_supplies + plots_collected  # Feature list
        plots_extent = QgsRectangle()
        for plot in plot_features:
            plots_extent.combineExtentWith(plot.geometry().boundingBox())

        if not plots_extent.isEmpty():
            self.utils.iface.mapCanvas().zoomToFeatureExtent(plots_extent)

            if plots_supplies and plots_collected:  # Otherwise the map swipe tool doesn't add any value :)
                # Activate Swipe Tool
                self.utils.app.gui.activate_layer(self.utils._supplies_layers[
                    self.utils._supplies_db.names.GC_PLOT_T])
                self.parent.activate_map_swipe_tool()

                # Send a custom mouse move on the map to make the map swipe tool's limit appear on the canvas
                coord_x = plots_extent.xMaximum() - (plots_extent.xMaximum(
                ) - plots_extent.xMinimum()) / 9  # 90%
                coord_y = plots_extent.yMaximum() - (plots_extent.yMaximum(
                ) - plots_extent.yMinimum()) / 2  # 50%

                coord_transform = self.utils.iface.mapCanvas(
                ).getCoordinateTransform()
                map_point = coord_transform.transform(coord_x, coord_y)
                widget_point = map_point.toQPointF().toPoint()
                global_point = self.utils.canvas.mapToGlobal(widget_point)

                self.utils.canvas.mousePressEvent(
                    QMouseEvent(QEvent.MouseButtonPress, global_point,
                                Qt.LeftButton, Qt.LeftButton, Qt.NoModifier))
                self.utils.canvas.mouseMoveEvent(
                    QMouseEvent(QEvent.MouseMove, widget_point + QPoint(1, 0),
                                Qt.NoButton, Qt.LeftButton, Qt.NoModifier))
                self.utils.canvas.mouseReleaseEvent(
                    QMouseEvent(QEvent.MouseButtonRelease,
                                widget_point + QPoint(1, 0), Qt.LeftButton,
                                Qt.LeftButton, Qt.NoModifier))

        # Once the query is done, activate the checkbox to alternate all plots/only selected plot
        self.chk_show_all_plots.setEnabled(True)

    def fill_table(self, search_criterion_supplies,
                   search_criterion_collected):
        """
        Shouldn't handle 'inverse' mode as we won't switch table columns at runtime.

        :param search_criterion_supplies: key-value pair to build an expression to search data in the supplies source
        :param search_criterion_collected: key-value pair to build an expression to search data in the collected source
        :return:
        """
        plural = LayerConfig.get_dict_plural(self.utils._db.names)
        dict_collected_parcels = self.utils.ladm_data.get_parcel_data_to_compare_changes(
            self.utils._db, search_criterion_collected)

        # Custom layer modifiers
        layer_modifiers = {
            LayerConfig.PREFIX_LAYER_MODIFIERS:
            LayerConfig.SUPPLIES_DB_PREFIX,
            LayerConfig.SUFFIX_LAYER_MODIFIERS:
            LayerConfig.SUPPLIES_DB_SUFFIX,
            LayerConfig.STYLE_GROUP_LAYER_MODIFIERS:
            Symbology().get_style_group_layer_modifiers(
                self.utils._supplies_db.names)
        }
        dict_supplies_parcels = self.utils.ladm_data.get_parcel_data_to_compare_changes_supplies(
            self.utils._supplies_db,
            search_criterion_supplies,
            layer_modifiers=layer_modifiers)

        # Before filling the table we make sure we get one and only one parcel attrs dict
        collected_attrs = dict()
        if dict_collected_parcels:
            collected_parcel_number = list(dict_collected_parcels.keys())[0]
            collected_attrs = dict_collected_parcels[collected_parcel_number][
                0]
            del collected_attrs[
                self.utils._db.names.
                T_ID_F]  # Remove this line if self.utils._db.names.T_ID_F is somehow needed

        supplies_attrs = dict()
        if dict_supplies_parcels:
            supplies_parcel_number = list(dict_supplies_parcels.keys())[0]
            supplies_attrs = dict_supplies_parcels[supplies_parcel_number][0]
            del supplies_attrs[
                self.utils._supplies_db.names.
                T_ID_F]  # Remove this line if self.utils._supplies_db.names,T_ID_F is somehow needed

        number_of_rows = len(collected_attrs) or len(supplies_attrs)
        self.tbl_changes_per_parcel.setRowCount(
            number_of_rows)  # t_id shouldn't be counted
        self.tbl_changes_per_parcel.setSortingEnabled(False)

        field_names = list(
            collected_attrs.keys()) if collected_attrs else list(
                supplies_attrs.keys())
        if PLOT_GEOMETRY_KEY in field_names:
            field_names.remove(
                PLOT_GEOMETRY_KEY)  # We'll handle plot geometry separately

        for row, field_name in enumerate(field_names):
            supplies_value = supplies_attrs[
                field_name] if field_name in supplies_attrs else NULL
            collected_value = collected_attrs[
                field_name] if field_name in collected_attrs else NULL
            field_alias = DICT_ALIAS_KEYS_CHANGE_DETECTION[
                field_name] if field_name in DICT_ALIAS_KEYS_CHANGE_DETECTION else field_name
            self.fill_row(field_alias, supplies_value, collected_value, row,
                          plural)

        if number_of_rows:  # At least one row in the table?
            self.fill_geometry_row(
                PLOT_GEOMETRY_KEY, supplies_attrs[PLOT_GEOMETRY_KEY]
                if PLOT_GEOMETRY_KEY in supplies_attrs else QgsGeometry(),
                collected_attrs[PLOT_GEOMETRY_KEY] if PLOT_GEOMETRY_KEY
                in collected_attrs else QgsGeometry(), number_of_rows - 1)

        self.tbl_changes_per_parcel.setSortingEnabled(True)

    def fill_row(self, field_name, supplies_value, collected_value, row,
                 plural):
        item = QTableWidgetItem(field_name)
        # item.setData(Qt.UserRole, parcel_attrs[self.names.T_ID_F])
        self.tbl_changes_per_parcel.setItem(row, 0, item)

        if field_name == DICT_ALIAS_KEYS_CHANGE_DETECTION[DICT_KEY_PARTIES]:
            item = self.fill_party_item(supplies_value)
            self.tbl_changes_per_parcel.setItem(row, 1, item)

            item = self.fill_party_item(collected_value)
            self.tbl_changes_per_parcel.setItem(row, 2, item)

            self.tbl_changes_per_parcel.setItem(row, 3, QTableWidgetItem())
            self.tbl_changes_per_parcel.item(row, 3).setBackground(
                Qt.green if supplies_value == collected_value else Qt.red)
        else:
            item = QTableWidgetItem(
                str(supplies_value) if supplies_value != NULL else '')
            #item.setData(Qt.UserRole, parcel_attrs[self.names.T_ID_F])
            self.tbl_changes_per_parcel.setItem(row, 1, item)

            item = QTableWidgetItem(
                str(collected_value) if collected_value != NULL else '')
            # item.setData(Qt.UserRole, parcel_attrs[self.names.T_ID_F])
            self.tbl_changes_per_parcel.setItem(row, 2, item)

            self.tbl_changes_per_parcel.setItem(row, 3, QTableWidgetItem())
            self.tbl_changes_per_parcel.item(row, 3).setBackground(
                Qt.green if supplies_value == collected_value else Qt.red)

    def fill_party_item(self, value):
        # Party's info comes in a list or a list of lists if it's a group party
        display_value = ''

        if value != NULL:
            if type(value) is list and value:
                display_value = "{} {}".format(
                    len(value),
                    QCoreApplication.translate("DockWidgetChanges", "parties")
                    if len(value) > 1 else QCoreApplication.translate(
                        "DockWidgetChanges", "party"))
        #else:
        #    display_value = QCoreApplication.translate("DockWidgetChanges", "0 parties")

        item = QTableWidgetItem(display_value)
        item.setData(Qt.UserRole, value)
        return item

    def fill_geometry_row(self, field_name, supplies_geom, collected_geom,
                          row):
        self.tbl_changes_per_parcel.setItem(
            row, 0,
            QTableWidgetItem(
                QCoreApplication.translate("DockWidgetChanges", "Geometry")))
        self.tbl_changes_per_parcel.setItem(
            row, 1,
            QTableWidgetItem(self.get_geometry_type_name(supplies_geom)))
        self.tbl_changes_per_parcel.setItem(
            row, 2,
            QTableWidgetItem(self.get_geometry_type_name(collected_geom)))

        self.tbl_changes_per_parcel.setItem(row, 3, QTableWidgetItem())
        self.tbl_changes_per_parcel.item(row, 3).setBackground(
            Qt.green if self.utils.compare_features_geometries(
                collected_geom, supplies_geom) else Qt.red)

    @staticmethod
    def get_geometry_type_name(geometry):
        if geometry is None:
            return QCoreApplication.translate("DockWidgetChanges",
                                              "No associated plot")
        elif geometry.type() == QgsWkbTypes.UnknownGeometry:
            return ''
        elif geometry.type() == QgsWkbTypes.PolygonGeometry:
            return QCoreApplication.translate("DockWidgetChanges", "Polygon")
        else:
            return "Type: {}".format(geometry.type())

    def alphanumeric_query(self):
        """
        Alphanumeric query (On supplies db)
        """
        option = self.cbo_parcel_fields.currentData()
        query = self.txt_alphanumeric_query.value()
        if query:
            if option == FMI_PARCEL_SEARCH_KEY:
                self.search_data(parcel_fmi=query)
            elif option == PARCEL_NUMBER_SEARCH_KEY:
                self.search_data(parcel_number=query)
            else:  # previous_parcel_number
                self.search_data(previous_parcel_number=query)

        else:
            self.utils.iface.messageBar().pushMessage(
                "Asistente LADM-COL",
                QCoreApplication.translate("DockWidgetChanges",
                                           "First enter a query"))

    def show_all_plots(self, state):
        try:
            self.utils._supplies_layers[
                self.utils._supplies_db.names.GC_PLOT_T].setSubsetString(
                    self._current_supplies_substring if not state else "")
        except RuntimeError:  # If the layer was previously removed
            pass

        try:
            self.utils._layers[self.utils._db.names.LC_PLOT_T].setSubsetString(
                self._current_substring if not state else "")
        except RuntimeError:  # If the layer was previously removed
            pass

    def initialize_tools_and_layers(self, panel=None):
        self.parent.deactivate_map_swipe_tool()
        self.show_all_plots(True)

    def initialize_maptool(self, new_tool, old_tool):
        if self.maptool_identify == old_tool:
            # custom identify was deactivated
            try:
                self.utils.canvas.mapToolSet.disconnect(
                    self.initialize_maptool)
            except TypeError as e:
                pass

            self.btn_identify_plot.setChecked(False)
        else:
            # custom identify was activated
            pass

    def close_panel(self):
        self.show_all_plots(
            True
        )  # Remove filter in plots layers if it was activate and panel is closed
        # custom identify was deactivated
        try:
            self.utils.canvas.mapToolSet.disconnect(self.initialize_maptool)
        except TypeError as e:
            pass

        self.utils.canvas.setMapTool(self.init_map_tool)
class CreateGroupPartyOperation(QDialog, DIALOG_UI):
    WIZARD_NAME = "CreateGroupPartyOperationWizard"
    WIZARD_TOOL_NAME = QCoreApplication.translate(WIZARD_NAME,
                                                  "Create group party")

    def __init__(self, iface, db, qgis_utils, parent=None):
        QDialog.__init__(self)
        self.setupUi(self)
        self.iface = iface
        self._db = db
        self.qgis_utils = qgis_utils
        self.logger = Logger()
        self.names = self._db.names
        self.help_strings = HelpStrings()

        self.data = {}  # {t_id: [display_text, denominator, numerator]}
        self.current_selected_parties = []  #  [t_ids]
        self.parties_to_group = {}  # {t_id: [denominator, numerator]}

        self._layers = {
            self.names.OP_GROUP_PARTY_T: {
                'name': self.names.OP_GROUP_PARTY_T,
                'geometry': None,
                LAYER: None
            },
            self.names.OP_PARTY_T: {
                'name': self.names.OP_PARTY_T,
                'geometry': None,
                LAYER: None
            },
            self.names.MEMBERS_T: {
                'name': self.names.MEMBERS_T,
                'geometry': None,
                LAYER: None
            },
            self.names.FRACTION_S: {
                'name': self.names.FRACTION_S,
                'geometry': None,
                LAYER: None
            },
            self.names.COL_GROUP_PARTY_TYPE_D: {
                'name': self.names.COL_GROUP_PARTY_TYPE_D,
                'geometry': None,
                LAYER: None
            }
        }

        # Fill combo of types
        col_group_party_type_table = self.qgis_utils.get_layer(
            self._db, self.names.COL_GROUP_PARTY_TYPE_D, None, True)
        if not col_group_party_type_table:
            return

        for feature in col_group_party_type_table.getFeatures():
            self.cbo_group_type.addItem(feature[self.names.DISPLAY_NAME_F],
                                        feature[self.names.T_ID_F])

        self.txt_search_party.setText("")
        self.btn_select.setEnabled(False)
        self.btn_deselect.setEnabled(False)

        self.tbl_selected_parties.setColumnCount(3)
        self.tbl_selected_parties.setColumnWidth(0, 140)
        self.tbl_selected_parties.setColumnWidth(1, 90)
        self.tbl_selected_parties.setColumnWidth(2, 90)
        self.tbl_selected_parties.sortItems(0, Qt.AscendingOrder)

        self.txt_search_party.textEdited.connect(self.search)
        self.lst_all_parties.itemSelectionChanged.connect(
            self.selection_changed_all)
        self.tbl_selected_parties.itemSelectionChanged.connect(
            self.selection_changed_selected)
        self.tbl_selected_parties.cellChanged.connect(self.valueEdited)
        self.btn_select_all.clicked.connect(self.select_all)
        self.btn_deselect_all.clicked.connect(self.deselect_all)
        self.btn_select.clicked.connect(self.select)
        self.btn_deselect.clicked.connect(self.deselect)
        self.buttonBox.helpRequested.connect(self.show_help)

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

    def closeEvent(self, e):
        # It's necessary to prevent message bar alert
        pass

    def required_layers_are_available(self):
        layers_are_available = self.qgis_utils.required_layers_are_available(
            self._db, self._layers, self.WIZARD_TOOL_NAME)
        return layers_are_available

    def load_parties_data(self):
        expression = QgsExpression(
            LayerConfig.get_dict_display_expressions(
                self.names)[self.names.OP_PARTY_T])
        context = QgsExpressionContext()
        data = dict()
        for feature in self._layers[
                self.names.OP_PARTY_T][LAYER].getFeatures():
            context.setFeature(feature)
            expression.prepare(context)
            value = expression.evaluate(context)
            data[feature[self.names.T_ID_F]] = [
                value if value != NULL else None, 0, 0
            ]
        self.set_parties_data(data)

    def set_parties_data(self, parties_data):
        """
        Initialize parties data.

        :param parties_data: Dictionary {t_id: [display_text, denominator, numerator]}
        :type parties_data: dict
        """
        self.data = parties_data
        self.update_lists()

    def search(self, text):
        self.update_lists(True)

    def selection_changed_all(self):
        self.btn_select.setEnabled(len(self.lst_all_parties.selectedItems()))

    def selection_changed_selected(self):
        self.btn_deselect.setEnabled(
            len(self.tbl_selected_parties.selectedItems()))

    def select_all(self):
        """
        SLOT. Select all parties listed from left list widget.
        """
        items_ids = []
        for index in range(self.lst_all_parties.count()):
            items_ids.append(
                self.lst_all_parties.item(index).data(Qt.UserRole))
        self.add_parties_to_selected(items_ids)

    def deselect_all(self):
        """
        SLOT. Remove all parties from left list widget.
        """
        items_ids = []
        for index in range(self.tbl_selected_parties.rowCount()):
            items_ids.append(
                self.tbl_selected_parties.item(index, 0).data(Qt.UserRole))
        self.remove_parties_from_selected(items_ids)

    def select(self):
        """
        SLOT. Select all parties highlighted in left list widget.
        """
        self.add_parties_to_selected([
            item.data(Qt.UserRole)
            for item in self.lst_all_parties.selectedItems()
        ])

    def deselect(self):
        """
        SLOT. Remove all parties highlighted in right list widget.
        """
        self.remove_parties_from_selected([
            item.data(Qt.UserRole)
            for item in self.tbl_selected_parties.selectedItems()
            if item.column() == 0
        ])

    def add_parties_to_selected(self, parties_ids):
        self.current_selected_parties.extend(parties_ids)
        self.update_lists()

    def remove_parties_from_selected(self, parties_ids):
        for party_id in parties_ids:
            self.current_selected_parties.remove(party_id)
            if party_id in self.parties_to_group:
                del self.parties_to_group[party_id]
        self.update_lists()

    def update_lists(self, only_update_all_list=False):
        """
        Update left list widget and optionally the right one.

        :param only_update_all_list: Only update left list widget.
        :type only_update_all_list: bool
        """
        # All parties
        self.lst_all_parties.clear()
        if self.txt_search_party.text():
            tmp_parties = {
                i: d
                for i, d in self.data.items()
                if self.txt_search_party.text().lower() in d[0].lower()
            }
        else:
            tmp_parties = copy.deepcopy(self.data)  # Copy all!

        for party_id in self.current_selected_parties:
            if party_id in tmp_parties:
                del tmp_parties[party_id]

        for i, d in tmp_parties.items():
            item = QListWidgetItem(d[0])
            item.setData(Qt.UserRole, i)
            self.lst_all_parties.addItem(item)

        if not only_update_all_list:
            # Selected parties
            self.tbl_selected_parties.clearContents()
            self.tbl_selected_parties.setRowCount(
                len(self.current_selected_parties))
            self.tbl_selected_parties.setColumnCount(3)
            self.tbl_selected_parties.setSortingEnabled(False)

            for row, party_id in enumerate(self.current_selected_parties):
                item = QTableWidgetItem(self.data[party_id][0])
                item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                item.setData(Qt.UserRole, party_id)
                self.tbl_selected_parties.setItem(row, 0, item)
                value_denominator = self.parties_to_group[party_id][
                    0] if party_id in self.parties_to_group else self.data[
                        party_id][1]
                self.tbl_selected_parties.setItem(
                    row, 1, QTableWidgetItem(str(value_denominator)))
                value_numerator = self.parties_to_group[party_id][
                    1] if party_id in self.parties_to_group else self.data[
                        party_id][2]
                self.tbl_selected_parties.setItem(
                    row, 2, QTableWidgetItem(str(value_numerator)))

            self.tbl_selected_parties.setSortingEnabled(True)

    def valueEdited(self, row, column):
        """
        SLOT. Update either the denominator or the numerator for given row.

        :param row: Edited row
        :type row: int
        :param column: Edited column
        :type column: int
        """
        if column != 0:
            party_id = self.tbl_selected_parties.item(row, 0).data(Qt.UserRole)
            value_denominator = self.tbl_selected_parties.item(row, 1).text()

            # While creating a row and the second column is created, the third
            # one doesn't exist, so use the value already stored for that case
            value_numerator = self.parties_to_group[party_id][
                1] if party_id in self.parties_to_group else 0
            if self.tbl_selected_parties.item(row, 2) is not None:
                value_numerator = self.tbl_selected_parties.item(row, 2).text()

            self.parties_to_group[party_id] = [
                value_denominator, value_numerator
            ]

    def accept(self):
        """ Overwrite the dialog's `accept
        <https://doc.qt.io/qt-5/qdialog.html#accept>`_ SLOT to store
        selected parties and numerator-denominator before closing the dialog.
        """
        self.parties_to_group = {}
        for index in range(self.tbl_selected_parties.rowCount()):
            k = self.tbl_selected_parties.item(index, 0).data(Qt.UserRole)
            try:
                v_n = int(self.tbl_selected_parties.item(index, 1).text())
            except ValueError as e:
                self.show_message(
                    QCoreApplication.translate(
                        "WizardTranslations",
                        "There are some invalid values in the numerator column. Fix them before continuing..."
                    ), Qgis.Warning)
                return
            try:
                v_d = int(self.tbl_selected_parties.item(index, 2).text())
            except ValueError as e:
                self.show_message(
                    QCoreApplication.translate(
                        "WizardTranslations",
                        "There are some invalid values in the denominator column. Fix them before continuing..."
                    ), Qgis.Warning)
                return

            self.parties_to_group[k] = [v_n, v_d]

        name = self.txt_group_name.text()
        group_party_type = self.cbo_group_type.itemData(
            self.cbo_group_type.currentIndex())
        dict_params = {
            self.names.COL_PARTY_T_NAME_F: name,
            self.names.COL_GROUP_PARTY_T_TYPE_F: group_party_type,
            'porcentajes': self.parties_to_group
        }

        res, msg = self.validate_group_party(dict_params)

        if not res:
            self.show_message(msg, Qgis.Warning)
            return

        self.save_group_party(self._db, [dict_params])

    def validate_group_party(self, params):
        name = params[self.names.COL_PARTY_T_NAME_F]
        group_party_type = params[self.names.COL_GROUP_PARTY_T_TYPE_F]
        porcentajes = params['porcentajes']

        if not porcentajes:
            return (False,
                    QCoreApplication.translate(
                        "CreateGroupParty",
                        "You need to select some parties to create a group."))
        elif len(porcentajes) == 1:
            return (
                False,
                QCoreApplication.translate(
                    "CreateGroupParty",
                    "There is just one party, you need to add at least two parties to a group."
                ))

        there_percents = False
        fraction = Fraction()
        for t, nd in porcentajes.items():
            if porcentajes[t] != [0, 0]:
                there_percents = True
                break

        if there_percents:
            for t, nd in porcentajes.items():
                if porcentajes[t][1] == 0:
                    return (
                        False,
                        QCoreApplication.translate(
                            "CreateGroupParty",
                            "There are denominators equal to zero. You need to change those values."
                        ))
                elif porcentajes[t][1] < porcentajes[t][0]:
                    return (
                        False,
                        QCoreApplication.translate(
                            "CreateGroupParty",
                            "The denominator cannot be less than the numerator."
                        ))
                else:
                    fraction = Fraction(porcentajes[t][0],
                                        porcentajes[t][1]) + fraction
            if fraction != 1.0:
                return (False,
                        QCoreApplication.translate(
                            "CreateGroupParty",
                            "The sum of the fractions must be equal to one."))

        return (True,
                QCoreApplication.translate("CreateGroupParty",
                                           "Validation passed!"))

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

    def save_group_party(self, db, params):
        """
        Save group party data into associated tables: self.names.OP_GROUP_PARTY_T,
        self.names.MEMBERS_T and self.names.FRACTION_S.

        params: List of dicts, where each dict is an independent group party:
            {
                self.names.COL_PARTY_T_NAME_F: '',
                self.names.COL_GROUP_PARTY_T_TYPE_F: '',
                'porcentajes': {
                    't_id_miembro': [20, 100], # numerador/denominador
                    't_id_miembro2': [40, 100]
                }
            }
        """
        # Disconnect from previous runs
        self.disconnect_signals()

        for group in params:
            # Create connections to react when a group party is stored to the DB
            self._layers[self.names.OP_GROUP_PARTY_T][
                LAYER].committedFeaturesAdded.connect(
                    partial(self.finish_group_party_saving,
                            group['porcentajes']))

            # First save the group party
            new_feature = QgsVectorLayerUtils().createFeature(
                self._layers[self.names.OP_GROUP_PARTY_T][LAYER])
            new_feature.setAttribute(
                self.names.COL_GROUP_PARTY_T_TYPE_F,
                group[self.names.COL_GROUP_PARTY_T_TYPE_F])
            new_feature.setAttribute(self.names.COL_PARTY_T_NAME_F,
                                     group[self.names.COL_PARTY_T_NAME_F])

            # TODO: Remove when local id and working space are defined
            new_feature.setAttribute(self.names.OID_T_LOCAL_ID_F, 1)
            new_feature.setAttribute(self.names.OID_T_NAMESPACE_F,
                                     self.names.OP_GROUP_PARTY_T)

            # TODO: Gui should allow users to ented namespace, local_id and date values
            #new_feature.setAttribute("p_espacio_de_nombres", self.names.OP_GROUP_PARTY_T)
            #new_feature.setAttribute("p_local_id", '0')
            #new_feature.setAttribute("comienzo_vida_util_version", 'now()')

            self.logger.info(__name__, "Saving Group Party: {}".format(group))
            with edit(self._layers[self.names.OP_GROUP_PARTY_T][LAYER]):
                self._layers[self.names.OP_GROUP_PARTY_T][LAYER].addFeature(
                    new_feature)

    def finish_group_party_saving(self, members, layer_id, features):
        try:
            self._layers[self.names.OP_GROUP_PARTY_T][
                LAYER].committedFeaturesAdded.disconnect()
        except TypeError as e:
            pass

        message = QCoreApplication.translate(
            "WizardTranslations",
            "'{}' tool has been closed because an error occurred while trying to save the data."
        ).format(self.WIZARD_TOOL_NAME)
        if len(features) != 1:
            message = QCoreApplication.translate(
                "WizardTranslations",
                "'{}' tool has been closed. We should have got only one group party... We cannot do anything with {} group parties"
            ).format(self.WIZARD_TOOL_NAME, len(features))
            self.logger.warning(
                __name__,
                "We should have got only one group party... We cannot do anything with {} group parties"
                .format(len(features)))
        else:
            fid = features[0].id()
            if not self._layers[self.names.OP_GROUP_PARTY_T][LAYER].getFeature(
                    fid).isValid():
                self.logger.warning(
                    __name__, "Feature not found in table Group Party...")
            else:
                group_party_id = self._layers[self.names.OP_GROUP_PARTY_T][
                    LAYER].getFeature(fid)[self.names.T_ID_F]

                # Now save members
                party_ids = list()
                for party_id, fraction in members.items():
                    # Create connections to react when a group party is stored to the DB
                    self._layers[self.names.MEMBERS_T][
                        LAYER].committedFeaturesAdded.connect(
                            partial(self.finish_member_saving, fraction))

                    new_feature = QgsVectorLayerUtils().createFeature(
                        self._layers[self.names.MEMBERS_T][LAYER])
                    new_feature.setAttribute(
                        self.names.MEMBERS_T_GROUP_PARTY_F, group_party_id)
                    new_feature.setAttribute(self.names.MEMBERS_T_PARTY_F,
                                             party_id)
                    self.logger.info(
                        __name__,
                        "Saving group party's member ({}: {}).".format(
                            group_party_id, party_id))
                    with edit(self._layers[self.names.MEMBERS_T][LAYER]):
                        self._layers[self.names.MEMBERS_T][LAYER].addFeature(
                            new_feature)
                        party_ids.append(party_id)

                if len(party_ids):
                    message = QCoreApplication.translate(
                        "WizardTranslations",
                        "The new group party (t_id={}) was successfully created and associated with its corresponding party(ies) (t_id={})!"
                    ).format(group_party_id,
                             ", ".join([str(b) for b in party_ids]))
                else:
                    message = QCoreApplication.translate(
                        "WizardTranslations",
                        "The new group party (t_id={}) was successfully created but this one wasn't associated with a party(ies)"
                    ).format(group_party_id)
        self.close_wizard(message)

    def finish_member_saving(self, fraction, layer_id, features):
        try:
            self._layers[self.names.
                         MEMBERS_T][LAYER].committedFeaturesAdded.disconnect()
        except TypeError as e:
            pass

        if len(features) != 1:
            self.logger.warning(
                __name__,
                "We should have got only one member... We cannot do anything with {} members"
                .format(len(features)))
        else:
            fid = features[0].id()
            if not self._layers[self.names.MEMBERS_T][LAYER].getFeature(
                    fid).isValid():
                self.logger.warning(__name__,
                                    "Feature not found in table Members...")
            else:
                member_id = self._layers[self.names.MEMBERS_T][
                    LAYER].getFeature(fid)[self.names.T_ID_F]

                if fraction == [0, 0]:
                    return

                # And finally save fractions
                new_feature = QgsVectorLayerUtils().createFeature(
                    self._layers[self.names.FRACTION_S][LAYER])
                new_feature.setAttribute(self.names.FRACTION_S_MEMBER_F,
                                         member_id)
                new_feature.setAttribute(self.names.FRACTION_S_NUMERATOR_F,
                                         fraction[0])
                new_feature.setAttribute(self.names.FRACTION_S_DENOMINATOR_F,
                                         fraction[1])
                with edit(self._layers[self.names.FRACTION_S][LAYER]):
                    self.logger.info(
                        __name__, "Saving member's fraction ({}: {}).".format(
                            member_id, fraction))
                    self._layers[self.names.FRACTION_S][LAYER].addFeature(
                        new_feature)

    def close_wizard(self, message=None, show_message=True):
        if message is None:
            message = QCoreApplication.translate(
                "WizardTranslations",
                "'{}' tool has been closed.").format(self.WIZARD_TOOL_NAME)
        if show_message:
            self.logger.info_msg(__name__, message)
        self.disconnect_signals()
        self.close()

    def disconnect_signals(self):
        try:
            self._layers[self.names.OP_GROUP_PARTY_T][
                LAYER].committedFeaturesAdded.disconnect()
        except TypeError as e:
            pass
        try:
            self._layers[self.names.
                         MEMBERS_T][LAYER].committedFeaturesAdded.disconnect()
        except TypeError as e:
            pass

    def show_help(self):
        self.qgis_utils.show_help("group_party")
コード例 #28
0
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()
コード例 #29
0
class CreatePointsSurveyWizard(QWizard, WIZARD_UI):
    WIZARD_NAME = "CreatePointsSurveyWizard"
    WIZARD_TOOL_NAME = QCoreApplication.translate(WIZARD_NAME, "Create Point")

    def __init__(self, iface, db):
        QWizard.__init__(self)
        self.setupUi(self)
        self.iface = iface
        self._db = db
        self.logger = Logger()
        self.app = AppInterface()

        self.names = self._db.names
        self.help_strings = HelpStrings()

        self._layers = {
            self.names.LC_BOUNDARY_POINT_T: None,
            self.names.LC_SURVEY_POINT_T: None,
            self.names.LC_CONTROL_POINT_T: None
        }

        self.target_layer = None

        # Auxiliary data to set nonlinear next pages
        self.pages = [self.wizardPage1, self.wizardPage2, self.wizardPage3]
        self.dict_pages_ids = {self.pages[idx] : pid for idx, pid in enumerate(self.pageIds())}

        self.mMapLayerComboBox.setFilters(QgsMapLayerProxyModel.PointLayer)

        # Set connections
        self.btn_browse_file.clicked.connect(
            make_file_selector(self.txt_file_path,
                               file_filter=QCoreApplication.translate("WizardTranslations",'CSV File (*.csv *.txt)')))
        self.txt_file_path.textChanged.connect(self.file_path_changed)
        self.crsSelector.crsChanged.connect(self.crs_changed)
        self.crs = ""  # SRS auth id
        self.txt_delimiter.textChanged.connect(self.fill_long_lat_combos)
        self.mMapLayerComboBox.layerChanged.connect(self.import_layer_changed)

        self.known_delimiters = [
            {'name': ';', 'value': ';'},
            {'name': ',', 'value': ','},
            {'name': 'tab', 'value': '\t'},
            {'name': 'space', 'value': ' '},
            {'name': '|', 'value': '|'},
            {'name': '~', 'value': '~'},
            {'name': 'Other', 'value': ''}
        ]
        self.cbo_delimiter.addItems([ item['name'] for item in self.known_delimiters ])
        self.cbo_delimiter.currentTextChanged.connect(self.separator_changed)

        self.restore_settings()

        self.txt_file_path.textChanged.emit(self.txt_file_path.text())

        self.rad_boundary_point.toggled.connect(self.point_option_changed)
        self.rad_control_point.toggled.connect(self.point_option_changed)
        self.rad_csv.toggled.connect(self.adjust_page_2_controls)
        self.point_option_changed() # Initialize it
        self.button(QWizard.FinishButton).clicked.connect(self.finished_dialog)
        self.currentIdChanged.connect(self.current_page_changed)

        self.txt_help_page_2.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_2_OPTION_CSV)

        self.wizardPage2.setButtonText(QWizard.FinishButton,
                                       QCoreApplication.translate("WizardTranslations",
                                            "Import"))
        self.txt_help_page_3.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_3_OPTION_CSV)
        self.txt_help_page_3.anchorClicked.connect(self.save_template)
        self.button(QWizard.HelpButton).clicked.connect(self.show_help)
        self.rejected.connect(self.close_wizard)

        # Set MessageBar for QWizard
        self.bar = QgsMessageBar()
        self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.setLayout(QGridLayout())
        self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop)

    def nextId(self):
        """
        Set navigation order. Should return an integer. -1 is Finish.
        """
        if self.currentId() == self.dict_pages_ids[self.wizardPage1]:
            return self.dict_pages_ids[self.wizardPage2]
        elif self.currentId() == self.dict_pages_ids[self.wizardPage2]:
            if self.rad_csv.isChecked():
                return self.dict_pages_ids[self.wizardPage3]
            elif self.rad_refactor.isChecked():
                return -1
        elif self.currentId() == self.dict_pages_ids[self.wizardPage3]:
            return -1
        else:
            return -1

    def current_page_changed(self, id):
        """
        Reset the Next button. Needed because Next might have been disabled by a
        condition in a another SLOT.
        """
        enable_next_wizard(self)

        if id == self.dict_pages_ids[self.wizardPage2]:
            self.adjust_page_2_controls()
        elif id == self.dict_pages_ids[self.wizardPage3]:
            self.set_buttons_visible(False)
            self.set_buttons_enabled(False)

            QCoreApplication.processEvents()
            self.check_z_in_geometry()
            QCoreApplication.processEvents()
            self.fill_long_lat_combos("")
            QCoreApplication.processEvents()

            self.set_buttons_visible(True)
            self.set_buttons_enabled(True)

    def set_buttons_visible(self, visible):
        self.button(self.BackButton).setVisible(visible)
        self.button(self.FinishButton).setVisible(visible)
        self.button(self.CancelButton).setVisible(visible)

    def set_buttons_enabled(self, enabled):
        self.wizardPage3.setEnabled(enabled)
        self.button(self.BackButton).setEnabled(enabled)
        self.button(self.FinishButton).setEnabled(enabled)
        self.button(self.CancelButton).setEnabled(enabled)

    def check_z_in_geometry(self):
        self.target_layer = self.app.core.get_layer(self._db, self.current_point_name(), load=True)
        if not self.target_layer:
            return

        if not QgsWkbTypes().hasZ(self.target_layer.wkbType()):
            self.labelZ.setEnabled(False)
            self.cbo_elevation.setEnabled(False)
            msg = QCoreApplication.translate("WizardTranslations",
                                             "The current model does not support 3D geometries")
            self.cbo_elevation.setToolTip(msg)
            self.labelZ.setToolTip(msg)
        else:
            self.labelZ.setEnabled(True)
            self.cbo_elevation.setEnabled(True)
            self.labelZ.setToolTip("")
            self.cbo_elevation.setToolTip("")

    def adjust_page_2_controls(self):
        self.cbo_mapping.clear()
        self.cbo_mapping.addItem("")
        self.cbo_mapping.addItems(self.app.core.get_field_mappings_file_names(self.current_point_name()))

        if self.rad_refactor.isChecked():
            self.lbl_refactor_source.setEnabled(True)
            self.mMapLayerComboBox.setEnabled(True)
            self.lbl_field_mapping.setEnabled(True)
            self.cbo_mapping.setEnabled(True)
            self.import_layer_changed(self.mMapLayerComboBox.currentLayer())

            disable_next_wizard(self)
            self.wizardPage2.setFinalPage(True)
            self.txt_help_page_2.setHtml(self.help_strings.get_refactor_help_string(self._db, self._layers[self.current_point_name()]))

        elif self.rad_csv.isChecked():
            self.lbl_refactor_source.setEnabled(False)
            self.mMapLayerComboBox.setEnabled(False)
            self.lbl_field_mapping.setEnabled(False)
            self.cbo_mapping.setEnabled(False)
            self.lbl_refactor_source.setStyleSheet('')

            enable_next_wizard(self)
            self.wizardPage2.setFinalPage(False)
            self.txt_help_page_2.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_2_OPTION_CSV)

    def point_option_changed(self):
        if self.rad_boundary_point.isChecked():
            self.gbx_page_2.setTitle(QCoreApplication.translate("WizardTranslations", "Load data to Boundary Points..."))
            self.gbx_page_3.setTitle(QCoreApplication.translate("WizardTranslations", "Configure CSV data source for Boundary Points..."))
            self.txt_help_page_1.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_1_OPTION_BP)
        elif self.rad_survey_point.isChecked(): # self.rad_survey_point is checked
            self.gbx_page_2.setTitle(QCoreApplication.translate("WizardTranslations", "Load data to Survey Points..."))
            self.gbx_page_3.setTitle(QCoreApplication.translate("WizardTranslations", "Configure CSV data source for Survey Points..."))
            self.txt_help_page_1.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_1_OPTION_SP)
        else: # self.rad_control_point is checked
            self.gbx_page_2.setTitle(QCoreApplication.translate("WizardTranslations", "Load data to Control Points..."))
            self.gbx_page_3.setTitle(QCoreApplication.translate("WizardTranslations", "Configure CSV data source for Control Points..."))
            self.txt_help_page_1.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_1_OPTION_CP)

    def finished_dialog(self):
        self.save_settings()

        if self.rad_refactor.isChecked():
            output_layer_name = self.current_point_name()

            if self.mMapLayerComboBox.currentLayer() is not None:
                field_mapping = self.cbo_mapping.currentText()
                res_etl_model = self.app.core.show_etl_model(self._db,
                                                               self.mMapLayerComboBox.currentLayer(),
                                                               output_layer_name,
                                                               field_mapping=field_mapping)

                if res_etl_model:
                    self.app.gui.redraw_all_layers()  # Redraw all layers to show imported data

                    # If the result of the etl_model is successful and we used a stored recent mapping, we delete the
                    # previous mapping used (we give preference to the latest used mapping)
                    if field_mapping:
                        self.app.core.delete_old_field_mapping(field_mapping)

                    self.app.core.save_field_mapping(output_layer_name)

            else:
                self.logger.warning_msg(__name__, QCoreApplication.translate("WizardTranslations",
                    "Select a source layer to set the field mapping to '{}'.").format(output_layer_name))

            self.close_wizard()

        elif self.rad_csv.isChecked():
            self.prepare_copy_csv_points_to_db()

    def close_wizard(self, message=None, show_message=True):
        if message is None:
            message = QCoreApplication.translate("WizardTranslations", "'{}' tool has been closed.").format(self.WIZARD_TOOL_NAME)
        if show_message:
            self.logger.info_msg(__name__, message)
        self.close()

    def current_point_name(self):
        if self.rad_boundary_point.isChecked():
            return self.names.LC_BOUNDARY_POINT_T
        elif self.rad_survey_point.isChecked():
            return self.names.LC_SURVEY_POINT_T
        else:
            return self.names.LC_CONTROL_POINT_T

    def prepare_copy_csv_points_to_db(self):
        csv_path = self.txt_file_path.text().strip()

        if not csv_path or not os.path.exists(csv_path):
            self.logger.warning_msg(__name__, QCoreApplication.translate("WizardTranslations",
                                                                         "No CSV file given or file doesn't exist."))
            return

        target_layer_name = self.current_point_name()

        with OverrideCursor(Qt.WaitCursor):
            csv_layer = self.app.core.csv_to_layer(csv_path,
                                                   self.txt_delimiter.text(),
                                                   self.cbo_longitude.currentText(),
                                                   self.cbo_latitude.currentText(),
                                                   self.crs,
                                                   self.cbo_elevation.currentText() or None,
                                                   self.detect_decimal_point(csv_path))

            self.app.core.copy_csv_to_db(csv_layer, self._db, target_layer_name)

    def required_layers_are_available(self):
        layers_are_available = self.app.core.required_layers_are_available(self._db, self._layers, self.WIZARD_TOOL_NAME)
        return layers_are_available

    def file_path_changed(self):
        self.autodetect_separator()
        self.fill_long_lat_combos("")
        self.cbo_delimiter.currentTextChanged.connect(self.separator_changed)

    def detect_decimal_point(self, csv_path):
        if os.path.exists(csv_path):
            with open(csv_path) as file:
                file.readline() # headers
                data = file.readline().strip() # 1st line with data

            if data:
                fields = self.get_fields_from_csv_file(csv_path)
                if self.cbo_latitude.currentText() in fields:
                    num_col = data.split(self.cbo_delimiter.currentText())[fields.index(self.cbo_latitude.currentText())]
                    for decimal_point in ['.', ',']:
                        if decimal_point in num_col:
                            return decimal_point

        return '.' # just use the default one

    def autodetect_separator(self):
        csv_path = self.txt_file_path.text().strip()
        if os.path.exists(csv_path):
            with open(csv_path) as file:
                first_line = file.readline()
                for delimiter in self.known_delimiters:
                    if delimiter['value'] == '':
                        continue
                    # if separator works like a column separator in header
                    # number of cols is greater than 1
                    if len(first_line.split(delimiter['value'])) > 1:
                        self.cbo_delimiter.setCurrentText(delimiter['name'])
                        return

    def update_crs_info(self):
        self.crsSelector.setCrs(QgsCoordinateReferenceSystem(self.crs))

    def crs_changed(self):
        self.crs = get_crs_authid(self.crsSelector.crs())
        if self.crs != DEFAULT_SRS_AUTHID:
            self.lbl_crs.setStyleSheet('color: orange')
            self.lbl_crs.setToolTip(QCoreApplication.translate("WizardTranslations",
                "Your CSV data will be reprojected for you to '{}' (Colombian National Origin),<br>before attempting to import it into LADM-COL.").format(DEFAULT_SRS_AUTHID))
        else:
            self.lbl_crs.setStyleSheet('')
            self.lbl_crs.setToolTip(QCoreApplication.translate("WizardTranslations", "Coordinate Reference System"))

    def fill_long_lat_combos(self, text):
        csv_path = self.txt_file_path.text().strip()
        self.cbo_longitude.clear()
        self.cbo_latitude.clear()
        self.cbo_elevation.clear()
        if os.path.exists(csv_path):
            self.button(QWizard.FinishButton).setEnabled(True)

            fields = self.get_fields_from_csv_file(csv_path)
            fields_dict = {field: field.lower() for field in fields}
            if not fields:
                self.button(QWizard.FinishButton).setEnabled(False)
                return

            self.cbo_longitude.addItems(fields)
            self.cbo_latitude.addItems(fields)
            self.cbo_elevation.addItems([""] + fields)

            # Heuristics to suggest values for x, y and z
            x_potential_names = ['x', 'lon', 'long', 'longitud', 'longitude', 'este', 'east', 'oeste', 'west']
            y_potential_names = ['y', 'lat', 'latitud', 'latitude', 'norte', 'north']
            z_potential_names = ['z', 'altura', 'elevacion', 'elevation', 'elevación', 'height']
            for x_potential_name in x_potential_names:
                for k,v in fields_dict.items():
                    if x_potential_name == v:
                        self.cbo_longitude.setCurrentText(k)
                        break
            for y_potential_name in y_potential_names:
                for k, v in fields_dict.items():
                    if y_potential_name == v:
                        self.cbo_latitude.setCurrentText(k)
                        break
            if self.cbo_elevation.isEnabled():
                for z_potential_name in z_potential_names:
                    for k, v in fields_dict.items():
                        if z_potential_name == v:
                            self.cbo_elevation.setCurrentText(k)
                            break

        else:
            self.button(QWizard.FinishButton).setEnabled(False)

    def get_fields_from_csv_file(self, csv_path):
        if not self.txt_delimiter.text():
            return []

        error_reading = False
        try:
            reader  = open(csv_path, "r")
        except IOError:
            error_reading = True
        line = reader.readline().replace("\n", "")
        reader.close()
        if not line:
            error_reading = True
        else:
            return line.split(self.txt_delimiter.text())

        if error_reading:
            self.logger.warning_msg(__name__, QCoreApplication.translate("WizardTranslations",
                "It was not possible to read field names from the CSV. Check the file and try again."))
        return []

    def separator_changed(self, text):
        # first ocurrence
        value = next((x['value'] for x in self.known_delimiters if x['name'] == text), '')
        self.txt_delimiter.setText(value)
        if value == '':
            self.txt_delimiter.setEnabled(True)
        else:
            self.txt_delimiter.setEnabled(False)

    def save_template(self, url):
        link = url.url()
        if self.rad_boundary_point.isChecked():
            if link == '#template':
                self.download_csv_file('template_boundary_points.csv')
            elif link == '#data':
                self.download_csv_file('sample_boundary_points.csv')
        elif self.rad_survey_point.isChecked():
            if link == '#template':
                self.download_csv_file('template_survey_points.csv')
            elif link == '#data':
                self.download_csv_file('sample_survey_points.csv')
        elif self.rad_control_point.isChecked():
            if link == '#template':
                self.download_csv_file('template_control_points.csv')
            elif link == '#data':
                self.download_csv_file('sample_control_points.csv')

    def download_csv_file(self, filename):
        settings = QSettings()
        settings.setValue('Asistente-LADM-COL/wizards/points_csv_file_delimiter', self.txt_delimiter.text().strip())

        new_filename, filter = QFileDialog.getSaveFileName(self,
                                   QCoreApplication.translate("WizardTranslations",
                                                              "Save File"),
                                   os.path.join(settings.value('Asistente-LADM-COL/wizards/points_download_csv_path', '.'), filename),
                                   QCoreApplication.translate("WizardTranslations",
                                                              "CSV File (*.csv *.txt)"))

        if new_filename:
            settings.setValue('Asistente-LADM-COL/wizards/points_download_csv_path', os.path.dirname(new_filename))
            template_file = QFile(":/Asistente-LADM-COL/resources/csv/" + filename)

            if not template_file.exists():
                self.logger.critical(__name__, "CSV doesn't exist! Probably due to a missing 'make' execution to generate resources...")
                msg = QCoreApplication.translate("WizardTranslations", "CSV file not found. Update your plugin. For details see log.")
                self.show_message(msg, Qgis.Warning)
                return

            if os.path.isfile(new_filename):
                self.logger.info(__name__, 'Removing existing file {}...'.format(new_filename))
                os.chmod(new_filename, 0o777)
                os.remove(new_filename)

            if template_file.copy(new_filename):
                os.chmod(new_filename, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
                msg = QCoreApplication.translate("WizardTranslations", """The file <a href="file:///{}">{}</a> was successfully saved!""").format(normalize_local_url(new_filename), os.path.basename(new_filename))
                self.show_message(msg, Qgis.Info)
            else:
                self.logger.warning(__name__, 'There was an error copying the CSV file {}!'.format(new_filename))
                msg = QCoreApplication.translate("WizardTranslations", "The file couldn\'t be saved.")
                self.show_message(msg, Qgis.Warning)

    def import_layer_changed(self, layer):
        if layer:
            crs = get_crs_authid(layer.crs())
            if crs != DEFAULT_SRS_AUTHID:
                self.lbl_refactor_source.setStyleSheet('color: orange')
                self.lbl_refactor_source.setToolTip(QCoreApplication.translate("WizardTranslations",
                                                                   "This layer will be reprojected for you to '{}' (Colombian National Origin),<br>before attempting to import it into LADM-COL.").format(
                    DEFAULT_SRS_AUTHID))
            else:
                self.lbl_refactor_source.setStyleSheet('')
                self.lbl_refactor_source.setToolTip('')

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

    def save_settings(self):
        settings = QSettings()
        point_type = None
        if self.rad_boundary_point.isChecked():
            point_type = 'boundary_point'
        elif self.rad_survey_point.isChecked():
            point_type = 'survey_point'
        else:
            point_type = 'control_point'

        settings.setValue('Asistente-LADM-COL/wizards/points_add_points_type', point_type)
        settings.setValue('Asistente-LADM-COL/wizards/points_load_data_type', 'csv' if self.rad_csv.isChecked() else 'refactor')
        settings.setValue('Asistente-LADM-COL/wizards/points_add_points_csv_file', self.txt_file_path.text().strip())
        settings.setValue('Asistente-LADM-COL/wizards/points_csv_file_delimiter', self.txt_delimiter.text().strip())
        settings.setValue('Asistente-LADM-COL/wizards/points_csv_crs', self.crs)

    def restore_settings(self):
        settings = QSettings()
        point_type = settings.value('Asistente-LADM-COL/wizards/points_add_points_type') or 'boundary_point'
        if point_type == 'boundary_point':
            self.rad_boundary_point.setChecked(True)
        elif point_type == 'survey_point':
            self.rad_survey_point.setChecked(True)
        else: # 'control_point'
            self.rad_control_point.setChecked(True)

        load_data_type = settings.value('Asistente-LADM-COL/wizards/points_load_data_type') or 'csv'
        if load_data_type == 'refactor':
            self.rad_refactor.setChecked(True)
        else:
            self.rad_csv.setChecked(True)

        self.txt_file_path.setText(settings.value('Asistente-LADM-COL/wizards/points_add_points_csv_file'))
        self.txt_delimiter.setText(settings.value('Asistente-LADM-COL/wizards/points_csv_file_delimiter'))

        self.crs = settings.value('Asistente-LADM-COL/wizards/points_csv_crs', DEFAULT_SRS_AUTHID, str)
        self.update_crs_info()

    def show_help(self):
        show_plugin_help("create_points")
コード例 #30
0
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)