Exemple #1
0
    def _set_widget_type(self, layer, column, widget_type_id):
        """
        Sets the widget type for each field into
        QGIS form configuration.
        :param layer: The layer to which the widget type is set.
        :type layer: QgsVectorLayer
        :param column: STDM column object
        :type column: Object
        :param widget_type_id: The widget type id which could be
         the default QGIS or the custom STDM widget id which is
         based on column.TYPE_INFO.
        :type widget_type_id: String
        :return: None
        :rtype:NoneType
        """
        idx = layer.fields().indexFromName(column.name)
        # Set Alias/ Display names for the column names
        layer.setFieldAlias(
            idx,
            column.header()
        )

        setup = QgsEditorWidgetSetup(widget_type_id, {})
        layer.setEditorWidgetSetup(idx, setup)
Exemple #2
0
    def testExportAttributes(self):
        """ test exporting feature's attributes to JSON object """
        fields = QgsFields()

        # test empty attributes
        feature = QgsFeature(fields, 5)
        expected = "{}"
        self.assertEqual(QgsJsonUtils.exportAttributes(feature), expected)

        # test feature with attributes
        fields.append(QgsField("name", QVariant.String))
        fields.append(QgsField("cost", QVariant.Double))
        fields.append(QgsField("population", QVariant.Int))

        feature = QgsFeature(fields, 5)
        feature.setGeometry(QgsGeometry(QgsPoint(5, 6)))
        feature.setAttributes(['Valsier Peninsula', 6.8, 198])

        expected = """{"name":"Valsier Peninsula",
"cost":6.8,
"population":198}"""
        self.assertEqual(QgsJsonUtils.exportAttributes(feature), expected)

        # test using field formatters
        source = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer",
                                "parent", "memory")
        pf1 = QgsFeature()
        pf1.setFields(source.fields())
        pf1.setAttributes(["test1", 1])

        setup = QgsEditorWidgetSetup('ValueMap', {"map": {"one": 1, "two": 2, "three": 3}})
        source.setEditorWidgetSetup(1, setup)

        expected = """{"fldtxt":"test1",
"fldint":"one"}"""
        self.assertEqual(QgsJsonUtils.exportAttributes(pf1, source), expected)
Exemple #3
0
    def create_relations(self):
        # Real relation
        # rel = QgsRelation()
        # rel.setName('Relation installation - count')
        # rel.setId('rel_installation_count')
        # rel.setReferencedLayer(self.layers['installation'].id())
        # rel.setReferencingLayer(self.layers['count'].id())
        # rel.addFieldPair('id_installation', 'id')
        # QgsProject.instance().relationManager().addRelation(rel)

        widget = QgsEditorWidgetSetup(
            'ValueRelation', {
                'AllowMulti': False,
                'AllowNull': False,
                'FilterExpression': '',
                'Key': 'id',
                'Layer': self.layers['installation'].id(),
                'OrderByValue': False,
                'UseCompleter': False,
                'Value': 'name'
            })
        data_provider = self.layers['count'].dataProvider()
        index = data_provider.fieldNameIndex('id_installation')
        self.layers['count'].setEditorWidgetSetup(index, widget)

        widget = QgsEditorWidgetSetup(
            'ValueRelation', {
                'AllowMulti': False,
                'AllowNull': True,
                'FilterExpression': '',
                'Key': 'id',
                'Layer': self.layers['class'].id(),
                'OrderByValue': False,
                'UseCompleter': False,
                'Value': 'name'
            })
        data_provider = self.layers['count'].dataProvider()
        index = data_provider.fieldNameIndex('id_class')
        self.layers['count'].setEditorWidgetSetup(index, widget)

        widget = QgsEditorWidgetSetup(
            'ValueRelation', {
                'AllowMulti': False,
                'AllowNull': False,
                'FilterExpression': '',
                'Key': 'id',
                'Layer': self.layers['sensor_type'].id(),
                'OrderByValue': False,
                'UseCompleter': False,
                'Value': 'name'
            })
        data_provider = self.layers['count'].dataProvider()
        index = data_provider.fieldNameIndex('id_sensor_type')
        self.layers['count'].setEditorWidgetSetup(index, widget)

        widget = QgsEditorWidgetSetup(
            'ValueRelation', {
                'AllowMulti': False,
                'AllowNull': False,
                'FilterExpression': '',
                'Key': 'id',
                'Layer': self.layers['device'].id(),
                'OrderByValue': False,
                'UseCompleter': False,
                'Value': 'name'
            })
        data_provider = self.layers['count'].dataProvider()
        index = data_provider.fieldNameIndex('id_device')
        self.layers['count'].setEditorWidgetSetup(index, widget)

        widget = QgsEditorWidgetSetup(
            'ValueRelation', {
                'AllowMulti': False,
                'AllowNull': False,
                'FilterExpression': '',
                'Key': 'id',
                'Layer': self.layers['model'].id(),
                'OrderByValue': False,
                'UseCompleter': False,
                'Value': 'name'
            })
        data_provider = self.layers['count'].dataProvider()
        index = data_provider.fieldNameIndex('id_model')
        self.layers['count'].setEditorWidgetSetup(index, widget)
    def create(self, path: str, qgis_project: QgsProject,
               group: QgsLayerTreeGroup):
        qgis_project.setAutoTransaction(self.auto_transaction)
        qgis_project.setEvaluateDefaultValues(self.evaluate_default_values)
        qgis_layers = list()
        for layer in self.layers:
            qgis_layer = layer.create()
            self.layer_added.emit(qgis_layer.id())
            if not self.crs and qgis_layer.isSpatial():
                self.crs = qgis_layer.crs()

            qgis_layers.append(qgis_layer)

        qgis_project.addMapLayers(qgis_layers, not self.legend)

        if self.crs:
            if isinstance(self.crs, QgsCoordinateReferenceSystem):
                qgis_project.setCrs(self.crs)
            else:
                crs = QgsCoordinateReferenceSystem.fromEpsgId(self.crs)
                if not crs.isValid():
                    crs = QgsCoordinateReferenceSystem(self.crs)  # Fallback
                qgis_project.setCrs(crs)

        qgis_relations = list(
            qgis_project.relationManager().relations().values())
        dict_domains = {
            layer.layer.id(): layer.is_domain
            for layer in self.layers
        }
        for relation in self.relations:
            rel = relation.create(qgis_project, qgis_relations)
            assert rel.isValid()
            qgis_relations.append(rel)

            referencing_layer = rel.referencingLayer()
            referencing_field_constraints = referencing_layer.fieldConstraints(
                rel.referencingFields()[0])
            allow_null = not bool(referencing_field_constraints
                                  & QgsFieldConstraints.ConstraintNotNull)

            # If we have an extended ili2db domain, we need to filter its values
            filter_expression = "\"{}\" = '{}'".format(
                ENUM_THIS_CLASS_COLUMN, relation.child_domain_name
            ) if relation.child_domain_name else ''

            if rel.referencedLayerId() in dict_domains and dict_domains[
                    rel.referencedLayerId()]:
                editor_widget_setup = QgsEditorWidgetSetup(
                    'RelationReference', {
                        'Relation': rel.id(),
                        'ShowForm': False,
                        'OrderByValue': True,
                        'ShowOpenFormButton': False,
                        'AllowNULL': allow_null,
                        'FilterExpression': filter_expression,
                        'FilterFields': list()
                    })
            else:
                editor_widget_setup = QgsEditorWidgetSetup(
                    'RelationReference', {
                        'Relation': rel.id(),
                        'ShowForm': False,
                        'OrderByValue': True,
                        'ShowOpenFormButton': False,
                        'AllowAddFeatures': True,
                        'AllowNULL': allow_null
                    })

            referencing_layer = rel.referencingLayer()
            referencing_layer.setEditorWidgetSetup(rel.referencingFields()[0],
                                                   editor_widget_setup)

        qgis_project.relationManager().setRelations(qgis_relations)

        # Set Bag of Enum widget
        for layer_name, bag_of_enum in self.bags_of_enum.items():
            for attribute, bag_of_enum_info in bag_of_enum.items():
                layer_obj = bag_of_enum_info[0]
                cardinality = bag_of_enum_info[1]
                domain_table = bag_of_enum_info[2]
                key_field = bag_of_enum_info[3]
                value_field = bag_of_enum_info[4]

                minimal_selection = cardinality.startswith('1')

                current_layer = layer_obj.create()

                field_widget = 'ValueRelation'
                field_widget_config = {
                    'AllowMulti': True,
                    'UseCompleter': False,
                    'Value': value_field,
                    'OrderByValue': False,
                    'AllowNull': True,
                    'Layer': domain_table.create().id(),
                    'FilterExpression': '',
                    'Key': key_field,
                    'NofColumns': 1
                }
                field_idx = current_layer.fields().indexOf(attribute)
                setup = QgsEditorWidgetSetup(field_widget, field_widget_config)
                current_layer.setEditorWidgetSetup(field_idx, setup)
                if minimal_selection:
                    constraint_expression = 'array_length("{}")>0'.format(
                        attribute)
                    current_layer.setConstraintExpression(
                        field_idx, constraint_expression,
                        self.tr('The minimal selection is 1'))

        for layer in self.layers:
            layer.create_form(self)

        if self.legend:
            self.legend.create(qgis_project, group)

        if path:
            qgis_project.write(path)
Exemple #5
0
    def create_relations(self):
        # Real relation
        # rel = QgsRelation()
        # rel.setName('Relation installation - count')
        # rel.setId('rel_installation_count')
        # rel.setReferencedLayer(self.layers['installation'].id())
        # rel.setReferencingLayer(self.layers['count'].id())
        # rel.addFieldPair('id_installation', 'id')
        # QgsProject.instance().relationManager().addRelation(rel)

        widget = QgsEditorWidgetSetup(
            'ValueRelation', {
                'AllowMulti': False,
                'AllowNull': False,
                'FilterExpression': '',
                'Key': 'id',
                'Layer': self.layers['installation'].id(),
                'OrderByValue': False,
                'UseCompleter': False,
                'Value': 'name'
            })
        data_provider = self.layers['count'].dataProvider()
        index = data_provider.fieldNameIndex('id_installation')
        self.layers['count'].setEditorWidgetSetup(index, widget)

        classes_1 = "(" + ",".join(
            "'" + str(i) + "'"
            for i in self.get_classes_by_sensor_type(1)) + ")"
        classes_2 = "(" + ",".join(
            "'" + str(i) + "'"
            for i in self.get_classes_by_sensor_type(2)) + ")"
        classes_3 = "(" + ",".join(
            "'" + str(i) + "'"
            for i in self.get_classes_by_sensor_type(3)) + ")"

        widget = QgsEditorWidgetSetup(
            'ValueRelation', {
                'AllowMulti': False,
                'AllowNull': True,
                'FilterExpression': f"""
                CASE WHEN current_value('id_sensor_type') = 1 THEN \"id\" IN {classes_1}
                WHEN current_value('id_sensor_type') = 2 THEN \"id\" IN {classes_2}
                WHEN current_value('id_sensor_type') = 3 THEN \"id\" IN {classes_3}
                ELSE \"id\"
                END""",
                'Key': 'id',
                'Layer': self.layers['class'].id(),
                'OrderByValue': False,
                'UseCompleter': False,
                'Value': 'name'
            })
        data_provider = self.layers['count'].dataProvider()
        index = data_provider.fieldNameIndex('id_class')
        self.layers['count'].setEditorWidgetSetup(index, widget)

        widget = QgsEditorWidgetSetup(
            'ValueRelation', {
                'AllowMulti': False,
                'AllowNull': False,
                'FilterExpression': '',
                'Key': 'id',
                'Layer': self.layers['sensor_type'].id(),
                'OrderByValue': False,
                'UseCompleter': False,
                'Value': 'name'
            })
        data_provider = self.layers['count'].dataProvider()
        index = data_provider.fieldNameIndex('id_sensor_type')
        self.layers['count'].setEditorWidgetSetup(index, widget)

        widget = QgsEditorWidgetSetup(
            'ValueRelation', {
                'AllowMulti': False,
                'AllowNull': False,
                'FilterExpression': '',
                'Key': 'id',
                'Layer': self.layers['device'].id(),
                'OrderByValue': False,
                'UseCompleter': False,
                'Value': 'name'
            })
        data_provider = self.layers['count'].dataProvider()
        index = data_provider.fieldNameIndex('id_device')
        self.layers['count'].setEditorWidgetSetup(index, widget)

        models_1 = "(" + ",".join(
            "'" + str(i) + "'"
            for i in self.get_models_by_sensor_type(1)) + ")"
        models_2 = "(" + ",".join(
            "'" + str(i) + "'"
            for i in self.get_models_by_sensor_type(2)) + ")"
        models_3 = "(" + ",".join(
            "'" + str(i) + "'"
            for i in self.get_models_by_sensor_type(3)) + ")"

        widget = QgsEditorWidgetSetup(
            'ValueRelation', {
                'AllowMulti': False,
                'AllowNull': False,
                'FilterExpression': f"""
                CASE WHEN current_value('id_sensor_type') = 1 THEN \"id\" IN {models_1}
                WHEN current_value('id_sensor_type') = 2 THEN \"id\" IN {models_2}
                WHEN current_value('id_sensor_type') = 3 THEN \"id\" IN {models_3}
                ELSE \"id\"
                END""",
                'Key': 'id',
                'Layer': self.layers['model'].id(),
                'OrderByValue': False,
                'UseCompleter': False,
                'Value': 'name'
            })
        data_provider = self.layers['count'].dataProvider()
        index = data_provider.fieldNameIndex('id_model')
        self.layers['count'].setEditorWidgetSetup(index, widget)

        model_objs = models.Model.objects.all()
        filter_expression = "CASE "
        for model in model_objs:
            devices = list(
                models.Device.objects.filter(
                    id_model__id=model.id).values_list('id', flat=True))
            if not devices:
                devices = " "
            devices_exp = "(" + ",".join("'" + str(i) + "'"
                                         for i in devices) + ")"
            filter_expression += f" WHEN current_value('id_model') = {model.id} THEN \"id\" IN {devices_exp} "

        filter_expression += " ELSE \"id\" END"
        widget = QgsEditorWidgetSetup(
            'ValueRelation', {
                'AllowMulti': False,
                'AllowNull': False,
                'FilterExpression': filter_expression,
                'Key': 'id',
                'Layer': self.layers['device'].id(),
                'OrderByValue': False,
                'UseCompleter': False,
                'Value': 'name'
            })
        data_provider = self.layers['count'].dataProvider()
        index = data_provider.fieldNameIndex('id_device')
        self.layers['count'].setEditorWidgetSetup(index, widget)
