2 Commits

Author SHA1 Message Date
04f9c9252a ... 2025-11-04 10:15:06 +01:00
c2d45917ce clean up d3_json 2025-10-31 09:38:41 +01:00
36 changed files with 169 additions and 486 deletions

View File

@ -52,28 +52,6 @@ INSTALLED_APPS = [
"migration", "migration",
] ]
# Add the TENANT providing implementations for
# Required
# - Package
# - Compound (TODO)
# - CompoundStructure (TODO)
# Optional
# - PackageManager (TODO)
# - GroupManager (TODO)
# - SettingManager (TODO)
TENANT = os.environ.get("TENANT", "public")
INSTALLED_APPS.append(TENANT)
PACKAGE_IMPLEMENTATION = f"{TENANT}.Package"
PACKAGE_MODULE_PATH = f"{TENANT}.models.Package"
def GET_PACKAGE_MODEL():
from django.apps import apps
return apps.get_model(TENANT, "Package")
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend", "django.contrib.auth.backends.ModelBackend",
] ]

View File

@ -1,11 +1,11 @@
from django.contrib import admin from django.contrib import admin
from django.conf import settings as s
from .models import ( from .models import (
User, User,
UserPackagePermission, UserPackagePermission,
Group, Group,
GroupPackagePermission, GroupPackagePermission,
Package,
MLRelativeReasoning, MLRelativeReasoning,
EnviFormer, EnviFormer,
Compound, Compound,
@ -24,9 +24,6 @@ from .models import (
) )
Package = s.GET_PACKAGE_MODEL()
class UserAdmin(admin.ModelAdmin): class UserAdmin(admin.ModelAdmin):
list_display = ["username", "email", "is_active"] list_display = ["username", "email", "is_active"]

View File

@ -1,6 +1,5 @@
from typing import List, Dict, Optional, Any from typing import List, Dict, Optional, Any
from django.conf import settings as s
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
@ -11,6 +10,7 @@ from .logic import PackageManager, UserManager, SettingManager
from .models import ( from .models import (
Compound, Compound,
CompoundStructure, CompoundStructure,
Package,
User, User,
UserPackagePermission, UserPackagePermission,
Rule, Rule,
@ -23,9 +23,6 @@ from .models import (
) )
Package = s.GET_PACKAGE_MODEL()
def _anonymous_or_real(request): def _anonymous_or_real(request):
if request.user.is_authenticated and not request.user.is_anonymous: if request.user.is_authenticated and not request.user.is_anonymous:
return request.user return request.user

View File

@ -11,6 +11,7 @@ from pydantic import ValidationError
from epdb.models import ( from epdb.models import (
User, User,
Package,
UserPackagePermission, UserPackagePermission,
GroupPackagePermission, GroupPackagePermission,
Permission, Permission,
@ -32,8 +33,6 @@ from utilities.misc import PackageImporter, PackageExporter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
Package = s.GET_PACKAGE_MODEL()
class EPDBURLParser: class EPDBURLParser:
UUID_PATTERN = r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}" UUID_PATTERN = r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"

View File

@ -2,9 +2,7 @@ from django.conf import settings as s
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.db import transaction from django.db import transaction
from epdb.models import EnviFormer, MLRelativeReasoning from epdb.models import MLRelativeReasoning, EnviFormer, Package
Package = s.GET_PACKAGE_MODEL()
class Command(BaseCommand): class Command(BaseCommand):
@ -77,13 +75,11 @@ class Command(BaseCommand):
return packages return packages
# Iteratively create models in options["model_names"] # Iteratively create models in options["model_names"]
print( print(f"Creating models: {options['model_names']}\n"
f"Creating models: {options['model_names']}\n" f"Data packages: {options['data_packages']}\n"
f"Data packages: {options['data_packages']}\n" f"Rule Packages (only for MLRR): {options['rule_packages']}\n"
f"Rule Packages (only for MLRR): {options['rule_packages']}\n" f"Eval Packages: {options['eval_packages']}\n"
f"Eval Packages: {options['eval_packages']}\n" f"Threshold: {options['threshold']:.2f}")
f"Threshold: {options['threshold']:.2f}"
)
data_packages = decode_packages(options["data_packages"]) data_packages = decode_packages(options["data_packages"])
eval_packages = decode_packages(options["eval_packages"]) eval_packages = decode_packages(options["eval_packages"])
rule_packages = decode_packages(options["rule_packages"]) rule_packages = decode_packages(options["rule_packages"])
@ -94,10 +90,10 @@ class Command(BaseCommand):
pack, pack,
data_packages=data_packages, data_packages=data_packages,
eval_packages=eval_packages, eval_packages=eval_packages,
threshold=options["threshold"], threshold=options['threshold'],
name=f"EnviFormer - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}", name=f"EnviFormer - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
description=f"EnviFormer transformer trained on {options['data_packages']} " description=f"EnviFormer transformer trained on {options['data_packages']} "
f"evaluated on {options['eval_packages']}.", f"evaluated on {options['eval_packages']}.",
) )
elif model_name == "mlrr": elif model_name == "mlrr":
model = MLRelativeReasoning.create( model = MLRelativeReasoning.create(
@ -105,10 +101,10 @@ class Command(BaseCommand):
rule_packages=rule_packages, rule_packages=rule_packages,
data_packages=data_packages, data_packages=data_packages,
eval_packages=eval_packages, eval_packages=eval_packages,
threshold=options["threshold"], threshold=options['threshold'],
name=f"ECC - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}", name=f"ECC - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
description=f"ML Relative Reasoning trained on {options['data_packages']} with rules from " description=f"ML Relative Reasoning trained on {options['data_packages']} with rules from "
f"{options['rule_packages']} and evaluated on {options['eval_packages']}.", f"{options['rule_packages']} and evaluated on {options['eval_packages']}.",
) )
else: else:
raise ValueError(f"Cannot create model of type {model_name}, unknown model type") raise ValueError(f"Cannot create model of type {model_name}, unknown model type")

View File

@ -8,9 +8,7 @@ from django.conf import settings as s
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.db import transaction from django.db import transaction
from epdb.models import EnviFormer from epdb.models import EnviFormer, Package
Package = s.GET_PACKAGE_MODEL()
class Command(BaseCommand): class Command(BaseCommand):

View File

@ -1,8 +1,8 @@
from django.apps import apps from django.apps import apps
from django.conf import settings as s
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.db.models import F, JSONField, TextField, Value
from django.db.models.functions import Cast, Replace from django.db.models import F, Value, TextField, JSONField
from django.db.models.functions import Replace, Cast
from epdb.models import EnviPathModel from epdb.models import EnviPathModel
@ -23,13 +23,10 @@ class Command(BaseCommand):
) )
def handle(self, *args, **options): def handle(self, *args, **options):
Package = s.GET_PACKAGE_MODEL()
print("Localizing urls for Package")
Package.objects.update(url=Replace(F("url"), Value(options["old"]), Value(options["new"])))
MODELS = [ MODELS = [
"User", "User",
"Group", "Group",
"Package",
"Compound", "Compound",
"CompoundStructure", "CompoundStructure",
"Pathway", "Pathway",

View File

@ -1,190 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-29 13:32
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("epdb", "0009_joblog"),
("public", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="userpackagepermission",
name="package",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="public.package",
verbose_name="Permission on",
),
),
migrations.AlterField(
model_name="grouppackagepermission",
name="package",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="public.package",
verbose_name="Permission on",
),
),
migrations.AlterField(
model_name="epmodel",
name="package",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="public.package",
verbose_name="Package",
),
),
migrations.AlterField(
model_name="rule",
name="package",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="public.package",
verbose_name="Package",
),
),
migrations.AlterField(
model_name="compound",
name="package",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="public.package",
verbose_name="Package",
),
),
migrations.AlterField(
model_name="scenario",
name="package",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="public.package",
verbose_name="Package",
),
),
migrations.AlterField(
model_name="pathway",
name="package",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="public.package",
verbose_name="Package",
),
),
migrations.AlterField(
model_name="reaction",
name="package",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="public.package",
verbose_name="Package",
),
),
migrations.AlterField(
model_name="user",
name="default_package",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="public.package",
verbose_name="Default Package",
),
),
migrations.AlterField(
model_name="enviformer",
name="data_packages",
field=models.ManyToManyField(
related_name="%(app_label)s_%(class)s_data_packages",
to="public.package",
verbose_name="Data Packages",
),
),
migrations.AlterField(
model_name="enviformer",
name="eval_packages",
field=models.ManyToManyField(
related_name="%(app_label)s_%(class)s_eval_packages",
to="public.package",
verbose_name="Evaluation Packages",
),
),
migrations.AlterField(
model_name="enviformer",
name="rule_packages",
field=models.ManyToManyField(
related_name="%(app_label)s_%(class)s_rule_packages",
to="public.package",
verbose_name="Rule Packages",
),
),
migrations.AlterField(
model_name="mlrelativereasoning",
name="data_packages",
field=models.ManyToManyField(
related_name="%(app_label)s_%(class)s_data_packages",
to="public.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="public.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="public.package",
verbose_name="Rule Packages",
),
),
migrations.AlterField(
model_name="rulebasedrelativereasoning",
name="data_packages",
field=models.ManyToManyField(
related_name="%(app_label)s_%(class)s_data_packages",
to="public.package",
verbose_name="Data Packages",
),
),
migrations.AlterField(
model_name="rulebasedrelativereasoning",
name="eval_packages",
field=models.ManyToManyField(
related_name="%(app_label)s_%(class)s_eval_packages",
to="public.package",
verbose_name="Evaluation Packages",
),
),
migrations.AlterField(
model_name="rulebasedrelativereasoning",
name="rule_packages",
field=models.ManyToManyField(
related_name="%(app_label)s_%(class)s_rule_packages",
to="public.package",
verbose_name="Rule Packages",
),
),
migrations.AlterField(
model_name="setting",
name="rule_packages",
field=models.ManyToManyField(
blank=True,
related_name="setting_rule_packages",
to="public.package",
verbose_name="Setting Rule Packages",
),
),
migrations.DeleteModel(
name="Package",
),
]

