Skip to content

Commit 87a8857

Browse files
add CRS verification for Provider.CRS list
1 parent f98c714 commit 87a8857

File tree

5 files changed

+136
-79
lines changed

5 files changed

+136
-79
lines changed

models/top_level/utils.py

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
from enum import Enum
2-
import requests
3-
from urllib.parse import urlparse
42

53
STRING_SEPARATOR = " | "
64

@@ -46,31 +44,3 @@ def bbox_from_list(raw_bbox_list: list):
4644
)
4745

4846
return InlineList(list_bbox_val)
49-
50-
51-
def is_url_responsive(url: str) -> bool:
52-
53-
parsed_url = urlparse(url)
54-
if not all([parsed_url.scheme, parsed_url.netloc]):
55-
return False, "Invalid URL"
56-
57-
try:
58-
# Detect OGC ExceptionReport (invalid request)
59-
response = requests.get(url, allow_redirects=True, timeout=5)
60-
if response.status_code != 200:
61-
return False, f"HTTP response status code: {response.status_code}"
62-
else:
63-
text = response.text
64-
if "ExceptionReport" in text or "ExceptionText" in text:
65-
return False, "Invalid CRS URL"
66-
return True, "Valid CRS URL"
67-
68-
except requests.ConnectionError:
69-
# No internet or server unreachable
70-
return False, "No internet connection or cannot reach server."
71-
72-
except requests.Timeout:
73-
return False, "Request timed out."
74-
75-
except requests.RequestException:
76-
return False, "Something went wrong with the request :("

pygeoapi_config_dialog.py

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import os
2626
import yaml
2727

28+
from .ui_widgets.utils import get_url_status
29+
2830

2931
from .models.top_level.providers.records import ProviderTypes
3032
from .ui_widgets.providers.NewProviderWindow import NewProviderWindow
@@ -34,7 +36,6 @@
3436
from .models.top_level.utils import (
3537
InlineList,
3638
get_enum_value_from_string,
37-
is_url_responsive,
3839
)
3940
from .models.top_level.utils import STRING_SEPARATOR
4041

@@ -364,19 +365,7 @@ def _validate_and_add_res_provider(
364365
def validate_res_extents_crs(self):
365366
"""Called from .ui file."""
366367
url = self.data_from_ui_setter.get_extents_crs_from_ui(self)
367-
response = is_url_responsive(url)
368-
if response[0]:
369-
QMessageBox.information(
370-
self,
371-
"Information",
372-
f"{response[1]}",
373-
)
374-
else:
375-
QMessageBox.warning(
376-
self,
377-
"Warning",
378-
f"{response[1]}",
379-
)
368+
get_url_status(url, self)
380369

381370
def delete_metadata_id_title(self):
382371
"""Delete keyword from metadata, called from .ui file."""

ui_widgets/providers/StringListWidget.py

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
QLabel,
99
QLineEdit,
1010
QMessageBox,
11+
QDialog,
12+
QDialogButtonBox,
1113
)
1214
from PyQt5.QtGui import QFontMetrics
1315

@@ -16,7 +18,9 @@ class StringListWidget(QWidget):
1618
label: QLabel
1719
default_new_string: str = ""
1820

19-
def __init__(self, label_text: str, default_new_string: str = ""):
21+
def __init__(
22+
self, label_text: str, default_new_string: str = "", validation_callback=None
23+
):
2024
super().__init__()
2125
# self.setWindowTitle(label_text)
2226

@@ -36,14 +40,14 @@ def __init__(self, label_text: str, default_new_string: str = ""):
3640
layout.addLayout(button_layout)
3741

3842
self.add_button = QPushButton("Add")
39-
self.add_button.clicked.connect(self.add_item)
43+
self.add_button.clicked.connect(lambda: self.add_item(validation_callback))
4044
button_layout.addWidget(self.add_button)
4145

4246
self.remove_button = QPushButton("Remove")
4347
self.remove_button.clicked.connect(self.remove_item)
4448
button_layout.addWidget(self.remove_button)
4549

