Skip to content

Commit

Permalink
Merge pull request #553 from Gustry/wfs-fields-layers
Browse files Browse the repository at this point in the history
Add new checks about WFS for layers and fields
  • Loading branch information
Gustry authored Feb 5, 2024
2 parents 130dca7 + 12f3d3b commit 9156142
Show file tree
Hide file tree
Showing 16 changed files with 283 additions and 37 deletions.
4 changes: 4 additions & 0 deletions lizmap/definitions/atlas.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ def __init__(self):
self._use_single_row = True
self._layer_config['layer'] = {
'type': InputType.Layer,
'wfs_required': True,
'header': tr('Layer'),
'default': None,
'tooltip': tr('The vector layer for the atlas.')
}
self._layer_config['primaryKey'] = {
'type': InputType.PrimaryKeyField,
'wfs_required': True,
'header': tr('Primary key'),
'default': None,
'tooltip': tr('Layer primary key (must be integer for PostgreSQL).')
Expand All @@ -51,6 +53,7 @@ def __init__(self):
}
self._layer_config['featureLabel'] = {
'type': InputType.Field,
'wfs_required': True,
'header': tr('Feature label'),
'default': None,
'tooltip': tr(
Expand All @@ -59,6 +62,7 @@ def __init__(self):
}
self._layer_config['sortField'] = {
'type': InputType.Field,
'wfs_required': True,
'header': tr('Sort field'),
'default': None,
'tooltip': tr('Your atlas will be sorted according to this field.')
Expand Down
3 changes: 3 additions & 0 deletions lizmap/definitions/attribute_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,21 @@ def __init__(self):
super().__init__()
self._layer_config['layerId'] = {
'type': InputType.Layer,
'wfs_required': True,
'header': tr('Layer'),
'default': None,
'tooltip': tr('The vector layer for the attribute table.')
}
self._layer_config['primaryKey'] = {
'type': InputType.PrimaryKeyField,
'wfs_required': True,
'header': tr('Primary key'),
'default': None,
'tooltip': tr('Primary key of the layer.')
}
self._layer_config['hiddenFields'] = {
'type': InputType.Fields,
'wfs_required': False,
'header': tr('Fields to hide'),
'default': '',
'tooltip': tr('List of fields to hide in the attribute table.')
Expand Down
6 changes: 6 additions & 0 deletions lizmap/definitions/dataviz.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,14 @@ def __init__(self):
}
self._layer_config['layerId'] = {
'type': InputType.Layer,
'wfs_required': True,
'header': tr('Layer'),
'default': None,
'tooltip': tr('The vector layer for the graph.')
}
self._layer_config['x_field'] = {
'type': InputType.Field,
'wfs_required': True,
'header': tr('X field'),
'default': '',
'tooltip': tr('X field of your graph, it might be empty according to the kind of graph (box).')
Expand All @@ -206,6 +208,7 @@ def __init__(self):
}
self._layer_config['traces'] = {
'type': InputType.Collection,
'wfs_required': True,
'header': tr('Traces'),
'tooltip': tr('Textual representations of traces'),
'items': [
Expand All @@ -219,6 +222,7 @@ def __init__(self):
self._layer_config['y_field'] = {
'plural': 'y{}_field',
'type': InputType.Field,
'wfs_required': True,
'header': tr('Y field'),
'default': '',
'tooltip': tr('The Y field of your graph.')
Expand All @@ -233,6 +237,7 @@ def __init__(self):
self._layer_config['colorfield'] = {
'plural': 'colorfield{}',
'type': InputType.Field,
'wfs_required': True,
'header': tr('Color field'),
'default': '',
'tooltip': tr(
Expand All @@ -243,6 +248,7 @@ def __init__(self):
self._layer_config['z_field'] = {
'plural': 'z_field_{}',
'type': InputType.Field,
'wfs_required': True,
'header': tr('Z field'),
'default': '',
'tooltip': tr('The Z field of your graph.'),
Expand Down
2 changes: 2 additions & 0 deletions lizmap/definitions/edition.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def __init__(self):
super().__init__()
self._layer_config['layerId'] = {
'type': InputType.Layer,
'wfs_required': True,
'header': tr('Layer'),
'default': None,
'tooltip': tr('The vector layer for the edition.')
Expand Down Expand Up @@ -67,6 +68,7 @@ def __init__(self):
}
self._layer_config['snap_layers'] = {
'type': InputType.Layers,
'wfs_required': True,
'header': tr('Layers'),
'default': '',
'tooltip': tr('List of layers to snap on.'),
Expand Down
3 changes: 3 additions & 0 deletions lizmap/definitions/locate_by_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,21 @@ def __init__(self):
super().__init__()
self._layer_config['layerId'] = {
'type': InputType.Layer,
'wfs_required': True,
'header': tr('Layer'),
'default': None,
'tooltip': tr('The vector layer for the locate by layer.')
}
self._layer_config['fieldName'] = {
'type': InputType.Field,
'wfs_required': True,
'header': tr('Display field'),
'default': None,
'tooltip': tr('The field to display.')
}
self._layer_config['filterFieldName'] = {
'type': InputType.Field,
'wfs_required': True,
'header': tr('Optional group by field'),
'default': '',
'tooltip': tr(
Expand Down
3 changes: 3 additions & 0 deletions lizmap/definitions/time_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,21 @@ def __init__(self):
super().__init__()
self._layer_config['layerId'] = {
'type': InputType.Layer,
'wfs_required': True,
'header': tr('Layer'),
'default': None,
'tooltip': tr('Layer with the date/time.')
}
self._layer_config['startAttribute'] = {
'type': InputType.Field,
'wfs_required': True,
'header': tr('Start'),
'default': None,
'tooltip': tr('Column with the date/time.')
}
self._layer_config['endAttribute'] = {
'type': InputType.Field,
'wfs_required': True,
'header': tr('End'),
'default': '',
'tooltip': tr('Field with the end date/time.'),
Expand Down
2 changes: 2 additions & 0 deletions lizmap/definitions/tooltip.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ def __init__(self):
super().__init__()
self._layer_config['layerId'] = {
'type': InputType.Layer,
'wfs_required': True,
'header': tr('Layer'),
'default': None,
'tooltip': tr('The vector layer for the tooltip.')
}
self._layer_config['fields'] = {
'type': InputType.Fields,
'wfs_required': True,
'header': tr('Fields'),
'default': None,
'tooltip': tr('Fields to display in the tooltip.')
Expand Down
3 changes: 2 additions & 1 deletion lizmap/dialogs/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ def __init__(self, parent=None, is_dev_version=True, lwc_version: LwcVersions =
'Open the help in the web-browser'
))
self.buttonBox.button(QDialogButtonBox.Ok).setToolTip(tr(
'The Lizmap configuration file is generated and the dialog is closed.'
'The Lizmap configuration file is generated and the dialog is closed, except if there is at least one '
'blocking check.'
))
self.buttonBox.button(QDialogButtonBox.Cancel).setToolTip(tr(
'The Lizmap configuration file is not generated and the dialog is closed.'
Expand Down
7 changes: 3 additions & 4 deletions lizmap/forms/base_edition_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
__email__ = '[email protected]'

from lizmap.tools import is_database_layer
from lizmap.widgets.project_tools import is_layer_published_wfs


class BaseEditionDialog(QDialog):
Expand Down Expand Up @@ -248,10 +249,8 @@ def version_lwc(self):
@staticmethod
def is_layer_in_wfs(layer: QgsVectorLayer) -> Union[None, str]:
""" Check if the layer in the WFS capabilities. """
# noinspection PyArgumentList
for wfs_layer in QgsProject.instance().readListEntry('WFSLayers', '')[0]:
if layer.id() == wfs_layer:
return None
if is_layer_published_wfs(QgsProject.instance(), layer.id()):
return None

msg = tr(
'The layers you have chosen for this tool must be checked in the "WFS Capabilities"\n'
Expand Down
7 changes: 2 additions & 5 deletions lizmap/forms/edition_edition.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from lizmap.forms.base_edition_dialog import BaseEditionDialog
from lizmap.qgis_plugin_tools.tools.i18n import tr
from lizmap.qgis_plugin_tools.tools.resources import load_ui, resources_path
from lizmap.widgets.project_tools import is_layer_published_wfs

__copyright__ = 'Copyright 2020, 3Liz'
__license__ = 'GPL version 3'
Expand Down Expand Up @@ -168,11 +169,7 @@ def validate(self) -> str:

missing_layers = []
for layer in layers:
wfs_layers_list = QgsProject.instance().readListEntry('WFSLayers', '')[0]
for wfs_layer in wfs_layers_list:
if layer == wfs_layer:
break
else:
if not is_layer_published_wfs(QgsProject.instance(), layer):
missing_layers.append(layer)
if missing_layers:
missing_layers = [QgsProject.instance().mapLayer(layer_id).name() for layer_id in missing_layers]
Expand Down
45 changes: 37 additions & 8 deletions lizmap/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,10 @@
from lizmap.table_manager.dataviz import TableManagerDataviz
from lizmap.table_manager.layouts import TableManagerLayouts
from lizmap.tools import cast_to_group, cast_to_layer
from lizmap.widgets.check_project import Check
from lizmap.widgets.check_project import Check, SourceField
from lizmap.widgets.project_tools import (
empty_baselayers,
is_layer_published_wfs,
is_layer_wms_excluded,
)

Expand Down Expand Up @@ -1951,13 +1952,9 @@ def layout_removed(self, name: str):

self.layers_table['layouts']['manager'].layout_removed(name)

def check_wfs_is_checked(self, layer):
wfs_layers_list = self.project.readListEntry('WFSLayers', '')[0]
has_wfs_option = False
for wfs_layer in wfs_layers_list:
if layer.id() == wfs_layer:
has_wfs_option = True
if not has_wfs_option:
def check_wfs_is_checked(self, layer: QgsVectorLayer):
""" Check if the layer is published as WFS. """
if not is_layer_published_wfs(self.project, layer.id()):
self.display_error(tr(
'The layers you have chosen for this tool must be checked in the "WFS Capabilities" option of the '
'QGIS Server tab in the "Project Properties" dialog.'))
Expand Down Expand Up @@ -3066,6 +3063,38 @@ def project_config_file(
)
self.dlg.enabled_simplify_geom(True)

data = {}
for key in self.layers_table.keys():
manager: TableManager = self.layers_table[key].get('manager')
if manager:
for layer_id, fields in manager.wfs_fields_used().items():
if layer_id not in data.keys():
data[layer_id] = []
for f in fields:
if f not in data[layer_id]:
data[layer_id].append(f)

for layer_id, fields in data.items():
layer = self.project.mapLayer(layer_id)
if not is_layer_published_wfs(self.project, layer.id()):
self.dlg.check_results.add_error(
Error(
layer.name(),
checks.MissingWfsLayer,
source_type=SourceLayer(layer.name(), layer.id()),
)
)

for field in fields:
if field in layer.excludeAttributesWfs():
self.dlg.check_results.add_error(
Error(
field,
checks.MissingWfsField,
source_type=SourceField(field, layer.id()),
)
)

results = use_estimated_metadata(self.project)
for layer in results:
self.dlg.check_results.add_error(
Expand Down
71 changes: 70 additions & 1 deletion lizmap/table_manager/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import os

from collections import namedtuple
from typing import Optional, Union
from typing import Dict, List, Optional, Union

from qgis.core import QgsMapLayerModel, QgsMasterLayoutInterface, QgsProject
from qgis.PyQt.QtCore import Qt
Expand Down Expand Up @@ -59,6 +59,7 @@ def __init__(
self.lwc_versions.append(LwcVersions.Lizmap_3_5)
self.lwc_versions.append(LwcVersions.Lizmap_3_6)
self.lwc_versions.append(LwcVersions.Lizmap_3_7)
self.lwc_versions.append(LwcVersions.Lizmap_3_8)

self.keys = [i for i, j in self.definitions.layer_config.items() if j.get('plural') is None]
self.table.setColumnCount(len(self.keys))
Expand Down Expand Up @@ -1114,3 +1115,71 @@ def from_json(self, data: dict):
row = self.table.rowCount()
self.table.setRowCount(row + 1)
self._edit_row(row, layer_data)

def wfs_fields_used(self) -> Dict[str, List[str]]:
""" List of layers and fields used in the table, needed in WFS. """
# Loop over the table definitions to fetch layers and fields columns
index_layer = None
index_fields = []
index_list_fields = []
index_traces = None
for i, key in enumerate(self.keys):
widget_type = self.definitions.layer_config[key]['type']

if not self.definitions.layer_config[key].get('wfs_required', False):
continue

if widget_type == InputType.Layer:
index_layer = i
elif widget_type in (InputType.Field, InputType.PrimaryKeyField):
index_fields.append(i)
elif widget_type == InputType.Fields:
index_list_fields.append(i)
elif widget_type == InputType.Collection:
index_traces = i

if index_layer is None:
return {}

# Loop over the data to fetch all cell contents
layers = {}
for row in range(self.table.rowCount()):
cell = self.table.item(row, index_layer)
layer_id = cell.data(Qt.UserRole)

if layer_id not in layers.keys():
layers[layer_id] = []

for i in index_fields:
cell = self.table.item(row, i)
field_text = cell.data(Qt.UserRole)
if not field_text:
# Some field input are not required
continue
if field_text in layers[layer_id]:
continue

layers[layer_id].append(field_text)

for i in index_list_fields:
cell = self.table.item(row, i)
field_text = cell.data(Qt.UserRole)
if not field_text:
# Some field input are not required
continue
for f in field_text.split(','):
if f in layers[layer_id]:
continue
layers[layer_id].append(f)

if index_traces is not None:
cell = self.table.item(row, index_traces)
field_json = cell.data(Qt.UserRole)
for trace in field_json:
fields = ('y_field', 'z_field', 'colorfield')
for f in fields:
field_content = trace.get(f)
if field_content:
layers[layer_id].append(field_content)

return layers
Loading

0 comments on commit 9156142

Please sign in to comment.