feature/additional_information (#30)

Fixes #12

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#30
This commit is contained in:
2025-07-19 08:10:40 +12:00
parent 4fff78541b
commit 49e02ed97d
11 changed files with 534 additions and 344 deletions

View File

@ -1,6 +1,6 @@
from django.contrib import admin from django.contrib import admin
from .models import User, Group, UserPackagePermission, GroupPackagePermission, Setting, SimpleAmbitRule from .models import User, Group, UserPackagePermission, GroupPackagePermission, Setting, SimpleAmbitRule, Scenario
class UserAdmin(admin.ModelAdmin): class UserAdmin(admin.ModelAdmin):
@ -22,15 +22,19 @@ class GroupPackagePermissionAdmin(admin.ModelAdmin):
class SettingAdmin(admin.ModelAdmin): class SettingAdmin(admin.ModelAdmin):
pass pass
admin.site.register(User, UserAdmin)
admin.site.register(Group, GroupAdmin)
admin.site.register(UserPackagePermission, UserPackagePermissionAdmin)
admin.site.register(GroupPackagePermission, GroupPackagePermissionAdmin)
admin.site.register(Setting, SettingAdmin)
class SimpleAmbitRuleAdmin(admin.ModelAdmin): class SimpleAmbitRuleAdmin(admin.ModelAdmin):
pass pass
class ScenarioAdmin(admin.ModelAdmin):
pass
admin.site.register(User, UserAdmin)
admin.site.register(Group, GroupAdmin)
admin.site.register(UserPackagePermission, UserPackagePermissionAdmin)
admin.site.register(GroupPackagePermission, GroupPackagePermissionAdmin)
admin.site.register(Setting, SettingAdmin)
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin) admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
admin.site.register(Scenario, ScenarioAdmin)

View File

