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)
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)
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)
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)
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'))
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)
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
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)
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)
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)
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)
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)
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)