Exemple #6
0
    def convert(self):
        """
        Convert the project to a portable project.

        :param offline_editing: The offline editing instance
        :param export_folder:   The folder to export to
        """

        project = QgsProject.instance()
        original_project = project

        original_project_path = project.fileName()
        project_filename, _ = os.path.splitext(
            os.path.basename(original_project_path))

        # Write a backup of the current project to a temporary file
        project_backup_folder = tempfile.mkdtemp()
        backup_project_path = os.path.join(project_backup_folder,
                                           project_filename + '.qgs')
        QgsProject.instance().write(backup_project_path)

        try:
            if not os.path.exists(self.export_folder):
                os.makedirs(self.export_folder)

            QApplication.setOverrideCursor(Qt.WaitCursor)

            self.__offline_layers = list()
            self.__layers = list(project.mapLayers().values())

            original_layer_info = {}
            for layer in self.__layers:
                original_layer_info[layer.id()] = (layer.source(),
                                                   layer.name())

            # We store the pks of the original vector layers
            # and we check that the primary key fields names don't
            # have a comma in the name
            original_pk_fields_by_layer_name = {}
            for layer in self.__layers:
                if layer.type() == QgsMapLayer.VectorLayer:
                    keys = []
                    for idx in layer.primaryKeyAttributes():
                        key = layer.fields()[idx].name()
                        assert (','
                                not in key), 'Comma in field names not allowed'
                        keys.append(key)
                    original_pk_fields_by_layer_name[layer.name()] = ','.join(
                        keys)

            self.total_progress_updated.emit(0, 1,
                                             self.trUtf8('Creating base map…'))
            # Create the base map before layers are removed
            if self.project_configuration.create_base_map:
                if 'processing' not in qgis.utils.plugins:
                    QMessageBox.warning(
                        None, self.tr('QFieldSync requires processing'),
                        self.
                        tr('Creating a basemap with QFieldSync requires the processing plugin to be enabled. Processing is not enabled on your system. Please go to Plugins > Manage and Install Plugins and enable processing.'
                           ))
                    return

                if self.project_configuration.base_map_type == ProjectProperties.BaseMapType.SINGLE_LAYER:
                    self.createBaseMapLayer(
                        None, self.project_configuration.base_map_layer,
                        self.project_configuration.base_map_tile_size,
                        self.project_configuration.base_map_mupp)
                else:
                    self.createBaseMapLayer(
                        self.project_configuration.base_map_theme, None,
                        self.project_configuration.base_map_tile_size,
                        self.project_configuration.base_map_mupp)

            # Loop through all layers and copy/remove/offline them
            pathResolver = QgsProject.instance().pathResolver()
            copied_files = list()
            for current_layer_index, layer in enumerate(self.__layers):
                self.total_progress_updated.emit(
                    current_layer_index - len(self.__offline_layers),
                    len(self.__layers), self.trUtf8('Copying layers…'))

                layer_source = LayerSource(layer)
                if not layer_source.is_supported:
                    project.removeMapLayer(layer)
                    continue

                if layer.dataProvider() is not None:
                    md = QgsProviderRegistry.instance().providerMetadata(
                        layer.dataProvider().name())
                    if md is not None:
                        decoded = md.decodeUri(layer.source())
                        if "path" in decoded:
                            path = pathResolver.writePath(decoded["path"])
                            if path.startswith("localized:"):
                                # Layer stored in localized data path, skip
                                continue

                if layer_source.action == SyncAction.OFFLINE:
                    if self.project_configuration.offline_copy_only_aoi and not self.project_configuration.offline_copy_only_selected_features:
                        layer.selectByRect(self.extent)
                    elif self.project_configuration.offline_copy_only_aoi and self.project_configuration.offline_copy_only_selected_features:
                        # This option is only possible via API
                        QgsApplication.instance().messageLog().logMessage(
                            self.
                            tr('Both "Area of Interest" and "only selected features" options were enabled, tha latter takes precedence.'
                               ), 'QFieldSync')
                    self.__offline_layers.append(layer)

                    # Store the primary key field name(s) as comma separated custom property
                    if layer.type() == QgsMapLayer.VectorLayer:
                        key_fields = ','.join([
                            layer.fields()[x].name()
                            for x in layer.primaryKeyAttributes()
                        ])
                        layer.setCustomProperty(
                            'QFieldSync/sourceDataPrimaryKeys', key_fields)

                elif layer_source.action == SyncAction.NO_ACTION:
                    copied_files = layer_source.copy(self.export_folder,
                                                     copied_files)
                elif layer_source.action == SyncAction.KEEP_EXISTENT:
                    layer_source.copy(self.export_folder, copied_files, True)
                elif layer_source.action == SyncAction.REMOVE:
                    project.removeMapLayer(layer)

            project_path = os.path.join(self.export_folder,
                                        project_filename + "_qfield.qgs")

            # save the original project path
            ProjectConfiguration(
                project).original_project_path = original_project_path

            # save the offline project twice so that the offline plugin can "know" that it's a relative path
            QgsProject.instance().write(project_path)

            # export the DCIM folder
            copy_images(
                os.path.join(os.path.dirname(original_project_path), "DCIM"),
                os.path.join(os.path.dirname(project_path), "DCIM"))
            try:
                # Run the offline plugin for gpkg
                gpkg_filename = "data.gpkg"
                if self.__offline_layers:
                    offline_layer_ids = [l.id() for l in self.__offline_layers]
                    only_selected = self.project_configuration.offline_copy_only_aoi or self.project_configuration.offline_copy_only_selected_features
                    if not self.offline_editing.convertToOfflineProject(
                            self.export_folder, gpkg_filename,
                            offline_layer_ids, only_selected,
                            self.offline_editing.GPKG):
                        raise Exception(
                            self.
                            tr("Error trying to convert layers to offline layers"
                               ))

            except AttributeError:
                # Run the offline plugin for spatialite
                spatialite_filename = "data.sqlite"
                if self.__offline_layers:
                    offline_layer_ids = [l.id() for l in self.__offline_layers]
                    only_selected = self.project_configuration.offline_copy_only_aoi or self.project_configuration.offline_copy_only_selected_features
                    if not self.offline_editing.convertToOfflineProject(
                            self.export_folder, spatialite_filename,
                            offline_layer_ids, only_selected):
                        raise Exception(
                            self.
                            tr("Error trying to convert layers to offline layers"
                               ))

            # Disable project options that could create problems on a portable
            # project with offline layers
            if self.__offline_layers:
                QgsProject.instance().setEvaluateDefaultValues(False)
                QgsProject.instance().setAutoTransaction(False)

                # check if value relations point to offline layers and adjust if necessary
                for layer in project.mapLayers().values():
                    if layer.type() == QgsMapLayer.VectorLayer:

                        # Before QGIS 3.14 the custom properties of a layer are not
                        # kept into the new layer during the conversion to offline project
                        # So we try to identify the new created layer by its name and
                        # we set the custom properties again.
                        if not layer.customProperty(
                                'QFieldSync/cloudPrimaryKeys'):
                            original_layer_name = layer.name().rsplit(' ',
                                                                      1)[0]
                            stored_fields = original_pk_fields_by_layer_name.get(
                                original_layer_name, None)
                            if stored_fields:
                                layer.setCustomProperty(
                                    'QFieldSync/sourceDataPrimaryKeys',
                                    stored_fields)

                        for field in layer.fields():
                            ews = field.editorWidgetSetup()
                            if ews.type() == 'ValueRelation':
                                widget_config = ews.config()
                                online_layer_id = widget_config['Layer']
                                if project.mapLayer(online_layer_id):
                                    continue

                                layer_id = None
                                loose_layer_id = None
                                for offline_layer in project.mapLayers(
                                ).values():
                                    if offline_layer.customProperty(
                                            'remoteSource'
                                    ) == original_layer_info[online_layer_id][
                                            0]:
                                        #  First try strict matching: the offline layer should have a "remoteSource" property
                                        layer_id = offline_layer.id()
                                        break
                                    elif offline_layer.name().startswith(
                                            original_layer_info[
                                                online_layer_id][1] + ' '):
                                        #  If that did not work, go with loose matching
                                        #    The offline layer should start with the online layer name + a translated version of " (offline)"
                                        loose_layer_id = offline_layer.id()
                                widget_config[
                                    'Layer'] = layer_id or loose_layer_id
                                offline_ews = QgsEditorWidgetSetup(
                                    ews.type(), widget_config)
                                layer.setEditorWidgetSetup(
                                    layer.fields().indexOf(field.name()),
                                    offline_ews)

            # Now we have a project state which can be saved as offline project
            QgsProject.instance().write(project_path)
        finally:
            # We need to let the app handle events before loading the next project or QGIS will crash with rasters
            QCoreApplication.processEvents()
            QgsProject.instance().clear()
            QCoreApplication.processEvents()
            QgsProject.instance().read(backup_project_path)
            QgsProject.instance().setFileName(original_project_path)
            QApplication.restoreOverrideCursor()

        self.offline_editing.layerProgressUpdated.disconnect(
            self.on_offline_editing_next_layer)
        self.offline_editing.progressModeSet.disconnect(
            self.on_offline_editing_max_changed)
        self.offline_editing.progressUpdated.disconnect(
            self.offline_editing_task_progress)

        self.total_progress_updated.emit(100, 100, self.tr('Finished'))