@ -255,6 +255,382 @@ class PackageManager(object):
else: else:
_ = perm_cls.objects.update_or_create(defaults={'permission': new_perm}, **data) _ = perm_cls.objects.update_or_create(defaults={'permission': new_perm}, **data)
@staticmethod
@transaction.atomic
def import_package(data: dict, owner: User, keep_ids=False):
from uuid import UUID, uuid4
from datetime import datetime
from collections import defaultdict
from .models import Package, Compound, CompoundStructure, SimpleRule, SimpleAmbitRule, SimpleRDKitRule, \
ParallelRule, SequentialRule, SequentialRuleOrdering, Reaction, Pathway, Node, Edge, Scenario
from envipy_additional_information import AdditionalInformationConverter
pack = Package()
pack.uuid = UUID(data['id'].split('/')[-1]) if keep_ids else uuid4()
pack.name = '{} - {}'.format(data['name'], datetime.now().strftime('%Y-%m-%d %H:%M'))
pack.reviewed = True if data['reviewStatus'] == 'reviewed' else False
pack.description = data['description']
pack.save()
up = UserPackagePermission()
up.user = owner
up.package = pack
up.permission = up.ALL[0]
up.save()
# Stores old_id to new_id
mapping = {}
# Stores new_scen_id to old_parent_scen_id
parent_mapping = {}
# Mapping old scen_id to old_obj_id
scen_mapping = defaultdict(list)
# Store Scenarios
for scenario in data['scenarios']:
scen = Scenario()
scen.package = pack
scen.uuid = UUID(scenario['id'].split('/')[-1]) if keep_ids else uuid4()
scen.name = scenario['name']
scen.description = scenario['description']
scen.scenario_type = scenario['type']
scen.scenario_date = scenario['date']
scen.additional_information = dict()
scen.save()
mapping[scenario['id']] = scen.uuid
new_add_inf = defaultdict(list)
# TODO Store AI...
for ex in scenario.get('additionalInformationCollection', {}).get('additionalInformation', []):
name = ex['name']
addinf_data = ex['data']
# park the parent scen id for now and link it later
if name == 'referringscenario':
parent_mapping[scen.uuid] = addinf_data
continue
# Broken eP Data
if name == 'initialmasssediment' and addinf_data == 'missing data':
continue
# TODO Enzymes arent ready yet
if name == 'enzyme':
continue
try:
res = AdditionalInformationConverter.convert(name, addinf_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()
print('Scenarios imported...')
# Store compounds and its structures
for compound in data['compounds']:
comp = Compound()
comp.package = pack
comp.uuid = UUID(compound['id'].split('/')[-1]) if keep_ids else uuid4()
comp.name = compound['name']
comp.description = compound['description']
comp.aliases = compound['aliases']
comp.save()
mapping[compound['id']] = comp.uuid
for scen in compound['scenarios']:
scen_mapping[scen['id']].append(comp)
default_structure = None
for structure in compound['structures']:
struc = CompoundStructure()
# struc.object_url = Command.get_id(structure, keep_ids)
struc.compound = comp
struc.uuid = UUID(structure['id'].split('/')[-1]) if keep_ids else uuid4()
struc.name = structure['name']
struc.description = structure['description']
struc.smiles = structure['smiles']
struc.save()
for scen in structure['scenarios']:
scen_mapping[scen['id']].append(struc)
mapping[structure['id']] = struc.uuid
if structure['id'] == compound['defaultStructure']['id']:
default_structure = struc
struc.save()
if default_structure is None:
raise ValueError('No default structure set')
comp.default_structure = default_structure
comp.save()
print('Compounds imported...')
# Store simple and parallel-rules
par_rules = []
seq_rules = []
for rule in data['rules']:
if rule['identifier'] == 'parallel-rule':
par_rules.append(rule)
continue
if rule['identifier'] == 'sequential-rule':
seq_rules.append(rule)
continue
r = SimpleAmbitRule()
r.uuid = UUID(rule['id'].split('/')[-1]) if keep_ids else uuid4()
r.package = pack
r.name = rule['name']
r.description = rule['description']
r.smirks = rule['smirks']
r.reactant_filter_smarts = rule.get('reactantFilterSmarts', None)
r.product_filter_smarts = rule.get('productFilterSmarts', None)
r.save()
mapping[rule['id']] = r.uuid
for scen in rule['scenarios']:
scen_mapping[scen['id']].append(r)
print("Par: ", len(par_rules))
print("Seq: ", len(seq_rules))
for par_rule in par_rules:
r = ParallelRule()
r.package = pack
r.uuid = UUID(par_rule['id'].split('/')[-1]) if keep_ids else uuid4()
r.name = par_rule['name']
r.description = par_rule['description']
r.save()
mapping[par_rule['id']] = r.uuid
for scen in par_rule['scenarios']:
scen_mapping[scen['id']].append(r)
for simple_rule in par_rule['simpleRules']:
if simple_rule['id'] in mapping:
r.simple_rules.add(SimpleRule.objects.get(uuid=mapping[simple_rule['id']]))
r.save()
for seq_rule in seq_rules:
r = SequentialRule()
r.package = pack
r.uuid = UUID(seq_rule['id'].split('/')[-1]) if keep_ids else uuid4()
r.name = seq_rule['name']
r.description = seq_rule['description']
r.save()
mapping[seq_rule['id']] = r.uuid
for scen in seq_rule['scenarios']:
scen_mapping[scen['id']].append(r)
for i, simple_rule in enumerate(seq_rule['simpleRules']):
sro = SequentialRuleOrdering()
sro.simple_rule = simple_rule
sro.sequential_rule = r
sro.order_index = i
sro.save()
# r.simple_rules.add(SimpleRule.objects.get(uuid=mapping[simple_rule['id']]))
r.save()
print('Rules imported...')
for reaction in data['reactions']:
r = Reaction()
r.package = pack
r.uuid = UUID(reaction['id'].split('/')[-1]) if keep_ids else uuid4()
r.name = reaction['name']
r.description = reaction['description']
r.medlinereferences = reaction['medlinereferences'],
r.multi_step = True if reaction['multistep'] == 'true' else False
r.save()
mapping[reaction['id']] = r.uuid
for scen in reaction['scenarios']:
scen_mapping[scen['id']].append(r)
for educt in reaction['educts']:
r.educts.add(CompoundStructure.objects.get(uuid=mapping[educt['id']]))
for product in reaction['products']:
r.products.add(CompoundStructure.objects.get(uuid=mapping[product['id']]))
if 'rules' in reaction:
for rule in reaction['rules']:
try:
r.rules.add(Rule.objects.get(uuid=mapping[rule['id']]))
except Exception as e:
print(f"Rule with id {rule['id']} not found!")
print(e)
r.save()
print('Reactions imported...')
for pathway in data['pathways']:
pw = Pathway()
pw.package = pack
pw.uuid = UUID(pathway['id'].split('/')[-1]) if keep_ids else uuid4()
pw.name = pathway['name']
pw.description = pathway['description']
pw.save()
mapping[pathway['id']] = pw.uuid
for scen in pathway['scenarios']:
scen_mapping[scen['id']].append(pw)
out_nodes_mapping = defaultdict(set)
root_node = None
for node in pathway['nodes']:
n = Node()
n.uuid = UUID(node['id'].split('/')[-1]) if keep_ids else uuid4()
n.name = node['name']
n.pathway = pw
n.depth = node['depth']
n.default_node_label = CompoundStructure.objects.get(uuid=mapping[node['defaultNodeLabel']['id']])
n.save()
mapping[node['id']] = n.uuid
for scen in node['scenarios']:
scen_mapping[scen['id']].append(n)
for node_label in node['nodeLabels']:
n.node_labels.add(CompoundStructure.objects.get(uuid=mapping[node_label['id']]))
n.save()
for out_edge in node['outEdges']:
out_nodes_mapping[n.uuid].add(out_edge)
for edge in pathway['edges']:
e = Edge()
e.uuid = UUID(edge['id'].split('/')[-1]) if keep_ids else uuid4()
e.name = edge['name']
e.pathway = pw
e.description = edge['description']
e.edge_label = Reaction.objects.get(uuid=mapping[edge['edgeLabel']['id']])
e.save()
mapping[edge['id']] = e.uuid
for scen in edge['scenarios']:
scen_mapping[scen['id']].append(e)
for start_node in edge['startNodes']:
e.start_nodes.add(Node.objects.get(uuid=mapping[start_node]))
for end_node in edge['endNodes']:
e.end_nodes.add(Node.objects.get(uuid=mapping[end_node]))
e.save()
for k, v in out_nodes_mapping.items():
n = Node.objects.get(uuid=k)
for v1 in v:
n.out_edges.add(Edge.objects.get(uuid=mapping[v1]))
n.save()
print('Pathways imported...')
# Linking Phase
for child, parent in parent_mapping.items():
child_obj = Scenario.objects.get(uuid=child)
parent_obj = Scenario.objects.get(uuid=mapping[parent])
child_obj.parent = parent_obj
child_obj.save()
for scen_id, objects in scen_mapping.items():
scen = Scenario.objects.get(uuid=mapping[scen_id])
for o in objects:
o.scenarios.add(scen)
o.save()
print("Scenarios linked...")
print('Import statistics:')
print('Package {} stored'.format(pack.url))
print('Imported {} compounds'.format(Compound.objects.filter(package=pack).count()))
print('Imported {} rules'.format(Rule.objects.filter(package=pack).count()))
print('Imported {} reactions'.format(Reaction.objects.filter(package=pack).count()))
print('Imported {} pathways'.format(Pathway.objects.filter(package=pack).count()))
print('Imported {} Scenarios'.format(Scenario.objects.filter(package=pack).count()))
print("Fixing Node depths...")
total_pws = Pathway.objects.filter(package=pack).count()
for p, pw in enumerate(Pathway.objects.filter(package=pack)):
print(pw.url)
in_count = defaultdict(lambda: 0)
out_count = defaultdict(lambda: 0)
for e in pw.edges:
# TODO check if this will remain
for react in e.start_nodes.all():
out_count[str(react.uuid)] += 1
for prod in e.end_nodes.all():
in_count[str(prod.uuid)] += 1
root_nodes = []
for n in pw.nodes:
num_parents = in_count[str(n.uuid)]
if num_parents == 0:
# must be a root node or unconnected node
if n.depth != 0:
n.depth = 0
n.save()
# Only root node may have children
if out_count[str(n.uuid)] > 0:
root_nodes.append(n)
levels = [root_nodes]
seen = set()
# Do a bfs to determine depths starting with level 0 a.k.a. root nodes
for i, level_nodes in enumerate(levels):
new_level = []
for n in level_nodes:
for e in n.out_edges.all():
for prod in e.end_nodes.all():
if str(prod.uuid) not in seen:
old_depth = prod.depth
if old_depth != i + 1:
print(f'updating depth from {old_depth} to {i + 1}')
prod.depth = i + 1
prod.save()
new_level.append(prod)
seen.add(str(n.uuid))
if new_level:
levels.append(new_level)
print(f'{p + 1}/{total_pws} fixed.')
return pack
class SettingManager(object): class SettingManager(object):
setting_pattern = re.compile(r".*/setting/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$") setting_pattern = re.compile(r".*/setting/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")

View File

@ -1,11 +1,11 @@
import json import json
from collections import defaultdict
from datetime import datetime
from uuid import UUID
from django.core.management.base import BaseCommand
from django.conf import settings as s from django.conf import settings as s
from epdb.models import * from django.core.management.base import BaseCommand
from django.db import transaction
from epdb.logic import UserManager, GroupManager, PackageManager, SettingManager from epdb.logic import UserManager, GroupManager, PackageManager, SettingManager
from epdb.models import UserSettingPermission, MLRelativeReasoning, EnviFormer, Permission, User
class Command(BaseCommand): class Command(BaseCommand):
@ -52,283 +52,7 @@ class Command(BaseCommand):
return anon, admin, g, jebus return anon, admin, g, jebus
def import_package(self, data, owner): def import_package(self, data, owner):
# Start import return PackageManager.import_package(data, owner, keep_ids=True)
pack = Package()
pack.uuid = UUID(data['id'].split('/')[-1])
pack.name = '{} - {}'.format(data['name'], datetime.now().strftime('%Y-%m-%d %H:%M'))
pack.reviewed = True if data['reviewStatus'] == 'reviewed' else False
pack.description = data['description']
pack.save()
up = UserPackagePermission()
up.user = owner
up.package = pack
up.permission = up.ALL[0]
up.save()
# Stores old_id to new_id
mapping = {}
# Store compounds and its structures
for compound in data['compounds']:
comp = Compound()
comp.package = pack
comp.uuid = UUID(compound['id'].split('/')[-1])
comp.name = compound['name']
comp.description = compound['description']
comp.aliases = compound['aliases']
comp.save()
mapping[compound['id']] = comp.uuid
default_structure = None
for structure in compound['structures']:
struc = CompoundStructure()
# struc.object_url = Command.get_id(structure, keep_ids)
struc.compound = comp
struc.uuid = UUID(structure['id'].split('/')[-1])
struc.name = structure['name']
struc.description = structure['description']
struc.smiles = structure['smiles']
struc.save()
mapping[structure['id']] = struc.uuid
if structure['id'] == compound['defaultStructure']['id']:
default_structure = struc
struc.save()
if default_structure is None:
raise ValueError('No default structure set')
comp.default_structure = default_structure
comp.save()
print('Compounds imported...')
# Store simple and parallel-rules
par_rules = []
seq_rules = []
for rule in data['rules']:
if rule['identifier'] == 'parallel-rule':
par_rules.append(rule)
continue
if rule['identifier'] == 'sequential-rule':
seq_rules.append(rule)
continue
r = SimpleAmbitRule()
r.uuid = UUID(rule['id'].split('/')[-1])
r.package = pack
r.name = rule['name']
r.description = rule['description']
r.smirks = rule['smirks']
r.reactant_filter_smarts = rule.get('reactantFilterSmarts', None)
r.product_filter_smarts = rule.get('productFilterSmarts', None)
r.save()
mapping[rule['id']] = r.uuid
print("Par: ", len(par_rules))
print("Seq: ", len(seq_rules))
for par_rule in par_rules:
r = ParallelRule()
r.package = pack
r.uuid = UUID(par_rule['id'].split('/')[-1])
r.name = par_rule['name']
r.description = par_rule['description']
r.save()
mapping[par_rule['id']] = r.uuid
for simple_rule in par_rule['simpleRules']:
if simple_rule['id'] in mapping:
r.simple_rules.add(SimpleRule.objects.get(uuid=mapping[simple_rule['id']]))
r.save()
for seq_rule in seq_rules:
r = SequentialRule()
r.package = pack
r.uuid = UUID(seq_rule['id'].split('/')[-1])
r.name = seq_rule['name']
r.description = seq_rule['description']
r.save()
mapping[seq_rule['id']] = r.uuid
# m1 = Membership(
# ... person=ringo,
# ... group=beatles,
# ... date_joined=date(1962, 8, 16),
# ... invite_reason="Needed a new drummer.",
# ... )
# >>> m1.save()
for i, simple_rule in enumerate(seq_rule['simpleRules']):
sro = SequentialRuleOrdering()
sro.simple_rule = simple_rule
sro.sequential_rule = r
sro.order_index = i
sro.save()
# r.simple_rules.add(SimpleRule.objects.get(uuid=mapping[simple_rule['id']]))
r.save()
print('Rules imported...')
for reaction in data['reactions']:
r = Reaction()
r.package = pack
r.uuid = UUID(reaction['id'].split('/')[-1])
r.name = reaction['name']
r.description = reaction['description']
r.medlinereferences = reaction['medlinereferences'],
r.multi_step = True if reaction['multistep'] == 'true' else False
r.save()
mapping[reaction['id']] = r.uuid
for educt in reaction['educts']:
r.educts.add(CompoundStructure.objects.get(uuid=mapping[educt['id']]))
for product in reaction['products']:
r.products.add(CompoundStructure.objects.get(uuid=mapping[product['id']]))
if 'rules' in reaction:
for rule in reaction['rules']:
try:
r.rules.add(Rule.objects.get(uuid=mapping[rule['id']]))
except Exception as e:
print(f"Rule with id {rule['id']} not found!")
print(e)
r.save()
print('Reactions imported...')
for pathway in data['pathways']:
pw = Pathway()
pw.package = pack
pw.uuid = UUID(pathway['id'].split('/')[-1])
pw.name = pathway['name']
pw.description = pathway['description']
pw.save()
mapping[pathway['id']] = pw.uuid
out_nodes_mapping = defaultdict(set)
root_node = None
for node in pathway['nodes']:
n = Node()
n.uuid = UUID(node['id'].split('/')[-1])
n.name = node['name']
n.pathway = pw
n.depth = node['depth']
n.default_node_label = CompoundStructure.objects.get(uuid=mapping[node['defaultNodeLabel']['id']])
n.save()
mapping[node['id']] = n.uuid
for node_label in node['nodeLabels']:
n.node_labels.add(CompoundStructure.objects.get(uuid=mapping[node_label['id']]))
n.save()
for out_edge in node['outEdges']:
out_nodes_mapping[n.uuid].add(out_edge)
for edge in pathway['edges']:
e = Edge()
e.uuid = UUID(edge['id'].split('/')[-1])
e.name = edge['name']
e.pathway = pw
e.description = edge['description']
e.edge_label = Reaction.objects.get(uuid=mapping[edge['edgeLabel']['id']])
e.save()
mapping[edge['id']] = e.uuid
for start_node in edge['startNodes']:
e.start_nodes.add(Node.objects.get(uuid=mapping[start_node]))
for end_node in edge['endNodes']:
e.end_nodes.add(Node.objects.get(uuid=mapping[end_node]))
e.save()
for k, v in out_nodes_mapping.items():
n = Node.objects.get(uuid=k)
for v1 in v:
n.out_edges.add(Edge.objects.get(uuid=mapping[v1]))
n.save()
print('Pathways imported...')
print('Import statistics:')
print('Package {} stored'.format(pack.url))
print('Imported {} compounds'.format(Compound.objects.filter(package=pack).count()))
print('Imported {} rules'.format(Rule.objects.filter(package=pack).count()))
print('Imported {} reactions'.format(Reaction.objects.filter(package=pack).count()))
print('Imported {} pathways'.format(Pathway.objects.filter(package=pack).count()))
print("Fixing Node depths...")
total_pws = Pathway.objects.filter(package=pack).count()
for p, pw in enumerate(Pathway.objects.filter(package=pack)):
print(pw.url)
in_count = defaultdict(lambda: 0)
out_count = defaultdict(lambda: 0)
for e in pw.edges:
# TODO check if this will remain
for react in e.start_nodes.all():
out_count[str(react.uuid)] += 1
for prod in e.end_nodes.all():
in_count[str(prod.uuid)] += 1
root_nodes = []
for n in pw.nodes:
num_parents = in_count[str(n.uuid)]
if num_parents == 0:
# must be a root node or unconnected node
if n.depth != 0:
n.depth = 0
n.save()
# Only root node may have children
if out_count[str(n.uuid)] > 0:
root_nodes.append(n)
levels = [root_nodes]
seen = set()
# Do a bfs to determine depths starting with level 0 a.k.a. root nodes
for i, level_nodes in enumerate(levels):
new_level = []
for n in level_nodes:
for e in n.out_edges.all():
for prod in e.end_nodes.all():
if str(prod.uuid) not in seen:
old_depth = prod.depth
if old_depth != i + 1:
print(f'updating depth from {old_depth} to {i + 1}')
prod.depth = i + 1
prod.save()
new_level.append(prod)
seen.add(str(n.uuid))
if new_level:
levels.append(new_level)
print(f'{p + 1}/{total_pws} fixed.')
return pack
def create_default_setting(self, owner, packages): def create_default_setting(self, owner, packages):
s = SettingManager.create_setting( s = SettingManager.create_setting(
@ -344,8 +68,6 @@ class Command(BaseCommand):
return s return s
@transaction.atomic @transaction.atomic
def handle(self, *args, **options): def handle(self, *args, **options):
# Create users # Create users
@ -356,12 +78,13 @@ class Command(BaseCommand):
'EAWAG-BBD.json', 'EAWAG-BBD.json',
'EAWAG-SOIL.json', 'EAWAG-SOIL.json',
'EAWAG-SLUDGE.json', 'EAWAG-SLUDGE.json',
'EAWAG-SEDIMENT.json',
] ]
mapping = {} mapping = {}
for p in packages: for p in packages:
print(f"Importing {p}...") print(f"Importing {p}...")
package_data = json.loads(open(s.BASE_DIR / 'fixtures' / p).read()) package_data = json.loads(open(s.BASE_DIR / 'fixtures' / 'packages' / '2025-07-18' / p).read())
imported_package = self.import_package(package_data, admin) imported_package = self.import_package(package_data, admin)
mapping[p.replace('.json', '')] = imported_package mapping[p.replace('.json', '')] = imported_package
@ -378,7 +101,8 @@ class Command(BaseCommand):
usp.save() usp.save()
# Create Model Package # Create Model Package
pack = PackageManager.create_package(admin, "Public Prediction Models", "Package to make Prediction Models publicly available") pack = PackageManager.create_package(admin, "Public Prediction Models",
"Package to make Prediction Models publicly available")
pack.reviewed = True pack.reviewed = True
pack.save() pack.save()

View File

@ -0,0 +1,27 @@
from django.core.management.base import BaseCommand
from epdb.logic import PackageManager
from epdb.models import *
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
'--data',
type=str,
help='Path of the Package to import.',
required=True,
)
parser.add_argument(
'--owner',
type=str,
help='Username of the desired Owner.',
required=True,
)
@transaction.atomic
def handle(self, *args, **options):
owner = User.objects.get(username=options['owner'])
package_data = json.load(open(options['data']))
PackageManager.import_package(package_data, owner)