View File

@ -7,7 +7,7 @@ import secrets
from abc import abstractmethod from abc import abstractmethod
from collections import defaultdict from collections import defaultdict
from datetime import datetime from datetime import datetime
from typing import Union, List, Optional, Dict, Tuple, Set, Any, TYPE_CHECKING from typing import Union, List, Optional, Dict, Tuple, Set, Any
from uuid import uuid4 from uuid import uuid4
import math import math
import joblib import joblib
@ -32,8 +32,6 @@ from utilities.ml import Dataset, ApplicabilityDomainPCA, EnsembleClassifierChai
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if TYPE_CHECKING:
Package = s.GET_PACKAGE_MODEL()
########################## ##########################
# User/Groups/Permission # # User/Groups/Permission #
@ -47,10 +45,7 @@ class User(AbstractUser):
) )
url = models.TextField(blank=False, null=True, verbose_name="URL", unique=True) url = models.TextField(blank=False, null=True, verbose_name="URL", unique=True)
default_package = models.ForeignKey( default_package = models.ForeignKey(
s.PACKAGE_IMPLEMENTATION, "epdb.Package", verbose_name="Default Package", null=True, on_delete=models.SET_NULL
verbose_name="Default Package",
null=True,
on_delete=models.SET_NULL,
) )
default_group = models.ForeignKey( default_group = models.ForeignKey(
"Group", "Group",
@ -240,7 +235,7 @@ class UserPackagePermission(Permission):
) )
user = models.ForeignKey("User", verbose_name="Permission to", on_delete=models.CASCADE) user = models.ForeignKey("User", verbose_name="Permission to", on_delete=models.CASCADE)
package = models.ForeignKey( package = models.ForeignKey(
s.PACKAGE_IMPLEMENTATION, verbose_name="Permission on", on_delete=models.CASCADE "epdb.Package", verbose_name="Permission on", on_delete=models.CASCADE
) )
class Meta: class Meta:
@ -256,7 +251,7 @@ class GroupPackagePermission(Permission):
) )
group = models.ForeignKey("Group", verbose_name="Permission to", on_delete=models.CASCADE) group = models.ForeignKey("Group", verbose_name="Permission to", on_delete=models.CASCADE)
package = models.ForeignKey( package = models.ForeignKey(
s.PACKAGE_IMPLEMENTATION, verbose_name="Permission on", on_delete=models.CASCADE "epdb.Package", verbose_name="Permission on", on_delete=models.CASCADE
) )
class Meta: class Meta:
@ -656,7 +651,7 @@ class License(models.Model):
image_link = models.URLField(blank=False, null=False, verbose_name="Image link") image_link = models.URLField(blank=False, null=False, verbose_name="Image link")
class AbstractPackage(EnviPathModel): class Package(EnviPathModel):
reviewed = models.BooleanField(verbose_name="Reviewstatus", default=False) reviewed = models.BooleanField(verbose_name="Reviewstatus", default=False)
license = models.ForeignKey( license = models.ForeignKey(
"epdb.License", on_delete=models.SET_NULL, blank=True, null=True, verbose_name="License" "epdb.License", on_delete=models.SET_NULL, blank=True, null=True, verbose_name="License"
@ -724,13 +719,10 @@ class AbstractPackage(EnviPathModel):
rules = sorted(rules, key=lambda x: x.url) rules = sorted(rules, key=lambda x: x.url)
return rules return rules
class Meta:
abstract = True
class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin): class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin):
package = models.ForeignKey( package = models.ForeignKey(
s.PACKAGE_IMPLEMENTATION, verbose_name="Package", on_delete=models.CASCADE, db_index=True "epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
) )
default_structure = models.ForeignKey( default_structure = models.ForeignKey(
"CompoundStructure", "CompoundStructure",
@ -780,7 +772,7 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin
@staticmethod @staticmethod
@transaction.atomic @transaction.atomic
def create( def create(
package: "Package", smiles: str, name: str = None, description: str = None, *args, **kwargs package: Package, smiles: str, name: str = None, description: str = None, *args, **kwargs
) -> "Compound": ) -> "Compound":
if smiles is None or smiles.strip() == "": if smiles is None or smiles.strip() == "":
raise ValueError("SMILES is required") raise ValueError("SMILES is required")
@ -1058,7 +1050,7 @@ class EnzymeLink(EnviPathModel, KEGGIdentifierMixin):
class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin): class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
package = models.ForeignKey( package = models.ForeignKey(
s.PACKAGE_IMPLEMENTATION, verbose_name="Package", on_delete=models.CASCADE, db_index=True "epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
) )
# # https://github.com/django-polymorphic/django-polymorphic/issues/229 # # https://github.com/django-polymorphic/django-polymorphic/issues/229
@ -1164,7 +1156,7 @@ class SimpleAmbitRule(SimpleRule):
@staticmethod @staticmethod
@transaction.atomic @transaction.atomic
def create( def create(
package: "Package", package: Package,
name: str = None, name: str = None,
description: str = None, description: str = None,
smirks: str = None, smirks: str = None,
@ -1230,7 +1222,6 @@ class SimpleAmbitRule(SimpleRule):
@property @property
def related_reactions(self): def related_reactions(self):
Package = s.GET_PACKAGE_MODEL()
qs = Package.objects.filter(reviewed=True) qs = Package.objects.filter(reviewed=True)
return self.reaction_rule.filter(package__in=qs).order_by("name") return self.reaction_rule.filter(package__in=qs).order_by("name")
@ -1323,7 +1314,7 @@ class SequentialRuleOrdering(models.Model):
class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin): class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin):
package = models.ForeignKey( package = models.ForeignKey(
s.PACKAGE_IMPLEMENTATION, verbose_name="Package", on_delete=models.CASCADE, db_index=True "epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
) )
educts = models.ManyToManyField( educts = models.ManyToManyField(
"epdb.CompoundStructure", verbose_name="Educts", related_name="reaction_educts" "epdb.CompoundStructure", verbose_name="Educts", related_name="reaction_educts"
@ -1345,7 +1336,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
@staticmethod @staticmethod
@transaction.atomic @transaction.atomic
def create( def create(
package: "Package", package: Package,
name: str = None, name: str = None,
description: str = None, description: str = None,
educts: Union[List[str], List[CompoundStructure]] = None, educts: Union[List[str], List[CompoundStructure]] = None,
@ -1505,7 +1496,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
class Pathway(EnviPathModel, AliasMixin, ScenarioMixin): class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
package = models.ForeignKey( package = models.ForeignKey(
s.PACKAGE_IMPLEMENTATION, verbose_name="Package", on_delete=models.CASCADE, db_index=True "epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
) )
setting = models.ForeignKey( setting = models.ForeignKey(
"epdb.Setting", verbose_name="Setting", on_delete=models.CASCADE, null=True, blank=True "epdb.Setting", verbose_name="Setting", on_delete=models.CASCADE, null=True, blank=True
@ -1582,14 +1573,12 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
while len(queue): while len(queue):
current = queue.pop() current = queue.pop()
processed.add(current) processed.add(current)
nodes.append(current.d3_json()) nodes.append(current.d3_json())
for e in self.edges: for e in self.edges.filter(start_nodes=current).distinct():
if current in e.start_nodes.all(): for prod in e.end_nodes.all():
for prod in e.end_nodes.all(): if prod not in queue and prod not in processed:
if prod not in queue and prod not in processed: queue.append(prod)
queue.append(prod)
# We shouldn't lose or make up nodes... # We shouldn't lose or make up nodes...
assert len(nodes) == len(self.nodes) assert len(nodes) == len(self.nodes)
@ -2061,7 +2050,7 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin):
class EPModel(PolymorphicModel, EnviPathModel): class EPModel(PolymorphicModel, EnviPathModel):
package = models.ForeignKey( package = models.ForeignKey(
s.PACKAGE_IMPLEMENTATION, verbose_name="Package", on_delete=models.CASCADE, db_index=True "epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
) )
def _url(self): def _url(self):
@ -2070,17 +2059,17 @@ class EPModel(PolymorphicModel, EnviPathModel):
class PackageBasedModel(EPModel): class PackageBasedModel(EPModel):
rule_packages = models.ManyToManyField( rule_packages = models.ManyToManyField(
s.PACKAGE_IMPLEMENTATION, "Package",
verbose_name="Rule Packages", verbose_name="Rule Packages",
related_name="%(app_label)s_%(class)s_rule_packages", related_name="%(app_label)s_%(class)s_rule_packages",
) )
data_packages = models.ManyToManyField( data_packages = models.ManyToManyField(
s.PACKAGE_IMPLEMENTATION, "Package",
verbose_name="Data Packages", verbose_name="Data Packages",
related_name="%(app_label)s_%(class)s_data_packages", related_name="%(app_label)s_%(class)s_data_packages",
) )
eval_packages = models.ManyToManyField( eval_packages = models.ManyToManyField(
s.PACKAGE_IMPLEMENTATION, "Package",
verbose_name="Evaluation Packages", verbose_name="Evaluation Packages",
related_name="%(app_label)s_%(class)s_eval_packages", related_name="%(app_label)s_%(class)s_eval_packages",
) )
@ -3448,7 +3437,7 @@ class PluginModel(EPModel):
class Scenario(EnviPathModel): class Scenario(EnviPathModel):
package = models.ForeignKey( package = models.ForeignKey(
s.PACKAGE_IMPLEMENTATION, verbose_name="Package", on_delete=models.CASCADE, db_index=True "epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
) )
scenario_date = models.CharField(max_length=256, null=False, blank=False, default="No date") scenario_date = models.CharField(max_length=256, null=False, blank=False, default="No date")
scenario_type = models.CharField( scenario_type = models.CharField(
@ -3599,7 +3588,7 @@ class Setting(EnviPathModel):
) )
rule_packages = models.ManyToManyField( rule_packages = models.ManyToManyField(
s.PACKAGE_IMPLEMENTATION, "Package",
verbose_name="Setting Rule Packages", verbose_name="Setting Rule Packages",
related_name="setting_rule_packages", related_name="setting_rule_packages",
blank=True, blank=True,

View File

@ -7,12 +7,9 @@ from uuid import uuid4
from celery import shared_task from celery import shared_task
from celery.utils.functional import LRUCache from celery.utils.functional import LRUCache
from django.conf import settings as s
from epdb.logic import SPathway from epdb.logic import SPathway
from epdb.models import Edge, EPModel, JobLog, Node, Pathway, Rule, Setting, User from epdb.models import EPModel, JobLog, Node, Package, Pathway, Rule, Setting, User, Edge
Package = s.GET_PACKAGE_MODEL()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times. ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times.
@ -175,6 +172,7 @@ def predict(
except Exception as e: except Exception as e:
pw.kv.update({"status": "failed"}) pw.kv.update({"status": "failed"})
pw.kv.update(**{"error": str(e)})
pw.save() pw.save()
if JobLog.objects.filter(task_id=self.request.id).exists(): if JobLog.objects.filter(task_id=self.request.id).exists():

View File

@ -1,11 +1,11 @@
import json import json
import logging import logging
from typing import Any, Dict, List from typing import List, Dict, Any
from django.conf import settings as s from django.conf import settings as s
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed, JsonResponse from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest
from django.shortcuts import redirect, render from django.shortcuts import render, redirect
from django.urls import reverse from django.urls import reverse
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from envipy_additional_information import NAME_MAPPING from envipy_additional_information import NAME_MAPPING
@ -14,43 +14,42 @@ from oauth2_provider.decorators import protected_resource
from utilities.chem import FormatConverter, IndigoUtils from utilities.chem import FormatConverter, IndigoUtils
from utilities.decorators import package_permission_required from utilities.decorators import package_permission_required
from utilities.misc import HTMLGenerator from utilities.misc import HTMLGenerator
from .logic import ( from .logic import (
EPDBURLParser,
GroupManager, GroupManager,
PackageManager, PackageManager,
SearchManager,
SettingManager,
UserManager, UserManager,
SettingManager,
SearchManager,
EPDBURLParser,
) )
from .models import ( from .models import (
APIToken, Package,
Compound,
CompoundStructure,
Edge,
EnviFormer,
EnzymeLink,
EPModel,
ExternalDatabase,
ExternalIdentifier,
Group,
GroupPackagePermission, GroupPackagePermission,
JobLog, Group,
License, CompoundStructure,
MLRelativeReasoning, Compound,
Node,
Pathway,
Permission,
Reaction, Reaction,
Rule, Rule,
Pathway,
Node,
EPModel,
EnviFormer,
MLRelativeReasoning,
RuleBasedRelativeReasoning, RuleBasedRelativeReasoning,
Scenario, Scenario,
SimpleAmbitRule, SimpleAmbitRule,
User, APIToken,
UserPackagePermission, UserPackagePermission,
Permission,
License,
User,
Edge,
ExternalDatabase,
ExternalIdentifier,
EnzymeLink,
JobLog,
) )
Package = s.GET_PACKAGE_MODEL()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -83,7 +82,8 @@ def login(request):
return render(request, "static/login.html", context) return render(request, "static/login.html", context)
elif request.method == "POST": elif request.method == "POST":
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate
from django.contrib.auth import login
username = request.POST.get("username") username = request.POST.get("username")
password = request.POST.get("password") password = request.POST.get("password")
@ -832,7 +832,7 @@ def package_models(request, package_uuid):
request, "Invalid model type.", f'Model type "{model_type}" is not supported."' request, "Invalid model type.", f'Model type "{model_type}" is not supported."'
) )
from .tasks import build_model, dispatch from .tasks import dispatch, build_model
dispatch(current_user, build_model, mod.pk) dispatch(current_user, build_model, mod.pk)
@ -2325,9 +2325,9 @@ def package_scenarios(request, package_uuid):
context["unreviewed_objects"] = unreviewed_scenario_qs context["unreviewed_objects"] = unreviewed_scenario_qs
from envipy_additional_information import ( from envipy_additional_information import (
SEDIMENT_ADDITIONAL_INFORMATION,
SLUDGE_ADDITIONAL_INFORMATION, SLUDGE_ADDITIONAL_INFORMATION,
SOIL_ADDITIONAL_INFORMATION, SOIL_ADDITIONAL_INFORMATION,
SEDIMENT_ADDITIONAL_INFORMATION,
) )
context["scenario_types"] = { context["scenario_types"] = {

Binary file not shown.

View File

@ -1,18 +1,21 @@
import gzip
import json import json
import logging import logging
import os.path import os.path
from datetime import datetime
from django.conf import settings as s from django.conf import settings as s
from django.http import HttpResponseNotAllowed from django.http import HttpResponseNotAllowed
from django.shortcuts import render from django.shortcuts import render
from rdkit import Chem
from rdkit.Chem.MolStandardize import rdMolStandardize
from epdb.models import CompoundStructure, Rule, SimpleAmbitRule from epdb.logic import PackageManager
from epdb.views import get_base_context from epdb.models import Rule, SimpleAmbitRule, Package, CompoundStructure
from epdb.views import get_base_context, _anonymous_or_real
from utilities.chem import FormatConverter from utilities.chem import FormatConverter
Package = s.GET_PACKAGE_MODEL()
from rdkit import Chem
from rdkit.Chem.MolStandardize import rdMolStandardize
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -56,7 +59,9 @@ def run_both_engines(SMILES, SMIRKS):
set( set(
[ [
normalize_smiles(str(x)) normalize_smiles(str(x))
for x in FormatConverter.sanitize_smiles([str(s) for s in all_rdkit_prods])[0] for x in FormatConverter.sanitize_smiles(
[str(s) for s in all_rdkit_prods]
)[0]
] ]
) )
) )
@ -80,7 +85,8 @@ def migration(request):
url="http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1" url="http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1"
) )
ALL_SMILES = [ ALL_SMILES = [
cs.smiles for cs in CompoundStructure.objects.filter(compound__package=BBD) cs.smiles
for cs in CompoundStructure.objects.filter(compound__package=BBD)
] ]
RULES = SimpleAmbitRule.objects.filter(package=BBD) RULES = SimpleAmbitRule.objects.filter(package=BBD)
@ -136,7 +142,9 @@ def migration(request):
) )
for r in migration_status["results"]: for r in migration_status["results"]:
r["detail_url"] = r["detail_url"].replace("http://localhost:8000", s.SERVER_URL) r["detail_url"] = r["detail_url"].replace(
"http://localhost:8000", s.SERVER_URL
)
context.update(**migration_status) context.update(**migration_status)
@ -144,6 +152,8 @@ def migration(request):
def migration_detail(request, package_uuid, rule_uuid): def migration_detail(request, package_uuid, rule_uuid):
current_user = _anonymous_or_real(request)
if request.method == "GET": if request.method == "GET":
context = get_base_context(request) context = get_base_context(request)
@ -225,7 +235,9 @@ def compare(request):
context["smirks"] = ( context["smirks"] = (
"[#1,#6:6][#7;X3;!$(NC1CC1)!$([N][C]=O)!$([!#8]CNC=O):1]([#1,#6:7])[#6;A;X4:2][H:3]>>[#1,#6:6][#7;X3:1]([#1,#6:7])[H:3].[#6;A:2]=O" "[#1,#6:6][#7;X3;!$(NC1CC1)!$([N][C]=O)!$([!#8]CNC=O):1]([#1,#6:7])[#6;A;X4:2][H:3]>>[#1,#6:6][#7;X3:1]([#1,#6:7])[H:3].[#6;A:2]=O"
) )
context["smiles"] = "C(CC(=O)N[C@@H](CS[Se-])C(=O)NCC(=O)[O-])[C@@H](C(=O)[O-])N" context["smiles"] = (
"C(CC(=O)N[C@@H](CS[Se-])C(=O)NCC(=O)[O-])[C@@H](C(=O)[O-])N"
)
return render(request, "compare.html", context) return render(request, "compare.html", context)
elif request.method == "POST": elif request.method == "POST":

View File

View File

@ -1 +0,0 @@
# Register your models here.

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class PublicConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "public"

View File

@ -1,56 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-29 13:32
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")),
],
options={
"db_table": "epdb_package",
"managed": False,
},
),
]