Exemple #7
0
    def set_layer_config(self, layers):
        """ Set layer fields configured according to client configuration.
            At the moment manage:
                Column names as alias, combos as ValueMap, typeahead as textedit"""

        self.controller.log_info("Start set_layer_config")

        msg_failed = ""
        msg_key = ""
        total_layers = len(layers)
        layer_number = 0
        for layer_name in layers:

            if self.isCanceled():
                return False

            layer = self.controller.get_layer_by_tablename(layer_name)
            if not layer:
                continue

            layer_number = layer_number + 1
            self.setProgress((layer_number * 100) / total_layers)

            feature = '"tableName":"' + str(
                layer_name) + '", "id":"", "isLayer":true'
            extras = f'"infoType":"{self.qgis_project_infotype}"'
            body = self.create_body(feature=feature, extras=extras)
            complet_result = self.controller.get_json('gw_fct_getinfofromid',
                                                      body,
                                                      log_sql=False)
            if not complet_result:
                continue

            # self.controller.log_info(str(complet_result))
            if not 'body' in complet_result:
                self.controller.log_info("Not 'body'")
                continue
            if not 'data' in complet_result['body']:
                self.controller.log_info("Not 'data'")
                continue

            # self.controller.log_info(complet_result['body']['data']['fields'])
            for field in complet_result['body']['data']['fields']:
                valuemap_values = {}

                # Get column index
                fieldIndex = layer.fields().indexFromName(field['columnname'])

                # Hide selected fields according table config_api_form_fields.hidden
                if 'hidden' in field:
                    self.set_column_visibility(layer, field['columnname'],
                                               field['hidden'])

                # Set alias column
                if field['label']:
                    layer.setFieldAlias(fieldIndex, field['label'])

                # multiline: key comes from widgecontrol but it's used here in order to set false when key is missing
                if field['widgettype'] == 'text':
                    self.set_column_multiline(layer, field, fieldIndex)

                # widgetcontrols
                if 'widgetcontrols' in field:

                    # Set field constraints
                    if field[
                            'widgetcontrols'] and 'setQgisConstraints' in field[
                                'widgetcontrols']:
                        if field['widgetcontrols'][
                                'setQgisConstraints'] is True:
                            layer.setFieldConstraint(
                                fieldIndex,
                                QgsFieldConstraints.ConstraintNotNull,
                                QgsFieldConstraints.ConstraintStrengthSoft)
                            layer.setFieldConstraint(
                                fieldIndex,
                                QgsFieldConstraints.ConstraintUnique,
                                QgsFieldConstraints.ConstraintStrengthHard)

                if 'ismandatory' in field and not field['ismandatory']:
                    layer.setFieldConstraint(
                        fieldIndex, QgsFieldConstraints.ConstraintNotNull,
                        QgsFieldConstraints.ConstraintStrengthSoft)

                # Manage editability
                self.set_read_only(layer, field, fieldIndex)

                # delete old values on ValueMap
                editor_widget_setup = QgsEditorWidgetSetup(
                    'ValueMap', {'map': valuemap_values})
                layer.setEditorWidgetSetup(fieldIndex, editor_widget_setup)

                # Manage new values in ValueMap
                if field['widgettype'] == 'combo':
                    if 'comboIds' in field:
                        # Set values
                        for i in range(0, len(field['comboIds'])):
                            valuemap_values[field['comboNames']
                                            [i]] = field['comboIds'][i]
                    # Set values into valueMap
                    editor_widget_setup = QgsEditorWidgetSetup(
                        'ValueMap', {'map': valuemap_values})
                    layer.setEditorWidgetSetup(fieldIndex, editor_widget_setup)
                elif field['widgettype'] == 'check':
                    config = {
                        'CheckedState': 'true',
                        'UncheckedState': 'false'
                    }
                    editor_widget_setup = QgsEditorWidgetSetup(
                        'CheckBox', config)
                    layer.setEditorWidgetSetup(fieldIndex, editor_widget_setup)
                elif field['widgettype'] == 'datetime':
                    config = {
                        'allow_null': True,
                        'calendar_popup': True,
                        'display_format': 'yyyy-MM-dd',
                        'field_format': 'yyyy-MM-dd',
                        'field_iso_format': False
                    }
                    editor_widget_setup = QgsEditorWidgetSetup(
                        'DateTime', config)
                    layer.setEditorWidgetSetup(fieldIndex, editor_widget_setup)
                else:
                    editor_widget_setup = QgsEditorWidgetSetup(
                        'TextEdit', {'IsMultiline': 'True'})
                    layer.setEditorWidgetSetup(fieldIndex, editor_widget_setup)

        if msg_failed != "":
            self.controller.show_exceptions_msg("Execute failed.", msg_failed)

        if msg_key != "":
            self.controller.show_exceptions_msg(
                "Key on returned json from ddbb is missed.", msg_key)

        self.controller.log_info("Finish set_layer_config")
    def create(self, path: str, qgis_project: QgsProject):
        qgis_project.setAutoTransaction(self.auto_transaction)
        qgis_project.setEvaluateDefaultValues(self.evaluate_default_values)
        qgis_layers = list()
        for layer in self.layers:
            qgis_layer = layer.create()
            self.layer_added.emit(qgis_layer.id())
            if not self.crs and qgis_layer.isSpatial():
                self.crs = qgis_layer.crs()

            qgis_layers.append(qgis_layer)

        qgis_project.addMapLayers(qgis_layers, not self.legend)

        if self.crs:
            if isinstance(self.crs, QgsCoordinateReferenceSystem):
                qgis_project.setCrs(self.crs)
            else:
                crs = QgsCoordinateReferenceSystem.fromEpsgId(self.crs)
                if not crs.isValid():
                    crs = QgsCoordinateReferenceSystem(self.crs)  # Fallback
                qgis_project.setCrs(crs)

        qgis_relations = list(
            qgis_project.relationManager().relations().values())
        dict_layers = {layer.layer.id(): layer for layer in self.layers}
        for relation in self.relations:
            rel = relation.create(qgis_project, qgis_relations)
            assert rel.isValid()
            qgis_relations.append(rel)

            referenced_layer = dict_layers.get(rel.referencedLayerId(), None)
            referencing_layer = dict_layers.get(rel.referencingLayerId(), None)

            if referenced_layer and referenced_layer.is_domain:
                editor_widget_setup = QgsEditorWidgetSetup(
                    "RelationReference",
                    {
                        "Relation":
                        rel.id(),
                        "ShowForm":
                        False,
                        "OrderByValue":
                        True,
                        "ShowOpenFormButton":
                        False,
                        "AllowNULL":
                        True,
                        "FilterExpression":
                        "\"{}\" = '{}'".format(ENUM_THIS_CLASS_COLUMN,
                                               relation.child_domain_name)
                        if relation.child_domain_name else "",
                        "FilterFields":
                        list(),
                    },
                )
            elif referenced_layer and referenced_layer.is_basket_table:
                editor_widget_setup = QgsEditorWidgetSetup(
                    "RelationReference",
                    {
                        "Relation":
                        rel.id(),
                        "ShowForm":
                        False,
                        "OrderByValue":
                        True,
                        "ShowOpenFormButton":
                        False,
                        "AllowNULL":
                        True,
                        "AllowAddFeatures":
                        False,
                        "FilterExpression":
                        "\"topic\" = '{}' and attribute(get_feature('{}', 't_id', \"dataset\"), 'datasetname') != '{}'"
                        .format(
                            referencing_layer.model_topic_name,
                            "T_ILI2DB_DATASET" if referenced_layer.provider
                            == "ogr" else "t_ili2db_dataset",
                            CATALOGUE_DATASETNAME,
                        ) if referencing_layer.model_topic_name else "",
                        "FilterFields":
                        list(),
                    },
                )
            else:
                editor_widget_setup = QgsEditorWidgetSetup(
                    "RelationReference",
                    {
                        "Relation": rel.id(),
                        "ShowForm": False,
                        "OrderByValue": True,
                        "ShowOpenFormButton": False,
                        "AllowAddFeatures": True,
                        "AllowNULL": True,
                    },
                )

            referencing_layer = rel.referencingLayer()
            referencing_layer.setEditorWidgetSetup(rel.referencingFields()[0],
                                                   editor_widget_setup)

        qgis_project.relationManager().setRelations(qgis_relations)

        # Set Bag of Enum widget
        for layer_name, bag_of_enum in self.bags_of_enum.items():
            for attribute, bag_of_enum_info in bag_of_enum.items():
                layer_obj = bag_of_enum_info[0]
                cardinality = bag_of_enum_info[1]
                domain_table = bag_of_enum_info[2]
                key_field = bag_of_enum_info[3]
                value_field = bag_of_enum_info[4]

                minimal_selection = cardinality.startswith("1")

                current_layer = layer_obj.create()

                field_widget = "ValueRelation"
                field_widget_config = {
                    "AllowMulti": True,
                    "UseCompleter": False,
                    "Value": value_field,
                    "OrderByValue": False,
                    "AllowNull": True,
                    "Layer": domain_table.create().id(),
                    "FilterExpression": "",
                    "Key": key_field,
                    "NofColumns": 1,
                }
                field_idx = current_layer.fields().indexOf(attribute)
                setup = QgsEditorWidgetSetup(field_widget, field_widget_config)
                current_layer.setEditorWidgetSetup(field_idx, setup)
                if minimal_selection:
                    constraint_expression = 'array_length("{}")>0'.format(
                        attribute)
                    current_layer.setConstraintExpression(
                        field_idx,
                        constraint_expression,
                        self.tr("The minimal selection is 1"),
                    )

        for layer in self.layers:
            layer.create_form(self)

        if self.legend:
            self.legend.create(qgis_project)

        custom_layer_order = list()
        for custom_layer_name in self.custom_layer_order_structure:
            custom_layer = qgis_project.mapLayersByName(custom_layer_name)
            if custom_layer:
                custom_layer_order.append(custom_layer[0])
        if custom_layer_order:
            root = qgis_project.layerTreeRoot()
            order = root.customLayerOrder() if root.hasCustomLayerOrder(
            ) else []
            order.extend(custom_layer_order)
            root.setCustomLayerOrder(custom_layer_order)
            root.setHasCustomLayerOrder(True)

        if path:
            qgis_project.write(path)
    def convert(self):
        """
        Convert the project to a portable project.

        :param offline_editing: The offline editing instance
        :param export_folder:   The folder to export to
        """

        project = QgsProject.instance()
        original_project = project

        original_project_path = project.fileName()
        project_filename, _ = os.path.splitext(
            os.path.basename(original_project_path))

        # Write a backup of the current project to a temporary file
        project_backup_folder = tempfile.mkdtemp()
        backup_project_path = os.path.join(project_backup_folder,
                                           project_filename + '.qgs')
        QgsProject.instance().write(backup_project_path)

        try:
            if not os.path.exists(self.export_folder):
                os.makedirs(self.export_folder)

            QApplication.setOverrideCursor(Qt.WaitCursor)

            self.__offline_layers = list()
            self.__layers = list(project.mapLayers().values())

            original_layer_info = {}
            for layer in self.__layers:
                original_layer_info[layer.id()] = (layer.source(),
                                                   layer.name())

            self.total_progress_updated.emit(0, 1,
                                             self.trUtf8('Creating base map…'))
            # Create the base map before layers are removed
            if self.project_configuration.create_base_map:
                if 'processing' not in qgis.utils.plugins:
                    QMessageBox.warning(
                        None, self.tr('QFieldSync requires processing'),
                        self.
                        tr('Creating a basemap with QFieldSync requires the processing plugin to be enabled. Processing is not enabled on your system. Please go to Plugins > Manage and Install Plugins and enable processing.'
                           ))
                    return

                if self.project_configuration.base_map_type == ProjectProperties.BaseMapType.SINGLE_LAYER:
                    self.createBaseMapLayer(
                        None, self.project_configuration.base_map_layer,
                        self.project_configuration.base_map_tile_size,
                        self.project_configuration.base_map_mupp)
                else:
                    self.createBaseMapLayer(
                        self.project_configuration.base_map_theme, None,
                        self.project_configuration.base_map_tile_size,
                        self.project_configuration.base_map_mupp)

            # Loop through all layers and copy/remove/offline them
            copied_files = list()
            for current_layer_index, layer in enumerate(self.__layers):
                self.total_progress_updated.emit(
                    current_layer_index - len(self.__offline_layers),
                    len(self.__layers), self.trUtf8('Copying layers…'))
                layer_source = LayerSource(layer)

                if layer_source.action == SyncAction.OFFLINE:
                    if self.project_configuration.offline_copy_only_aoi:
                        layer.selectByRect(self.extent)
                    self.__offline_layers.append(layer)
                elif layer_source.action == SyncAction.NO_ACTION:
                    copied_files = layer_source.copy(self.export_folder,
                                                     copied_files)
                elif layer_source.action == SyncAction.KEEP_EXISTENT:
                    layer_source.copy(self.export_folder, copied_files, True)
                elif layer_source.action == SyncAction.REMOVE:
                    project.removeMapLayer(layer)

            project_path = os.path.join(self.export_folder,
                                        project_filename + "_qfield.qgs")

            # save the original project path
            ProjectConfiguration(
                project).original_project_path = original_project_path

            # save the offline project twice so that the offline plugin can "know" that it's a relative path
            QgsProject.instance().write(project_path)

            # export the DCIM folder
            copy_images(
                os.path.join(os.path.dirname(original_project_path), "DCIM"),
                os.path.join(os.path.dirname(project_path), "DCIM"))
            try:
                # Run the offline plugin for gpkg
                gpkg_filename = "data.gpkg"
                if self.__offline_layers:
                    offline_layer_ids = [l.id() for l in self.__offline_layers]
                    if not self.offline_editing.convertToOfflineProject(
                            self.export_folder, gpkg_filename,
                            offline_layer_ids,
                            self.project_configuration.offline_copy_only_aoi,
                            self.offline_editing.GPKG):
                        raise Exception(
                            self.
                            tr("Error trying to convert layers to offline layers"
                               ))

            except AttributeError:
                # Run the offline plugin for spatialite
                spatialite_filename = "data.sqlite"
                if self.__offline_layers:
                    offline_layer_ids = [l.id() for l in self.__offline_layers]
                    if not self.offline_editing.convertToOfflineProject(
                            self.export_folder, spatialite_filename,
                            offline_layer_ids,
                            self.project_configuration.offline_copy_only_aoi):
                        raise Exception(
                            self.
                            tr("Error trying to convert layers to offline layers"
                               ))

            # Disable project options that could create problems on a portable
            # project with offline layers
            if self.__offline_layers:
                QgsProject.instance().setEvaluateDefaultValues(False)
                QgsProject.instance().setAutoTransaction(False)

                # check if value relations point to offline layers and adjust if necessary
                for layer in project.mapLayers().values():
                    if layer.type() == QgsMapLayer.VectorLayer:
                        for field in layer.fields():
                            ews = field.editorWidgetSetup()
                            if ews.type() == 'ValueRelation':
                                widget_config = ews.config()
                                online_layer_id = widget_config['Layer']
                                if project.mapLayer(online_layer_id):
                                    continue

                                layer_id = None
                                loose_layer_id = None
                                for offline_layer in project.mapLayers(
                                ).values():
                                    if offline_layer.customProperty(
                                            'remoteSource'
                                    ) == original_layer_info[online_layer_id][
                                            0]:
                                        #  First try strict matching: the offline layer should have a "remoteSource" property
                                        layer_id = offline_layer.id()
                                        break
                                    elif offline_layer.name().startswith(
                                            original_layer_info[
                                                online_layer_id][1] + ' '):
                                        #  If that did not work, go with loose matching
                                        #    The offline layer should start with the online layer name + a translated version of " (offline)"
                                        loose_layer_id = offline_layer.id()
                                widget_config[
                                    'Layer'] = layer_id or loose_layer_id
                                offline_ews = QgsEditorWidgetSetup(
                                    ews.type(), widget_config)
                                layer.setEditorWidgetSetup(
                                    layer.fields().indexOf(field.name()),
                                    offline_ews)

            # Now we have a project state which can be saved as offline project
            QgsProject.instance().write(project_path)
        finally:
            # We need to let the app handle events before loading the next project or QGIS will crash with rasters
            QCoreApplication.processEvents()
            QgsProject.instance().clear()
            QCoreApplication.processEvents()
            QgsProject.instance().read(backup_project_path)
            QgsProject.instance().setFileName(original_project_path)
            QApplication.restoreOverrideCursor()

        self.total_progress_updated.emit(100, 100, self.tr('Finished'))