46-
def add_item(self):
50+
def add_item(self, validation_callback):
4751
# short option, but the dialog window is too narrow
4852
r"""
4953
text, ok = QInputDialog.getText(
@@ -54,22 +58,11 @@ def add_item(self):
5458
"""
5559

5660
# longer option
57-
58-
# create the dialog instance
59-
dialog = QInputDialog(self)
60-
dialog.setWindowTitle("Add Item")
61-
dialog.setLabelText("Enter new item:")
62-
dialog.setTextValue(self.default_new_string)
63-
64-
# auto-size based on text width
65-
fm = QFontMetrics(dialog.font())
66-
width = (
67-
fm.horizontalAdvance(self.default_new_string) + 150
68-
) # extra space for padding/buttons
69-
dialog.resize(width, dialog.height())
70-
71-
if dialog.exec_() == QInputDialog.Accepted:
72-
text = dialog.textValue().strip()
61+
dialog = InputWithValidateDialog(
62+
self.default_new_string, validation_callback, self
63+
)
64+
if dialog.exec_() == QDialog.Accepted:
65+
text = dialog.line_edit.text().strip()
7366
if text:
7467
self.list_widget.addItem(text)
7568

@@ -90,3 +83,42 @@ def values_to_list(self):
9083
all_values.append(value)
9184

9285
return all_values
86+
87+
88+
class InputWithValidateDialog(QDialog):
89+
def __init__(self, default_text="", validation_callback=None, parent=None):
90+
super().__init__(parent)
91+
self.setWindowTitle("Add Item")
92+
93+
layout = QVBoxLayout(self)
94+
95+
# Label + input row
96+
label = QLabel("Enter new item:")
97+
layout.addWidget(label)
98+
99+
input_row = QHBoxLayout()
100+
self.line_edit = QLineEdit(default_text)
101+
input_row.addWidget(self.line_edit)
102+
103+
# Add validate button if requested
104+
if validation_callback is not None:
105+
self.validate_btn = QPushButton("Validate")
106+
self.validate_btn.clicked.connect(
107+
lambda: validation_callback(self.line_edit.text().strip(), self)
108+
)
109+
input_row.addWidget(self.validate_btn)
110+
111+
layout.addLayout(input_row)
112+
113+
# Standard OK/Cancel buttons
114+
self.button_box = QDialogButtonBox(
115+
QDialogButtonBox.Ok | QDialogButtonBox.Cancel
116+
)
117+
self.button_box.accepted.connect(self.accept)
118+
self.button_box.rejected.connect(self.reject)
119+
layout.addWidget(self.button_box)
120+
121+
# Auto-size based on default text
122+
fm = QFontMetrics(self.font())
123+
width = fm.horizontalAdvance(default_text) + 150
124+
self.resize(width, self.height())

ui_widgets/providers/utils.py

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
1-
from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QGridLayout, QComboBox
1+
from PyQt5.QtWidgets import (
2+
QHBoxLayout,
3+
QLabel,
4+
QLineEdit,
5+
QGridLayout,
6+
QComboBox,
7+
QPushButton,
8+
)
29
from PyQt5.QtGui import QIntValidator
310

411
from .StringListWidget import StringListWidget
5-
from ..utils import set_combo_box_value_from_data
12+
from ..utils import get_url_status, set_combo_box_value_from_data
613

714

815
def create_label_lineedit_pair(
9-
label_text: str, default_value="", placeholder: str = ""
16+
label_text: str, default_value="", placeholder: str = "", validation_callback=None
1017
) -> QHBoxLayout:
1118
label = QLabel(label_text)
1219
line_edit = QLineEdit(default_value)
1320
line_edit.setPlaceholderText(placeholder)
1421

15-
return label, line_edit
22+
return {"label": label, "line_edit": line_edit}
1623

1724

1825
def create_label_dropdown_pair(label_text: str, all_values: list) -> QHBoxLayout:
@@ -25,8 +32,10 @@ def create_label_dropdown_pair(label_text: str, all_values: list) -> QHBoxLayout
2532
return label, dropdown
2633