View File

@ -1,16 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-29 18:39
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("public", "0001_initial"),
]
operations = [
migrations.AlterModelOptions(
name="package",
options={},
),
]

View File

@ -1,25 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-29 18:40
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("epdb", "0010_alter_userpackagepermission_package_and_more"),
("public", "0002_alter_package_options"),
]
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",
),
),
]

View File

@ -1,6 +0,0 @@
from epdb.models import AbstractPackage
class Package(AbstractPackage):
class Meta:
db_table = "epdb_package"

View File

@ -1 +0,0 @@
# Create your tests here.

View File

@ -1 +0,0 @@
# Create your views here.

View File

@ -1,15 +1,10 @@
from collections import defaultdict from collections import defaultdict
from datetime import datetime from datetime import datetime
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from django.conf import settings as s
from django.test import TestCase, tag from django.test import TestCase, tag
from epdb.logic import PackageManager from epdb.logic import PackageManager
from epdb.models import EnviFormer, Setting, User from epdb.models import User, EnviFormer, Package, Setting
from epdb.tasks import predict, predict_simple from epdb.tasks import predict_simple, predict
Package = s.GET_PACKAGE_MODEL()
def measure_predict(mod, pathway_pk=None): def measure_predict(mod, pathway_pk=None):

View File

@ -1,13 +1,10 @@
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
import numpy as np import numpy as np
from django.conf import settings as s
from django.test import TestCase from django.test import TestCase
from epdb.logic import PackageManager from epdb.logic import PackageManager
from epdb.models import MLRelativeReasoning, User from epdb.models import User, MLRelativeReasoning, Package
Package = s.GET_PACKAGE_MODEL()
class ModelTest(TestCase): class ModelTest(TestCase):

