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 @@
-
-
+
Predict Pathway
diff --git a/templates/index/index.html b/templates/index/index.html
index 1390fcf7..fe83f845 100644
--- a/templates/index/index.html
+++ b/templates/index/index.html
@@ -103,6 +103,7 @@
var textSmiles = $('#index-form-text-input').val().trim();
if (textSmiles === '') {
+ $(this).prop("disabled", false);
return;
}
diff --git a/templates/modals/objects/generic_set_scenario_modal.html b/templates/modals/objects/generic_set_scenario_modal.html
index d21e15a4..f4616a75 100644
--- a/templates/modals/objects/generic_set_scenario_modal.html
+++ b/templates/modals/objects/generic_set_scenario_modal.html
@@ -64,6 +64,9 @@
$('#set_scenario_modal_form_submit').on('click', function (e) {
e.preventDefault();
+ if ($('##scenario-select').val().length == 0) {
+ $('##scenario-select').val([''])
+ }
$('#set_scenario_modal_form').submit();
});
});
diff --git a/templates/objects/model.html b/templates/objects/model.html
index 2c859780..b64bee39 100644
--- a/templates/objects/model.html
+++ b/templates/objects/model.html
@@ -315,12 +315,18 @@
$("#predict-button").on("click", function (e) {
e.preventDefault();
+ clear("predictResultTable");
+
data = {
"smiles": $("#smiles-to-predict").val(),
"classify": "ILikeCats!"
}
- clear("predictResultTable");
+ if (data["smiles"].trim() === "") {
+ $("#predictResultTable").addClass("alert alert-danger");
+ $("#predictResultTable").append("Please enter a SMILES string to predict!");
+ return;
+ }
makeLoadingGif("#predictLoading", "{% static '/images/wait.gif' %}");
$.ajax({
@@ -332,17 +338,17 @@
$("#predictLoading").empty();
handlePredictionResponse(data);
} catch (error) {
- console.log("Error");
+
$("#predictLoading").empty();
$("#predictResultTable").addClass("alert alert-danger");
- $("#predictResultTable").append("Error while processing request :/");
+ $("#predictResultTable").append("Error while processing response :/");
}
},
- error: function (jqXHR, textStatus, errorThrown) {
+ error: function (jqXHR, textStatus, errorThrown, x) {
$("#predictLoading").empty();
$("#predictResultTable").addClass("alert alert-danger");
- $("#predictResultTable").append("Error while processing request :/");
- }
+ $("#predictResultTable").append(jqXHR.responseJSON.error);
+ },
});
});
}
@@ -351,12 +357,20 @@
$("#assess-button").on("click", function (e) {
e.preventDefault();
+ clear("appDomainAssessmentResultTable");
+
data = {
"smiles": $("#smiles-to-assess").val(),
"app-domain-assessment": "ILikeCats!"
}
- clear("appDomainAssessmentResultTable");
+ if (data["smiles"].trim() === "") {
+ $("#appDomainAssessmentResultTable").addClass("alert alert-danger");
+ $("#appDomainAssessmentResultTable").append("Please enter a SMILES string to predict!");
+ return;
+ }
+
+
makeLoadingGif("#appDomainLoading", "{% static '/images/wait.gif' %}");
$.ajax({
@@ -369,16 +383,15 @@
handleAssessmentResponse("{% url 'depict' %}", data);
console.log(data);
} catch (error) {
- console.log("Error");
$("#appDomainLoading").empty();
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
- $("#appDomainAssessmentResultTable").append("Error while processing request :/");
+ $("#appDomainAssessmentResultTable").append("Error while processing response :/");
}
},
error: function (jqXHR, textStatus, errorThrown) {
$("#appDomainLoading").empty();
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
- $("#appDomainAssessmentResultTable").append("Error while processing request :/");
+ $("#appDomainAssessmentResultTable").append(jqXHR.responseJSON.error);
}
});
});
diff --git a/tests/views/test_model_views.py b/tests/views/test_model_views.py
new file mode 100644
index 00000000..36ca4cf6
--- /dev/null
+++ b/tests/views/test_model_views.py
@@ -0,0 +1,113 @@
+from django.test import TestCase, override_settings
+from django.urls import reverse
+from django.conf import settings as s
+
+from epdb.logic import UserManager, PackageManager
+from epdb.models import Pathway, Edge, Package, User
+
+
+@override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models")
+class PathwayViewTest(TestCase):
+ fixtures = ["test_fixtures_incl_model.jsonl.gz"]
+
+ @classmethod
+ def setUpClass(cls):
+ super(PathwayViewTest, cls).setUpClass()
+ cls.user1 = UserManager.create_user("user1", "user1@envipath.com", "SuperSafe",
+ set_setting=True, add_to_group=True, is_active=True)
+ cls.user1_default_package = cls.user1.default_package
+ cls.model_package = Package.objects.get(name='Fixtures')
+
+ def setUp(self):
+ self.client.force_login(self.user1)
+
+ def test_predict(self):
+ self.client.force_login(User.objects.get(username="admin"))
+ response = self.client.get(
+ reverse("package model detail", kwargs={
+ 'package_uuid': str(self.model_package.uuid),
+ 'model_uuid': str(self.model_package.models.first().uuid)
+ }), {
+ 'classify': 'ILikeCats!',
+ 'smiles': 'CCN(CC)C(=O)C1=CC(=CC=C1)CO',
+ }
+ )
+
+ expected = [
+ {
+ 'products': [
+ [
+ 'O=C(O)C1=CC(CO)=CC=C1',
+ 'CCNCC'
+ ]
+ ],
+ 'probability': 0.25,
+ 'btrule': {
+ 'url': 'http://localhost:8000/package/1869d3f0-60bb-41fd-b6f8-afa75ffb09d3/simple-ambit-rule/0e6e9290-b658-4450-b291-3ec19fa19206',
+ 'name': 'bt0430-4011'
+ }
+ }, {
+ 'products': [
+ [
+ 'CCNC(=O)C1=CC(CO)=CC=C1',
+ 'CC=O'
+ ]
+ ], 'probability': 0.0,
+ 'btrule': {
+ 'url': 'http://localhost:8000/package/1869d3f0-60bb-41fd-b6f8-afa75ffb09d3/simple-ambit-rule/27a3a353-0b66-4228-bd16-e407949e90df',
+ 'name': 'bt0243-4301'
+ }
+ }, {
+ 'products': [
+ [
+ 'CCN(CC)C(=O)C1=CC(C=O)=CC=C1'
+ ]
+ ], 'probability': 0.75,
+ 'btrule': {
+ 'url': 'http://localhost:8000/package/1869d3f0-60bb-41fd-b6f8-afa75ffb09d3/simple-ambit-rule/2f2e0c39-e109-4836-959f-2bda2524f022',
+ 'name': 'bt0001-3568'
+ }
+ }
+ ]
+
+ actual = response.json()
+ self.assertEqual(actual, expected)
+
+ response = self.client.get(
+ reverse("package model detail", kwargs={
+ 'package_uuid': str(self.model_package.uuid),
+ 'model_uuid': str(self.model_package.models.first().uuid)
+ }), {
+ 'classify': 'ILikeCats!',
+ 'smiles': '',
+ }
+ )
+
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(response.json()['error'], 'Received empty SMILES')
+
+ response = self.client.get(
+ reverse("package model detail", kwargs={
+ 'package_uuid': str(self.model_package.uuid),
+ 'model_uuid': str(self.model_package.models.first().uuid)
+ }), {
+ 'classify': 'ILikeCats!',
+ 'smiles': ' ', # Input should be stripped
+ }
+ )
+
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(response.json()['error'], 'Received empty SMILES')
+
+ response = self.client.get(
+ reverse("package model detail", kwargs={
+ 'package_uuid': str(self.model_package.uuid),
+ 'model_uuid': str(self.model_package.models.first().uuid)
+ }), {
+ 'classify': 'ILikeCats!',
+ 'smiles': 'RandomInput',
+ }
+ )
+
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(response.json()['error'], '"RandomInput" is not a valid SMILES')
diff --git a/tests/views/test_package_views.py b/tests/views/test_package_views.py
index 85b64b38..77bbf724 100644
--- a/tests/views/test_package_views.py
+++ b/tests/views/test_package_views.py
@@ -178,3 +178,15 @@ class PackageViewTest(TestCase):
response = self.client.get(package_url)
self.assertEqual(response.status_code, 404)
+
+ def test_delete_default_package(self):
+ self.client.force_login(self.user1)
+ # Try to delete the default package
+ response = self.client.post(self.user1.default_package.url, {
+ "hidden": "delete"
+ })
+
+ self.assertEqual(response.status_code, 400)
+ self.assertTrue(f'You cannot delete the default package. '
+ f'If you want to delete this package you have to '
+ f'set another default package first' in response.content.decode())
diff --git a/tests/views/test_rule_views.py b/tests/views/test_rule_views.py
index dc898a5f..cb9318f5 100644
--- a/tests/views/test_rule_views.py
+++ b/tests/views/test_rule_views.py
@@ -161,7 +161,8 @@ class RuleViewTest(TestCase):
'package_uuid': str(r.package.uuid),
'rule_uuid': str(r.uuid)
}), {
- "selected-scenarios": []
+ # We have to set an empty string to avoid that the parameter is removed
+ "selected-scenarios": ""
}
)