2734

28-
def create_list_widget(label_text: str, default_new_string: str = ""):
29-
return StringListWidget(label_text, default_new_string)
35+
def create_list_widget(
36+
label_text: str, default_new_string: str = "", validation_callback=None
37+
):
38+
return StringListWidget(label_text, default_new_string, validation_callback)
3039

3140

3241
def add_widgets_to_grid_by_specs(
@@ -42,22 +51,31 @@ def add_widgets_to_grid_by_specs(
4251

4352
# if regular widget (QLineEdit for str and int; QListWIdget for list)
4453
if not special_widget_type:
54+
55+
# set up defaults
56+
default_list_entry = ""
57+
validation_callback = None
58+
59+
if label.endswith("crs"):
60+
default_list_entry = "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
61+
validation_callback = lambda url, parent: get_url_status(url, parent)
62+
63+
# check data types and create corresponding widgets
4564
if data_type is str or data_type is int:
46-
label_widget, data_widget = create_label_lineedit_pair(
47-
label, default, placeholder
65+
new_widgets = create_label_lineedit_pair(
66+
label, default, placeholder, validation_callback
4867
)
49-
group_layout.addWidget(label_widget, i, 0)
50-
group_layout.addWidget(data_widget, i, 1)
68+
group_layout.addWidget(new_widgets["label"], i, 0)
69+
group_layout.addWidget(new_widgets["line_edit"], i, 1)
5170

5271
if data_type is int:
53-
data_widget.setValidator(QIntValidator())
72+
new_widgets["line_edit"].setValidator(QIntValidator())
5473

5574
elif data_type is list:
56-
default_list_entry = ""
57-
if label.endswith("crs"):
58-
default_list_entry = "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
5975

60-
data_widget = create_list_widget(label, default_list_entry)
76+
data_widget = create_list_widget(
77+
label, default_list_entry, validation_callback
78+
)
6179
group_layout.addWidget(data_widget.label, i, 0)
6280
group_layout.addWidget(data_widget, i, 1)
6381

ui_widgets/utils.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from enum import Enum
2-
from PyQt5.QtWidgets import QComboBox, QLineEdit
2+
import requests
3+
from urllib.parse import urlparse
4+
5+
from PyQt5.QtWidgets import QComboBox, QLineEdit, QMessageBox
36

47

58
def get_widget_text_value(widget):
@@ -39,3 +42,48 @@ def set_combo_box_value_from_data(*, combo_box, value):
3942
combo_box.setCurrentIndex(0)
4043
else:
4144
combo_box.clear()
45+
46+
47+
def _is_url_responsive(url: str) -> bool:
48+
49+
parsed_url = urlparse(url)
50+
if not all([parsed_url.scheme, parsed_url.netloc]):
51+
return False, "Invalid URL"
52+
53+
try:
54+
# Detect OGC ExceptionReport (invalid request)
55+
response = requests.get(url, allow_redirects=True, timeout=5)
56+
if response.status_code != 200:
57+
return False, f"HTTP response status code: {response.status_code}"
58+
else:
59+
text = response.text
60+
if "ExceptionReport" in text or "ExceptionText" in text:
61+
return False, "Invalid CRS URL"
62+
return True, "Valid CRS URL"
63+
64+
except requests.ConnectionError:
65+
# No internet or server unreachable
66+
return False, "No internet connection or cannot reach server."
67+
68+
except requests.Timeout:
69+
return False, "Request timed out."
70+
71+
except requests.RequestException:
72+
return False, "Something went wrong with the request :("
73+
74+
75+
def get_url_status(url, parent=None):
76+
77+
response = _is_url_responsive(url)
78+
if response[0]:
79+
QMessageBox.information(
80+
parent,
81+
"Information",
82+
f"{response[1]}",
83+
)
84+
else:
85+
QMessageBox.warning(
86+
parent,
87+
"Warning",
88+
f"{response[1]}",
89+
)

0 commit comments

Comments
 (0)