View File

@ -1,12 +1,8 @@
from django.conf import settings as s
from django.test import TestCase from django.test import TestCase
from networkx.utils.misc import graphs_equal from networkx.utils.misc import graphs_equal
from epdb.logic import PackageManager, SPathway from epdb.logic import PackageManager, SPathway
from epdb.models import Pathway, User from epdb.models import Pathway, User, Package
from utilities.ml import graph_from_pathway, multigen_eval, pathway_edit_eval from utilities.ml import multigen_eval, pathway_edit_eval, graph_from_pathway
Package = s.GET_PACKAGE_MODEL()
class MultiGenTest(TestCase): class MultiGenTest(TestCase):

View File

@ -1,10 +1,9 @@
from unittest.mock import MagicMock, PropertyMock, patch from unittest.mock import patch, MagicMock, PropertyMock
from django.conf import settings as s
from django.test import TestCase from django.test import TestCase
from epdb.logic import PackageManager from epdb.logic import PackageManager
from epdb.models import SimpleAmbitRule, User from epdb.models import User, SimpleAmbitRule
class SimpleAmbitRuleTest(TestCase): class SimpleAmbitRuleTest(TestCase):
@ -210,7 +209,7 @@ class SimpleAmbitRuleTest(TestCase):
self.assertEqual(rule.products_smarts, expected_products) self.assertEqual(rule.products_smarts, expected_products)
@patch(f"{s.PACKAGE_MODULE_PATH}.objects") @patch("epdb.models.Package.objects")
def test_related_reactions_property(self, mock_package_objects): def test_related_reactions_property(self, mock_package_objects):
"""Test related_reactions property returns correct queryset.""" """Test related_reactions property returns correct queryset."""
mock_qs = MagicMock() mock_qs = MagicMock()

