Ejemplo n.º 1
0
    def create(layer_wrapper: LayerWrapper, relation_manager: QgsRelationManager,
               relation: QgsRelation) -> 'RelationalLayerWrapper':

        referencing_layer: QgsVectorLayer = relation.referencingLayer()
        try:
            other_relation: QgsRelation = [r for r in relation_manager.referencingRelations(referencing_layer)
                                           if r.id() != relation.id()][0]
        except KeyError:
            raise QaavaLayerError(tr('Relation error'), bar_msg(
                tr('Relation {} does not contain referencing another layer', relation.name())))

        relation_layer_wrapper = LayerWrapper.from_qgs_layer(referencing_layer)
        a_pk: QgsField = referencing_layer.fields().toList()[relation.referencingFields()[0]]
        m_pk_a: QgsField = layer_wrapper.get_layer().fields().toList()[relation.referencedFields()[0]]

        fw_m_a = FieldWrapper.from_layer_wrapper(relation_layer_wrapper, a_pk, set(), '')
        fw_a = FieldWrapper.from_layer_wrapper(layer_wrapper, m_pk_a, set(), '')

        other_layer: QgsVectorLayer = other_relation.referencedLayer()
        b_pk = other_layer.fields().toList()[other_relation.referencedFields()[0]]
        m_pk_b = referencing_layer.fields().toList()[other_relation.referencingFields()[0]]

        fw_m_b = FieldWrapper.from_layer_wrapper(relation_layer_wrapper, m_pk_b, set(), '')
        other_layer_wrapper = LayerWrapper.from_qgs_layer(other_layer, relation_layer_wrapper, fw_m_b)

        fw_b = FieldWrapper.from_layer_wrapper(other_layer_wrapper, b_pk, set(), '')

        return RelationalLayerWrapper(referencing_layer.name(), other_layer.name(), layer_wrapper, fw_m_a,
                                      fw_a, fw_m_b, fw_b)
Ejemplo n.º 2
0
 def create_relations(
     self
 ):  #CURRENTLY NOT IN USE (NOT WORKING AS EXPECTED, ONLY RANDOMLY CREATING BOTH RELATIONS!!!  ALSO, SOMETIMES QGIS CRASH WHEN OPENING FORMS)
     """
     1. create project relations obs_points-comments and obs_points-stratigraphy
     2. add 2 tabs to the obs_points form and fill those tabs with related layers comments and stratigraphy
     """
     # create relations
     i = 0
     layers = default_layers()
     rel_tuples = relations()
     for lyr in list(layers.keys()):
         rel = QgsRelation()
         rel.setReferencingLayer(utils.find_layer(layers[lyr]).id())
         rel.setReferencedLayer(utils.find_layer(lyr).id())
         rel.addFieldPair('typ', 'typ')
         rel.setId(rel_tuples[i][1])
         rel.setName(rel_tuples[i][0])
         if rel.isValid(
         ):  # It will only be added if it is valid. If not, check the ids and field names
             QgsProject.instance().relationManager().addRelation(rel)
             #validate
             for key in QgsProject.instance().relationManager().relations(
             ).keys():
                 #print(key)
                 if str(key) == rel.id():
                     print(('added relation %s' % str(lyr)))
         else:
             qgis.utils.iface.messageBar().pushMessage(
                 "Error", """Failed to create relation %s!""" % str(lyr), 2)
             print(("""Failed to create relation %s!""" % str(lyr)))
         i += 1
     """
 def create_menu_action(parent_menu: QMenu,
                        title: str,
                        relation: QgsRelation,
                        slot,
                        data=None):
     # add legend context menu entry
     action = QAction(title, parent_menu)
     action.setData(relation.id())
     action.triggered.connect(
         lambda: slot(relation,
                      relation.referencedLayer().selectedFeatures(), data))
     parent_menu.addAction(action)
Ejemplo n.º 4
0
 def add_layer_tree_action(self,
                           title: str,
                           relation: QgsRelation,
                           slot,
                           data=None) -> QAction:
     # add legend context menu entry
     layer_tree_action = QAction(title, self.iface.mainWindow())
     self.iface.addCustomActionForLayerType(layer_tree_action, None,
                                            QgsMapLayer.VectorLayer, False)
     self.iface.addCustomActionForLayer(layer_tree_action,
                                        relation.referencedLayer())
     layer_tree_action.setData(relation.id())
     layer_tree_action.triggered.connect(
         lambda: slot(relation,
                      relation.referencedLayer().selectedFeatures(), data))
     self.layer_tree_actions.append(layer_tree_action)
Ejemplo n.º 5
0
    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()
Ejemplo n.º 6
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)
Ejemplo n.º 7
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)
Ejemplo n.º 8
0
    def testCreateExpression(self):
        """ Test creating an expression using the widget"""
        layer = QgsVectorLayer(
            "Point?field=fldtxt:string&field=fldint:integer", "test", "memory")
        # setup value relation
        parent_layer = QgsVectorLayer(
            "Point?field=stringkey:string&field=intkey:integer&field=display:string",
            "parent", "memory")
        f1 = QgsFeature(parent_layer.fields(), 1)
        f1.setAttributes(['a', 1, 'value a'])
        f2 = QgsFeature(parent_layer.fields(), 2)
        f2.setAttributes(['b', 2, 'value b'])
        f3 = QgsFeature(parent_layer.fields(), 3)
        f3.setAttributes(['c', 3, 'value c'])
        parent_layer.dataProvider().addFeatures([f1, f2, f3])
        QgsProject.instance().addMapLayers([layer, parent_layer])

        relationManager = QgsProject.instance().relationManager()
        relation = QgsRelation()
        relation.setId('relation')
        relation.setReferencingLayer(layer.id())
        relation.setReferencedLayer(parent_layer.id())
        relation.addFieldPair('fldtxt', 'stringkey')
        self.assertTrue(relation.isValid())

        relationManager.addRelation(relation)

        # Everything valid
        config = {'Relation': relation.id(), 'AllowNULL': True}

        w = QgsRelationReferenceSearchWidgetWrapper(layer, 0, None)
        w.setConfig(config)

        w.widget().setForeignKey('a')
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.IsNull),
                         '"fldtxt" IS NULL')
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.IsNotNull),
                         '"fldtxt" IS NOT NULL')
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.EqualTo),
                         '"fldtxt"=\'a\'')
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.NotEqualTo),
                         '"fldtxt"<>\'a\'')

        w.widget().setForeignKey('b')
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.IsNull),
                         '"fldtxt" IS NULL')
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.IsNotNull),
                         '"fldtxt" IS NOT NULL')
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.EqualTo),
                         '"fldtxt"=\'b\'')
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.NotEqualTo),
                         '"fldtxt"<>\'b\'')

        w.widget().setForeignKey('c')
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.IsNull),
                         '"fldtxt" IS NULL')
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.IsNotNull),
                         '"fldtxt" IS NOT NULL')
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.EqualTo),
                         '"fldtxt"=\'c\'')
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.NotEqualTo),
                         '"fldtxt"<>\'c\'')

        w.widget().setForeignKey(None)
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.IsNull),
                         '"fldtxt" IS NULL')
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.IsNotNull),
                         '"fldtxt" IS NOT NULL')
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.EqualTo),
                         '"fldtxt" IS NULL')
        self.assertEqual(w.createExpression(QgsSearchWidgetWrapper.NotEqualTo),
                         '"fldtxt" IS NOT NULL')
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)
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)