From d9530ce7554a7f9b8177c64ad453e6f1b6151ea0 Mon Sep 17 00:00:00 2001 From: Tim Lorsbach Date: Fri, 6 Mar 2026 15:15:08 +0100 Subject: [PATCH 1/5] adjusted migration Initial bayer app Show Pack Classification Adjusted docker compose to bayer specifics Adjusted Dockerfile for Bayer Adding secret flags to group, add secret pools to packages Adjusted View for Package creation Prep configs, added Package Create Modal wip More on PES wip wip --- Dockerfile | 1 + bayer/__init__.py | 0 bayer/admin.py | 19 + bayer/apps.py | 6 + bayer/epdb_hooks.py | 31 + bayer/migrations/0001_initial.py | 35 + bayer/migrations/0002_initial.py | 22 + ...compound_pesstructure_package_data_pool.py | 41 + bayer/migrations/__init__.py | 0 bayer/models.py | 236 ++++ .../actions/collections/new_pes.html | 9 + .../modals/collections/new_package_modal.html | 175 +++ .../modals/collections/new_pes_modal.html | 174 +++ .../objects/add_pathway_pes_node_modal.html | 174 +++ .../objects/compound_structure_viz.html | 19 + bayer/templates/objects/compound_viz.html | 19 + bayer/templates/objects/node_viz.html | 19 + bayer/templates/objects/package.html | 97 ++ bayer/templates/static/login.html | 154 +++ bayer/tests.py | 3 + bayer/tests/__init__.py | 0 bayer/tests/pes/__init__.py | 0 bayer/tests/pes/test_pes.py | 174 +++ bayer/urls.py | 19 + bayer/views.py | 160 +++ docker-compose.dev.yml | 44 +- docker-compose.yml | 18 +- envipath/settings.py | 36 +- epauth/views.py | 24 + epdb/legacy_api.py | 71 +- epdb/logic.py | 80 +- epdb/migrations/0001_initial.py | 594 ---------- ...abilitydomain_url_compound_url_and_more.py | 1020 ----------------- ...atabase_alter_apitoken_options_and_more.py | 128 --- ...abilitydomain_url_compound_url_and_more.py | 228 ---- ...er_mlrelativereasoning_options_and_more.py | 55 - .../0005_alter_group_group_member.py | 18 - ...elativereasoning_multigen_eval_and_more.py | 23 - ..._options_enviformer_app_domain_and_more.py | 53 - epdb/migrations/0008_enzymelink.py | 64 -- epdb/migrations/0009_joblog.py | 66 -- epdb/migrations/0010_license_cc_string.py | 18 - epdb/migrations/0011_auto_20251111_1413.py | 59 - ...2_node_stereo_removed_pathway_predicted.py | 22 - .../0013_setting_expansion_schema.py | 25 - ...pansion_schema_setting_expansion_scheme.py | 17 - epdb/migrations/0015_user_is_reviewer.py | 17 - ...remove_enviformer_model_status_and_more.py | 179 --- epdb/migrations/0017_additionalinformation.py | 93 -- epdb/migrations/0018_auto_20260220_1203.py | 132 --- ...cenario_additional_information_and_more.py | 739 +++++++++++- ...lter_compoundstructure_options_and_more.py | 5 + epdb/models.py | 24 +- epdb/views.py | 31 + static/images/Restricted.gif | Bin 0 -> 1577 bytes static/images/bayer-logo.svg | 30 + static/images/restricted_mid.png | Bin 0 -> 2243 bytes static/images/secret_mid.png | Bin 0 -> 1490 bytes static/images/secret_small.png | Bin 0 -> 3226 bytes templates/actions/objects/pathway.html | 8 + templates/components/navbar.html | 12 + templates/objects/node.html | 7 + templates/objects/pathway.html | 1 + 63 files changed, 2657 insertions(+), 2871 deletions(-) create mode 100644 bayer/__init__.py create mode 100644 bayer/admin.py create mode 100644 bayer/apps.py create mode 100644 bayer/epdb_hooks.py create mode 100644 bayer/migrations/0001_initial.py create mode 100644 bayer/migrations/0002_initial.py create mode 100644 bayer/migrations/0003_pescompound_pesstructure_package_data_pool.py create mode 100644 bayer/migrations/__init__.py create mode 100644 bayer/models.py create mode 100644 bayer/templates/actions/collections/new_pes.html create mode 100644 bayer/templates/modals/collections/new_package_modal.html create mode 100644 bayer/templates/modals/collections/new_pes_modal.html create mode 100644 bayer/templates/modals/objects/add_pathway_pes_node_modal.html create mode 100644 bayer/templates/objects/compound_structure_viz.html create mode 100644 bayer/templates/objects/compound_viz.html create mode 100644 bayer/templates/objects/node_viz.html create mode 100644 bayer/templates/objects/package.html create mode 100644 bayer/templates/static/login.html create mode 100644 bayer/tests.py create mode 100644 bayer/tests/__init__.py create mode 100644 bayer/tests/pes/__init__.py create mode 100644 bayer/tests/pes/test_pes.py create mode 100644 bayer/urls.py create mode 100644 bayer/views.py delete mode 100644 epdb/migrations/0001_initial.py delete mode 100644 epdb/migrations/0001_squashed_0003_applicabilitydomain_url_compound_url_and_more.py delete mode 100644 epdb/migrations/0002_externaldatabase_alter_apitoken_options_and_more.py delete mode 100644 epdb/migrations/0003_applicabilitydomain_url_compound_url_and_more.py delete mode 100644 epdb/migrations/0004_alter_mlrelativereasoning_options_and_more.py delete mode 100644 epdb/migrations/0005_alter_group_group_member.py delete mode 100644 epdb/migrations/0006_mlrelativereasoning_multigen_eval_and_more.py delete mode 100644 epdb/migrations/0007_alter_enviformer_options_enviformer_app_domain_and_more.py delete mode 100644 epdb/migrations/0008_enzymelink.py delete mode 100644 epdb/migrations/0009_joblog.py delete mode 100644 epdb/migrations/0010_license_cc_string.py delete mode 100644 epdb/migrations/0011_auto_20251111_1413.py delete mode 100644 epdb/migrations/0012_node_stereo_removed_pathway_predicted.py delete mode 100644 epdb/migrations/0013_setting_expansion_schema.py delete mode 100644 epdb/migrations/0014_rename_expansion_schema_setting_expansion_scheme.py delete mode 100644 epdb/migrations/0015_user_is_reviewer.py delete mode 100644 epdb/migrations/0016_remove_enviformer_model_status_and_more.py delete mode 100644 epdb/migrations/0017_additionalinformation.py delete mode 100644 epdb/migrations/0018_auto_20260220_1203.py create mode 100644 static/images/Restricted.gif create mode 100644 static/images/bayer-logo.svg create mode 100644 static/images/restricted_mid.png create mode 100644 static/images/secret_mid.png create mode 100644 static/images/secret_small.png diff --git a/Dockerfile b/Dockerfile index 08cf1600..909e0985 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,6 +37,7 @@ RUN --mount=type=ssh \ # Now copy source and do a final sync to install the project itself # Ensure .dockerignore is reasonable COPY biotransformer biotransformer +COPY bayer bayer COPY bridge bridge COPY envipath envipath COPY epapi epapi diff --git a/bayer/__init__.py b/bayer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bayer/admin.py b/bayer/admin.py new file mode 100644 index 00000000..f0b257bb --- /dev/null +++ b/bayer/admin.py @@ -0,0 +1,19 @@ +from django.contrib import admin + +# Register your models here. +from .models import ( + PESCompound, + PESStructure +) + + +class PESCompoundAdmin(admin.ModelAdmin): + pass + + +class PESStructureAdmin(admin.ModelAdmin): + pass + + +admin.site.register(PESCompound, PESCompoundAdmin) +admin.site.register(PESStructure, PESStructureAdmin) diff --git a/bayer/apps.py b/bayer/apps.py new file mode 100644 index 00000000..3821be18 --- /dev/null +++ b/bayer/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BayerConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'bayer' diff --git a/bayer/epdb_hooks.py b/bayer/epdb_hooks.py new file mode 100644 index 00000000..a1a237f5 --- /dev/null +++ b/bayer/epdb_hooks.py @@ -0,0 +1,31 @@ +import logging + +from epdb.template_registry import register_template + +logger = logging.getLogger(__name__) + +# PES Create +register_template( + "epdb.actions.collections.compound", + "actions/collections/new_pes.html", +) +register_template( + "modals.collections.compound", + "modals/collections/new_pes_modal.html", +) + +# PES Viz +register_template( + "epdb.objects.compound.viz", + "objects/compound_viz.html", +) + +register_template( + "epdb.objects.compound_structure.viz", + "objects/compound_structure_viz.html", +) + +register_template( + "epdb.objects.node.viz", + "objects/node_viz.html", +) \ No newline at end of file diff --git a/bayer/migrations/0001_initial.py b/bayer/migrations/0001_initial.py new file mode 100644 index 00000000..a9d11d9a --- /dev/null +++ b/bayer/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 5.2.7 on 2026-03-06 10:51 + +import django.utils.timezone +import model_utils.fields +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Package', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('reviewed', models.BooleanField(default=False, verbose_name='Reviewstatus')), + ('classification_level', models.IntegerField(choices=[(0, 'Internal'), (10, 'Restricted'), (20, 'Secret')], default=10)), + ], + options={ + 'db_table': 'epdb_package', + }, + ), + ] diff --git a/bayer/migrations/0002_initial.py b/bayer/migrations/0002_initial.py new file mode 100644 index 00000000..6a5a15d8 --- /dev/null +++ b/bayer/migrations/0002_initial.py @@ -0,0 +1,22 @@ +# Generated by Django 5.2.7 on 2026-03-06 10:51 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('bayer', '0001_initial'), + ('epdb', '0019_remove_scenario_additional_information_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='package', + name='license', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.license', verbose_name='License'), + ), + ] diff --git a/bayer/migrations/0003_pescompound_pesstructure_package_data_pool.py b/bayer/migrations/0003_pescompound_pesstructure_package_data_pool.py new file mode 100644 index 00000000..066eec65 --- /dev/null +++ b/bayer/migrations/0003_pescompound_pesstructure_package_data_pool.py @@ -0,0 +1,41 @@ +# Generated by Django 6.0.3 on 2026-04-17 21:22 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bayer', '0002_initial'), + ('epdb', '0023_alter_compoundstructure_options_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='PESCompound', + fields=[ + ('compound_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.compound')), + ], + options={ + 'abstract': False, + }, + bases=('epdb.compound',), + ), + migrations.CreateModel( + name='PESStructure', + fields=[ + ('compoundstructure_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.compoundstructure')), + ('pes_link', models.URLField(verbose_name='PES Link')), + ], + options={ + 'abstract': False, + }, + bases=('epdb.compoundstructure',), + ), + migrations.AddField( + model_name='package', + name='data_pool', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.group', verbose_name='Data pool'), + ), + ] diff --git a/bayer/migrations/__init__.py b/bayer/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bayer/models.py b/bayer/models.py new file mode 100644 index 00000000..b4ac7632 --- /dev/null +++ b/bayer/models.py @@ -0,0 +1,236 @@ +from typing import List +import urllib.parse +import nh3 +from django.conf import settings as s +from django.db import models, transaction +from django.db.models import QuerySet +from django.urls import reverse + +from epdb.models import ( + EnviPathModel, + Compound, + CompoundStructure, + ParallelRule, + SequentialRule, + SimpleAmbitRule, + SimpleRDKitRule, +) +from utilities.chem import FormatConverter + + +class Package(EnviPathModel): + reviewed = models.BooleanField(verbose_name="Reviewstatus", default=False) + license = models.ForeignKey( + "epdb.License", on_delete=models.SET_NULL, blank=True, null=True, verbose_name="License" + ) + + class Classification(models.IntegerChoices): + INTERNAL = 0, "Internal" + RESTRICTED = 10 , "Restricted" + SECRET = 20, "Secret" + + classification_level = models.IntegerField( + choices=Classification, + default=Classification.RESTRICTED, + ) + + data_pool = models.ForeignKey("epdb.Group", on_delete=models.SET_NULL, blank=True, null=True, + verbose_name="Data pool", default=None) + + def delete(self, *args, **kwargs): + # explicitly handle related Rules + for r in self.rules.all(): + r.delete() + super().delete(*args, **kwargs) + + def __str__(self): + return f"{self.name} (pk={self.pk})" + + @property + def compounds(self) -> QuerySet: + return self.compound_set.all() + + @property + def rules(self) -> QuerySet: + return self.rule_set.all() + + @property + def reactions(self) -> QuerySet: + return self.reaction_set.all() + + @property + def pathways(self) -> QuerySet: + return self.pathway_set.all() + + @property + def scenarios(self) -> QuerySet: + return self.scenario_set.all() + + @property + def models(self) -> QuerySet: + return self.epmodel_set.all() + + def _url(self): + return "{}/package/{}".format(s.SERVER_URL, self.uuid) + + def get_applicable_rules(self) -> List["Rule"]: + """ + Returns a ordered set of rules where the following applies: + 1. All Composite will be added to result + 2. All SimpleRules will be added if theres no CompositeRule present using the SimpleRule + Ordering is based on "url" field. + """ + rules = [] + rule_qs = self.rules + + reflected_simple_rules = set() + + for r in rule_qs: + if isinstance(r, ParallelRule) or isinstance(r, SequentialRule): + rules.append(r) + for sr in r.simple_rules.all(): + reflected_simple_rules.add(sr) + + for r in rule_qs: + if isinstance(r, SimpleAmbitRule) or isinstance(r, SimpleRDKitRule): + if r not in reflected_simple_rules: + rules.append(r) + + rules = sorted(rules, key=lambda x: x.url) + return rules + + class Meta: + db_table = "epdb_package" + + +class PESCompound(Compound): + + @staticmethod + @transaction.atomic + def create( + package: "Package", pes_data: dict, name: str = None, description: str = None, *args, **kwargs + ) -> "Compound": + + pes_url = pes_data["pes_url"] + + # Check if we find a direct match for a given pes_link + if PESStructure.objects.filter(pes_link=pes_url, compound__package=package).exists(): + # Due to normalization we might end up in having multiple structures + # All of them point to the same compound -> pick any + return PESStructure.objects.filter(pes_link=pes_url, compound__package=package).first().compound + + # Generate Compound + c = PESCompound() + c.package = package + + if name is not None: + # Clean for potential XSS + name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() + + if name is None or name == "": + name = f"Compound {Compound.objects.filter(package=package).count() + 1}" + + c.name = name + + # We have a default here only set the value if it carries some payload + if description is not None and description.strip() != "": + c.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() + + c.save() + + molfile = pes_data.get("representativeStructures", [{}])[0].get("ctab") + + if molfile is None: + raise ValueError("PES data does not contain a valid mol file!") + + smiles = FormatConverter.to_smiles(FormatConverter.from_molfile(molfile)) + + standardized_smiles = FormatConverter.standardize(smiles, remove_stereo=True) + + is_standardized = standardized_smiles == smiles + + if not is_standardized: + _ = PESStructure.create( + c, + pes_url, + molfile, + standardized_smiles, + name="Normalized structure of {}".format(name), + description="{} (in its normalized form)".format(description), + normalized_structure=True, + ) + + + cs = PESStructure.create( + c, + pes_url, + molfile, + smiles, + name=name, + description=description, + normalized_structure=is_standardized + ) + + c.default_structure = cs + c.save() + + return c + + +class PESStructure(CompoundStructure): + pes_link = models.URLField(blank=False, null=False, verbose_name="PES Link") + + @staticmethod + @transaction.atomic + def create( + compound: Compound, + pes_link: str, + mol_file: str, + smiles: str, + name: str = None, + description: str = None, + *args, + **kwargs + ): + if compound.pk is None: + raise ValueError("Unpersisted Compound! Persist compound first!") + + cs = PESStructure() + # Clean for potential XSS + if name is not None: + cs.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() + + if description is not None: + cs.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() + + cs.smiles = smiles + cs.mol_file = mol_file + cs.pes_link = pes_link + cs.compound = compound + + if "normalized_structure" in kwargs: + cs.normalized_structure = kwargs["normalized_structure"] + + cs.save() + + return cs + + @transaction.atomic + def add_structure( + self, + smiles: str, + name: str = None, + description: str = None, + default_structure: bool = False, + *args, + **kwargs, + ) -> "CompoundStructure": + raise ValueError("Not supported!") + + def d3_json(self): + return { + "is_pes": True, + "pes_link": self.pes_link, + # Will overwrite image from Node + "image": f"{reverse("depict_pes")}?pesLink={urllib.parse.quote(self.pes_link)}" + } diff --git a/bayer/templates/actions/collections/new_pes.html b/bayer/templates/actions/collections/new_pes.html new file mode 100644 index 00000000..bdae544f --- /dev/null +++ b/bayer/templates/actions/collections/new_pes.html @@ -0,0 +1,9 @@ +{% if meta.can_edit %} + +{% endif %} \ No newline at end of file diff --git a/bayer/templates/modals/collections/new_package_modal.html b/bayer/templates/modals/collections/new_package_modal.html new file mode 100644 index 00000000..54e03fae --- /dev/null +++ b/bayer/templates/modals/collections/new_package_modal.html @@ -0,0 +1,175 @@ +{% load static %} + + + + + + + \ No newline at end of file diff --git a/bayer/templates/modals/collections/new_pes_modal.html b/bayer/templates/modals/collections/new_pes_modal.html new file mode 100644 index 00000000..1ebbdc34 --- /dev/null +++ b/bayer/templates/modals/collections/new_pes_modal.html @@ -0,0 +1,174 @@ +{% load static %} + + + + + + + \ No newline at end of file diff --git a/bayer/templates/modals/objects/add_pathway_pes_node_modal.html b/bayer/templates/modals/objects/add_pathway_pes_node_modal.html new file mode 100644 index 00000000..bf08083f --- /dev/null +++ b/bayer/templates/modals/objects/add_pathway_pes_node_modal.html @@ -0,0 +1,174 @@ +{% load static %} + + + + + + + \ No newline at end of file diff --git a/bayer/templates/objects/compound_structure_viz.html b/bayer/templates/objects/compound_structure_viz.html new file mode 100644 index 00000000..8924c63e --- /dev/null +++ b/bayer/templates/objects/compound_structure_viz.html @@ -0,0 +1,19 @@ +{% if compound_structure.pes_link %} + +
+ +
Link to PES
+
{{ compound_structure.pes_link }}
+
+ + +
+ +
PES Image Representation
+
+
+ +
+
+
+{% endif %} \ No newline at end of file diff --git a/bayer/templates/objects/compound_viz.html b/bayer/templates/objects/compound_viz.html new file mode 100644 index 00000000..9ff10e75 --- /dev/null +++ b/bayer/templates/objects/compound_viz.html @@ -0,0 +1,19 @@ +{% if compound.default_structure.pes_link %} + +
+ +
Link to PES
+
{{ compound.default_structure.pes_link }}
+
+ + +
+ +
PES Image Representation
+
+
+ +
+
+
+{% endif %} \ No newline at end of file diff --git a/bayer/templates/objects/node_viz.html b/bayer/templates/objects/node_viz.html new file mode 100644 index 00000000..4426726b --- /dev/null +++ b/bayer/templates/objects/node_viz.html @@ -0,0 +1,19 @@ +{% if node.default_node_label.pes_link %} + +
+ +
Link to PES
+
{{ node.default_node_label.pes_link }}
+
+ + +
+ +
PES Image Representation
+
+
+ +
+
+
+{% endif %} \ No newline at end of file diff --git a/bayer/templates/objects/package.html b/bayer/templates/objects/package.html new file mode 100644 index 00000000..c03be1d2 --- /dev/null +++ b/bayer/templates/objects/package.html @@ -0,0 +1,97 @@ +{% extends "framework_modern.html" %} +{% load static %} +{% block content %} + + {% block action_modals %} + {% include "modals/objects/edit_package_modal.html" %} + {% include "modals/objects/edit_package_permissions_modal.html" %} + {% include "modals/objects/publish_package_modal.html" %} + {% include "modals/objects/set_license_modal.html" %} + {% include "modals/objects/export_package_modal.html" %} + {% include "modals/objects/generic_delete_modal.html" %} + {% endblock action_modals %} + +
+ +
+
+
+