View File

@ -1,11 +1,9 @@
from django.conf import settings as s
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from django.urls import reverse from django.urls import reverse
from django.conf import settings as s
from epdb.logic import UserManager from epdb.logic import UserManager
from epdb.models import User from epdb.models import Package, User
Package = s.GET_PACKAGE_MODEL()
@override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models", CELERY_TASK_ALWAYS_EAGER=True) @override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models", CELERY_TASK_ALWAYS_EAGER=True)

View File

@ -4,9 +4,7 @@ from django.test import TestCase, tag
from django.urls import reverse from django.urls import reverse
from epdb.logic import UserManager from epdb.logic import UserManager
from epdb.models import Group, GroupPackagePermission, Permission, UserPackagePermission from epdb.models import Package, UserPackagePermission, Permission, GroupPackagePermission, Group
Package = s.GET_PACKAGE_MODEL()
class PackageViewTest(TestCase): class PackageViewTest(TestCase):

View File

@ -1,11 +1,9 @@
from django.conf import settings as s
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from django.urls import reverse from django.urls import reverse
from django.conf import settings as s
from epdb.logic import PackageManager, UserManager from epdb.logic import UserManager, PackageManager
from epdb.models import Edge, Pathway from epdb.models import Pathway, Edge
Package = s.GET_PACKAGE_MODEL()
@override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models", CELERY_TASK_ALWAYS_EAGER=True) @override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models", CELERY_TASK_ALWAYS_EAGER=True)

