forked from enviPath/enviPy
[Feature] Scenario Creation (#78)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch> Reviewed-on: enviPath/enviPy#78
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
import re
|
||||
import logging
|
||||
import json
|
||||
from typing import Union, List, Optional, Set, Dict, Any
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
@ -552,11 +553,13 @@ class PackageManager(object):
|
||||
|
||||
try:
|
||||
res = AdditionalInformationConverter.convert(name, addinf_data)
|
||||
res_cls_name = res.__class__.__name__
|
||||
ai_data = json.loads(res.model_dump_json())
|
||||
ai_data['uuid'] = f"{uuid4()}"
|
||||
new_add_inf[res_cls_name].append(ai_data)
|
||||
except:
|
||||
logger.error(f"Failed to convert {name} with {addinf_data}")
|
||||
|
||||
new_add_inf[name].append(res.model_dump_json())
|
||||
|
||||
scen.additional_information = new_add_inf
|
||||
scen.save()
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ from django.db import models, transaction
|
||||
from django.db.models import JSONField, Count, Q, QuerySet
|
||||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
from envipy_additional_information import EnviPyModel
|
||||
from model_utils.models import TimeStampedModel
|
||||
from polymorphic.models import PolymorphicModel
|
||||
from sklearn.metrics import precision_score, recall_score, jaccard_score
|
||||
@ -2292,7 +2293,8 @@ class ApplicabilityDomain(EnviPathModel):
|
||||
transformation = {
|
||||
'rule': rule_data,
|
||||
'reliability': rule_reliabilities[rule_idx],
|
||||
# TODO
|
||||
# We're setting it here to False, as we don't know whether "assess" is called during pathway
|
||||
# prediction or from Model Page. For persisted Nodes this field will be overwritten at runtime
|
||||
'is_predicted': False,
|
||||
'local_compatibility': local_compatibilities[rule_idx],
|
||||
'probability': preds[rule_idx].probability,
|
||||
@ -2407,27 +2409,88 @@ class Scenario(EnviPathModel):
|
||||
|
||||
@staticmethod
|
||||
@transaction.atomic
|
||||
def create(package, name, description, date, type, additional_information):
|
||||
def create(package: 'Package', name:str, description:str, scenario_date:str, scenario_type:str, additional_information: List['EnviPyModel']):
|
||||
s = Scenario()
|
||||
s.package = package
|
||||
|
||||
if name is None or name.strip() == '':
|
||||
name = f"Scenario {Scenario.objects.filter(package=package).count() + 1}"
|
||||
|
||||
s.name = name
|
||||
s.description = description
|
||||
s.date = date
|
||||
s.type = type
|
||||
s.additional_information = additional_information
|
||||
|
||||
if description is not None and description.strip() != '':
|
||||
s.description = description
|
||||
|
||||
if scenario_date is not None and scenario_date.strip() != '':
|
||||
s.scenario_date = scenario_date
|
||||
|
||||
if scenario_type is not None and scenario_type.strip() != '':
|
||||
s.scenario_type = scenario_type
|
||||
|
||||
add_inf = defaultdict(list)
|
||||
|
||||
for info in additional_information:
|
||||
cls_name = info.__class__.__name__
|
||||
ai_data = json.loads(info.model_dump_json())
|
||||
ai_data['uuid'] = f"{uuid4()}"
|
||||
add_inf[cls_name].append(ai_data)
|
||||
|
||||
|
||||
s.additional_information = add_inf
|
||||
|
||||
s.save()
|
||||
|
||||
return s
|
||||
|
||||
def add_additional_information(self, data):
|
||||
pass
|
||||
@transaction.atomic
|
||||
def add_additional_information(self, data: 'EnviPyModel'):
|
||||
cls_name = data.__class__.__name__
|
||||
ai_data = json.loads(data.model_dump_json())
|
||||
ai_data['uuid'] = f"{uuid4()}"
|
||||
|
||||
def remove_additional_information(self, data):
|
||||
pass
|
||||
if cls_name not in self.additional_information:
|
||||
self.additional_information[cls_name] = []
|
||||
|
||||
def set_additional_information(self, data):
|
||||
pass
|
||||
self.additional_information[cls_name].append(ai_data)
|
||||
self.save()
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def remove_additional_information(self, ai_uuid):
|
||||
found_type = None
|
||||
found_idx = -1
|
||||
|
||||
for k, vals in self.additional_information.items():
|
||||
for i, v in enumerate(vals):
|
||||
if v['uuid'] == ai_uuid:
|
||||
found_type = k
|
||||
found_idx = i
|
||||
break
|
||||
|
||||
if found_type is not None and found_idx >= 0:
|
||||
if len(self.additional_information[found_type]) == 1:
|
||||
del self.additional_information[k]
|
||||
else:
|
||||
self.additional_information[k].pop(found_idx)
|
||||
self.save()
|
||||
else:
|
||||
raise ValueError(f"Could not find additional information with uuid {ai_uuid}")
|
||||
|
||||
@transaction.atomic
|
||||
def set_additional_information(self, data: Dict[str, 'EnviPyModel']):
|
||||
new_ais = defaultdict(list)
|
||||
for k, vals in data.items():
|
||||
for v in vals:
|
||||
ai_data = json.loads(v.model_dump_json())
|
||||
if hasattr(v, 'uuid'):
|
||||
ai_data['uuid'] = str(v.uuid)
|
||||
else:
|
||||
ai_data['uuid'] = str(uuid4())
|
||||
|
||||
new_ais[k].append(ai_data)
|
||||
|
||||
self.additional_information = new_ais
|
||||
self.save()
|
||||
|
||||
def get_additional_information(self):
|
||||
from envipy_additional_information import NAME_MAPPING
|
||||
@ -2437,7 +2500,14 @@ class Scenario(EnviPathModel):
|
||||
continue
|
||||
|
||||
for v in vals:
|
||||
yield NAME_MAPPING[k](**json.loads(v))
|
||||
# Per default additional fields are ignored
|
||||
MAPPING = {c.__name__: c for c in NAME_MAPPING.values()}
|
||||
inst = MAPPING[k](**v)
|
||||
# Add uuid to uniquely identify objects for manipulation
|
||||
if 'uuid' in v:
|
||||
inst.__dict__['uuid'] = v['uuid']
|
||||
|
||||
yield inst
|
||||
|
||||
|
||||
class UserSettingPermission(Permission):
|
||||
|
||||
113
epdb/views.py
113
epdb/views.py
@ -4,15 +4,14 @@ from typing import List, Dict, Any
|
||||
|
||||
from django.conf import settings as s
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db.models import F, Value
|
||||
from django.db.models.fields import CharField
|
||||
from django.db.models.functions import Concat
|
||||
from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest
|
||||
from django.shortcuts import render, redirect
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from envipy_additional_information import NAME_MAPPING
|
||||
|
||||
from utilities.chem import FormatConverter, IndigoUtils
|
||||
from utilities.decorators import package_permission_required
|
||||
from utilities.misc import HTMLGenerator
|
||||
from .logic import GroupManager, PackageManager, UserManager, SettingManager, SearchManager, EPDBURLParser
|
||||
from .models import Package, GroupPackagePermission, Group, CompoundStructure, Compound, Reaction, Rule, Pathway, Node, \
|
||||
EPModel, EnviFormer, MLRelativeReasoning, RuleBaseRelativeReasoning, Scenario, SimpleAmbitRule, APIToken, \
|
||||
@ -1726,7 +1725,7 @@ def package_scenarios(request, package_uuid):
|
||||
|
||||
if request.method == 'GET':
|
||||
|
||||
if 'application/json' in request.META.get('HTTP_ACCEPT') and not request.GET.get('all', None):
|
||||
if 'application/json' in request.META.get('HTTP_ACCEPT') and not request.GET.get('all', False):
|
||||
scens = Scenario.objects.filter(package=current_package).order_by('name')
|
||||
res = [{'name': s.name, 'url': s.url, 'uuid': s.uuid} for s in scens]
|
||||
return JsonResponse(res, safe=False)
|
||||
@ -1757,8 +1756,57 @@ def package_scenarios(request, package_uuid):
|
||||
context['reviewed_objects'] = reviewed_scenario_qs
|
||||
context['unreviewed_objects'] = unreviewed_scenario_qs
|
||||
|
||||
return render(request, 'collections/objects_list.html', context)
|
||||
from envipy_additional_information import SLUDGE_ADDITIONAL_INFORMATION, SOIL_ADDITIONAL_INFORMATION, \
|
||||
SEDIMENT_ADDITIONAL_INFORMATION
|
||||
context['scenario_types'] = {
|
||||
'Soil Data': {
|
||||
'name': 'soil',
|
||||
'widgets': [HTMLGenerator.generate_html(ai, prefix=f'soil_{0}') for ai in
|
||||
[x for s in SOIL_ADDITIONAL_INFORMATION.values() for x in s]]
|
||||
},
|
||||
'Sludge Data': {
|
||||
'name': 'sludge',
|
||||
'widgets': [HTMLGenerator.generate_html(ai, prefix=f'sludge_{0}') for ai in
|
||||
[x for s in SLUDGE_ADDITIONAL_INFORMATION.values() for x in s]]
|
||||
},
|
||||
'Water-Sediment System Data': {
|
||||
'name': 'sediment',
|
||||
'widgets': [HTMLGenerator.generate_html(ai, prefix=f'sediment_{0}') for ai in
|
||||
[x for s in SEDIMENT_ADDITIONAL_INFORMATION.values() for x in s]]
|
||||
}
|
||||
}
|
||||
|
||||
context['sludge_additional_information'] = SLUDGE_ADDITIONAL_INFORMATION
|
||||
context['soil_additional_information'] = SOIL_ADDITIONAL_INFORMATION
|
||||
context['sediment_additional_information'] = SEDIMENT_ADDITIONAL_INFORMATION
|
||||
|
||||
return render(request, 'collections/objects_list.html', context)
|
||||
elif request.method == 'POST':
|
||||
|
||||
log_post_params(request)
|
||||
|
||||
scenario_name = request.POST.get('scenario-name')
|
||||
scenario_description = request.POST.get('scenario-description')
|
||||
scenario_date_year = request.POST.get('scenario-date-year')
|
||||
scenario_date_month = request.POST.get('scenario-date-month')
|
||||
scenario_date_day = request.POST.get('scenario-date-day')
|
||||
|
||||
scenario_date = scenario_date_year
|
||||
if scenario_date_month is not None and scenario_date_month.strip() != '':
|
||||
scenario_date += f'-{int(scenario_date_month):02d}'
|
||||
if scenario_date_day is not None and scenario_date_day.strip() != '':
|
||||
scenario_date += f'-{int(scenario_date_day):02d}'
|
||||
|
||||
scenario_type = request.POST.get('scenario-type')
|
||||
|
||||
additional_information = HTMLGenerator.build_models(request.POST.dict())
|
||||
additional_information = [x for s in additional_information.values() for x in s]
|
||||
|
||||
s = Scenario.create(current_package, name=scenario_name, description=scenario_description,
|
||||
scenario_date=scenario_date, scenario_type=scenario_type,
|
||||
additional_information=additional_information)
|
||||
|
||||
return redirect(s.url)
|
||||
else:
|
||||
return HttpResponseNotAllowed(['GET', ])
|
||||
|
||||
@ -1779,10 +1827,63 @@ def package_scenario(request, package_uuid, scenario_uuid):
|
||||
|
||||
context['scenario'] = current_scenario
|
||||
|
||||
available_add_infs = []
|
||||
for add_inf in NAME_MAPPING.values():
|
||||
available_add_infs.append({
|
||||
'display_name': add_inf.property_name(None),
|
||||
'name': add_inf.__name__,
|
||||
'widget': HTMLGenerator.generate_html(add_inf, prefix=f'{0}')
|
||||
})
|
||||
context['available_additional_information'] = available_add_infs
|
||||
|
||||
context['update_widgets'] = [HTMLGenerator.generate_html(ai, prefix=f'{i}') for i, ai in enumerate(current_scenario.get_additional_information())]
|
||||
|
||||
return render(request, 'objects/scenario.html', context)
|
||||
|
||||
elif request.method == 'POST':
|
||||
|
||||
log_post_params(request)
|
||||
|
||||
if hidden := request.POST.get('hidden', None):
|
||||
|
||||
if hidden == 'delete':
|
||||
current_scenario.delete()
|
||||
return redirect(current_package.url + '/scenario')
|
||||
elif hidden == 'delete-additional-information':
|
||||
uuid = request.POST.get('uuid')
|
||||
current_scenario.remove_additional_information(uuid)
|
||||
return redirect(current_scenario.url)
|
||||
elif hidden == 'delete-all-additional-information':
|
||||
current_scenario.additional_information = dict()
|
||||
current_scenario.save()
|
||||
return redirect(current_scenario.url)
|
||||
elif hidden == 'set-additional-information':
|
||||
ais = HTMLGenerator.build_models(request.POST.dict())
|
||||
|
||||
if s.DEBUG:
|
||||
logger.info(ais)
|
||||
|
||||
current_scenario.set_additional_information(ais)
|
||||
return redirect(current_scenario.url)
|
||||
elif hidden == 'add-additional-information':
|
||||
ais = HTMLGenerator.build_models(request.POST.dict())
|
||||
|
||||
if len(ais.keys()) != 1:
|
||||
raise ValueError('Only one additional information field can be added at a time.')
|
||||
|
||||
ai = list(ais.values())[0][0]
|
||||
|
||||
if s.DEBUG:
|
||||
logger.info(ais)
|
||||
|
||||
current_scenario.add_additional_information(ai)
|
||||
return redirect(current_scenario.url)
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
else:
|
||||
return HttpResponseNotAllowed(['GET', ])
|
||||
return HttpResponseNotAllowed(['GET', 'POST'])
|
||||
|
||||
|
||||
##############
|
||||
|
||||
Reference in New Issue
Block a user