forked from enviPath/enviPy
Compare commits
2 Commits
feature/te
...
feature/en
| Author | SHA1 | Date | |
|---|---|---|---|
| 04f9c9252a | |||
| c2d45917ce |
@ -52,28 +52,6 @@ INSTALLED_APPS = [
|
||||
"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 = [
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
]
|
||||
|
||||
@ -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,9 +24,6 @@ from .models import (
|
||||
)
|
||||
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
|
||||
|
||||
class UserAdmin(admin.ModelAdmin):
|
||||
list_display = ["username", "email", "is_active"]
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
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
|
||||
@ -11,6 +10,7 @@ from .logic import PackageManager, UserManager, SettingManager
|
||||
from .models import (
|
||||
Compound,
|
||||
CompoundStructure,
|
||||
Package,
|
||||
User,
|
||||
UserPackagePermission,
|
||||
Rule,
|
||||
@ -23,9 +23,6 @@ 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
|
||||
|
||||
@ -11,6 +11,7 @@ from pydantic import ValidationError
|
||||
|
||||
from epdb.models import (
|
||||
User,
|
||||
Package,
|
||||
UserPackagePermission,
|
||||
GroupPackagePermission,
|
||||
Permission,
|
||||
@ -32,8 +33,6 @@ 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}"
|
||||
|
||||
@ -2,9 +2,7 @@ from django.conf import settings as s
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from epdb.models import EnviFormer, MLRelativeReasoning
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
from epdb.models import MLRelativeReasoning, EnviFormer, Package
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@ -77,13 +75,11 @@ class Command(BaseCommand):
|
||||
return packages
|
||||
|
||||
# Iteratively create models in options["model_names"]
|
||||
print(
|
||||
f"Creating models: {options['model_names']}\n"
|
||||
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}"
|
||||
)
|
||||
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"])
|
||||
@ -94,7 +90,7 @@ 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']}.",
|
||||
@ -105,7 +101,7 @@ 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']}.",
|
||||
|
||||
@ -8,9 +8,7 @@ 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 = s.GET_PACKAGE_MODEL()
|
||||
from epdb.models import EnviFormer, Package
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
@ -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, 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
|
||||
|
||||
@ -23,13 +23,10 @@ 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",
|
||||
|
||||
@ -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",
|
||||
),
|
||||
]
|
||||
@ -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, TYPE_CHECKING
|
||||
from typing import Union, List, Optional, Dict, Tuple, Set, Any
|
||||
from uuid import uuid4
|
||||
import math
|
||||
import joblib
|
||||
@ -32,8 +32,6 @@ from utilities.ml import Dataset, ApplicabilityDomainPCA, EnsembleClassifierChai
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
|
||||
##########################
|
||||
# User/Groups/Permission #
|
||||
@ -47,10 +45,7 @@ class User(AbstractUser):
|
||||
)
|
||||
url = models.TextField(blank=False, null=True, verbose_name="URL", unique=True)
|
||||
default_package = models.ForeignKey(
|
||||
s.PACKAGE_IMPLEMENTATION,
|
||||
verbose_name="Default Package",
|
||||
null=True,
|
||||
on_delete=models.SET_NULL,
|
||||
"epdb.Package", verbose_name="Default Package", null=True, on_delete=models.SET_NULL
|
||||
)
|
||||
default_group = models.ForeignKey(
|
||||
"Group",
|
||||
@ -240,7 +235,7 @@ class UserPackagePermission(Permission):
|
||||
)
|
||||
user = models.ForeignKey("User", verbose_name="Permission to", on_delete=models.CASCADE)
|
||||
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:
|
||||
@ -256,7 +251,7 @@ class GroupPackagePermission(Permission):
|
||||
)
|
||||
group = models.ForeignKey("Group", verbose_name="Permission to", on_delete=models.CASCADE)
|
||||
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:
|
||||
@ -656,7 +651,7 @@ class License(models.Model):
|
||||
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)
|
||||
license = models.ForeignKey(
|
||||
"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)
|
||||
return rules
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin):
|
||||
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(
|
||||
"CompoundStructure",
|
||||
@ -780,7 +772,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")
|
||||
@ -1058,7 +1050,7 @@ class EnzymeLink(EnviPathModel, KEGGIdentifierMixin):
|
||||
|
||||
class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
|
||||
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
|
||||
@ -1164,7 +1156,7 @@ class SimpleAmbitRule(SimpleRule):
|
||||
@staticmethod
|
||||
@transaction.atomic
|
||||
def create(
|
||||
package: "Package",
|
||||
package: Package,
|
||||
name: str = None,
|
||||
description: str = None,
|
||||
smirks: str = None,
|
||||
@ -1230,7 +1222,6 @@ 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")
|
||||
|
||||
@ -1323,7 +1314,7 @@ class SequentialRuleOrdering(models.Model):
|
||||
|
||||
class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin):
|
||||
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(
|
||||
"epdb.CompoundStructure", verbose_name="Educts", related_name="reaction_educts"
|
||||
@ -1345,7 +1336,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,
|
||||
@ -1505,7 +1496,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
|
||||
|
||||
class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
|
||||
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(
|
||||
"epdb.Setting", verbose_name="Setting", on_delete=models.CASCADE, null=True, blank=True
|
||||
@ -1582,11 +1573,9 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
|
||||
while len(queue):
|
||||
current = queue.pop()
|
||||
processed.add(current)
|
||||
|
||||
nodes.append(current.d3_json())
|
||||
|
||||
for e in self.edges:
|
||||
if current in e.start_nodes.all():
|
||||
for e in self.edges.filter(start_nodes=current).distinct():
|
||||
for prod in e.end_nodes.all():
|
||||
if prod not in queue and prod not in processed:
|
||||
queue.append(prod)
|
||||
@ -2061,7 +2050,7 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin):
|
||||
|
||||
class EPModel(PolymorphicModel, EnviPathModel):
|
||||
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):
|
||||
@ -2070,17 +2059,17 @@ class EPModel(PolymorphicModel, EnviPathModel):
|
||||
|
||||
class PackageBasedModel(EPModel):
|
||||
rule_packages = models.ManyToManyField(
|
||||
s.PACKAGE_IMPLEMENTATION,
|
||||
"Package",
|
||||
verbose_name="Rule Packages",
|
||||
related_name="%(app_label)s_%(class)s_rule_packages",
|
||||
)
|
||||
data_packages = models.ManyToManyField(
|
||||
s.PACKAGE_IMPLEMENTATION,
|
||||
"Package",
|
||||
verbose_name="Data Packages",
|
||||
related_name="%(app_label)s_%(class)s_data_packages",
|
||||
)
|
||||
eval_packages = models.ManyToManyField(
|
||||
s.PACKAGE_IMPLEMENTATION,
|
||||
"Package",
|
||||
verbose_name="Evaluation Packages",
|
||||
related_name="%(app_label)s_%(class)s_eval_packages",
|
||||
)
|
||||
@ -3448,7 +3437,7 @@ class PluginModel(EPModel):
|
||||
|
||||
class Scenario(EnviPathModel):
|
||||
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_type = models.CharField(
|
||||
@ -3599,7 +3588,7 @@ class Setting(EnviPathModel):
|
||||
)
|
||||
|
||||
rule_packages = models.ManyToManyField(
|
||||
s.PACKAGE_IMPLEMENTATION,
|
||||
"Package",
|
||||
verbose_name="Setting Rule Packages",
|
||||
related_name="setting_rule_packages",
|
||||
blank=True,
|
||||
|
||||
@ -7,12 +7,9 @@ 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 Edge, EPModel, JobLog, Node, Pathway, Rule, Setting, User
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
from epdb.models import EPModel, JobLog, Node, Package, Pathway, Rule, Setting, User, Edge
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
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:
|
||||
pw.kv.update({"status": "failed"})
|
||||
pw.kv.update(**{"error": str(e)})
|
||||
pw.save()
|
||||
|
||||
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, Dict, List
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from django.conf import settings as s
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed, JsonResponse
|
||||
from django.shortcuts import redirect, render
|
||||
from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest
|
||||
from django.shortcuts import render, redirect
|
||||
from django.urls import reverse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
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.decorators import package_permission_required
|
||||
from utilities.misc import HTMLGenerator
|
||||
|
||||
from .logic import (
|
||||
EPDBURLParser,
|
||||
GroupManager,
|
||||
PackageManager,
|
||||
SearchManager,
|
||||
SettingManager,
|
||||
UserManager,
|
||||
SettingManager,
|
||||
SearchManager,
|
||||
EPDBURLParser,
|
||||
)
|
||||
from .models import (
|
||||
APIToken,
|
||||
Compound,
|
||||
CompoundStructure,
|
||||
Edge,
|
||||
EnviFormer,
|
||||
EnzymeLink,
|
||||
EPModel,
|
||||
ExternalDatabase,
|
||||
ExternalIdentifier,
|
||||
Group,
|
||||
Package,
|
||||
GroupPackagePermission,
|
||||
JobLog,
|
||||
License,
|
||||
MLRelativeReasoning,
|
||||
Node,
|
||||
Pathway,
|
||||
Permission,
|
||||
Group,
|
||||
CompoundStructure,
|
||||
Compound,
|
||||
Reaction,
|
||||
Rule,
|
||||
Pathway,
|
||||
Node,
|
||||
EPModel,
|
||||
EnviFormer,
|
||||
MLRelativeReasoning,
|
||||
RuleBasedRelativeReasoning,
|
||||
Scenario,
|
||||
SimpleAmbitRule,
|
||||
User,
|
||||
APIToken,
|
||||
UserPackagePermission,
|
||||
Permission,
|
||||
License,
|
||||
User,
|
||||
Edge,
|
||||
ExternalDatabase,
|
||||
ExternalIdentifier,
|
||||
EnzymeLink,
|
||||
JobLog,
|
||||
)
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -83,7 +82,8 @@ def login(request):
|
||||
return render(request, "static/login.html", context)
|
||||
|
||||
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")
|
||||
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 build_model, dispatch
|
||||
from .tasks import dispatch, build_model
|
||||
|
||||
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"] = {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -1,18 +1,21 @@
|
||||
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 rdkit import Chem
|
||||
from rdkit.Chem.MolStandardize import rdMolStandardize
|
||||
|
||||
from epdb.models import CompoundStructure, Rule, SimpleAmbitRule
|
||||
from epdb.views import get_base_context
|
||||
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
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
|
||||
from rdkit import Chem
|
||||
from rdkit.Chem.MolStandardize import rdMolStandardize
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -56,7 +59,9 @@ 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]
|
||||
]
|
||||
)
|
||||
)
|
||||
@ -80,7 +85,8 @@ 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)
|
||||
|
||||
@ -136,7 +142,9 @@ 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)
|
||||
|
||||
@ -144,6 +152,8 @@ 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)
|
||||
|
||||
@ -225,7 +235,9 @@ 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":
|
||||
|
||||
@ -1 +0,0 @@
|
||||
# Register your models here.
|
||||
@ -1,6 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PublicConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "public"
|
||||
@ -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,
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -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={},
|
||||
),
|
||||
]
|
||||
@ -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",
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -1,6 +0,0 @@
|
||||
from epdb.models import AbstractPackage
|
||||
|
||||
|
||||
class Package(AbstractPackage):
|
||||
class Meta:
|
||||
db_table = "epdb_package"
|
||||
@ -1 +0,0 @@
|
||||
# Create your tests here.
|
||||
@ -1 +0,0 @@
|
||||
# Create your views here.
|
||||
@ -1,15 +1,10 @@
|
||||
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 EnviFormer, Setting, User
|
||||
from epdb.tasks import predict, predict_simple
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
from epdb.models import User, EnviFormer, Package, Setting
|
||||
from epdb.tasks import predict_simple, predict
|
||||
|
||||
|
||||
def measure_predict(mod, pathway_pk=None):
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
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 MLRelativeReasoning, User
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
from epdb.models import User, MLRelativeReasoning, Package
|
||||
|
||||
|
||||
class ModelTest(TestCase):
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
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
|
||||
from utilities.ml import graph_from_pathway, multigen_eval, pathway_edit_eval
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
from epdb.models import Pathway, User, Package
|
||||
from utilities.ml import multigen_eval, pathway_edit_eval, graph_from_pathway
|
||||
|
||||
|
||||
class MultiGenTest(TestCase):
|
||||
|
||||
@ -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 epdb.logic import PackageManager
|
||||
from epdb.models import SimpleAmbitRule, User
|
||||
from epdb.models import User, SimpleAmbitRule
|
||||
|
||||
|
||||
class SimpleAmbitRuleTest(TestCase):
|
||||
@ -210,7 +209,7 @@ class SimpleAmbitRuleTest(TestCase):
|
||||
|
||||
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):
|
||||
"""Test related_reactions property returns correct queryset."""
|
||||
mock_qs = MagicMock()
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
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 User
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
from epdb.models import Package, User
|
||||
|
||||
|
||||
@override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models", CELERY_TASK_ALWAYS_EAGER=True)
|
||||
|
||||
@ -4,9 +4,7 @@ from django.test import TestCase, tag
|
||||
from django.urls import reverse
|
||||
|
||||
from epdb.logic import UserManager
|
||||
from epdb.models import Group, GroupPackagePermission, Permission, UserPackagePermission
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
from epdb.models import Package, UserPackagePermission, Permission, GroupPackagePermission, Group
|
||||
|
||||
|
||||
class PackageViewTest(TestCase):
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
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 PackageManager, UserManager
|
||||
from epdb.models import Edge, Pathway
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
from epdb.logic import UserManager, PackageManager
|
||||
from epdb.models import Pathway, Edge
|
||||
|
||||
|
||||
@override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models", CELERY_TASK_ALWAYS_EAGER=True)
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
from django.conf import settings as s
|
||||
from django.test import TestCase
|
||||
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.models import ExternalDatabase, Reaction, Scenario
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
from epdb.logic import UserManager, PackageManager
|
||||
from epdb.models import Reaction, Scenario, ExternalDatabase
|
||||
|
||||
|
||||
class ReactionViewTest(TestCase):
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
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 User
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
from epdb.models import Package, User
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
class UserViewTest(TestCase):
|
||||
|
||||
@ -2,12 +2,13 @@ import logging
|
||||
import re
|
||||
from abc import ABC
|
||||
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.renderer import IndigoRenderer
|
||||
from rdkit import Chem, rdBase
|
||||
from rdkit.Chem import MACCSkeys, Descriptors
|
||||
from rdkit.Chem import rdchem
|
||||
from rdkit.Chem import rdChemReactions
|
||||
from rdkit.Chem.Draw import rdMolDraw2D
|
||||
from rdkit.Chem.MolStandardize import rdMolStandardize
|
||||
@ -90,8 +91,15 @@ class FormatConverter(object):
|
||||
return Chem.MolToSmiles(mol, canonical=canonical)
|
||||
|
||||
@staticmethod
|
||||
def InChIKey(smiles):
|
||||
return Chem.MolToInchiKey(FormatConverter.from_smiles(smiles))
|
||||
def InChIKey(mol_or_smiles: Union[rdchem.Mol | str]):
|
||||
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
|
||||
def InChI(smiles):
|
||||
@ -288,7 +296,8 @@ class FormatConverter(object):
|
||||
product = GetMolFrags(product, asMols=True)
|
||||
for p in product:
|
||||
p = FormatConverter.standardize(
|
||||
Chem.MolToSmiles(p), remove_stereo=remove_stereo
|
||||
Chem.MolToSmiles(p).replace("~", ""),
|
||||
remove_stereo=remove_stereo,
|
||||
)
|
||||
prods.append(p)
|
||||
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
# 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
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
from epdb.models import Package
|
||||
|
||||
# Map HTTP methods to required permissions
|
||||
DEFAULT_METHOD_PERMISSIONS = {
|
||||
|
||||
@ -11,7 +11,6 @@ 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
|
||||
@ -27,6 +26,7 @@ from epdb.models import (
|
||||
License,
|
||||
MLRelativeReasoning,
|
||||
Node,
|
||||
Package,
|
||||
ParallelRule,
|
||||
Pathway,
|
||||
PluginModel,
|
||||
@ -35,14 +35,13 @@ from epdb.models import (
|
||||
RuleBasedRelativeReasoning,
|
||||
Scenario,
|
||||
SequentialRule,
|
||||
Setting,
|
||||
SimpleAmbitRule,
|
||||
SimpleRDKitRule,
|
||||
SimpleRule,
|
||||
)
|
||||
from utilities.chem import FormatConverter
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -1260,3 +1259,46 @@ class PathwayUtils:
|
||||
res[edge.url] = rule_chain
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user