Skip to content

Commit

Permalink
Add new checks about WFS for layers and fields
Browse files Browse the repository at this point in the history
  • Loading branch information
Gustry committed Feb 5, 2024
1 parent 01ea092 commit f768769
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 37 deletions.
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 @@ -166,11 +167,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 @@ -143,9 +143,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 @@ -2066,13 +2067,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 @@ -3226,6 +3223,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.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
67 changes: 66 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,67 @@ def from_json(self, data: dict):
row = self.table.rowCount()
self.table.setRowCount(row + 1)
self._edit_row(row, layer_data)

def fields_used(self) -> Dict[str, List[str]]:
""" List of layers and fields used in the table. """
# 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 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
25 changes: 16 additions & 9 deletions lizmap/table_manager/dataviz.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,15 +210,22 @@ def preview_dataviz_dialog(self):
# Customized error from the server about the request
# We should have a JSON
response = request.reply().content()
json_response = json.loads(response.data().decode('utf-8'))
errors = json_response.get('errors')
if errors:
# Message from the server
message = '<b>{}</b><br><br>{}'.format(errors.get('title'), errors.get('detail'))
elif json_response.get('errorMessage'):
# Error from nginx or apache?
message = '<b>{}</b><br><br>{}'.format(
json_response.get('errorMessage'), json_response.get('errorCode'))
try:
json_response = json.loads(response.data().decode('utf-8'))
except json.JSONDecodeError:
message = tr(
'Not possible to decode the response from the server, please check the content of the '
'response.'
)
else:
errors = json_response.get('errors')
if errors:
# Message from the server
message = '<b>{}</b><br><br>{}'.format(errors.get('title'), errors.get('detail'))
elif json_response.get('errorMessage'):
# Error from nginx or apache?
message = '<b>{}</b><br><br>{}'.format(
json_response.get('errorMessage'), json_response.get('errorCode'))

# Let's add some more context to help
message += '<br><br>' + tr("Given context for the request") + ' : <br>'
Expand Down
45 changes: 36 additions & 9 deletions lizmap/test/test_table_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ def test_form_filter(self):
}
self.assertDictEqual(data, expected)

self.assertDictEqual(
{self.layer.id(): ['name', 'id']},
table_manager.fields_used()
)

def test_form_filter_3_7(self):
""" Test to write to 3.6 format. """
table_manager = TableManager(
Expand Down Expand Up @@ -221,6 +226,10 @@ def test_filter_by_login(self):
}
}
self.assertDictEqual(data, expected)
self.assertDictEqual(
{self.layer.id(): ['name']},
table_manager.fields_used()
)

def test_layout_definitions(self):
""" Test layout definitions. """
Expand Down Expand Up @@ -722,13 +731,13 @@ def test_dataviz_box(self):
'type': 'box',
'x_field': 'id',
'aggregation': '',
'y_field': 'name',
'y_field': 'first name',
'color': '#00aaff',
'colorfield': '',
'colorfield': 'first color field',
'has_y2_field': 'True',
'y2_field': 'name',
'y2_field': 'second name',
'color2': '#ffaa00',
'colorfield2': '',
'colorfield2': 'second color field',
'popup_display_child_plot': 'False',
'only_show_child': 'True',
'layerId': layer.id(),
Expand All @@ -750,12 +759,12 @@ def test_dataviz_box(self):
'traces': [
{
'color': '#00aaff',
'colorfield': '',
'y_field': 'name'
'colorfield': 'first color field',
'y_field': 'first name'
}, {
'color': '#ffaa00',
'colorfield': '',
'y_field': 'name'
'colorfield': 'second color field',
'y_field': 'second name'
}
],
'trigger_filter': True,
Expand All @@ -773,6 +782,19 @@ def test_dataviz_box(self):
del data['0']['uuid']
self.assertDictEqual(data, expected)

self.assertDictEqual(
{
layer.id(): [
'id',
'first name',
'first color field',
'second name',
'second color field',
]
},
table_manager.fields_used()
)

def test_dataviz(self):
"""Test we can read dataviz 3.4 format."""
table_widget = QTableWidget()
Expand Down Expand Up @@ -936,7 +958,7 @@ def test_attribute_table(self):
json = {
'lines': {
'primaryKey': 'id',
'hiddenFields': 'id,name',
'hiddenFields': 'id,name,value',
'pivot': 'False',
'hideAsChild': 'False',
'hideLayer': 'False',
Expand All @@ -954,6 +976,11 @@ def test_attribute_table(self):

self.assertDictEqual(data, json)

self.assertDictEqual(
{self.layer.id(): ['id', 'name', 'value']},
table_manager.fields_used()
)

def test_time_manager_table(self):
"""Test table manager with time manager."""
table = QTableWidget()
Expand Down
Loading

0 comments on commit f768769

Please sign in to comment.