View File

@ -468,10 +468,10 @@ class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
# I think this only affects Django Admin which we are barely using # I think this only affects Django Admin which we are barely using
# # https://github.com/django-polymorphic/django-polymorphic/issues/229 # # https://github.com/django-polymorphic/django-polymorphic/issues/229
_non_polymorphic = models.Manager() # _non_polymorphic = models.Manager()
#
class Meta: # class Meta:
base_manager_name = '_non_polymorphic' # base_manager_name = '_non_polymorphic'
@abc.abstractmethod @abc.abstractmethod
def apply(self, *args, **kwargs): def apply(self, *args, **kwargs):
@ -1431,14 +1431,13 @@ class PluginModel(EPModel):
pass pass
# #
# #
# # # TODO fully implement AdditionalInformation
# # # TODO consider Scenario, BaseScenario, RelatedScenario
class Scenario(EnviPathModel): class Scenario(EnviPathModel):
package = models.ForeignKey('epdb.Package', verbose_name='Package', on_delete=models.CASCADE, db_index=True) package = models.ForeignKey('epdb.Package', verbose_name='Package', on_delete=models.CASCADE, db_index=True)
type = models.CharField(max_length=256, null=False, blank=False, default='No date') scenario_date = models.CharField(max_length=256, null=False, blank=False, default='No date')
type = models.CharField(max_length=256, null=False, blank=False, default='Not specified') scenario_type = models.CharField(max_length=256, null=False, blank=False, default='Not specified')
# for Referring Scenarios this property will be filled
parent = models.ForeignKey('self', on_delete=models.CASCADE, default=None, null=True)
additional_information = models.JSONField(verbose_name='Additional Information') additional_information = models.JSONField(verbose_name='Additional Information')
@ -1470,47 +1469,15 @@ class Scenario(EnviPathModel):
def set_additional_information(self, data): def set_additional_information(self, data):
pass pass
example = { def get_additional_information(self):
"additionalInformationCollection": { from envipy_additional_information import NAME_MAPPING
"additionalInformation": [
{
"addInfoName": "referringscenario",
"creationDate": "2017-12-15 11:46:07.993",
"data": "http://localhost:8080/package/5882df9c-dae1-4d80-a40e-db4724271456/scenario/11482bc1-8a0c-44a0-ae8b-5a02ae732559",
"id": "http://localhost:8080/package/5882df9c-dae1-4d80-a40e-db4724271456/infocollection/0f30d0ca-b2bd-4c85-a425-ed8b22d4fed6/referringscenario/41532eac-e04a-4474-937a-df1344c3dce7",
"identifier": "referringscenario",
"lastModified": "2017-12-15 11:46:07.993",
"name": "referringscenario"
},
{
"addInfoName": "halflife",
"creationDate": "2017-12-15 11:46:07.934",
"data": "First Order;;reported,no further information about the model;3690.0 - 3690.0;McCorquodale, G. & Wardrope, L. (2006)",
"id": "http://localhost:8080/package/5882df9c-dae1-4d80-a40e-db4724271456/infocollection/0f30d0ca-b2bd-4c85-a425-ed8b22d4fed6/halflife/8f44fdd9-f453-4ab1-8509-2ee5826faad7",
"identifier": "halflife",
"lastModified": "2020-05-05 17:26:14.753",
"name": "halflife"
}
],
"creationDate": "2017-12-15 11:46:07.608",
"id": "http://localhost:8080/package/5882df9c-dae1-4d80-a40e-db4724271456/infocollection/0f30d0ca-b2bd-4c85-a425-ed8b22d4fed6",
"identifier": "infocollection",
"lastModified": "2020-05-05 17:26:15.496",
"name": "no name"
},
"aliases": [],
"creationDate": "2017-12-15 11:46:08.221",
"date": "no date",
"description": "no description",
"id": "http://localhost:8080/package/5882df9c-dae1-4d80-a40e-db4724271456/scenario/e7089e49-e07d-4a2d-8045-e144b7eb5a5e",
"identifier": "scenario",
"lastModified": "2020-05-05 17:26:15.065",
"name": "McCorquodale, G. & Wardrope, L. (2006) - (00002) (Related Scenario) - (00000)",
"reviewStatus": "reviewed",
"scenarios": [],
"type": "Not specified"
}
for k, vals in self.additional_information.items():
if k == 'enzyme':
continue
for v in vals:
yield NAME_MAPPING[k](**json.loads(v))
class UserSettingPermission(Permission): class UserSettingPermission(Permission):
uuid = models.UUIDField(null=False, blank=False, verbose_name='UUID of this object', primary_key=True, uuid = models.UUIDField(null=False, blank=False, verbose_name='UUID of this object', primary_key=True,

View File

@ -1141,7 +1141,24 @@ def package_scenarios(request, package_uuid):
# https://envipath.org/package/<id>/scenario/<id> # https://envipath.org/package/<id>/scenario/<id>
def package_scenario(request, package_uuid, scenario_uuid): def package_scenario(request, package_uuid, scenario_uuid):
pass current_user = _anonymous_or_real(request)
current_package = PackageManager.get_package_by_id(current_user, package_uuid)
current_scenario = Scenario.objects.get(package=current_package, uuid=scenario_uuid)
if request.method == 'GET':
context = get_base_context(request)
context['title'] = f'enviPath - {current_package.name} - {current_scenario.name}'
context['meta']['current_package'] = current_package
context['object_type'] = 'scenario'
context['breadcrumbs'] = breadcrumbs(current_package, 'scenario', current_scenario)
context['scenario'] = current_scenario
return render(request, 'objects/scenario.html', context)
### END UNTESTED ### END UNTESTED

View File

@ -12,6 +12,7 @@ dependencies = [
"django-ninja>=1.4.1", "django-ninja>=1.4.1",
"django-polymorphic>=4.1.0", "django-polymorphic>=4.1.0",
"enviformer", "enviformer",
"envipy-additional-information",
"envipy-plugins", "envipy-plugins",
"epam-indigo>=1.30.1", "epam-indigo>=1.30.1",
"gunicorn>=23.0.0", "gunicorn>=23.0.0",
@ -28,3 +29,4 @@ dependencies = [
[tool.uv.sources] [tool.uv.sources]
enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.0" } enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.0" }
envipy-plugins = { git = "ssh://git@git.envipath.com/enviPath/enviPy-plugins.git", rev = "v0.1.0" } envipy-plugins = { git = "ssh://git@git.envipath.com/enviPath/enviPy-plugins.git", rev = "v0.1.0" }
envipy-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git" }

View File

View File

@ -0,0 +1,12 @@
{% extends "framework.html" %}
{% load static %}
{% block content %}
<div class="alert alert-error" role="alert">
<h4 class="alert-heading">Your account has not been activated yet!</h4>
<p>Your account has not been activated yet. If you have question <a href="mailto:admin@envipath.org">contact
us.</a>
</p>
</div>
{% endblock content %}

View File

@ -0,0 +1,51 @@
{% extends "framework.html" %}
{% block content %}
{% block action_modals %}
{% endblock action_modals %}
<div class="panel-group" id="scenario-detail">
<div class="panel panel-default">
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ scenario.name }}
<div id="actionsButton"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;"
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
style="padding-right:1em"></span></a>
<ul id="actionsList" class="dropdown-menu">
{% block actions %}
{% include "actions/objects/scenario.html" %}
{% endblock %}
</ul>
</div>
</div>
</div>
</div>
<div class="table-responsive">
<table id="scenario-table" class="table table-bordered table-striped table-hover">
<tbody>
<tr>
<th>Property</th>
<th>Value</th>
<th>Unit</th>
<th>Remove</th>
</tr>
{% for ai in scenario.get_additional_information %}
<tr>
<td>{{ ai.property_name|safe }} </td>
<td> {{ ai.property_data|safe }} </td>
<td> {{ ai.property_unit|safe }} </td>
<td></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock content %}