def create_basic_qgis_project(project_path=None, project_name=None):
    """
    Create a basic QGIS project with OSM background and a simple vector layer.
    :return: Project file path on successful creation of a new project, None otherwise
    """
    if project_path is None:
        project_path = get_new_qgis_project_filepath(project_name=project_name)
    if project_path is None:
        return False
    new_project = QgsProject()
    crs = QgsCoordinateReferenceSystem()
    crs.createFromString("EPSG:3857")
    new_project.setCrs(crs)
    new_project.setFileName(project_path)
    osm_url = "crs=EPSG:3857&type=xyz&zmin=0&zmax=19&url=http://tile.openstreetmap.org/{z}/{x}/{y}.png"
    osm_layer = QgsRasterLayer(osm_url, "OpenStreetMap", "wms")
    new_project.addMapLayer(osm_layer)

    mem_uri = "Point?crs=epsg:3857"
    mem_layer = QgsVectorLayer(mem_uri, "Survey points", "memory")
    res = mem_layer.dataProvider().addAttributes([
        QgsField("date", QVariant.DateTime),
        QgsField("notes", QVariant.String),
        QgsField("photo", QVariant.String),
    ])
    mem_layer.updateFields()
    vector_fname, err = save_vector_layer_as_gpkg(
        mem_layer, os.path.dirname(project_path))
    if err:
        QMessageBox.warning(None, "Error Creating New Project",
                            f"Couldn't save vector layer to:\n{vector_fname}")
    vector_layer = QgsVectorLayer(vector_fname, "Survey", "ogr")
    symbol = QgsMarkerSymbol.createSimple({
        'name': 'circle',
        'color': '#d73027',
        'size': '3',
        "outline_color": '#e8e8e8',
        'outline_style': 'solid',
        'outline_width': '0.4'
    })
    vector_layer.renderer().setSymbol(symbol)
    fid_ws = QgsEditorWidgetSetup("Hidden", {})
    vector_layer.setEditorWidgetSetup(0, fid_ws)
    datetime_config = {
        'allow_null': True,
        'calendar_popup': True,
        'display_format': 'yyyy-MM-dd HH:mm:ss',
        'field_format': 'yyyy-MM-dd HH:mm:ss',
        'field_iso_format': False
    }
    datetime_ws = QgsEditorWidgetSetup("DateTime", datetime_config)
    vector_layer.setEditorWidgetSetup(1, datetime_ws)
    photo_config = {
        'DocumentViewer': 1,
        'DocumentViewerHeight': 0,
        'DocumentViewerWidth': 0,
        'FileWidget': True,
        'FileWidgetButton': True,
        'FileWidgetFilter': '',
        'RelativeStorage': 1,
        'StorageMode': 0,
        'PropertyCollection': {
            'name': NULL,
            'properties': {},
            'type': 'collection'
        },
    }
    photo_ws = QgsEditorWidgetSetup("ExternalResource", photo_config)
    vector_layer.setEditorWidgetSetup(3, photo_ws)
    new_project.addMapLayer(vector_layer)

    write_success = new_project.write()
    if not write_success:
        QMessageBox.warning(None, "Error Creating New Project",
                            f"Couldn't create new project:\n{project_path}")
        return None
    return project_path
 def setDomainsAndRestrictions(self, lyr):
     """
     Adjusts the domain restriction to all attributes in the layer
     :param lyr:
     :param lyrName:
     :param domLayerDict:
     :return:
     """
     pkIdxList = lyr.primaryKeyAttributes()
     lyrName = lyr.name()
     self.multiColumnsDict = {}
     for i, field in enumerate(lyr.fields()):
         attrName = field.name()
         if attrName == 'id' or 'id_' in attrName or i in pkIdxList:
             formConfig = lyr.editFormConfig()
             formConfig.setReadOnly(i, True)
             lyr.setEditFormConfig(formConfig)
         elif lyrName in self.domainDict \
             and attrName in self.domainDict[lyrName]['columns'] \
             and 'values' in self.domainDict[lyrName]['columns'][attrName]:
             attrMetadataDict = self.domainDict[lyrName]['columns'][
                 attrName]
             if lyrName in self.multiColumnsDict and \
                 attrName in self.multiColumnsDict[lyrName]:
                 #make filter
                 if 'constraintList' in attrMetadataDict \
                     and lyrName in self.domLayerDict \
                     and attrName in self.domLayerDict[lyrName]:
                     lyrFilter = '{0} in ({1})'.format(
                         attrMetadataDict['refPk'], ','.join(
                             map(str, attrMetadataDict['constraintList'])))
                     editDict = {
                         'Layer': self.domLayerDict[lyrName][attrName].id(),
                         'Key': self.attrMetadataDict['refPk'],
                         'Value': self.attrMetadataDict['otherKey'],
                         'AllowMulti': True,
                         'AllowNull': self.attrMetadataDict['nullable'],
                         'FilterExpression': lyrFilter
                     }
                     widgetSetup = QgsEditorWidgetSetup(
                         'ValueRelation', editDict)
                     lyr.setEditorWidgetSetup(i, widgetSetup)
             else:
                 #filter value dict
                 valueDict = attrMetadataDict['values']
                 if 'constraintList' in attrMetadataDict and \
                     attrMetadataDict['constraintList'] != [] and \
                     attrMetadataDict['constraintList'] != list(valueDict.keys()):
                     valueRelationDict = {
                         v: str(k)
                         for k, v in valueDict.items() if str(k) in map(
                             str, attrMetadataDict['constraintList'])
                     }
                 else:
                     valueRelationDict = {
                         v: str(k)
                         for k, v in valueDict.items()
                     }
                 widgetSetup = QgsEditorWidgetSetup(
                     'ValueMap', {'map': valueRelationDict})
                 lyr.setEditorWidgetSetup(i, widgetSetup)
     return lyr
def set_relations():
    v2_pipe_view = QgsProject.instance().mapLayersByName("v2_pipe_view")[0]
    v2_pipe_id = v2_pipe_view.id()
    pipe_idx = v2_pipe_view.fields().indexFromName(
        "pipe_cross_section_definition_id")

    v2_orifice_view = QgsProject.instance().mapLayersByName(
        "v2_orifice_view")[0]
    v2_orifice_id = v2_orifice_view.id()
    orf_idx = v2_orifice_view.fields().indexFromName(
        "orf_cross_section_definition_id")

    v2_weir_view = QgsProject.instance().mapLayersByName("v2_weir_view")[0]
    v2_weir_id = v2_weir_view.id()
    weir_idx = v2_weir_view.fields().indexFromName(
        "weir_cross_section_definition_id")

    v2_culvert_view = QgsProject.instance().mapLayersByName(
        "v2_culvert_view")[0]
    v2_culvert_id = v2_culvert_view.id()
    cul_idx = v2_culvert_view.fields().indexFromName(
        "cul_cross_section_definition_id")

    v2_cross_section_location_view = QgsProject.instance().mapLayersByName(
        "v2_cross_section_location_view")[0]
    v2_cross_section_location_id = v2_cross_section_location_view.id()
    xsec_loc_idx = v2_cross_section_location_view.fields().indexFromName(
        "loc_definition_id")

    v2_global_settings = QgsProject.instance().mapLayersByName(
        "v2_global_settings")[0]
    v2_global_settings_id = v2_global_settings.id()
    glob_num_idx = v2_global_settings.fields().indexFromName(
        "numerical_settings_id")
    glob_inf_idx = v2_global_settings.fields().indexFromName(
        "simple_infiltration_settings_id")
    glob_ground_idx = v2_global_settings.fields().indexFromName(
        "groundwater_settings_id")
    glob_int_idx = v2_global_settings.fields().indexFromName(
        "interflow_settings_id")

    v2_numerical_settings = QgsProject.instance().mapLayersByName(
        "v2_numerical_settings")[0]
    v2_numerical_settings_id = v2_numerical_settings.id()

    v2_simple_infiltration = QgsProject.instance().mapLayersByName(
        "v2_simple_infiltration")[0]
    v2_simple_infiltration_id = v2_simple_infiltration.id()

    v2_groundwater = QgsProject.instance().mapLayersByName("v2_groundwater")[0]
    v2_groundwater_id = v2_groundwater.id()

    v2_interflow = QgsProject.instance().mapLayersByName("v2_interflow")[0]
    v2_interflow_id = v2_interflow.id()

    v2_cross_section_definition = QgsProject.instance().mapLayersByName(
        "v2_cross_section_definition")[0]
    v2_cross_section_definition_id = v2_cross_section_definition.id()

    global_setting_relations = [[
        "6", "glob_num", 0, v2_global_settings_id, v2_numerical_settings_id,
        "numerical_settings_id"
    ],
                                [
                                    "7", "glob_inf", 0, v2_global_settings_id,
                                    v2_simple_infiltration_id,
                                    "simple_infiltration_settings_id"
                                ],
                                [
                                    "8", "glob_ground", 0,
                                    v2_global_settings_id, v2_groundwater_id,
                                    "groundwater_settings_id"
                                ],
                                [
                                    "9", "glob_int", 0, v2_global_settings_id,
                                    v2_interflow_id, "interflow_settings_id"
                                ]]

    for id, name, strength, referencing_layer, referenced_layer, referencing_field in global_setting_relations:
        rel = QgsRelation()
        rel.setReferencingLayer(referencing_layer)
        rel.setReferencedLayer(referenced_layer)
        rel.addFieldPair(referencing_field, "id")
        rel.setId(id)
        rel.setName(name)
        rel.setStrength(strength)
        QgsProject.instance().relationManager().addRelation(rel)

    cross_section_relations = [[
        "1", "pipe_xsec", 0, v2_pipe_id, v2_cross_section_definition_id,
        "pipe_cross_section_definition_id"
    ],
                               [
                                   "2", "weir_xsec", 0, v2_weir_id,
                                   v2_cross_section_definition_id,
                                   "weir_cross_section_definition_id"
                               ],
                               [
                                   "3", "orf_xsec", 0, v2_orifice_id,
                                   v2_cross_section_definition_id,
                                   "orf_cross_section_definition_id"
                               ],
                               [
                                   "4", "cul_xsec", 0, v2_culvert_id,
                                   v2_cross_section_definition_id,
                                   "cul_cross_section_definition_id"
                               ],
                               [
                                   "5", "loc_view_xsec", 0,
                                   v2_cross_section_location_id,
                                   v2_cross_section_definition_id,
                                   "loc_definition_id"
                               ]]

    for id, name, strength, referencing_layer, referenced_layer, referencing_field in cross_section_relations:
        rel = QgsRelation()
        rel.setReferencingLayer(referencing_layer)
        rel.setReferencedLayer(referenced_layer)
        rel.addFieldPair(referencing_field, "id")
        rel.setId(id)
        rel.setName(name)
        rel.setStrength(strength)
        QgsProject.instance().relationManager().addRelation(rel)

    cfg = dict()
    cfg["OrderByValue"] = True
    cfg["AllowNULL"] = False
    cfg["ShowOpenFormButton"] = True
    cfg["AllowAddFeatures"] = True
    cfg["ShowForm"] = False
    cfg["FilterFields"] = []
    cfg["ChainFilters"] = False
    settingssetup = QgsEditorWidgetSetup("RelationReference", cfg)

    cfg = dict()
    cfg["OrderByValue"] = True
    cfg["AllowNULL"] = False
    cfg["ShowOpenFormButton"] = False
    cfg["AllowAddFeatures"] = True
    cfg["ShowForm"] = False
    cfg["FilterFields"] = ["shape", "width", "height"]
    cfg["ChainFilters"] = False
    xsecsetup = QgsEditorWidgetSetup("RelationReference", cfg)

    settings_list = [[v2_global_settings, glob_num_idx],
                     [v2_global_settings, glob_inf_idx],
                     [v2_global_settings, glob_ground_idx],
                     [v2_global_settings, glob_int_idx]]

    xsec_list = [[v2_weir_view, weir_idx], [v2_pipe_view, pipe_idx],
                 [v2_culvert_view, cul_idx], [v2_orifice_view, orf_idx],
                 [v2_cross_section_location_view, xsec_loc_idx]]

    for view, idx in xsec_list:
        view.setEditorWidgetSetup(idx, xsecsetup)

    for view, idx in settings_list:
        view.setEditorWidgetSetup(idx, settingssetup)

    expression = " id || ' code: ' || code "
    v2_cross_section_definition.setDisplayExpression(expression)
