Skip to content

Commit 02317a4

Browse files
committed
Additional validation and guards against invalid inputs
1 parent 8af9028 commit 02317a4

File tree

1 file changed

+76
-24
lines changed

1 file changed

+76
-24
lines changed

ulc_mm_package/QtGUI/study_metadata_form.py

Lines changed: 76 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from pathlib import Path
2-
from typing import Dict, List, Optional
2+
from typing import Dict, List, Optional, Tuple
33

44
from PyQt5.QtCore import QDate
55
from PyQt5.QtGui import QIntValidator
@@ -84,7 +84,6 @@ def create_widget_for_field(field_def):
8484

8585
default = field_def.get("default")
8686
if default is not None:
87-
print("default is")
8887
idx = w.findText(default)
8988
if idx >= 0:
9089
w.setCurrentIndex(idx)
@@ -139,7 +138,7 @@ def __init__(self, cfg: dict, parent=None):
139138
self.btn_start.setEnabled(False)
140139
layout.addRow(label, widget)
141140

142-
self.btn_start.clicked.connect(self.accept)
141+
self.btn_start.clicked.connect(self.validate)
143142
btn_cancel.clicked.connect(self.close)
144143

145144
layout.addRow(btn_cancel, self.btn_start)
@@ -162,33 +161,76 @@ def __init__(self, cfg: dict, parent=None):
162161
# run an initial check to set OK button state
163162
self.check_required()
164163

164+
def _get_widget_text(self, w: QWidget) -> Optional[str]:
165+
if isinstance(w, QTextEdit):
166+
return w.toPlainText()
167+
elif isinstance(w, QLineEdit):
168+
return w.text()
169+
elif isinstance(w, QComboBox):
170+
return "" if w.currentIndex() < 0 else w.currentText()
171+
elif isinstance(w, QListWidget):
172+
return "\n".join(i.text() for i in w.selectedItems())
173+
174+
if hasattr(w, "text"):
175+
return w.text()
176+
return None
177+
178+
def _is_empty(self, w) -> bool:
179+
text = self._get_widget_text(w)
180+
return (text is not None) and (not text.strip())
181+
182+
def _has_value_and_valid(self, w: QWidget) -> bool:
183+
if self._is_empty(w):
184+
return True
185+
186+
if isinstance(w, QTextEdit):
187+
return bool(w.toPlainText().strip())
188+
elif isinstance(w, QLineEdit):
189+
text = w.text().strip()
190+
return bool(text) and w.hasAcceptableInput()
191+
elif isinstance(w, QComboBox):
192+
return w.currentIndex() >= 0
193+
elif isinstance(w, QListWidget):
194+
return len(w.selectedItems()) >= 0
195+
196+
if hasattr(w, "text"):
197+
return bool(w.text().strip())
198+
199+
return False
200+
201+
def _check_widget(self, w: QWidget, field: dict) -> Tuple[bool, str]:
202+
required = field.get("required")
203+
if self._is_empty(w) and required:
204+
return False, "Required"
205+
206+
if not self._has_value_and_valid(w):
207+
return False, "Invalid"
208+
209+
return True, ""
210+
165211
def check_required(self):
166212
"""Ensure that all required widgets have a value."""
167-
all_filled = True
168-
for w in self._required_widgets:
169-
if hasattr(w, "toPlainText"):
170-
if not w.toPlainText().strip():
171-
all_filled = False
172-
break
173-
elif hasattr(w, "text"):
174-
if not w.text().strip():
175-
all_filled = False
176-
break
177-
elif hasattr(w, "currentIndex"):
178-
# -1 index means no selection made
179-
if w.currentIndex() < 0:
180-
all_filled = False
181-
break
182-
if all_filled:
183-
self.btn_start.setEnabled(True)
184-
else:
185-
self.btn_start.setEnabled(False)
213+
self.btn_start.setEnabled(
214+
all([self._has_value_and_valid(x) for x in self._required_widgets])
215+
)
216+
217+
def validate(self):
218+
for lbl, (w, field) in self._widgets.items():
219+
valid, error_msg = self._check_widget(w, field)
220+
if not valid:
221+
print(lbl, field, error_msg)
222+
w.setFocus()
223+
return
224+
self.accept()
186225

187226
def get_form_input(self):
188227
result = {"study_id": self.config_data["study_description"]["key"]}
189228
errors = []
190-
for name, (widget, field) in self._widgets.items():
229+
possible_choices = []
230+
subkeys = []
231+
for _, (widget, field) in self._widgets.items():
191232
t = field["datatype"]
233+
key = field["key"]
192234

193235
if t == "string":
194236
value = (
@@ -209,10 +251,20 @@ def get_form_input(self):
209251
value = widget.currentData()
210252
elif t == "multiselect":
211253
value = [x.text() for x in widget.selectedItems()]
254+
possible_choices = [
255+
widget.item(i).text() for i in range(widget.count())
256+
]
257+
subkeys = field["subkeys"]
212258
else:
213259
value = None
214260

215-
result[name] = value
261+
# Accommodate the multiselect case separately (i.e. save each choice as its own col)
262+
if isinstance(value, list):
263+
selected_set = set(value)
264+
for choice, choice_key in zip(possible_choices, subkeys):
265+
result[choice_key] = choice in selected_set
266+
else:
267+
result[key] = value
216268

217269
if errors:
218270
raise ValueError("\n".join(errors))

0 commit comments

Comments
 (0)