From 3f5bb76633aef23dd219227fc9b0cb4b06464ebf Mon Sep 17 00:00:00 2001 From: jebus Date: Tue, 30 Sep 2025 19:10:57 +1300 Subject: [PATCH] [Fix] Remove all Scenarios, catch empty SMILES, prevent default Package delete (#134) Co-authored-by: Tim Lorsbach Reviewed-on: https://git.envipath.com/enviPath/enviPy/pulls/134 --- epdb/views.py | 98 +++++++++------ templates/errors/error.html | 3 +- templates/framework.html | 2 +- templates/index/index.html | 1 + .../objects/generic_set_scenario_modal.html | 3 + templates/objects/model.html | 33 +++-- tests/views/test_model_views.py | 113 ++++++++++++++++++ tests/views/test_package_views.py | 12 ++ tests/views/test_rule_views.py | 3 +- 9 files changed, 215 insertions(+), 53 deletions(-) create mode 100644 tests/views/test_model_views.py diff --git a/epdb/views.py b/epdb/views.py index 2c2cb51a..f89a2af4 100644 --- a/epdb/views.py +++ b/epdb/views.py @@ -233,6 +233,11 @@ def breadcrumbs(first_level_object=None, second_level_namespace=None, second_lev def set_scenarios(current_user, attach_object, scenario_urls: List[str]): scens = [] for scenario_url in scenario_urls: + + # As empty lists will be removed in POST request well send [''] + if scenario_url == '': + continue + package = PackageManager.get_package_by_url(current_user, scenario_url) scen = Scenario.objects.get(package=package, uuid=scenario_url.split('/')[-1]) scens.append(scen) @@ -743,33 +748,43 @@ def package_model(request, package_uuid, model_uuid): current_model = EPModel.objects.get(package=current_package, uuid=model_uuid) if request.method == 'GET': + classify = request.GET.get('classify', False) + ad_assessment = request.GET.get('app-domain-assessment', False) - if request.GET.get('classify', False): - smiles = request.GET['smiles'] - stand_smiles = FormatConverter.standardize(smiles) - pred_res = current_model.predict(stand_smiles) - res = [] + if classify or ad_assessment: + smiles = request.GET.get('smiles', '').strip() - for pr in pred_res: - if len(pr) > 0: - products = [] - for prod_set in pr.product_sets: - logger.debug(f"Checking {prod_set}") - products.append(tuple([x for x in prod_set])) + # Check if smiles is non empty and valid + if smiles == '': + return JsonResponse({'error': 'Received empty SMILES'}, status=400) - res.append({ - 'products': list(set(products)), - 'probability': pr.probability, - 'btrule': {k: getattr(pr.rule, k) for k in ['url', 'name']} if pr.rule is not None else None - }) + try: + stand_smiles = FormatConverter.standardize(smiles) + except ValueError as e: + return JsonResponse({'error': f'"{smiles}" is not a valid SMILES'}, status=400) - return JsonResponse(res, safe=False) + if classify: + pred_res = current_model.predict(stand_smiles) + res = [] - elif request.GET.get('app-domain-assessment', False): - smiles = request.GET['smiles'] - stand_smiles = FormatConverter.standardize(smiles) - app_domain_assessment = current_model.app_domain.assess(stand_smiles)[0] - return JsonResponse(app_domain_assessment, safe=False) + for pr in pred_res: + if len(pr) > 0: + products = [] + for prod_set in pr.product_sets: + logger.debug(f"Checking {prod_set}") + products.append(tuple([x for x in prod_set])) + + res.append({ + 'products': list(set(products)), + 'probability': pr.probability, + 'btrule': {k: getattr(pr.rule, k) for k in ['url', 'name']} if pr.rule is not None else None + }) + + return JsonResponse(res, safe=False) + + else: + app_domain_assessment = current_model.app_domain.assess(stand_smiles)[0] + return JsonResponse(app_domain_assessment, safe=False) context = get_base_context(request) context['title'] = f'enviPath - {current_package.name} - {current_model.name}' @@ -860,6 +875,11 @@ def package(request, package_uuid): if hidden := request.POST.get('hidden', None): if hidden == 'delete': + + if current_user.default_package == current_package: + return error(request, f'Package "{current_package.name}" is the default and cannot be deleted!', + 'You cannot delete the default package. If you want to delete this package you have to set another default package first.') + logger.debug(current_package.delete()) return redirect(s.SERVER_URL + '/package') elif hidden == 'publish-package': @@ -1017,9 +1037,9 @@ def package_compound(request, package_uuid, compound_uuid): else: return HttpResponseBadRequest() - selected_scenarios = request.POST.getlist('selected-scenarios') + if 'selected-scenarios' in request.POST: + selected_scenarios = request.POST.getlist('selected-scenarios') - if selected_scenarios is not None: set_scenarios(current_user, current_compound, selected_scenarios) return redirect(current_compound.url) @@ -1126,9 +1146,9 @@ def package_compound_structure(request, package_uuid, compound_uuid, structure_u else: return HttpResponseBadRequest() - selected_scenarios = request.POST.getlist('selected-scenarios') + if 'selected-scenarios' in request.POST: + selected_scenarios = request.POST.getlist('selected-scenarios') - if selected_scenarios is not None: set_scenarios(current_user, current_structure, selected_scenarios) return redirect(current_structure.url) @@ -1253,9 +1273,9 @@ def package_rule(request, package_uuid, rule_uuid): else: return HttpResponseBadRequest() - selected_scenarios = request.POST.getlist('selected-scenarios') + if 'selected-scenarios' in request.POST: + selected_scenarios = request.POST.getlist('selected-scenarios') - if selected_scenarios is not None: set_scenarios(current_user, current_rule, selected_scenarios) return redirect(current_rule.url) @@ -1356,9 +1376,9 @@ def package_reaction(request, package_uuid, reaction_uuid): else: return HttpResponseBadRequest() - selected_scenarios = request.POST.getlist('selected-scenarios') + if 'selected-scenarios' in request.POST: + selected_scenarios = request.POST.getlist('selected-scenarios') - if selected_scenarios is not None: set_scenarios(current_user, current_reaction, selected_scenarios) return redirect(current_reaction.url) @@ -1421,10 +1441,10 @@ def package_pathways(request, package_uuid): name = request.POST.get('name') description = request.POST.get('description') - pw_mode = request.POST.get('predict', 'predict') - smiles = request.POST.get('smiles') + pw_mode = request.POST.get('predict', 'predict').strip() + smiles = request.POST.get('smiles', '').strip() - if smiles is None or smiles.strip() == '': + if 'smiles' in request.POST and smiles == '': return error(request, "Pathway prediction failed!", "Pathway prediction failed due to missing or empty SMILES") @@ -1543,9 +1563,9 @@ def package_pathway(request, package_uuid, pathway_uuid): else: return HttpResponseBadRequest() - selected_scenarios = request.POST.getlist('selected-scenarios') + if 'selected-scenarios' in request.POST: + selected_scenarios = request.POST.getlist('selected-scenarios') - if selected_scenarios is not None: set_scenarios(current_user, current_pathway, selected_scenarios) return redirect(current_pathway.url) @@ -1689,9 +1709,9 @@ def package_pathway_node(request, package_uuid, pathway_uuid, node_uuid): else: return HttpResponseBadRequest() - selected_scenarios = request.POST.getlist('selected-scenarios') + if 'selected-scenarios' in request.POST: + selected_scenarios = request.POST.getlist('selected-scenarios') - if selected_scenarios is not None: set_scenarios(current_user, current_node, selected_scenarios) return redirect(current_node.url) @@ -1798,9 +1818,9 @@ def package_pathway_edge(request, package_uuid, pathway_uuid, edge_uuid): current_edge.delete() return redirect(current_pathway.url) - selected_scenarios = request.POST.getlist('selected-scenarios') + if 'selected-scenarios' in request.POST: + selected_scenarios = request.POST.getlist('selected-scenarios') - if selected_scenarios is not None: set_scenarios(current_user, current_edge, selected_scenarios) return redirect(current_edge.url) diff --git a/templates/errors/error.html b/templates/errors/error.html index 40db5623..11a35e03 100644 --- a/templates/errors/error.html +++ b/templates/errors/error.html @@ -6,8 +6,7 @@

{{ error_message }}


- {{ error_detail }}
- The error was logged and will be investigated. + {{ error_detail }}

diff --git a/templates/framework.html b/templates/framework.html index 68bf4468..e3f75176 100644 --- a/templates/framework.html +++ b/templates/framework.html @@ -89,7 +89,7 @@