Exemple #13
0
 def setDomainsAndRestrictions(self, lyr, lyrName, domainDict,
                               multiColumnsDict, domLayerDict):
     """
     Adjusts the domain restriction to all attributes in the layer
     :param lyr:
     :param lyrName:
     :param domainDict:
     :param multiColumnsDict:
     :param domLayerDict:
     :return:
     """
     lyrAttributes = [i for i in lyr.fields()]
     pkIdxList = lyr.primaryKeyAttributes()
     for i in range(len(lyrAttributes)):
         attrName = lyrAttributes[i].name()
         if attrName == 'id' or 'id_' in lyrAttributes[i].name(
         ) or i in pkIdxList:
             lyr.editFormConfig().setReadOnly(i, True)
         else:
             if lyrName in domainDict.keys():
                 if attrName in list(domainDict[lyrName]['columns'].keys()):
                     refTable = domainDict[lyrName]['columns'][attrName][
                         'references']
                     refPk = domainDict[lyrName]['columns'][attrName][
                         'refPk']
                     otherKey = domainDict[lyrName]['columns'][attrName][
                         'otherKey']
                     valueDict = domainDict[lyrName]['columns'][attrName][
                         'values']
                     isMulti = self.checkMulti(lyrName, attrName,
                                               multiColumnsDict)
                     if isMulti:
                         #make filter
                         if 'constraintList' in list(
                                 domainDict[lyrName]['columns']
                             [attrName].keys()):
                             #make editDict
                             if lyrName in domLayerDict:
                                 if attrName in domLayerDict[lyrName]:
                                     filter = '{0} in ({1})'.format(
                                         refPk, ','.join(
                                             map(
                                                 str, domainDict[lyrName]
                                                 ['columns'][attrName]
                                                 ['constraintList'])))
                                     allowNull = domainDict[lyrName][
                                         'columns'][attrName]['nullable']
                                     dom = domLayerDict[lyrName][attrName]
                                     editDict = {
                                         'Layer': dom.id(),
                                         'Key': refPk,
                                         'Value': otherKey,
                                         'AllowMulti': True,
                                         'AllowNull': allowNull,
                                         'FilterExpression': filter
                                     }
                                     widgetSetup = QgsEditorWidgetSetup(
                                         'ValueRelation', editDict)
                                     lyr.setEditorWidgetSetup(
                                         i, widgetSetup)
                     else:
                         #filter value dict
                         constraintList = domainDict[lyrName]['columns'][
                             attrName]['constraintList']
                         valueRelationDict = dict()
                         for key in list(valueDict.keys()):
                             if len(constraintList) > 0:
                                 if key in constraintList:
                                     valueRelationDict[
                                         valueDict[key]] = str(key)
                             else:
                                 valueRelationDict[valueDict[key]] = str(
                                     key)
                         widgetSetup = QgsEditorWidgetSetup(
                             'ValueMap', {'map': valueRelationDict})
                         lyr.setEditorWidgetSetup(i, widgetSetup)
     return lyr
Exemple #14
0
    def _set_layer_config(self, layers):
        """ Set layer fields configured according to client configuration.
            At the moment manage:
                Column names as alias, combos as ValueMap, typeahead as textedit"""

        # Check only once if function 'gw_fct_getinfofromid' exists
        row = tools_db.check_function('gw_fct_getinfofromid')
        if row in (None, ''):
            tools_qgis.show_warning("Function not found in database",
                                    parameter='gw_fct_getinfofromid')
            return False

        msg_failed = ""
        msg_key = ""
        total_layers = len(layers)
        layer_number = 0
        for layer_name in layers:

            if self.isCanceled():
                return False

            layer = tools_qgis.get_layer_by_tablename(layer_name)
            if not layer:
                continue

            layer_number = layer_number + 1
            self.setProgress((layer_number * 100) / total_layers)

            feature = f'"tableName":"{layer_name}", "isLayer":true'
            self.body = tools_gw.create_body(feature=feature)
            self.json_result = tools_gw.execute_procedure(
                'gw_fct_getinfofromid',
                self.body,
                aux_conn=self.aux_conn,
                is_thread=True,
                check_function=False)
            if not self.json_result:
                continue
            if 'status' not in self.json_result:
                continue
            if self.json_result['status'] == 'Failed':
                continue
            if 'body' not in self.json_result:
                tools_log.log_info("Not 'body'")
                continue
            if 'data' not in self.json_result['body']:
                tools_log.log_info("Not 'data'")
                continue

            for field in self.json_result['body']['data']['fields']:
                valuemap_values = {}

                # Get column index
                field_index = layer.fields().indexFromName(field['columnname'])

                # Hide selected fields according table config_form_fields.hidden
                if 'hidden' in field:
                    self._set_column_visibility(layer, field['columnname'],
                                                field['hidden'])

                # Set alias column
                if field['label']:
                    layer.setFieldAlias(field_index, field['label'])

                # widgetcontrols
                if 'widgetcontrols' in field:

                    # Set field constraints
                    if field[
                            'widgetcontrols'] and 'setQgisConstraints' in field[
                                'widgetcontrols']:
                        if field['widgetcontrols'][
                                'setQgisConstraints'] is True:
                            layer.setFieldConstraint(
                                field_index,
                                QgsFieldConstraints.ConstraintNotNull,
                                QgsFieldConstraints.ConstraintStrengthSoft)
                            layer.setFieldConstraint(
                                field_index,
                                QgsFieldConstraints.ConstraintUnique,
                                QgsFieldConstraints.ConstraintStrengthHard)

                if 'ismandatory' in field and not field['ismandatory']:
                    layer.setFieldConstraint(
                        field_index, QgsFieldConstraints.ConstraintNotNull,
                        QgsFieldConstraints.ConstraintStrengthSoft)

                # Manage editability
                self._set_read_only(layer, field, field_index)

                # delete old values on ValueMap
                editor_widget_setup = QgsEditorWidgetSetup(
                    'ValueMap', {'map': valuemap_values})
                layer.setEditorWidgetSetup(field_index, editor_widget_setup)

                # Manage ValueRelation configuration
                use_vr = 'widgetcontrols' in field and field['widgetcontrols'] \
                         and 'valueRelation' in field['widgetcontrols'] and field['widgetcontrols']['valueRelation']
                if use_vr:
                    value_relation = field['widgetcontrols']['valueRelation']
                    if 'activated' in value_relation and value_relation[
                            'activated']:
                        try:
                            vr_layer = value_relation['layer']
                            vr_layer = tools_qgis.get_layer_by_tablename(
                                vr_layer).id()  # Get layer id
                            vr_key_column = value_relation[
                                'keyColumn']  # Get 'Key'
                            vr_value_column = value_relation[
                                'valueColumn']  # Get 'Value'
                            vr_allow_nullvalue = value_relation[
                                'nullValue']  # Get null values
                            vr_filter_expression = value_relation[
                                'filterExpression']  # Get 'FilterExpression'
                            if vr_filter_expression is None:
                                vr_filter_expression = ''

                            # Create and apply ValueRelation config
                            editor_widget_setup = QgsEditorWidgetSetup(
                                'ValueRelation', {
                                    'Layer': f'{vr_layer}',
                                    'Key': f'{vr_key_column}',
                                    'Value': f'{vr_value_column}',
                                    'AllowNull': f'{vr_allow_nullvalue}',
                                    'FilterExpression':
                                    f'{vr_filter_expression}'
                                })
                            layer.setEditorWidgetSetup(field_index,
                                                       editor_widget_setup)

                        except Exception as e:
                            self.exception = e
                            self.vr_errors.add(layer_name)
                            if 'layer' in value_relation:
                                self.vr_missing.add(value_relation['layer'])
                            self.message = f"ValueRelation for {self.vr_errors} switched to ValueMap because " \
                                           f"layers {self.vr_missing} are not present on QGIS project"
                            use_vr = False

                if not use_vr:
                    # Manage new values in ValueMap
                    if field['widgettype'] == 'combo':
                        if 'comboIds' in field:
                            # Set values
                            for i in range(0, len(field['comboIds'])):
                                valuemap_values[field['comboNames']
                                                [i]] = field['comboIds'][i]
                        # Set values into valueMap
                        editor_widget_setup = QgsEditorWidgetSetup(
                            'ValueMap', {'map': valuemap_values})
                        layer.setEditorWidgetSetup(field_index,
                                                   editor_widget_setup)
                    elif field['widgettype'] == 'check':
                        config = {
                            'CheckedState': 'true',
                            'UncheckedState': 'false'
                        }
                        editor_widget_setup = QgsEditorWidgetSetup(
                            'CheckBox', config)
                        layer.setEditorWidgetSetup(field_index,
                                                   editor_widget_setup)
                    elif field['widgettype'] == 'datetime':
                        config = {
                            'allow_null': True,
                            'calendar_popup': True,
                            'display_format': 'yyyy-MM-dd',
                            'field_format': 'yyyy-MM-dd',
                            'field_iso_format': False
                        }
                        editor_widget_setup = QgsEditorWidgetSetup(
                            'DateTime', config)
                        layer.setEditorWidgetSetup(field_index,
                                                   editor_widget_setup)
                    elif field['widgettype'] == 'textarea':
                        editor_widget_setup = QgsEditorWidgetSetup(
                            'TextEdit', {'IsMultiline': 'True'})
                        layer.setEditorWidgetSetup(field_index,
                                                   editor_widget_setup)
                    else:
                        editor_widget_setup = QgsEditorWidgetSetup(
                            'TextEdit', {'IsMultiline': 'False'})
                        layer.setEditorWidgetSetup(field_index,
                                                   editor_widget_setup)

                # multiline: key comes from widgecontrol but it's used here in order to set false when key is missing
                if field['widgettype'] == 'text':
                    self._set_column_multiline(layer, field, field_index)

        if msg_failed != "":
            tools_qt.show_exception_message("Execute failed.", msg_failed)

        if msg_key != "":
            tools_qt.show_exception_message(
                "Key on returned json from ddbb is missed.", msg_key)
