diff --git a/Dockerfile b/Dockerfile
index 08cf1600..713b3c4f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -30,13 +30,15 @@ RUN mkdir -p -m 0700 /root/.ssh \
&& ssh-keyscan git.envipath.com >> /root/.ssh/known_hosts
# We'll need access to private repos, let docker make use of host ssh agent and use it like:
-# docker build --ssh default -t envipath/envipy:1.0 .
+# docker build --ssh default -t envipath/envipy-bayer:1.0 .
RUN --mount=type=ssh \
uv sync --locked --extra ms-login --extra pepper-plugin
# Now copy source and do a final sync to install the project itself
# Ensure .dockerignore is reasonable
+COPY bb4g bb4g
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..a5279572
--- /dev/null
+++ b/bayer/epdb_hooks.py
@@ -0,0 +1,39 @@
+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",
+)
+register_template(
+ "epdb.actions.objects.pathway.add",
+ "actions/objects/pathway_add_pes.html",
+)
+register_template(
+ "epdb.modals.objects.pathway.add",
+ "modals/objects/add_pathway_pes_node_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 %}
+
+ New PES
+
+{% endif %}
\ No newline at end of file
diff --git a/bayer/templates/actions/objects/pathway_add_pes.html b/bayer/templates/actions/objects/pathway_add_pes.html
new file mode 100644
index 00000000..41490fb1
--- /dev/null
+++ b/bayer/templates/actions/objects/pathway_add_pes.html
@@ -0,0 +1,8 @@
+
+
+ Add PES
+
\ 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 %}
+
+
+
+
+
New Package
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+ Submit
+
+ Creating...
+
+
+
+
+
+
+
\ 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 %}
+
+
+
+
+
New PES
+
+
+
+
+
+
+
+
+
+
+ Close
+
+
+ Submit
+
+ Creating...
+
+
+
+
+
+
+
\ 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 %}
+
+
+
+
+
New PES
+
+
+
+
+
+
+
+
+
+
+ Close
+
+
+ Submit
+
+ Creating...
+
+
+
+
+
+
+
\ 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 %}
+
+
+
+
+
+
+
+
+
+
+
+
+ Login with Microsoft
+
+
+
+
+
+
+{% 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..0f1d5671
--- /dev/null
+++ b/bayer/views.py
@@ -0,0 +1,155 @@
+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:
+ from epauth.views import get_access_token_from_request
+ token = get_access_token_from_request(request)
+
+ if token:
+ for k, v in s.PES_API_MAPPING.items():
+ if pes_url.startswith(k):
+ pes_id = pes_url.split('/')[-1]
+
+ if pes_id == 'dummy':
+ 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=s.PROXIES or None)
+
+ 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/bb4g/__init__.py b/bb4g/__init__.py
new file mode 100644
index 00000000..1b686c08
--- /dev/null
+++ b/bb4g/__init__.py
@@ -0,0 +1,183 @@
+import json
+import math
+from datetime import datetime
+from typing import List
+import enum
+import requests
+from django.conf import settings as s
+from envipy_additional_information import EnviPyModel, UIConfig, WidgetType
+from envipy_additional_information import register
+
+from bridge.contracts import Classifier # noqa: I001
+from bridge.dto import (
+ BuildResult,
+ EnviPyDTO,
+ EvaluationResult,
+ RunResult,
+ TransformationProductPrediction,
+) # noqa: I001
+
+class SamplingAlgorithm(enum.Enum):
+ EXACT = "exact"
+
+
+@register("bb4gconfig")
+class BB4GConfig(EnviPyModel):
+ sampling_algorithm: SamplingAlgorithm = SamplingAlgorithm.EXACT
+ cutoff: int = -5
+
+ class UI:
+ title = "BB4G Configuration"
+ sampling_algorithm = UIConfig(
+ widget=WidgetType.SELECT,
+ label="BB4G Sampling Algorithm",
+ order=1,
+ placeholder="If unset defaults to 'exact'"
+ )
+ cutoff = UIConfig(
+ widget=WidgetType.NUMBER,
+ label="BB4G Cutoff",
+ order=2,
+ placeholder="If unset defaults to -5"
+ )
+
+
+# Once stable these will be exposed by enviPy-plugins lib
+class BB4G(Classifier):
+ Config = BB4GConfig
+
+ def __init__(self, config: BB4GConfig | None = None):
+ super().__init__(config)
+ self.url = f"{s.BB4G_URL}"
+
+ self.token = self.acquire_token()
+ self.header = {
+ "Authorization": f"Bearer {self.token}",
+ "Content-Type": "application/json",
+ }
+
+ def acquire_token(self):
+ BB4G_TENANT_ID = s.BB4G_TENANT_ID
+ BB4G_CLIENT_ID = s.BB4G_CLIENT_ID
+ BB4G_CLIENT_SECRET = s.BB4G_CLIENT_SECRET
+ BB4G_SCOPE = s.BB4G_SCOPE
+
+ BB4G_TOKEN_URL = f"https://login.microsoftonline.com/{BB4G_TENANT_ID}/oauth2/v2.0/token"
+
+ payload = {
+ "client_id": BB4G_CLIENT_ID,
+ "client_secret": BB4G_CLIENT_SECRET,
+ "scope": BB4G_SCOPE,
+ "grant_type": "client_credentials"
+ }
+
+ # No Proxy required, URL is whitelisted
+ res = requests.post(BB4G_TOKEN_URL, data=payload)
+
+ res.raise_for_status()
+
+ return res.json()["access_token"]
+
+ def start(self):
+ header = {
+ "Authorization": f"Bearer {self.token}",
+ "Content-Type": "application/json",
+ }
+
+ started = False
+ retries = 0
+ while not started and retries < 5:
+ res = requests.post(f"{self.url}/start", headers=header, data={}, proxies=s.PROXIES or None)
+
+ if res.status_code == 200:
+ started = True
+ elif res.status_code in [500, 502]:
+ retries += 1
+ import time
+ time.sleep(5)
+ else:
+ raise ValueError(f"Unexpected status code: {res.status_code}")
+
+ @classmethod
+ def requires_rule_packages(cls) -> bool:
+ return False
+
+ @classmethod
+ def requires_data_packages(cls) -> bool:
+ return False
+
+ @classmethod
+ def identifier(cls) -> str:
+ return "bb4g"
+
+ @classmethod
+ def name(cls) -> str:
+ return "BB4G Template Free Model"
+
+ @classmethod
+ def display(cls) -> str:
+ return "BB4G Template Free Model"
+
+ def build(self, eP: EnviPyDTO, *args, **kwargs) -> BuildResult | None:
+ return
+
+ def run(self, eP: EnviPyDTO, *args, **kwargs) -> RunResult:
+
+ # Ensure Service is running
+ self.start()
+
+ smiles = [c.smiles for c in eP.get_compounds()]
+ preds = self._post(smiles)
+
+ results = []
+
+ for substrate in preds.keys():
+ results.append(
+ TransformationProductPrediction(
+ substrate=substrate,
+ products=preds[substrate],
+ )
+ )
+
+ return RunResult(
+ producer=eP.get_context().url,
+ description=f"Generated at {datetime.now()}",
+ result=results,
+ )
+
+ def evaluate(self, eP: EnviPyDTO, *args, **kwargs) -> EvaluationResult:
+ pass
+
+ def build_and_evaluate(self, eP: EnviPyDTO, *args, **kwargs) -> EvaluationResult:
+ pass
+
+ def _post(self, smiles: List[str]) -> dict[str, dict[str, float]]:
+ header = {
+ "Authorization": f"Bearer {self.token}",
+ "Content-Type": "application/json",
+ }
+
+ result = {}
+
+ for smi in smiles:
+ data = {
+ "smiles": smi,
+ "sampling_alg": self.config.sampling_algorithm.value,
+ "cutoff": self.config.cutoff,
+ }
+
+ resp = requests.post(f"{self.url}/compute", headers=header, data=json.dumps(data), proxies=s.PROXIES or None)
+
+ resp.raise_for_status()
+
+ for substrate, predictions in resp.json().items():
+ preds = {}
+
+ for pred in predictions:
+ prod = pred["prediction"]
+ prob = math.exp(pred["log_likelihood"])
+ preds[prod] = prob
+
+ result[substrate] = preds
+
+ return result
diff --git a/bridge/contracts.py b/bridge/contracts.py
index 0e74e9c0..9a5f998b 100644
--- a/bridge/contracts.py
+++ b/bridge/contracts.py
@@ -254,7 +254,15 @@ class Classifier(Plugin):
def parse_config(cls, data: dict | None = None) -> EnviPyModel | None:
if cls.Config is None:
return None
- return cls.Config(**(data or {}))
+
+ # remove empty strings a.k.a unset params to not overwrite defaults
+ cpy = {}
+ if data is not None:
+ for k, v in data.items():
+ if v != "":
+ cpy[k] = v
+
+ return cls.Config(**cpy)
@classmethod
def create(cls, data: dict | None = None):
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..318686f6 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)
@@ -143,6 +143,12 @@ if os.environ.get("USE_TEMPLATE_DB", False) == "True":
"TEMPLATE": os.environ["TEMPLATE_DB"],
}
+CACHES = {
+ "default": {
+ "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
+ "LOCATION": "unique-snowflake",
+ }
+}
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
@@ -442,3 +448,46 @@ 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 = {}
+
+PROXIES = {}
+if os.environ.get("HTTP_PROXY"):
+ PROXIES["http"] = os.environ.get("HTTP_PROXY")
+ PROXIES["https"] = os.environ.get("HTTPS_PROXY")
+
+# BB4g
+BB4G_URL = os.environ.get("BB4G_URL")
+BB4G_TENANT_ID = os.environ.get("BB4G_TENANT_ID")
+BB4G_CLIENT_ID = os.environ.get("BB4G_CLIENT_ID")
+BB4G_CLIENT_SECRET = os.environ.get("BB4G_CLIENT_SECRET")
+BB4G_SCOPE = os.environ.get("BB4G_SCOPE")
diff --git a/epauth/urls.py b/epauth/urls.py
index c251d799..d777c9f1 100644
--- a/epauth/urls.py
+++ b/epauth/urls.py
@@ -5,4 +5,5 @@ from . import views
urlpatterns = [
path("entra/login/", views.entra_login, name="entra_login"),
path("auth/redirect/", views.entra_callback, name="entra_callback"),
+ path("auth/token/", views.get_token, name="get_token"),
]
diff --git a/epauth/views.py b/epauth/views.py
index 66b922c6..d0243f7d 100644
--- a/epauth/views.py
+++ b/epauth/views.py
@@ -2,9 +2,11 @@ import msal
from django.conf import settings as s
from django.contrib.auth import get_user_model
from django.contrib.auth import login
+from django.http import HttpResponse
from django.shortcuts import redirect
-from epdb.logic import UserManager
+from epdb.logic import UserManager, GroupManager
+from epdb.models import Group
def get_msal_app_with_cache(request):
@@ -80,6 +82,33 @@ 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
@@ -87,6 +116,11 @@ def get_access_token_from_request(request, scopes=None):
"""
Get an access token from the request using MSAL token cache.
"""
+
+ # Check if auth via Access Token
+ if request.headers.get("Authorization"):
+ return {"access_token": request.headers.get("Authorization").split(" ")[1]}
+
if scopes is None:
scopes = s.MS_ENTRA_SCOPES
@@ -128,3 +162,9 @@ def get_access_token_from_request(request, scopes=None):
return result
return None
+
+
+def get_token(request):
+ token = get_access_token_from_request(request)
+ msg = f"{token}"
+ return HttpResponse(msg, content_type='text/plain')
diff --git a/epdb/legacy_api.py b/epdb/legacy_api.py
index b340bd21..a8e62d7a 100644
--- a/epdb/legacy_api.py
+++ b/epdb/legacy_api.py
@@ -1,17 +1,20 @@
from collections import defaultdict
from typing import Any, Dict, List, Optional
+import jwt
import nh3
+import requests
from django.conf import settings as s
from django.contrib.auth import get_user_model
+from django.core.cache import cache
from django.http import HttpResponse, JsonResponse
from django.shortcuts import redirect
+from jwt import InvalidIssuerError
from ninja import Field, Form, Query, Router, Schema
-from ninja.security import SessionAuth
+from ninja.security import HttpBearer
from utilities.chem import FormatConverter
from utilities.misc import PackageExporter
-
from .logic import (
EPDBURLParser,
GroupManager,
@@ -46,6 +49,26 @@ from .models import (
Package = s.GET_PACKAGE_MODEL()
+def get_cached_jwks(tenant_id: str, force=False) -> Dict:
+ """Get JWKS using Django cache"""
+ cache_key = f"jwks_{tenant_id}"
+
+ jwks = cache.get(cache_key)
+
+ if jwks is None or force:
+ # Cache miss, fetch new keys
+ jwks_uri = f"https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys"
+ response = requests.get(jwks_uri)
+ response.raise_for_status()
+
+ jwks = response.json()
+
+ # Cache for 1 hour (3600 seconds)
+ cache.set(cache_key, jwks, 3600)
+
+ return jwks
+
+
def get_package_for_write(user, package_uuid):
p = PackageManager.get_package_by_id(user, package_uuid)
if not PackageManager.writable(user, p):
@@ -59,7 +82,52 @@ 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
+
+ jwks = get_cached_jwks(TENANT_ID)
+
+ 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"])
+ )
+
+ # Handle V1 and V2 tokens
+ try:
+ claims = jwt.decode(
+ token,
+ public_key,
+ algorithms=["RS256"],
+ audience=[CLIENT_ID, f"api://{CLIENT_ID}"],
+ issuer=[
+ f"https://sts.windows.net/{TENANT_ID}/",
+ f"https://login.microsoftonline.com/{TENANT_ID}/v2.0"
+ ]
+ )
+ except Exception as e:
+ raise ValueError(f"Token verification failed! - {e}")
+
+ 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 +221,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 #
@@ -767,6 +820,7 @@ class CreateCompound(Schema):
compoundName: str | None = None
compoundDescription: str | None = None
inchi: str | None = None
+ pesLink: str | None = None
@router.post("/package/{uuid:package_uuid}/compound")
@@ -778,9 +832,28 @@ def create_package_compound(
try:
p = get_package_for_write(request.user, package_uuid)
# inchi is not used atm
- c = Compound.create(
- p, c.compoundSmiles, c.compoundName, c.compoundDescription, inchi=c.inchi
- )
+
+ if c.pesLink is not None:
+ from bayer.views import fetch_pes
+ from bayer.models import PESCompound
+
+ try:
+ pes_data = fetch_pes(request, c.pesLink)
+ except ValueError as e:
+ return 400, {"message": f"Could not fetch PES data for {c.pesLink}"}
+
+ classification = pes_data.get("classificationLevel", "")
+ if "secret" == classification.lower():
+ data_pools = pes_data.get("dataPools")
+ if data_pools:
+ if s.DATA_POOL_MAPPING[p.data_pool.name] not in data_pools:
+ return 400, { "messsage": f"PES data pool {s.DATA_POOL_MAPPING[p.data_pool.name]} not found in PES data"}
+
+ c = PESCompound.create(p, pes_data, c.compoundName, c.compoundDescription)
+ else:
+ c = Compound.create(
+ p, c.compoundSmiles, c.compoundName, c.compoundDescription, inchi=c.inchi
+ )
return redirect(c.url)
except ValueError as e:
return 400, {"message": str(e)}
@@ -1801,6 +1874,7 @@ class CreateNode(Schema):
nodeName: str | None = None
nodeReason: str | None = None
nodeDepth: str | None = None
+ pesLink: str | None = None
@router.post(
@@ -1812,14 +1886,43 @@ def add_pathway_node(request, package_uuid, pathway_uuid, n: Form[CreateNode]):
p = get_package_for_write(request.user, package_uuid)
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
- if n.nodeDepth is not None and n.nodeDepth.strip() != "":
- node_depth = int(n.nodeDepth)
+ if n.pesLink:
+ from bayer.views import fetch_pes
+ from bayer.models import PESCompound
+
+ try:
+ pes_data = fetch_pes(request, c.pesLink)
+ except ValueError as e:
+ return 400, {"message": f"Could not fetch PES data for {c.pesLink}"}
+
+ classification = pes_data.get("classificationLevel", "")
+ if "secret" == classification.lower():
+ data_pools = pes_data.get("dataPools")
+ if data_pools:
+ if s.DATA_POOL_MAPPING[p.data_pool.name] not in data_pools:
+ return 400, { "messsage": f"PES data pool {s.DATA_POOL_MAPPING[p.data_pool.name]} not found in PES data"}
+
+ c = PESCompound.create(p, pes_data, c.compoundName, c.compoundDescription)
+
+ node = Node()
+ node.stereo_removed = False
+ node.pathway = pw
+ node.depth = 0
+
+ node.default_node_label = c.default_structure
+ node.save()
+
+ node.node_labels.add(c.default_structure)
+ node.save()
else:
- node_depth = -1
+ if n.nodeDepth is not None and n.nodeDepth.strip() != "":
+ node_depth = int(n.nodeDepth)
+ else:
+ node_depth = -1
- n = Node.create(pw, n.nodeAsSmiles, node_depth, n.nodeName, n.nodeReason)
+ node = Node.create(pw, n.nodeAsSmiles, node_depth, n.nodeName, n.nodeReason)
- return redirect(n.url)
+ return redirect(node.url)
except ValueError:
return 403, {"message": "Adding node failed!"}
@@ -1929,13 +2032,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 +2052,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..95b3eb42 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":
@@ -906,12 +937,14 @@ def package_models(request, package_uuid):
"requires_rule_packages": True,
"requires_data_packages": True,
},
- "EnviFormer": {
+ }
+
+ if s.ENVIFORMER_PRESENT:
+ context["model_types"]["EnviFormer"] = {
"type": "enviformer",
"requires_rule_packages": False,
"requires_data_packages": True,
},
- }
if s.FLAGS.get("PLUGINS", False):
for k, v in s.CLASSIFIER_PLUGINS.items():
diff --git a/static/images/Restricted.gif b/static/images/Restricted.gif
new file mode 100644
index 00000000..12ea8cfd
Binary files /dev/null and b/static/images/Restricted.gif differ
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 00000000..6a5c248c
Binary files /dev/null and b/static/images/restricted_mid.png differ
diff --git a/static/images/secret_mid.png b/static/images/secret_mid.png
new file mode 100644
index 00000000..f9772030
Binary files /dev/null and b/static/images/secret_mid.png differ
diff --git a/static/images/secret_small.png b/static/images/secret_small.png
new file mode 100644
index 00000000..f8d6cc03
Binary files /dev/null and b/static/images/secret_small.png differ
diff --git a/static/js/pw.js b/static/js/pw.js
index 26f4d870..4f581fc2 100644
--- a/static/js/pw.js
+++ b/static/js/pw.js
@@ -605,7 +605,36 @@ function draw(pathway, elem) {
// Check if target is pseudo and draw marker only if not pseudo
.attr("class", d => d.target.pseudo ? "link_no_arrow" : "link")
.attr("marker-end", d => d.target.pseudo ? '' : d.multi_step ? 'url(#doublearrow)' : 'url(#arrow)')
+ .on("click", function(event, d) {
+ const wasHighlighted = d3.select(this).classed("highlighted");
+ d3.selectAll("line").classed("highlighted", false);
+
+ if (!wasHighlighted) {
+ const toHighlight = [];
+ toHighlight.push(d.el);
+
+ if (d.source.pseudo || d.target.pseudo) {
+ if (d.target.pseudo) {
+ d3.selectAll("line").each(e => {
+ if (e !== undefined && e.source.id === d.target.id) {
+ toHighlight.push(e.el);
+ }
+ });
+ } else {
+ d3.selectAll("line").each(e => {
+ if (e !== undefined && (e.target.id === d.source.id || e.source.id === d.source.id)) {
+ toHighlight.push(e.el);
+ }
+ });
+ }
+ }
+
+ for (const e of toHighlight) {
+ d3.select(e).classed("highlighted", true);
+ }
+ }
+ })
// add element to links array
link.each(function (d) {
@@ -624,7 +653,13 @@ function draw(pathway, elem) {
.on("drag", dragged)
.on("end", dragended))
.on("click", function (event, d) {
- d3.select(this).select("circle").classed("highlighted", !d3.select(this).select("circle").classed("highlighted"));
+ const wasHighlighted = d3.select(this).select("circle").classed("highlighted");
+
+ d3.selectAll('circle.highlighted').classed('highlighted', false);
+
+ if (!wasHighlighted) {
+ d3.select(this).select("circle").classed("highlighted", !d3.select(this).select("circle").classed("highlighted"));
+ }
})
// Kreise für die Knoten hinzufügen
diff --git a/templates/actions/objects/pathway.html b/templates/actions/objects/pathway.html
index 85e4164b..91c861d6 100644
--- a/templates/actions/objects/pathway.html
+++ b/templates/actions/objects/pathway.html
@@ -22,7 +22,7 @@
{% for tpl in action_button_templates %}
{% include tpl %}
{% endfor %}
-
+
{% endif %}
-
+
Set Aliases
-
+
Delete Compound
@@ -111,7 +116,12 @@
Delete Reaction
diff --git a/templates/collections/compounds_paginated.html b/templates/collections/compounds_paginated.html
index f5868225..fd30d98e 100644
--- a/templates/collections/compounds_paginated.html
+++ b/templates/collections/compounds_paginated.html
@@ -5,7 +5,7 @@
{% block action_button %}
- {% if meta.can_edit %}
+ {% if meta.can_edit or not meta.url_contains_package %}
- New Model
-
+ {% if meta.can_edit or not meta.url_contains_package %}
+ {% if meta.enabled_features.MODEL_BUILDING %}
+
+ New Model
+
+ {% endif %}
{% endif %}
{% endblock action_button %}
diff --git a/templates/collections/packages_paginated.html b/templates/collections/packages_paginated.html
index c9d9668b..cd59b6eb 100644
--- a/templates/collections/packages_paginated.html
+++ b/templates/collections/packages_paginated.html
@@ -3,7 +3,6 @@
{% block page_title %}Packages{% endblock %}
{% block action_button %}
- {% if meta.can_edit %}
- {% endif %}
{% endblock action_button %}
{% block action_modals %}
diff --git a/templates/collections/pathways_paginated.html b/templates/collections/pathways_paginated.html
index 900f68d4..e87711dd 100644
--- a/templates/collections/pathways_paginated.html
+++ b/templates/collections/pathways_paginated.html
@@ -3,7 +3,7 @@
{% block page_title %}Pathways{% endblock %}
{% block action_button %}
- {% if meta.can_edit %}
+ {% if meta.can_edit or not meta.url_contains_package %}
+
{% if not public_mode %}
@@ -88,12 +89,23 @@
>Scenario
+
+
+ Group
+
{% endif %}
+ {% 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 %}
{% if not public_mode %}
diff --git a/templates/modals/objects/delete_pathway_edge_modal.html b/templates/modals/objects/delete_pathway_edge_modal.html
index 82ad8f4f..a56e3137 100644
--- a/templates/modals/objects/delete_pathway_edge_modal.html
+++ b/templates/modals/objects/delete_pathway_edge_modal.html
@@ -4,6 +4,25 @@
id="delete_pathway_edge_modal"
class="modal"
x-data="modalForm({ state: { selectedEdge: '', imageUrl: '' } })"
+ @modal-opened.window="
+ const links = d3.selectAll('line.highlighted');
+ console.log(links);
+ if (!links.empty()) {
+ const el = links.node();
+ const selectElement = document.getElementById('delete_pathway_edge_edges');
+ console.log(el);
+ console.log(el.__data__);
+ for (let option of selectElement.options) {
+ if (option.value === el.__data__.url) {
+ option.selected = true;
+ break;
+ }
+ }
+
+ selectElement.dispatchEvent(new Event('change'));
+
+ }
+ "
@close="reset()"
>
diff --git a/templates/modals/objects/delete_pathway_node_modal.html b/templates/modals/objects/delete_pathway_node_modal.html
index 41767e8f..b2075f3e 100644
--- a/templates/modals/objects/delete_pathway_node_modal.html
+++ b/templates/modals/objects/delete_pathway_node_modal.html
@@ -4,6 +4,22 @@
id="delete_pathway_node_modal"
class="modal"
x-data="modalForm({ state: { selectedNode: '', imageUrl: '' } })"
+ @modal-opened.window="
+ const el = d3.select('circle.highlighted').node();
+
+ if (el !== null) {
+ const selectElement = document.getElementById('delete_pathway_node_nodes');
+
+ for (let option of selectElement.options) {
+ if (option.value === el.__data__.url) {
+ option.selected = true;
+ break;
+ }
+ }
+
+ selectElement.dispatchEvent(new Event('change'));
+ }
+ "
@close="reset()"
>
diff --git a/templates/objects/node.html b/templates/objects/node.html
index 3278b69c..5f5515f9 100644
--- a/templates/objects/node.html
+++ b/templates/objects/node.html
@@ -1,4 +1,5 @@
{% extends "framework_modern.html" %}
+{% load envipytags %}
{% block content %}
@@ -54,6 +55,12 @@
+ {% epdb_slot_templates "epdb.objects.node.viz" as viz_templates %}
+
+ {% for tpl in viz_templates %}
+ {% include tpl %}
+ {% endfor %}
+
diff --git a/templates/objects/pathway.html b/templates/objects/pathway.html
index 3ca6045e..e90eee2f 100644
--- a/templates/objects/pathway.html
+++ b/templates/objects/pathway.html
@@ -106,12 +106,12 @@
-