View File

@ -1,12 +1,9 @@
from django.conf import settings as s
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from envipy_additional_information import Interval, Temperature from envipy_additional_information import Temperature, Interval
from epdb.logic import PackageManager, UserManager from epdb.logic import UserManager, PackageManager
from epdb.models import ExternalDatabase, Reaction, Scenario from epdb.models import Reaction, Scenario, ExternalDatabase
Package = s.GET_PACKAGE_MODEL()
class ReactionViewTest(TestCase): class ReactionViewTest(TestCase):

View File

@ -1,11 +1,8 @@
from django.conf import settings as s
from django.test import TestCase from django.test import TestCase
from django.urls import reverse
from epdb.logic import PackageManager from epdb.logic import PackageManager
from epdb.models import User from epdb.models import Package, User
from django.urls import reverse
Package = s.GET_PACKAGE_MODEL()
class UserViewTest(TestCase): class UserViewTest(TestCase):

View File

@ -2,12 +2,13 @@ import logging
import re import re
from abc import ABC from abc import ABC
from collections import defaultdict from collections import defaultdict
from typing import List, Optional, Dict, TYPE_CHECKING from typing import List, Optional, Dict, TYPE_CHECKING, Union
from indigo import Indigo, IndigoException, IndigoObject from indigo import Indigo, IndigoException, IndigoObject
from indigo.renderer import IndigoRenderer from indigo.renderer import IndigoRenderer
from rdkit import Chem, rdBase from rdkit import Chem, rdBase
from rdkit.Chem import MACCSkeys, Descriptors from rdkit.Chem import MACCSkeys, Descriptors
from rdkit.Chem import rdchem
from rdkit.Chem import rdChemReactions from rdkit.Chem import rdChemReactions
from rdkit.Chem.Draw import rdMolDraw2D from rdkit.Chem.Draw import rdMolDraw2D
from rdkit.Chem.MolStandardize import rdMolStandardize from rdkit.Chem.MolStandardize import rdMolStandardize
@ -90,8 +91,15 @@ class FormatConverter(object):
return Chem.MolToSmiles(mol, canonical=canonical) return Chem.MolToSmiles(mol, canonical=canonical)
@staticmethod @staticmethod
def InChIKey(smiles): def InChIKey(mol_or_smiles: Union[rdchem.Mol | str]):
return Chem.MolToInchiKey(FormatConverter.from_smiles(smiles)) if isinstance(mol_or_smiles, str):
mol_or_smiles = mol_or_smiles.replace("~", "")
mol_or_smiles = FormatConverter.from_smiles(mol_or_smiles)
if mol_or_smiles is None:
return None
return Chem.MolToInchiKey(mol_or_smiles)
@staticmethod @staticmethod
def InChI(smiles): def InChI(smiles):
@ -288,7 +296,8 @@ class FormatConverter(object):
product = GetMolFrags(product, asMols=True) product = GetMolFrags(product, asMols=True)
for p in product: for p in product:
p = FormatConverter.standardize( p = FormatConverter.standardize(
Chem.MolToSmiles(p), remove_stereo=remove_stereo Chem.MolToSmiles(p).replace("~", ""),
remove_stereo=remove_stereo,
) )
prods.append(p) prods.append(p)