Exemple #15
0
def on_resolve_href(dialog, layer, feature, field):
    """
    @param dialog the dialog where the feature form is opened
    @param layer the layer on which the href link stands
    @param feature the current feature
    @param field the field name storing the href URL
    @param linked_layer_id the QGIS layer id of the already resolved layer, for update
    """
    from .import_gmlas_panel import ImportGmlasPanel
    path = feature[field]
    if not path:
        return

    # if parent is a Dialog, we are in a feature form
    # else in a attribute table
    is_feature_form = isinstance(dialog.parent, QDialog)

    # The href is resolved thanks to the OGR GMLAS driver.
    # We need to determine what is the "root" layer of the imported
    # href, so that we can connect the xlink:href link to the
    # newly loaded set of layers.
    # There seems to be no way to determine what is the "root" layer
    # of a GMLAS database.
    # So, we rely on XML parsing to determine the root element
    # and on layer xpath found in metadata

    # Download the file so that it is used for XML parsing
    # and for GMLAS loading
    from ..core.qgis_urlopener import remote_open_from_qgis
    from ..core.gml_utils import extract_features
    from ..core.xml_utils import xml_root_tag, no_ns, no_prefix
    import tempfile

    with remote_open_from_qgis(path) as fi:
        with tempfile.NamedTemporaryFile(delete=False) as fo:
            fo.write(fi.read())
            tmp_file = fo.name

    with open(tmp_file, 'r') as file_io:
        root_tag = xml_root_tag(file_io)

    # reuse the GMLAS import panel widget
    dlg = QDialog()
    import_widget = ImportGmlasPanel(dlg, gml_path=tmp_file)
    path_edit = QLineEdit(path, dlg)
    path_edit.setEnabled(False)
    btn = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, dlg)
    layout = QVBoxLayout()
    layout.addWidget(path_edit)
    layout.addWidget(import_widget)
    layout.addItem(
        QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding))
    layout.addWidget(btn)
    dlg.setLayout(layout)
    btn.accepted.connect(dlg.accept)
    btn.rejected.connect(dlg.reject)
    dlg.resize(400, 300)
    dlg.setWindowTitle("Options for xlink:href loading")
    if not dlg.exec_():
        return

    # close the current form
    w = dialog
    while not isinstance(w, QDialog):
        w = w.parent()
    w.close()

    import_widget.do_load()
    # Add a link between the current layer
    # and the root layer of the newly loaded (complex) features

    # 1. determine the root layer and pkid of all its features
    root_layer = None
    for l in QgsProject.instance().mapLayers().values():
        if no_ns(l.customProperty("xpath", "")) == no_prefix(root_tag):
            root_layer = l
            break
    if root_layer is None:
        raise RuntimeError("Cannot determine the root layer")

    pkid = layer.customProperty("pkid")
    pkid_value = feature[pkid]
    root_layer.startEditing()
    # 2. add a href_origin_pkid field in the root layer
    if "parent_href_pkid" not in [f.name() for f in root_layer.fields()]:
        new_field = QgsField(layer.fields().field(pkid))
        new_field.setName("parent_href_pkid")
        root_layer.addAttribute(new_field)

    # 3. set its value to the id of current feature
    ids_to_change = []
    for f in root_layer.getFeatures():
        if f["parent_href_pkid"] is None:
            ids_to_change.append(f.id())
    idx = root_layer.fields().indexFromName("parent_href_pkid")
    for fid in ids_to_change:
        # sets the pkid_value
        root_layer.changeAttributeValue(fid, idx, pkid_value)

    root_layer.commitChanges()

    # 4. declare a new QgsRelation
    rel_name = "1_n_" + layer.name() + "_" + field
    rel = QgsProject.instance().relationManager().relations().get(rel_name)
    if rel is None:
        rel = QgsRelation()
        rel.setId(rel_name)
        rel.setName(field)
        rel.setReferencedLayer(layer.id())
        rel.setReferencingLayer(root_layer.id())
        rel.addFieldPair("parent_href_pkid", pkid)
        QgsProject.instance().relationManager().addRelation(rel)

    # 5. declare the new relation in the form widgets
    # new 1:N in the current layer
    fc = layer.editFormConfig()
    rel_tab = fc.tabs()[1]
    rel_tab.addChildElement(
        QgsAttributeEditorRelation(rel.name(), rel, rel_tab))
    # new field in the root layer
    fc = root_layer.editFormConfig()
    main_tab = fc.tabs()[0]
    main_tab.addChildElement(
        QgsAttributeEditorField("parent_href_pkid", idx, main_tab))
    # declare as reference relation widget
    s = QgsEditorWidgetSetup(
        "RelationReference", {
            'AllowNULL': False,
            'ReadOnly': True,
            'Relation': rel.id(),
            'OrderByValue': False,
            'MapIdentification': False,
            'AllowAddFeatures': False,
            'ShowForm': True
        })
    root_layer.setEditorWidgetSetup(idx, s)

    # write metadata in layers
    href_resolved = layer.customProperty("href_resolved", [])
    if path not in href_resolved:
        layer.setCustomProperty("href_resolved", href_resolved + [path])
    href_linked_layers = layer.customProperty("href_linked_layers", {})
    href_linked_layers[field] = root_layer.id()
    layer.setCustomProperty("href_linked_layers", href_linked_layers)

    # 6. reload the current form
    from ..main import get_iface
    if is_feature_form:
        get_iface().openFeatureForm(layer, feature)
    else:
        get_iface().showAttributeTable(layer)
Exemple #16
0
def import_in_qgis(gmlas_uri, provider, schema=None):
    """Imports layers from a GMLAS file in QGIS with relations and editor widgets

    @param gmlas_uri connection parameters
    @param provider name of the QGIS provider that handles gmlas_uri parameters (postgresql or spatialite)
    @param schema name of the PostgreSQL schema where tables and metadata tables are
    """
    if schema is not None:
        schema_s = schema + "."
    else:
        schema_s = ""

    ogr.UseExceptions()
    drv = ogr.GetDriverByName(provider)
    ds = drv.Open(gmlas_uri)
    if ds is None:
        raise RuntimeError("Problem opening {}".format(gmlas_uri))

    # get list of layers
    sql = "select o.*, g.f_geometry_column, g.srid from {}_ogr_layers_metadata o left join geometry_columns g on g.f_table_name = o.layer_name".format(
        schema_s)

    couches = ds.ExecuteSQL(sql)
    layers = {}
    for f in couches:
        ln = f.GetField("layer_name")
        if ln not in layers:
            layers[ln] = {
                "uid": f.GetField("layer_pkid_name"),
                "category": f.GetField("layer_category"),
                "xpath": f.GetField("layer_xpath"),
                "parent_pkid": f.GetField("layer_parent_pkid_name"),
                "srid": f.GetField("srid"),
                "geometry_column": f.GetField("f_geometry_column"),
                "1_n": [],  # 1:N relations
                "layer_id": None,
                "layer_name": ln,
                "layer": None,
                "fields": [],
            }
        else:
            # additional geometry columns
            g = f.GetField("f_geometry_column")
            k = "{} ({})".format(ln, g)
            layers[k] = dict(layers[ln])
            layers[k]["geometry_column"] = g

    # collect fields with xlink:href
    href_fields = {}
    for ln, layer in layers.items():
        layer_name = layer["layer_name"]
        for f in ds.ExecuteSQL(
                "select field_name, field_xpath from {}_ogr_fields_metadata where layer_name='{}'"
                .format(schema_s, layer_name)):
            field_name, field_xpath = f.GetField("field_name"), f.GetField(
                "field_xpath")
            if field_xpath and field_xpath.endswith("@xlink:href"):
                if ln not in href_fields:
                    href_fields[ln] = []
                href_fields[ln].append(field_name)

    # with unknown srid, don't ask for each layer, set to a default
    settings = QgsSettings()
    projection_behavior = settings.value("Projections/defaultBehavior")
    projection_default = settings.value("Projections/layerDefaultCrs")
    settings.setValue("Projections/defaultBehavior", "useGlobal")
    settings.setValue("Projections/layerDefaultCrs", "EPSG:4326")

    # add layers
    crs = QgsCoordinateReferenceSystem("EPSG:4326")
    for ln in sorted(layers.keys()):
        lyr = layers[ln]
        g_column = lyr["geometry_column"] or None
        couches = _qgis_layer(
            gmlas_uri,
            schema,
            lyr["layer_name"],
            g_column,
            provider,
            ln,
            lyr["xpath"],
            lyr["uid"],
        )
        if not couches.isValid():
            raise RuntimeError("Problem loading layer {} with {}".format(
                ln, couches.source()))
        if g_column is not None:
            if lyr["srid"]:
                crs = QgsCoordinateReferenceSystem("EPSG:{}".format(
                    lyr["srid"]))
            couches.setCrs(crs)
        QgsProject.instance().addMapLayer(couches)
        layers[ln]["layer_id"] = couches.id()
        layers[ln]["layer"] = couches
        # save fields which represent a xlink:href
        if ln in href_fields:
            couches.setCustomProperty("href_fields", href_fields[ln])
        # save gmlas_uri
        couches.setCustomProperty("ogr_uri", gmlas_uri)
        couches.setCustomProperty("ogr_schema", schema)

        # change icon the layer has a custom viewer
        xpath = no_ns(couches.customProperty("xpath", ""))
        for viewer_cls, _ in get_custom_viewers().values():
            tag = no_prefix(viewer_cls.xml_tag())
            if tag == xpath:
                lg = CustomViewerLegend(viewer_cls.name(), viewer_cls.icon())
                couches.setLegend(lg)

    # restore settings
    settings.setValue("Projections/defaultBehavior", projection_behavior)
    settings.setValue("Projections/layerDefaultCrs", projection_default)

    # add 1:1 relations
    relations_1_1 = []
    sql = """
select
  layer_name, field_name, field_related_layer, r.child_pkid
from
  {0}_ogr_fields_metadata f
  join {0}_ogr_layer_relationships r
    on r.parent_layer = f.layer_name
   and r.parent_element_name = f.field_name
where
  field_category in ('PATH_TO_CHILD_ELEMENT_WITH_LINK', 'PATH_TO_CHILD_ELEMENT_NO_LINK')
  and field_max_occurs=1
""".format(schema_s)
    couches = ds.ExecuteSQL(sql)
    if couches is not None:
        for f in couches:
            rel = QgsRelation()
            rel.setId("1_1_" + f.GetField("layer_name") + "_" +
                      f.GetField("field_name"))
            rel.setName("1_1_" + f.GetField("layer_name") + "_" +
                        f.GetField("field_name"))
            # parent layer
            rel.setReferencingLayer(
                layers[f.GetField("layer_name")]["layer_id"])
            # child layer
            rel.setReferencedLayer(
                layers[f.GetField("field_related_layer")]["layer_id"])
            # parent, child
            rel.addFieldPair(f.GetField("field_name"),
                             f.GetField("child_pkid"))
            # rel.generateId()
            if rel.isValid():
                relations_1_1.append(rel)

    # add 1:N relations
    relations_1_n = []
    sql = """
select
  layer_name, r.parent_pkid, field_related_layer as child_layer, r.child_pkid
from
  {0}_ogr_fields_metadata f
  join {0}_ogr_layer_relationships r
    on r.parent_layer = f.layer_name
   and r.child_layer = f.field_related_layer
where
  field_category in ('PATH_TO_CHILD_ELEMENT_WITH_LINK', 'PATH_TO_CHILD_ELEMENT_NO_LINK')
  and field_max_occurs>1
-- junctions - 1st way
union all
select
  layer_name, r.parent_pkid, field_junction_layer as child_layer, 'parent_pkid' as child_pkid
from
  {0}_ogr_fields_metadata f
  join {0}_ogr_layer_relationships r
    on r.parent_layer = f.layer_name
   and r.child_layer = f.field_related_layer
where
  field_category = 'PATH_TO_CHILD_ELEMENT_WITH_JUNCTION_TABLE'
-- junctions - 2nd way
union all
select
  field_related_layer as layer_name, r.child_pkid, field_junction_layer as child_layer, 'child_pkid' as child_pkid
from
  {0}_ogr_fields_metadata f
  join {0}_ogr_layer_relationships r
    on r.parent_layer = f.layer_name
   and r.child_layer = f.field_related_layer
where
  field_category = 'PATH_TO_CHILD_ELEMENT_WITH_JUNCTION_TABLE'
""".format(schema_s)
    couches = ds.ExecuteSQL(sql)
    if couches is not None:
        for f in couches:
            parent_layer = f.GetField("layer_name")
            child_layer = f.GetField("child_layer")
            if parent_layer not in layers or child_layer not in layers:
                continue
            rel = QgsRelation()
            rel.setId("1_n_" + f.GetField("layer_name") + "_" +
                      f.GetField("child_layer") + "_" +
                      f.GetField("parent_pkid") + "_" +
                      f.GetField("child_pkid"))
            rel.setName(f.GetField("child_layer"))
            # parent layer
            rel.setReferencedLayer(layers[parent_layer]["layer_id"])
            # child layer
            rel.setReferencingLayer(layers[child_layer]["layer_id"])
            # parent, child
            rel.addFieldPair(f.GetField("child_pkid"),
                             f.GetField("parent_pkid"))
            # rel.addFieldPair(f.GetField('child_pkid'), 'ogc_fid')
            if rel.isValid():
                relations_1_n.append(rel)
                # add relation to layer
                layers[f.GetField("layer_name")]["1_n"].append(rel)

    for rel in relations_1_1 + relations_1_n:
        QgsProject.instance().relationManager().addRelation(rel)

    # add "show form" option to 1:1 relations
    for rel in relations_1_1:
        couches = rel.referencingLayer()
        idx = rel.referencingFields()[0]
        s = QgsEditorWidgetSetup(
            "RelationReference",
            {
                "AllowNULL": False,
                "ReadOnly": True,
                "Relation": rel.id(),
                "OrderByValue": False,
                "MapIdentification": False,
                "AllowAddFeatures": False,
                "ShowForm": True,
            },
        )
        couches.setEditorWidgetSetup(idx, s)

    # setup form for layers
    for layer, lyr in layers.items():
        couche = lyr["layer"]
        fc = couche.editFormConfig()
        fc.clearTabs()
        fc.setLayout(QgsEditFormConfig.TabLayout)
        # Add fields
        c = QgsAttributeEditorContainer("Main", fc.invisibleRootContainer())
        c.setIsGroupBox(False)  # a tab
        for idx, f in enumerate(couche.fields()):
            c.addChildElement(QgsAttributeEditorField(f.name(), idx, c))
        fc.addTab(c)

        # Add 1:N relations
        c_1_n = QgsAttributeEditorContainer("1:N links",
                                            fc.invisibleRootContainer())
        c_1_n.setIsGroupBox(False)  # a tab
        fc.addTab(c_1_n)

        for rel in lyr["1_n"]:
            c_1_n.addChildElement(
                QgsAttributeEditorRelation(rel.name(), rel, c_1_n))

        couche.setEditFormConfig(fc)

        install_viewer_on_feature_form(couche)
