diff --git a/envipath/settings.py b/envipath/settings.py index 5a18368b..58316549 100644 --- a/envipath/settings.py +++ b/envipath/settings.py @@ -52,6 +52,28 @@ INSTALLED_APPS = [ "migration", ] + +# Add the TENANT providing implementations for +# Required +# - Package +# - Compound +# - CompoundStructure +# Optional +# - PackageManager +# - GroupManager +# - SettingManaget +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 = [ "django.contrib.auth.backends.ModelBackend", ] diff --git a/epdb/admin.py b/epdb/admin.py index 88f851af..3027c4b6 100644 --- a/epdb/admin.py +++ b/epdb/admin.py @@ -1,11 +1,11 @@ from django.contrib import admin +from django.conf import settings as s from .models import ( User, UserPackagePermission, Group, GroupPackagePermission, - Package, MLRelativeReasoning, EnviFormer, Compound, @@ -24,6 +24,9 @@ from .models import ( ) +Package = s.GET_PACKAGE_MODEL() + + class UserAdmin(admin.ModelAdmin): list_display = ["username", "email", "is_active"] diff --git a/epdb/legacy_api.py b/epdb/legacy_api.py index b71e7148..a90e51e0 100644 --- a/epdb/legacy_api.py +++ b/epdb/legacy_api.py @@ -1,5 +1,6 @@ from typing import List, Dict, Optional, Any +from django.conf import settings as s from django.contrib.auth import get_user_model from django.http import HttpResponse from django.shortcuts import redirect @@ -10,7 +11,6 @@ from .logic import PackageManager, UserManager, SettingManager from .models import ( Compound, CompoundStructure, - Package, User, UserPackagePermission, Rule, @@ -23,6 +23,9 @@ from .models import ( ) +Package = s.GET_PACKAGE_MODEL() + + def _anonymous_or_real(request): if request.user.is_authenticated and not request.user.is_anonymous: return request.user diff --git a/epdb/logic.py b/epdb/logic.py index 19f03ae2..69d64649 100644 --- a/epdb/logic.py +++ b/epdb/logic.py @@ -11,7 +11,6 @@ from pydantic import ValidationError from epdb.models import ( User, - Package, UserPackagePermission, GroupPackagePermission, Permission, @@ -33,6 +32,8 @@ from utilities.misc import PackageImporter, PackageExporter logger = logging.getLogger(__name__) +Package = s.GET_PACKAGE_MODEL() + 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}" diff --git a/epdb/management/commands/create_ml_models.py b/epdb/management/commands/create_ml_models.py index 89fbc0ec..4f86c957 100644 --- a/epdb/management/commands/create_ml_models.py +++ b/epdb/management/commands/create_ml_models.py @@ -2,7 +2,9 @@ from django.conf import settings as s from django.core.management.base import BaseCommand from django.db import transaction -from epdb.models import MLRelativeReasoning, EnviFormer, Package +from epdb.models import EnviFormer, MLRelativeReasoning + +Package = s.GET_PACKAGE_MODEL() class Command(BaseCommand): @@ -75,11 +77,13 @@ class Command(BaseCommand): return packages # Iteratively create models in options["model_names"] - print(f"Creating models: {options['model_names']}\n" - f"Data packages: {options['data_packages']}\n" - f"Rule Packages (only for MLRR): {options['rule_packages']}\n" - f"Eval Packages: {options['eval_packages']}\n" - f"Threshold: {options['threshold']:.2f}") + print( + f"Creating models: {options['model_names']}\n" + f"Data packages: {options['data_packages']}\n" + f"Rule Packages (only for MLRR): {options['rule_packages']}\n" + f"Eval Packages: {options['eval_packages']}\n" + f"Threshold: {options['threshold']:.2f}" + ) data_packages = decode_packages(options["data_packages"]) eval_packages = decode_packages(options["eval_packages"]) rule_packages = decode_packages(options["rule_packages"]) @@ -90,10 +94,10 @@ class Command(BaseCommand): pack, data_packages=data_packages, eval_packages=eval_packages, - threshold=options['threshold'], + threshold=options["threshold"], name=f"EnviFormer - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}", 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": model = MLRelativeReasoning.create( @@ -101,10 +105,10 @@ class Command(BaseCommand): rule_packages=rule_packages, data_packages=data_packages, eval_packages=eval_packages, - threshold=options['threshold'], + threshold=options["threshold"], name=f"ECC - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}", 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: raise ValueError(f"Cannot create model of type {model_name}, unknown model type") diff --git a/epdb/management/commands/load_enviformer.py b/epdb/management/commands/load_enviformer.py index b2f9c3e3..74d97449 100644 --- a/epdb/management/commands/load_enviformer.py +++ b/epdb/management/commands/load_enviformer.py @@ -8,7 +8,9 @@ from django.conf import settings as s from django.core.management.base import BaseCommand from django.db import transaction -from epdb.models import EnviFormer, Package +from epdb.models import EnviFormer + +Package = s.GET_PACKAGE_MODEL() class Command(BaseCommand): diff --git a/epdb/management/commands/localize_urls.py b/epdb/management/commands/localize_urls.py index cc0a3726..9d876fd4 100644 --- a/epdb/management/commands/localize_urls.py +++ b/epdb/management/commands/localize_urls.py @@ -1,8 +1,8 @@ from django.apps import apps +from django.conf import settings as s from django.core.management.base import BaseCommand - -from django.db.models import F, Value, TextField, JSONField -from django.db.models.functions import Replace, Cast +from django.db.models import F, JSONField, TextField, Value +from django.db.models.functions import Cast, Replace from epdb.models import EnviPathModel @@ -23,10 +23,13 @@ class Command(BaseCommand): ) 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 = [ "User", "Group", - "Package", "Compound", "CompoundStructure", "Pathway", diff --git a/epdb/migrations/0010_alter_userpackagepermission_package_and_more.py b/epdb/migrations/0010_alter_userpackagepermission_package_and_more.py new file mode 100644 index 00000000..9c9e10ce --- /dev/null +++ b/epdb/migrations/0010_alter_userpackagepermission_package_and_more.py @@ -0,0 +1,190 @@ +# 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", + ), + ] diff --git a/epdb/models.py b/epdb/models.py index 324fe301..d0fda114 100644 --- a/epdb/models.py +++ b/epdb/models.py @@ -7,7 +7,7 @@ import secrets from abc import abstractmethod from collections import defaultdict from datetime import datetime -from typing import Union, List, Optional, Dict, Tuple, Set, Any +from typing import Union, List, Optional, Dict, Tuple, Set, Any, TYPE_CHECKING from uuid import uuid4 import math import joblib @@ -32,6 +32,8 @@ from utilities.ml import Dataset, ApplicabilityDomainPCA, EnsembleClassifierChai logger = logging.getLogger(__name__) +if TYPE_CHECKING: + Package = s.GET_PACKAGE_MODEL() ########################## # User/Groups/Permission # @@ -45,7 +47,10 @@ class User(AbstractUser): ) url = models.TextField(blank=False, null=True, verbose_name="URL", unique=True) default_package = models.ForeignKey( - "epdb.Package", verbose_name="Default Package", null=True, on_delete=models.SET_NULL + s.PACKAGE_IMPLEMENTATION, + verbose_name="Default Package", + null=True, + on_delete=models.SET_NULL, ) default_group = models.ForeignKey( "Group", @@ -235,7 +240,7 @@ class UserPackagePermission(Permission): ) user = models.ForeignKey("User", verbose_name="Permission to", on_delete=models.CASCADE) package = models.ForeignKey( - "epdb.Package", verbose_name="Permission on", on_delete=models.CASCADE + s.PACKAGE_IMPLEMENTATION, verbose_name="Permission on", on_delete=models.CASCADE ) class Meta: @@ -251,7 +256,7 @@ class GroupPackagePermission(Permission): ) group = models.ForeignKey("Group", verbose_name="Permission to", on_delete=models.CASCADE) package = models.ForeignKey( - "epdb.Package", verbose_name="Permission on", on_delete=models.CASCADE + s.PACKAGE_IMPLEMENTATION, verbose_name="Permission on", on_delete=models.CASCADE ) class Meta: @@ -651,7 +656,7 @@ class License(models.Model): image_link = models.URLField(blank=False, null=False, verbose_name="Image link") -class Package(EnviPathModel): +class AbstractPackage(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" @@ -719,10 +724,13 @@ class Package(EnviPathModel): rules = sorted(rules, key=lambda x: x.url) return rules + class Meta: + abstract = True + class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin): package = models.ForeignKey( - "epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True + s.PACKAGE_IMPLEMENTATION, verbose_name="Package", on_delete=models.CASCADE, db_index=True ) default_structure = models.ForeignKey( "CompoundStructure", @@ -772,7 +780,7 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin @staticmethod @transaction.atomic 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": if smiles is None or smiles.strip() == "": raise ValueError("SMILES is required") @@ -1050,7 +1058,7 @@ class EnzymeLink(EnviPathModel, KEGGIdentifierMixin): class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin): package = models.ForeignKey( - "epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True + s.PACKAGE_IMPLEMENTATION, verbose_name="Package", on_delete=models.CASCADE, db_index=True ) # # https://github.com/django-polymorphic/django-polymorphic/issues/229 @@ -1156,7 +1164,7 @@ class SimpleAmbitRule(SimpleRule): @staticmethod @transaction.atomic def create( - package: Package, + package: "Package", name: str = None, description: str = None, smirks: str = None, @@ -1222,6 +1230,7 @@ class SimpleAmbitRule(SimpleRule): @property def related_reactions(self): + Package = s.GET_PACKAGE_MODEL() qs = Package.objects.filter(reviewed=True) return self.reaction_rule.filter(package__in=qs).order_by("name") @@ -1314,7 +1323,7 @@ class SequentialRuleOrdering(models.Model): class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin): package = models.ForeignKey( - "epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True + s.PACKAGE_IMPLEMENTATION, verbose_name="Package", on_delete=models.CASCADE, db_index=True ) educts = models.ManyToManyField( "epdb.CompoundStructure", verbose_name="Educts", related_name="reaction_educts" @@ -1336,7 +1345,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin @staticmethod @transaction.atomic def create( - package: Package, + package: "Package", name: str = None, description: str = None, educts: Union[List[str], List[CompoundStructure]] = None, @@ -1496,7 +1505,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin class Pathway(EnviPathModel, AliasMixin, ScenarioMixin): package = models.ForeignKey( - "epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True + s.PACKAGE_IMPLEMENTATION, verbose_name="Package", on_delete=models.CASCADE, db_index=True ) setting = models.ForeignKey( "epdb.Setting", verbose_name="Setting", on_delete=models.CASCADE, null=True, blank=True @@ -2052,7 +2061,7 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin): class EPModel(PolymorphicModel, EnviPathModel): package = models.ForeignKey( - "epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True + s.PACKAGE_IMPLEMENTATION, verbose_name="Package", on_delete=models.CASCADE, db_index=True ) def _url(self): @@ -2061,17 +2070,17 @@ class EPModel(PolymorphicModel, EnviPathModel): class PackageBasedModel(EPModel): rule_packages = models.ManyToManyField( - "Package", + s.PACKAGE_IMPLEMENTATION, verbose_name="Rule Packages", related_name="%(app_label)s_%(class)s_rule_packages", ) data_packages = models.ManyToManyField( - "Package", + s.PACKAGE_IMPLEMENTATION, verbose_name="Data Packages", related_name="%(app_label)s_%(class)s_data_packages", ) eval_packages = models.ManyToManyField( - "Package", + s.PACKAGE_IMPLEMENTATION, verbose_name="Evaluation Packages", related_name="%(app_label)s_%(class)s_eval_packages", ) @@ -3439,7 +3448,7 @@ class PluginModel(EPModel): class Scenario(EnviPathModel): package = models.ForeignKey( - "epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True + s.PACKAGE_IMPLEMENTATION, 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_type = models.CharField( @@ -3590,7 +3599,7 @@ class Setting(EnviPathModel): ) rule_packages = models.ManyToManyField( - "Package", + s.PACKAGE_IMPLEMENTATION, verbose_name="Setting Rule Packages", related_name="setting_rule_packages", blank=True, diff --git a/epdb/tasks.py b/epdb/tasks.py index b872d4a9..73c12f36 100644 --- a/epdb/tasks.py +++ b/epdb/tasks.py @@ -7,9 +7,12 @@ from uuid import uuid4 from celery import shared_task from celery.utils.functional import LRUCache +from django.conf import settings as s from epdb.logic import SPathway -from epdb.models import EPModel, JobLog, Node, Package, Pathway, Rule, Setting, User, Edge +from epdb.models import Edge, EPModel, JobLog, Node, Pathway, Rule, Setting, User + +Package = s.GET_PACKAGE_MODEL() logger = logging.getLogger(__name__) ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times. diff --git a/epdb/views.py b/epdb/views.py index 6778a221..f7494365 100644 --- a/epdb/views.py +++ b/epdb/views.py @@ -1,11 +1,11 @@ import json import logging -from typing import List, Dict, Any +from typing import Any, Dict, List from django.conf import settings as s from django.contrib.auth import get_user_model -from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest -from django.shortcuts import render, redirect +from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed, JsonResponse +from django.shortcuts import redirect, render from django.urls import reverse from django.views.decorators.csrf import csrf_exempt from envipy_additional_information import NAME_MAPPING @@ -14,42 +14,43 @@ from oauth2_provider.decorators import protected_resource from utilities.chem import FormatConverter, IndigoUtils from utilities.decorators import package_permission_required from utilities.misc import HTMLGenerator + from .logic import ( + EPDBURLParser, GroupManager, PackageManager, - UserManager, - SettingManager, SearchManager, - EPDBURLParser, + SettingManager, + UserManager, ) from .models import ( - Package, - GroupPackagePermission, - Group, - CompoundStructure, + APIToken, Compound, + CompoundStructure, + Edge, + EnviFormer, + EnzymeLink, + EPModel, + ExternalDatabase, + ExternalIdentifier, + Group, + GroupPackagePermission, + JobLog, + License, + MLRelativeReasoning, + Node, + Pathway, + Permission, Reaction, Rule, - Pathway, - Node, - EPModel, - EnviFormer, - MLRelativeReasoning, RuleBasedRelativeReasoning, Scenario, SimpleAmbitRule, - APIToken, - UserPackagePermission, - Permission, - License, User, - Edge, - ExternalDatabase, - ExternalIdentifier, - EnzymeLink, - JobLog, + UserPackagePermission, ) +Package = s.GET_PACKAGE_MODEL() logger = logging.getLogger(__name__) @@ -82,8 +83,7 @@ def login(request): return render(request, "static/login.html", context) elif request.method == "POST": - from django.contrib.auth import authenticate - from django.contrib.auth import login + from django.contrib.auth import authenticate, login username = request.POST.get("username") 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."' ) - from .tasks import dispatch, build_model + from .tasks import build_model, dispatch dispatch(current_user, build_model, mod.pk) @@ -2325,9 +2325,9 @@ def package_scenarios(request, package_uuid): context["unreviewed_objects"] = unreviewed_scenario_qs from envipy_additional_information import ( + SEDIMENT_ADDITIONAL_INFORMATION, SLUDGE_ADDITIONAL_INFORMATION, SOIL_ADDITIONAL_INFORMATION, - SEDIMENT_ADDITIONAL_INFORMATION, ) context["scenario_types"] = { diff --git a/fixtures/test_fixtures.jsonl.gz b/fixtures/test_fixtures.jsonl.gz index 4db92bf1..90e96c25 100644 Binary files a/fixtures/test_fixtures.jsonl.gz and b/fixtures/test_fixtures.jsonl.gz differ diff --git a/fixtures/test_fixtures_incl_model.jsonl.gz b/fixtures/test_fixtures_incl_model.jsonl.gz index c09475ef..33042878 100644 Binary files a/fixtures/test_fixtures_incl_model.jsonl.gz and b/fixtures/test_fixtures_incl_model.jsonl.gz differ diff --git a/migration/views.py b/migration/views.py index c819c7b5..38ed472e 100644 --- a/migration/views.py +++ b/migration/views.py @@ -1,22 +1,19 @@ -import gzip import json import logging import os.path -from datetime import datetime from django.conf import settings as s from django.http import HttpResponseNotAllowed from django.shortcuts import render - -from epdb.logic import PackageManager -from epdb.models import Rule, SimpleAmbitRule, Package, CompoundStructure -from epdb.views import get_base_context, _anonymous_or_real -from utilities.chem import FormatConverter - - from rdkit import Chem from rdkit.Chem.MolStandardize import rdMolStandardize +from epdb.models import CompoundStructure, Rule, SimpleAmbitRule +from epdb.views import get_base_context +from utilities.chem import FormatConverter + +Package = s.GET_PACKAGE_MODEL() + logger = logging.getLogger(__name__) @@ -59,9 +56,7 @@ def run_both_engines(SMILES, SMIRKS): set( [ 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] ] ) ) @@ -85,8 +80,7 @@ def migration(request): url="http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1" ) 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) @@ -142,9 +136,7 @@ def migration(request): ) 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) @@ -152,8 +144,6 @@ def migration(request): def migration_detail(request, package_uuid, rule_uuid): - current_user = _anonymous_or_real(request) - if request.method == "GET": context = get_base_context(request) @@ -235,9 +225,7 @@ def compare(request): 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" ) - 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) elif request.method == "POST": diff --git a/public/__init__.py b/public/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/public/admin.py b/public/admin.py new file mode 100644 index 00000000..846f6b40 --- /dev/null +++ b/public/admin.py @@ -0,0 +1 @@ +# Register your models here. diff --git a/public/apps.py b/public/apps.py new file mode 100644 index 00000000..ce864bbe --- /dev/null +++ b/public/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PublicConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "public" diff --git a/public/migrations/0001_initial.py b/public/migrations/0001_initial.py new file mode 100644 index 00000000..eca5840f --- /dev/null +++ b/public/migrations/0001_initial.py @@ -0,0 +1,56 @@ +# 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, + }, + ), + ] diff --git a/public/migrations/0002_alter_package_options.py b/public/migrations/0002_alter_package_options.py new file mode 100644 index 00000000..c8c3e940 --- /dev/null +++ b/public/migrations/0002_alter_package_options.py @@ -0,0 +1,16 @@ +# 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={}, + ), + ] diff --git a/public/migrations/0003_package_license.py b/public/migrations/0003_package_license.py new file mode 100644 index 00000000..61197652 --- /dev/null +++ b/public/migrations/0003_package_license.py @@ -0,0 +1,25 @@ +# 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", + ), + ), + ] diff --git a/public/migrations/__init__.py b/public/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/public/models.py b/public/models.py new file mode 100644 index 00000000..6c5f7ce3 --- /dev/null +++ b/public/models.py @@ -0,0 +1,6 @@ +from epdb.models import AbstractPackage + + +class Package(AbstractPackage): + class Meta: + db_table = "epdb_package" diff --git a/public/tests.py b/public/tests.py new file mode 100644 index 00000000..a39b155a --- /dev/null +++ b/public/tests.py @@ -0,0 +1 @@ +# Create your tests here. diff --git a/public/views.py b/public/views.py new file mode 100644 index 00000000..60f00ef0 --- /dev/null +++ b/public/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/tests/test_enviformer.py b/tests/test_enviformer.py index 647433fc..39760dfb 100644 --- a/tests/test_enviformer.py +++ b/tests/test_enviformer.py @@ -1,10 +1,15 @@ from collections import defaultdict from datetime import datetime from tempfile import TemporaryDirectory + +from django.conf import settings as s from django.test import TestCase, tag + from epdb.logic import PackageManager -from epdb.models import User, EnviFormer, Package, Setting -from epdb.tasks import predict_simple, predict +from epdb.models import EnviFormer, Setting, User +from epdb.tasks import predict, predict_simple + +Package = s.GET_PACKAGE_MODEL() def measure_predict(mod, pathway_pk=None): diff --git a/tests/test_model.py b/tests/test_model.py index f0355be9..c5b9e493 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1,10 +1,13 @@ from tempfile import TemporaryDirectory import numpy as np +from django.conf import settings as s from django.test import TestCase from epdb.logic import PackageManager -from epdb.models import User, MLRelativeReasoning, Package +from epdb.models import MLRelativeReasoning, User + +Package = s.GET_PACKAGE_MODEL() class ModelTest(TestCase): diff --git a/tests/test_multigen_eval.py b/tests/test_multigen_eval.py index 96cf4c14..d8d14ef8 100644 --- a/tests/test_multigen_eval.py +++ b/tests/test_multigen_eval.py @@ -1,8 +1,12 @@ +from django.conf import settings as s from django.test import TestCase from networkx.utils.misc import graphs_equal + from epdb.logic import PackageManager, SPathway -from epdb.models import Pathway, User, Package -from utilities.ml import multigen_eval, pathway_edit_eval, graph_from_pathway +from epdb.models import Pathway, User +from utilities.ml import graph_from_pathway, multigen_eval, pathway_edit_eval + +Package = s.GET_PACKAGE_MODEL() class MultiGenTest(TestCase): diff --git a/tests/test_simpleambitrule.py b/tests/test_simpleambitrule.py index b2e7fdd7..1d21acfe 100644 --- a/tests/test_simpleambitrule.py +++ b/tests/test_simpleambitrule.py @@ -1,9 +1,10 @@ -from unittest.mock import patch, MagicMock, PropertyMock +from unittest.mock import MagicMock, PropertyMock, patch +from django.conf import settings as s from django.test import TestCase from epdb.logic import PackageManager -from epdb.models import User, SimpleAmbitRule +from epdb.models import SimpleAmbitRule, User class SimpleAmbitRuleTest(TestCase): @@ -209,7 +210,7 @@ class SimpleAmbitRuleTest(TestCase): self.assertEqual(rule.products_smarts, expected_products) - @patch("epdb.models.Package.objects") + @patch(f"{s.PACKAGE_MODULE_PATH}.objects") def test_related_reactions_property(self, mock_package_objects): """Test related_reactions property returns correct queryset.""" mock_qs = MagicMock() diff --git a/tests/views/test_model_views.py b/tests/views/test_model_views.py index 10cbefe2..75806d56 100644 --- a/tests/views/test_model_views.py +++ b/tests/views/test_model_views.py @@ -1,9 +1,11 @@ +from django.conf import settings as s from django.test import TestCase, override_settings from django.urls import reverse -from django.conf import settings as s from epdb.logic import UserManager -from epdb.models import Package, User +from epdb.models import User + +Package = s.GET_PACKAGE_MODEL() @override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models", CELERY_TASK_ALWAYS_EAGER=True) diff --git a/tests/views/test_package_views.py b/tests/views/test_package_views.py index f185f721..93528c64 100644 --- a/tests/views/test_package_views.py +++ b/tests/views/test_package_views.py @@ -4,7 +4,9 @@ from django.test import TestCase, tag from django.urls import reverse from epdb.logic import UserManager -from epdb.models import Package, UserPackagePermission, Permission, GroupPackagePermission, Group +from epdb.models import Group, GroupPackagePermission, Permission, UserPackagePermission + +Package = s.GET_PACKAGE_MODEL() class PackageViewTest(TestCase): diff --git a/tests/views/test_pathway_views.py b/tests/views/test_pathway_views.py index b5fe99fd..0072fce6 100644 --- a/tests/views/test_pathway_views.py +++ b/tests/views/test_pathway_views.py @@ -1,9 +1,11 @@ +from django.conf import settings as s from django.test import TestCase, override_settings from django.urls import reverse -from django.conf import settings as s -from epdb.logic import UserManager, PackageManager -from epdb.models import Pathway, Edge +from epdb.logic import PackageManager, UserManager +from epdb.models import Edge, Pathway + +Package = s.GET_PACKAGE_MODEL() @override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models", CELERY_TASK_ALWAYS_EAGER=True) diff --git a/tests/views/test_reaction_views.py b/tests/views/test_reaction_views.py index 2c40ac0b..2d8ecb85 100644 --- a/tests/views/test_reaction_views.py +++ b/tests/views/test_reaction_views.py @@ -1,9 +1,12 @@ +from django.conf import settings as s from django.test import TestCase from django.urls import reverse -from envipy_additional_information import Temperature, Interval +from envipy_additional_information import Interval, Temperature -from epdb.logic import UserManager, PackageManager -from epdb.models import Reaction, Scenario, ExternalDatabase +from epdb.logic import PackageManager, UserManager +from epdb.models import ExternalDatabase, Reaction, Scenario + +Package = s.GET_PACKAGE_MODEL() class ReactionViewTest(TestCase): diff --git a/tests/views/test_user_views.py b/tests/views/test_user_views.py index 1760aaa8..2925f113 100644 --- a/tests/views/test_user_views.py +++ b/tests/views/test_user_views.py @@ -1,8 +1,11 @@ +from django.conf import settings as s from django.test import TestCase +from django.urls import reverse from epdb.logic import PackageManager -from epdb.models import Package, User -from django.urls import reverse +from epdb.models import User + +Package = s.GET_PACKAGE_MODEL() class UserViewTest(TestCase): diff --git a/utilities/decorators.py b/utilities/decorators.py index eabbde16..d4ccdbb2 100644 --- a/utilities/decorators.py +++ b/utilities/decorators.py @@ -1,10 +1,12 @@ # decorators.py from functools import wraps +from django.conf import settings as s from django.shortcuts import get_object_or_404 from epdb.logic import PackageManager -from epdb.models import Package + +Package = s.GET_PACKAGE_MODEL() # Map HTTP methods to required permissions DEFAULT_METHOD_PERMISSIONS = { diff --git a/utilities/misc.py b/utilities/misc.py index 0b7222f7..3e680ce4 100644 --- a/utilities/misc.py +++ b/utilities/misc.py @@ -11,6 +11,7 @@ from enum import Enum from types import NoneType from typing import Any, Dict, List +from django.conf import settings as s from django.db import transaction from envipy_additional_information import NAME_MAPPING, EnviPyModel, Interval from pydantic import BaseModel, HttpUrl @@ -26,7 +27,6 @@ from epdb.models import ( License, MLRelativeReasoning, Node, - Package, ParallelRule, Pathway, PluginModel, @@ -41,6 +41,8 @@ from epdb.models import ( ) from utilities.chem import FormatConverter +Package = s.GET_PACKAGE_MODEL() + logger = logging.getLogger(__name__)