forked from enviPath/enviPy
[Fix] Remove all Scenarios, catch empty SMILES, prevent default Package delete (#134)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch> Reviewed-on: enviPath/enviPy#134
This commit is contained in:
@ -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,10 +748,22 @@ 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']
|
||||
if classify or ad_assessment:
|
||||
smiles = request.GET.get('smiles', '').strip()
|
||||
|
||||
# Check if smiles is non empty and valid
|
||||
if smiles == '':
|
||||
return JsonResponse({'error': 'Received empty SMILES'}, status=400)
|
||||
|
||||
try:
|
||||
stand_smiles = FormatConverter.standardize(smiles)
|
||||
except ValueError as e:
|
||||
return JsonResponse({'error': f'"{smiles}" is not a valid SMILES'}, status=400)
|
||||
|
||||
if classify:
|
||||
pred_res = current_model.predict(stand_smiles)
|
||||
res = []
|
||||
|
||||
@ -765,9 +782,7 @@ def package_model(request, package_uuid, model_uuid):
|
||||
|
||||
return JsonResponse(res, safe=False)
|
||||
|
||||
elif request.GET.get('app-domain-assessment', False):
|
||||
smiles = request.GET['smiles']
|
||||
stand_smiles = FormatConverter.standardize(smiles)
|
||||
else:
|
||||
app_domain_assessment = current_model.app_domain.assess(stand_smiles)[0]
|
||||
return JsonResponse(app_domain_assessment, safe=False)
|
||||
|
||||
@ -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()
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@ -6,8 +6,7 @@
|
||||
<h4 class="alert-heading">{{ error_message }}</h4>
|
||||
<hr>
|
||||
<p class="mb-0">
|
||||
{{ error_detail }}<br>
|
||||
The error was logged and will be investigated.
|
||||
{{ error_detail }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@ -89,7 +89,7 @@
|
||||
<div class="collapse navbar-collapse collapse-framework navbar-collapse-framework" id="navbarCollapse">
|
||||
<ul class="nav navbar-nav navbar-nav-framework">
|
||||
<li>
|
||||
<a class="button" data-toggle="modal" data-target="#predict_modal">
|
||||
<a href="#" data-toggle="modal" data-target="#predict_modal">
|
||||
Predict Pathway
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@ -103,6 +103,7 @@
|
||||
var textSmiles = $('#index-form-text-input').val().trim();
|
||||
|
||||
if (textSmiles === '') {
|
||||
$(this).prop("disabled", false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
113
tests/views/test_model_views.py
Normal file
113
tests/views/test_model_views.py
Normal file
@ -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')
|
||||
@ -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())
|
||||
|
||||
@ -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": ""
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user