Exemple #17
0
    def create(self, path: str, qgis_project: QgsProject):
        qgis_project.setAutoTransaction(self.auto_transaction)
        qgis_project.setEvaluateDefaultValues(self.evaluate_default_values)
        qgis_layers = list()
        for layer in self.layers:
            qgis_layer = layer.create()
            self.layer_added.emit(qgis_layer.id())
            if not self.crs and qgis_layer.isSpatial():
                self.crs = qgis_layer.crs()

            qgis_layers.append(qgis_layer)

        qgis_project.addMapLayers(qgis_layers, not self.legend)

        if self.crs:
            if isinstance(self.crs, QgsCoordinateReferenceSystem):
                qgis_project.setCrs(self.crs)
            else:
                qgis_project.setCrs(
                    QgsCoordinateReferenceSystem.fromEpsgId(self.crs))

        qgis_relations = list(
            qgis_project.relationManager().relations().values())
        dict_domains = {
            layer.layer.id(): layer.is_domain
            for layer in self.layers
        }
        for relation in self.relations:
            rel = relation.create(qgis_project, qgis_relations)
            assert rel.isValid()
            qgis_relations.append(rel)

            if rel.referencedLayerId() in dict_domains and dict_domains[
                    rel.referencedLayerId()]:
                editor_widget_setup = QgsEditorWidgetSetup(
                    'RelationReference', {
                        'Relation': rel.id(),
                        'ShowForm': False,
                        'OrderByValue': True,
                        'ShowOpenFormButton': False
                    })
            else:
                editor_widget_setup = QgsEditorWidgetSetup(
                    'RelationReference', {
                        'Relation': rel.id(),
                        'ShowForm': False,
                        'OrderByValue': True,
                        'ShowOpenFormButton': False,
                        'AllowAddFeatures': True
                    })

            referencing_layer = rel.referencingLayer()
            referencing_layer.setEditorWidgetSetup(rel.referencingFields()[0],
                                                   editor_widget_setup)

        qgis_project.relationManager().setRelations(qgis_relations)

        # Set Bag of Enum widget
        for layer_name, bag_of_enum in self.bags_of_enum.items():
            for attribute, bag_of_enum_info in bag_of_enum.items():
                layer_obj = bag_of_enum_info[0]
                cardinality = bag_of_enum_info[1]
                domain_table = bag_of_enum_info[2]
                key_field = bag_of_enum_info[3]
                value_field = bag_of_enum_info[4]

                allow_null = cardinality.startswith('0')
                allow_multi = cardinality.endswith('*')

                current_layer = layer_obj.create()

                field_widget = 'ValueRelation'
                field_widget_config = {
                    'AllowMulti': allow_multi,
                    'UseCompleter': False,
                    'Value': value_field,
                    'OrderByValue': False,
                    'AllowNull': allow_null,
                    'Layer': domain_table.create().id(),
                    'FilterExpression': '',
                    'Key': key_field,
                    'NofColumns': 1
                }

                field_idx = current_layer.fields().indexOf(attribute)
                setup = QgsEditorWidgetSetup(field_widget, field_widget_config)
                current_layer.setEditorWidgetSetup(field_idx, setup)

        for layer in self.layers:
            layer.create_form(self)

        if self.legend:
            self.legend.create(qgis_project)

        if path:
            qgis_project.write(path)
 def setEditorWidgetSetup(self, layer, fieldName, mapConfig):
     """Docstring"""
     layerIdx = layer.fields().indexOf(fieldName)
     setup = QgsEditorWidgetSetup('ValueMap', mapConfig)
     layer.setEditorWidgetSetup(layerIdx, setup)
    def refresh_attribute_table(self, **kwargs):
        """ Function called in def wait_notifications(...) -->  getattr(self, function_name)(**params) """
        """ Set layer fields configured according to client configuration.
            At the moment manage:
                Column names as alias, combos and typeahead as ValueMap"""

        # Get list of layer names
        layers_name_list = kwargs['tableName']
        if not layers_name_list:
            return

        for layer_name in layers_name_list:
            layer = self.controller.get_layer_by_tablename(layer_name)
            if not layer:
                msg = f"Layer {layer_name} does not found, therefore, not configured"
                self.controller.log_info(msg)
                continue

            feature = '"tableName":"' + str(layer_name) + '", "id":""'
            body = self.create_body(feature=feature)
            sql = f"SELECT gw_api_getinfofromid($${{{body}}}$$)"
            row = self.controller.get_row(sql, log_sql=True, commit=True)
            if not row:
                self.controller.log_info(f'NOT ROW FOR: {sql}')
                continue

            # When info is nothing
            if 'results' in row[0]:
                if row[0]['results'] == 0:
                    self.controller.log_info(f"{row[0]['message']['text']}")
                    continue

            complet_result = row[0]
            for field in complet_result['body']['data']['fields']:
                _values = {}
                # Get column index
                fieldIndex = layer.fields().indexFromName(field['column_id'])

                # Hide selected fields according table config_api_form_fields.hidden
                if 'hidden' in field:
                    self.set_column_visibility(layer, field['column_id'],
                                               field['hidden'])
                # Set multiline fields according table config_api_form_fields.widgetcontrols['setQgisMultiline']
                if field[
                        'widgetcontrols'] is not None and 'setQgisMultiline' in field[
                            'widgetcontrols']:
                    self.set_column_multiline(layer, field, fieldIndex)
                # Set alias column
                if field['label']:
                    layer.setFieldAlias(fieldIndex, field['label'])

                # Set field constraints
                if field['widgetcontrols'] and 'setQgisConstraints' in field[
                        'widgetcontrols']:
                    if field['widgetcontrols']['setQgisConstraints'] is True:
                        layer.setFieldConstraint(
                            fieldIndex, QgsFieldConstraints.ConstraintNotNull,
                            QgsFieldConstraints.ConstraintStrengthSoft)
                        layer.setFieldConstraint(
                            fieldIndex, QgsFieldConstraints.ConstraintUnique,
                            QgsFieldConstraints.ConstraintStrengthHard)

                # Manage editability
                self.set_read_only(layer, field, fieldIndex)

                # Manage fields
                if field['widgettype'] == 'combo':
                    if 'comboIds' in field:
                        for i in range(0, len(field['comboIds'])):
                            _values[field['comboNames']
                                    [i]] = field['comboIds'][i]
                    # Set values into valueMap
                    editor_widget_setup = QgsEditorWidgetSetup(
                        'ValueMap', {'map': _values})
                    layer.setEditorWidgetSetup(fieldIndex, editor_widget_setup)
    def batch_insert(self,
                     relation: QgsRelation,
                     features: [QgsFeature],
                     data=None):
        """
        :param relation: the relation
        :param features: the list of feature on the referenced layer
        :return:
        """
        layer = relation.referencingLayer()
        if not layer.isEditable():
            self.iface.messageBar().pushMessage(
                'Relation Batch Insert',
                self.tr('layer "{layer}" is not editable').format(
                    layer=layer.name()), Qgis.Warning)
            return

        if len(features) < 1:
            self.iface.messageBar().pushMessage(
                'Relation Batch Insert',
                self.
                tr('There is no features to batch insert for. Select some in layer "{layer}" first.'
                   ).format(layer=relation.referencedLayer().name()),
                Qgis.Warning)
            return

        default_values = {}
        orignal_cfg = {}
        first_feature_created = False
        referencing_feature = QgsFeature()
        features_written = 0
        ok = True

        for referenced_feature in features:
            # define values for the referencing field over the possible several field pairs
            for referencing_field, referenced_field in relation.fieldPairs(
            ).items():
                referencing_field_index = relation.referencingLayer().fields(
                ).indexFromName(referencing_field)

                # disabled the widgets for the referenced feature in the form (since it will be replaced)
                if not first_feature_created:
                    default_values[
                        referencing_field_index] = referenced_feature[
                            referenced_field]
                    orignal_cfg[
                        referencing_field_index] = layer.editorWidgetSetup(
                            referencing_field_index)
                    layer.setEditorWidgetSetup(
                        referencing_field_index,
                        QgsEditorWidgetSetup('Hidden', {}))

                else:
                    # it has been created at previous iteration
                    referencing_feature[
                        referencing_field_index] = referenced_feature[
                            referenced_field]

            if not first_feature_created:
                # show form for the feature with disabled widgets for the referencing fields
                ok, referencing_feature = self.iface.vectorLayerTools(
                ).addFeature(layer, default_values, QgsGeometry())
                if not ok:
                    break
                # restore widget config of the layer
                for index, cfg in orignal_cfg.items():
                    layer.setEditorWidgetSetup(index, cfg)
                first_feature_created = True
            else:
                ok = layer.addFeature(referencing_feature)
                if not ok:
                    break
            features_written += 1

        if ok:
            self.iface.messageBar().pushMessage(
                'Relation Batch Insert',
                self.tr('{count} features were written to "{layer}"').format(
                    count=features_written, layer=layer.name()))
        else:
            self.iface.messageBar().pushMessage(
                'Relation Batch Insert',
                self.tr('There was an error while inserting features, '
                        '{count} features were written to "{layer}", '
                        '{expected_count} were expected.').format(
                            count=features_written,
                            layer=layer.name(),
                            expected_count=len(features)), Qgis.Critical)
Exemple #21
0
    def refresh_attribute_table(self, **kwargs):
        """ Function called in def wait_notifications(...) -->  getattr(self, function_name)(**params) """
        """ Set layer fields configured according to client configuration.
            At the moment manage:
                Column names as alias, combos and typeahead as ValueMap"""

        # Get list of layer names
        layers_name_list = kwargs['tableName']
        if not layers_name_list:
            return

        for layer_name in layers_name_list:
            layer = self.controller.get_layer_by_tablename(layer_name)
            if not layer:
                msg = f"Layer {layer_name} does not found, therefore, not configured"
                self.controller.log_info(msg)
                continue

            # get sys variale
            self.qgis_project_infotype = self.controller.plugin_settings_value(
                'infoType')

            feature = '"tableName":"' + str(
                layer_name) + '", "id":"", "isLayer":true'
            extras = f'"infoType":"{self.qgis_project_infotype}"'
            body = self.create_body(feature=feature, extras=extras)
            result = self.controller.get_json('gw_fct_getinfofromid',
                                              body,
                                              is_notify=True,
                                              log_sql=True)
            if not result:
                continue
            for field in result['body']['data']['fields']:
                _values = {}
                # Get column index
                fieldIndex = layer.fields().indexFromName(field['columnname'])

                # Hide selected fields according table config_api_form_fields.hidden
                if 'hidden' in field:
                    self.set_column_visibility(layer, field['columnname'],
                                               field['hidden'])
                # Set multiline fields according table config_api_form_fields.widgetcontrols['setQgisMultiline']
                if field[
                        'widgetcontrols'] is not None and 'setQgisMultiline' in field[
                            'widgetcontrols']:
                    self.set_column_multiline(layer, field, fieldIndex)
                # Set alias column
                if field['label']:
                    layer.setFieldAlias(fieldIndex, field['label'])

                # Set field constraints
                if field['widgetcontrols'] and 'setQgisConstraints' in field[
                        'widgetcontrols']:
                    if field['widgetcontrols']['setQgisConstraints'] is True:
                        layer.setFieldConstraint(
                            fieldIndex, QgsFieldConstraints.ConstraintNotNull,
                            QgsFieldConstraints.ConstraintStrengthSoft)
                        layer.setFieldConstraint(
                            fieldIndex, QgsFieldConstraints.ConstraintUnique,
                            QgsFieldConstraints.ConstraintStrengthHard)

                # Manage editability
                self.set_read_only(layer, field, fieldIndex)

                # Manage fields
                if field['widgettype'] == 'combo':
                    if 'comboIds' in field:
                        for i in range(0, len(field['comboIds'])):
                            _values[field['comboNames']
                                    [i]] = field['comboIds'][i]
                    # Set values into valueMap
                    editor_widget_setup = QgsEditorWidgetSetup(
                        'ValueMap', {'map': _values})
                    layer.setEditorWidgetSetup(fieldIndex, editor_widget_setup)
                elif field['widgettype'] == 'text':
                    editor_widget_setup = QgsEditorWidgetSetup(
                        'TextEdit', {'IsMultiline': 'True'})
                    layer.setEditorWidgetSetup(fieldIndex, editor_widget_setup)
                elif field['widgettype'] == 'check':
                    config = {
                        'CheckedState': 'true',
                        'UncheckedState': 'false'
                    }
                    editor_widget_setup = QgsEditorWidgetSetup(
                        'CheckBox', config)
                    layer.setEditorWidgetSetup(fieldIndex, editor_widget_setup)
                elif field['widgettype'] == 'datetime':
                    config = {
                        'allow_null': True,
                        'calendar_popup': True,
                        'display_format': 'yyyy-MM-dd',
                        'field_format': 'yyyy-MM-dd',
                        'field_iso_format': False
                    }
                    editor_widget_setup = QgsEditorWidgetSetup(
                        'DateTime', config)
                    layer.setEditorWidgetSetup(fieldIndex, editor_widget_setup)
                else:
                    editor_widget_setup = QgsEditorWidgetSetup(
                        'TextEdit', {'IsMultiline': 'True'})
                    layer.setEditorWidgetSetup(fieldIndex, editor_widget_setup)
