From 49e02ed97d19cfa7cca51202cc0d46529769337d Mon Sep 17 00:00:00 2001 From: jebus Date: Sat, 19 Jul 2025 08:10:40 +1200 Subject: [PATCH] feature/additional_information (#30) Fixes #12 Co-authored-by: Tim Lorsbach Reviewed-on: https://git.envipath.com/enviPath/enviPy/pulls/30 --- epdb/admin.py | 18 +- epdb/logic.py | 376 ++++++++++++++++++++ epdb/management/commands/bootstrap.py | 296 +-------------- epdb/management/commands/import_package.py | 27 ++ epdb/models.py | 67 +--- epdb/views.py | 19 +- pyproject.toml | 2 + templates/actions/objects/scenario.html | 0 templates/errors/user_account_inactive.html | 12 + templates/objects/scenario.html | 51 +++ uv.lock | 10 + 11 files changed, 534 insertions(+), 344 deletions(-) create mode 100644 epdb/management/commands/import_package.py create mode 100644 templates/actions/objects/scenario.html create mode 100644 templates/errors/user_account_inactive.html create mode 100644 templates/objects/scenario.html diff --git a/epdb/admin.py b/epdb/admin.py index c4dae9ff..bd3a3dee 100644 --- a/epdb/admin.py +++ b/epdb/admin.py @@ -1,6 +1,6 @@ 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): @@ -22,15 +22,19 @@ class GroupPackagePermissionAdmin(admin.ModelAdmin): class SettingAdmin(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) - class SimpleAmbitRuleAdmin(admin.ModelAdmin): 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(Scenario, ScenarioAdmin) diff --git a/epdb/logic.py b/epdb/logic.py index 07bdb9f6..c4c541e6 100644 --- a/epdb/logic.py +++ b/epdb/logic.py @@ -255,6 +255,382 @@ class PackageManager(object): else: _ = 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): 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}$") diff --git a/epdb/management/commands/bootstrap.py b/epdb/management/commands/bootstrap.py index 5f3714ad..b28e6620 100644 --- a/epdb/management/commands/bootstrap.py +++ b/epdb/management/commands/bootstrap.py @@ -1,11 +1,11 @@ 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 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.models import UserSettingPermission, MLRelativeReasoning, EnviFormer, Permission, User class Command(BaseCommand): @@ -52,283 +52,7 @@ class Command(BaseCommand): return anon, admin, g, jebus def import_package(self, data, owner): - # Start import - 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 + return PackageManager.import_package(data, owner, keep_ids=True) def create_default_setting(self, owner, packages): s = SettingManager.create_setting( @@ -344,8 +68,6 @@ class Command(BaseCommand): return s - - @transaction.atomic def handle(self, *args, **options): # Create users @@ -356,12 +78,13 @@ class Command(BaseCommand): 'EAWAG-BBD.json', 'EAWAG-SOIL.json', 'EAWAG-SLUDGE.json', + 'EAWAG-SEDIMENT.json', ] mapping = {} for p in packages: 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) mapping[p.replace('.json', '')] = imported_package @@ -378,7 +101,8 @@ class Command(BaseCommand): usp.save() # 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.save() diff --git a/epdb/management/commands/import_package.py b/epdb/management/commands/import_package.py new file mode 100644 index 00000000..8065cea0 --- /dev/null +++ b/epdb/management/commands/import_package.py @@ -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) diff --git a/epdb/models.py b/epdb/models.py index c015c6cd..c19de2ba 100644 --- a/epdb/models.py +++ b/epdb/models.py @@ -468,10 +468,10 @@ class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin): # I think this only affects Django Admin which we are barely using # # https://github.com/django-polymorphic/django-polymorphic/issues/229 - _non_polymorphic = models.Manager() - - class Meta: - base_manager_name = '_non_polymorphic' + # _non_polymorphic = models.Manager() + # + # class Meta: + # base_manager_name = '_non_polymorphic' @abc.abstractmethod def apply(self, *args, **kwargs): @@ -1431,14 +1431,13 @@ class PluginModel(EPModel): pass -# # -# # -# # # TODO fully implement AdditionalInformation -# # # TODO consider Scenario, BaseScenario, RelatedScenario class Scenario(EnviPathModel): 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') - type = models.CharField(max_length=256, null=False, blank=False, default='Not specified') + scenario_date = models.CharField(max_length=256, null=False, blank=False, default='No date') + 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') @@ -1470,47 +1469,15 @@ class Scenario(EnviPathModel): def set_additional_information(self, data): pass -example = { - "additionalInformationCollection": { - "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" -} + def get_additional_information(self): + from envipy_additional_information import NAME_MAPPING + 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): uuid = models.UUIDField(null=False, blank=False, verbose_name='UUID of this object', primary_key=True, diff --git a/epdb/views.py b/epdb/views.py index ed1eae99..e3a3d327 100644 --- a/epdb/views.py +++ b/epdb/views.py @@ -1141,7 +1141,24 @@ def package_scenarios(request, package_uuid): # https://envipath.org/package//scenario/ 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 diff --git a/pyproject.toml b/pyproject.toml index 06e254f9..c1d11d46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "django-ninja>=1.4.1", "django-polymorphic>=4.1.0", "enviformer", + "envipy-additional-information", "envipy-plugins", "epam-indigo>=1.30.1", "gunicorn>=23.0.0", @@ -28,3 +29,4 @@ dependencies = [ [tool.uv.sources] 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-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git" } diff --git a/templates/actions/objects/scenario.html b/templates/actions/objects/scenario.html new file mode 100644 index 00000000..e69de29b diff --git a/templates/errors/user_account_inactive.html b/templates/errors/user_account_inactive.html new file mode 100644 index 00000000..89d451ac --- /dev/null +++ b/templates/errors/user_account_inactive.html @@ -0,0 +1,12 @@ +{% extends "framework.html" %} +{% load static %} +{% block content %} + + + +{% endblock content %} diff --git a/templates/objects/scenario.html b/templates/objects/scenario.html new file mode 100644 index 00000000..41c69c29 --- /dev/null +++ b/templates/objects/scenario.html @@ -0,0 +1,51 @@ +{% extends "framework.html" %} + +{% block content %} + + {% block action_modals %} + + {% endblock action_modals %} +
+
+
+ {{ scenario.name }} + +
+
+
+
+ + + + + + + + + + {% for ai in scenario.get_additional_information %} + + + + + + + {% endfor %} + + +
PropertyValueUnitRemove
{{ ai.property_name|safe }} {{ ai.property_data|safe }} {{ ai.property_unit|safe }}
+
+ + +{% endblock content %} \ No newline at end of file diff --git a/uv.lock b/uv.lock index 119d4f92..b07eebad 100644 --- a/uv.lock +++ b/uv.lock @@ -403,6 +403,7 @@ dependencies = [ { name = "django-ninja" }, { name = "django-polymorphic" }, { name = "enviformer" }, + { name = "envipy-additional-information" }, { name = "envipy-plugins" }, { name = "epam-indigo" }, { name = "gunicorn" }, @@ -425,6 +426,7 @@ requires-dist = [ { name = "django-ninja", specifier = ">=1.4.1" }, { name = "django-polymorphic", specifier = ">=4.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 = "epam-indigo", specifier = ">=1.30.1" }, { name = "gunicorn", specifier = ">=23.0.0" }, @@ -438,6 +440,14 @@ requires-dist = [ { 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]] name = "envipy-plugins" version = "0.1.0"