View File

@ -1,12 +1,10 @@
# decorators.py # decorators.py
from functools import wraps from functools import wraps
from django.conf import settings as s
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from epdb.logic import PackageManager from epdb.logic import PackageManager
from epdb.models import Package
Package = s.GET_PACKAGE_MODEL()
# Map HTTP methods to required permissions # Map HTTP methods to required permissions
DEFAULT_METHOD_PERMISSIONS = { DEFAULT_METHOD_PERMISSIONS = {

View File

@ -11,7 +11,6 @@ from enum import Enum
from types import NoneType from types import NoneType
from typing import Any, Dict, List from typing import Any, Dict, List
from django.conf import settings as s
from django.db import transaction from django.db import transaction
from envipy_additional_information import NAME_MAPPING, EnviPyModel, Interval from envipy_additional_information import NAME_MAPPING, EnviPyModel, Interval
from pydantic import BaseModel, HttpUrl from pydantic import BaseModel, HttpUrl
@ -27,6 +26,7 @@ from epdb.models import (
License, License,
MLRelativeReasoning, MLRelativeReasoning,
Node, Node,
Package,
ParallelRule, ParallelRule,
Pathway, Pathway,
PluginModel, PluginModel,
@ -35,14 +35,13 @@ from epdb.models import (
RuleBasedRelativeReasoning, RuleBasedRelativeReasoning,
Scenario, Scenario,
SequentialRule, SequentialRule,
Setting,
SimpleAmbitRule, SimpleAmbitRule,
SimpleRDKitRule, SimpleRDKitRule,
SimpleRule, SimpleRule,
) )
from utilities.chem import FormatConverter from utilities.chem import FormatConverter
Package = s.GET_PACKAGE_MODEL()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -1260,3 +1259,46 @@ class PathwayUtils:
res[edge.url] = rule_chain res[edge.url] = rule_chain
return res return res
def _find_intermediates(self, data_pathway, pred_pathway):
pass
def engineer(self, setting: "Setting"):
from epdb.logic import SPathway
# get a fresh copy
pw = Pathway.objects.get(id=self.pathway.pk)
root_nodes = [n.default_node_label.smiles for n in pw.root_nodes]
if len(root_nodes) != 1:
logger.warning(f"Pathway {pw.name} has {len(root_nodes)} root nodes")
return
spw = SPathway(root_nodes[0], None, setting)
level = 0
while not spw.done:
spw.predict_step(from_depth=level)
level += 1
# Generate Node / SMILES mapping
node_mapping = {}
from utilities.chem import FormatConverter
for node in pw.nodes:
for snode in spw.smiles_to_node.values():
data_smiles = node.default_node_label.smiles
pred_smiles = snode.smiles
data_key = FormatConverter.InChIKey(data_smiles.replace("~", ""))
pred_key = FormatConverter.InChIKey(pred_smiles.replace("~", ""))
if data_key == pred_key:
node_mapping[snode] = node
print(node_mapping)
return spw
pass