Exemple #22
0
    def testExportFeatureRelations(self):
        """ Test exporting a feature with relations """

        #parent layer
        parent = QgsVectorLayer(
            "Point?field=fldtxt:string&field=fldint:integer&field=foreignkey:integer",
            "parent", "memory")
        pr = parent.dataProvider()
        pf1 = QgsFeature()
        pf1.setFields(parent.fields())
        pf1.setAttributes(["test1", 67, 123])
        pf2 = QgsFeature()
        pf2.setFields(parent.fields())
        pf2.setAttributes(["test2", 68, 124])
        assert pr.addFeatures([pf1, pf2])

        #child layer
        child = QgsVectorLayer(
            "Point?field=x:string&field=y:integer&field=z:integer",
            "referencedlayer", "memory")
        pr = child.dataProvider()
        f1 = QgsFeature()
        f1.setFields(child.fields())
        f1.setAttributes(["foo", 123, 321])
        f2 = QgsFeature()
        f2.setFields(child.fields())
        f2.setAttributes(["bar", 123, 654])
        f3 = QgsFeature()
        f3.setFields(child.fields())
        f3.setAttributes(["foobar", 124, 554])
        assert pr.addFeatures([f1, f2, f3])

        QgsProject.instance().addMapLayers([child, parent])

        rel = QgsRelation()
        rel.setId('rel1')
        rel.setName('relation one')
        rel.setReferencingLayer(child.id())
        rel.setReferencedLayer(parent.id())
        rel.addFieldPair('y', 'foreignkey')

        QgsProject.instance().relationManager().addRelation(rel)

        exporter = QgsJsonExporter()

        exporter.setVectorLayer(parent)
        self.assertEqual(exporter.vectorLayer(), parent)
        exporter.setIncludeRelated(True)
        self.assertEqual(exporter.includeRelated(), True)

        expected = """{
   "type":"Feature",
   "id":0,
   "geometry":null,
   "properties":{
      "fldtxt":"test1",
      "fldint":67,
      "foreignkey":123,
      "relation one":[{"x":"foo",
"y":123,
"z":321},
{"x":"bar",
"y":123,
"z":654}]
   }
}"""
        self.assertEqual(exporter.exportFeature(pf1), expected)

        expected = """{
   "type":"Feature",
   "id":0,
   "geometry":null,
   "properties":{
      "fldtxt":"test2",
      "fldint":68,
      "foreignkey":124,
      "relation one":[{"x":"foobar",
"y":124,
"z":554}]
   }
}"""
        self.assertEqual(exporter.exportFeature(pf2), expected)

        # with field formatter
        setup = QgsEditorWidgetSetup('ValueMap',
                                     {"map": {
                                         "apples": 123,
                                         "bananas": 124
                                     }})
        child.setEditorWidgetSetup(1, setup)
        expected = """{
   "type":"Feature",
   "id":0,
   "geometry":null,
   "properties":{
      "fldtxt":"test1",
      "fldint":67,
      "foreignkey":123,
      "relation one":[{"x":"foo",
"y":"apples",
"z":321},
{"x":"bar",
"y":"apples",
"z":654}]
   }
}"""
        self.assertEqual(exporter.exportFeature(pf1), expected)

        # test excluding related attributes
        exporter.setIncludeRelated(False)
        self.assertEqual(exporter.includeRelated(), False)

        expected = """{
   "type":"Feature",
   "id":0,
   "geometry":null,
   "properties":{
      "fldtxt":"test2",
      "fldint":68,
      "foreignkey":124
   }
}"""
        self.assertEqual(exporter.exportFeature(pf2), expected)

        # test without vector layer set
        exporter.setIncludeRelated(True)
        exporter.setVectorLayer(None)

        expected = """{
   "type":"Feature",
   "id":0,
   "geometry":null,
   "properties":{
      "fldtxt":"test2",
      "fldint":68,
      "foreignkey":124
   }
}"""
        self.assertEqual(exporter.exportFeature(pf2), expected)
def import_in_qgis(gmlas_uri, provider, schema=None):
    """Imports layers from a GMLAS file in QGIS with relations and editor widgets

    @param gmlas_uri connection parameters
    @param provider name of the QGIS provider that handles gmlas_uri parameters (postgresql or spatialite)
    @param schema name of the PostgreSQL schema where tables and metadata tables are
    """
    if schema is not None:
        schema_s = schema + "."
    else:
        schema_s = ""

    ogr.UseExceptions()
    drv = ogr.GetDriverByName(provider)
    ds = drv.Open(gmlas_uri)
    if ds is None:
        raise RuntimeError("Problem opening {}".format(gmlas_uri))

    # get list of layers
    sql = "select o.*, g.f_geometry_column, g.srid from {}_ogr_layers_metadata o left join geometry_columns g on g.f_table_name = o.layer_name".format(
        schema_s)

    l = ds.ExecuteSQL(sql)
    layers = {}
    for f in l:
        ln = f.GetField("layer_name")
        if ln not in layers:
            layers[ln] = {
                'uid': f.GetField("layer_pkid_name"),
                'category': f.GetField("layer_category"),
                'xpath': f.GetField("layer_xpath"),
                'parent_pkid': f.GetField("layer_parent_pkid_name"),
                'srid': f.GetField("srid"),
                'geometry_column': f.GetField("f_geometry_column"),
                '1_n': [],  # 1:N relations
                'layer_id': None,
                'layer_name': ln,
                'layer': None,
                'fields': []
            }
        else:
            # additional geometry columns
            g = f.GetField("f_geometry_column")
            k = "{} ({})".format(ln, g)
            layers[k] = dict(layers[ln])
            layers[k]["geometry_column"] = g

    crs = QgsCoordinateReferenceSystem("EPSG:4326")
    for ln in sorted(layers.keys()):
        lyr = layers[ln]
        g_column = lyr["geometry_column"] or None
        l = _qgis_layer(gmlas_uri,
                        schema,
                        lyr["layer_name"],
                        g_column,
                        provider,
                        qgis_layer_name=ln)
        if not l.isValid():
            raise RuntimeError("Problem loading layer {} with {}".format(
                ln, l.source()))
        if lyr["srid"]:
            crs = QgsCoordinateReferenceSystem("EPSG:{}".format(lyr["srid"]))
        l.setCrs(crs)
        QgsProject.instance().addMapLayer(l)
        layers[ln]['layer_id'] = l.id()
        layers[ln]['layer'] = l

    # add 1:1 relations
    relations_1_1 = []
    sql = """
select
  layer_name, field_name, field_related_layer, r.child_pkid
from
  {0}_ogr_fields_metadata f
  join {0}_ogr_layer_relationships r
    on r.parent_layer = f.layer_name
   and r.parent_element_name = f.field_name
where
  field_category in ('PATH_TO_CHILD_ELEMENT_WITH_LINK', 'PATH_TO_CHILD_ELEMENT_NO_LINK')
  and field_max_occurs=1
""".format(schema_s)
    l = ds.ExecuteSQL(sql)
    if l is not None:
        for f in l:
            rel = QgsRelation()
            rel.setId('1_1_' + f.GetField('layer_name') + '_' +
                      f.GetField('field_name'))
            rel.setName('1_1_' + f.GetField('layer_name') + '_' +
                        f.GetField('field_name'))
            # parent layer
            rel.setReferencingLayer(
                layers[f.GetField('layer_name')]['layer_id'])
            # child layer
            rel.setReferencedLayer(
                layers[f.GetField('field_related_layer')]['layer_id'])
            # parent, child
            rel.addFieldPair(f.GetField('field_name'),
                             f.GetField('child_pkid'))
            #rel.generateId()
            if rel.isValid():
                relations_1_1.append(rel)

    # add 1:N relations
    relations_1_n = []
    sql = """
select
  layer_name, r.parent_pkid, field_related_layer as child_layer, r.child_pkid
from
  {0}_ogr_fields_metadata f
  join {0}_ogr_layer_relationships r
    on r.parent_layer = f.layer_name
   and r.child_layer = f.field_related_layer
where
  field_category in ('PATH_TO_CHILD_ELEMENT_WITH_LINK', 'PATH_TO_CHILD_ELEMENT_NO_LINK')
  and field_max_occurs>1
-- junctions - 1st way
union all
select
  layer_name, r.parent_pkid, field_junction_layer as child_layer, 'parent_pkid' as child_pkid
from
  {0}_ogr_fields_metadata f
  join {0}_ogr_layer_relationships r
    on r.parent_layer = f.layer_name
   and r.child_layer = f.field_related_layer
where
  field_category = 'PATH_TO_CHILD_ELEMENT_WITH_JUNCTION_TABLE'
-- junctions - 2nd way
union all
select
  field_related_layer as layer_name, r.child_pkid, field_junction_layer as child_layer, 'child_pkid' as child_pkid
from
  {0}_ogr_fields_metadata f
  join {0}_ogr_layer_relationships r
    on r.parent_layer = f.layer_name
   and r.child_layer = f.field_related_layer
where
  field_category = 'PATH_TO_CHILD_ELEMENT_WITH_JUNCTION_TABLE'
""".format(schema_s)
    l = ds.ExecuteSQL(sql)
    if l is not None:
        for f in l:
            rel = QgsRelation()
            rel.setId('1_n_' + f.GetField('layer_name') + '_' +
                      f.GetField('child_layer') + '_' +
                      f.GetField('parent_pkid') + '_' +
                      f.GetField('child_pkid'))
            rel.setName(f.GetField('child_layer'))
            # parent layer
            rel.setReferencedLayer(
                layers[f.GetField('layer_name')]['layer_id'])
            # child layer
            rel.setReferencingLayer(
                layers[f.GetField('child_layer')]['layer_id'])
            # parent, child
            rel.addFieldPair(f.GetField('child_pkid'),
                             f.GetField('parent_pkid'))
            #rel.addFieldPair(f.GetField('child_pkid'), 'ogc_fid')
            if rel.isValid():
                relations_1_n.append(rel)
                # add relation to layer
                layers[f.GetField('layer_name')]['1_n'].append(rel)

    QgsProject.instance().relationManager().setRelations(relations_1_1 +
                                                         relations_1_n)

    # add "show form" option to 1:1 relations
    for rel in relations_1_1:
        l = rel.referencingLayer()
        idx = rel.referencingFields()[0]
        s = QgsEditorWidgetSetup(
            "RelationReference", {
                'AllowNULL': False,
                'ReadOnly': True,
                'Relation': rel.id(),
                'OrderByValue': False,
                'MapIdentification': False,
                'AllowAddFeatures': False,
                'ShowForm': True
            })
        l.setEditorWidgetSetup(idx, s)

    # setup form for layers
    for layer, lyr in layers.items():
        l = lyr['layer']
        fc = l.editFormConfig()
        fc.clearTabs()
        fc.setLayout(QgsEditFormConfig.TabLayout)
        # Add fields
        c = QgsAttributeEditorContainer("Main", fc.invisibleRootContainer())
        c.setIsGroupBox(False)  # a tab
        for idx, f in enumerate(l.fields()):
            c.addChildElement(QgsAttributeEditorField(f.name(), idx, c))
        fc.addTab(c)

        # Add 1:N relations
        c_1_n = QgsAttributeEditorContainer("1:N links",
                                            fc.invisibleRootContainer())
        c_1_n.setIsGroupBox(False)  # a tab
        fc.addTab(c_1_n)

        for rel in lyr['1_n']:
            c_1_n.addChildElement(
                QgsAttributeEditorRelation(rel.name(), rel, c_1_n))

        l.setEditFormConfig(fc)