def test_isValid(self): rel = QgsRelation() assert not rel.isValid() rel.setRelationId("rel1") assert not rel.isValid() rel.setRelationName("Relation Number One") assert not rel.isValid() rel.setReferencingLayer(self.referencingLayer.id()) assert not rel.isValid() rel.setReferencedLayer(self.referencedLayer.id()) assert not rel.isValid() rel.addFieldPair("foreignkey", "y") assert rel.isValid()
def test_isValid(self): rel = QgsRelation() assert not rel.isValid() rel.setId('rel1') assert not rel.isValid() rel.setName('Relation Number One') assert not rel.isValid() rel.setReferencingLayer(self.referencingLayer.id()) assert not rel.isValid() rel.setReferencedLayer(self.referencedLayer.id()) assert not rel.isValid() rel.addFieldPair('foreignkey', 'y') assert rel.isValid()
def test_isValid(self): referencedLayer = createReferencedLayer() referencingLayer = createReferencingLayer() QgsMapLayerRegistry.instance().addMapLayers([referencedLayer,referencingLayer]) rel = QgsRelation() assert not rel.isValid() rel.setRelationId( 'rel1' ) assert not rel.isValid() rel.setRelationName( 'Relation Number One' ) assert not rel.isValid() rel.setReferencingLayer( referencingLayer.id() ) assert not rel.isValid() rel.setReferencedLayer( referencedLayer.id() ) assert not rel.isValid() rel.addFieldPair( 'foreignkey', 'y' ) assert rel.isValid() QgsMapLayerRegistry.instance().removeAllMapLayers()
def testDuplicateFeature(self): """ test duplicating a feature """ project = QgsProject().instance() # LAYERS # - add first layer (parent) layer1 = QgsVectorLayer("Point?field=fldtxt:string&field=pkid:integer", "parentlayer", "memory") # > check first layer (parent) self.assertTrue(layer1.isValid()) # - set the value for the copy layer1.setDefaultValueDefinition(1, QgsDefaultValue("rand(1000,2000)")) # > check first layer (parent) self.assertTrue(layer1.isValid()) # - add second layer (child) layer2 = QgsVectorLayer("Point?field=fldtxt:string&field=id:integer&field=foreign_key:integer", "childlayer", "memory") # > check second layer (child) self.assertTrue(layer2.isValid()) # - add layers project.addMapLayers([layer1, layer2]) # FEATURES # - add 2 features on layer1 (parent) l1f1orig = QgsFeature() l1f1orig.setFields(layer1.fields()) l1f1orig.setAttributes(["F_l1f1", 100]) l1f2orig = QgsFeature() l1f2orig.setFields(layer1.fields()) l1f2orig.setAttributes(["F_l1f2", 101]) # > check by adding features self.assertTrue(layer1.dataProvider().addFeatures([l1f1orig, l1f2orig])) # add 4 features on layer2 (child) l2f1orig = QgsFeature() l2f1orig.setFields(layer2.fields()) l2f1orig.setAttributes(["F_l2f1", 201, 100]) l2f2orig = QgsFeature() l2f2orig.setFields(layer2.fields()) l2f2orig.setAttributes(["F_l2f2", 202, 100]) l2f3orig = QgsFeature() l2f3orig.setFields(layer2.fields()) l2f3orig.setAttributes(["F_l2f3", 203, 100]) l2f4orig = QgsFeature() l2f4orig.setFields(layer2.fields()) l2f4orig.setAttributes(["F_l2f4", 204, 101]) # > check by adding features self.assertTrue(layer2.dataProvider().addFeatures([l2f1orig, l2f2orig, l2f3orig, l2f4orig])) # RELATION # - create the relationmanager relMgr = project.relationManager() # - create the relation rel = QgsRelation() rel.setId('rel1') rel.setName('childrel') rel.setReferencingLayer(layer2.id()) rel.setReferencedLayer(layer1.id()) rel.addFieldPair('foreign_key', 'pkid') rel.setStrength(QgsRelation.Composition) # > check relation self.assertTrue(rel.isValid()) # - add relation relMgr.addRelation(rel) # > check if referencedLayer is layer1 self.assertEqual(rel.referencedLayer(), layer1) # > check if referencingLayer is layer2 self.assertEqual(rel.referencingLayer(), layer2) # > check if the layers are correct in relation when loading from relationManager relations = project.relationManager().relations() relation = relations[list(relations.keys())[0]] # > check if referencedLayer is layer1 self.assertEqual(relation.referencedLayer(), layer1) # > check if referencingLayer is layer2 self.assertEqual(relation.referencingLayer(), layer2) # > check the relatedfeatures ''' # testoutput 1 print( "\nAll Features and relations") featit=layer1.getFeatures() f=QgsFeature() while featit.nextFeature(f): print( f.attributes()) childFeature = QgsFeature() relfeatit=rel.getRelatedFeatures(f) while relfeatit.nextFeature(childFeature): print( childFeature.attributes() ) print( "\n--------------------------") print( "\nFeatures on layer1") for f in layer1.getFeatures(): print( f.attributes() ) print( "\nFeatures on layer2") for f in layer2.getFeatures(): print( f.attributes() ) ''' # DUPLICATION # - duplicate feature l1f1orig with children layer1.startEditing() results = QgsVectorLayerUtils.duplicateFeature(layer1, l1f1orig, project, 0) # > check if name is name of duplicated (pk is different) result_feature = results[0] self.assertEqual(result_feature.attribute('fldtxt'), l1f1orig.attribute('fldtxt')) # > check duplicated child layer result_layer = results[1].layers()[0] self.assertEqual(result_layer, layer2) # > check duplicated child features self.assertTrue(results[1].duplicatedFeatures(result_layer)) ''' # testoutput 2 print( "\nFeatures on layer1 (after duplication)") for f in layer1.getFeatures(): print( f.attributes() ) print( "\nFeatures on layer2 (after duplication)") for f in layer2.getFeatures(): print( f.attributes() ) print( "\nAll Features and relations") featit=layer1.getFeatures() f=QgsFeature() while featit.nextFeature(f): print( f.attributes()) childFeature = QgsFeature() relfeatit=rel.getRelatedFeatures(f) while relfeatit.nextFeature(childFeature): print( childFeature.attributes() ) ''' # > compare text of parent feature self.assertEqual(result_feature.attribute('fldtxt'), l1f1orig.attribute('fldtxt')) # - create copyValueList childFeature = QgsFeature() relfeatit = rel.getRelatedFeatures(result_feature) copyValueList = [] while relfeatit.nextFeature(childFeature): copyValueList.append(childFeature.attribute('fldtxt')) # - create origValueList childFeature = QgsFeature() relfeatit = rel.getRelatedFeatures(l1f1orig) origValueList = [] while relfeatit.nextFeature(childFeature): origValueList.append(childFeature.attribute('fldtxt')) # - check if the ids are still the same self.assertEqual(copyValueList, origValueList)
def open_db(self): edb_filename = self.open_db_lineedit.text() edb_name, ext = os.path.splitext(os.path.basename(str(edb_filename))) QgsMessageLog.logMessage( "Loading edb %s" % edb_filename, 'AirviroOfflineEdb', QgsMessageLog.INFO ) self.db_uri.setDatabase(edb_filename) self.con, self.cur = connect(str(self.db_uri.database())) self.epsg = get_epsg(self.con) root = QgsProject.instance().layerTreeRoot() edb_increment = 1 while root.findGroup(edb_name) is not None: edb_name = edb_name + unicode(edb_increment) edb_increment += 1 QgsMessageLog.logMessage( "Adding edb layers in %s" % edb_name, 'AirviroOfflineEdb', QgsMessageLog.INFO ) edb_group = root.addGroup(edb_name) point_group = edb_group.addGroup('Point sources') area_group = edb_group.addGroup('Area sources') grid_group = edb_group.addGroup('Grid sources') road_group = edb_group.addGroup('Road sources') subtable_group = edb_group.addGroup('Subtables') company_group = edb_group.addGroup('Companies') facility_group = edb_group.addGroup('Facilities') emis_group = edb_group.addGroup('Emissions') point_support_group = point_group.addGroup('Support tables') area_support_group = area_group.addGroup('Support tables') grid_support_group = grid_group.addGroup('Support tables') road_support_group = road_group.addGroup('Support tables') facility_support_group = facility_group.addGroup('Support tables') company_support_group = company_group.addGroup('Support tables') unit_group = subtable_group.addGroup('Units') road_vehicle_group = subtable_group.addGroup('Road vehicles') road_vehicle_support_group = road_vehicle_group.addGroup( 'Support tables' ) roadtype_group = subtable_group.addGroup('Roadtypes') emis_func_group = subtable_group.addGroup('Emission functions') searchkey_group = subtable_group.addGroup('Searchkeys') timevar_group = subtable_group.addGroup('Time variations') subgrp_group = subtable_group.addGroup('Substance groups') self.layers = {} schema = '' geom_table_column_dict = dict(GEOMETRY_TABLES_COLUMNS) for table in TABLES: if not table_in_db(self.cur, table): iface.messageBar().pushMessage( "Warning", "Table %s not found in edb" % table, level=QgsMessageBar.WARNING, duration=3 ) continue geom_col = geom_table_column_dict.get(table, None) self.db_uri.setDataSource( schema, table, geom_col or '' ) layer_uri = self.db_uri.uri() # + "&crs=EPSG:4326" layer = QgsVectorLayer(layer_uri, table, 'spatialite') layer.setCrs(QgsCoordinateReferenceSystem( self.epsg, QgsCoordinateReferenceSystem.EpsgCrsId) ) if not layer.isValid(): raise ValueError(edb_filename) map_layer = QgsMapLayerRegistry.instance().addMapLayer( layer, False ) if 'timevar' in table: group = timevar_group elif 'emission_function' in table: group = emis_func_group elif 'searchkey' in table: group = searchkey_group elif 'unit' in table: group = unit_group elif 'subgrp' in table: group = subgrp_group elif table == 'substances': group = subtable_group elif table.endswith('_emis'): group = emis_group elif table == 'points': group = point_group elif 'point_' in table: group = point_support_group elif table == 'areas': group = area_group elif 'area_' in table: group = area_support_group elif table == 'roads': group = road_group elif table in ('road_vehicle_link', 'road_alobs'): group = road_support_group elif 'road_' in table: group = road_vehicle_group elif 'roadtype' in table: group = roadtype_group elif table == 'facilties': group = facility_group elif 'facility' in table: group = facility_support_group elif 'companies' == table: group = company_group elif 'company' in table: group = company_support_group elif 'traffic_situation' in table: group = road_vehicle_support_group group.setVisible(False) group.setExpanded(False) group.addLayer(map_layer) self.layers[table] = map_layer.id() for table in TABLES: foreign_keys = get_foreign_keys(self.con, table) referencing_layer = self.layers[table] for row in foreign_keys: referenced_layer = self.layers[row['table']] from_column = row['from'] to_column = row['to'] rel = QgsRelation() rel.setReferencingLayer(referencing_layer) rel.setReferencedLayer(referenced_layer) rel.addFieldPair(from_column, to_column) rel_name = 'fk_%s_%s-%s_%s' % ( table, from_column, row['table'], to_column ) rel.setRelationId(rel_name) rel.setRelationName( 'fk_%s_%s-%s_%s' % ( table, from_column, row['table'], to_column) ) if not rel.isValid(): raise ValueError( 'Reference %s is invalid' % rel_name ) QgsProject.instance().relationManager().addRelation(rel)
class LinkerDock(QDockWidget, Ui_linker, SettingDialog): def __init__(self, iface): # QGIS self.iface = iface self.settings = MySettings() self.linkRubber = QgsRubberBand(self.iface.mapCanvas()) self.featureHighlight = None # Relation management self.relationManager = QgsProject.instance().relationManager() self.relationManager.changed.connect(self.loadRelations) self.relation = QgsRelation() self.referencingFeature = QgsFeature() self.relationWidgetWrapper = None self.editorContext = QgsAttributeEditorContext() self.editorContext.setVectorLayerTools(self.iface.vectorLayerTools()) # GUI QDockWidget.__init__(self) self.setupUi(self) SettingDialog.__init__(self, MySettings(), False, True) self.drawButton.setChecked(self.settings.value("drawEnabled")) self.relationReferenceWidget.setAllowMapIdentification(True) self.relationReferenceWidget.setEmbedForm(False) self.mapTool = QgsMapToolIdentifyFeature(self.iface.mapCanvas()) self.mapTool.setButton(self.identifyReferencingFeatureButton) # Connect signal/slot self.relationComboBox.currentIndexChanged.connect(self.currentRelationChanged) self.mapTool.featureIdentified.connect(self.setReferencingFeature) # load relations at start self.loadRelations() def showEvent(self, QShowEvent): self.drawLink() def closeEvent(self, e): self.iface.mapCanvas().unsetMapTool(self.mapTool) self.linkRubber.reset() self.deleteHighlight() self.deleteWrapper() self.disconnectLayer() def disconnectLayer(self): if self.relation.isValid(): self.relation.referencingLayer().editingStarted.disconnect(self.relationEditableChanged) self.relation.referencingLayer().editingStopped.disconnect(self.relationEditableChanged) self.relation.referencingLayer().attributeValueChanged.disconnect(self.layerValueChangedOutside) def runForFeature(self, relationId, layer, feature): index = self.relationComboBox.findData(relationId) self.relationComboBox.setCurrentIndex(index) self.setReferencingFeature(feature) self.show() if not layer.isEditable(): self.iface.messageBar().pushMessage("Link It", "Cannot set a new related feature since %s is not editable" % layer.name(), QgsMessageBar.WARNING, 4) else: self.relationReferenceWidget.mapIdentification() @pyqtSlot(name="on_identifyReferencingFeatureButton_clicked") def activateMapTool(self): self.iface.mapCanvas().setMapTool(self.mapTool) def deactivateMapTool(self): self.iface.mapCanvas().unsetMapTool(self.mapTool) def loadRelations(self): self.deleteWrapper() self.disconnectLayer() self.relation = QgsRelation() self.referencingFeature = QgsFeature() self.relationComboBox.currentIndexChanged.disconnect(self.currentRelationChanged) self.relationComboBox.clear() for relation in self.relationManager.referencedRelations(): if relation.referencingLayer().hasGeometryType(): self.relationComboBox.addItem(relation.name(), relation.id()) self.relationComboBox.setCurrentIndex(-1) self.relationComboBox.currentIndexChanged.connect(self.currentRelationChanged) self.currentRelationChanged(-1) def currentRelationChanged(self, index): # disconnect previous relation if self.relation.isValid(): try: self.relation.referencingLayer().editingStarted.disconnect(self.relationEditableChanged) self.relation.referencingLayer().editingStopped.disconnect(self.relationEditableChanged) self.relation.referencingLayer().attributeValueChanged.disconnect(self.layerValueChangedOutside) except TypeError: pass self.referencingFeatureLayout.setEnabled(index >= 0) relationId = self.relationComboBox.itemData(index) self.relation = self.relationManager.relation(relationId) self.mapTool.setLayer(self.relation.referencingLayer()) self.setReferencingFeature() # connect if self.relation.isValid(): self.relation.referencingLayer().editingStarted.connect(self.relationEditableChanged) self.relation.referencingLayer().editingStopped.connect(self.relationEditableChanged) self.relation.referencingLayer().attributeValueChanged.connect(self.layerValueChangedOutside) def setReferencingFeature(self, feature=QgsFeature()): self.deactivateMapTool() self.referencingFeature = QgsFeature(feature) self.deleteWrapper() # disable relation reference widget if no referencing feature self.referencedFeatureLayout.setEnabled(feature.isValid()) # set line edit if not self.relation.isValid() or not feature.isValid(): self.referencingFeatureLineEdit.clear() return self.referencingFeatureLineEdit.setText("%s" % feature.id()) fieldIdx = self.referencingFieldIndex() widgetConfig = self.relation.referencingLayer().editorWidgetV2Config(fieldIdx) self.relationWidgetWrapper = QgsEditorWidgetRegistry.instance().create("RelationReference", self.relation.referencingLayer(), fieldIdx, widgetConfig, self.relationReferenceWidget, self, self.editorContext) self.relationWidgetWrapper.setEnabled(self.relation.referencingLayer().isEditable()) self.relationWidgetWrapper.setValue(feature[fieldIdx]) self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged) # override field definition to allow map identification self.relationReferenceWidget.setAllowMapIdentification(True) self.relationReferenceWidget.setEmbedForm(False) # update drawn link self.highlightReferencingFeature() self.drawLink() def deleteWrapper(self): if self.relationWidgetWrapper is not None: self.relationWidgetWrapper.valueChanged.disconnect(self.foreignKeyChanged) self.relationWidgetWrapper.setValue(None) del self.relationWidgetWrapper self.relationWidgetWrapper = None def foreignKeyChanged(self, newKey): if not self.relation.isValid() or not self.relation.referencingLayer().isEditable() or not self.referencingFeature.isValid(): self.drawLink() return if not self.relation.referencingLayer().editBuffer().changeAttributeValue(self.referencingFeature.id(), self.referencingFieldIndex(), newKey): self.iface.messageBar().pushMessage("Link It", "Cannot change attribute value.", QgsMessageBar.CRITICAL) self.drawLink() def relationEditableChanged(self): if self.relationWidgetWrapper is not None: self.relationWidgetWrapper.setEnabled(self.relation.isValid() and self.relation.referencingLayer().isEditable()) def layerValueChangedOutside(self, fid, fieldIdx, value): if not self.relation.isValid() or not self.referencingFeature.isValid() or self.relationWidgetWrapper is None: return # not the correct feature if fid != self.referencingFeature.id(): return # not the correct field if fieldIdx != self.referencingFieldIndex(): return # widget already has this value if value == self.relationWidgetWrapper.value(): return self.relationWidgetWrapper.valueChanged.disconnect(self.foreignKeyChanged) self.relationWidgetWrapper.setValue(value) self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged) def referencingFieldIndex(self): if not self.relation.isValid(): return -1 fieldName = self.relation.fieldPairs().keys()[0] fieldIdx = self.relation.referencingLayer().fieldNameIndex(fieldName) return fieldIdx @pyqtSlot(bool, name="on_drawButton_toggled") def drawLink(self): self.settings.setValue("drawEnabled", self.drawButton.isChecked()) self.linkRubber.reset() if not self.drawButton.isChecked() or not self.referencingFeature.isValid() or not self.relation.isValid(): return referencedFeature = self.relationReferenceWidget.referencedFeature() if not referencedFeature.isValid(): return p1 = self.centroid(self.relation.referencedLayer(), referencedFeature) p2 = self.centroid(self.relation.referencingLayer(), self.referencingFeature) geom = arc(p1, p2) self.linkRubber.setToGeometry(geom, None) self.linkRubber.setWidth(self.settings.value("rubberWidth")) self.linkRubber.setColor(self.settings.value("rubberColor")) self.linkRubber.setLineStyle(Qt.DashLine) def centroid(self, layer, feature): geom = feature.geometry() if geom.type() == QGis.Line: geom = geom.interpolate(geom.length()/2) else: geom = geom.centroid() return self.iface.mapCanvas().mapSettings().layerToMapCoordinates(layer, geom.asPoint()) @pyqtSlot(name="on_highlightReferencingFeatureButton_clicked") def highlightReferencingFeature(self): self.deleteHighlight() if not self.relation.isValid() or not self.referencingFeature.isValid(): return self.featureHighlight = QgsHighlight(self.iface.mapCanvas(), self.referencingFeature.geometry(), self.relation.referencingLayer()) settings = QSettings() color = QColor( settings.value("/Map/highlight/color", QGis.DEFAULT_HIGHLIGHT_COLOR.name())) alpha = int(settings.value("/Map/highlight/colorAlpha", QGis.DEFAULT_HIGHLIGHT_COLOR.alpha())) bbuffer = float(settings.value("/Map/highlight/buffer", QGis.DEFAULT_HIGHLIGHT_BUFFER_MM)) minWidth = float(settings.value("/Map/highlight/minWidth", QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM)) self.featureHighlight.setColor(color) color.setAlpha(alpha) self.featureHighlight.setFillColor(color) self.featureHighlight.setBuffer(bbuffer) self.featureHighlight.setMinWidth(minWidth) self.featureHighlight.show() timer = QTimer(self) timer.setSingleShot(True) timer.timeout.connect(self.deleteHighlight) timer.start(3000) def deleteHighlight(self): if self.featureHighlight: del self.featureHighlight self.featureHighlight = None
def test_representValue(self): first_layer = QgsVectorLayer("none?field=foreign_key:integer", "first_layer", "memory") self.assertTrue(first_layer.isValid()) second_layer = QgsVectorLayer( "none?field=pkid:integer&field=decoded:string", "second_layer", "memory") self.assertTrue(second_layer.isValid()) QgsProject.instance().addMapLayers([first_layer, second_layer]) f = QgsFeature() f.setAttributes([123]) first_layer.dataProvider().addFeatures([f]) f = QgsFeature() f.setAttributes([123, 'decoded_val']) second_layer.dataProvider().addFeatures([f]) relMgr = QgsProject.instance().relationManager() fieldFormatter = QgsRelationReferenceFieldFormatter() rel = QgsRelation() rel.setId('rel1') rel.setName('Relation Number One') rel.setReferencingLayer(first_layer.id()) rel.setReferencedLayer(second_layer.id()) rel.addFieldPair('foreign_key', 'pkid') self.assertTrue(rel.isValid()) relMgr.addRelation(rel) # Everything valid config = {'Relation': rel.id()} second_layer.setDisplayExpression('decoded') self.assertEqual( fieldFormatter.representValue(first_layer, 0, config, None, '123'), 'decoded_val') # Code not find match in foreign layer config = {'Relation': rel.id()} second_layer.setDisplayExpression('decoded') self.assertEqual( fieldFormatter.representValue(first_layer, 0, config, None, '456'), '456') # Invalid relation id config = {'Relation': 'invalid'} second_layer.setDisplayExpression('decoded') self.assertEqual( fieldFormatter.representValue(first_layer, 0, config, None, '123'), '123') # No display expression config = {'Relation': rel.id()} second_layer.setDisplayExpression(None) self.assertEqual( fieldFormatter.representValue(first_layer, 0, config, None, '123'), '123') # Invalid display expression config = {'Relation': rel.id()} second_layer.setDisplayExpression('invalid +') self.assertEqual( fieldFormatter.representValue(first_layer, 0, config, None, '123'), '123') # Missing relation config = {} second_layer.setDisplayExpression('decoded') self.assertEqual( fieldFormatter.representValue(first_layer, 0, config, None, '123'), '123') # Inconsistent layer provided to representValue() config = {'Relation': rel.id()} second_layer.setDisplayExpression('decoded') self.assertEqual( fieldFormatter.representValue(second_layer, 0, config, None, '123'), '123') # Inconsistent idx provided to representValue() config = {'Relation': rel.id()} second_layer.setDisplayExpression('decoded') self.assertEqual( fieldFormatter.representValue(first_layer, 1, config, None, '123'), '123') # Invalid relation rel = QgsRelation() rel.setId('rel2') rel.setName('Relation Number Two') rel.setReferencingLayer(first_layer.id()) rel.addFieldPair('foreign_key', 'pkid') self.assertFalse(rel.isValid()) relMgr.addRelation(rel) config = {'Relation': rel.id()} second_layer.setDisplayExpression('decoded') self.assertEqual( fieldFormatter.representValue(first_layer, 0, config, None, '123'), '123') QgsProject.instance().removeAllMapLayers()
class TestQgsRelationEditWidget(unittest.TestCase): @classmethod def setUpClass(cls): """ Setup the involved layers and relations for a n:m relation :return: """ cls.mapCanvas = QgsMapCanvas() QgsGui.editorWidgetRegistry().initEditors(cls.mapCanvas) cls.dbconn = 'service=\'qgis_test\'' if 'QGIS_PGTEST_DB' in os.environ: cls.dbconn = os.environ['QGIS_PGTEST_DB'] # Create test layer cls.vl_books = QgsVectorLayer( cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."books" sql=', 'books', 'postgres') cls.vl_authors = QgsVectorLayer( cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."authors" sql=', 'authors', 'postgres') cls.vl_editors = QgsVectorLayer( cls.dbconn + ' sslmode=disable key=\'fk_book,fk_author\' table="qgis_test"."editors" sql=', 'editors', 'postgres') cls.vl_link_books_authors = QgsVectorLayer( cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."books_authors" sql=', 'books_authors', 'postgres') QgsProject.instance().addMapLayer(cls.vl_books) QgsProject.instance().addMapLayer(cls.vl_authors) QgsProject.instance().addMapLayer(cls.vl_editors) QgsProject.instance().addMapLayer(cls.vl_link_books_authors) cls.relMgr = QgsProject.instance().relationManager() # Our mock QgsVectorLayerTools, that allow injecting data where user input is expected cls.vltools = VlTools() cls.layers = {cls.vl_authors, cls.vl_books, cls.vl_link_books_authors} assert (cls.vl_authors.isValid()) assert (cls.vl_books.isValid()) assert (cls.vl_editors.isValid()) assert (cls.vl_link_books_authors.isValid()) @classmethod def tearDownClass(cls): QgsProject.instance().removeAllMapLayers() cls.vl_books = None cls.vl_authors = None cls.vl_editors = None cls.vl_link_books_authors = None cls.layers = None cls.mapCanvas = None cls.vltools = None cls.relMgr = None def setUp(self): self.rel_a = QgsRelation() self.rel_a.setReferencingLayer(self.vl_link_books_authors.id()) self.rel_a.setReferencedLayer(self.vl_authors.id()) self.rel_a.addFieldPair('fk_author', 'pk') self.rel_a.setId('rel_a') assert (self.rel_a.isValid()) self.relMgr.addRelation(self.rel_a) self.rel_b = QgsRelation() self.rel_b.setReferencingLayer(self.vl_link_books_authors.id()) self.rel_b.setReferencedLayer(self.vl_books.id()) self.rel_b.addFieldPair('fk_book', 'pk') self.rel_b.setId('rel_b') assert (self.rel_b.isValid()) self.relMgr.addRelation(self.rel_b) self.startTransaction() def tearDown(self): self.rollbackTransaction() del self.transaction self.relMgr.clear() def startTransaction(self): """ Start a new transaction and set all layers into transaction mode. :return: None """ self.transaction = QgsTransaction.create(self.layers) self.transaction.begin() for layer in self.layers: layer.startEditing() def rollbackTransaction(self): """ Rollback all changes done in this transaction. We always rollback and never commit to have the database in a pristine state at the end of each test. :return: None """ for layer in self.layers: layer.commitChanges() self.transaction.rollback() def test_delete_feature(self): """ Check if a feature can be deleted properly """ self.createWrapper(self.vl_authors, '"name"=\'Erich Gamma\'') self.assertEqual(self.table_view.model().rowCount(), 1) self.assertEqual(1, len([f for f in self.vl_books.getFeatures()])) fid = next( self.vl_books.getFeatures(QgsFeatureRequest().setFilterExpression( '"name"=\'Design Patterns. Elements of Reusable Object-Oriented Software\'' ))).id() self.widget.featureSelectionManager().select([fid]) btn = self.widget.findChild(QToolButton, 'mDeleteFeatureButton') def clickOk(): # Click the "Delete features" button on the confirmation message # box widget = self.widget.findChild(QMessageBox) buttonBox = widget.findChild(QDialogButtonBox) deleteButton = next( (b for b in buttonBox.buttons() if buttonBox.buttonRole(b) == QDialogButtonBox.AcceptRole)) deleteButton.click() QTimer.singleShot(1, clickOk) btn.click() # This is the important check that the feature is deleted self.assertEqual(0, len([f for f in self.vl_books.getFeatures()])) # This is actually more checking that the database on delete action is properly set on the relation self.assertEqual( 0, len([f for f in self.vl_link_books_authors.getFeatures()])) self.assertEqual(self.table_view.model().rowCount(), 0) def test_list(self): """ Simple check if several related items are shown """ wrapper = self.createWrapper(self.vl_books) # NOQA self.assertEqual(self.table_view.model().rowCount(), 4) def test_add_feature(self): """ Check if a new related feature is added """ self.createWrapper(self.vl_authors, '"name"=\'Douglas Adams\'') self.assertEqual(self.table_view.model().rowCount(), 0) self.vltools.setValues([ None, 'The Hitchhiker\'s Guide to the Galaxy', 'Sputnik Editions', 1961 ]) btn = self.widget.findChild(QToolButton, 'mAddFeatureButton') btn.click() # Book entry has been created self.assertEqual(2, len([f for f in self.vl_books.getFeatures()])) # Link entry has been created self.assertEqual( 5, len([f for f in self.vl_link_books_authors.getFeatures()])) self.assertEqual(self.table_view.model().rowCount(), 1) def test_link_feature(self): """ Check if an existing feature can be linked """ wrapper = self.createWrapper(self.vl_authors, '"name"=\'Douglas Adams\'') # NOQA f = QgsFeature(self.vl_books.fields()) f.setAttributes([ self.vl_books.dataProvider().defaultValueClause(0), 'The Hitchhiker\'s Guide to the Galaxy', 'Sputnik Editions', 1961 ]) self.vl_books.addFeature(f) btn = self.widget.findChild(QToolButton, 'mLinkFeatureButton') btn.click() dlg = self.widget.findChild(QDialog) dlg.setSelectedFeatures([f.id()]) dlg.accept() # magically the above code selects the feature here... link_feature = next( self.vl_link_books_authors.getFeatures( QgsFeatureRequest().setFilterExpression('"fk_book"={}'.format( f[0])))) self.assertIsNotNone(link_feature[0]) self.assertEqual(self.table_view.model().rowCount(), 1) def test_unlink_feature(self): """ Check if a linked feature can be unlinked """ wrapper = self.createWrapper(self.vl_books) # NOQA # All authors are listed self.assertEqual(self.table_view.model().rowCount(), 4) it = self.vl_authors.getFeatures( QgsFeatureRequest().setFilterExpression( '"name" IN (\'Richard Helm\', \'Ralph Johnson\')')) self.widget.featureSelectionManager().select([f.id() for f in it]) self.assertEqual( 2, self.widget.featureSelectionManager().selectedFeatureCount()) btn = self.widget.findChild(QToolButton, 'mUnlinkFeatureButton') btn.click() # This is actually more checking that the database on delete action is properly set on the relation self.assertEqual( 2, len([f for f in self.vl_link_books_authors.getFeatures()])) self.assertEqual(2, self.table_view.model().rowCount()) def test_discover_relations(self): """ Test the automatic discovery of relations """ relations = self.relMgr.discoverRelations( [], [self.vl_authors, self.vl_books, self.vl_link_books_authors]) relations = {r.name(): r for r in relations} self.assertEqual( {'books_authors_fk_book_fkey', 'books_authors_fk_author_fkey'}, set(relations.keys())) ba2b = relations['books_authors_fk_book_fkey'] self.assertTrue(ba2b.isValid()) self.assertEqual('books_authors', ba2b.referencingLayer().name()) self.assertEqual('books', ba2b.referencedLayer().name()) self.assertEqual([0], ba2b.referencingFields()) self.assertEqual([0], ba2b.referencedFields()) ba2a = relations['books_authors_fk_author_fkey'] self.assertTrue(ba2a.isValid()) self.assertEqual('books_authors', ba2a.referencingLayer().name()) self.assertEqual('authors', ba2a.referencedLayer().name()) self.assertEqual([1], ba2a.referencingFields()) self.assertEqual([0], ba2a.referencedFields()) self.assertEqual( [], self.relMgr.discoverRelations( [self.rel_a, self.rel_b], [self.vl_authors, self.vl_books, self.vl_link_books_authors])) self.assertEqual( 1, len( self.relMgr.discoverRelations( [], [self.vl_authors, self.vl_link_books_authors]))) # composite keys relation relations = self.relMgr.discoverRelations( [], [self.vl_books, self.vl_editors]) self.assertEqual(len(relations), 1) relation = relations[0] self.assertEqual('books_fk_editor_fkey', relation.name()) self.assertTrue(relation.isValid()) self.assertEqual('books', relation.referencingLayer().name()) self.assertEqual('editors', relation.referencedLayer().name()) self.assertEqual([2, 3], relation.referencingFields()) self.assertEqual([0, 1], relation.referencedFields()) def test_selection(self): fbook = QgsFeature(self.vl_books.fields()) fbook.setAttributes([ self.vl_books.dataProvider().defaultValueClause(0), 'The Hitchhiker\'s Guide to the Galaxy', 'Sputnik Editions', 1961 ]) self.vl_books.addFeature(fbook) flink = QgsFeature(self.vl_link_books_authors.fields()) flink.setAttributes([fbook.id(), 5]) self.vl_link_books_authors.addFeature(flink) self.createWrapper(self.vl_authors, '"name"=\'Douglas Adams\'') self.zoomToButton = self.widget.findChild(QToolButton, "mDeleteFeatureButton") self.assertTrue(self.zoomToButton) self.assertTrue(not self.zoomToButton.isEnabled()) selectionMgr = self.widget.featureSelectionManager() self.assertTrue(selectionMgr) self.vl_books.select(fbook.id()) self.assertEqual([fbook.id()], selectionMgr.selectedFeatureIds()) self.assertTrue(self.zoomToButton.isEnabled()) selectionMgr.deselect([fbook.id()]) self.assertEqual([], selectionMgr.selectedFeatureIds()) self.assertTrue(not self.zoomToButton.isEnabled()) self.vl_books.select([1, fbook.id()]) self.assertEqual([fbook.id()], selectionMgr.selectedFeatureIds()) self.assertTrue(self.zoomToButton.isEnabled()) def test_add_feature_geometry(self): """ Test to add a feature with a geometry """ vl_pipes = QgsVectorLayer( self.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."pipes" (geom) sql=', 'pipes', 'postgres') vl_leaks = QgsVectorLayer( self.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."leaks" (geom) sql=', 'leaks', 'postgres') vl_leaks.startEditing() QgsProject.instance().addMapLayer(vl_pipes) QgsProject.instance().addMapLayer(vl_leaks) self.assertEqual(vl_pipes.featureCount(), 2) self.assertEqual(vl_leaks.featureCount(), 3) rel = QgsRelation() rel.setReferencingLayer(vl_leaks.id()) rel.setReferencedLayer(vl_pipes.id()) rel.addFieldPair('pipe', 'id') rel.setId('rel_pipe_leak') self.assertTrue(rel.isValid()) self.relMgr.addRelation(rel) # Mock vector layer tool to just set default value on created feature class DummyVlTools(QgsVectorLayerTools): def addFeature(self, layer, defaultValues, defaultGeometry, parentWidget=None, showModal=True, hideParent=False): f = QgsFeature(layer.fields()) for idx, value in defaultValues.items(): f.setAttribute(idx, value) f.setGeometry(defaultGeometry) ok = layer.addFeature(f) return ok, f wrapper = QgsRelationWidgetWrapper(vl_leaks, rel) context = QgsAttributeEditorContext() vltool = DummyVlTools() context.setVectorLayerTools(vltool) context.setMapCanvas(self.mapCanvas) cadDockWidget = QgsAdvancedDigitizingDockWidget(self.mapCanvas) context.setCadDockWidget(cadDockWidget) wrapper.setContext(context) widget = wrapper.widget() widget.show() pipe = next(vl_pipes.getFeatures()) self.assertEqual(pipe.id(), 1) wrapper.setFeature(pipe) table_view = widget.findChild(QTableView) self.assertEqual(table_view.model().rowCount(), 1) btn = widget.findChild(QToolButton, 'mAddFeatureGeometryButton') self.assertTrue(btn.isVisible()) self.assertTrue(btn.isEnabled()) btn.click() self.assertTrue(self.mapCanvas.mapTool()) feature = QgsFeature(vl_leaks.fields()) feature.setGeometry(QgsGeometry.fromWkt('POINT(0 0.8)')) self.mapCanvas.mapTool().digitizingCompleted.emit(feature) self.assertEqual(table_view.model().rowCount(), 2) self.assertEqual(vl_leaks.featureCount(), 4) request = QgsFeatureRequest() request.addOrderBy("id", False) # get new created feature feat = next(vl_leaks.getFeatures('"id" is NULL')) self.assertTrue(feat.isValid()) self.assertTrue(feat.geometry().equals( QgsGeometry.fromWkt('POINT(0 0.8)'))) vl_leaks.rollBack() def createWrapper(self, layer, filter=None): """ Basic setup of a relation widget wrapper. Will create a new wrapper and set its feature to the one and only book in the table. It will also assign some instance variables to help * self.widget The created widget * self.table_view The table view of the widget :return: The created wrapper """ if layer == self.vl_books: relation = self.rel_b nmrel = self.rel_a else: relation = self.rel_a nmrel = self.rel_b self.wrapper = QgsRelationWidgetWrapper(layer, relation) self.wrapper.setConfig({'nm-rel': nmrel.id()}) context = QgsAttributeEditorContext() context.setMapCanvas(self.mapCanvas) context.setVectorLayerTools(self.vltools) self.wrapper.setContext(context) self.widget = self.wrapper.widget() self.widget.show() request = QgsFeatureRequest() if filter: request.setFilterExpression(filter) book = next(layer.getFeatures(request)) self.wrapper.setFeature(book) self.table_view = self.widget.findChild(QTableView) return self.wrapper
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 # 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 l = _qgis_layer(gmlas_uri, schema, lyr["layer_name"], g_column, provider, ln, lyr["xpath"], lyr["uid"]) if not l.isValid(): raise RuntimeError("Problem loading layer {} with {}".format(ln, l.source())) if g_column is not None: 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 # save fields which represent a xlink:href if ln in href_fields: l.setCustomProperty("href_fields", href_fields[ln]) # save gmlas_uri l.setCustomProperty("ogr_uri", gmlas_uri) l.setCustomProperty("ogr_schema", schema) # change icon the layer has a custom viewer xpath = no_ns(l.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()) l.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) 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: 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: 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) install_viewer_on_feature_form(l)
class LinkerDock(QDockWidget, Ui_linker, SettingDialog): def __init__(self, iface): # QGIS self.iface = iface self.settings = MySettings() self.linkRubber = QgsRubberBand(self.iface.mapCanvas()) self.featureHighlight = None # Relation management self.relationManager = QgsProject.instance().relationManager() self.relationManager.changed.connect(self.loadRelations) self.relation = QgsRelation() self.referencingFeature = QgsFeature() self.relationWidgetWrapper = None self.editorContext = QgsAttributeEditorContext() self.editorContext.setVectorLayerTools(self.iface.vectorLayerTools()) # GUI QDockWidget.__init__(self) self.setupUi(self) SettingDialog.__init__(self, MySettings(), False, True) self.drawButton.setChecked(self.settings.value("drawEnabled")) self.relationReferenceWidget.setAllowMapIdentification(True) self.relationReferenceWidget.setEmbedForm(False) self.mapTool = QgsMapToolIdentifyFeature(self.iface.mapCanvas()) self.mapTool.setButton(self.identifyReferencingFeatureButton) # Connect signal/slot self.relationComboBox.currentIndexChanged.connect( self.currentRelationChanged) self.mapTool.featureIdentified.connect(self.setReferencingFeature) # load relations at start self.loadRelations() def showEvent(self, QShowEvent): self.drawLink() def closeEvent(self, e): self.iface.mapCanvas().unsetMapTool(self.mapTool) self.linkRubber.reset() self.deleteHighlight() self.deleteWrapper() self.disconnectLayer() def disconnectLayer(self): if self.relation.isValid(): self.relation.referencingLayer().editingStarted.disconnect( self.relationEditableChanged) self.relation.referencingLayer().editingStopped.disconnect( self.relationEditableChanged) self.relation.referencingLayer().attributeValueChanged.disconnect( self.layerValueChangedOutside) def runForFeature(self, relationId, layer, feature): index = self.relationComboBox.findData(relationId) self.relationComboBox.setCurrentIndex(index) self.setReferencingFeature(feature) self.show() if not layer.isEditable(): self.iface.messageBar().pushMessage( "Link It", "Cannot set a new related feature since %s is not editable" % layer.name(), QgsMessageBar.WARNING, 4) else: self.relationReferenceWidget.mapIdentification() @pyqtSlot(name="on_identifyReferencingFeatureButton_clicked") def activateMapTool(self): self.iface.mapCanvas().setMapTool(self.mapTool) def deactivateMapTool(self): self.iface.mapCanvas().unsetMapTool(self.mapTool) def loadRelations(self): self.deleteWrapper() self.disconnectLayer() self.relation = QgsRelation() self.referencingFeature = QgsFeature() self.relationComboBox.currentIndexChanged.disconnect( self.currentRelationChanged) self.relationComboBox.clear() for relation in self.relationManager.referencedRelations(): if relation.referencingLayer().hasGeometryType(): self.relationComboBox.addItem(relation.name(), relation.id()) self.relationComboBox.setCurrentIndex(-1) self.relationComboBox.currentIndexChanged.connect( self.currentRelationChanged) self.currentRelationChanged(-1) def currentRelationChanged(self, index): # disconnect previous relation if self.relation.isValid(): try: self.relation.referencingLayer().editingStarted.disconnect( self.relationEditableChanged) self.relation.referencingLayer().editingStopped.disconnect( self.relationEditableChanged) self.relation.referencingLayer( ).attributeValueChanged.disconnect( self.layerValueChangedOutside) except TypeError: pass self.referencingFeatureLayout.setEnabled(index >= 0) relationId = self.relationComboBox.itemData(index) self.relation = self.relationManager.relation(relationId) self.mapTool.setLayer(self.relation.referencingLayer()) self.setReferencingFeature() # connect if self.relation.isValid(): self.relation.referencingLayer().editingStarted.connect( self.relationEditableChanged) self.relation.referencingLayer().editingStopped.connect( self.relationEditableChanged) self.relation.referencingLayer().attributeValueChanged.connect( self.layerValueChangedOutside) def setReferencingFeature(self, feature=QgsFeature()): self.deactivateMapTool() self.referencingFeature = QgsFeature(feature) self.deleteWrapper() # disable relation reference widget if no referencing feature self.referencedFeatureLayout.setEnabled(feature.isValid()) # set line edit if not self.relation.isValid() or not feature.isValid(): self.referencingFeatureLineEdit.clear() return self.referencingFeatureLineEdit.setText("%s" % feature.id()) fieldIdx = self.referencingFieldIndex() widgetConfig = self.relation.referencingLayer().editorWidgetV2Config( fieldIdx) self.relationWidgetWrapper = QgsEditorWidgetRegistry.instance().create( "RelationReference", self.relation.referencingLayer(), fieldIdx, widgetConfig, self.relationReferenceWidget, self, self.editorContext) self.relationWidgetWrapper.setEnabled( self.relation.referencingLayer().isEditable()) self.relationWidgetWrapper.setValue(feature[fieldIdx]) self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged) # override field definition to allow map identification self.relationReferenceWidget.setAllowMapIdentification(True) self.relationReferenceWidget.setEmbedForm(False) # update drawn link self.highlightReferencingFeature() self.drawLink() def deleteWrapper(self): if self.relationWidgetWrapper is not None: self.relationWidgetWrapper.valueChanged.disconnect( self.foreignKeyChanged) self.relationWidgetWrapper.setValue(None) del self.relationWidgetWrapper self.relationWidgetWrapper = None def foreignKeyChanged(self, newKey): if not self.relation.isValid() or not self.relation.referencingLayer( ).isEditable() or not self.referencingFeature.isValid(): self.drawLink() return if not self.relation.referencingLayer().editBuffer( ).changeAttributeValue(self.referencingFeature.id(), self.referencingFieldIndex(), newKey): self.iface.messageBar().pushMessage( "Link It", "Cannot change attribute value.", QgsMessageBar.CRITICAL) self.drawLink() def relationEditableChanged(self): if self.relationWidgetWrapper is not None: self.relationWidgetWrapper.setEnabled( self.relation.isValid() and self.relation.referencingLayer().isEditable()) def layerValueChangedOutside(self, fid, fieldIdx, value): if not self.relation.isValid() or not self.referencingFeature.isValid( ) or self.relationWidgetWrapper is None: return # not the correct feature if fid != self.referencingFeature.id(): return # not the correct field if fieldIdx != self.referencingFieldIndex(): return # widget already has this value if value == self.relationWidgetWrapper.value(): return self.relationWidgetWrapper.valueChanged.disconnect( self.foreignKeyChanged) self.relationWidgetWrapper.setValue(value) self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged) def referencingFieldIndex(self): if not self.relation.isValid(): return -1 fieldName = self.relation.fieldPairs().keys()[0] fieldIdx = self.relation.referencingLayer().fieldNameIndex(fieldName) return fieldIdx @pyqtSlot(bool, name="on_drawButton_toggled") def drawLink(self): self.settings.setValue("drawEnabled", self.drawButton.isChecked()) self.linkRubber.reset() if not self.drawButton.isChecked( ) or not self.referencingFeature.isValid( ) or not self.relation.isValid(): return referencedFeature = self.relationReferenceWidget.referencedFeature() if not referencedFeature.isValid(): return p1 = self.centroid(self.relation.referencedLayer(), referencedFeature) p2 = self.centroid(self.relation.referencingLayer(), self.referencingFeature) geom = arc(p1, p2) self.linkRubber.setToGeometry(geom, None) self.linkRubber.setWidth(self.settings.value("rubberWidth")) self.linkRubber.setColor(self.settings.value("rubberColor")) self.linkRubber.setLineStyle(Qt.DashLine) def centroid(self, layer, feature): geom = feature.geometry() if geom.type() == QGis.Line: geom = geom.interpolate(geom.length() / 2) else: geom = geom.centroid() return self.iface.mapCanvas().mapSettings().layerToMapCoordinates( layer, geom.asPoint()) @pyqtSlot(name="on_highlightReferencingFeatureButton_clicked") def highlightReferencingFeature(self): self.deleteHighlight() if not self.relation.isValid() or not self.referencingFeature.isValid( ): return self.featureHighlight = QgsHighlight( self.iface.mapCanvas(), self.referencingFeature.geometry(), self.relation.referencingLayer()) settings = QSettings() color = QColor( settings.value("/Map/highlight/color", QGis.DEFAULT_HIGHLIGHT_COLOR.name())) alpha = int( settings.value("/Map/highlight/colorAlpha", QGis.DEFAULT_HIGHLIGHT_COLOR.alpha())) bbuffer = float( settings.value("/Map/highlight/buffer", QGis.DEFAULT_HIGHLIGHT_BUFFER_MM)) minWidth = float( settings.value("/Map/highlight/minWidth", QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM)) self.featureHighlight.setColor(color) color.setAlpha(alpha) self.featureHighlight.setFillColor(color) self.featureHighlight.setBuffer(bbuffer) self.featureHighlight.setMinWidth(minWidth) self.featureHighlight.show() timer = QTimer(self) timer.setSingleShot(True) timer.timeout.connect(self.deleteHighlight) timer.start(3000) def deleteHighlight(self): if self.featureHighlight: del self.featureHighlight self.featureHighlight = None
class DocumentModel(QAbstractTableModel): DocumentIdRole = Qt.UserRole + 1 DocumentPathRole = Qt.UserRole + 2 DocumentNameRole = Qt.UserRole + 3 DocumentExistsRole = Qt.UserRole + 4 DocumentToolTipRole = Qt.UserRole + 5 DocumentIsImageRole = Qt.UserRole + 6 def __init__(self, parent: QObject = None): super(DocumentModel, self).__init__(parent) self._relation = QgsRelation() self._nmRelation = QgsRelation() self._documents_path = str() self._document_filename = str() self._feature = QgsFeature() self._document_list = [] def init(self, relation: QgsRelation, nmRelation: QgsRelation, feature: QgsFeature, documents_path: str, document_filename: str): self._relation = relation self._nmRelation = nmRelation self._documents_path = documents_path self._document_filename = document_filename self._feature = feature self.reloadData() def rowCount(self, parent: QModelIndex = ...) -> int: return len(self._document_list) def columnCount(self, parent: QModelIndex = ...) -> int: return 1 def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...): return None def flags(self, index: QModelIndex) -> Qt.ItemFlags: flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable return flags def data(self, index: QModelIndex, role: int = ...): if index.row() < 0 or index.row() >= self.rowCount(QModelIndex()): return None return self._document_list[index.row()][role] def setData(self, index: QModelIndex, value, role: int = Qt.EditRole) -> bool: if index.row() < 0 or index.row() >= self.rowCount(QModelIndex()): return False return False def roleNames(self): return { self.DocumentIdRole: b'DocumentId', self.DocumentPathRole: b'DocumentPath', self.DocumentNameRole: b'DocumentName', self.DocumentExistsRole: b'DocumentExists', self.DocumentToolTipRole: b'DocumentToolTip', self.DocumentIsImageRole: b'DocumentIsImage' } def reloadData(self): self.beginResetModel() self._document_list = [] if self._relation.isValid() is False or self._feature.isValid( ) is False: self.endResetModel() return feature_list = [] layer = self._relation.referencingLayer() request = self._relation.getRelatedFeaturesRequest(self._feature) for feature in layer.getFeatures(request): feature_list.append(feature) if self._nmRelation.isValid(): filters = [] for joinTableFeature in feature_list: referencedFeatureRequest = self._nmRelation.getReferencedFeatureRequest( joinTableFeature) filterExpression = referencedFeatureRequest.filterExpression() filters.append("(" + filterExpression.expression() + ")") nmRequest = QgsFeatureRequest() nmRequest.setFilterExpression(" OR ".join(filters)) feature_list = [] layer = self._nmRelation.referencedLayer() for documentFeature in layer.getFeatures(nmRequest): feature_list.append(documentFeature) for documentFeature in feature_list: documents_path = str() if self._documents_path: exp = QgsExpression(self._documents_path) context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes(layer)) context.setFeature(documentFeature) documents_path = exp.evaluate(context) document_filename = str() if self._document_filename: exp = QgsExpression(self._document_filename) context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes(layer)) context.setFeature(documentFeature) document_filename = exp.evaluate(context) file_info = QFileInfo(QDir(str(documents_path)), str(document_filename)) # ToolTip toolTipList = [] toolTipList.append("<ul>") for field in documentFeature.fields(): index = documentFeature.fields().indexFromName(field.name()) toolTipList.append("<li><strong>{0}</strong>: {1}</li>".format( field.displayName(), documentFeature[index])) toolTipList.append("</ul>") self._document_list.append({ self.DocumentIdRole: documentFeature.id(), self.DocumentPathRole: file_info.filePath(), self.DocumentNameRole: file_info.fileName(), self.DocumentExistsRole: file_info.exists(), self.DocumentToolTipRole: "".join(toolTipList), self.DocumentIsImageRole: PreviewImageProvider.isMimeTypeSupported(file_info.filePath()) }) self.endResetModel()
def open_db(self): edb_filename = self.open_db_lineedit.text() edb_name, ext = os.path.splitext(os.path.basename(str(edb_filename))) QgsMessageLog.logMessage("Loading edb %s" % edb_filename, 'AirviroOfflineEdb', QgsMessageLog.INFO) self.db_uri.setDatabase(edb_filename) self.con, self.cur = connect(str(self.db_uri.database())) self.epsg = get_epsg(self.con) root = QgsProject.instance().layerTreeRoot() edb_increment = 1 while root.findGroup(edb_name) is not None: edb_name = edb_name + unicode(edb_increment) edb_increment += 1 QgsMessageLog.logMessage("Adding edb layers in %s" % edb_name, 'AirviroOfflineEdb', QgsMessageLog.INFO) edb_group = root.addGroup(edb_name) point_group = edb_group.addGroup('Point sources') area_group = edb_group.addGroup('Area sources') grid_group = edb_group.addGroup('Grid sources') road_group = edb_group.addGroup('Road sources') subtable_group = edb_group.addGroup('Subtables') company_group = edb_group.addGroup('Companies') facility_group = edb_group.addGroup('Facilities') emis_group = edb_group.addGroup('Emissions') point_support_group = point_group.addGroup('Support tables') area_support_group = area_group.addGroup('Support tables') grid_support_group = grid_group.addGroup('Support tables') road_support_group = road_group.addGroup('Support tables') facility_support_group = facility_group.addGroup('Support tables') company_support_group = company_group.addGroup('Support tables') unit_group = subtable_group.addGroup('Units') road_vehicle_group = subtable_group.addGroup('Road vehicles') road_vehicle_support_group = road_vehicle_group.addGroup( 'Support tables') roadtype_group = subtable_group.addGroup('Roadtypes') emis_func_group = subtable_group.addGroup('Emission functions') searchkey_group = subtable_group.addGroup('Searchkeys') timevar_group = subtable_group.addGroup('Time variations') subgrp_group = subtable_group.addGroup('Substance groups') self.layers = {} schema = '' geom_table_column_dict = dict(GEOMETRY_TABLES_COLUMNS) for table in TABLES: if not table_in_db(self.cur, table): iface.messageBar().pushMessage("Warning", "Table %s not found in edb" % table, level=QgsMessageBar.WARNING, duration=3) continue geom_col = geom_table_column_dict.get(table, None) self.db_uri.setDataSource(schema, table, geom_col or '') layer_uri = self.db_uri.uri() # + "&crs=EPSG:4326" layer = QgsVectorLayer(layer_uri, table, 'spatialite') layer.setCrs( QgsCoordinateReferenceSystem( self.epsg, QgsCoordinateReferenceSystem.EpsgCrsId)) if not layer.isValid(): raise ValueError(edb_filename) map_layer = QgsMapLayerRegistry.instance().addMapLayer( layer, False) if 'timevar' in table: group = timevar_group elif 'emission_function' in table: group = emis_func_group elif 'searchkey' in table: group = searchkey_group elif 'unit' in table: group = unit_group elif 'subgrp' in table: group = subgrp_group elif table == 'substances': group = subtable_group elif table.endswith('_emis'): group = emis_group elif table == 'points': group = point_group elif 'point_' in table: group = point_support_group elif table == 'areas': group = area_group elif 'area_' in table: group = area_support_group elif table == 'roads': group = road_group elif table in ('road_vehicle_link', 'road_alobs'): group = road_support_group elif 'road_' in table: group = road_vehicle_group elif 'roadtype' in table: group = roadtype_group elif table == 'facilties': group = facility_group elif 'facility' in table: group = facility_support_group elif 'companies' == table: group = company_group elif 'company' in table: group = company_support_group elif 'traffic_situation' in table: group = road_vehicle_support_group group.setVisible(False) group.setExpanded(False) group.addLayer(map_layer) self.layers[table] = map_layer.id() for table in TABLES: foreign_keys = get_foreign_keys(self.con, table) referencing_layer = self.layers[table] for row in foreign_keys: referenced_layer = self.layers[row['table']] from_column = row['from'] to_column = row['to'] rel = QgsRelation() rel.setReferencingLayer(referencing_layer) rel.setReferencedLayer(referenced_layer) rel.addFieldPair(from_column, to_column) rel_name = 'fk_%s_%s-%s_%s' % (table, from_column, row['table'], to_column) rel.setRelationId(rel_name) rel.setRelationName( 'fk_%s_%s-%s_%s' % (table, from_column, row['table'], to_column)) if not rel.isValid(): raise ValueError('Reference %s is invalid' % rel_name) QgsProject.instance().relationManager().addRelation(rel)
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)
def test_add_feature_geometry(self): """ Test to add a feature with a geometry """ vl_pipes = QgsVectorLayer( self.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."pipes" (geom) sql=', 'pipes', 'postgres') vl_leaks = QgsVectorLayer( self.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."leaks" (geom) sql=', 'leaks', 'postgres') vl_leaks.startEditing() QgsProject.instance().addMapLayer(vl_pipes) QgsProject.instance().addMapLayer(vl_leaks) self.assertEqual(vl_pipes.featureCount(), 2) self.assertEqual(vl_leaks.featureCount(), 3) rel = QgsRelation() rel.setReferencingLayer(vl_leaks.id()) rel.setReferencedLayer(vl_pipes.id()) rel.addFieldPair('pipe', 'id') rel.setId('rel_pipe_leak') self.assertTrue(rel.isValid()) self.relMgr.addRelation(rel) # Mock vector layer tool to just set default value on created feature class DummyVlTools(QgsVectorLayerTools): def addFeature(self, layer, defaultValues, defaultGeometry, parentWidget=None, showModal=True, hideParent=False): f = QgsFeature(layer.fields()) for idx, value in defaultValues.items(): f.setAttribute(idx, value) f.setGeometry(defaultGeometry) ok = layer.addFeature(f) return ok, f wrapper = QgsRelationWidgetWrapper(vl_leaks, rel) context = QgsAttributeEditorContext() vltool = DummyVlTools() context.setVectorLayerTools(vltool) context.setMapCanvas(self.mapCanvas) cadDockWidget = QgsAdvancedDigitizingDockWidget(self.mapCanvas) context.setCadDockWidget(cadDockWidget) wrapper.setContext(context) widget = wrapper.widget() widget.show() pipe = next(vl_pipes.getFeatures()) self.assertEqual(pipe.id(), 1) wrapper.setFeature(pipe) table_view = widget.findChild(QTableView) self.assertEqual(table_view.model().rowCount(), 1) btn = widget.findChild(QToolButton, 'mAddFeatureGeometryButton') self.assertTrue(btn.isVisible()) self.assertTrue(btn.isEnabled()) btn.click() self.assertTrue(self.mapCanvas.mapTool()) feature = QgsFeature(vl_leaks.fields()) feature.setGeometry(QgsGeometry.fromWkt('POINT(0 0.8)')) self.mapCanvas.mapTool().digitizingCompleted.emit(feature) self.assertEqual(table_view.model().rowCount(), 2) self.assertEqual(vl_leaks.featureCount(), 4) request = QgsFeatureRequest() request.addOrderBy("id", False) # get new created feature feat = next(vl_leaks.getFeatures('"id" is NULL')) self.assertTrue(feat.isValid()) self.assertTrue(feat.geometry().equals( QgsGeometry.fromWkt('POINT(0 0.8)'))) vl_leaks.rollBack()
def test_RelationReference_representValue(self): first_layer = QgsVectorLayer("none?field=foreign_key:integer", "first_layer", "memory") assert first_layer.isValid() second_layer = QgsVectorLayer("none?field=pkid:integer&field=decoded:string", "second_layer", "memory") assert second_layer.isValid() QgsMapLayerRegistry.instance().addMapLayers([first_layer, second_layer]) f = QgsFeature() f.setAttributes([123]) assert first_layer.dataProvider().addFeatures([f]) f = QgsFeature() f.setAttributes([123, 'decoded_val']) assert second_layer.dataProvider().addFeatures([f]) relMgr = QgsProject.instance().relationManager() reg = QgsEditorWidgetRegistry.instance() factory = reg.factory("RelationReference") self.assertIsNotNone(factory) rel = QgsRelation() rel.setRelationId('rel1') rel.setRelationName('Relation Number One') rel.setReferencingLayer(first_layer.id()) rel.setReferencedLayer(second_layer.id()) rel.addFieldPair('foreign_key', 'pkid') assert(rel.isValid()) relMgr.addRelation(rel) # Everything valid config = {'Relation': rel.id()} second_layer.setDisplayExpression('decoded') self.assertEqual(factory.representValue(first_layer, 0, config, None, '123'), 'decoded_val') # Code not find match in foreign layer config = {'Relation': rel.id()} second_layer.setDisplayExpression('decoded') self.assertEqual(factory.representValue(first_layer, 0, config, None, '456'), '456') # Invalid relation id config = {'Relation': 'invalid'} second_layer.setDisplayExpression('decoded') self.assertEqual(factory.representValue(first_layer, 0, config, None, '123'), '123') # No display expression config = {'Relation': rel.id()} second_layer.setDisplayExpression(None) self.assertEqual(factory.representValue(first_layer, 0, config, None, '123'), '123') # Invalid display expression config = {'Relation': rel.id()} second_layer.setDisplayExpression('invalid +') self.assertEqual(factory.representValue(first_layer, 0, config, None, '123'), '123') # Missing relation config = {} second_layer.setDisplayExpression('decoded') self.assertEqual(factory.representValue(first_layer, 0, config, None, '123'), '123') # Inconsistent layer provided to representValue() config = {'Relation': rel.id()} second_layer.setDisplayExpression('decoded') self.assertEqual(factory.representValue(second_layer, 0, config, None, '123'), '123') # Inconsistent idx provided to representValue() config = {'Relation': rel.id()} second_layer.setDisplayExpression('decoded') self.assertEqual(factory.representValue(first_layer, 1, config, None, '123'), '123') # Invalid relation rel = QgsRelation() rel.setRelationId('rel2') rel.setRelationName('Relation Number Two') rel.setReferencingLayer(first_layer.id()) rel.addFieldPair('foreign_key', 'pkid') self.assertFalse(rel.isValid()) relMgr.addRelation(rel) config = {'Relation': rel.id()} second_layer.setDisplayExpression('decoded') self.assertEqual(factory.representValue(first_layer, 0, config, None, '123'), '123') QgsMapLayerRegistry.instance().removeAllMapLayers()
def testDuplicateFeature(self): """ test duplicating a feature """ project = QgsProject().instance() # LAYERS # - add first layer (parent) layer1 = QgsVectorLayer("Point?field=fldtxt:string&field=pkid:integer", "parentlayer", "memory") # > check first layer (parent) self.assertTrue(layer1.isValid()) # - set the value for the copy layer1.setDefaultValueDefinition(1, QgsDefaultValue("rand(1000,2000)")) # > check first layer (parent) self.assertTrue(layer1.isValid()) # - add second layer (child) layer2 = QgsVectorLayer( "Point?field=fldtxt:string&field=id:integer&field=foreign_key:integer", "childlayer", "memory") # > check second layer (child) self.assertTrue(layer2.isValid()) # - add layers project.addMapLayers([layer1, layer2]) # FEATURES # - add 2 features on layer1 (parent) l1f1orig = QgsFeature() l1f1orig.setFields(layer1.fields()) l1f1orig.setAttributes(["F_l1f1", 100]) l1f2orig = QgsFeature() l1f2orig.setFields(layer1.fields()) l1f2orig.setAttributes(["F_l1f2", 101]) # > check by adding features self.assertTrue(layer1.dataProvider().addFeatures([l1f1orig, l1f2orig])) # add 4 features on layer2 (child) l2f1orig = QgsFeature() l2f1orig.setFields(layer2.fields()) l2f1orig.setAttributes(["F_l2f1", 201, 100]) l2f2orig = QgsFeature() l2f2orig.setFields(layer2.fields()) l2f2orig.setAttributes(["F_l2f2", 202, 100]) l2f3orig = QgsFeature() l2f3orig.setFields(layer2.fields()) l2f3orig.setAttributes(["F_l2f3", 203, 100]) l2f4orig = QgsFeature() l2f4orig.setFields(layer2.fields()) l2f4orig.setAttributes(["F_l2f4", 204, 101]) # > check by adding features self.assertTrue(layer2.dataProvider().addFeatures( [l2f1orig, l2f2orig, l2f3orig, l2f4orig])) # RELATION # - create the relationmanager relMgr = project.relationManager() # - create the relation rel = QgsRelation() rel.setId('rel1') rel.setName('childrel') rel.setReferencingLayer(layer2.id()) rel.setReferencedLayer(layer1.id()) rel.addFieldPair('foreign_key', 'pkid') rel.setStrength(QgsRelation.Composition) # > check relation self.assertTrue(rel.isValid()) # - add relation relMgr.addRelation(rel) # > check if referencedLayer is layer1 self.assertEqual(rel.referencedLayer(), layer1) # > check if referencingLayer is layer2 self.assertEqual(rel.referencingLayer(), layer2) # > check if the layers are correct in relation when loading from relationManager relations = project.relationManager().relations() relation = relations[list(relations.keys())[0]] # > check if referencedLayer is layer1 self.assertEqual(relation.referencedLayer(), layer1) # > check if referencingLayer is layer2 self.assertEqual(relation.referencingLayer(), layer2) # > check the relatedfeatures ''' # testoutput 1 print( "\nAll Features and relations") featit=layer1.getFeatures() f=QgsFeature() while featit.nextFeature(f): print( f.attributes()) childFeature = QgsFeature() relfeatit=rel.getRelatedFeatures(f) while relfeatit.nextFeature(childFeature): print( childFeature.attributes() ) print( "\n--------------------------") print( "\nFeatures on layer1") for f in layer1.getFeatures(): print( f.attributes() ) print( "\nFeatures on layer2") for f in layer2.getFeatures(): print( f.attributes() ) ''' # DUPLICATION # - duplicate feature l1f1orig with children layer1.startEditing() results = QgsVectorLayerUtils.duplicateFeature(layer1, l1f1orig, project, 0) # > check if name is name of duplicated (pk is different) result_feature = results[0] self.assertEqual(result_feature.attribute('fldtxt'), l1f1orig.attribute('fldtxt')) # > check duplicated child layer result_layer = results[1].layers()[0] self.assertEqual(result_layer, layer2) # > check duplicated child features self.assertTrue(results[1].duplicatedFeatures(result_layer)) ''' # testoutput 2 print( "\nFeatures on layer1 (after duplication)") for f in layer1.getFeatures(): print( f.attributes() ) print( "\nFeatures on layer2 (after duplication)") for f in layer2.getFeatures(): print( f.attributes() ) print( "\nAll Features and relations") featit=layer1.getFeatures() f=QgsFeature() while featit.nextFeature(f): print( f.attributes()) childFeature = QgsFeature() relfeatit=rel.getRelatedFeatures(f) while relfeatit.nextFeature(childFeature): print( childFeature.attributes() ) ''' # > compare text of parent feature self.assertEqual(result_feature.attribute('fldtxt'), l1f1orig.attribute('fldtxt')) # - create copyValueList childFeature = QgsFeature() relfeatit = rel.getRelatedFeatures(result_feature) copyValueList = [] while relfeatit.nextFeature(childFeature): copyValueList.append(childFeature.attribute('fldtxt')) # - create origValueList childFeature = QgsFeature() relfeatit = rel.getRelatedFeatures(l1f1orig) origValueList = [] while relfeatit.nextFeature(childFeature): origValueList.append(childFeature.attribute('fldtxt')) # - check if the ids are still the same self.assertEqual(copyValueList, origValueList)
def setUpClass(cls): """Run before all tests""" QCoreApplication.setOrganizationName("QGIS_Test") QCoreApplication.setOrganizationDomain( "QGIS_TestPyQgsPackageLayers.com") QCoreApplication.setApplicationName("QGIS_TestPyQgsPackageLayers") QgsSettings().clear() Processing.initialize() QgsApplication.processingRegistry().addProvider(QgsNativeAlgorithms()) cls.registry = QgsApplication.instance().processingRegistry() cls.tmp_dir = QTemporaryDir() cls.temp_path = os.path.join(cls.tmp_dir.path(), 'package_layers.gpkg') cls.temp_export_path = os.path.join(cls.tmp_dir.path(), 'package_layers_export.gpkg') # Create test DB """ Test data: Region 1 Province 1 City 1 City 2 Province 2 City 3 Region 2 Province 3 Province 4 City 4 """ ds = ogr.GetDriverByName('GPKG').CreateDataSource(cls.temp_path) lyr = ds.CreateLayer('region', geom_type=ogr.wkbNone) lyr.CreateField(ogr.FieldDefn('name', ogr.OFTString)) f = ogr.Feature(lyr.GetLayerDefn()) f['name'] = 'region one' lyr.CreateFeature(f) f = ogr.Feature(lyr.GetLayerDefn()) f['name'] = 'region two' lyr.CreateFeature(f) lyr = ds.CreateLayer('province', geom_type=ogr.wkbNone) lyr.CreateField(ogr.FieldDefn('name', ogr.OFTString)) lyr.CreateField(ogr.FieldDefn('region', ogr.OFTInteger)) f = ogr.Feature(lyr.GetLayerDefn()) f['name'] = 'province one' f['region'] = 1 lyr.CreateFeature(f) f = ogr.Feature(lyr.GetLayerDefn()) f['name'] = 'province two' f['region'] = 1 lyr.CreateFeature(f) f = ogr.Feature(lyr.GetLayerDefn()) f['name'] = 'province three' f['region'] = 2 lyr.CreateFeature(f) f = ogr.Feature(lyr.GetLayerDefn()) f['name'] = 'province four' f['region'] = 2 lyr.CreateFeature(f) lyr = ds.CreateLayer('city', geom_type=ogr.wkbNone) lyr.CreateField(ogr.FieldDefn('name', ogr.OFTString)) lyr.CreateField(ogr.FieldDefn('province', ogr.OFTInteger)) f = ogr.Feature(lyr.GetLayerDefn()) f['name'] = 'city one' f['province'] = 1 lyr.CreateFeature(f) f = ogr.Feature(lyr.GetLayerDefn()) f['name'] = 'city two' f['province'] = 1 lyr.CreateFeature(f) f = ogr.Feature(lyr.GetLayerDefn()) f['name'] = 'city three' f['province'] = 2 lyr.CreateFeature(f) f = ogr.Feature(lyr.GetLayerDefn()) f['name'] = 'city four' f['province'] = 4 lyr.CreateFeature(f) f = None ds = None region = QgsVectorLayer(cls.temp_path + '|layername=region', 'region') province = QgsVectorLayer(cls.temp_path + '|layername=province', 'province') city = QgsVectorLayer(cls.temp_path + '|layername=city', 'city') QgsProject.instance().addMapLayers([region, province, city]) relMgr = QgsProject.instance().relationManager() rel = QgsRelation() rel.setId('rel1') rel.setName('province -> region') rel.setReferencingLayer(province.id()) rel.setReferencedLayer(region.id()) rel.addFieldPair('region', 'fid') assert rel.isValid() relMgr.addRelation(rel) rel = QgsRelation() rel.setId('rel2') rel.setName('city -> province') rel.setReferencingLayer(city.id()) rel.setReferencedLayer(province.id()) rel.addFieldPair('province', 'fid') assert rel.isValid() relMgr.addRelation(rel)