def create(self, parent, layer): try: widget = QgsAttributeEditorRelation(self.relation.id, parent) except TypeError: # Feed deprecated API for 3.0.0 and 3.0.1 widget = QgsAttributeEditorRelation(self.relation.id, self.relation.id, parent) return widget
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 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)