10
uv.lock generated
View File

@ -403,6 +403,7 @@ dependencies = [
{ name = "django-ninja" }, { name = "django-ninja" },
{ name = "django-polymorphic" }, { name = "django-polymorphic" },
{ name = "enviformer" }, { name = "enviformer" },
{ name = "envipy-additional-information" },
{ name = "envipy-plugins" }, { name = "envipy-plugins" },
{ name = "epam-indigo" }, { name = "epam-indigo" },
{ name = "gunicorn" }, { name = "gunicorn" },
@ -425,6 +426,7 @@ requires-dist = [
{ name = "django-ninja", specifier = ">=1.4.1" }, { name = "django-ninja", specifier = ">=1.4.1" },
{ name = "django-polymorphic", specifier = ">=4.1.0" }, { name = "django-polymorphic", specifier = ">=4.1.0" },
{ name = "enviformer", git = "ssh://git@git.envipath.com/enviPath/enviformer.git?rev=v0.1.0" }, { name = "enviformer", git = "ssh://git@git.envipath.com/enviPath/enviformer.git?rev=v0.1.0" },
{ name = "envipy-additional-information", git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git" },
{ name = "envipy-plugins", git = "ssh://git@git.envipath.com/enviPath/enviPy-plugins.git?rev=v0.1.0" }, { name = "envipy-plugins", git = "ssh://git@git.envipath.com/enviPath/enviPy-plugins.git?rev=v0.1.0" },
{ name = "epam-indigo", specifier = ">=1.30.1" }, { name = "epam-indigo", specifier = ">=1.30.1" },
{ name = "gunicorn", specifier = ">=23.0.0" }, { name = "gunicorn", specifier = ">=23.0.0" },
@ -438,6 +440,14 @@ requires-dist = [
{ name = "setuptools", specifier = ">=80.8.0" }, { name = "setuptools", specifier = ">=80.8.0" },
] ]
[[package]]
name = "envipy-additional-information"
version = "0.1.0"
source = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git#4804b24b3479bed6108a49e4401bff8947c03cbd" }
dependencies = [
{ name = "pydantic" },
]
[[package]] [[package]]
name = "envipy-plugins" name = "envipy-plugins"
version = "0.1.0" version = "0.1.0"