Skip to content

Commit cbace9d

Browse files
Fix code loading bug (#1519)
To prevent users from submitting calculations with unconfigured codes, we filter them out. However, this SHOULD NOT happen when loading a process, since the user cannot submit in that state. But it was, so step 3 was emitting blockers due to unselected codes. Prior to #1513, the blockers didn't do their job of blocking the step, but now they do. So if someone loads the app from a process that was run by someone else (e.g., a downloaded example), step 3 is blocked and step 4 never loads. This PR adds a `filter_codes_for_user` flag injected through the resources refresh mechanism that is generally `True` but set to `False` if we're loading a process. In addition, the PR relaxes default code handling, falling back on the first available code option if the default code does not exist. Finally, the PR removes the blocking of step 3 w.r.t the local QE installation status, since users should still be allowed to proceed with remote submission.
1 parent 9ba5c42 commit cbace9d

6 files changed

Lines changed: 74 additions & 41 deletions

File tree

src/aiidalab_qe/app/submission/global_settings/model.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ def update_global_codes(self):
4747
self.global_codes = self.get_model_state()["codes"]
4848

4949
def update_active_codes(self):
50+
"""Toggle code selectors conditional on plugin activity and required parameters.
51+
52+
For a given code (e.g., pw), if at least one active plugin requires it (if any condition
53+
registered for the code is met), we activate it.
54+
"""
5055
for identifier, code_model in self.get_models():
5156
if identifier != "quantumespresso__pw":
5257
code_model.deactivate()

src/aiidalab_qe/app/submission/model.py

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import ipywidgets as ipw
43
import traitlets as tl
54
from IPython.display import Javascript, display
65

@@ -64,6 +63,7 @@ def confirm(self):
6463
super().confirm()
6564
if not self.has_process:
6665
self._submit()
66+
self.lock()
6767

6868
def _update(self, specific=""):
6969
self.update_process_label()
@@ -150,7 +150,10 @@ def update_process_metadata(self):
150150
return
151151
self.process_label = self.process.label
152152
self.process_description = self.process.description
153-
self.locked = True
153+
154+
def refresh_codes(self, filter_codes_for_user: bool = True):
155+
for _, resource_model in self.get_models():
156+
resource_model.refresh_codes(filter_codes_for_user=filter_codes_for_user)
154157

155158
def get_model_state(self) -> dict:
156159
return {
@@ -207,20 +210,6 @@ def _submit(self):
207210
pk = process_node.pk
208211
display(Javascript(f"window.history.pushState(null, '', '?pk={pk}');"))
209212

210-
def _link_model(self, model: ResourceSettingsModel):
211-
for dependency in model.dependencies:
212-
dependency_parts = dependency.split(".")
213-
if len(dependency_parts) == 1: # from parent, e.g. input_structure
214-
target_model = self
215-
trait = dependency
216-
else: # from sibling, e.g. workchain.protocol
217-
sibling, trait = dependency_parts
218-
target_model = self.get_model(sibling)
219-
ipw.dlink(
220-
(target_model, trait),
221-
(model, trait),
222-
)
223-
224213
def _get_properties(self) -> list[str]:
225214
return self.input_parameters.get("workchain", {}).get("properties", [])
226215

@@ -256,12 +245,6 @@ def _check_blockers(self):
256245
if not self.input_parameters:
257246
yield "No selected input parameters"
258247

259-
if self.installing_qe:
260-
yield "Installing Quantum ESPRESSO codes..."
261-
262-
if not self.qe_installed:
263-
yield "Quantum ESPRESSO is not yet installed"
264-
265248
def _check_warnings(self):
266249
"""Check for any warnings that should be displayed to the user."""
267250
return ""

src/aiidalab_qe/app/wizard/model.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ def load_from_state(self, state: dict):
4545
SubmissionStepModel,
4646
self.get_model("submit"),
4747
)
48+
if process_uuid is not None:
49+
submission_model.refresh_codes(filter_codes_for_user=False)
4850
submission_model.set_model_state(resources_state)
4951

5052
if step_index >= 3:

src/aiidalab_qe/common/code/model.py

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,20 @@ def activate(self):
6868
def deactivate(self):
6969
self.is_active = False
7070

71-
def update(self, user_email: str, default_code=None, refresh=False):
71+
def update(
72+
self,
73+
user_email: str,
74+
default_code: str | None = None,
75+
filter_codes_for_user: bool = True,
76+
refresh: bool = False,
77+
):
7278
if not self.options or refresh:
73-
self.options = self._get_codes(user_email)
79+
self.options = self._get_codes(user_email, filter_codes_for_user)
7480
if default_code:
7581
try:
7682
selected = orm.load_code(default_code).uuid
7783
except NotExistent:
78-
selected = None
79-
self.warning = self._WARNING_TEMPLATE.format(
80-
warning=f"Code '{default_code}' not found"
81-
)
84+
selected = self.first_option
8285
else:
8386
selected = self.first_option
8487
self.selected = selected
@@ -115,7 +118,11 @@ def _get_uuid(self, identifier):
115118
# in the app and thus will not be considered as an option!
116119
return uuid if uuid in [opt[1] for opt in self.options] else None
117120

118-
def _get_codes(self, user_email: str):
121+
def _get_codes(
122+
self,
123+
user_email: str,
124+
filter_codes_for_user: bool = True,
125+
) -> list[tuple[str, str]]:
119126
user = orm.User.collection.get(email=user_email)
120127

121128
filters = (
@@ -124,25 +131,33 @@ def _get_codes(self, user_email: str):
124131
else {}
125132
)
126133

127-
codes = (
128-
orm.QueryBuilder()
129-
.append(
130-
orm.Code,
131-
filters=filters,
132-
)
133-
.all(flat=True)
134+
codes = t.cast(
135+
list[orm.Code],
136+
(
137+
orm.QueryBuilder()
138+
.append(
139+
orm.Code,
140+
filters=filters,
141+
)
142+
.all(flat=True)
143+
),
134144
)
135145

136146
return [
137147
(self._full_code_label(code), code.uuid)
138148
for code in codes
139-
if code.computer.is_user_configured(user)
140-
and (self.allow_hidden_codes or not code.is_hidden)
141-
and (self.allow_disabled_computers or code.computer.is_user_enabled(user))
149+
if not filter_codes_for_user
150+
or (
151+
code.computer.is_user_configured(user)
152+
and (self.allow_hidden_codes or not code.is_hidden)
153+
and (
154+
self.allow_disabled_computers or code.computer.is_user_enabled(user)
155+
)
156+
)
142157
]
143158

144159
@staticmethod
145-
def _full_code_label(code):
160+
def _full_code_label(code: orm.Code) -> str:
146161
return f"{code.label}@{code.computer.label}"
147162

148163

src/aiidalab_qe/common/panel.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,12 +172,13 @@ def update_active_codes(self):
172172
else:
173173
code_model.deactivate()
174174

175-
def refresh_codes(self):
175+
def refresh_codes(self, filter_codes_for_user: bool = True):
176176
for _, code_model in self.get_models():
177177
code_key = code_model.default_calc_job_plugin.split(".")[-1]
178178
code_model.update(
179179
user_email=self.default_user_email,
180180
default_code=self.default_codes.get(code_key, {}).get("code"),
181+
filter_codes_for_user=filter_codes_for_user,
181182
refresh=True,
182183
)
183184

tests/test_codes.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,30 @@ def condition(params):
136136
model.input_parameters = {"use_this_code": False}
137137
assert not model.get_model("test").is_active
138138
assert panel.code_widgets["test"].layout.display == "none"
139+
140+
141+
def test_filter_codes_for_user(app: Wizard, aiida_computer_ssh, aiida_code_installed):
142+
unconfigured_computer = aiida_computer_ssh(
143+
label="unconfigured_computer",
144+
configure=False,
145+
)
146+
new_code = aiida_code_installed(
147+
default_calc_job_plugin="quantumespresso.pw",
148+
computer=unconfigured_computer,
149+
).store()
150+
151+
global_model = app.submit_model.get_model("global")
152+
153+
# In general, we filter out unconfigured codes so the user can't submit with them
154+
app.submit_model.refresh_codes(filter_codes_for_user=True)
155+
assert new_code.uuid not in [
156+
code_uuid
157+
for _, code_uuid in global_model.get_model("quantumespresso__pw").options
158+
]
159+
160+
# We allow for these codes when loading from a process, since the user can no longer submit
161+
app.submit_model.refresh_codes(filter_codes_for_user=False)
162+
assert new_code.uuid in [
163+
code_uuid
164+
for _, code_uuid in global_model.get_model("quantumespresso__pw").options
165+
]

0 commit comments

Comments
 (0)