{{ package.name }} {% if meta.url_contains_package and meta.current_package.get_classification_level_display == "Restricted" %}{% elif meta.url_contains_package and meta.current_package.get_classification_level_display == "Secret" %}{% endif %}

+ +
+

{{ package.description|safe }}

+ +
+
+
+ + +{% endblock content %} diff --git a/bayer/templates/static/login.html b/bayer/templates/static/login.html new file mode 100644 index 00000000..65c54132 --- /dev/null +++ b/bayer/templates/static/login.html @@ -0,0 +1,154 @@ +{% extends "static/login_base.html" %} +{% load static %} +{% block title %}enviPath - Sign In{% endblock %} + +{% block extra_styles %} + +{% endblock %} + +{% block content %} +
+ + +
+
+

+

+ +
+ + + + + +
+ +
+ + +
+
+ {% csrf_token %} + + +
+ + +
+ +
+ + +
+ + + + + + +
+
+ +{% endblock %} + +{% block extra_scripts %} + +{% endblock %} diff --git a/bayer/tests.py b/bayer/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/bayer/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/bayer/tests/__init__.py b/bayer/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bayer/tests/pes/__init__.py b/bayer/tests/pes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bayer/tests/pes/test_pes.py b/bayer/tests/pes/test_pes.py new file mode 100644 index 00000000..2ed868d4 --- /dev/null +++ b/bayer/tests/pes/test_pes.py @@ -0,0 +1,174 @@ +from django.test import TestCase + +from bayer.models import PESCompound, PESStructure +from epdb.logic import UserManager +from epdb.models import CompoundStructure, Compound + + +class PESTest(TestCase): + + @classmethod + def setUpTestData(cls): + cls.user = UserManager.create_user( + "test-user", + "test-user@test.com", + "TestPass123", + set_setting=False, + add_to_group=False, + is_active=True, + ) + + cls.pes_dummy = { + "representativeStructures": [ + { + "ctab": "\n RDKit 2D\n\n 14 15 0 0 0 0 0 0 0 0999 V2000\n 2.7760 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 1.2760 0.0000 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0\n 0.3943 1.2135 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 0.7062 2.6807 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n -0.4086 3.6844 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0\n -0.0967 5.1517 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n -1.8351 3.2209 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n -2.1470 1.7537 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0\n -3.5736 1.2902 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n -1.0323 0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n -1.0323 -0.7500 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0\n 0.3943 -1.2135 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n -2.9499 4.2246 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0\n 2.1328 3.1443 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0\n 1 2 1 0\n 2 3 1 0\n 3 4 1 0\n 4 5 1 0\n 5 6 1 0\n 5 7 1 0\n 7 8 1 0\n 8 9 1 0\n 8 10 1 0\n 10 11 1 0\n 11 12 2 0\n 7 13 2 0\n 4 14 2 0\n 12 2 1 0\n 10 3 2 0\nM END\n" + } + ], + "representations": [ + { + "renderingOptions": { + "molecularFormulaRenderingOption": { + "displayOnRepresentation": False, + "labelCoords": { + "x": 0, + "y": 0 + } + }, + "modificationRenderingOptions": [ + { + "localModificationId": 1, + "displayNominalMass": False, + "displaySumFormula": False + } + ], + "contouringType": "convexHull", + "colored": True, + "abbreviations": False, + "structureSize": 0, + "showDirection": None, + "showStereoFlags": False + }, + "type": "black-and-white", + "mimeType": "image/png", + "base64": "data:image/png;base64,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" + }, + { + "renderingOptions": { + "molecularFormulaRenderingOption": { + "displayOnRepresentation": False, + "labelCoords": { + "x": 0, + "y": 0 + } + }, + "modificationRenderingOptions": [ + { + "localModificationId": 1, + "displayNominalMass": False, + "displaySumFormula": False + } + ], + "contouringType": "convexHull", + "colored": True, + "abbreviations": False, + "structureSize": 0, + "showDirection": None, + "showStereoFlags": False + }, + "type": "color", + "mimeType": "image/png", + "base64": "data:image/png;base64,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" + } + ], + "lifecycleStatus": "Published", + "corporateId": "PES-000126", + 'pes_url': 'https://pesregapp-test.cropkey-np.ag/entities/PES-000126', + "classificationLevel": "Restricted", + "dataPools": None, + } + + def test_smoke(self): + pes_compound = PESCompound.create( + self.user.default_package, + self.pes_dummy, + 'Test PES', + 'Test Description' + ) + + self.assertEqual(PESCompound.objects.count(), 1) + + obj = PESCompound.objects.first() + + self.assertEqual(obj.name, 'Test PES') + self.assertEqual(obj.description, 'Test Description') + self.assertTrue(isinstance(obj.default_structure, PESStructure)) + self.assertEqual(obj.default_structure.pes_link, 'https://pesregapp-test.cropkey-np.ag/entities/PES-000126') + self.assertEqual(obj.default_structure.smiles, "Cn1c2c(=O)n(C)c(=O)n(C)c2nc1") + self.assertTrue(isinstance(obj.normalized_structure, PESStructure)) + self.assertEqual(obj.normalized_structure.pes_link, 'https://pesregapp-test.cropkey-np.ag/entities/PES-000126') + self.assertEqual(obj.normalized_structure.smiles, "CN1C(=O)C2=C(N=CN2C)N(C)C1=O") + + self.assertEqual(CompoundStructure.objects.count(), 2) + self.assertEqual(PESStructure.objects.count(), 2) + + + def test_pes_dedup(self): + _ = PESCompound.create( + self.user.default_package, + self.pes_dummy, + 'Test PES', + 'Test Description' + ) + + self.assertEqual(PESCompound.objects.count(), 1) + + _ = PESCompound.create( + self.user.default_package, + self.pes_dummy, + 'Test PES 2', + 'Test Description 2' + ) + + # Assert deduplication works we only have one object + self.assertEqual(PESCompound.objects.count(), 1) + + obj = PESCompound.objects.first() + + # Assert name and description remain + self.assertEqual(obj.name, 'Test PES') + self.assertEqual(obj.description, 'Test Description') + + + def test_add_pes_and_compound_with_same_representative(self): + pes = PESCompound.create( + self.user.default_package, + self.pes_dummy, + 'Test PES', + 'Test Description' + ) + + regular = Compound.create( + self.user.default_package, + "CN1C(=O)C2=C(N=CN2C)N(C)C1=O", # Already normalized + 'Test PES', + 'Test Description' + ) + + self.assertNotEqual(pes, regular) + self.assertTrue(isinstance(pes, PESCompound)) + self.assertTrue(isinstance(regular, Compound)) + self.assertEqual(Compound.objects.count(), 2) + self.assertEqual(PESCompound.objects.count(), 1) + + self.assertEqual(CompoundStructure.objects.count(), 3) + + + + + # def test_api_add_pes(self): + # + # + # def test_my_endpoint(): + # client = TestClient(router, auth=MockMSAuth()) + # response = client.get("/my-endpoint", headers={"Authorization": "Bearer mock-token"}) + # assert response.status_code == 200 diff --git a/bayer/urls.py b/bayer/urls.py new file mode 100644 index 00000000..5f64cb86 --- /dev/null +++ b/bayer/urls.py @@ -0,0 +1,19 @@ +from django.urls import re_path + +from . import views as v + +UUID = "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}" + +urlpatterns = [ + re_path(r"^depict_pes$", v.visualize_pes, name="depict_pes"), + re_path( + rf"^package/(?P{UUID})/pes$", + v.create_pes, + name="create pes", + ), + re_path( + rf"^package/(?P{UUID})/pathway/(?P{UUID})/pes$", + v.create_pes_node, + name="create pes node", + ), +] diff --git a/bayer/views.py b/bayer/views.py new file mode 100644 index 00000000..58b2501a --- /dev/null +++ b/bayer/views.py @@ -0,0 +1,160 @@ +import base64 + +import requests +from django.conf import settings as s +from django.core.exceptions import BadRequest +from django.http import HttpResponse +from django.shortcuts import redirect + +from bayer.models import PESCompound +from epdb.logic import PackageManager +from epdb.models import Pathway, Node +from epdb.views import _anonymous_or_real +from utilities.decorators import package_permission_required + +Package = s.GET_PACKAGE_MODEL() + + +@package_permission_required() +def create_pes(request, package_uuid): + current_user = _anonymous_or_real(request) + current_package = PackageManager.get_package_by_id(current_user, package_uuid) + + if request.method == "POST": + + if current_package.classification_level == Package.Classification.INTERNAL: + raise BadRequest("Cannot create PESs for internal packages.") + + compound_name = request.POST.get('compound-name') + compound_description = request.POST.get('compound-description') + pes_link = request.POST.get('pes-link') + + if pes_link: + try: + pes_data = fetch_pes(request, pes_link) + except ValueError as e: + return BadRequest(f"Could not fetch PES data for {pes_link}") + + classification = pes_data.get("classificationLevel", "") + if "secret" == classification.lower(): + data_pools = pes_data.get("dataPools") + if data_pools: + if s.DATA_POOL_MAPPING[current_package.data_pool.name] not in data_pools: + return BadRequest( + f"PES data pool {s.DATA_POOL_MAPPING[current_package.data_pool.name]} not found in PES data") + + pes = PESCompound.create(current_package, pes_data, compound_name, compound_description) + + return redirect(pes.url) + else: + return BadRequest("Please provide a PES link.") + else: + pass + + +@package_permission_required() +def create_pes_node(request, package_uuid, pathway_uuid): + current_user = _anonymous_or_real(request) + current_package = PackageManager.get_package_by_id(current_user, package_uuid) + current_pathway = Pathway.objects.get(package=current_package, uuid=pathway_uuid) + + if request.method == "POST": + + if current_package.classification_level == Package.Classification.INTERNAL: + raise BadRequest("Cannot create PESs for internal packages.") + + compound_name = request.POST.get('compound-name') + compound_description = request.POST.get('compound-description') + pes_link = request.POST.get('pes-link') + + if pes_link: + try: + pes_data = fetch_pes(request, pes_link) + except ValueError as e: + return BadRequest(f"Could not fetch PES data for {pes_link}") + + classification = pes_data.get("classificationLevel", "") + if "secret" == classification.lower(): + data_pools = pes_data.get("dataPools") + if data_pools: + if s.DATA_POOL_MAPPING[current_package.data_pool.name] not in data_pools: + return BadRequest( + f"PES data pool {s.DATA_POOL_MAPPING[current_package.data_pool.name]} not found in PES data") + + pes = PESCompound.create(current_package, pes_data, compound_name, compound_description) + + n = Node() + n.stereo_removed = False + n.pathway = current_pathway + n.depth = 0 + + n.default_node_label = pes.default_structure + n.save() + + n.node_labels.add(pes.default_structure) + n.save() + + return redirect(current_pathway.url) + + else: + return BadRequest("Please provide a PES link.") + else: + pass + + +def fetch_pes(request, pes_url) -> dict: + proxies = { + "http": "http://10.185.190.100:8080", + "https": "http://10.185.190.100:8080", + } + + from epauth.views import get_access_token_from_request + token = get_access_token_from_request(request) + + if token or True: + for k, v in s.PES_API_MAPPING.items(): + if pes_url.startswith(k): + pes_id = pes_url.split('/')[-1] + + if pes_id == 'dummy' or True: + import json + res_data = json.load(open(s.BASE_DIR / "fixtures/pes.json")) + res_data["pes_url"] = pes_url + return res_data + else: + headers = {"Authorization": f"Bearer {token['access_token']}"} + params = {"pes_reg_entity_corporate_id": pes_id} + + res = requests.get(v, headers=headers, params=params, proxies=proxies) + + try: + res.raise_for_status() + pes_data = res.json() + + if len(pes_data) == 0: + raise ValueError(f"PES with id {pes_id} not found") + + res_data = pes_data[0] + res_data["pes_url"] = pes_url + return res_data + + except requests.exceptions.HTTPError as e: + raise ValueError(f"Error fetching PES with id {pes_id}: {e}") + else: + raise ValueError(f"Unknown URL {pes_url}") + else: + raise ValueError("Could not fetch access token from request.") + + +def visualize_pes(request): + pes_link = request.GET.get('pesLink') + + if pes_link: + pes_data = fetch_pes(request, pes_link) + + representations = pes_data.get('representations') + + for rep in representations: + if rep.get('type') == 'color': + image_data = base64.b64decode(rep.get('base64').replace("data:image/png;base64,", "")) + return HttpResponse(image_data, content_type="image/png") diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 0c0cdac6..ad4174fa 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,26 +1,54 @@ services: db: image: postgres:18 - container_name: envipath-postgres + container_name: eppostgres environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: envipath + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} ports: - "5432:5432" volumes: - - postgres_data:/var/lib/postgresql + - ep_bayer_postgres_data:/var/lib/postgresql healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] + test: [ "CMD-SHELL", "pg_isready -U postgres" ] interval: 5s timeout: 5s retries: 5 redis: image: redis:7-alpine - container_name: envipath-redis + container_name: epredis ports: - "6379:6379" + volumes: + - ep_bayer_redis_data:/data + + biotransformer3: + image: envipath/biotransformer3:1.0 + container_name: epbiotransformer3 + +# web: +# image: envipath/envipy-bayer:1.0 +# container_name: epdjango +# ports: +# - "127.0.0.1:8000:8000" +# env_file: +# - .env +# command: gunicorn envipath.wsgi:application --bind 0.0.0.0:8000 --workers 3 +# volumes: +# - ep_bayer_data:/opt/enviPy/ + + celery_worker: + image: envipath/envipy-bayer:1.0 + container_name: epcelery + env_file: + - .env.dev + command: celery -A envipath worker --concurrency=6 -Q model,predict,background --pool threads + volumes: + - ep_bayer_data:/opt/enviPy/ volumes: - postgres_data: + ep_bayer_postgres_data: + ep_bayer_redis_data: + ep_bayer_data: diff --git a/docker-compose.yml b/docker-compose.yml index 8f2f8e7a..088a6e00 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - - ep_postgres_data:/var/lib/postgresql + - ep_bayer_postgres_data:/var/lib/postgresql healthcheck: test: [ "CMD-SHELL", "pg_isready -U postgres" ] interval: 5s @@ -18,14 +18,14 @@ services: image: redis:7-alpine container_name: epredis volumes: - - ep_redis_data:/data + - ep_bayer_redis_data:/data biotransformer3: image: envipath/biotransformer3:1.0 container_name: epbiotransformer3 web: - image: envipath/envipy:1.0 + image: envipath/envipy-bayer:1.0 container_name: epdjango ports: - "127.0.0.1:8000:8000" @@ -33,18 +33,18 @@ services: - .env command: gunicorn envipath.wsgi:application --bind 0.0.0.0:8000 --workers 3 volumes: - - ep_data:/opt/enviPy/ + - ep_bayer_data:/opt/enviPy/ celery_worker: - image: envipath/envipy:1.0 + image: envipath/envipy-bayer:1.0 container_name: epcelery env_file: - .env command: celery -A envipath worker --concurrency=6 -Q model,predict,background --pool threads volumes: - - ep_data:/opt/enviPy/ + - ep_bayer_data:/opt/enviPy/ volumes: - ep_postgres_data: - ep_redis_data: - ep_data: + ep_bayer_postgres_data: + ep_bayer_redis_data: + ep_bayer_data: diff --git a/envipath/settings.py b/envipath/settings.py index 29a7f694..66b8fea3 100644 --- a/envipath/settings.py +++ b/envipath/settings.py @@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/4.2/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/4.2/ref/settings/ """ - +import json import os from pathlib import Path @@ -20,7 +20,7 @@ from sklearn.tree import DecisionTreeClassifier # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent -ENV_PATH = os.environ.get("ENV_PATH", BASE_DIR / ".env") +ENV_PATH = os.environ.get("ENV_PATH", BASE_DIR / ".env.dev") print(f"Loading env from {ENV_PATH}") load_dotenv(ENV_PATH, override=False) @@ -442,3 +442,35 @@ BIOTRANSFORMER_ENABLED = os.environ.get("BIOTRANSFORMER_ENABLED", "False") == "T FLAGS["BIOTRANSFORMER"] = BIOTRANSFORMER_ENABLED if BIOTRANSFORMER_ENABLED: BIOTRANSFORMER_URL = os.environ.get("BIOTRANSFORMER_URL", None) + +# PES +PES_API_MAPPING = os.environ.get("PES_API_MAPPING", None) +if PES_API_MAPPING: + import json + PES_API_MAPPING = json.loads(PES_API_MAPPING) +else: + PES_API_MAPPING = {} + +# Entra Groups +ENTRA_GROUPS = os.environ.get("ENTRA_GROUPS", None) +if ENTRA_GROUPS: + import json + ENTRA_GROUPS = json.loads(ENTRA_GROUPS) +else: + ENTRA_GROUPS = {} + +ENTRA_SECRET_GROUPS = os.environ.get("ENTRA_SECRET_GROUPS", None) +if ENTRA_SECRET_GROUPS: + import json + ENTRA_SECRET_GROUPS = json.loads(ENTRA_SECRET_GROUPS) +else: + ENTRA_SECRET_GROUPS = {} + +# PES Data Pools vs Entra Mapping +DATA_POOL_MAPPING = os.environ.get("DATA_POOL_MAPPING", None) +if DATA_POOL_MAPPING: + import json + DATA_POOL_MAPPING = json.loads(DATA_POOL_MAPPING) +else: + DATA_POOL_MAPPING = {} + diff --git a/epauth/views.py b/epauth/views.py index 66b922c6..1bab7da4 100644 --- a/epauth/views.py +++ b/epauth/views.py @@ -80,6 +80,30 @@ def entra_callback(request): login(request, u) + # EDIT START + # Ensure groups exists in eP + for id, name in s.ENTRA_SECRET_GROUPS.items(): + if not Group.objects.filter(uuid=id).exists(): + g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ", uuid=id) + else: + g = Group.objects.get(uuid=id) + # Ensure its secret + g.secret = True + g.save() + + for id, name in s.ENTRA_GROUPS.items(): + if not Group.objects.filter(uuid=id).exists(): + g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ", uuid=id) + else: + g = Group.objects.get(uuid=id) + + for group_uuid in claims.get("groups", []): + if Group.objects.filter(uuid=group_uuid).exists(): + g = Group.objects.get(uuid=group_uuid) + g.user_member.add(u) + + # EDIT END + return redirect(s.SERVER_URL) # Handle errors diff --git a/epdb/legacy_api.py b/epdb/legacy_api.py index b340bd21..7b09b251 100644 --- a/epdb/legacy_api.py +++ b/epdb/legacy_api.py @@ -1,17 +1,22 @@ +import hashlib from collections import defaultdict from typing import Any, Dict, List, Optional +import jwt +import requests + import nh3 from django.conf import settings as s from django.contrib.auth import get_user_model from django.http import HttpResponse, JsonResponse from django.shortcuts import redirect from ninja import Field, Form, Query, Router, Schema +from ninja.errors import HttpError +from ninja.security import HttpBearer from ninja.security import SessionAuth from utilities.chem import FormatConverter from utilities.misc import PackageExporter - from .logic import ( EPDBURLParser, GroupManager, @@ -59,7 +64,46 @@ def _anonymous_or_real(request): return get_user_model().objects.get(username="anonymous") -router = Router(auth=SessionAuth(csrf=False)) +def validate_token(token: str) -> dict: + TENANT_ID = s.MS_ENTRA_TENANT_ID + CLIENT_ID = s.MS_ENTRA_CLIENT_ID + + # Fetch Microsoft's public keys + jwks_uri = f"https://login.microsoftonline.com/{TENANT_ID}/discovery/v2.0/keys" + jwks = requests.get(jwks_uri).json() + + header = jwt.get_unverified_header(token) + + public_key = jwt.algorithms.RSAAlgorithm.from_jwk( + next(k for k in jwks["keys"] if k["kid"] == header["kid"]) + ) + + claims = jwt.decode( + token, + public_key, + algorithms=["RS256"], + audience=[CLIENT_ID, f"api://{CLIENT_ID}"], + issuer=f"https://sts.windows.net/{TENANT_ID}/", + ) + return claims + + +class MSBearerTokenAuth(HttpBearer): + + def authenticate(self, request, token): + if token is None: + return None + + claims = validate_token(token) + + if not User.objects.filter(uuid=claims['oid']).exists(): + return None + + request.user = User.objects.get(uuid=claims['oid']) + return request.user + + +router = Router(auth=MSBearerTokenAuth()) class Error(Schema): @@ -153,21 +197,6 @@ class SimpleModel(SimpleObject): identifier: str = "relative-reasoning" -################ -# Login/Logout # -################ -@router.post("/", response={200: SimpleUser, 403: Error}, auth=None) -def login(request, loginusername: Form[str], loginpassword: Form[str]): - from django.contrib.auth import authenticate, login - - email = User.objects.get(username=loginusername).email - user = authenticate(username=email, password=loginpassword) - if user: - login(request, user) - return user - else: - return 403, {"message": "Invalid username and/or password"} - ######## # User # @@ -1929,13 +1958,16 @@ def add_pathway_edge(request, package_uuid, pathway_uuid, e: Form[CreateEdge]): educts = [] products = [] + subclasses = CompoundStructure.__subclasses__() + if e.edgeAsSmirks: for ed in e.edgeAsSmirks.split(">>")[0].split("\\."): stand_ed = FormatConverter.standardize(ed, remove_stereo=True) educts.append( Node.objects.get( pathway=pw, - default_node_label=CompoundStructure.objects.get( + default_node_label=CompoundStructure.objects.not_instance_of(*subclasses). + get( compound__package=p, smiles=stand_ed ).compound.default_structure, ) @@ -1946,7 +1978,8 @@ def add_pathway_edge(request, package_uuid, pathway_uuid, e: Form[CreateEdge]): products.append( Node.objects.get( pathway=pw, - default_node_label=CompoundStructure.objects.get( + default_node_label=CompoundStructure.objects.not_instance_of(*subclasses). + get( compound__package=p, smiles=stand_pr ).compound.default_structure, ) diff --git a/epdb/logic.py b/epdb/logic.py index 6de2b73a..d73766d2 100644 --- a/epdb/logic.py +++ b/epdb/logic.py @@ -7,6 +7,7 @@ import nh3 from django.conf import settings as s from django.contrib.auth import get_user_model from django.db import transaction +from django.db.models import QuerySet from pydantic import ValidationError from epdb.models import ( @@ -364,6 +365,14 @@ class PackageManager(object): groups = GroupManager.get_groups(user) + # EDIT START + + if package.classification_level == Package.Classification.SECRET: + if package.data_pool not in groups: + return False + + # EDIT END + perms = {"all": ["all"], "write": ["all", "write"], "read": ["all", "write", "read"]} valid_perms = perms.get(permission) @@ -406,6 +415,7 @@ class PackageManager(object): try: p = Package.objects.get(uuid=package_id) if PackageManager.readable(user, p): + p = PackageManager.check_package_classification(user, p) return p else: # FIXME: use custom exception to be translatable to 403 in API @@ -415,6 +425,37 @@ class PackageManager(object): except Package.DoesNotExist: raise ValueError("Package with ID {} does not exist!".format(package_id)) + # EDIT START + + @staticmethod + def check_package_classification(user, pack: Package): + if pack.classification_level == Package.Classification.SECRET: + if pack.data_pool.user_member.filter(id=user.id).exists(): + return pack + + raise ValueError("Package is secret and not accessible to user!") + + else: + return pack + + + @staticmethod + def check_package_classifications(user, package_qs: QuerySet[Package]): + non_secret = package_qs.exclude(classification_level=Package.Classification.SECRET) + secret = package_qs.filter(classification_level=Package.Classification.SECRET) + + # TODO we should be able to do via the db + accessible_secret = [] + + for s_package in secret: + if s_package.data_pool.user_member.filter(id=user.id).exists(): + accessible_secret.append(s_package.pk) + + # Cannot combine a unique query with a non-unique query -> we have to call distinct + return Package.objects.filter(pk__in=accessible_secret).distinct() | non_secret.distinct() + + # EDIT END + @staticmethod def get_all_readable_packages(user, include_reviewed=False): # UserPermission only exists if at least read is granted... @@ -441,6 +482,10 @@ class PackageManager(object): qs = qs.distinct() + # EDIT START + qs = PackageManager.check_package_classifications(user, qs) + # EDIT END + return qs @staticmethod @@ -487,11 +532,11 @@ class PackageManager(object): qs = qs.distinct() - return qs + # EDIT START + qs = PackageManager.check_package_classifications(user, qs) + # EDIT END - @staticmethod - def get_packages(): - return Package.objects.all() + return qs @staticmethod @transaction.atomic @@ -596,6 +641,25 @@ class PackageManager(object): else: pack.reviewed = False + # EDIT START + if data.get("classification"): + if data["classification"] == "INTERNAL": + pack.classification = Package.Classification.RESTRICTED + elif data["classification"] == "RESTRICTED": + pack.classification = Package.Classification.RESTRICTED + elif data["classification"] == "SECRET": + pack.classification = Package.Classification.SECRET + + if not "datapool" in data: + raise ValueError("Missing datapool in package") + + g = Group.objects.get(uuid=data["datapool"].split('/')[-1]) + pack.data_pool = g + else: + raise ValueError(f"Invalid classification {data['classification']}") + + # EDIT END + pack.description = data["description"] pack.save() @@ -681,7 +745,13 @@ class PackageManager(object): default_structure = None for structure in compound["structures"]: - struc = CompoundStructure() + if structure.get("pesLink"): + from bayer.models import PESStructure + struc = PESStructure() + struc.pes_link = structure["pesLink"] + else: + 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() diff --git a/epdb/migrations/0001_initial.py b/epdb/migrations/0001_initial.py deleted file mode 100644 index e6cc4f69..00000000 --- a/epdb/migrations/0001_initial.py +++ /dev/null @@ -1,594 +0,0 @@ -# Generated by Django 5.2.1 on 2025-07-22 20:58 - -import datetime -import django.contrib.auth.models -import django.contrib.auth.validators -import django.contrib.postgres.fields -import django.db.models.deletion -import django.utils.timezone -import model_utils.fields -import uuid -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), - ('contenttypes', '0002_remove_content_type_name'), - ] - - operations = [ - migrations.CreateModel( - name='Compound', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), - ], - ), - migrations.CreateModel( - name='EPModel', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - ), - migrations.CreateModel( - name='Permission', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('permission', models.CharField(choices=[('read', 'Read'), ('write', 'Write'), ('all', 'All')], max_length=32)), - ], - ), - migrations.CreateModel( - name='License', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('link', models.URLField(verbose_name='link')), - ('image_link', models.URLField(verbose_name='Image link')), - ], - ), - migrations.CreateModel( - name='Rule', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - ), - migrations.CreateModel( - name='User', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('email', models.EmailField(max_length=254, unique=True)), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), - ], - options={ - 'verbose_name': 'user', - 'verbose_name_plural': 'users', - 'abstract': False, - }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), - ], - ), - migrations.CreateModel( - name='APIToken', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('hashed_key', models.CharField(max_length=128, unique=True)), - ('created', models.DateTimeField(auto_now_add=True)), - ('expires_at', models.DateTimeField(blank=True, default=datetime.datetime(2025, 10, 20, 20, 58, 48, 351675, tzinfo=datetime.timezone.utc), null=True)), - ('name', models.CharField(blank=True, help_text='Optional name for the token', max_length=100)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='CompoundStructure', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), - ('smiles', models.TextField(verbose_name='SMILES')), - ('canonical_smiles', models.TextField(verbose_name='Canonical SMILES')), - ('inchikey', models.TextField(max_length=27, verbose_name='InChIKey')), - ('normalized_structure', models.BooleanField(default=False)), - ('compound', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.compound')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='compound', - name='default_structure', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='compound_default_structure', to='epdb.compoundstructure', verbose_name='Default Structure'), - ), - migrations.CreateModel( - name='Edge', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), - ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - ), - migrations.CreateModel( - name='EnviFormer', - fields=[ - ('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')), - ('threshold', models.FloatField(default=0.5)), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.epmodel',), - ), - migrations.CreateModel( - name='PluginModel', - fields=[ - ('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.epmodel',), - ), - migrations.CreateModel( - name='RuleBaseRelativeReasoning', - fields=[ - ('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.epmodel',), - ), - migrations.CreateModel( - name='Group', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(verbose_name='Group name')), - ('public', models.BooleanField(default=False, verbose_name='Public Group')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('group_member', models.ManyToManyField(related_name='groups_in_group', to='epdb.group', verbose_name='Group member')), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Group Owner')), - ('user_member', models.ManyToManyField(related_name='users_in_group', to=settings.AUTH_USER_MODEL, verbose_name='User members')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='user', - name='default_group', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_group', to='epdb.group', verbose_name='Default Group'), - ), - migrations.CreateModel( - name='Node', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), - ('depth', models.IntegerField(verbose_name='Node depth')), - ('default_node_label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='default_node_structure', to='epdb.compoundstructure', verbose_name='Default Node Label')), - ('node_labels', models.ManyToManyField(related_name='node_structures', to='epdb.compoundstructure', verbose_name='All Node Labels')), - ('out_edges', models.ManyToManyField(to='epdb.edge', verbose_name='Outgoing Edges')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='edge', - name='end_nodes', - field=models.ManyToManyField(related_name='edge_products', to='epdb.node', verbose_name='End Nodes'), - ), - migrations.AddField( - model_name='edge', - name='start_nodes', - field=models.ManyToManyField(related_name='edge_educts', to='epdb.node', verbose_name='Start Nodes'), - ), - migrations.CreateModel( - name='Package', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('reviewed', models.BooleanField(default=False, verbose_name='Reviewstatus')), - ('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.license', verbose_name='License')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='epmodel', - name='package', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'), - ), - migrations.AddField( - model_name='compound', - name='package', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'), - ), - migrations.AddField( - model_name='user', - name='default_package', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.package', verbose_name='Default Package'), - ), - migrations.CreateModel( - name='SequentialRule', - fields=[ - ('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.rule',), - ), - migrations.CreateModel( - name='SimpleRule', - fields=[ - ('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.rule',), - ), - migrations.AddField( - model_name='rule', - name='package', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'), - ), - migrations.AddField( - model_name='rule', - name='polymorphic_ctype', - field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'), - ), - migrations.CreateModel( - name='Pathway', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), - ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='node', - name='pathway', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'), - ), - migrations.AddField( - model_name='edge', - name='pathway', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'), - ), - migrations.CreateModel( - name='Reaction', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), - ('multi_step', models.BooleanField(verbose_name='Multistep Reaction')), - ('medline_references', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), null=True, size=None, verbose_name='Medline References')), - ('educts', models.ManyToManyField(related_name='reaction_educts', to='epdb.compoundstructure', verbose_name='Educts')), - ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')), - ('products', models.ManyToManyField(related_name='reaction_products', to='epdb.compoundstructure', verbose_name='Products')), - ('rules', models.ManyToManyField(related_name='reaction_rule', to='epdb.rule', verbose_name='Rule')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='edge', - name='edge_label', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.reaction', verbose_name='Edge label'), - ), - migrations.CreateModel( - name='Scenario', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('scenario_date', models.CharField(default='No date', max_length=256)), - ('scenario_type', models.CharField(default='Not specified', max_length=256)), - ('additional_information', models.JSONField(verbose_name='Additional Information')), - ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')), - ('parent', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.scenario')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='rule', - name='scenarios', - field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), - ), - migrations.AddField( - model_name='reaction', - name='scenarios', - field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), - ), - migrations.AddField( - model_name='pathway', - name='scenarios', - field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), - ), - migrations.AddField( - model_name='node', - name='scenarios', - field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), - ), - migrations.AddField( - model_name='edge', - name='scenarios', - field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), - ), - migrations.AddField( - model_name='compoundstructure', - name='scenarios', - field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), - ), - migrations.AddField( - model_name='compound', - name='scenarios', - field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), - ), - migrations.CreateModel( - name='Setting', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('public', models.BooleanField(default=False)), - ('global_default', models.BooleanField(default=False)), - ('max_depth', models.IntegerField(default=5, verbose_name='Setting Max Depth')), - ('max_nodes', models.IntegerField(default=30, verbose_name='Setting Max Number of Nodes')), - ('model_threshold', models.FloatField(blank=True, default=0.25, null=True, verbose_name='Setting Model Threshold')), - ('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.epmodel', verbose_name='Setting EPModel')), - ('rule_packages', models.ManyToManyField(blank=True, related_name='setting_rule_packages', to='epdb.package', verbose_name='Setting Rule Packages')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='pathway', - name='setting', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Setting'), - ), - migrations.AddField( - model_name='user', - name='default_setting', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.setting', verbose_name='The users default settings'), - ), - migrations.CreateModel( - name='MLRelativeReasoning', - fields=[ - ('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')), - ('threshold', models.FloatField(default=0.5)), - ('model_status', models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL')), - ('eval_results', models.JSONField(blank=True, default=dict, null=True)), - ('data_packages', models.ManyToManyField(related_name='data_packages', to='epdb.package', verbose_name='Data Packages')), - ('eval_packages', models.ManyToManyField(related_name='eval_packages', to='epdb.package', verbose_name='Evaluation Packages')), - ('rule_packages', models.ManyToManyField(related_name='rule_packages', to='epdb.package', verbose_name='Rule Packages')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.epmodel',), - ), - migrations.CreateModel( - name='ApplicabilityDomain', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('num_neighbours', models.FloatField(default=5)), - ('reliability_threshold', models.FloatField(default=0.5)), - ('local_compatibilty_threshold', models.FloatField(default=0.5)), - ('model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.mlrelativereasoning')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='SimpleAmbitRule', - fields=[ - ('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')), - ('smirks', models.TextField(verbose_name='SMIRKS')), - ('reactant_filter_smarts', models.TextField(null=True, verbose_name='Reactant Filter SMARTS')), - ('product_filter_smarts', models.TextField(null=True, verbose_name='Product Filter SMARTS')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.simplerule',), - ), - migrations.CreateModel( - name='SimpleRDKitRule', - fields=[ - ('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')), - ('reaction_smarts', models.TextField(verbose_name='SMIRKS')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.simplerule',), - ), - migrations.CreateModel( - name='SequentialRuleOrdering', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('order_index', models.IntegerField()), - ('sequential_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.sequentialrule')), - ('simple_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.simplerule')), - ], - ), - migrations.AddField( - model_name='sequentialrule', - name='simple_rules', - field=models.ManyToManyField(through='epdb.SequentialRuleOrdering', to='epdb.simplerule', verbose_name='Simple rules'), - ), - migrations.CreateModel( - name='ParallelRule', - fields=[ - ('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')), - ('simple_rules', models.ManyToManyField(to='epdb.simplerule', verbose_name='Simple rules')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.rule',), - ), - migrations.AlterUniqueTogether( - name='compound', - unique_together={('uuid', 'package')}, - ), - migrations.CreateModel( - name='GroupPackagePermission', - fields=[ - ('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')), - ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')), - ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.group', verbose_name='Permission to')), - ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Permission on')), - ], - options={ - 'unique_together': {('package', 'group')}, - }, - bases=('epdb.permission',), - ), - migrations.CreateModel( - name='UserPackagePermission', - fields=[ - ('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')), - ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')), - ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Permission on')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')), - ], - options={ - 'unique_together': {('package', 'user')}, - }, - bases=('epdb.permission',), - ), - migrations.CreateModel( - name='UserSettingPermission', - fields=[ - ('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')), - ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')), - ('setting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Permission on')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')), - ], - options={ - 'unique_together': {('setting', 'user')}, - }, - bases=('epdb.permission',), - ), - ] diff --git a/epdb/migrations/0001_squashed_0003_applicabilitydomain_url_compound_url_and_more.py b/epdb/migrations/0001_squashed_0003_applicabilitydomain_url_compound_url_and_more.py deleted file mode 100644 index a6eb8f50..00000000 --- a/epdb/migrations/0001_squashed_0003_applicabilitydomain_url_compound_url_and_more.py +++ /dev/null @@ -1,1020 +0,0 @@ -# Generated by Django 5.2.1 on 2025-08-26 18:11 - -import django.contrib.auth.models -import django.contrib.auth.validators -import django.contrib.postgres.fields -import django.db.migrations.operations.special -import django.db.models.deletion -import django.utils.timezone -import model_utils.fields -import uuid -from django.conf import settings -from django.db import migrations, models - - -def populate_url(apps, schema_editor): - MODELS = [ - 'User', - 'Group', - 'Package', - 'Compound', - 'CompoundStructure', - 'Pathway', - 'Edge', - 'Node', - 'Reaction', - 'SimpleAmbitRule', - 'SimpleRDKitRule', - 'ParallelRule', - 'SequentialRule', - 'Scenario', - 'Setting', - 'MLRelativeReasoning', - 'EnviFormer', - 'ApplicabilityDomain', - ] - for model in MODELS: - obj_cls = apps.get_model("epdb", model) - for obj in obj_cls.objects.all(): - obj.url = assemble_url(obj) - if obj.url is None: - raise ValueError(f"Could not assemble url for {obj}") - obj.save() - - -def assemble_url(obj): - from django.conf import settings as s - match obj.__class__.__name__: - case 'User': - return '{}/user/{}'.format(s.SERVER_URL, obj.uuid) - case 'Group': - return '{}/group/{}'.format(s.SERVER_URL, obj.uuid) - case 'Package': - return '{}/package/{}'.format(s.SERVER_URL, obj.uuid) - case 'Compound': - return '{}/compound/{}'.format(obj.package.url, obj.uuid) - case 'CompoundStructure': - return '{}/structure/{}'.format(obj.compound.url, obj.uuid) - case 'SimpleAmbitRule': - return '{}/simple-ambit-rule/{}'.format(obj.package.url, obj.uuid) - case 'SimpleRDKitRule': - return '{}/simple-rdkit-rule/{}'.format(obj.package.url, obj.uuid) - case 'ParallelRule': - return '{}/parallel-rule/{}'.format(obj.package.url, obj.uuid) - case 'SequentialRule': - return '{}/sequential-rule/{}'.format(obj.compound.url, obj.uuid) - case 'Reaction': - return '{}/reaction/{}'.format(obj.package.url, obj.uuid) - case 'Pathway': - return '{}/pathway/{}'.format(obj.package.url, obj.uuid) - case 'Node': - return '{}/node/{}'.format(obj.pathway.url, obj.uuid) - case 'Edge': - return '{}/edge/{}'.format(obj.pathway.url, obj.uuid) - case 'MLRelativeReasoning': - return '{}/model/{}'.format(obj.package.url, obj.uuid) - case 'EnviFormer': - return '{}/model/{}'.format(obj.package.url, obj.uuid) - case 'ApplicabilityDomain': - return '{}/model/{}/applicability-domain/{}'.format(obj.model.package.url, obj.model.uuid, obj.uuid) - case 'Scenario': - return '{}/scenario/{}'.format(obj.package.url, obj.uuid) - case 'Setting': - return '{}/setting/{}'.format(s.SERVER_URL, obj.uuid) - case _: - raise ValueError(f"Unknown model {obj.__class__.__name__}") - - -class Migration(migrations.Migration): - replaces = [('epdb', '0001_initial'), ('epdb', '0002_externaldatabase_alter_apitoken_options_and_more'), - ('epdb', '0003_applicabilitydomain_url_compound_url_and_more')] - - initial = True - - dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), - ('contenttypes', '0002_remove_content_type_name'), - ] - - operations = [ - migrations.CreateModel( - name='Compound', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('aliases', - django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, - verbose_name='Aliases')), - ], - ), - migrations.CreateModel( - name='EPModel', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('polymorphic_ctype', - models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='polymorphic_%(app_label)s.%(class)s_set+', - to='contenttypes.contenttype')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - ), - migrations.CreateModel( - name='Permission', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('permission', - models.CharField(choices=[('read', 'Read'), ('write', 'Write'), ('all', 'All')], max_length=32)), - ], - ), - migrations.CreateModel( - name='License', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('link', models.URLField(verbose_name='link')), - ('image_link', models.URLField(verbose_name='Image link')), - ], - ), - migrations.CreateModel( - name='Rule', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('aliases', - django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, - verbose_name='Aliases')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - ), - migrations.CreateModel( - name='User', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, - help_text='Designates that this user has all permissions without explicitly assigning them.', - verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, - help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', - max_length=150, unique=True, - validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], - verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('is_staff', models.BooleanField(default=False, - help_text='Designates whether the user can log into this admin site.', - verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, - help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', - verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('email', models.EmailField(max_length=254, unique=True)), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('groups', models.ManyToManyField(blank=True, - help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', - related_name='user_set', related_query_name='user', to='auth.group', - verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', - related_name='user_set', related_query_name='user', - to='auth.permission', verbose_name='user permissions')), - ], - options={ - 'verbose_name': 'user', - 'verbose_name_plural': 'users', - 'abstract': False, - }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), - ], - ), - migrations.CreateModel( - name='CompoundStructure', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('aliases', - django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, - verbose_name='Aliases')), - ('smiles', models.TextField(verbose_name='SMILES')), - ('canonical_smiles', models.TextField(verbose_name='Canonical SMILES')), - ('inchikey', models.TextField(max_length=27, verbose_name='InChIKey')), - ('normalized_structure', models.BooleanField(default=False)), - ('compound', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.compound')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='compound', - name='default_structure', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='compound_default_structure', to='epdb.compoundstructure', - verbose_name='Default Structure'), - ), - migrations.CreateModel( - name='Edge', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('aliases', - django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, - verbose_name='Aliases')), - ('polymorphic_ctype', - models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='polymorphic_%(app_label)s.%(class)s_set+', - to='contenttypes.contenttype')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - ), - migrations.CreateModel( - name='EnviFormer', - fields=[ - ('epmodel_ptr', - models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, - primary_key=True, serialize=False, to='epdb.epmodel')), - ('threshold', models.FloatField(default=0.5)), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.epmodel',), - ), - migrations.CreateModel( - name='PluginModel', - fields=[ - ('epmodel_ptr', - models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, - primary_key=True, serialize=False, to='epdb.epmodel')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.epmodel',), - ), - migrations.CreateModel( - name='RuleBaseRelativeReasoning', - fields=[ - ('epmodel_ptr', - models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, - primary_key=True, serialize=False, to='epdb.epmodel')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.epmodel',), - ), - migrations.CreateModel( - name='Group', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(verbose_name='Group name')), - ('public', models.BooleanField(default=False, verbose_name='Public Group')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('group_member', - models.ManyToManyField(related_name='groups_in_group', to='epdb.group', verbose_name='Group member')), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, - verbose_name='Group Owner')), - ('user_member', models.ManyToManyField(related_name='users_in_group', to=settings.AUTH_USER_MODEL, - verbose_name='User members')), - ('url', models.TextField(null=True, verbose_name='URL')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='user', - name='default_group', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='default_group', to='epdb.group', verbose_name='Default Group'), - ), - migrations.CreateModel( - name='Node', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('aliases', - django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, - verbose_name='Aliases')), - ('depth', models.IntegerField(verbose_name='Node depth')), - ('default_node_label', - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='default_node_structure', - to='epdb.compoundstructure', verbose_name='Default Node Label')), - ('node_labels', models.ManyToManyField(related_name='node_structures', to='epdb.compoundstructure', - verbose_name='All Node Labels')), - ('out_edges', models.ManyToManyField(to='epdb.edge', verbose_name='Outgoing Edges')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='edge', - name='end_nodes', - field=models.ManyToManyField(related_name='edge_products', to='epdb.node', verbose_name='End Nodes'), - ), - migrations.AddField( - model_name='edge', - name='start_nodes', - field=models.ManyToManyField(related_name='edge_educts', to='epdb.node', verbose_name='Start Nodes'), - ), - migrations.CreateModel( - name='Package', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('reviewed', models.BooleanField(default=False, verbose_name='Reviewstatus')), - ('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - to='epdb.license', verbose_name='License')), - ('url', models.TextField(null=True, verbose_name='URL')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='epmodel', - name='package', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', - verbose_name='Package'), - ), - migrations.AddField( - model_name='compound', - name='package', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', - verbose_name='Package'), - ), - migrations.AddField( - model_name='user', - name='default_package', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.package', - verbose_name='Default Package'), - ), - migrations.CreateModel( - name='SequentialRule', - fields=[ - ('rule_ptr', - models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, - primary_key=True, serialize=False, to='epdb.rule')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.rule',), - ), - migrations.CreateModel( - name='SimpleRule', - fields=[ - ('rule_ptr', - models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, - primary_key=True, serialize=False, to='epdb.rule')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.rule',), - ), - migrations.AddField( - model_name='rule', - name='package', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', - verbose_name='Package'), - ), - migrations.AddField( - model_name='rule', - name='polymorphic_ctype', - field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='polymorphic_%(app_label)s.%(class)s_set+', - to='contenttypes.contenttype'), - ), - migrations.CreateModel( - name='Pathway', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('aliases', - django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, - verbose_name='Aliases')), - ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', - verbose_name='Package')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='node', - name='pathway', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', - verbose_name='belongs to'), - ), - migrations.AddField( - model_name='edge', - name='pathway', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', - verbose_name='belongs to'), - ), - migrations.CreateModel( - name='Reaction', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('aliases', - django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, - verbose_name='Aliases')), - ('multi_step', models.BooleanField(verbose_name='Multistep Reaction')), - ('medline_references', - django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), null=True, size=None, - verbose_name='Medline References')), - ('educts', models.ManyToManyField(related_name='reaction_educts', to='epdb.compoundstructure', - verbose_name='Educts')), - ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', - verbose_name='Package')), - ('products', models.ManyToManyField(related_name='reaction_products', to='epdb.compoundstructure', - verbose_name='Products')), - ('rules', models.ManyToManyField(related_name='reaction_rule', to='epdb.rule', verbose_name='Rule')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='edge', - name='edge_label', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.reaction', - verbose_name='Edge label'), - ), - migrations.CreateModel( - name='Scenario', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('scenario_date', models.CharField(default='No date', max_length=256)), - ('scenario_type', models.CharField(default='Not specified', max_length=256)), - ('additional_information', models.JSONField(verbose_name='Additional Information')), - ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', - verbose_name='Package')), - ('parent', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, - to='epdb.scenario')), - ('url', models.TextField(null=True, verbose_name='URL')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='rule', - name='scenarios', - field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), - ), - migrations.AddField( - model_name='reaction', - name='scenarios', - field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), - ), - migrations.AddField( - model_name='pathway', - name='scenarios', - field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), - ), - migrations.AddField( - model_name='node', - name='scenarios', - field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), - ), - migrations.AddField( - model_name='edge', - name='scenarios', - field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), - ), - migrations.AddField( - model_name='compoundstructure', - name='scenarios', - field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), - ), - migrations.AddField( - model_name='compound', - name='scenarios', - field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), - ), - migrations.CreateModel( - name='Setting', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('public', models.BooleanField(default=False)), - ('global_default', models.BooleanField(default=False)), - ('max_depth', models.IntegerField(default=5, verbose_name='Setting Max Depth')), - ('max_nodes', models.IntegerField(default=30, verbose_name='Setting Max Number of Nodes')), - ('model_threshold', - models.FloatField(blank=True, default=0.25, null=True, verbose_name='Setting Model Threshold')), - ('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - to='epdb.epmodel', verbose_name='Setting EPModel')), - ('rule_packages', - models.ManyToManyField(blank=True, related_name='setting_rule_packages', to='epdb.package', - verbose_name='Setting Rule Packages')), - ('url', models.TextField(null=True, verbose_name='URL')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='pathway', - name='setting', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, - to='epdb.setting', verbose_name='Setting'), - ), - migrations.AddField( - model_name='user', - name='default_setting', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.setting', - verbose_name='The users default settings'), - ), - migrations.CreateModel( - name='MLRelativeReasoning', - fields=[ - ('epmodel_ptr', - models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, - primary_key=True, serialize=False, to='epdb.epmodel')), - ('threshold', models.FloatField(default=0.5)), - ('model_status', models.CharField( - choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), - ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', - 'Model is built and can be used for predictions, Model is not evaluated yet.'), - ('EVALUATING', 'Model is evaluating'), - ('FINISHED', 'Model has finished building and evaluation.'), - ('ERROR', 'Model has failed.')], default='INITIAL')), - ('eval_results', models.JSONField(blank=True, default=dict, null=True)), - ('data_packages', - models.ManyToManyField(related_name='data_packages', to='epdb.package', verbose_name='Data Packages')), - ('eval_packages', models.ManyToManyField(related_name='eval_packages', to='epdb.package', - verbose_name='Evaluation Packages')), - ('rule_packages', - models.ManyToManyField(related_name='rule_packages', to='epdb.package', verbose_name='Rule Packages')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.epmodel',), - ), - migrations.CreateModel( - name='SimpleAmbitRule', - fields=[ - ('simplerule_ptr', - models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, - primary_key=True, serialize=False, to='epdb.simplerule')), - ('smirks', models.TextField(verbose_name='SMIRKS')), - ('reactant_filter_smarts', models.TextField(null=True, verbose_name='Reactant Filter SMARTS')), - ('product_filter_smarts', models.TextField(null=True, verbose_name='Product Filter SMARTS')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.simplerule',), - ), - migrations.CreateModel( - name='SimpleRDKitRule', - fields=[ - ('simplerule_ptr', - models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, - primary_key=True, serialize=False, to='epdb.simplerule')), - ('reaction_smarts', models.TextField(verbose_name='SMIRKS')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.simplerule',), - ), - migrations.CreateModel( - name='SequentialRuleOrdering', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('order_index', models.IntegerField()), - ('sequential_rule', - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.sequentialrule')), - ('simple_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.simplerule')), - ], - ), - migrations.AddField( - model_name='sequentialrule', - name='simple_rules', - field=models.ManyToManyField(through='epdb.SequentialRuleOrdering', to='epdb.simplerule', - verbose_name='Simple rules'), - ), - migrations.CreateModel( - name='ParallelRule', - fields=[ - ('rule_ptr', - models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, - primary_key=True, serialize=False, to='epdb.rule')), - ('simple_rules', models.ManyToManyField(to='epdb.simplerule', verbose_name='Simple rules')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('epdb.rule',), - ), - migrations.AlterUniqueTogether( - name='compound', - unique_together={('uuid', 'package')}, - ), - migrations.CreateModel( - name='GroupPackagePermission', - fields=[ - ('permission_ptr', - models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, - to='epdb.permission')), - ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, - verbose_name='UUID of this object')), - ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.group', - verbose_name='Permission to')), - ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', - verbose_name='Permission on')), - ], - options={ - 'unique_together': {('package', 'group')}, - }, - bases=('epdb.permission',), - ), - migrations.CreateModel( - name='UserPackagePermission', - fields=[ - ('permission_ptr', - models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, - to='epdb.permission')), - ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, - verbose_name='UUID of this object')), - ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', - verbose_name='Permission on')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, - verbose_name='Permission to')), - ], - options={ - 'unique_together': {('package', 'user')}, - }, - bases=('epdb.permission',), - ), - migrations.CreateModel( - name='UserSettingPermission', - fields=[ - ('permission_ptr', - models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, - to='epdb.permission')), - ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, - verbose_name='UUID of this object')), - ('setting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', - verbose_name='Permission on')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, - verbose_name='Permission to')), - ], - options={ - 'unique_together': {('setting', 'user')}, - }, - bases=('epdb.permission',), - ), - migrations.CreateModel( - name='ExternalDatabase', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), - ('name', models.CharField(max_length=100, unique=True, verbose_name='Database Name')), - ('full_name', models.CharField(blank=True, max_length=255, verbose_name='Full Database Name')), - ('description', models.TextField(blank=True, verbose_name='Description')), - ('base_url', models.URLField(blank=True, null=True, verbose_name='Base URL')), - ('url_pattern', models.CharField(blank=True, - help_text="URL pattern with {id} placeholder, e.g., 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'", - max_length=500, verbose_name='URL Pattern')), - ('is_active', models.BooleanField(default=True, verbose_name='Is Active')), - ], - options={ - 'verbose_name': 'External Database', - 'verbose_name_plural': 'External Databases', - 'db_table': 'epdb_external_database', - 'ordering': ['name'], - }, - ), - migrations.AlterModelOptions( - name='edge', - options={}, - ), - migrations.RemoveField( - model_name='edge', - name='polymorphic_ctype', - ), - migrations.CreateModel( - name='ApplicabilityDomain', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), - ('name', models.TextField(default='no name', verbose_name='Name')), - ('description', models.TextField(default='no description', verbose_name='Descriptions')), - ('kv', models.JSONField(blank=True, default=dict, null=True)), - ('num_neighbours', models.IntegerField(default=5)), - ('reliability_threshold', models.FloatField(default=0.5)), - ('local_compatibilty_threshold', models.FloatField(default=0.5)), - ('model', - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.mlrelativereasoning')), - ('functional_groups', models.JSONField(blank=True, default=dict, null=True)), - ('url', models.TextField(null=True, verbose_name='URL')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='mlrelativereasoning', - name='app_domain', - field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, - to='epdb.applicabilitydomain'), - ), - migrations.CreateModel( - name='APIToken', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('hashed_key', - models.CharField(help_text='SHA-256 hash of the token key', max_length=128, unique=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('expires_at', - models.DateTimeField(blank=True, help_text='Token expiration time (null for no expiration)', - null=True)), - ('name', models.CharField(help_text='Descriptive name for this token', max_length=100)), - ('user', - models.ForeignKey(help_text='User who owns this token', on_delete=django.db.models.deletion.CASCADE, - related_name='api_tokens', to=settings.AUTH_USER_MODEL)), - ('is_active', models.BooleanField(default=True, help_text='Whether this token is active')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ], - options={ - 'ordering': ['-created'], - 'verbose_name': 'API Token', - 'verbose_name_plural': 'API Tokens', - 'db_table': 'epdb_api_token', - }, - ), - migrations.CreateModel( - name='ExternalIdentifier', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, - verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, - verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), - ('object_id', models.IntegerField()), - ('identifier_value', models.CharField(max_length=255, verbose_name='Identifier Value')), - ('url', models.URLField(blank=True, null=True, verbose_name='Direct URL')), - ('is_primary', - models.BooleanField(default=False, help_text='Mark this as the primary identifier for this database', - verbose_name='Is Primary')), - ('content_type', - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), - ('database', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.externaldatabase', - verbose_name='External Database')), - ], - options={ - 'verbose_name': 'External Identifier', - 'verbose_name_plural': 'External Identifiers', - 'db_table': 'epdb_external_identifier', - 'indexes': [models.Index(fields=['content_type', 'object_id'], name='epdb_extern_content_b76813_idx'), - models.Index(fields=['database', 'identifier_value'], - name='epdb_extern_databas_486422_idx')], - 'unique_together': {('content_type', 'object_id', 'database', 'identifier_value')}, - }, - ), - migrations.AddField( - model_name='compound', - name='url', - field=models.TextField(null=True, verbose_name='URL'), - ), - migrations.AddField( - model_name='compoundstructure', - name='url', - field=models.TextField(null=True, verbose_name='URL'), - ), - migrations.AddField( - model_name='edge', - name='url', - field=models.TextField(null=True, verbose_name='URL'), - ), - migrations.AddField( - model_name='epmodel', - name='url', - field=models.TextField(null=True, verbose_name='URL'), - ), - migrations.AddField( - model_name='node', - name='url', - field=models.TextField(null=True, verbose_name='URL'), - ), - migrations.AddField( - model_name='pathway', - name='url', - field=models.TextField(null=True, verbose_name='URL'), - ), - migrations.AddField( - model_name='reaction', - name='url', - field=models.TextField(null=True, verbose_name='URL'), - ), - migrations.AddField( - model_name='rule', - name='url', - field=models.TextField(null=True, verbose_name='URL'), - ), - migrations.AddField( - model_name='user', - name='url', - field=models.TextField(null=True, verbose_name='URL'), - ), - migrations.RunPython( - code=populate_url, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), - migrations.AlterField( - model_name='applicabilitydomain', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='compound', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='compoundstructure', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='edge', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='epmodel', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='group', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='node', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='package', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='pathway', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='reaction', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='rule', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='scenario', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='setting', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='user', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - ] diff --git a/epdb/migrations/0002_externaldatabase_alter_apitoken_options_and_more.py b/epdb/migrations/0002_externaldatabase_alter_apitoken_options_and_more.py deleted file mode 100644 index 44215433..00000000 --- a/epdb/migrations/0002_externaldatabase_alter_apitoken_options_and_more.py +++ /dev/null @@ -1,128 +0,0 @@ -# Generated by Django 5.2.1 on 2025-08-25 18:07 - -import django.db.models.deletion -import django.utils.timezone -import model_utils.fields -import uuid -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('epdb', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='ExternalDatabase', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), - ('name', models.CharField(max_length=100, unique=True, verbose_name='Database Name')), - ('full_name', models.CharField(blank=True, max_length=255, verbose_name='Full Database Name')), - ('description', models.TextField(blank=True, verbose_name='Description')), - ('base_url', models.URLField(blank=True, null=True, verbose_name='Base URL')), - ('url_pattern', models.CharField(blank=True, help_text="URL pattern with {id} placeholder, e.g., 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'", max_length=500, verbose_name='URL Pattern')), - ('is_active', models.BooleanField(default=True, verbose_name='Is Active')), - ], - options={ - 'verbose_name': 'External Database', - 'verbose_name_plural': 'External Databases', - 'db_table': 'epdb_external_database', - 'ordering': ['name'], - }, - ), - migrations.AlterModelOptions( - name='apitoken', - options={'ordering': ['-created'], 'verbose_name': 'API Token', 'verbose_name_plural': 'API Tokens'}, - ), - migrations.AlterModelOptions( - name='edge', - options={}, - ), - migrations.RemoveField( - model_name='edge', - name='polymorphic_ctype', - ), - migrations.AddField( - model_name='apitoken', - name='is_active', - field=models.BooleanField(default=True, help_text='Whether this token is active'), - ), - migrations.AddField( - model_name='apitoken', - name='modified', - field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'), - ), - migrations.AddField( - model_name='applicabilitydomain', - name='functional_groups', - field=models.JSONField(blank=True, default=dict, null=True), - ), - migrations.AddField( - model_name='mlrelativereasoning', - name='app_domain', - field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain'), - ), - migrations.AlterField( - model_name='apitoken', - name='created', - field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'), - ), - migrations.AlterField( - model_name='apitoken', - name='expires_at', - field=models.DateTimeField(blank=True, help_text='Token expiration time (null for no expiration)', null=True), - ), - migrations.AlterField( - model_name='apitoken', - name='hashed_key', - field=models.CharField(help_text='SHA-256 hash of the token key', max_length=128, unique=True), - ), - migrations.AlterField( - model_name='apitoken', - name='name', - field=models.CharField(help_text='Descriptive name for this token', max_length=100), - ), - migrations.AlterField( - model_name='apitoken', - name='user', - field=models.ForeignKey(help_text='User who owns this token', on_delete=django.db.models.deletion.CASCADE, related_name='api_tokens', to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='applicabilitydomain', - name='num_neighbours', - field=models.IntegerField(default=5), - ), - migrations.AlterModelTable( - name='apitoken', - table='epdb_api_token', - ), - migrations.CreateModel( - name='ExternalIdentifier', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), - ('object_id', models.IntegerField()), - ('identifier_value', models.CharField(max_length=255, verbose_name='Identifier Value')), - ('url', models.URLField(blank=True, null=True, verbose_name='Direct URL')), - ('is_primary', models.BooleanField(default=False, help_text='Mark this as the primary identifier for this database', verbose_name='Is Primary')), - ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), - ('database', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.externaldatabase', verbose_name='External Database')), - ], - options={ - 'verbose_name': 'External Identifier', - 'verbose_name_plural': 'External Identifiers', - 'db_table': 'epdb_external_identifier', - 'indexes': [models.Index(fields=['content_type', 'object_id'], name='epdb_extern_content_b76813_idx'), models.Index(fields=['database', 'identifier_value'], name='epdb_extern_databas_486422_idx')], - 'unique_together': {('content_type', 'object_id', 'database', 'identifier_value')}, - }, - ), - ] diff --git a/epdb/migrations/0003_applicabilitydomain_url_compound_url_and_more.py b/epdb/migrations/0003_applicabilitydomain_url_compound_url_and_more.py deleted file mode 100644 index 5cb39127..00000000 --- a/epdb/migrations/0003_applicabilitydomain_url_compound_url_and_more.py +++ /dev/null @@ -1,228 +0,0 @@ -# Generated by Django 5.2.1 on 2025-08-26 17:05 - -from django.db import migrations, models - - -def populate_url(apps, schema_editor): - MODELS = [ - 'User', - 'Group', - 'Package', - 'Compound', - 'CompoundStructure', - 'Pathway', - 'Edge', - 'Node', - 'Reaction', - 'SimpleAmbitRule', - 'SimpleRDKitRule', - 'ParallelRule', - 'SequentialRule', - 'Scenario', - 'Setting', - 'MLRelativeReasoning', - 'EnviFormer', - 'ApplicabilityDomain', - ] - for model in MODELS: - obj_cls = apps.get_model("epdb", model) - for obj in obj_cls.objects.all(): - obj.url = assemble_url(obj) - if obj.url is None: - raise ValueError(f"Could not assemble url for {obj}") - obj.save() - - -def assemble_url(obj): - from django.conf import settings as s - match obj.__class__.__name__: - case 'User': - return '{}/user/{}'.format(s.SERVER_URL, obj.uuid) - case 'Group': - return '{}/group/{}'.format(s.SERVER_URL, obj.uuid) - case 'Package': - return '{}/package/{}'.format(s.SERVER_URL, obj.uuid) - case 'Compound': - return '{}/compound/{}'.format(obj.package.url, obj.uuid) - case 'CompoundStructure': - return '{}/structure/{}'.format(obj.compound.url, obj.uuid) - case 'SimpleAmbitRule': - return '{}/simple-ambit-rule/{}'.format(obj.package.url, obj.uuid) - case 'SimpleRDKitRule': - return '{}/simple-rdkit-rule/{}'.format(obj.package.url, obj.uuid) - case 'ParallelRule': - return '{}/parallel-rule/{}'.format(obj.package.url, obj.uuid) - case 'SequentialRule': - return '{}/sequential-rule/{}'.format(obj.compound.url, obj.uuid) - case 'Reaction': - return '{}/reaction/{}'.format(obj.package.url, obj.uuid) - case 'Pathway': - return '{}/pathway/{}'.format(obj.package.url, obj.uuid) - case 'Node': - return '{}/node/{}'.format(obj.pathway.url, obj.uuid) - case 'Edge': - return '{}/edge/{}'.format(obj.pathway.url, obj.uuid) - case 'MLRelativeReasoning': - return '{}/model/{}'.format(obj.package.url, obj.uuid) - case 'EnviFormer': - return '{}/model/{}'.format(obj.package.url, obj.uuid) - case 'ApplicabilityDomain': - return '{}/model/{}/applicability-domain/{}'.format(obj.model.package.url, obj.model.uuid, obj.uuid) - case 'Scenario': - return '{}/scenario/{}'.format(obj.package.url, obj.uuid) - case 'Setting': - return '{}/setting/{}'.format(s.SERVER_URL, obj.uuid) - case _: - raise ValueError(f"Unknown model {obj.__class__.__name__}") - - -class Migration(migrations.Migration): - dependencies = [ - ('epdb', '0002_externaldatabase_alter_apitoken_options_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='applicabilitydomain', - name='url', - field=models.TextField(null=True, unique=False, verbose_name='URL'), - ), - migrations.AddField( - model_name='compound', - name='url', - field=models.TextField(null=True, unique=False, verbose_name='URL'), - ), - migrations.AddField( - model_name='compoundstructure', - name='url', - field=models.TextField(null=True, unique=False, verbose_name='URL'), - ), - migrations.AddField( - model_name='edge', - name='url', - field=models.TextField(null=True, unique=False, verbose_name='URL'), - ), - migrations.AddField( - model_name='epmodel', - name='url', - field=models.TextField(null=True, unique=False, verbose_name='URL'), - ), - migrations.AddField( - model_name='group', - name='url', - field=models.TextField(null=True, unique=False, verbose_name='URL'), - ), - migrations.AddField( - model_name='node', - name='url', - field=models.TextField(null=True, unique=False, verbose_name='URL'), - ), - migrations.AddField( - model_name='package', - name='url', - field=models.TextField(null=True, unique=False, verbose_name='URL'), - ), - migrations.AddField( - model_name='pathway', - name='url', - field=models.TextField(null=True, unique=False, verbose_name='URL'), - ), - migrations.AddField( - model_name='reaction', - name='url', - field=models.TextField(null=True, unique=False, verbose_name='URL'), - ), - migrations.AddField( - model_name='rule', - name='url', - field=models.TextField(null=True, unique=False, verbose_name='URL'), - ), - migrations.AddField( - model_name='scenario', - name='url', - field=models.TextField(null=True, unique=False, verbose_name='URL'), - ), - migrations.AddField( - model_name='setting', - name='url', - field=models.TextField(null=True, unique=False, verbose_name='URL'), - ), - migrations.AddField( - model_name='user', - name='url', - field=models.TextField(null=True, unique=False, verbose_name='URL'), - ), - - migrations.RunPython(populate_url, reverse_code=migrations.RunPython.noop), - - migrations.AlterField( - model_name='applicabilitydomain', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='compound', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='compoundstructure', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='edge', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='epmodel', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='group', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='node', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='package', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='pathway', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='reaction', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='rule', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='scenario', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='setting', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - migrations.AlterField( - model_name='user', - name='url', - field=models.TextField(null=True, unique=True, verbose_name='URL'), - ), - ] diff --git a/epdb/migrations/0004_alter_mlrelativereasoning_options_and_more.py b/epdb/migrations/0004_alter_mlrelativereasoning_options_and_more.py deleted file mode 100644 index 674a73dc..00000000 --- a/epdb/migrations/0004_alter_mlrelativereasoning_options_and_more.py +++ /dev/null @@ -1,55 +0,0 @@ -# Generated by Django 5.2.1 on 2025-09-09 09:21 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('epdb', '0001_squashed_0003_applicabilitydomain_url_compound_url_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='mlrelativereasoning', - options={}, - ), - migrations.AlterField( - model_name='mlrelativereasoning', - name='data_packages', - field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to='epdb.package', verbose_name='Data Packages'), - ), - migrations.AlterField( - model_name='mlrelativereasoning', - name='eval_packages', - field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_eval_packages', to='epdb.package', verbose_name='Evaluation Packages'), - ), - migrations.AlterField( - model_name='mlrelativereasoning', - name='rule_packages', - field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_rule_packages', to='epdb.package', verbose_name='Rule Packages'), - ), - migrations.CreateModel( - name='RuleBasedRelativeReasoning', - fields=[ - ('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')), - ('threshold', models.FloatField(default=0.5)), - ('eval_results', models.JSONField(blank=True, default=dict, null=True)), - ('model_status', models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL')), - ('min_count', models.IntegerField(default=10)), - ('max_count', models.IntegerField(default=0)), - ('app_domain', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain')), - ('data_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to='epdb.package', verbose_name='Data Packages')), - ('eval_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_eval_packages', to='epdb.package', verbose_name='Evaluation Packages')), - ('rule_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_rule_packages', to='epdb.package', verbose_name='Rule Packages')), - ], - options={ - 'abstract': False, - }, - bases=('epdb.epmodel',), - ), - migrations.DeleteModel( - name='RuleBaseRelativeReasoning', - ), - ] diff --git a/epdb/migrations/0005_alter_group_group_member.py b/epdb/migrations/0005_alter_group_group_member.py deleted file mode 100644 index 62aa724a..00000000 --- a/epdb/migrations/0005_alter_group_group_member.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.1 on 2025-09-11 06:21 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('epdb', '0004_alter_mlrelativereasoning_options_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='group', - name='group_member', - field=models.ManyToManyField(blank=True, related_name='groups_in_group', to='epdb.group', verbose_name='Group member'), - ), - ] diff --git a/epdb/migrations/0006_mlrelativereasoning_multigen_eval_and_more.py b/epdb/migrations/0006_mlrelativereasoning_multigen_eval_and_more.py deleted file mode 100644 index 008214ae..00000000 --- a/epdb/migrations/0006_mlrelativereasoning_multigen_eval_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.2.1 on 2025-09-18 06:42 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('epdb', '0005_alter_group_group_member'), - ] - - operations = [ - migrations.AddField( - model_name='mlrelativereasoning', - name='multigen_eval', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='rulebasedrelativereasoning', - name='multigen_eval', - field=models.BooleanField(default=False), - ), - ] diff --git a/epdb/migrations/0007_alter_enviformer_options_enviformer_app_domain_and_more.py b/epdb/migrations/0007_alter_enviformer_options_enviformer_app_domain_and_more.py deleted file mode 100644 index 5ffbe7f6..00000000 --- a/epdb/migrations/0007_alter_enviformer_options_enviformer_app_domain_and_more.py +++ /dev/null @@ -1,53 +0,0 @@ -# Generated by Django 5.2.1 on 2025-10-07 08:19 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('epdb', '0006_mlrelativereasoning_multigen_eval_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='enviformer', - options={}, - ), - migrations.AddField( - model_name='enviformer', - name='app_domain', - field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain'), - ), - migrations.AddField( - model_name='enviformer', - name='data_packages', - field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to='epdb.package', verbose_name='Data Packages'), - ), - migrations.AddField( - model_name='enviformer', - name='eval_packages', - field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_eval_packages', to='epdb.package', verbose_name='Evaluation Packages'), - ), - migrations.AddField( - model_name='enviformer', - name='eval_results', - field=models.JSONField(blank=True, default=dict, null=True), - ), - migrations.AddField( - model_name='enviformer', - name='model_status', - field=models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL'), - ), - migrations.AddField( - model_name='enviformer', - name='multigen_eval', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='enviformer', - name='rule_packages', - field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_rule_packages', to='epdb.package', verbose_name='Rule Packages'), - ), - ] diff --git a/epdb/migrations/0008_enzymelink.py b/epdb/migrations/0008_enzymelink.py deleted file mode 100644 index 35d0a950..00000000 --- a/epdb/migrations/0008_enzymelink.py +++ /dev/null @@ -1,64 +0,0 @@ -# Generated by Django 5.2.7 on 2025-10-10 06:58 - -import django.db.models.deletion -import django.utils.timezone -import model_utils.fields -import uuid -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("epdb", "0007_alter_enviformer_options_enviformer_app_domain_and_more"), - ] - - operations = [ - migrations.CreateModel( - name="EnzymeLink", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, primary_key=True, serialize=False, verbose_name="ID" - ), - ), - ( - "created", - model_utils.fields.AutoCreatedField( - default=django.utils.timezone.now, editable=False, verbose_name="created" - ), - ), - ( - "modified", - model_utils.fields.AutoLastModifiedField( - default=django.utils.timezone.now, editable=False, verbose_name="modified" - ), - ), - ( - "uuid", - models.UUIDField( - default=uuid.uuid4, unique=True, verbose_name="UUID of this object" - ), - ), - ("name", models.TextField(default="no name", verbose_name="Name")), - ( - "description", - models.TextField(default="no description", verbose_name="Descriptions"), - ), - ("url", models.TextField(null=True, unique=True, verbose_name="URL")), - ("kv", models.JSONField(blank=True, default=dict, null=True)), - ("ec_number", models.TextField(verbose_name="EC Number")), - ("classification_level", models.IntegerField(verbose_name="Classification Level")), - ("linking_method", models.TextField(verbose_name="Linking Method")), - ("edge_evidence", models.ManyToManyField(to="epdb.edge")), - ("reaction_evidence", models.ManyToManyField(to="epdb.reaction")), - ( - "rule", - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="epdb.rule"), - ), - ], - options={ - "abstract": False, - }, - ), - ] diff --git a/epdb/migrations/0009_joblog.py b/epdb/migrations/0009_joblog.py deleted file mode 100644 index 5c731eb1..00000000 --- a/epdb/migrations/0009_joblog.py +++ /dev/null @@ -1,66 +0,0 @@ -# Generated by Django 5.2.7 on 2025-10-27 09:39 - -import django.db.models.deletion -import django.utils.timezone -import model_utils.fields -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("epdb", "0008_enzymelink"), - ] - - operations = [ - migrations.CreateModel( - name="JobLog", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, primary_key=True, serialize=False, verbose_name="ID" - ), - ), - ( - "created", - model_utils.fields.AutoCreatedField( - default=django.utils.timezone.now, editable=False, verbose_name="created" - ), - ), - ( - "modified", - model_utils.fields.AutoLastModifiedField( - default=django.utils.timezone.now, editable=False, verbose_name="modified" - ), - ), - ("task_id", models.UUIDField(unique=True)), - ("job_name", models.TextField()), - ( - "status", - models.CharField( - choices=[ - ("INITIAL", "Initial"), - ("SUCCESS", "Success"), - ("FAILURE", "Failure"), - ("REVOKED", "Revoked"), - ("IGNORED", "Ignored"), - ], - default="INITIAL", - max_length=20, - ), - ), - ("done_at", models.DateTimeField(blank=True, default=None, null=True)), - ("task_result", models.TextField(blank=True, default=None, null=True)), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL - ), - ), - ], - options={ - "abstract": False, - }, - ), - ] diff --git a/epdb/migrations/0010_license_cc_string.py b/epdb/migrations/0010_license_cc_string.py deleted file mode 100644 index 5594c756..00000000 --- a/epdb/migrations/0010_license_cc_string.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-11 14:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("epdb", "0009_joblog"), - ] - - operations = [ - migrations.AddField( - model_name="license", - name="cc_string", - field=models.TextField(default="by-nc-sa", verbose_name="CC string"), - preserve_default=False, - ), - ] diff --git a/epdb/migrations/0011_auto_20251111_1413.py b/epdb/migrations/0011_auto_20251111_1413.py deleted file mode 100644 index d0a3463a..00000000 --- a/epdb/migrations/0011_auto_20251111_1413.py +++ /dev/null @@ -1,59 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-11 14:13 - -import re - -from django.contrib.postgres.aggregates import ArrayAgg -from django.db import migrations -from django.db.models import Min - - -def set_cc(apps, schema_editor): - License = apps.get_model("epdb", "License") - - # For all existing licenses extract cc_string from link - for license in License.objects.all(): - pattern = r"/licenses/([^/]+)/4\.0" - match = re.search(pattern, license.link) - if match: - license.cc_string = match.group(1) - license.save() - else: - raise ValueError(f"Could not find license for {license.link}") - - # Ensure we have all licenses - cc_strings = ["by", "by-nc", "by-nc-nd", "by-nc-sa", "by-nd", "by-sa"] - for cc_string in cc_strings: - if not License.objects.filter(cc_string=cc_string).exists(): - new_license = License() - new_license.cc_string = cc_string - new_license.link = f"https://creativecommons.org/licenses/{cc_string}/4.0/" - new_license.image_link = f"https://licensebuttons.net/l/{cc_string}/4.0/88x31.png" - new_license.save() - - # As we might have existing Licenses representing the same License, - # get min pk and all pks as a list - license_lookup_qs = License.objects.values("cc_string").annotate( - lowest_pk=Min("id"), all_pks=ArrayAgg("id", order_by=("id",)) - ) - - license_lookup = { - row["cc_string"]: (row["lowest_pk"], row["all_pks"]) for row in license_lookup_qs - } - - Packages = apps.get_model("epdb", "Package") - - for k, v in license_lookup.items(): - # Set min pk to all packages pointing to any of the duplicates - Packages.objects.filter(pk__in=v[1]).update(license_id=v[0]) - # remove the min pk from "other" pks as we use them for deletion - v[1].remove(v[0]) - # Delete redundant License objects - License.objects.filter(pk__in=v[1]).delete() - - -class Migration(migrations.Migration): - dependencies = [ - ("epdb", "0010_license_cc_string"), - ] - - operations = [migrations.RunPython(set_cc)] diff --git a/epdb/migrations/0012_node_stereo_removed_pathway_predicted.py b/epdb/migrations/0012_node_stereo_removed_pathway_predicted.py deleted file mode 100644 index 648090d7..00000000 --- a/epdb/migrations/0012_node_stereo_removed_pathway_predicted.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 5.2.7 on 2025-12-02 13:09 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("epdb", "0011_auto_20251111_1413"), - ] - - operations = [ - migrations.AddField( - model_name="node", - name="stereo_removed", - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name="pathway", - name="predicted", - field=models.BooleanField(default=False), - ), - ] diff --git a/epdb/migrations/0013_setting_expansion_schema.py b/epdb/migrations/0013_setting_expansion_schema.py deleted file mode 100644 index 9a981795..00000000 --- a/epdb/migrations/0013_setting_expansion_schema.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 5.2.7 on 2025-12-14 11:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("epdb", "0012_node_stereo_removed_pathway_predicted"), - ] - - operations = [ - migrations.AddField( - model_name="setting", - name="expansion_schema", - field=models.CharField( - choices=[ - ("BFS", "Breadth First Search"), - ("DFS", "Depth First Search"), - ("GREEDY", "Greedy"), - ], - default="BFS", - max_length=20, - ), - ), - ] diff --git a/epdb/migrations/0014_rename_expansion_schema_setting_expansion_scheme.py b/epdb/migrations/0014_rename_expansion_schema_setting_expansion_scheme.py deleted file mode 100644 index b7332fee..00000000 --- a/epdb/migrations/0014_rename_expansion_schema_setting_expansion_scheme.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.2.7 on 2025-12-14 16:02 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("epdb", "0013_setting_expansion_schema"), - ] - - operations = [ - migrations.RenameField( - model_name="setting", - old_name="expansion_schema", - new_name="expansion_scheme", - ), - ] diff --git a/epdb/migrations/0015_user_is_reviewer.py b/epdb/migrations/0015_user_is_reviewer.py deleted file mode 100644 index b26db739..00000000 --- a/epdb/migrations/0015_user_is_reviewer.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.2.7 on 2026-01-19 19:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("epdb", "0014_rename_expansion_schema_setting_expansion_scheme"), - ] - - operations = [ - migrations.AddField( - model_name="user", - name="is_reviewer", - field=models.BooleanField(default=False), - ), - ] diff --git a/epdb/migrations/0016_remove_enviformer_model_status_and_more.py b/epdb/migrations/0016_remove_enviformer_model_status_and_more.py deleted file mode 100644 index 2f85b8aa..00000000 --- a/epdb/migrations/0016_remove_enviformer_model_status_and_more.py +++ /dev/null @@ -1,179 +0,0 @@ -# Generated by Django 5.2.7 on 2026-02-12 09:38 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("epdb", "0015_user_is_reviewer"), - ] - - operations = [ - migrations.RemoveField( - model_name="enviformer", - name="model_status", - ), - migrations.RemoveField( - model_name="mlrelativereasoning", - name="model_status", - ), - migrations.RemoveField( - model_name="rulebasedrelativereasoning", - name="model_status", - ), - migrations.AddField( - model_name="epmodel", - name="model_status", - field=models.CharField( - choices=[ - ("INITIAL", "Initial"), - ("INITIALIZING", "Model is initializing."), - ("BUILDING", "Model is building."), - ( - "BUILT_NOT_EVALUATED", - "Model is built and can be used for predictions, Model is not evaluated yet.", - ), - ("EVALUATING", "Model is evaluating"), - ("FINISHED", "Model has finished building and evaluation."), - ("ERROR", "Model has failed."), - ], - default="INITIAL", - ), - ), - migrations.AlterField( - model_name="enviformer", - name="eval_packages", - field=models.ManyToManyField( - blank=True, - related_name="%(app_label)s_%(class)s_eval_packages", - to=settings.EPDB_PACKAGE_MODEL, - verbose_name="Evaluation Packages", - ), - ), - migrations.AlterField( - model_name="enviformer", - name="rule_packages", - field=models.ManyToManyField( - blank=True, - related_name="%(app_label)s_%(class)s_rule_packages", - to=settings.EPDB_PACKAGE_MODEL, - verbose_name="Rule Packages", - ), - ), - migrations.AlterField( - model_name="mlrelativereasoning", - name="eval_packages", - field=models.ManyToManyField( - blank=True, - related_name="%(app_label)s_%(class)s_eval_packages", - to=settings.EPDB_PACKAGE_MODEL, - verbose_name="Evaluation Packages", - ), - ), - migrations.AlterField( - model_name="mlrelativereasoning", - name="rule_packages", - field=models.ManyToManyField( - blank=True, - related_name="%(app_label)s_%(class)s_rule_packages", - to=settings.EPDB_PACKAGE_MODEL, - verbose_name="Rule Packages", - ), - ), - migrations.AlterField( - model_name="rulebasedrelativereasoning", - name="eval_packages", - field=models.ManyToManyField( - blank=True, - related_name="%(app_label)s_%(class)s_eval_packages", - to=settings.EPDB_PACKAGE_MODEL, - verbose_name="Evaluation Packages", - ), - ), - migrations.AlterField( - model_name="rulebasedrelativereasoning", - name="rule_packages", - field=models.ManyToManyField( - blank=True, - related_name="%(app_label)s_%(class)s_rule_packages", - to=settings.EPDB_PACKAGE_MODEL, - verbose_name="Rule Packages", - ), - ), - migrations.CreateModel( - name="PropertyPluginModel", - fields=[ - ( - "epmodel_ptr", - models.OneToOneField( - auto_created=True, - on_delete=django.db.models.deletion.CASCADE, - parent_link=True, - primary_key=True, - serialize=False, - to="epdb.epmodel", - ), - ), - ("threshold", models.FloatField(default=0.5)), - ("eval_results", models.JSONField(blank=True, default=dict, null=True)), - ("multigen_eval", models.BooleanField(default=False)), - ("plugin_identifier", models.CharField(max_length=255)), - ( - "app_domain", - models.ForeignKey( - blank=True, - default=None, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="epdb.applicabilitydomain", - ), - ), - ( - "data_packages", - models.ManyToManyField( - blank=True, - related_name="%(app_label)s_%(class)s_data_packages", - to=settings.EPDB_PACKAGE_MODEL, - verbose_name="Data Packages", - ), - ), - ( - "eval_packages", - models.ManyToManyField( - blank=True, - related_name="%(app_label)s_%(class)s_eval_packages", - to=settings.EPDB_PACKAGE_MODEL, - verbose_name="Evaluation Packages", - ), - ), - ( - "rule_packages", - models.ManyToManyField( - blank=True, - related_name="%(app_label)s_%(class)s_rule_packages", - to=settings.EPDB_PACKAGE_MODEL, - verbose_name="Rule Packages", - ), - ), - ], - options={ - "abstract": False, - }, - bases=("epdb.epmodel",), - ), - migrations.AddField( - model_name="setting", - name="property_models", - field=models.ManyToManyField( - blank=True, - related_name="settings", - to="epdb.propertypluginmodel", - verbose_name="Setting Property Models", - ), - ), - migrations.DeleteModel( - name="PluginModel", - ), - ] diff --git a/epdb/migrations/0017_additionalinformation.py b/epdb/migrations/0017_additionalinformation.py deleted file mode 100644 index a02af573..00000000 --- a/epdb/migrations/0017_additionalinformation.py +++ /dev/null @@ -1,93 +0,0 @@ -# Generated by Django 5.2.7 on 2026-02-20 12:02 - -import django.db.models.deletion -import uuid -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("contenttypes", "0002_remove_content_type_name"), - ("epdb", "0016_remove_enviformer_model_status_and_more"), - ] - - operations = [ - migrations.CreateModel( - name="AdditionalInformation", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, primary_key=True, serialize=False, verbose_name="ID" - ), - ), - ("uuid", models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), - ("url", models.TextField(null=True, unique=True, verbose_name="URL")), - ("kv", models.JSONField(blank=True, default=dict, null=True)), - ("type", models.TextField(verbose_name="Additional Information Type")), - ("data", models.JSONField(blank=True, default=dict, null=True)), - ("object_id", models.PositiveBigIntegerField(blank=True, null=True)), - ( - "content_type", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="contenttypes.contenttype", - ), - ), - ( - "package", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.EPDB_PACKAGE_MODEL, - verbose_name="Package", - ), - ), - ( - "scenario", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="scenario_additional_information", - to="epdb.scenario", - ), - ), - ], - options={ - "indexes": [ - models.Index(fields=["type"], name="epdb_additi_type_394349_idx"), - models.Index( - fields=["scenario", "type"], name="epdb_additi_scenari_a59edf_idx" - ), - models.Index( - fields=["content_type", "object_id"], name="epdb_additi_content_44d4b4_idx" - ), - models.Index( - fields=["scenario", "content_type", "object_id"], - name="epdb_additi_scenari_ef2bf5_idx", - ), - ], - "constraints": [ - models.CheckConstraint( - condition=models.Q( - models.Q(("content_type__isnull", True), ("object_id__isnull", True)), - models.Q(("content_type__isnull", False), ("object_id__isnull", False)), - _connector="OR", - ), - name="ck_addinfo_gfk_pair", - ), - models.CheckConstraint( - condition=models.Q( - ("scenario__isnull", False), - ("content_type__isnull", False), - _connector="OR", - ), - name="ck_addinfo_not_both_null", - ), - ], - }, - ), - ] diff --git a/epdb/migrations/0018_auto_20260220_1203.py b/epdb/migrations/0018_auto_20260220_1203.py deleted file mode 100644 index d1f73e1a..00000000 --- a/epdb/migrations/0018_auto_20260220_1203.py +++ /dev/null @@ -1,132 +0,0 @@ -# Generated by Django 5.2.7 on 2026-02-20 12:03 - -from django.db import migrations - - -def get_additional_information(scenario): - from envipy_additional_information import registry - from envipy_additional_information.parsers import TypeOfAerationParser - - for k, vals in scenario.additional_information.items(): - if k == "enzyme": - continue - - if k == "SpikeConentration": - k = "SpikeConcentration" - - if k == "AerationType": - k = "TypeOfAeration" - - for v in vals: - # Per default additional fields are ignored - MAPPING = {c.__name__: c for c in registry.list_models().values()} - try: - inst = MAPPING[k](**v) - except Exception: - if k == "TypeOfAeration": - toa = TypeOfAerationParser() - inst = toa.from_string(v["type"]) - - # Add uuid to uniquely identify objects for manipulation - if "uuid" in v: - inst.__dict__["uuid"] = v["uuid"] - - yield inst - - -def forward_func(apps, schema_editor): - Scenario = apps.get_model("epdb", "Scenario") - ContentType = apps.get_model("contenttypes", "ContentType") - AdditionalInformation = apps.get_model("epdb", "AdditionalInformation") - - bulk = [] - related = [] - ctype = {o.model: o for o in ContentType.objects.all()} - parents = Scenario.objects.prefetch_related( - "compound_set", - "compoundstructure_set", - "reaction_set", - "rule_set", - "pathway_set", - "node_set", - "edge_set", - ).filter(parent__isnull=True) - - for i, scenario in enumerate(parents): - print(f"{i + 1}/{len(parents)}", end="\r") - if scenario.parent is not None: - related.append(scenario.parent) - continue - - for ai in get_additional_information(scenario): - bulk.append( - AdditionalInformation( - package=scenario.package, - scenario=scenario, - type=ai.__class__.__name__, - data=ai.model_dump(mode="json"), - ) - ) - - print("\n", len(bulk)) - - related = Scenario.objects.prefetch_related( - "compound_set", - "compoundstructure_set", - "reaction_set", - "rule_set", - "pathway_set", - "node_set", - "edge_set", - ).filter(parent__isnull=False) - - for i, scenario in enumerate(related): - print(f"{i + 1}/{len(related)}", end="\r") - parent = scenario.parent - # Check to which objects this scenario is attached to - for ai in get_additional_information(scenario): - rel_objs = [ - "compound", - "compoundstructure", - "reaction", - "rule", - "pathway", - "node", - "edge", - ] - for rel_obj in rel_objs: - for o in getattr(scenario, f"{rel_obj}_set").all(): - bulk.append( - AdditionalInformation( - package=scenario.package, - scenario=parent, - type=ai.__class__.__name__, - data=ai.model_dump(mode="json"), - content_type=ctype[rel_obj], - object_id=o.pk, - ) - ) - - print("Start creating additional information objects...") - AdditionalInformation.objects.bulk_create(bulk) - print("Done!") - print(len(bulk)) - - Scenario.objects.filter(parent__isnull=False).delete() - # Call ai save to fix urls - ais = AdditionalInformation.objects.all() - total = ais.count() - - for i, ai in enumerate(ais): - print(f"{i + 1}/{total}", end="\r") - ai.save() - - -class Migration(migrations.Migration): - dependencies = [ - ("epdb", "0017_additionalinformation"), - ] - - operations = [ - migrations.RunPython(forward_func, reverse_code=migrations.RunPython.noop), - ] diff --git a/epdb/migrations/0019_remove_scenario_additional_information_and_more.py b/epdb/migrations/0019_remove_scenario_additional_information_and_more.py index 0aff3833..5883f705 100644 --- a/epdb/migrations/0019_remove_scenario_additional_information_and_more.py +++ b/epdb/migrations/0019_remove_scenario_additional_information_and_more.py @@ -1,20 +1,741 @@ -# Generated by Django 5.2.7 on 2026-02-23 08:45 +# Generated by Django 5.2.7 on 2026-03-06 10:51 -from django.db import migrations +import django.contrib.auth.models +import django.contrib.auth.validators +import django.contrib.postgres.fields +import django.db.models.deletion +import django.utils.timezone +import model_utils.fields +import uuid +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): + + initial = True + dependencies = [ - ("epdb", "0018_auto_20260220_1203"), + ('auth', '0012_alter_user_first_name_max_length'), + ('contenttypes', '0002_remove_content_type_name'), + migrations.swappable_dependency(settings.EPDB_PACKAGE_MODEL), ] operations = [ - migrations.RemoveField( - model_name="scenario", - name="additional_information", + migrations.CreateModel( + name='ApplicabilityDomain', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('num_neighbours', models.IntegerField(default=5)), + ('reliability_threshold', models.FloatField(default=0.5)), + ('local_compatibilty_threshold', models.FloatField(default=0.5)), + ('functional_groups', models.JSONField(blank=True, default=dict, null=True)), + ], + options={ + 'abstract': False, + }, ), - migrations.RemoveField( - model_name="scenario", - name="parent", + migrations.CreateModel( + name='Edge', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='EPModel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('model_status', models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')), + ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + ), + migrations.CreateModel( + name='ExternalDatabase', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('name', models.CharField(max_length=100, unique=True, verbose_name='Database Name')), + ('full_name', models.CharField(blank=True, max_length=255, verbose_name='Full Database Name')), + ('description', models.TextField(blank=True, verbose_name='Description')), + ('base_url', models.URLField(blank=True, null=True, verbose_name='Base URL')), + ('url_pattern', models.CharField(blank=True, help_text="URL pattern with {id} placeholder, e.g., 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'", max_length=500, verbose_name='URL Pattern')), + ('is_active', models.BooleanField(default=True, verbose_name='Is Active')), + ], + options={ + 'verbose_name': 'External Database', + 'verbose_name_plural': 'External Databases', + 'db_table': 'epdb_external_database', + 'ordering': ['name'], + }, + ), + migrations.CreateModel( + name='Permission', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('permission', models.CharField(choices=[('read', 'Read'), ('write', 'Write'), ('all', 'All')], max_length=32)), + ], + ), + migrations.CreateModel( + name='License', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('cc_string', models.TextField(verbose_name='CC string')), + ('link', models.URLField(verbose_name='link')), + ('image_link', models.URLField(verbose_name='Image link')), + ], + ), + migrations.CreateModel( + name='Rule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')), + ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + ), + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('email', models.EmailField(max_length=254, unique=True)), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('is_reviewer', models.BooleanField(default=False)), + ('default_package', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Default Package')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='APIToken', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('hashed_key', models.CharField(help_text='SHA-256 hash of the token key', max_length=128, unique=True)), + ('expires_at', models.DateTimeField(blank=True, help_text='Token expiration time (null for no expiration)', null=True)), + ('name', models.CharField(help_text='Descriptive name for this token', max_length=100)), + ('is_active', models.BooleanField(default=True, help_text='Whether this token is active')), + ('user', models.ForeignKey(help_text='User who owns this token', on_delete=django.db.models.deletion.CASCADE, related_name='api_tokens', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'API Token', + 'verbose_name_plural': 'API Tokens', + 'db_table': 'epdb_api_token', + 'ordering': ['-created'], + }, + ), + migrations.CreateModel( + name='Compound', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')), + ], + ), + migrations.CreateModel( + name='CompoundStructure', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), + ('smiles', models.TextField(verbose_name='SMILES')), + ('canonical_smiles', models.TextField(verbose_name='Canonical SMILES')), + ('inchikey', models.TextField(max_length=27, verbose_name='InChIKey')), + ('normalized_structure', models.BooleanField(default=False)), + ('compound', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.compound')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='compound', + name='default_structure', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='compound_default_structure', to='epdb.compoundstructure', verbose_name='Default Structure'), + ), + migrations.CreateModel( + name='PropertyPluginModel', + fields=[ + ('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')), + ('threshold', models.FloatField(default=0.5)), + ('eval_results', models.JSONField(blank=True, default=dict, null=True)), + ('multigen_eval', models.BooleanField(default=False)), + ('plugin_identifier', models.CharField(max_length=255)), + ], + options={ + 'abstract': False, + }, + bases=('epdb.epmodel',), + ), + migrations.CreateModel( + name='Group', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('name', models.TextField(verbose_name='Group name')), + ('public', models.BooleanField(default=False, verbose_name='Public Group')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('group_member', models.ManyToManyField(blank=True, related_name='groups_in_group', to='epdb.group', verbose_name='Group member')), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Group Owner')), + ('user_member', models.ManyToManyField(related_name='users_in_group', to=settings.AUTH_USER_MODEL, verbose_name='User members')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='user', + name='default_group', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_group', to='epdb.group', verbose_name='Default Group'), + ), + migrations.CreateModel( + name='JobLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('task_id', models.UUIDField(unique=True)), + ('job_name', models.TextField()), + ('status', models.CharField(choices=[('INITIAL', 'Initial'), ('SUCCESS', 'Success'), ('FAILURE', 'Failure'), ('REVOKED', 'Revoked'), ('IGNORED', 'Ignored')], default='INITIAL', max_length=20)), + ('done_at', models.DateTimeField(blank=True, default=None, null=True)), + ('task_result', models.TextField(blank=True, default=None, null=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Package', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('reviewed', models.BooleanField(default=False, verbose_name='Reviewstatus')), + ('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.license', verbose_name='License')), + ], + options={ + 'swappable': 'EPDB_PACKAGE_MODEL', + }, + ), + migrations.CreateModel( + name='Node', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), + ('depth', models.IntegerField(verbose_name='Node depth')), + ('stereo_removed', models.BooleanField(default=False)), + ('default_node_label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='default_node_structure', to='epdb.compoundstructure', verbose_name='Default Node Label')), + ('node_labels', models.ManyToManyField(related_name='node_structures', to='epdb.compoundstructure', verbose_name='All Node Labels')), + ('out_edges', models.ManyToManyField(to='epdb.edge', verbose_name='Outgoing Edges')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='edge', + name='end_nodes', + field=models.ManyToManyField(related_name='edge_products', to='epdb.node', verbose_name='End Nodes'), + ), + migrations.AddField( + model_name='edge', + name='start_nodes', + field=models.ManyToManyField(related_name='edge_educts', to='epdb.node', verbose_name='Start Nodes'), + ), + migrations.CreateModel( + name='SequentialRule', + fields=[ + ('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.rule',), + ), + migrations.CreateModel( + name='SimpleRule', + fields=[ + ('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.rule',), + ), + migrations.CreateModel( + name='Pathway', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), + ('predicted', models.BooleanField(default=False)), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='node', + name='pathway', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'), + ), + migrations.AddField( + model_name='edge', + name='pathway', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'), + ), + migrations.CreateModel( + name='Reaction', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), + ('multi_step', models.BooleanField(verbose_name='Multistep Reaction')), + ('medline_references', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), null=True, size=None, verbose_name='Medline References')), + ('educts', models.ManyToManyField(related_name='reaction_educts', to='epdb.compoundstructure', verbose_name='Educts')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')), + ('products', models.ManyToManyField(related_name='reaction_products', to='epdb.compoundstructure', verbose_name='Products')), + ('rules', models.ManyToManyField(related_name='reaction_rule', to='epdb.rule', verbose_name='Rule')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='EnzymeLink', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('ec_number', models.TextField(verbose_name='EC Number')), + ('classification_level', models.IntegerField(verbose_name='Classification Level')), + ('linking_method', models.TextField(verbose_name='Linking Method')), + ('edge_evidence', models.ManyToManyField(to='epdb.edge')), + ('rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.rule')), + ('reaction_evidence', models.ManyToManyField(to='epdb.reaction')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='edge', + name='edge_label', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.reaction', verbose_name='Edge label'), + ), + migrations.CreateModel( + name='Scenario', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('scenario_date', models.CharField(default='No date', max_length=256)), + ('scenario_type', models.CharField(default='Not specified', max_length=256)), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='rule', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='reaction', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='pathway', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='node', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='edge', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='compoundstructure', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='compound', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.CreateModel( + name='Setting', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('public', models.BooleanField(default=False)), + ('global_default', models.BooleanField(default=False)), + ('max_depth', models.IntegerField(default=5, verbose_name='Setting Max Depth')), + ('max_nodes', models.IntegerField(default=30, verbose_name='Setting Max Number of Nodes')), + ('model_threshold', models.FloatField(blank=True, default=0.25, null=True, verbose_name='Setting Model Threshold')), + ('expansion_scheme', models.CharField(choices=[('BFS', 'Breadth First Search'), ('DFS', 'Depth First Search'), ('GREEDY', 'Greedy')], default='BFS', max_length=20)), + ('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.epmodel', verbose_name='Setting EPModel')), + ('rule_packages', models.ManyToManyField(blank=True, related_name='setting_rule_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Setting Rule Packages')), + ('property_models', models.ManyToManyField(blank=True, related_name='settings', to='epdb.propertypluginmodel', verbose_name='Setting Property Models')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='pathway', + name='setting', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Setting'), + ), + migrations.AddField( + model_name='user', + name='default_setting', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.setting', verbose_name='The users default settings'), + ), + migrations.CreateModel( + name='EnviFormer', + fields=[ + ('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')), + ('threshold', models.FloatField(default=0.5)), + ('eval_results', models.JSONField(blank=True, default=dict, null=True)), + ('multigen_eval', models.BooleanField(default=False)), + ('app_domain', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain')), + ('data_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Data Packages')), + ('eval_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_eval_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Evaluation Packages')), + ('rule_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_rule_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Rule Packages')), + ], + options={ + 'abstract': False, + }, + bases=('epdb.epmodel',), + ), + migrations.CreateModel( + name='MLRelativeReasoning', + fields=[ + ('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')), + ('threshold', models.FloatField(default=0.5)), + ('eval_results', models.JSONField(blank=True, default=dict, null=True)), + ('multigen_eval', models.BooleanField(default=False)), + ('app_domain', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain')), + ('data_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Data Packages')), + ('eval_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_eval_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Evaluation Packages')), + ('rule_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_rule_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Rule Packages')), + ], + options={ + 'abstract': False, + }, + bases=('epdb.epmodel',), + ), + migrations.AddField( + model_name='applicabilitydomain', + name='model', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.mlrelativereasoning'), + ), + migrations.AddField( + model_name='propertypluginmodel', + name='app_domain', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain'), + ), + migrations.AddField( + model_name='propertypluginmodel', + name='data_packages', + field=models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_data_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Data Packages'), + ), + migrations.AddField( + model_name='propertypluginmodel', + name='eval_packages', + field=models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_eval_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Evaluation Packages'), + ), + migrations.AddField( + model_name='propertypluginmodel', + name='rule_packages', + field=models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_rule_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Rule Packages'), + ), + migrations.CreateModel( + name='RuleBasedRelativeReasoning', + fields=[ + ('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')), + ('threshold', models.FloatField(default=0.5)), + ('eval_results', models.JSONField(blank=True, default=dict, null=True)), + ('multigen_eval', models.BooleanField(default=False)), + ('min_count', models.IntegerField(default=10)), + ('max_count', models.IntegerField(default=0)), + ('app_domain', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain')), + ('data_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Data Packages')), + ('eval_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_eval_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Evaluation Packages')), + ('rule_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_rule_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Rule Packages')), + ], + options={ + 'abstract': False, + }, + bases=('epdb.epmodel',), + ), + migrations.CreateModel( + name='ExternalIdentifier', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('object_id', models.IntegerField()), + ('identifier_value', models.CharField(max_length=255, verbose_name='Identifier Value')), + ('url', models.URLField(blank=True, null=True, verbose_name='Direct URL')), + ('is_primary', models.BooleanField(default=False, help_text='Mark this as the primary identifier for this database', verbose_name='Is Primary')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ('database', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.externaldatabase', verbose_name='External Database')), + ], + options={ + 'verbose_name': 'External Identifier', + 'verbose_name_plural': 'External Identifiers', + 'db_table': 'epdb_external_identifier', + 'indexes': [models.Index(fields=['content_type', 'object_id'], name='epdb_extern_content_b76813_idx'), models.Index(fields=['database', 'identifier_value'], name='epdb_extern_databas_486422_idx')], + 'unique_together': {('content_type', 'object_id', 'database', 'identifier_value')}, + }, + ), + migrations.CreateModel( + name='SimpleAmbitRule', + fields=[ + ('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')), + ('smirks', models.TextField(verbose_name='SMIRKS')), + ('reactant_filter_smarts', models.TextField(null=True, verbose_name='Reactant Filter SMARTS')), + ('product_filter_smarts', models.TextField(null=True, verbose_name='Product Filter SMARTS')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.simplerule',), + ), + migrations.CreateModel( + name='SimpleRDKitRule', + fields=[ + ('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')), + ('reaction_smarts', models.TextField(verbose_name='SMIRKS')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.simplerule',), + ), + migrations.CreateModel( + name='SequentialRuleOrdering', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order_index', models.IntegerField()), + ('sequential_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.sequentialrule')), + ('simple_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.simplerule')), + ], + ), + migrations.AddField( + model_name='sequentialrule', + name='simple_rules', + field=models.ManyToManyField(through='epdb.SequentialRuleOrdering', to='epdb.simplerule', verbose_name='Simple rules'), + ), + migrations.CreateModel( + name='ParallelRule', + fields=[ + ('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')), + ('simple_rules', models.ManyToManyField(to='epdb.simplerule', verbose_name='Simple rules')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.rule',), + ), + migrations.AlterUniqueTogether( + name='compound', + unique_together={('uuid', 'package')}, + ), + migrations.CreateModel( + name='AdditionalInformation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('url', models.TextField(null=True, unique=True, verbose_name='URL')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('type', models.TextField(verbose_name='Additional Information Type')), + ('data', models.JSONField(blank=True, default=dict, null=True)), + ('object_id', models.PositiveBigIntegerField(blank=True, null=True)), + ('content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')), + ('scenario', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='scenario_additional_information', to='epdb.scenario')), + ], + options={ + 'indexes': [models.Index(fields=['type'], name='epdb_additi_type_394349_idx'), models.Index(fields=['scenario', 'type'], name='epdb_additi_scenari_a59edf_idx'), models.Index(fields=['content_type', 'object_id'], name='epdb_additi_content_44d4b4_idx'), models.Index(fields=['scenario', 'content_type', 'object_id'], name='epdb_additi_scenari_ef2bf5_idx')], + 'constraints': [models.CheckConstraint(condition=models.Q(models.Q(('content_type__isnull', True), ('object_id__isnull', True)), models.Q(('content_type__isnull', False), ('object_id__isnull', False)), _connector='OR'), name='ck_addinfo_gfk_pair'), models.CheckConstraint(condition=models.Q(('scenario__isnull', False), ('content_type__isnull', False), _connector='OR'), name='ck_addinfo_not_both_null')], + }, + ), + migrations.CreateModel( + name='GroupPackagePermission', + fields=[ + ('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')), + ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')), + ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.group', verbose_name='Permission to')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Permission on')), + ], + options={ + 'unique_together': {('package', 'group')}, + }, + bases=('epdb.permission',), + ), + migrations.CreateModel( + name='UserPackagePermission', + fields=[ + ('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')), + ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Permission on')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')), + ], + options={ + 'unique_together': {('package', 'user')}, + }, + bases=('epdb.permission',), + ), + migrations.CreateModel( + name='UserSettingPermission', + fields=[ + ('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')), + ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')), + ('setting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Permission on')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')), + ], + options={ + 'unique_together': {('setting', 'user')}, + }, + bases=('epdb.permission',), ), ] diff --git a/epdb/migrations/0023_alter_compoundstructure_options_and_more.py b/epdb/migrations/0023_alter_compoundstructure_options_and_more.py index 6868f514..7da59c39 100644 --- a/epdb/migrations/0023_alter_compoundstructure_options_and_more.py +++ b/epdb/migrations/0023_alter_compoundstructure_options_and_more.py @@ -46,4 +46,9 @@ class Migration(migrations.Migration): name="molfile", field=models.TextField(blank=True, null=True, verbose_name="Molfile"), ), + migrations.AddField( + model_name="group", + name="secret", + field=models.BooleanField(default=False, verbose_name="Secret Group"), + ), ] diff --git a/epdb/models.py b/epdb/models.py index 9b8bd633..3f66cc5b 100644 --- a/epdb/models.py +++ b/epdb/models.py @@ -204,6 +204,7 @@ class Group(TimeStampedModel): name = models.TextField(blank=False, null=False, verbose_name="Group name") owner = models.ForeignKey("User", verbose_name="Group Owner", on_delete=models.CASCADE) public = models.BooleanField(verbose_name="Public Group", default=False) + secret = models.BooleanField(verbose_name="Secret Group", default=False) description = models.TextField( blank=False, null=False, verbose_name="Descriptions", default="no description" ) @@ -867,18 +868,25 @@ class Compound( standardized_smiles = FormatConverter.standardize(smiles, remove_stereo=True) + subclasses = CompoundStructure.__subclasses__() + + qs = CompoundStructure.objects.filter(smiles=smiles, compound__package=package) + if subclasses: + qs = qs.not_instance_of(*subclasses) + # Check if we find a direct match for a given SMILES - if CompoundStructure.objects.filter(smiles=smiles, compound__package=package).exists(): - return CompoundStructure.objects.get(smiles=smiles, compound__package=package).compound + if qs.exists(): + return qs.first().compound + + + qs = CompoundStructure.objects.filter(smiles=standardized_smiles, compound__package=package) + if subclasses: + qs = qs.not_instance_of(*subclasses) # Check if we can find the standardized one - if CompoundStructure.objects.filter( - smiles=standardized_smiles, compound__package=package - ).exists(): + if qs.exists(): # TODO should we add a structure? - return CompoundStructure.objects.get( - smiles=standardized_smiles, compound__package=package - ).compound + return qs.first().compound # Generate Compound c = Compound() diff --git a/epdb/views.py b/epdb/views.py index 50385576..eb1adbed 100644 --- a/epdb/views.py +++ b/epdb/views.py @@ -388,6 +388,9 @@ def get_base_context(request, for_user=None) -> Dict[str, Any]: "debug": s.DEBUG, "external_databases": ExternalDatabase.get_databases(), "site_id": s.MATOMO_SITE_ID, + # EDIT START + "secret_groups": Group.objects.filter(secret=True), + # EDIT END }, } @@ -587,10 +590,38 @@ def packages(request): "package-description", s.DEFAULT_VALUES["description"] ) + # EDIT START + data_pool = None + package_classification = request.POST.get("package-classification") + classification = Package.Classification(int(package_classification)) + # For SECRET we'll need a data pool which will be an additional perm check later + if classification == Package.Classification.SECRET: + package_data_pool = request.POST.get("package-data-pool") + + if package_data_pool is None: + return error(request, "Invalid data pool.", "Data Pool is required!") + + data_pool = GroupManager.get_group_by_url(current_user, package_data_pool) + + if data_pool is None: + return error(request, "Invalid data pool.", "Data Pool does not exist or no access!") + + if not data_pool.secret: + return error(request, "Invalid data pool.", "Data Pool is not a secret group!") + created_package = PackageManager.create_package( current_user, package_name, package_description ) + created_package.classification_level = classification + + # Set previously determined data pool + if classification == Package.Classification.SECRET: + created_package.data_pool = data_pool + + created_package.save() + # EDIT END + return redirect(created_package.url) elif request.method == "OPTIONS": diff --git a/static/images/Restricted.gif b/static/images/Restricted.gif new file mode 100644 index 0000000000000000000000000000000000000000..12ea8cfd6a69e31a06bb32bc57bfcbc4aac5f35a GIT binary patch literal 1577 zcmd7N`#X~h0KoBgi@ChrE z{jOF?lpia^g{I=C2C6xha;8?^bxl%gp`2of@^z$tdCEHoa(0+vI73o-NcE{t(NBiN zpX6MsC{Jg5^TU6q|6^KeigkQKViGkuCG}xi`lF1@$61d(+)?hi`FXU0r-k(A&x(sm zUX)doR+hhHRMk}1y=o}HXfRn#%`L5M?H!%$u5M0G@85m>1A}k4LvM#iM#tWbPrT=S znEW_3J$3@&ikO=hppLg{3l`Sb_O~3zS_6Q!iG7R7ntXBbHB+&Uz`W+}b2Y0&A{Jl+ zgl@B1P;D7fpA<3IN<>Du9j%A&ND0)@8Qh~o!Jj&hGOo&DV9IP!uK4Vo-^R|S zzk2|8xWA_Cvih4j@s>4U4y*siKub=83RPKcJ3&?2HI1={pg@Btk5Qo z1K9XarN6vU17ekt#B^F0=#3JOdjBO$Mxv*ffu~P-E@}k zguaeX9)r%@SES)i#hOQjX7=eu*=k`7mj^7_7?%~`HZp6Y&3!lE^sezzVBe8NI4lMk zM&?Hm5NfY&7eUGy4`VyG^w4};S^x{Y;vi@YxOQhgujs^meq&*@(NyUkT^9Mr)1)RE zCfg8XX${?InbGRHrx9~ZU`vU;>lM%5DCX&niiNX@2aS&UCc~+Y*~H#T+MNG>2_r{s z=hU0Pxk=`eV1W?%MF4&swsXpdq|7YTipDdh=Ny}Ze6>BdAU!D*>E zp0L;5|ExZGU2aLEp7Fnu!wd}bjuS4?xrK3t0U*7pSDJMx5{IV|G*^8S4XUyx_C+Vc zfA!PX9X4J6GZs-6QqdZ0l3$SNTbZUw`o(0pX0JIpAiIaM6%XHg@+CK;HJtp}Ay-aL zdnEgKr(4r}iWKMEI^_%0bzrZ9HNsKna9gv9tK}zDV&R6S6KNFAN`O+qhrXQYOOpN8K^3i{$z z*>Ew~lL?!#%1(7&vx6Ntccs$4Y8fx=uc%dh8f-$(lyqNuvx|deirOdblqTp#wqAja zwN|ZuXnm&@4o?D+vESF|*2d`Ra4Tj#cibUJ<=1~8;mtEc&9F5L*CKO9>?Xj5**JC$ zCM=H|L^(tRZ}ZHMZ;yQsxivbu&|!B$&hr(Gg@@cr3&~hGnGJ7xobG6+Il(_Bh&%s` z+ldd$X|UC9%Dvt$J9M!n!~=ntjzJDofZ7cQhj50C^B0wyKPai)4$f#?g8gf5#Pjf; z2awHe^y)CEWv`0B!WfJ2giXp^ky64$!Y*vK?HPGXl`ZApg5;}=I!Lk6^c7MLGz&N_ zqH*>g+bgqm4b>BosleJQmYj~+UntAuJB1Zv~xl4 M@-9=j1{^s44_EQ;A^-pY literal 0 HcmV?d00001 diff --git a/static/images/bayer-logo.svg b/static/images/bayer-logo.svg new file mode 100644 index 00000000..d3b5d5df --- /dev/null +++ b/static/images/bayer-logo.svg @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/static/images/restricted_mid.png b/static/images/restricted_mid.png new file mode 100644 index 0000000000000000000000000000000000000000..6a5c248ccceac1089c9221bd4d762e1e3c397a33 GIT binary patch literal 2243 zcmV;!2t4Px#1ZP1_K>z@;j|==^1poj5W>8F2Mf8&Z{_)@Z(BevpZ)IF^p*(z`0x79kNLb{_p(U#r!e@nQ1zV{`qP*C z#(4YMr}m*B^_md*%!l@*DEPQo00021+(Qol001I%QchF<|NsC0|NsC0|NsC0|NsC0 z|NsC0|NsC0|NsC0|NsC001eQ?aR2}S32;bRa{vGr5dZ)e5dq33^FIIp2e?T@K~z{r z?OBU%A~_Hol2J&2kZhy_@tV#5f8|wmb^9@x2`eIPPDf=Kcl*(9m*3v*3iMy1Tl8Ju z4FjZp-|ZpmZ@wB3edq?r3mBp?+HY0nd30 z*n4V#!nJ)(p_ly9Ur=D8<>pWTJ}rG=Hqf^$#%@P|UbfK6GM+!z_xNGd_$M;ShUJqD zKq>LDitz799luw88a7lBpx`+r<8xvp9SQ#Zm*-GJ{fL9Ebc z3_nd@d#r*P17&>h1kMaV8o%@Y&>b{;4p42N>@l-25FjlrkNt-3qaTLub6EHI*qQ^C zqmY#@s?IhcJ}Pb^t!fCxSme*a@nV6+M5>Y*L=AvUc!G;qy&&d1&!ogn^jHj-3)6(E zeHvbH`P-$2obba6m`w2Jo@fq@OuTl_GOLWhgX~Jj?;sA8v5A_W;@8*mNq7V^etHH6iP9WAI3TisFlG?uU5mo88Anfx8zOK1u=GM#gKO-HE&36m1C=u$QOpNBa#H zes2DNO4x=~PQXfo!doYC!cC*fXzQ5EK&3&x;Ds_KFFbBk+*FqUDB*>g$PmGrO+ZL} zFF?V{&!%F3$4+^`E#`=w8v*=>7xbFPAa0q+Xd|;KTQk&wMzdqt1n{{XjO;k^p#NeTm}`V)3cURasG~odpsQ;rkD~Fn?Zj zt^pU^IZ@iTMk9>K1}oF>LSqL+>>4i!8h~{2UE0LpU ziYKil3nnjcio%I6!y;YwW1e*|5C^Y{3x~N2E;u}(QY(?8EH}Xt(Ie~;c9%Nr1VJ)%Os9Agx(MMwRA9%8HZaKdX*5q^FI zX>`b`$o}d!V;5#%!l)XNruuHc{HwiGOKK|g3^rX|P$Ac%R|v&a%S+3{tEm8d;6Xa0 zLX$JRk(C|8j$T=07+8BF{Wi!o9V2TRhpji?q zO9FiCXitz5$5?KFiV7*szAeyNE8>x(K$sB~P*HIOJr2lX$F={MGKdyXnK7^3Ui;YuacBL}dWs74+Sa3p)04+0T(MTz!!_U>o>77ER=TjLxh5`; ziW(QFSnX2D|G*`^YGo6TSLFE`_xFl~I~56cDiVD4xTBkO5A&D%AO2qYe*q9<-C&IH R{8#`0002ovPDHLkV1hWzIc@*| literal 0 HcmV?d00001 diff --git a/static/images/secret_mid.png b/static/images/secret_mid.png new file mode 100644 index 0000000000000000000000000000000000000000..f97720300df7d0b429342306019c100843c51762 GIT binary patch literal 1490 zcmV;@1ugoCP)Px#1ZP1_K>z@;j|==^1poj5W>8F2Mfc?6!zv&D|Ns5?`0&cd^3cxWo0j2}kLIYP z^w-top`H2b>fVTf$2Tp>J~YckJJMiR`||PJfqKO;Cg`uL>b0`&!@%skx!{h7&{+Ts4Ajzjo`G>1++cq%F2O}5;lr(*qqiGj_ z&d`3b2wvdVv;ob~9!ROh&KrvBU_z+6UXlxF+THGBw>zth-^RVjv83H-u(s_v4jb)j z+nngc7b*Ed&mcF1VQ4>1uIt!G@d}N*l6$l0^#Ypq9bR%kRUaxv#p=Wo&k-<%=(3Vr z)@^&l+IWF6=QY_O*(TT>DoDBf9|#1+9er*msL^+pskE3)pCGvlvXGre&-4 zM!Z|aB?THx#+8pHCG=w9H9@R2pK9U}{_tihm&9n)2T+Ilfj6s;F4kFX0xSa}+O27E z)NEjC&#nQ1M&EuppVhTP%&A#D>8BACJF8D5#WrE9*c8rHbQfRl#m8Z5z-@!9?YkEd zbKJV_hc~xCt6(7MO(V|U4yS^JKq&-mHw(<5XK?#*{8K_66~HBg9qXOd&O$G%To5=| zZ^NJfUZt3ZWZa@spTqRw2zPuEL@pqbHR6T230tZd5?$UBq)ces72l4o12L785Zn;q z66UbS_nI{}3?#j2M53IKKE^Q0fz$>RPx>f4*xJUdx(yFChUD--JohSi{TR^Nk72M~ zI>(;Jz;-k-?G3tFj@PVQ4!G^hXyQL z;js;952b?X?~qoVGLQh=03yUWT&rAYZq3jalz5qMK#!wC3w?+@88l`-z+6 z%+oXmHcATqLP;IAbaDQ@+|eUrxK!HA=n*d zvc`IDZCcZZ;eTY!C-Wg_n~&-yY?tKpt+@BUc=vU?PlpFD!b9=fweR5BUF$EFEOB!f zvLnjxGa3A)Mdm$y5P;V?0QDb zd3RlVQ$w?LwiiJ=jqkY@GtS$2cZxF}Cr4UW9H02S`HhB$YvL8S>zeBLTsDLD24;q$ zpE=AkSrz1fUZsIbOS~w$YSB=#-L=m=S|U~rDP;1vJK{!q zFw+W$b&L7bFSthRV!#qbWaMzIPK34Rm}woR`TdS-j+5znXq1u@>|W@rD-0vx+c4L< zX@XOzty*+cu8EABJI0ZZEt5lLiz*f z9oO3UL=L@b7%JED2wAe2=c}1(#BN~H9KT-vHd?s(AA>SD$8b~hkC&>Ka@l~=bNXK_ sma63reJcER?K^m8*OvZy`V0u|KQ%O6QSeBH>Hq)$07*qoM6N<$g1M&XWdHyG literal 0 HcmV?d00001 diff --git a/static/images/secret_small.png b/static/images/secret_small.png new file mode 100644 index 0000000000000000000000000000000000000000..f8d6cc03c54a9659ade9e6bddc748fc7fca43da1 GIT binary patch literal 3226 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 M07*qoM6N<$g4@kImjD0& literal 0 HcmV?d00001 diff --git a/templates/actions/objects/pathway.html b/templates/actions/objects/pathway.html index 85e4164b..7ead1c57 100644 --- a/templates/actions/objects/pathway.html +++ b/templates/actions/objects/pathway.html @@ -9,6 +9,14 @@ Add Compound +
  • + + Add PES +
  • + {% if not public_mode %} @@ -88,12 +89,23 @@ >Scenario
  • +
    +
  • + Group +
  • {% endif %}