Exemple #1
0
 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
Exemple #2
0
def on_resolve_href(dialog, layer, feature, field):
    """
    @param dialog the dialog where the feature form is opened
    @param layer the layer on which the href link stands
    @param feature the current feature
    @param field the field name storing the href URL
    @param linked_layer_id the QGIS layer id of the already resolved layer, for update
    """
    from .import_gmlas_panel import ImportGmlasPanel
    path = feature[field]
    if not path:
        return

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

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

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

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

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

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

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

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

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

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

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

    root_layer.commitChanges()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        couche.setEditFormConfig(fc)

        install_viewer_on_feature_form(couche)
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)