forked from enviPath/enviPy
Compare commits
4 Commits
c367d24d9e
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bc9f86ff1 | |||
| dba6514013 | |||
| a092d4a558 | |||
| 2502c020f7 |
@ -35,15 +35,13 @@ RUN mkdir -p -m 0700 /root/.ssh \
|
|||||||
&& ssh-keyscan git.envipath.com >> /root/.ssh/known_hosts
|
&& ssh-keyscan git.envipath.com >> /root/.ssh/known_hosts
|
||||||
|
|
||||||
# We'll need access to private repos, let docker make use of host ssh agent and use it like:
|
# We'll need access to private repos, let docker make use of host ssh agent and use it like:
|
||||||
# docker build --ssh default -t envipath/envipy-bayer:1.0 .
|
# docker build --ssh default -t envipath/envipy:1.0 .
|
||||||
RUN --mount=type=ssh \
|
RUN --mount=type=ssh \
|
||||||
uv sync --locked --extra ms-login --extra pepper-plugin
|
uv sync --locked --extra pepper-plugin
|
||||||
|
|
||||||
# Now copy source and do a final sync to install the project itself
|
# Now copy source and do a final sync to install the project itself
|
||||||
# Ensure .dockerignore is reasonable
|
# Ensure .dockerignore is reasonable
|
||||||
COPY bb4g bb4g
|
|
||||||
COPY biotransformer biotransformer
|
COPY biotransformer biotransformer
|
||||||
COPY bayer bayer
|
|
||||||
COPY bridge bridge
|
COPY bridge bridge
|
||||||
COPY envipath envipath
|
COPY envipath envipath
|
||||||
COPY epapi epapi
|
COPY epapi epapi
|
||||||
|
|||||||
@ -1,178 +0,0 @@
|
|||||||
import enum
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from envipy_additional_information import GroupEnum as G
|
|
||||||
from envipy_additional_information import SubcategoryEnum as S
|
|
||||||
from envipy_additional_information import (
|
|
||||||
register,
|
|
||||||
register_parser_command,
|
|
||||||
EnviPyModel,
|
|
||||||
# EnviPyModelParser,
|
|
||||||
Interval,
|
|
||||||
UIConfig,
|
|
||||||
IntervalConfig,
|
|
||||||
WidgetType,
|
|
||||||
registry,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@register(keyname="compoundlabel", groups=[S.MISC, G.SLUDGE, G.SEDIMENT, G.SOIL])
|
|
||||||
class CompoundLabel(EnviPyModel):
|
|
||||||
label: str
|
|
||||||
|
|
||||||
class UI:
|
|
||||||
title = "Compound Label"
|
|
||||||
label = UIConfig(widget=WidgetType.TEXT, label="Label", order=1)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO Expose EnviPyModelParser in lib and subclass
|
|
||||||
@register_parser_command("compoundlabel")
|
|
||||||
class CompoundLabelParser:
|
|
||||||
@staticmethod
|
|
||||||
def from_string(data: str) -> CompoundLabel:
|
|
||||||
return CompoundLabel(label=data)
|
|
||||||
|
|
||||||
|
|
||||||
@register(keyname="studywaterstoragecapacity", groups=[S.MISC, G.SLUDGE, G.SEDIMENT, G.SOIL])
|
|
||||||
class StudyWaterStorageCapacity(EnviPyModel):
|
|
||||||
capacity: str
|
|
||||||
|
|
||||||
class UI:
|
|
||||||
title = "Study Water Storage Capacity"
|
|
||||||
capacity = UIConfig(widget=WidgetType.TEXT, label="Study Water Storage Capacity", order=1)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO Expose EnviPyModelParser in lib and subclass
|
|
||||||
@register_parser_command("studywst")
|
|
||||||
class StudyWaterStorageCapacityParser:
|
|
||||||
@staticmethod
|
|
||||||
def from_string(data: str) -> StudyWaterStorageCapacity:
|
|
||||||
return StudyWaterStorageCapacity(capacity=data)
|
|
||||||
|
|
||||||
|
|
||||||
class ObservationType(enum.Enum):
|
|
||||||
OBSERVED = "observed"
|
|
||||||
APPLIED = "applied"
|
|
||||||
NA = 'NA'
|
|
||||||
|
|
||||||
|
|
||||||
@register(keyname="observation", groups=[S.MISC, G.SLUDGE, G.SEDIMENT, G.SOIL])
|
|
||||||
class Observation(EnviPyModel):
|
|
||||||
type: ObservationType
|
|
||||||
min_value: Optional[float] = None
|
|
||||||
max_value: Optional[float] = None
|
|
||||||
|
|
||||||
class UI:
|
|
||||||
title = "Observation"
|
|
||||||
type = UIConfig(widget=WidgetType.SELECT, label="Observed or Applied", order=1)
|
|
||||||
min_value = UIConfig(widget=WidgetType.NUMBER, label="Min Value", order=2)
|
|
||||||
max_value = UIConfig(widget=WidgetType.NUMBER, label="Max Value", order=3)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO Expose EnviPyModelParser in lib and subclass
|
|
||||||
@register_parser_command("observation")
|
|
||||||
class ObservationParser:
|
|
||||||
@staticmethod
|
|
||||||
def from_string(data: str) -> Observation:
|
|
||||||
parts = data.split(";")
|
|
||||||
|
|
||||||
observation_type = ObservationType(parts[0])
|
|
||||||
min_value = None
|
|
||||||
if parts[1]:
|
|
||||||
try:
|
|
||||||
min_value = float(parts[1])
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
max_value = None
|
|
||||||
if parts[2]:
|
|
||||||
try:
|
|
||||||
max_value = float(parts[2])
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return Observation(type=observation_type, min_value=min_value, max_value=max_value)
|
|
||||||
|
|
||||||
|
|
||||||
@register(keyname="kinetics", groups=[S.MISC, G.SLUDGE, G.SEDIMENT, G.SOIL])
|
|
||||||
class Kinetics(EnviPyModel):
|
|
||||||
dt50: Interval[float]
|
|
||||||
normalized_dt50: bool
|
|
||||||
chi2err: Optional[float] = None
|
|
||||||
t_test: Optional[float] = None
|
|
||||||
swarc: Optional[float] = None
|
|
||||||
visual_fit: Optional[int] = None
|
|
||||||
comment: str
|
|
||||||
source: str
|
|
||||||
kinetic_model: str
|
|
||||||
k1: Optional[float] = None
|
|
||||||
k2: Optional[float] = None
|
|
||||||
g: Optional[float] = None
|
|
||||||
tb: Optional[float] = None
|
|
||||||
alpha: Optional[float] = None
|
|
||||||
beta: Optional[float] = None
|
|
||||||
|
|
||||||
class UI:
|
|
||||||
title = "Kinetics"
|
|
||||||
|
|
||||||
# Field config
|
|
||||||
dt50 = IntervalConfig(label="DT50 Range", order=1, unit="d")
|
|
||||||
normalized_dt50 = UIConfig(widget=WidgetType.CHECKBOX, label="Normalized DT50", order=2)
|
|
||||||
chi2err = UIConfig(widget=WidgetType.NUMBER, label="Chi2err", order=3)
|
|
||||||
t_test = UIConfig(widget=WidgetType.NUMBER, label="T-Test", order=4)
|
|
||||||
swarc = UIConfig(widget=WidgetType.NUMBER, label="SWARC", order=5)
|
|
||||||
visual_fit = UIConfig(widget=WidgetType.NUMBER, label="Visual Fit", order=6)
|
|
||||||
comment = UIConfig(widget=WidgetType.TEXTAREA, label="Comments", order=7)
|
|
||||||
source = UIConfig(widget=WidgetType.TEXT, label="Source", order=8)
|
|
||||||
kinetic_model = UIConfig(widget=WidgetType.SELECT, label="Kinetic Model", order=9)
|
|
||||||
k1 = UIConfig(widget=WidgetType.NUMBER, label="K1", order=10)
|
|
||||||
k2 = UIConfig(widget=WidgetType.NUMBER, label="K2", order=11)
|
|
||||||
g = UIConfig(widget=WidgetType.NUMBER, label="G", order=12)
|
|
||||||
tb = UIConfig(widget=WidgetType.NUMBER, label="TB", order=13)
|
|
||||||
alpha = UIConfig(widget=WidgetType.NUMBER, label="Alpha", order=14)
|
|
||||||
beta = UIConfig(widget=WidgetType.NUMBER, label="Beta", order=15)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO Expose EnviPyModelParser in lib and subclass
|
|
||||||
@register_parser_command("kineticevaluation")
|
|
||||||
class KinecticsParser:
|
|
||||||
@staticmethod
|
|
||||||
def from_string(data: str) -> Kinetics:
|
|
||||||
parts = data.split(";")
|
|
||||||
dt50 = registry.get_parser("interval").from_string(parts[0])
|
|
||||||
normalized_dt50 = parts[1] == "true"
|
|
||||||
chi2err = float(parts[2]) if parts[2] else None
|
|
||||||
t_test = float(parts[3]) if parts[3] else None
|
|
||||||
swarc = float(parts[4]) if parts[4] else None
|
|
||||||
visual_fit = int(parts[5]) if parts[5] else None
|
|
||||||
comment = parts[6]
|
|
||||||
source = parts[7]
|
|
||||||
kinetic_model = parts[8]
|
|
||||||
k1 = float(parts[9]) if parts[9] else None
|
|
||||||
k2 = float(parts[10]) if parts[10] else None
|
|
||||||
g = float(parts[11]) if parts[11] else None
|
|
||||||
tb = float(parts[12]) if parts[12] else None
|
|
||||||
alpha = float(parts[13]) if parts[13] else None
|
|
||||||
beta = float(parts[14]) if parts[14] else None
|
|
||||||
|
|
||||||
return Kinetics(
|
|
||||||
dt50=dt50,
|
|
||||||
normalized_dt50=normalized_dt50,
|
|
||||||
chi2err=chi2err,
|
|
||||||
t_test=t_test,
|
|
||||||
swarc=swarc,
|
|
||||||
visual_fit=visual_fit,
|
|
||||||
comment=comment,
|
|
||||||
source=source,
|
|
||||||
kinetic_model=kinetic_model,
|
|
||||||
k1=k1,
|
|
||||||
k2=k2,
|
|
||||||
g=g,
|
|
||||||
tb=tb,
|
|
||||||
alpha=alpha,
|
|
||||||
beta=beta,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print(KinecticsParser.from_string("187.0 - 187.0;false;;;;;;;AFO;;;;;;"))
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
from .models import (
|
|
||||||
PESCompound,
|
|
||||||
PESStructure
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PESCompoundAdmin(admin.ModelAdmin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PESStructureAdmin(admin.ModelAdmin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(PESCompound, PESCompoundAdmin)
|
|
||||||
admin.site.register(PESStructure, PESStructureAdmin)
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class BayerConfig(AppConfig):
|
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
|
||||||
name = 'bayer'
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
from bayer import additional_information # noqa: F401
|
|
||||||
from epdb.template_registry import register_template
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# PES Create
|
|
||||||
register_template(
|
|
||||||
"epdb.actions.collections.compound",
|
|
||||||
"actions/collections/new_pes.html",
|
|
||||||
)
|
|
||||||
register_template(
|
|
||||||
"modals.collections.compound",
|
|
||||||
"modals/collections/new_pes_modal.html",
|
|
||||||
)
|
|
||||||
register_template(
|
|
||||||
"epdb.actions.objects.pathway.add",
|
|
||||||
"actions/objects/pathway_add_pes.html",
|
|
||||||
)
|
|
||||||
register_template(
|
|
||||||
"epdb.modals.objects.pathway.add",
|
|
||||||
"modals/objects/add_pathway_pes_node_modal.html"
|
|
||||||
)
|
|
||||||
|
|
||||||
# PES Viz
|
|
||||||
register_template(
|
|
||||||
"epdb.objects.compound.viz",
|
|
||||||
"objects/compound_viz.html",
|
|
||||||
)
|
|
||||||
|
|
||||||
register_template(
|
|
||||||
"epdb.objects.compound_structure.viz",
|
|
||||||
"objects/compound_structure_viz.html",
|
|
||||||
)
|
|
||||||
|
|
||||||
register_template(
|
|
||||||
"epdb.objects.node.viz",
|
|
||||||
"objects/node_viz.html",
|
|
||||||
)
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2026-03-06 10:51
|
|
||||||
|
|
||||||
import django.utils.timezone
|
|
||||||
import model_utils.fields
|
|
||||||
import uuid
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Package',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('reviewed', models.BooleanField(default=False, verbose_name='Reviewstatus')),
|
|
||||||
('classification_level', models.IntegerField(choices=[(0, 'Internal'), (10, 'Restricted'), (20, 'Secret')], default=10)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'db_table': 'epdb_package',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2026-03-06 10:51
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bayer', '0001_initial'),
|
|
||||||
('epdb', '0019_remove_scenario_additional_information_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='package',
|
|
||||||
name='license',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.license', verbose_name='License'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
# Generated by Django 6.0.3 on 2026-04-17 21:22
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bayer', '0002_initial'),
|
|
||||||
('epdb', '0023_alter_compoundstructure_options_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PESCompound',
|
|
||||||
fields=[
|
|
||||||
('compound_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.compound')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('epdb.compound',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PESStructure',
|
|
||||||
fields=[
|
|
||||||
('compoundstructure_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.compoundstructure')),
|
|
||||||
('pes_link', models.URLField(verbose_name='PES Link')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('epdb.compoundstructure',),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='package',
|
|
||||||
name='data_pool',
|
|
||||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.group', verbose_name='Data pool'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
237
bayer/models.py
237
bayer/models.py
@ -1,237 +0,0 @@
|
|||||||
from typing import List
|
|
||||||
import urllib.parse
|
|
||||||
import nh3
|
|
||||||
from django.conf import settings as s
|
|
||||||
from django.db import models, transaction
|
|
||||||
from django.db.models import QuerySet
|
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from epdb.models import (
|
|
||||||
EnviPathModel,
|
|
||||||
Compound,
|
|
||||||
CompoundStructure,
|
|
||||||
ParallelRule,
|
|
||||||
SequentialRule,
|
|
||||||
SimpleAmbitRule,
|
|
||||||
SimpleRDKitRule,
|
|
||||||
)
|
|
||||||
from utilities.chem import FormatConverter
|
|
||||||
|
|
||||||
|
|
||||||
class Package(EnviPathModel):
|
|
||||||
reviewed = models.BooleanField(verbose_name="Reviewstatus", default=False)
|
|
||||||
license = models.ForeignKey(
|
|
||||||
"epdb.License", on_delete=models.SET_NULL, blank=True, null=True, verbose_name="License"
|
|
||||||
)
|
|
||||||
|
|
||||||
class Classification(models.IntegerChoices):
|
|
||||||
INTERNAL = 0, "Internal"
|
|
||||||
RESTRICTED = 10 , "Restricted"
|
|
||||||
SECRET = 20, "Secret"
|
|
||||||
|
|
||||||
classification_level = models.IntegerField(
|
|
||||||
choices=Classification,
|
|
||||||
default=Classification.RESTRICTED,
|
|
||||||
)
|
|
||||||
|
|
||||||
data_pool = models.ForeignKey("epdb.Group", on_delete=models.SET_NULL, blank=True, null=True,
|
|
||||||
verbose_name="Data pool", default=None)
|
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
|
||||||
# explicitly handle related Rules
|
|
||||||
for r in self.rules.all():
|
|
||||||
r.delete()
|
|
||||||
super().delete(*args, **kwargs)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.name} (pk={self.pk})"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def compounds(self) -> QuerySet:
|
|
||||||
return self.compound_set.all()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def rules(self) -> QuerySet:
|
|
||||||
return self.rule_set.all()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def reactions(self) -> QuerySet:
|
|
||||||
return self.reaction_set.all()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pathways(self) -> QuerySet:
|
|
||||||
return self.pathway_set.all()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def scenarios(self) -> QuerySet:
|
|
||||||
return self.scenario_set.all()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def models(self) -> QuerySet:
|
|
||||||
return self.epmodel_set.all()
|
|
||||||
|
|
||||||
def _url(self):
|
|
||||||
return "{}/package/{}".format(s.SERVER_URL, self.uuid)
|
|
||||||
|
|
||||||
def get_applicable_rules(self) -> List["Rule"]:
|
|
||||||
"""
|
|
||||||
Returns a ordered set of rules where the following applies:
|
|
||||||
1. All Composite will be added to result
|
|
||||||
2. All SimpleRules will be added if theres no CompositeRule present using the SimpleRule
|
|
||||||
Ordering is based on "url" field.
|
|
||||||
"""
|
|
||||||
rules = []
|
|
||||||
rule_qs = self.rules
|
|
||||||
|
|
||||||
reflected_simple_rules = set()
|
|
||||||
|
|
||||||
for r in rule_qs:
|
|
||||||
if isinstance(r, ParallelRule) or isinstance(r, SequentialRule):
|
|
||||||
rules.append(r)
|
|
||||||
for sr in r.simple_rules.all():
|
|
||||||
reflected_simple_rules.add(sr)
|
|
||||||
|
|
||||||
for r in rule_qs:
|
|
||||||
if isinstance(r, SimpleAmbitRule) or isinstance(r, SimpleRDKitRule):
|
|
||||||
if r not in reflected_simple_rules:
|
|
||||||
rules.append(r)
|
|
||||||
|
|
||||||
rules = sorted(rules, key=lambda x: x.url)
|
|
||||||
return rules
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = "epdb_package"
|
|
||||||
|
|
||||||
|
|
||||||
class PESCompound(Compound):
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
@transaction.atomic
|
|
||||||
def create(
|
|
||||||
package: "Package", pes_data: dict, name: str = None, description: str = None, *args, **kwargs
|
|
||||||
) -> "Compound":
|
|
||||||
|
|
||||||
pes_url = pes_data["pes_url"]
|
|
||||||
|
|
||||||
# Check if we find a direct match for a given pes_link
|
|
||||||
if PESStructure.objects.filter(pes_link=pes_url, compound__package=package).exists():
|
|
||||||
# Due to normalization we might end up in having multiple structures
|
|
||||||
# All of them point to the same compound -> pick any
|
|
||||||
return PESStructure.objects.filter(pes_link=pes_url, compound__package=package).first().compound
|
|
||||||
|
|
||||||
# Generate Compound
|
|
||||||
c = PESCompound()
|
|
||||||
c.package = package
|
|
||||||
|
|
||||||
if name is not None:
|
|
||||||
# Clean for potential XSS
|
|
||||||
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
|
||||||
|
|
||||||
if name is None or name == "":
|
|
||||||
name = f"Compound {Compound.objects.filter(package=package).count() + 1}"
|
|
||||||
|
|
||||||
c.name = name
|
|
||||||
|
|
||||||
# We have a default here only set the value if it carries some payload
|
|
||||||
if description is not None and description.strip() != "":
|
|
||||||
c.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
|
||||||
|
|
||||||
c.save()
|
|
||||||
|
|
||||||
molfile = pes_data.get("representativeStructures", [{}])[0].get("ctab")
|
|
||||||
|
|
||||||
if molfile is None:
|
|
||||||
raise ValueError("PES data does not contain a valid mol file!")
|
|
||||||
|
|
||||||
smiles = FormatConverter.to_smiles(FormatConverter.from_molfile(molfile))
|
|
||||||
|
|
||||||
standardized_smiles = FormatConverter.standardize(smiles, remove_stereo=True)
|
|
||||||
|
|
||||||
is_standardized = standardized_smiles == smiles
|
|
||||||
|
|
||||||
if not is_standardized:
|
|
||||||
_ = PESStructure.create(
|
|
||||||
c,
|
|
||||||
pes_url,
|
|
||||||
molfile,
|
|
||||||
standardized_smiles,
|
|
||||||
name="Normalized structure of {}".format(name),
|
|
||||||
description="{} (in its normalized form)".format(description),
|
|
||||||
normalized_structure=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
cs = PESStructure.create(
|
|
||||||
c,
|
|
||||||
pes_url,
|
|
||||||
molfile,
|
|
||||||
smiles,
|
|
||||||
name=name,
|
|
||||||
description=description,
|
|
||||||
normalized_structure=is_standardized
|
|
||||||
)
|
|
||||||
|
|
||||||
c.default_structure = cs
|
|
||||||
c.save()
|
|
||||||
|
|
||||||
return c
|
|
||||||
|
|
||||||
|
|
||||||
class PESStructure(CompoundStructure):
|
|
||||||
pes_link = models.URLField(blank=False, null=False, verbose_name="PES Link")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
@transaction.atomic
|
|
||||||
def create(
|
|
||||||
compound: Compound,
|
|
||||||
pes_link: str,
|
|
||||||
mol_file: str,
|
|
||||||
smiles: str,
|
|
||||||
name: str = None,
|
|
||||||
description: str = None,
|
|
||||||
*args,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
if compound.pk is None:
|
|
||||||
raise ValueError("Unpersisted Compound! Persist compound first!")
|
|
||||||
|
|
||||||
cs = PESStructure()
|
|
||||||
# Clean for potential XSS
|
|
||||||
if name is not None:
|
|
||||||
cs.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
|
||||||
|
|
||||||
if description is not None:
|
|
||||||
cs.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
|
||||||
|
|
||||||
cs.smiles = smiles
|
|
||||||
cs.mol_file = mol_file
|
|
||||||
cs.pes_link = pes_link
|
|
||||||
cs.compound = compound
|
|
||||||
|
|
||||||
if "normalized_structure" in kwargs:
|
|
||||||
cs.normalized_structure = kwargs["normalized_structure"]
|
|
||||||
|
|
||||||
cs.save()
|
|
||||||
|
|
||||||
return cs
|
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def add_structure(
|
|
||||||
self,
|
|
||||||
smiles: str,
|
|
||||||
name: str = None,
|
|
||||||
description: str = None,
|
|
||||||
default_structure: bool = False,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
) -> "CompoundStructure":
|
|
||||||
raise ValueError("Not supported!")
|
|
||||||
|
|
||||||
def d3_json(self):
|
|
||||||
return {
|
|
||||||
"is_pes": True,
|
|
||||||
"pes_link": self.pes_link,
|
|
||||||
# Will overwrite image from Node
|
|
||||||
"image": f"{reverse('depict_pes')}?pesLink={urllib.parse.quote(self.pes_link)}",
|
|
||||||
"image_type": "png",
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
{% if meta.can_edit %}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary btn-sm"
|
|
||||||
onclick="document.getElementById('new_pes_modal').showModal(); return false;"
|
|
||||||
>
|
|
||||||
New PES
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
<li>
|
|
||||||
<a
|
|
||||||
class="button"
|
|
||||||
onclick="document.getElementById('add_pathway_pes_node_modal').showModal(); return false;"
|
|
||||||
>
|
|
||||||
<i class="glyphicon glyphicon-plus"></i> Add PES</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
@ -1,175 +0,0 @@
|
|||||||
{% load static %}
|
|
||||||
|
|
||||||
<dialog
|
|
||||||
id="new_package_modal"
|
|
||||||
class="modal"
|
|
||||||
x-data="{
|
|
||||||
isSubmitting: false,
|
|
||||||
packageClassification: null,
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.isSubmitting = false;
|
|
||||||
this.packageClassification = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
setFormData(data) {
|
|
||||||
this.formData = data;
|
|
||||||
},
|
|
||||||
|
|
||||||
get isSecret() {
|
|
||||||
return this.packageClassification === '20';
|
|
||||||
},
|
|
||||||
|
|
||||||
submit(formId) {
|
|
||||||
const form = document.getElementById(formId);
|
|
||||||
|
|
||||||
// Remove previously injected inputs
|
|
||||||
form.querySelectorAll('.dynamic-param').forEach(el => el.remove());
|
|
||||||
|
|
||||||
// Add values from dynamic form into the html form
|
|
||||||
if (this.formData) {
|
|
||||||
Object.entries(this.formData).forEach(([key, value]) => {
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.type = 'hidden';
|
|
||||||
input.name = key;
|
|
||||||
input.value = value;
|
|
||||||
input.classList.add('dynamic-param');
|
|
||||||
|
|
||||||
form.appendChild(input);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (form && form.checkValidity()) {
|
|
||||||
this.isSubmitting = true;
|
|
||||||
form.submit();
|
|
||||||
} else if (form) {
|
|
||||||
form.reportValidity();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}"
|
|
||||||
@close="reset()"
|
|
||||||
>
|
|
||||||
<div class="modal-box max-w-3xl">
|
|
||||||
<!-- Header -->
|
|
||||||
<h3 class="text-lg font-bold">New Package</h3>
|
|
||||||
|
|
||||||
<!-- Close button (X) -->
|
|
||||||
<form method="dialog">
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
|
||||||
:disabled="isSubmitting"
|
|
||||||
>
|
|
||||||
✕
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Body -->
|
|
||||||
<div class="py-4">
|
|
||||||
<form
|
|
||||||
id="new_package_form"
|
|
||||||
accept-charset="UTF-8"
|
|
||||||
action=""
|
|
||||||
method="post"
|
|
||||||
>
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<!-- Name -->
|
|
||||||
<div class="form-control mb-3">
|
|
||||||
<label class="label" for="package-name">
|
|
||||||
<span class="label-text">Name</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="package-name"
|
|
||||||
class="input input-bordered w-full"
|
|
||||||
name="package-name"
|
|
||||||
placeholder="Name"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Description -->
|
|
||||||
<div class="form-control mb-3">
|
|
||||||
<label class="label" for="package-description">
|
|
||||||
<span class="label-text">Description</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="package-description"
|
|
||||||
type="text"
|
|
||||||
class="input input-bordered w-full"
|
|
||||||
placeholder="Description..."
|
|
||||||
name="package-description"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Classification Level -->
|
|
||||||
<div class="form-control mb-3">
|
|
||||||
<label class="label" for="package-classification">
|
|
||||||
<span class="label-text">Package Classification</span>
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
id="package-classification"
|
|
||||||
name="package-classification"
|
|
||||||
class="select select-bordered w-full"
|
|
||||||
x-model="packageClassification"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="null" disabled selected>Select Classification</option>
|
|
||||||
<option value="0">Internal</option>
|
|
||||||
<option value="10">Restricted</option>
|
|
||||||
<option value="20">Secret</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Secret Groups -->
|
|
||||||
<div class="form-control mb-3" x-show="isSecret" x-cloak>
|
|
||||||
<label class="label" for="package-data-pool">
|
|
||||||
<span class="label-text">Data Pool for SECRET Package</span>
|
|
||||||
</label>
|
|
||||||
<p>Only users with this role can be granted access to this package</p>
|
|
||||||
<select
|
|
||||||
id="package-data-pool"
|
|
||||||
name="package-data-pool"
|
|
||||||
class="select select-bordered w-full"
|
|
||||||
>
|
|
||||||
<option value="" disabled selected>Select Data Pool</option>
|
|
||||||
{% for obj in meta.secret_groups %}
|
|
||||||
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Footer -->
|
|
||||||
<div class="modal-action">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn"
|
|
||||||
onclick="this.closest('dialog').close()"
|
|
||||||
:disabled="isSubmitting"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary"
|
|
||||||
@click="submit('new_package_form')"
|
|
||||||
:disabled="isSubmitting || !selectedType || loadingSchemas"
|
|
||||||
>
|
|
||||||
<span x-show="!isSubmitting">Submit</span>
|
|
||||||
<span
|
|
||||||
x-show="isSubmitting"
|
|
||||||
class="loading loading-spinner loading-sm"
|
|
||||||
></span>
|
|
||||||
<span x-show="isSubmitting">Creating...</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Backdrop -->
|
|
||||||
<form method="dialog" class="modal-backdrop">
|
|
||||||
<button :disabled="isSubmitting">close</button>
|
|
||||||
</form>
|
|
||||||
</dialog>
|
|
||||||
@ -1,174 +0,0 @@
|
|||||||
{% load static %}
|
|
||||||
|
|
||||||
<dialog
|
|
||||||
id="new_pes_modal"
|
|
||||||
class="modal"
|
|
||||||
x-data="{
|
|
||||||
isSubmitting: false,
|
|
||||||
pesLink: null,
|
|
||||||
pesVizHtml: '',
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.isSubmitting = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
get isPESSet() {
|
|
||||||
console.log(this.pesLink);
|
|
||||||
return this.pesLink !== null;
|
|
||||||
},
|
|
||||||
|
|
||||||
updatePesViz() {
|
|
||||||
if (!this.isPESSet) {
|
|
||||||
this.pesVizHtml = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const img = new Image();
|
|
||||||
img.src = '{% url 'depict_pes' %}?pesLink=' + encodeURIComponent(this.pesLink);
|
|
||||||
img.style.width = '100%';
|
|
||||||
img.style.height = '100%';
|
|
||||||
img.style.objectFit = 'cover';
|
|
||||||
|
|
||||||
img.onload = () => {
|
|
||||||
this.pesVizHtml = img.outerHTML;
|
|
||||||
};
|
|
||||||
|
|
||||||
img.onerror = () => {
|
|
||||||
this.pesVizHtml = `
|
|
||||||
<div class='alert alert-error' role='alert'>
|
|
||||||
<h4 class='alert-heading'>Could not render PES!</h4>
|
|
||||||
<p>Could not render PES - Do you have access?</p>
|
|
||||||
</div>`;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
submit(formId) {
|
|
||||||
const form = document.getElementById(formId);
|
|
||||||
|
|
||||||
// Remove previously injected inputs
|
|
||||||
form.querySelectorAll('.dynamic-param').forEach(el => el.remove());
|
|
||||||
|
|
||||||
// Add values from dynamic form into the html form
|
|
||||||
if (this.formData) {
|
|
||||||
Object.entries(this.formData).forEach(([key, value]) => {
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.type = 'hidden';
|
|
||||||
input.name = key;
|
|
||||||
input.value = value;
|
|
||||||
input.classList.add('dynamic-param');
|
|
||||||
|
|
||||||
form.appendChild(input);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (form && form.checkValidity()) {
|
|
||||||
this.isSubmitting = true;
|
|
||||||
form.submit();
|
|
||||||
} else if (form) {
|
|
||||||
form.reportValidity();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
@close="reset()"
|
|
||||||
>
|
|
||||||
<div class="modal-box max-w-3xl">
|
|
||||||
<!-- Header -->
|
|
||||||
<h3 class="text-lg font-bold">New PES</h3>
|
|
||||||
|
|
||||||
<!-- Close button (X) -->
|
|
||||||
<form method="dialog">
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
|
||||||
:disabled="isSubmitting"
|
|
||||||
>
|
|
||||||
✕
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Body -->
|
|
||||||
<div class="py-4">
|
|
||||||
<form
|
|
||||||
id="new-pes-modal-form"
|
|
||||||
accept-charset="UTF-8"
|
|
||||||
action="{% url 'create pes' meta.current_package.uuid %}"
|
|
||||||
method="post"
|
|
||||||
>
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="form-control mb-3">
|
|
||||||
<label class="label" for="compound-name">
|
|
||||||
<span class="label-text">Name</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="compound-name"
|
|
||||||
class="input input-bordered w-full"
|
|
||||||
name="compound-name"
|
|
||||||
placeholder="Name"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-control mb-3">
|
|
||||||
<label class="label" for="compound-description">
|
|
||||||
<span class="label-text">Description</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="compound-description"
|
|
||||||
class="input input-bordered w-full"
|
|
||||||
name="compound-description"
|
|
||||||
placeholder="Description"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-control mb-3">
|
|
||||||
<label class="label" for="pes-link">
|
|
||||||
<span class="label-text">Link to PES</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="pes-link"
|
|
||||||
name="pes-link"
|
|
||||||
type="text"
|
|
||||||
class="input input-bordered w-full"
|
|
||||||
placeholder="Link to PES e.g. https://pesregapp-test.cropkey-np.ag/entities/PES-000126"
|
|
||||||
x-model="pesLink"
|
|
||||||
@input="updatePesViz()"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="pes-viz" class="mb-3" x-html="pesVizHtml"></div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Footer -->
|
|
||||||
<div class="modal-action">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn"
|
|
||||||
onclick="this.closest('dialog').close()"
|
|
||||||
:disabled="isSubmitting"
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary"
|
|
||||||
@click="submit('new-pes-modal-form')"
|
|
||||||
:disabled="isSubmitting"
|
|
||||||
>
|
|
||||||
<span x-show="!isSubmitting">Submit</span>
|
|
||||||
<span
|
|
||||||
x-show="isSubmitting"
|
|
||||||
class="loading loading-spinner loading-sm"
|
|
||||||
></span>
|
|
||||||
<span x-show="isSubmitting">Creating...</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Backdrop -->
|
|
||||||
<form method="dialog" class="modal-backdrop">
|
|
||||||
<button :disabled="isSubmitting">close</button>
|
|
||||||
</form>
|
|
||||||
</dialog>
|
|
||||||
@ -1,174 +0,0 @@
|
|||||||
{% load static %}
|
|
||||||
|
|
||||||
<dialog
|
|
||||||
id="add_pathway_pes_node_modal"
|
|
||||||
class="modal"
|
|
||||||
x-data="{
|
|
||||||
isSubmitting: false,
|
|
||||||
pesLink: null,
|
|
||||||
pesVizHtml: '',
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.isSubmitting = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
get isPESSet() {
|
|
||||||
console.log(this.pesLink);
|
|
||||||
return this.pesLink !== null;
|
|
||||||
},
|
|
||||||
|
|
||||||
updatePesViz() {
|
|
||||||
if (!this.isPESSet) {
|
|
||||||
this.pesVizHtml = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const img = new Image();
|
|
||||||
img.src = '{% url 'depict_pes' %}?pesLink=' + encodeURIComponent(this.pesLink);
|
|
||||||
img.style.width = '100%';
|
|
||||||
img.style.height = '100%';
|
|
||||||
img.style.objectFit = 'cover';
|
|
||||||
|
|
||||||
img.onload = () => {
|
|
||||||
this.pesVizHtml = img.outerHTML;
|
|
||||||
};
|
|
||||||
|
|
||||||
img.onerror = () => {
|
|
||||||
this.pesVizHtml = `
|
|
||||||
<div class='alert alert-error' role='alert'>
|
|
||||||
<h4 class='alert-heading'>Could not render PES!</h4>
|
|
||||||
<p>Could not render PES - Do you have access?</p>
|
|
||||||
</div>`;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
submit(formId) {
|
|
||||||
const form = document.getElementById(formId);
|
|
||||||
|
|
||||||
// Remove previously injected inputs
|
|
||||||
form.querySelectorAll('.dynamic-param').forEach(el => el.remove());
|
|
||||||
|
|
||||||
// Add values from dynamic form into the html form
|
|
||||||
if (this.formData) {
|
|
||||||
Object.entries(this.formData).forEach(([key, value]) => {
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.type = 'hidden';
|
|
||||||
input.name = key;
|
|
||||||
input.value = value;
|
|
||||||
input.classList.add('dynamic-param');
|
|
||||||
|
|
||||||
form.appendChild(input);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (form && form.checkValidity()) {
|
|
||||||
this.isSubmitting = true;
|
|
||||||
form.submit();
|
|
||||||
} else if (form) {
|
|
||||||
form.reportValidity();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
@close="reset()"
|
|
||||||
>
|
|
||||||
<div class="modal-box max-w-3xl">
|
|
||||||
<!-- Header -->
|
|
||||||
<h3 class="text-lg font-bold">New PES</h3>
|
|
||||||
|
|
||||||
<!-- Close button (X) -->
|
|
||||||
<form method="dialog">
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
|
||||||
:disabled="isSubmitting"
|
|
||||||
>
|
|
||||||
✕
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Body -->
|
|
||||||
<div class="py-4">
|
|
||||||
<form
|
|
||||||
id="new-pes-node-modal-form"
|
|
||||||
accept-charset="UTF-8"
|
|
||||||
action="{% url 'create pes node' current_object.package.uuid current_object.uuid %}"
|
|
||||||
method="post"
|
|
||||||
>
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="form-control mb-3">
|
|
||||||
<label class="label" for="compound-name">
|
|
||||||
<span class="label-text">Name</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="compound-name"
|
|
||||||
class="input input-bordered w-full"
|
|
||||||
name="compound-name"
|
|
||||||
placeholder="Name"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-control mb-3">
|
|
||||||
<label class="label" for="compound-description">
|
|
||||||
<span class="label-text">Description</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="compound-description"
|
|
||||||
class="input input-bordered w-full"
|
|
||||||
name="compound-description"
|
|
||||||
placeholder="Description"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-control mb-3">
|
|
||||||
<label class="label" for="pes-link">
|
|
||||||
<span class="label-text">Link to PES</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="pes-link"
|
|
||||||
name="pes-link"
|
|
||||||
type="text"
|
|
||||||
class="input input-bordered w-full"
|
|
||||||
placeholder="Link to PES e.g. https://pesregapp-test.cropkey-np.ag/entities/PES-000126"
|
|
||||||
x-model="pesLink"
|
|
||||||
@input="updatePesViz()"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="pes-viz" class="mb-3" x-html="pesVizHtml"></div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Footer -->
|
|
||||||
<div class="modal-action">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn"
|
|
||||||
onclick="this.closest('dialog').close()"
|
|
||||||
:disabled="isSubmitting"
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary"
|
|
||||||
@click="submit('new-pes-node-modal-form')"
|
|
||||||
:disabled="isSubmitting"
|
|
||||||
>
|
|
||||||
<span x-show="!isSubmitting">Submit</span>
|
|
||||||
<span
|
|
||||||
x-show="isSubmitting"
|
|
||||||
class="loading loading-spinner loading-sm"
|
|
||||||
></span>
|
|
||||||
<span x-show="isSubmitting">Creating...</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Backdrop -->
|
|
||||||
<form method="dialog" class="modal-backdrop">
|
|
||||||
<button :disabled="isSubmitting">close</button>
|
|
||||||
</form>
|
|
||||||
</dialog>
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
{% if compound_structure.pes_link %}
|
|
||||||
<!-- PES -->
|
|
||||||
<div class="collapse-arrow bg-base-200 collapse">
|
|
||||||
<input type="checkbox" checked />
|
|
||||||
<div class="collapse-title text-xl font-medium">Link to PES</div>
|
|
||||||
<div class="collapse-content">{{ compound_structure.pes_link }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Image Representation -->
|
|
||||||
<div class="collapse-arrow bg-base-200 collapse">
|
|
||||||
<input type="checkbox" checked />
|
|
||||||
<div class="collapse-title text-xl font-medium">PES Image Representation</div>
|
|
||||||
<div class="collapse-content">
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<img src='{% url 'depict_pes' %}?pesLink={{ compound_structure.pes_link|urlencode }}'/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
{% if compound.default_structure.pes_link %}
|
|
||||||
<!-- PES -->
|
|
||||||
<div class="collapse-arrow bg-base-200 collapse">
|
|
||||||
<input type="checkbox" checked />
|
|
||||||
<div class="collapse-title text-xl font-medium">Link to PES</div>
|
|
||||||
<div class="collapse-content">{{ compound.default_structure.pes_link }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Image Representation -->
|
|
||||||
<div class="collapse-arrow bg-base-200 collapse">
|
|
||||||
<input type="checkbox" checked />
|
|
||||||
<div class="collapse-title text-xl font-medium">PES Image Representation</div>
|
|
||||||
<div class="collapse-content">
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<img src='{% url 'depict_pes' %}?pesLink={{ compound.default_structure.pes_link|urlencode }}'/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
{% if node.default_node_label.pes_link %}
|
|
||||||
<!-- PES -->
|
|
||||||
<div class="collapse-arrow bg-base-200 collapse">
|
|
||||||
<input type="checkbox" checked />
|
|
||||||
<div class="collapse-title text-xl font-medium">Link to PES</div>
|
|
||||||
<div class="collapse-content">{{ node.default_node_label.pes_link }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Image Representation -->
|
|
||||||
<div class="collapse-arrow bg-base-200 collapse">
|
|
||||||
<input type="checkbox" checked />
|
|
||||||
<div class="collapse-title text-xl font-medium">PES Image Representation</div>
|
|
||||||
<div class="collapse-content">
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<img src='{% url 'depict_pes' %}?pesLink={{ node.default_node_label.pes_link|urlencode }}'/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
{% extends "framework_modern.html" %}
|
|
||||||
{% load static %}
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
{% block action_modals %}
|
|
||||||
{% include "modals/objects/edit_package_modal.html" %}
|
|
||||||
{% include "modals/objects/edit_package_permissions_modal.html" %}
|
|
||||||
{% include "modals/objects/publish_package_modal.html" %}
|
|
||||||
{% include "modals/objects/set_license_modal.html" %}
|
|
||||||
{% include "modals/objects/export_package_modal.html" %}
|
|
||||||
{% include "modals/objects/generic_delete_modal.html" %}
|
|
||||||
{% endblock action_modals %}
|
|
||||||
|
|
||||||
<div class="space-y-2 p-4">
|
|
||||||
<!-- Header Section -->
|
|
||||||
<div class="card bg-base-100">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<h2 class="card-title text-2xl">{{ package.name }} {% if meta.url_contains_package and meta.current_package.get_classification_level_display == "Restricted" %}<img src="{% static 'images/restricted_mid.png' %}" width="100">{% elif meta.url_contains_package and meta.current_package.get_classification_level_display == "Secret" %}<img src="{% static 'images/secret_mid.png' %}" width="60">{% endif %}</h2>
|
|
||||||
<div id="actionsButton" class="dropdown dropdown-e nd hidden">
|
|
||||||
<div tabindex="0" role="button" class="btn btn-ghost btn-sm">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
class="lucide lucide-wrench"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Actions
|
|
||||||
</div>
|
|
||||||
<ul
|
|
||||||
tabindex="-1"
|
|
||||||
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
|
|
||||||
>
|
|
||||||
{% block actions %}
|
|
||||||
{% include "actions/objects/package.html" %}
|
|
||||||
{% endblock %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="mt-2">{{ package.description|safe }}</p>
|
|
||||||
<ul class="menu bg-base-200 rounded-box mt-4 w-full">
|
|
||||||
<li>
|
|
||||||
<a href="{{ package.url }}/pathway" class="hover:bg-base-300"
|
|
||||||
>Pathways ({{ package.pathways.count }})</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{{ package.url }}/rule" class="hover:bg-base-300"
|
|
||||||
>Rules ({{ package.rules.count }})</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{{ package.url }}/compound" class="hover:bg-base-300"
|
|
||||||
>Compounds ({{ package.compounds.count }})</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{{ package.url }}/reaction" class="hover:bg-base-300"
|
|
||||||
>Reactions ({{ package.reactions.count }})</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{{ package.url }}/model" class="hover:bg-base-300"
|
|
||||||
>Models ({{ package.models.count }})</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{{ package.url }}/scenario" class="hover:bg-base-300"
|
|
||||||
>Scenarios ({{ package.scenarios.count }})</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Show actions button if there are actions
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
const actionsButton = document.getElementById("actionsButton");
|
|
||||||
const actionsList = actionsButton?.querySelector("ul");
|
|
||||||
if (actionsList && actionsList.children.length > 0) {
|
|
||||||
actionsButton?.classList.remove("hidden");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock content %}
|
|
||||||
@ -1,154 +0,0 @@
|
|||||||
{% extends "static/login_base.html" %}
|
|
||||||
{% load static %}
|
|
||||||
{% block title %}enviPath - Sign In{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_styles %}
|
|
||||||
<style>
|
|
||||||
/* Tab styling */
|
|
||||||
.tab-content {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.tab-content.active {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
input[type="radio"].tab-radio {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.tab-label {
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
border-bottom: 2px solid transparent;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
.tab-label:hover {
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
input[type="radio"].tab-radio:checked + .tab-label {
|
|
||||||
border-bottom-color: #3b82f6;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div>
|
|
||||||
<img src="{% static 'images/bayer-logo.svg' %}">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col space-y-4 ...">
|
|
||||||
<div><p></p></div>
|
|
||||||
<div><p></p></div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tab Navigation -->
|
|
||||||
<div class="border-base-300 mb-6 border-b" hidden>
|
|
||||||
<div class="flex justify-start">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="auth-tab"
|
|
||||||
id="tab-sso"
|
|
||||||
class="tab-radio"
|
|
||||||
checked
|
|
||||||
/>
|
|
||||||
<label for="tab-sso" class="tab-label">SSO</label>
|
|
||||||
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="auth-tab"
|
|
||||||
id="tab-signin"
|
|
||||||
class="tab-radio"
|
|
||||||
/>
|
|
||||||
<label for="tab-signin" class="tab-label">Local User</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- SSO Tab -->
|
|
||||||
<div id="content-sso" class="tab-content active">
|
|
||||||
<button role="link" onclick="window.location.href='/entra/login'" name="sso" class="btn btn-primary w-full">
|
|
||||||
Login with Microsoft
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Sign In Tab -->
|
|
||||||
<div id="content-signin" class="tab-content">
|
|
||||||
<form method="post" action="{% url 'login' %}" class="space-y-4">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="login" value="true" />
|
|
||||||
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label" for="username">
|
|
||||||
<span class="label-text">Account</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="username"
|
|
||||||
name="username"
|
|
||||||
placeholder="Username or Email"
|
|
||||||
class="input input-bordered w-full"
|
|
||||||
required
|
|
||||||
autocomplete="username"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label" for="passwordinput">
|
|
||||||
<span class="label-text">Password</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="passwordinput"
|
|
||||||
name="password"
|
|
||||||
placeholder="••••••••"
|
|
||||||
class="input input-bordered w-full"
|
|
||||||
required
|
|
||||||
autocomplete="current-password"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-right">
|
|
||||||
<a href="{% url 'password_reset' %}" class="link link-primary text-sm"
|
|
||||||
>Forgot password?</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="hidden" name="next" value="{{ next }}" />
|
|
||||||
|
|
||||||
<button type="submit" name="signin" class="btn btn-primary w-full">
|
|
||||||
Sign In
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_scripts %}
|
|
||||||
<script>
|
|
||||||
// Tab switching functionality
|
|
||||||
document.querySelectorAll('input[name="auth-tab"]').forEach((radio) => {
|
|
||||||
radio.addEventListener("change", function () {
|
|
||||||
// Hide all content
|
|
||||||
document.querySelectorAll(".tab-content").forEach((content) => {
|
|
||||||
content.classList.remove("active");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show selected content
|
|
||||||
const contentId = "content-" + this.id.replace("tab-", "");
|
|
||||||
document.getElementById(contentId).classList.add("active");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check for hash in URL to auto-select tab
|
|
||||||
window.addEventListener("DOMContentLoaded", function () {
|
|
||||||
const hash = window.location.hash.substring(1); // Remove the # symbol
|
|
||||||
if (hash === "signup" || hash === "signin") {
|
|
||||||
const tabRadio = document.getElementById("tab-" + hash);
|
|
||||||
if (tabRadio) {
|
|
||||||
tabRadio.checked = true;
|
|
||||||
// Trigger change event to show correct content
|
|
||||||
tabRadio.dispatchEvent(new Event("change"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1,19 +0,0 @@
|
|||||||
from django.urls import re_path
|
|
||||||
|
|
||||||
from . import views as v
|
|
||||||
|
|
||||||
UUID = "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
re_path(r"^depict_pes$", v.visualize_pes, name="depict_pes"),
|
|
||||||
re_path(
|
|
||||||
rf"^package/(?P<package_uuid>{UUID})/pes$",
|
|
||||||
v.create_pes,
|
|
||||||
name="create pes",
|
|
||||||
),
|
|
||||||
re_path(
|
|
||||||
rf"^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/pes$",
|
|
||||||
v.create_pes_node,
|
|
||||||
name="create pes node",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
167
bayer/views.py
167
bayer/views.py
@ -1,167 +0,0 @@
|
|||||||
import base64
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from django.conf import settings as s
|
|
||||||
from django.core.exceptions import BadRequest
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.shortcuts import redirect
|
|
||||||
|
|
||||||
from bayer.models import PESCompound
|
|
||||||
from epdb.logic import PackageManager
|
|
||||||
from epdb.models import Pathway, Node
|
|
||||||
from epdb.views import _anonymous_or_real
|
|
||||||
from utilities.decorators import package_permission_required
|
|
||||||
|
|
||||||
Package = s.GET_PACKAGE_MODEL()
|
|
||||||
|
|
||||||
|
|
||||||
@package_permission_required()
|
|
||||||
def create_pes(request, package_uuid):
|
|
||||||
current_user = _anonymous_or_real(request)
|
|
||||||
current_package = PackageManager.get_package_by_id(current_user, package_uuid)
|
|
||||||
|
|
||||||
if request.method == "POST":
|
|
||||||
|
|
||||||
if current_package.classification_level == Package.Classification.INTERNAL:
|
|
||||||
raise BadRequest("Cannot create PESs for internal packages.")
|
|
||||||
|
|
||||||
compound_name = request.POST.get('compound-name')
|
|
||||||
compound_description = request.POST.get('compound-description')
|
|
||||||
pes_link = request.POST.get('pes-link')
|
|
||||||
|
|
||||||
if pes_link:
|
|
||||||
try:
|
|
||||||
pes_data = fetch_pes(request, pes_link)
|
|
||||||
except ValueError as e:
|
|
||||||
return BadRequest(f"Could not fetch PES data for {pes_link}")
|
|
||||||
|
|
||||||
classification = pes_data.get("classificationLevel", "")
|
|
||||||
if "secret" == classification.lower():
|
|
||||||
|
|
||||||
if current_package.classification_level != Package.Classification.SECRET:
|
|
||||||
return BadRequest("Cannot create PESs for non-secret packages.")
|
|
||||||
|
|
||||||
data_pools = pes_data.get("dataPools")
|
|
||||||
if data_pools:
|
|
||||||
if s.DATA_POOL_MAPPING[current_package.data_pool.name] not in data_pools:
|
|
||||||
return BadRequest(
|
|
||||||
f"PES data pool {s.DATA_POOL_MAPPING[current_package.data_pool.name]} not found in PES data")
|
|
||||||
|
|
||||||
pes = PESCompound.create(current_package, pes_data, compound_name, compound_description)
|
|
||||||
|
|
||||||
return redirect(pes.url)
|
|
||||||
else:
|
|
||||||
return BadRequest("Please provide a PES link.")
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@package_permission_required()
|
|
||||||
def create_pes_node(request, package_uuid, pathway_uuid):
|
|
||||||
current_user = _anonymous_or_real(request)
|
|
||||||
current_package = PackageManager.get_package_by_id(current_user, package_uuid)
|
|
||||||
current_pathway = Pathway.objects.get(package=current_package, uuid=pathway_uuid)
|
|
||||||
|
|
||||||
if request.method == "POST":
|
|
||||||
|
|
||||||
if current_package.classification_level == Package.Classification.INTERNAL:
|
|
||||||
raise BadRequest("Cannot create PESs for internal packages.")
|
|
||||||
|
|
||||||
compound_name = request.POST.get('compound-name')
|
|
||||||
compound_description = request.POST.get('compound-description')
|
|
||||||
pes_link = request.POST.get('pes-link')
|
|
||||||
|
|
||||||
if pes_link:
|
|
||||||
try:
|
|
||||||
pes_data = fetch_pes(request, pes_link)
|
|
||||||
except ValueError as e:
|
|
||||||
return BadRequest(f"Could not fetch PES data for {pes_link}")
|
|
||||||
|
|
||||||
classification = pes_data.get("classificationLevel", "")
|
|
||||||
if "secret" == classification.lower():
|
|
||||||
|
|
||||||
if current_package.classification_level != Package.Classification.SECRET:
|
|
||||||
return BadRequest("Cannot create PESs for non-secret packages.")
|
|
||||||
|
|
||||||
data_pools = pes_data.get("dataPools")
|
|
||||||
if data_pools:
|
|
||||||
if s.DATA_POOL_MAPPING[current_package.data_pool.name] not in data_pools:
|
|
||||||
return BadRequest(
|
|
||||||
f"PES data pool {s.DATA_POOL_MAPPING[current_package.data_pool.name]} not found in PES data")
|
|
||||||
|
|
||||||
pes = PESCompound.create(current_package, pes_data, compound_name, compound_description)
|
|
||||||
|
|
||||||
node_qs = Node.objects.filter(pathway=current_pathway, default_node_label=pes.default_structure)
|
|
||||||
if node_qs.exists():
|
|
||||||
return redirect(current_pathway.url)
|
|
||||||
|
|
||||||
n = Node()
|
|
||||||
n.stereo_removed = False
|
|
||||||
n.pathway = current_pathway
|
|
||||||
n.depth = 0
|
|
||||||
|
|
||||||
n.default_node_label = pes.default_structure
|
|
||||||
n.save()
|
|
||||||
|
|
||||||
n.node_labels.add(pes.default_structure)
|
|
||||||
n.save()
|
|
||||||
|
|
||||||
return redirect(current_pathway.url)
|
|
||||||
|
|
||||||
else:
|
|
||||||
return BadRequest("Please provide a PES link.")
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_pes(request, pes_url) -> dict:
|
|
||||||
from epauth.views import get_access_token_from_request
|
|
||||||
token = get_access_token_from_request(request)
|
|
||||||
|
|
||||||
if token:
|
|
||||||
for k, v in s.PES_API_MAPPING.items():
|
|
||||||
if pes_url.startswith(k):
|
|
||||||
pes_id = pes_url.split('/')[-1]
|
|
||||||
|
|
||||||
if pes_id == 'dummy':
|
|
||||||
import json
|
|
||||||
res_data = json.load(open(s.BASE_DIR / "fixtures/pes.json"))
|
|
||||||
res_data["pes_url"] = pes_url
|
|
||||||
return res_data
|
|
||||||
else:
|
|
||||||
headers = {"Authorization": f"Bearer {token['access_token']}"}
|
|
||||||
params = {"pes_reg_entity_corporate_id": pes_id}
|
|
||||||
|
|
||||||
res = requests.get(v, headers=headers, params=params, proxies=s.PROXIES or None)
|
|
||||||
|
|
||||||
try:
|
|
||||||
res.raise_for_status()
|
|
||||||
pes_data = res.json()
|
|
||||||
|
|
||||||
if len(pes_data) == 0:
|
|
||||||
raise ValueError(f"PES with id {pes_id} not found")
|
|
||||||
|
|
||||||
res_data = pes_data[0]
|
|
||||||
res_data["pes_url"] = pes_url
|
|
||||||
return res_data
|
|
||||||
|
|
||||||
except requests.exceptions.HTTPError as e:
|
|
||||||
raise ValueError(f"Error fetching PES with id {pes_id}: {e}")
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Unknown URL {pes_url}")
|
|
||||||
else:
|
|
||||||
raise ValueError("Could not fetch access token from request.")
|
|
||||||
|
|
||||||
|
|
||||||
def visualize_pes(request):
|
|
||||||
pes_link = request.GET.get('pesLink')
|
|
||||||
|
|
||||||
if pes_link:
|
|
||||||
pes_data = fetch_pes(request, pes_link)
|
|
||||||
|
|
||||||
representations = pes_data.get('representations')
|
|
||||||
|
|
||||||
for rep in representations:
|
|
||||||
if rep.get('type') == 'color':
|
|
||||||
image_data = base64.b64decode(rep.get('base64').replace("data:image/png;base64,", ""))
|
|
||||||
return HttpResponse(image_data, content_type="image/png")
|
|
||||||
199
bb4g/__init__.py
199
bb4g/__init__.py
@ -1,199 +0,0 @@
|
|||||||
import enum
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import math
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from django.conf import settings as s
|
|
||||||
from envipy_additional_information import register, EnviPyModel, UIConfig, WidgetType
|
|
||||||
|
|
||||||
from bridge.contracts import Classifier # noqa: I001
|
|
||||||
from bridge.dto import (
|
|
||||||
BuildResult,
|
|
||||||
EnviPyDTO,
|
|
||||||
EvaluationResult,
|
|
||||||
RunResult,
|
|
||||||
TransformationProductPrediction,
|
|
||||||
) # noqa: I001
|
|
||||||
|
|
||||||
logger = logging.getLogger("epdb")
|
|
||||||
|
|
||||||
|
|
||||||
class SamplingAlgorithm(enum.Enum):
|
|
||||||
EXACT = "exact"
|
|
||||||
|
|
||||||
|
|
||||||
@register("bb4gconfig")
|
|
||||||
class BB4GConfig(EnviPyModel):
|
|
||||||
sampling_algorithm: SamplingAlgorithm = SamplingAlgorithm.EXACT
|
|
||||||
cutoff: int = -5
|
|
||||||
|
|
||||||
class UI:
|
|
||||||
title = "BB4G Configuration"
|
|
||||||
sampling_algorithm = UIConfig(
|
|
||||||
widget=WidgetType.SELECT,
|
|
||||||
label="BB4G Sampling Algorithm",
|
|
||||||
order=1,
|
|
||||||
placeholder="If unset defaults to 'exact'"
|
|
||||||
)
|
|
||||||
cutoff = UIConfig(
|
|
||||||
widget=WidgetType.NUMBER,
|
|
||||||
label="BB4G Cutoff",
|
|
||||||
order=2,
|
|
||||||
placeholder="If unset defaults to -5"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Once stable these will be exposed by enviPy-plugins lib
|
|
||||||
class BB4G(Classifier):
|
|
||||||
Config = BB4GConfig
|
|
||||||
|
|
||||||
def __init__(self, config: BB4GConfig | None = None):
|
|
||||||
super().__init__(config)
|
|
||||||
self.url = f"{s.BB4G_URL}"
|
|
||||||
|
|
||||||
self.token = self.acquire_token()
|
|
||||||
self.header = {
|
|
||||||
"Authorization": f"Bearer {self.token}",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
|
|
||||||
def acquire_token(self):
|
|
||||||
BB4G_TENANT_ID = s.BB4G_TENANT_ID
|
|
||||||
BB4G_CLIENT_ID = s.BB4G_CLIENT_ID
|
|
||||||
BB4G_CLIENT_SECRET = s.BB4G_CLIENT_SECRET
|
|
||||||
BB4G_SCOPE = s.BB4G_SCOPE
|
|
||||||
|
|
||||||
BB4G_TOKEN_URL = f"https://login.microsoftonline.com/{BB4G_TENANT_ID}/oauth2/v2.0/token"
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
"client_id": BB4G_CLIENT_ID,
|
|
||||||
"client_secret": BB4G_CLIENT_SECRET,
|
|
||||||
"scope": BB4G_SCOPE,
|
|
||||||
"grant_type": "client_credentials"
|
|
||||||
}
|
|
||||||
|
|
||||||
# No Proxy required, URL is whitelisted
|
|
||||||
res = requests.post(BB4G_TOKEN_URL, data=payload)
|
|
||||||
|
|
||||||
res.raise_for_status()
|
|
||||||
|
|
||||||
return res.json()["access_token"]
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
header = {
|
|
||||||
"Authorization": f"Bearer {self.token}",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
|
|
||||||
started = False
|
|
||||||
retries = 0
|
|
||||||
while not started and retries < 5:
|
|
||||||
res = requests.post(f"{self.url}/start", headers=header, data={}, proxies=s.PROXIES or None)
|
|
||||||
logger.info(f"Starting BB4G: {res.status_code}")
|
|
||||||
if res.status_code == 200:
|
|
||||||
started = True
|
|
||||||
elif res.status_code in [500, 502]:
|
|
||||||
retries += 1
|
|
||||||
import time
|
|
||||||
time.sleep(5)
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Unexpected status code: {res.status_code}")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def requires_rule_packages(cls) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def requires_data_packages(cls) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def identifier(cls) -> str:
|
|
||||||
return "bb4g"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def name(cls) -> str:
|
|
||||||
return "BB4G Template Free Model"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def display(cls) -> str:
|
|
||||||
return "BB4G Template Free Model"
|
|
||||||
|
|
||||||
def build(self, eP: EnviPyDTO, *args, **kwargs) -> BuildResult | None:
|
|
||||||
return
|
|
||||||
|
|
||||||
def run(self, eP: EnviPyDTO, *args, **kwargs) -> RunResult:
|
|
||||||
|
|
||||||
# Ensure Service is running
|
|
||||||
self.start()
|
|
||||||
|
|
||||||
smiles = [c.smiles for c in eP.get_compounds()]
|
|
||||||
preds = self._post(smiles)
|
|
||||||
|
|
||||||
results = []
|
|
||||||
|
|
||||||
for substrate in preds.keys():
|
|
||||||
results.append(
|
|
||||||
TransformationProductPrediction(
|
|
||||||
substrate=substrate,
|
|
||||||
products=preds[substrate],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return RunResult(
|
|
||||||
producer=eP.get_context().url,
|
|
||||||
description=f"Generated at {datetime.now()}",
|
|
||||||
result=results,
|
|
||||||
)
|
|
||||||
|
|
||||||
def evaluate(self, eP: EnviPyDTO, *args, **kwargs) -> EvaluationResult:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def build_and_evaluate(self, eP: EnviPyDTO, *args, **kwargs) -> EvaluationResult:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _post(self, smiles: List[str]) -> dict[str, dict[str, float]]:
|
|
||||||
header = {
|
|
||||||
"Authorization": f"Bearer {self.token}",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
|
|
||||||
for smi in smiles:
|
|
||||||
data = {
|
|
||||||
"smiles": smi,
|
|
||||||
"sampling_alg": self.config.sampling_algorithm.value,
|
|
||||||
"cutoff": self.config.cutoff,
|
|
||||||
}
|
|
||||||
|
|
||||||
retries = 0
|
|
||||||
while retries < 5:
|
|
||||||
resp = requests.post(f"{self.url}/compute", headers=header, data=json.dumps(data),
|
|
||||||
proxies=s.PROXIES or None)
|
|
||||||
|
|
||||||
if resp.status_code == 418:
|
|
||||||
retries += 1
|
|
||||||
logger.info(f"BB4G predict hit a 418, retrying in 60 seconds")
|
|
||||||
import time
|
|
||||||
time.sleep(60)
|
|
||||||
continue
|
|
||||||
|
|
||||||
resp.raise_for_status()
|
|
||||||
|
|
||||||
for substrate, predictions in resp.json().items():
|
|
||||||
preds = {}
|
|
||||||
|
|
||||||
for pred in predictions:
|
|
||||||
prod = pred["prediction"]
|
|
||||||
prob = math.exp(pred["log_likelihood"])
|
|
||||||
preds[prod] = prob
|
|
||||||
|
|
||||||
result[substrate] = preds
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
return result
|
|
||||||
@ -261,7 +261,6 @@ class Classifier(Plugin):
|
|||||||
for k, v in data.items():
|
for k, v in data.items():
|
||||||
if v != "":
|
if v != "":
|
||||||
cpy[k] = v
|
cpy[k] = v
|
||||||
|
|
||||||
return cls.Config(**cpy)
|
return cls.Config(**cpy)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -1,54 +1,26 @@
|
|||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: postgres:18
|
image: postgres:18
|
||||||
container_name: eppostgres
|
container_name: envipath-postgres
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
POSTGRES_DB: envipath
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- ep_bayer_postgres_data:/var/lib/postgresql
|
- postgres_data:/var/lib/postgresql
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
container_name: epredis
|
container_name: envipath-redis
|
||||||
ports:
|
ports:
|
||||||
- "6379:6379"
|
- "6379:6379"
|
||||||
volumes:
|
|
||||||
- ep_bayer_redis_data:/data
|
|
||||||
|
|
||||||
biotransformer3:
|
|
||||||
image: git.envipath.com/envipath/biotransformer3:1.0
|
|
||||||
container_name: epbiotransformer3
|
|
||||||
|
|
||||||
# web:
|
|
||||||
# image: envipath/envipy-bayer:1.0
|
|
||||||
# container_name: epdjango
|
|
||||||
# ports:
|
|
||||||
# - "127.0.0.1:8000:8000"
|
|
||||||
# env_file:
|
|
||||||
# - .env
|
|
||||||
# command: gunicorn envipath.wsgi:application --bind 0.0.0.0:8000 --workers 3
|
|
||||||
# volumes:
|
|
||||||
# - ep_bayer_data:/opt/enviPy/
|
|
||||||
|
|
||||||
celery_worker:
|
|
||||||
image: git.envipath.com/envipath/envipy-bayer:1.0
|
|
||||||
container_name: epcelery
|
|
||||||
env_file:
|
|
||||||
- .env.dev
|
|
||||||
command: celery -A envipath worker --concurrency=6 -Q model,predict,background --pool threads
|
|
||||||
volumes:
|
|
||||||
- ep_bayer_data:/opt/enviPy/
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
ep_bayer_postgres_data:
|
postgres_data:
|
||||||
ep_bayer_redis_data:
|
|
||||||
ep_bayer_data:
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ services:
|
|||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
volumes:
|
volumes:
|
||||||
- ep_bayer_postgres_data:/var/lib/postgresql
|
- ep_postgres_data:/var/lib/postgresql
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
|
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
@ -18,14 +18,14 @@ services:
|
|||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
container_name: epredis
|
container_name: epredis
|
||||||
volumes:
|
volumes:
|
||||||
- ep_bayer_redis_data:/data
|
- ep_redis_data:/data
|
||||||
|
|
||||||
biotransformer3:
|
biotransformer3:
|
||||||
image: envipath/biotransformer3:1.0
|
image: envipath/biotransformer3:1.0
|
||||||
container_name: epbiotransformer3
|
container_name: epbiotransformer3
|
||||||
|
|
||||||
web:
|
web:
|
||||||
image: envipath/envipy-bayer:1.0
|
image: envipath/envipy:1.0
|
||||||
container_name: epdjango
|
container_name: epdjango
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:8000:8000"
|
- "127.0.0.1:8000:8000"
|
||||||
@ -33,18 +33,18 @@ services:
|
|||||||
- .env
|
- .env
|
||||||
command: gunicorn envipath.wsgi:application --bind 0.0.0.0:8000 --workers 3
|
command: gunicorn envipath.wsgi:application --bind 0.0.0.0:8000 --workers 3
|
||||||
volumes:
|
volumes:
|
||||||
- ep_bayer_data:/opt/enviPy/
|
- ep_data:/opt/enviPy/
|
||||||
|
|
||||||
celery_worker:
|
celery_worker:
|
||||||
image: envipath/envipy-bayer:1.0
|
image: envipath/envipy:1.0
|
||||||
container_name: epcelery
|
container_name: epcelery
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
command: celery -A envipath worker --concurrency=6 -Q model,predict,background --pool threads
|
command: celery -A envipath worker --concurrency=6 -Q model,predict,background --pool threads
|
||||||
volumes:
|
volumes:
|
||||||
- ep_bayer_data:/opt/enviPy/
|
- ep_data:/opt/enviPy/
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
ep_bayer_postgres_data:
|
ep_postgres_data:
|
||||||
ep_bayer_redis_data:
|
ep_redis_data:
|
||||||
ep_bayer_data:
|
ep_data:
|
||||||
|
|||||||
@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/4.2/topics/settings/
|
|||||||
For the full list of settings and their values, see
|
For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||||
"""
|
"""
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ from sklearn.tree import DecisionTreeClassifier
|
|||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
ENV_PATH = os.environ.get("ENV_PATH", BASE_DIR / ".env.dev")
|
ENV_PATH = os.environ.get("ENV_PATH", BASE_DIR / ".env")
|
||||||
print(f"Loading env from {ENV_PATH}")
|
print(f"Loading env from {ENV_PATH}")
|
||||||
load_dotenv(ENV_PATH, override=False)
|
load_dotenv(ENV_PATH, override=False)
|
||||||
|
|
||||||
@ -143,12 +143,6 @@ if os.environ.get("USE_TEMPLATE_DB", False) == "True":
|
|||||||
"TEMPLATE": os.environ["TEMPLATE_DB"],
|
"TEMPLATE": os.environ["TEMPLATE_DB"],
|
||||||
}
|
}
|
||||||
|
|
||||||
CACHES = {
|
|
||||||
"default": {
|
|
||||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
|
||||||
"LOCATION": "unique-snowflake",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||||
@ -275,12 +269,6 @@ LOGGING = {
|
|||||||
"filename": os.path.join(LOG_DIR, "debug.log"),
|
"filename": os.path.join(LOG_DIR, "debug.log"),
|
||||||
"formatter": "simple",
|
"formatter": "simple",
|
||||||
},
|
},
|
||||||
"auth_file": {
|
|
||||||
"level": "INFO", # Or higher
|
|
||||||
"class": "logging.FileHandler",
|
|
||||||
"filename": os.path.join(LOG_DIR, "auth.log"),
|
|
||||||
"formatter": "simple",
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"loggers": {
|
"loggers": {
|
||||||
# For everything under epdb/ loaded via getlogger(__name__)
|
# For everything under epdb/ loaded via getlogger(__name__)
|
||||||
@ -301,11 +289,6 @@ LOGGING = {
|
|||||||
"propagate": True,
|
"propagate": True,
|
||||||
"level": os.environ.get("LOG_LEVEL", "INFO"),
|
"level": os.environ.get("LOG_LEVEL", "INFO"),
|
||||||
},
|
},
|
||||||
"auth": {
|
|
||||||
"handlers": ["auth_file"],
|
|
||||||
"propagate": True,
|
|
||||||
"level": os.environ.get("LOG_LEVEL", "INFO"),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,48 +442,3 @@ BIOTRANSFORMER_ENABLED = os.environ.get("BIOTRANSFORMER_ENABLED", "False") == "T
|
|||||||
FLAGS["BIOTRANSFORMER"] = BIOTRANSFORMER_ENABLED
|
FLAGS["BIOTRANSFORMER"] = BIOTRANSFORMER_ENABLED
|
||||||
if BIOTRANSFORMER_ENABLED:
|
if BIOTRANSFORMER_ENABLED:
|
||||||
BIOTRANSFORMER_URL = os.environ.get("BIOTRANSFORMER_URL", None)
|
BIOTRANSFORMER_URL = os.environ.get("BIOTRANSFORMER_URL", None)
|
||||||
|
|
||||||
# PES
|
|
||||||
PES_API_MAPPING = os.environ.get("PES_API_MAPPING", None)
|
|
||||||
if PES_API_MAPPING:
|
|
||||||
import json
|
|
||||||
PES_API_MAPPING = json.loads(PES_API_MAPPING)
|
|
||||||
else:
|
|
||||||
PES_API_MAPPING = {}
|
|
||||||
|
|
||||||
# Entra Groups
|
|
||||||
ENTRA_GROUPS = os.environ.get("ENTRA_GROUPS", None)
|
|
||||||
if ENTRA_GROUPS:
|
|
||||||
import json
|
|
||||||
ENTRA_GROUPS = json.loads(ENTRA_GROUPS)
|
|
||||||
else:
|
|
||||||
ENTRA_GROUPS = {}
|
|
||||||
|
|
||||||
ENTRA_SECRET_GROUPS = os.environ.get("ENTRA_SECRET_GROUPS", None)
|
|
||||||
if ENTRA_SECRET_GROUPS:
|
|
||||||
import json
|
|
||||||
ENTRA_SECRET_GROUPS = json.loads(ENTRA_SECRET_GROUPS)
|
|
||||||
else:
|
|
||||||
ENTRA_SECRET_GROUPS = {}
|
|
||||||
|
|
||||||
# PES Data Pools vs Entra Mapping
|
|
||||||
DATA_POOL_MAPPING = os.environ.get("DATA_POOL_MAPPING", None)
|
|
||||||
if DATA_POOL_MAPPING:
|
|
||||||
import json
|
|
||||||
DATA_POOL_MAPPING = json.loads(DATA_POOL_MAPPING)
|
|
||||||
else:
|
|
||||||
DATA_POOL_MAPPING = {}
|
|
||||||
|
|
||||||
PROXIES = {}
|
|
||||||
if os.environ.get("HTTP_PROXY"):
|
|
||||||
PROXIES["http"] = os.environ.get("HTTP_PROXY")
|
|
||||||
PROXIES["https"] = os.environ.get("HTTPS_PROXY")
|
|
||||||
|
|
||||||
# BB4g
|
|
||||||
BB4G_URL = os.environ.get("BB4G_URL")
|
|
||||||
BB4G_TENANT_ID = os.environ.get("BB4G_TENANT_ID")
|
|
||||||
BB4G_CLIENT_ID = os.environ.get("BB4G_CLIENT_ID")
|
|
||||||
BB4G_CLIENT_SECRET = os.environ.get("BB4G_CLIENT_SECRET")
|
|
||||||
BB4G_SCOPE = os.environ.get("BB4G_SCOPE")
|
|
||||||
|
|
||||||
os.environ["NO_PROXY"] = "localhost,127.0.0.1,epbiotransformer3"
|
|
||||||
@ -5,5 +5,4 @@ from . import views
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("entra/login/", views.entra_login, name="entra_login"),
|
path("entra/login/", views.entra_login, name="entra_login"),
|
||||||
path("auth/redirect/", views.entra_callback, name="entra_callback"),
|
path("auth/redirect/", views.entra_callback, name="entra_callback"),
|
||||||
path("auth/token/", views.get_token, name="get_token"),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -2,11 +2,9 @@ import msal
|
|||||||
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.contrib.auth import login
|
from django.contrib.auth import login
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
|
||||||
from epdb.logic import UserManager, GroupManager
|
from epdb.logic import UserManager
|
||||||
from epdb.models import Group
|
|
||||||
|
|
||||||
|
|
||||||
def get_msal_app_with_cache(request):
|
def get_msal_app_with_cache(request):
|
||||||
@ -82,33 +80,6 @@ def entra_callback(request):
|
|||||||
|
|
||||||
login(request, u)
|
login(request, u)
|
||||||
|
|
||||||
# EDIT START
|
|
||||||
|
|
||||||
# Ensure groups exists in eP
|
|
||||||
for id, name in s.ENTRA_SECRET_GROUPS.items():
|
|
||||||
if not Group.objects.filter(uuid=id).exists():
|
|
||||||
g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ",
|
|
||||||
uuid=id)
|
|
||||||
else:
|
|
||||||
g = Group.objects.get(uuid=id)
|
|
||||||
# Ensure its secret
|
|
||||||
g.secret = True
|
|
||||||
g.save()
|
|
||||||
|
|
||||||
for id, name in s.ENTRA_GROUPS.items():
|
|
||||||
if not Group.objects.filter(uuid=id).exists():
|
|
||||||
g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ",
|
|
||||||
uuid=id)
|
|
||||||
else:
|
|
||||||
g = Group.objects.get(uuid=id)
|
|
||||||
|
|
||||||
for group_uuid in claims.get("groups", []):
|
|
||||||
if Group.objects.filter(uuid=group_uuid).exists():
|
|
||||||
g = Group.objects.get(uuid=group_uuid)
|
|
||||||
g.user_member.add(u)
|
|
||||||
|
|
||||||
# EDIT END
|
|
||||||
|
|
||||||
return redirect(s.SERVER_URL) # Handle errors
|
return redirect(s.SERVER_URL) # Handle errors
|
||||||
|
|
||||||
|
|
||||||
@ -116,11 +87,6 @@ def get_access_token_from_request(request, scopes=None):
|
|||||||
"""
|
"""
|
||||||
Get an access token from the request using MSAL token cache.
|
Get an access token from the request using MSAL token cache.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Check if auth via Access Token
|
|
||||||
if request.headers.get("Authorization"):
|
|
||||||
return {"access_token": request.headers.get("Authorization").split(" ")[1]}
|
|
||||||
|
|
||||||
if scopes is None:
|
if scopes is None:
|
||||||
scopes = s.MS_ENTRA_SCOPES
|
scopes = s.MS_ENTRA_SCOPES
|
||||||
|
|
||||||
@ -162,9 +128,3 @@ def get_access_token_from_request(request, scopes=None):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_token(request):
|
|
||||||
token = get_access_token_from_request(request)
|
|
||||||
msg = f"{token}"
|
|
||||||
return HttpResponse(msg, content_type='text/plain')
|
|
||||||
|
|||||||
2
epdb/exceptions.py
Normal file
2
epdb/exceptions.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
class InvalidSMILESException(Exception):
|
||||||
|
pass
|
||||||
@ -1,20 +1,17 @@
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
import jwt
|
|
||||||
import nh3
|
import nh3
|
||||||
import requests
|
|
||||||
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.core.cache import cache
|
|
||||||
from django.http import HttpResponse, JsonResponse
|
from django.http import HttpResponse, JsonResponse
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from jwt import InvalidIssuerError
|
|
||||||
from ninja import Field, Form, Query, Router, Schema
|
from ninja import Field, Form, Query, Router, Schema
|
||||||
from ninja.security import HttpBearer
|
from ninja.security import SessionAuth
|
||||||
|
|
||||||
from utilities.chem import FormatConverter
|
from utilities.chem import FormatConverter
|
||||||
from utilities.misc import PackageExporter
|
from utilities.misc import PackageExporter
|
||||||
|
|
||||||
from .logic import (
|
from .logic import (
|
||||||
EPDBURLParser,
|
EPDBURLParser,
|
||||||
GroupManager,
|
GroupManager,
|
||||||
@ -49,26 +46,6 @@ from .models import (
|
|||||||
Package = s.GET_PACKAGE_MODEL()
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
def get_cached_jwks(tenant_id: str, force=False) -> Dict:
|
|
||||||
"""Get JWKS using Django cache"""
|
|
||||||
cache_key = f"jwks_{tenant_id}"
|
|
||||||
|
|
||||||
jwks = cache.get(cache_key)
|
|
||||||
|
|
||||||
if jwks is None or force:
|
|
||||||
# Cache miss, fetch new keys
|
|
||||||
jwks_uri = f"https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys"
|
|
||||||
response = requests.get(jwks_uri)
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
jwks = response.json()
|
|
||||||
|
|
||||||
# Cache for 1 hour (3600 seconds)
|
|
||||||
cache.set(cache_key, jwks, 3600)
|
|
||||||
|
|
||||||
return jwks
|
|
||||||
|
|
||||||
|
|
||||||
def get_package_for_write(user, package_uuid):
|
def get_package_for_write(user, package_uuid):
|
||||||
p = PackageManager.get_package_by_id(user, package_uuid)
|
p = PackageManager.get_package_by_id(user, package_uuid)
|
||||||
if not PackageManager.writable(user, p):
|
if not PackageManager.writable(user, p):
|
||||||
@ -82,52 +59,7 @@ def _anonymous_or_real(request):
|
|||||||
return get_user_model().objects.get(username="anonymous")
|
return get_user_model().objects.get(username="anonymous")
|
||||||
|
|
||||||
|
|
||||||
def validate_token(token: str) -> dict:
|
router = Router(auth=SessionAuth(csrf=False))
|
||||||
TENANT_ID = s.MS_ENTRA_TENANT_ID
|
|
||||||
CLIENT_ID = s.MS_ENTRA_CLIENT_ID
|
|
||||||
|
|
||||||
jwks = get_cached_jwks(TENANT_ID)
|
|
||||||
|
|
||||||
header = jwt.get_unverified_header(token)
|
|
||||||
|
|
||||||
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(
|
|
||||||
next(k for k in jwks["keys"] if k["kid"] == header["kid"])
|
|
||||||
)
|
|
||||||
|
|
||||||
# Handle V1 and V2 tokens
|
|
||||||
try:
|
|
||||||
claims = jwt.decode(
|
|
||||||
token,
|
|
||||||
public_key,
|
|
||||||
algorithms=["RS256"],
|
|
||||||
audience=[CLIENT_ID, f"api://{CLIENT_ID}"],
|
|
||||||
issuer=[
|
|
||||||
f"https://sts.windows.net/{TENANT_ID}/",
|
|
||||||
f"https://login.microsoftonline.com/{TENANT_ID}/v2.0"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
raise ValueError(f"Token verification failed! - {e}")
|
|
||||||
|
|
||||||
return claims
|
|
||||||
|
|
||||||
|
|
||||||
class MSBearerTokenAuth(HttpBearer):
|
|
||||||
|
|
||||||
def authenticate(self, request, token):
|
|
||||||
if token is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
claims = validate_token(token)
|
|
||||||
|
|
||||||
if not User.objects.filter(uuid=claims['oid']).exists():
|
|
||||||
return None
|
|
||||||
|
|
||||||
request.user = User.objects.get(uuid=claims['oid'])
|
|
||||||
return request.user
|
|
||||||
|
|
||||||
|
|
||||||
router = Router(auth=MSBearerTokenAuth())
|
|
||||||
|
|
||||||
|
|
||||||
class Error(Schema):
|
class Error(Schema):
|
||||||
@ -221,6 +153,21 @@ class SimpleModel(SimpleObject):
|
|||||||
identifier: str = "relative-reasoning"
|
identifier: str = "relative-reasoning"
|
||||||
|
|
||||||
|
|
||||||
|
################
|
||||||
|
# Login/Logout #
|
||||||
|
################
|
||||||
|
@router.post("/", response={200: SimpleUser, 403: Error}, auth=None)
|
||||||
|
def login(request, loginusername: Form[str], loginpassword: Form[str]):
|
||||||
|
from django.contrib.auth import authenticate, login
|
||||||
|
|
||||||
|
email = User.objects.get(username=loginusername).email
|
||||||
|
user = authenticate(username=email, password=loginpassword)
|
||||||
|
if user:
|
||||||
|
login(request, user)
|
||||||
|
return user
|
||||||
|
else:
|
||||||
|
return 403, {"message": "Invalid username and/or password"}
|
||||||
|
|
||||||
|
|
||||||
########
|
########
|
||||||
# User #
|
# User #
|
||||||
@ -847,7 +794,6 @@ class CreateCompound(Schema):
|
|||||||
compoundName: str | None = None
|
compoundName: str | None = None
|
||||||
compoundDescription: str | None = None
|
compoundDescription: str | None = None
|
||||||
inchi: str | None = None
|
inchi: str | None = None
|
||||||
pesLink: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/package/{uuid:package_uuid}/compound")
|
@router.post("/package/{uuid:package_uuid}/compound")
|
||||||
@ -859,29 +805,6 @@ def create_package_compound(
|
|||||||
try:
|
try:
|
||||||
p = get_package_for_write(request.user, package_uuid)
|
p = get_package_for_write(request.user, package_uuid)
|
||||||
# inchi is not used atm
|
# inchi is not used atm
|
||||||
|
|
||||||
if c.pesLink is not None:
|
|
||||||
from bayer.views import fetch_pes
|
|
||||||
from bayer.models import PESCompound
|
|
||||||
|
|
||||||
try:
|
|
||||||
pes_data = fetch_pes(request, c.pesLink)
|
|
||||||
except ValueError as e:
|
|
||||||
return 400, {"message": f"Could not fetch PES data for {c.pesLink}"}
|
|
||||||
|
|
||||||
classification = pes_data.get("classificationLevel", "")
|
|
||||||
if "secret" == classification.lower():
|
|
||||||
|
|
||||||
if p.classification_level != Package.Classification.SECRET:
|
|
||||||
return 400, {"Cannot create PESs for non-secret packages."}
|
|
||||||
|
|
||||||
data_pools = pes_data.get("dataPools")
|
|
||||||
if data_pools:
|
|
||||||
if s.DATA_POOL_MAPPING[p.data_pool.name] not in data_pools:
|
|
||||||
return 400, { "messsage": f"PES data pool {s.DATA_POOL_MAPPING[p.data_pool.name]} not found in PES data"}
|
|
||||||
|
|
||||||
c = PESCompound.create(p, pes_data, c.compoundName, c.compoundDescription)
|
|
||||||
else:
|
|
||||||
c = Compound.create(
|
c = Compound.create(
|
||||||
p, c.compoundSmiles, c.compoundName, c.compoundDescription, inchi=c.inchi
|
p, c.compoundSmiles, c.compoundName, c.compoundDescription, inchi=c.inchi
|
||||||
)
|
)
|
||||||
@ -1687,13 +1610,13 @@ class PathwayNode(Schema):
|
|||||||
dt50s: List[Dict[str, str]] = Field([], alias="dt50s")
|
dt50s: List[Dict[str, str]] = Field([], alias="dt50s")
|
||||||
engineeredIntermediate: bool = Field(None, alias="engineered_intermediate")
|
engineeredIntermediate: bool = Field(None, alias="engineered_intermediate")
|
||||||
id: str = Field(None, alias="url")
|
id: str = Field(None, alias="url")
|
||||||
idcomp: str = Field(None, alias="default_node_label.url")
|
idcomp: str = Field(None, alias="node_label_id")
|
||||||
idreact: str = Field(None, alias="default_node_label.url")
|
idreact: str = Field(None, alias="node_label_id")
|
||||||
image: str = Field(None, alias="image")
|
image: str = Field(None, alias="image")
|
||||||
imageSize: int = Field(None, alias="image_size")
|
imageSize: int = Field(None, alias="image_size")
|
||||||
name: str = Field(None, alias="name")
|
name: str = Field(None, alias="name")
|
||||||
proposed: List[Dict[str, str]] = Field([], alias="proposed_intermediate")
|
proposed: List[Dict[str, str]] = Field([], alias="proposed_intermediate")
|
||||||
smiles: str = Field(None, alias="default_node_label.smiles")
|
smiles: str = Field(None, alias="smiles")
|
||||||
pseudo: bool = Field(False, alias="pseudo")
|
pseudo: bool = Field(False, alias="pseudo")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -1979,67 +1902,25 @@ class CreateNode(Schema):
|
|||||||
nodeName: str | None = None
|
nodeName: str | None = None
|
||||||
nodeReason: str | None = None
|
nodeReason: str | None = None
|
||||||
nodeDepth: str | None = None
|
nodeDepth: str | None = None
|
||||||
pesLink: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}/node",
|
"/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}/node",
|
||||||
response={200: str | Any, 400: Error, 403: Error},
|
response={200: str | Any, 403: Error},
|
||||||
)
|
)
|
||||||
def add_pathway_node(request, package_uuid, pathway_uuid, n: Form[CreateNode]):
|
def add_pathway_node(request, package_uuid, pathway_uuid, n: Form[CreateNode]):
|
||||||
try:
|
try:
|
||||||
p = get_package_for_write(request.user, package_uuid)
|
p = get_package_for_write(request.user, package_uuid)
|
||||||
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
||||||
|
|
||||||
# TODO Code Dup from bayer.views
|
|
||||||
|
|
||||||
if n.pesLink:
|
|
||||||
from bayer.views import fetch_pes
|
|
||||||
from bayer.models import PESCompound
|
|
||||||
|
|
||||||
try:
|
|
||||||
pes_data = fetch_pes(request, n.pesLink)
|
|
||||||
except ValueError as e:
|
|
||||||
return 400, {"message": f"Could not fetch PES data for {n.pesLink}"}
|
|
||||||
|
|
||||||
classification = pes_data.get("classificationLevel", "")
|
|
||||||
if "secret" == classification.lower():
|
|
||||||
|
|
||||||
if p.classification_level != Package.Classification.SECRET:
|
|
||||||
return 400, "Cannot create PESs for non-secret packages."
|
|
||||||
|
|
||||||
data_pools = pes_data.get("dataPools")
|
|
||||||
if data_pools:
|
|
||||||
if s.DATA_POOL_MAPPING[p.data_pool.name] not in data_pools:
|
|
||||||
return 400, {
|
|
||||||
"messsage": f"PES data pool {s.DATA_POOL_MAPPING[p.data_pool.name]} not found in PES data"
|
|
||||||
}
|
|
||||||
|
|
||||||
c = PESCompound.create(p, pes_data, n.nodeName, n.nodeReason)
|
|
||||||
|
|
||||||
node_qs = Node.objects.filter(pathway=pw, default_node_label=c.default_structure)
|
|
||||||
if node_qs.exists():
|
|
||||||
return redirect(pw.url)
|
|
||||||
|
|
||||||
node = Node()
|
|
||||||
node.stereo_removed = False
|
|
||||||
node.pathway = pw
|
|
||||||
node.depth = 0
|
|
||||||
|
|
||||||
node.default_node_label = c.default_structure
|
|
||||||
node.save()
|
|
||||||
|
|
||||||
node.node_labels.add(c.default_structure)
|
|
||||||
node.save()
|
|
||||||
else:
|
|
||||||
if n.nodeDepth is not None and n.nodeDepth.strip() != "":
|
if n.nodeDepth is not None and n.nodeDepth.strip() != "":
|
||||||
node_depth = int(n.nodeDepth)
|
node_depth = int(float(n.nodeDepth))
|
||||||
else:
|
else:
|
||||||
node_depth = -1
|
node_depth = -1
|
||||||
|
|
||||||
node = Node.create(pw, n.nodeAsSmiles, node_depth, n.nodeName, n.nodeReason)
|
n = Node.create(pw, n.nodeAsSmiles, node_depth, n.nodeName, n.nodeReason)
|
||||||
|
|
||||||
return redirect(node.url)
|
return redirect(n.url)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return 403, {"message": "Adding node failed!"}
|
return 403, {"message": "Adding node failed!"}
|
||||||
|
|
||||||
@ -2149,16 +2030,13 @@ def add_pathway_edge(request, package_uuid, pathway_uuid, e: Form[CreateEdge]):
|
|||||||
educts = []
|
educts = []
|
||||||
products = []
|
products = []
|
||||||
|
|
||||||
subclasses = CompoundStructure.__subclasses__()
|
|
||||||
|
|
||||||
if e.edgeAsSmirks:
|
if e.edgeAsSmirks:
|
||||||
for ed in e.edgeAsSmirks.split(">>")[0].split("\\."):
|
for ed in e.edgeAsSmirks.split(">>")[0].split("\\."):
|
||||||
stand_ed = FormatConverter.standardize(ed, remove_stereo=True)
|
stand_ed = FormatConverter.standardize(ed, remove_stereo=True)
|
||||||
educts.append(
|
educts.append(
|
||||||
Node.objects.get(
|
Node.objects.get(
|
||||||
pathway=pw,
|
pathway=pw,
|
||||||
default_node_label=CompoundStructure.objects.not_instance_of(*subclasses).
|
default_node_label=CompoundStructure.objects.get(
|
||||||
get(
|
|
||||||
compound__package=p, smiles=stand_ed
|
compound__package=p, smiles=stand_ed
|
||||||
).compound.default_structure,
|
).compound.default_structure,
|
||||||
)
|
)
|
||||||
@ -2169,8 +2047,7 @@ def add_pathway_edge(request, package_uuid, pathway_uuid, e: Form[CreateEdge]):
|
|||||||
products.append(
|
products.append(
|
||||||
Node.objects.get(
|
Node.objects.get(
|
||||||
pathway=pw,
|
pathway=pw,
|
||||||
default_node_label=CompoundStructure.objects.not_instance_of(*subclasses).
|
default_node_label=CompoundStructure.objects.get(
|
||||||
get(
|
|
||||||
compound__package=p, smiles=stand_pr
|
compound__package=p, smiles=stand_pr
|
||||||
).compound.default_structure,
|
).compound.default_structure,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import nh3
|
|||||||
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.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import QuerySet
|
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from epdb.models import (
|
from epdb.models import (
|
||||||
@ -365,14 +364,6 @@ class PackageManager(object):
|
|||||||
|
|
||||||
groups = GroupManager.get_groups(user)
|
groups = GroupManager.get_groups(user)
|
||||||
|
|
||||||
# EDIT START
|
|
||||||
|
|
||||||
if package.classification_level == Package.Classification.SECRET:
|
|
||||||
if package.data_pool not in groups:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# EDIT END
|
|
||||||
|
|
||||||
perms = {"all": ["all"], "write": ["all", "write"], "read": ["all", "write", "read"]}
|
perms = {"all": ["all"], "write": ["all", "write"], "read": ["all", "write", "read"]}
|
||||||
|
|
||||||
valid_perms = perms.get(permission)
|
valid_perms = perms.get(permission)
|
||||||
@ -415,7 +406,6 @@ class PackageManager(object):
|
|||||||
try:
|
try:
|
||||||
p = Package.objects.get(uuid=package_id)
|
p = Package.objects.get(uuid=package_id)
|
||||||
if PackageManager.readable(user, p):
|
if PackageManager.readable(user, p):
|
||||||
p = PackageManager.check_package_classification(user, p)
|
|
||||||
return p
|
return p
|
||||||
else:
|
else:
|
||||||
# FIXME: use custom exception to be translatable to 403 in API
|
# FIXME: use custom exception to be translatable to 403 in API
|
||||||
@ -425,37 +415,6 @@ class PackageManager(object):
|
|||||||
except Package.DoesNotExist:
|
except Package.DoesNotExist:
|
||||||
raise ValueError("Package with ID {} does not exist!".format(package_id))
|
raise ValueError("Package with ID {} does not exist!".format(package_id))
|
||||||
|
|
||||||
# EDIT START
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def check_package_classification(user, pack: Package):
|
|
||||||
if pack.classification_level == Package.Classification.SECRET:
|
|
||||||
if pack.data_pool.user_member.filter(id=user.id).exists():
|
|
||||||
return pack
|
|
||||||
|
|
||||||
raise ValueError("Package is secret and not accessible to user!")
|
|
||||||
|
|
||||||
else:
|
|
||||||
return pack
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def check_package_classifications(user, package_qs: QuerySet[Package]):
|
|
||||||
non_secret = package_qs.exclude(classification_level=Package.Classification.SECRET)
|
|
||||||
secret = package_qs.filter(classification_level=Package.Classification.SECRET)
|
|
||||||
|
|
||||||
# TODO we should be able to do via the db
|
|
||||||
accessible_secret = []
|
|
||||||
|
|
||||||
for s_package in secret:
|
|
||||||
if s_package.data_pool.user_member.filter(id=user.id).exists():
|
|
||||||
accessible_secret.append(s_package.pk)
|
|
||||||
|
|
||||||
# Cannot combine a unique query with a non-unique query -> we have to call distinct
|
|
||||||
return Package.objects.filter(pk__in=accessible_secret).distinct() | non_secret.distinct()
|
|
||||||
|
|
||||||
# EDIT END
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all_readable_packages(user, include_reviewed=False):
|
def get_all_readable_packages(user, include_reviewed=False):
|
||||||
# UserPermission only exists if at least read is granted...
|
# UserPermission only exists if at least read is granted...
|
||||||
@ -482,10 +441,6 @@ class PackageManager(object):
|
|||||||
|
|
||||||
qs = qs.distinct()
|
qs = qs.distinct()
|
||||||
|
|
||||||
# EDIT START
|
|
||||||
qs = PackageManager.check_package_classifications(user, qs)
|
|
||||||
# EDIT END
|
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -532,12 +487,12 @@ class PackageManager(object):
|
|||||||
|
|
||||||
qs = qs.distinct()
|
qs = qs.distinct()
|
||||||
|
|
||||||
# EDIT START
|
|
||||||
qs = PackageManager.check_package_classifications(user, qs)
|
|
||||||
# EDIT END
|
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_packages():
|
||||||
|
return Package.objects.all()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create_package(current_user, name: str, description: str = None):
|
def create_package(current_user, name: str, description: str = None):
|
||||||
@ -641,25 +596,6 @@ class PackageManager(object):
|
|||||||
else:
|
else:
|
||||||
pack.reviewed = False
|
pack.reviewed = False
|
||||||
|
|
||||||
# EDIT START
|
|
||||||
if data.get("classification"):
|
|
||||||
if data["classification"] == "INTERNAL":
|
|
||||||
pack.classification = Package.Classification.RESTRICTED
|
|
||||||
elif data["classification"] == "RESTRICTED":
|
|
||||||
pack.classification = Package.Classification.RESTRICTED
|
|
||||||
elif data["classification"] == "SECRET":
|
|
||||||
pack.classification = Package.Classification.SECRET
|
|
||||||
|
|
||||||
if not "datapool" in data:
|
|
||||||
raise ValueError("Missing datapool in package")
|
|
||||||
|
|
||||||
g = Group.objects.get(uuid=data["datapool"].split('/')[-1])
|
|
||||||
pack.data_pool = g
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Invalid classification {data['classification']}")
|
|
||||||
|
|
||||||
# EDIT END
|
|
||||||
|
|
||||||
pack.description = data["description"]
|
pack.description = data["description"]
|
||||||
pack.save()
|
pack.save()
|
||||||
|
|
||||||
@ -745,13 +681,7 @@ class PackageManager(object):
|
|||||||
default_structure = None
|
default_structure = None
|
||||||
|
|
||||||
for structure in compound["structures"]:
|
for structure in compound["structures"]:
|
||||||
if structure.get("pesLink"):
|
|
||||||
from bayer.models import PESStructure
|
|
||||||
struc = PESStructure()
|
|
||||||
struc.pes_link = structure["pesLink"]
|
|
||||||
else:
|
|
||||||
struc = CompoundStructure()
|
struc = CompoundStructure()
|
||||||
|
|
||||||
# struc.object_url = Command.get_id(structure, keep_ids)
|
# struc.object_url = Command.get_id(structure, keep_ids)
|
||||||
struc.compound = comp
|
struc.compound = comp
|
||||||
struc.uuid = UUID(structure["id"].split("/")[-1]) if keep_ids else uuid4()
|
struc.uuid = UUID(structure["id"].split("/")[-1]) if keep_ids else uuid4()
|
||||||
|
|||||||
@ -99,11 +99,15 @@ class Command(BaseCommand):
|
|||||||
new_license.image_link = f"https://licensebuttons.net/l/{cc_string}/4.0/88x31.png"
|
new_license.image_link = f"https://licensebuttons.net/l/{cc_string}/4.0/88x31.png"
|
||||||
new_license.save()
|
new_license.save()
|
||||||
|
|
||||||
def import_package(self, data, owner):
|
def import_package(self, data, owner, all_envipath_user_group):
|
||||||
return PackageManager.import_legacy_package(
|
p = PackageManager.import_legacy_package(
|
||||||
data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True
|
data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PackageManager.grant_read(owner, p, all_envipath_user_group)
|
||||||
|
|
||||||
|
return p
|
||||||
|
|
||||||
def create_default_setting(self, owner, packages):
|
def create_default_setting(self, owner, packages):
|
||||||
s = SettingManager.create_setting(
|
s = SettingManager.create_setting(
|
||||||
owner,
|
owner,
|
||||||
@ -198,7 +202,7 @@ class Command(BaseCommand):
|
|||||||
s.BASE_DIR / "fixtures" / "packages" / "2025-07-18" / p, encoding="utf-8"
|
s.BASE_DIR / "fixtures" / "packages" / "2025-07-18" / p, encoding="utf-8"
|
||||||
).read()
|
).read()
|
||||||
)
|
)
|
||||||
imported_package = self.import_package(package_data, admin)
|
imported_package = self.import_package(package_data, admin, g)
|
||||||
mapping[p.replace(".json", "")] = imported_package
|
mapping[p.replace(".json", "")] = imported_package
|
||||||
|
|
||||||
setting = self.create_default_setting(admin, [mapping["EAWAG-BBD"]])
|
setting = self.create_default_setting(admin, [mapping["EAWAG-BBD"]])
|
||||||
|
|||||||
594
epdb/migrations/0001_initial.py
Normal file
594
epdb/migrations/0001_initial.py
Normal file
@ -0,0 +1,594 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-07-22 20:58
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import django.contrib.auth.models
|
||||||
|
import django.contrib.auth.validators
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
import uuid
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Compound',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EPModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Permission',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('permission', models.CharField(choices=[('read', 'Read'), ('write', 'Write'), ('all', 'All')], max_length=32)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='License',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('link', models.URLField(verbose_name='link')),
|
||||||
|
('image_link', models.URLField(verbose_name='Image link')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Rule',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='User',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||||
|
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||||
|
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||||
|
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||||
|
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||||
|
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||||
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||||
|
('email', models.EmailField(max_length=254, unique=True)),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||||
|
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'user',
|
||||||
|
'verbose_name_plural': 'users',
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
managers=[
|
||||||
|
('objects', django.contrib.auth.models.UserManager()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='APIToken',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('hashed_key', models.CharField(max_length=128, unique=True)),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('expires_at', models.DateTimeField(blank=True, default=datetime.datetime(2025, 10, 20, 20, 58, 48, 351675, tzinfo=datetime.timezone.utc), null=True)),
|
||||||
|
('name', models.CharField(blank=True, help_text='Optional name for the token', max_length=100)),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CompoundStructure',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('smiles', models.TextField(verbose_name='SMILES')),
|
||||||
|
('canonical_smiles', models.TextField(verbose_name='Canonical SMILES')),
|
||||||
|
('inchikey', models.TextField(max_length=27, verbose_name='InChIKey')),
|
||||||
|
('normalized_structure', models.BooleanField(default=False)),
|
||||||
|
('compound', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.compound')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compound',
|
||||||
|
name='default_structure',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='compound_default_structure', to='epdb.compoundstructure', verbose_name='Default Structure'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Edge',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EnviFormer',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
('threshold', models.FloatField(default=0.5)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PluginModel',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RuleBaseRelativeReasoning',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Group',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(verbose_name='Group name')),
|
||||||
|
('public', models.BooleanField(default=False, verbose_name='Public Group')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('group_member', models.ManyToManyField(related_name='groups_in_group', to='epdb.group', verbose_name='Group member')),
|
||||||
|
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Group Owner')),
|
||||||
|
('user_member', models.ManyToManyField(related_name='users_in_group', to=settings.AUTH_USER_MODEL, verbose_name='User members')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='default_group',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_group', to='epdb.group', verbose_name='Default Group'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Node',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('depth', models.IntegerField(verbose_name='Node depth')),
|
||||||
|
('default_node_label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='default_node_structure', to='epdb.compoundstructure', verbose_name='Default Node Label')),
|
||||||
|
('node_labels', models.ManyToManyField(related_name='node_structures', to='epdb.compoundstructure', verbose_name='All Node Labels')),
|
||||||
|
('out_edges', models.ManyToManyField(to='epdb.edge', verbose_name='Outgoing Edges')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='end_nodes',
|
||||||
|
field=models.ManyToManyField(related_name='edge_products', to='epdb.node', verbose_name='End Nodes'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='start_nodes',
|
||||||
|
field=models.ManyToManyField(related_name='edge_educts', to='epdb.node', verbose_name='Start Nodes'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Package',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('reviewed', models.BooleanField(default=False, verbose_name='Reviewstatus')),
|
||||||
|
('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.license', verbose_name='License')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='epmodel',
|
||||||
|
name='package',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compound',
|
||||||
|
name='package',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='default_package',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.package', verbose_name='Default Package'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SequentialRule',
|
||||||
|
fields=[
|
||||||
|
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.rule',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SimpleRule',
|
||||||
|
fields=[
|
||||||
|
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.rule',),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rule',
|
||||||
|
name='package',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rule',
|
||||||
|
name='polymorphic_ctype',
|
||||||
|
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Pathway',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='node',
|
||||||
|
name='pathway',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='pathway',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Reaction',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('multi_step', models.BooleanField(verbose_name='Multistep Reaction')),
|
||||||
|
('medline_references', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), null=True, size=None, verbose_name='Medline References')),
|
||||||
|
('educts', models.ManyToManyField(related_name='reaction_educts', to='epdb.compoundstructure', verbose_name='Educts')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')),
|
||||||
|
('products', models.ManyToManyField(related_name='reaction_products', to='epdb.compoundstructure', verbose_name='Products')),
|
||||||
|
('rules', models.ManyToManyField(related_name='reaction_rule', to='epdb.rule', verbose_name='Rule')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='edge_label',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.reaction', verbose_name='Edge label'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Scenario',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('scenario_date', models.CharField(default='No date', max_length=256)),
|
||||||
|
('scenario_type', models.CharField(default='Not specified', max_length=256)),
|
||||||
|
('additional_information', models.JSONField(verbose_name='Additional Information')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')),
|
||||||
|
('parent', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.scenario')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rule',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='reaction',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pathway',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='node',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compoundstructure',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compound',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Setting',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('public', models.BooleanField(default=False)),
|
||||||
|
('global_default', models.BooleanField(default=False)),
|
||||||
|
('max_depth', models.IntegerField(default=5, verbose_name='Setting Max Depth')),
|
||||||
|
('max_nodes', models.IntegerField(default=30, verbose_name='Setting Max Number of Nodes')),
|
||||||
|
('model_threshold', models.FloatField(blank=True, default=0.25, null=True, verbose_name='Setting Model Threshold')),
|
||||||
|
('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.epmodel', verbose_name='Setting EPModel')),
|
||||||
|
('rule_packages', models.ManyToManyField(blank=True, related_name='setting_rule_packages', to='epdb.package', verbose_name='Setting Rule Packages')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pathway',
|
||||||
|
name='setting',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Setting'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='default_setting',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.setting', verbose_name='The users default settings'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MLRelativeReasoning',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
('threshold', models.FloatField(default=0.5)),
|
||||||
|
('model_status', models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL')),
|
||||||
|
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('data_packages', models.ManyToManyField(related_name='data_packages', to='epdb.package', verbose_name='Data Packages')),
|
||||||
|
('eval_packages', models.ManyToManyField(related_name='eval_packages', to='epdb.package', verbose_name='Evaluation Packages')),
|
||||||
|
('rule_packages', models.ManyToManyField(related_name='rule_packages', to='epdb.package', verbose_name='Rule Packages')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ApplicabilityDomain',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('num_neighbours', models.FloatField(default=5)),
|
||||||
|
('reliability_threshold', models.FloatField(default=0.5)),
|
||||||
|
('local_compatibilty_threshold', models.FloatField(default=0.5)),
|
||||||
|
('model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.mlrelativereasoning')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SimpleAmbitRule',
|
||||||
|
fields=[
|
||||||
|
('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')),
|
||||||
|
('smirks', models.TextField(verbose_name='SMIRKS')),
|
||||||
|
('reactant_filter_smarts', models.TextField(null=True, verbose_name='Reactant Filter SMARTS')),
|
||||||
|
('product_filter_smarts', models.TextField(null=True, verbose_name='Product Filter SMARTS')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.simplerule',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SimpleRDKitRule',
|
||||||
|
fields=[
|
||||||
|
('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')),
|
||||||
|
('reaction_smarts', models.TextField(verbose_name='SMIRKS')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.simplerule',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SequentialRuleOrdering',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('order_index', models.IntegerField()),
|
||||||
|
('sequential_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.sequentialrule')),
|
||||||
|
('simple_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.simplerule')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sequentialrule',
|
||||||
|
name='simple_rules',
|
||||||
|
field=models.ManyToManyField(through='epdb.SequentialRuleOrdering', to='epdb.simplerule', verbose_name='Simple rules'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ParallelRule',
|
||||||
|
fields=[
|
||||||
|
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
||||||
|
('simple_rules', models.ManyToManyField(to='epdb.simplerule', verbose_name='Simple rules')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.rule',),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='compound',
|
||||||
|
unique_together={('uuid', 'package')},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GroupPackagePermission',
|
||||||
|
fields=[
|
||||||
|
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
||||||
|
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.group', verbose_name='Permission to')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Permission on')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('package', 'group')},
|
||||||
|
},
|
||||||
|
bases=('epdb.permission',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserPackagePermission',
|
||||||
|
fields=[
|
||||||
|
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Permission on')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('package', 'user')},
|
||||||
|
},
|
||||||
|
bases=('epdb.permission',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserSettingPermission',
|
||||||
|
fields=[
|
||||||
|
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
||||||
|
('setting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Permission on')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('setting', 'user')},
|
||||||
|
},
|
||||||
|
bases=('epdb.permission',),
|
||||||
|
),
|
||||||
|
]
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,128 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-08-25 18:07
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
import uuid
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
('epdb', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ExternalDatabase',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||||
|
('name', models.CharField(max_length=100, unique=True, verbose_name='Database Name')),
|
||||||
|
('full_name', models.CharField(blank=True, max_length=255, verbose_name='Full Database Name')),
|
||||||
|
('description', models.TextField(blank=True, verbose_name='Description')),
|
||||||
|
('base_url', models.URLField(blank=True, null=True, verbose_name='Base URL')),
|
||||||
|
('url_pattern', models.CharField(blank=True, help_text="URL pattern with {id} placeholder, e.g., 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'", max_length=500, verbose_name='URL Pattern')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Is Active')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'External Database',
|
||||||
|
'verbose_name_plural': 'External Databases',
|
||||||
|
'db_table': 'epdb_external_database',
|
||||||
|
'ordering': ['name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='apitoken',
|
||||||
|
options={'ordering': ['-created'], 'verbose_name': 'API Token', 'verbose_name_plural': 'API Tokens'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='edge',
|
||||||
|
options={},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='edge',
|
||||||
|
name='polymorphic_ctype',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='is_active',
|
||||||
|
field=models.BooleanField(default=True, help_text='Whether this token is active'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='modified',
|
||||||
|
field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applicabilitydomain',
|
||||||
|
name='functional_groups',
|
||||||
|
field=models.JSONField(blank=True, default=dict, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mlrelativereasoning',
|
||||||
|
name='app_domain',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='created',
|
||||||
|
field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='expires_at',
|
||||||
|
field=models.DateTimeField(blank=True, help_text='Token expiration time (null for no expiration)', null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='hashed_key',
|
||||||
|
field=models.CharField(help_text='SHA-256 hash of the token key', max_length=128, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(help_text='Descriptive name for this token', max_length=100),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(help_text='User who owns this token', on_delete=django.db.models.deletion.CASCADE, related_name='api_tokens', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='applicabilitydomain',
|
||||||
|
name='num_neighbours',
|
||||||
|
field=models.IntegerField(default=5),
|
||||||
|
),
|
||||||
|
migrations.AlterModelTable(
|
||||||
|
name='apitoken',
|
||||||
|
table='epdb_api_token',
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ExternalIdentifier',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||||
|
('object_id', models.IntegerField()),
|
||||||
|
('identifier_value', models.CharField(max_length=255, verbose_name='Identifier Value')),
|
||||||
|
('url', models.URLField(blank=True, null=True, verbose_name='Direct URL')),
|
||||||
|
('is_primary', models.BooleanField(default=False, help_text='Mark this as the primary identifier for this database', verbose_name='Is Primary')),
|
||||||
|
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
||||||
|
('database', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.externaldatabase', verbose_name='External Database')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'External Identifier',
|
||||||
|
'verbose_name_plural': 'External Identifiers',
|
||||||
|
'db_table': 'epdb_external_identifier',
|
||||||
|
'indexes': [models.Index(fields=['content_type', 'object_id'], name='epdb_extern_content_b76813_idx'), models.Index(fields=['database', 'identifier_value'], name='epdb_extern_databas_486422_idx')],
|
||||||
|
'unique_together': {('content_type', 'object_id', 'database', 'identifier_value')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,228 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-08-26 17:05
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def populate_url(apps, schema_editor):
|
||||||
|
MODELS = [
|
||||||
|
'User',
|
||||||
|
'Group',
|
||||||
|
'Package',
|
||||||
|
'Compound',
|
||||||
|
'CompoundStructure',
|
||||||
|
'Pathway',
|
||||||
|
'Edge',
|
||||||
|
'Node',
|
||||||
|
'Reaction',
|
||||||
|
'SimpleAmbitRule',
|
||||||
|
'SimpleRDKitRule',
|
||||||
|
'ParallelRule',
|
||||||
|
'SequentialRule',
|
||||||
|
'Scenario',
|
||||||
|
'Setting',
|
||||||
|
'MLRelativeReasoning',
|
||||||
|
'EnviFormer',
|
||||||
|
'ApplicabilityDomain',
|
||||||
|
]
|
||||||
|
for model in MODELS:
|
||||||
|
obj_cls = apps.get_model("epdb", model)
|
||||||
|
for obj in obj_cls.objects.all():
|
||||||
|
obj.url = assemble_url(obj)
|
||||||
|
if obj.url is None:
|
||||||
|
raise ValueError(f"Could not assemble url for {obj}")
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
|
||||||
|
def assemble_url(obj):
|
||||||
|
from django.conf import settings as s
|
||||||
|
match obj.__class__.__name__:
|
||||||
|
case 'User':
|
||||||
|
return '{}/user/{}'.format(s.SERVER_URL, obj.uuid)
|
||||||
|
case 'Group':
|
||||||
|
return '{}/group/{}'.format(s.SERVER_URL, obj.uuid)
|
||||||
|
case 'Package':
|
||||||
|
return '{}/package/{}'.format(s.SERVER_URL, obj.uuid)
|
||||||
|
case 'Compound':
|
||||||
|
return '{}/compound/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'CompoundStructure':
|
||||||
|
return '{}/structure/{}'.format(obj.compound.url, obj.uuid)
|
||||||
|
case 'SimpleAmbitRule':
|
||||||
|
return '{}/simple-ambit-rule/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'SimpleRDKitRule':
|
||||||
|
return '{}/simple-rdkit-rule/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'ParallelRule':
|
||||||
|
return '{}/parallel-rule/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'SequentialRule':
|
||||||
|
return '{}/sequential-rule/{}'.format(obj.compound.url, obj.uuid)
|
||||||
|
case 'Reaction':
|
||||||
|
return '{}/reaction/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'Pathway':
|
||||||
|
return '{}/pathway/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'Node':
|
||||||
|
return '{}/node/{}'.format(obj.pathway.url, obj.uuid)
|
||||||
|
case 'Edge':
|
||||||
|
return '{}/edge/{}'.format(obj.pathway.url, obj.uuid)
|
||||||
|
case 'MLRelativeReasoning':
|
||||||
|
return '{}/model/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'EnviFormer':
|
||||||
|
return '{}/model/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'ApplicabilityDomain':
|
||||||
|
return '{}/model/{}/applicability-domain/{}'.format(obj.model.package.url, obj.model.uuid, obj.uuid)
|
||||||
|
case 'Scenario':
|
||||||
|
return '{}/scenario/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'Setting':
|
||||||
|
return '{}/setting/{}'.format(s.SERVER_URL, obj.uuid)
|
||||||
|
case _:
|
||||||
|
raise ValueError(f"Unknown model {obj.__class__.__name__}")
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('epdb', '0002_externaldatabase_alter_apitoken_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applicabilitydomain',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compound',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compoundstructure',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='epmodel',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='group',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='node',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='package',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pathway',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='reaction',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rule',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='scenario',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='setting',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
|
||||||
|
migrations.RunPython(populate_url, reverse_code=migrations.RunPython.noop),
|
||||||
|
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='applicabilitydomain',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='compound',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='compoundstructure',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='edge',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='epmodel',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='group',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='node',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='package',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='pathway',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='reaction',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rule',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='scenario',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='setting',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-09-09 09:21
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('epdb', '0001_squashed_0003_applicabilitydomain_url_compound_url_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='mlrelativereasoning',
|
||||||
|
options={},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='mlrelativereasoning',
|
||||||
|
name='data_packages',
|
||||||
|
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to='epdb.package', verbose_name='Data Packages'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='mlrelativereasoning',
|
||||||
|
name='eval_packages',
|
||||||
|
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_eval_packages', to='epdb.package', verbose_name='Evaluation Packages'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='mlrelativereasoning',
|
||||||
|
name='rule_packages',
|
||||||
|
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_rule_packages', to='epdb.package', verbose_name='Rule Packages'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RuleBasedRelativeReasoning',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
('threshold', models.FloatField(default=0.5)),
|
||||||
|
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('model_status', models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL')),
|
||||||
|
('min_count', models.IntegerField(default=10)),
|
||||||
|
('max_count', models.IntegerField(default=0)),
|
||||||
|
('app_domain', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain')),
|
||||||
|
('data_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to='epdb.package', verbose_name='Data Packages')),
|
||||||
|
('eval_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_eval_packages', to='epdb.package', verbose_name='Evaluation Packages')),
|
||||||
|
('rule_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_rule_packages', to='epdb.package', verbose_name='Rule Packages')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='RuleBaseRelativeReasoning',
|
||||||
|
),
|
||||||
|
]
|
||||||
18
epdb/migrations/0005_alter_group_group_member.py
Normal file
18
epdb/migrations/0005_alter_group_group_member.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-09-11 06:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('epdb', '0004_alter_mlrelativereasoning_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='group',
|
||||||
|
name='group_member',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='groups_in_group', to='epdb.group', verbose_name='Group member'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-09-18 06:42
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('epdb', '0005_alter_group_group_member'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mlrelativereasoning',
|
||||||
|
name='multigen_eval',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rulebasedrelativereasoning',
|
||||||
|
name='multigen_eval',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-10-07 08:19
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('epdb', '0006_mlrelativereasoning_multigen_eval_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='enviformer',
|
||||||
|
options={},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='enviformer',
|
||||||
|
name='app_domain',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='enviformer',
|
||||||
|
name='data_packages',
|
||||||
|
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to='epdb.package', verbose_name='Data Packages'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='enviformer',
|
||||||
|
name='eval_packages',
|
||||||
|
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_eval_packages', to='epdb.package', verbose_name='Evaluation Packages'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='enviformer',
|
||||||
|
name='eval_results',
|
||||||
|
field=models.JSONField(blank=True, default=dict, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='enviformer',
|
||||||
|
name='model_status',
|
||||||
|
field=models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='enviformer',
|
||||||
|
name='multigen_eval',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='enviformer',
|
||||||
|
name='rule_packages',
|
||||||
|
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_rule_packages', to='epdb.package', verbose_name='Rule Packages'),
|
||||||
|
),
|
||||||
|
]
|
||||||
64
epdb/migrations/0008_enzymelink.py
Normal file
64
epdb/migrations/0008_enzymelink.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-10-10 06:58
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0007_alter_enviformer_options_enviformer_app_domain_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="EnzymeLink",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
model_utils.fields.AutoCreatedField(
|
||||||
|
default=django.utils.timezone.now, editable=False, verbose_name="created"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
model_utils.fields.AutoLastModifiedField(
|
||||||
|
default=django.utils.timezone.now, editable=False, verbose_name="modified"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"uuid",
|
||||||
|
models.UUIDField(
|
||||||
|
default=uuid.uuid4, unique=True, verbose_name="UUID of this object"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.TextField(default="no name", verbose_name="Name")),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
models.TextField(default="no description", verbose_name="Descriptions"),
|
||||||
|
),
|
||||||
|
("url", models.TextField(null=True, unique=True, verbose_name="URL")),
|
||||||
|
("kv", models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
("ec_number", models.TextField(verbose_name="EC Number")),
|
||||||
|
("classification_level", models.IntegerField(verbose_name="Classification Level")),
|
||||||
|
("linking_method", models.TextField(verbose_name="Linking Method")),
|
||||||
|
("edge_evidence", models.ManyToManyField(to="epdb.edge")),
|
||||||
|
("reaction_evidence", models.ManyToManyField(to="epdb.reaction")),
|
||||||
|
(
|
||||||
|
"rule",
|
||||||
|
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="epdb.rule"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
66
epdb/migrations/0009_joblog.py
Normal file
66
epdb/migrations/0009_joblog.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-10-27 09:39
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0008_enzymelink"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="JobLog",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
model_utils.fields.AutoCreatedField(
|
||||||
|
default=django.utils.timezone.now, editable=False, verbose_name="created"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
model_utils.fields.AutoLastModifiedField(
|
||||||
|
default=django.utils.timezone.now, editable=False, verbose_name="modified"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("task_id", models.UUIDField(unique=True)),
|
||||||
|
("job_name", models.TextField()),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("INITIAL", "Initial"),
|
||||||
|
("SUCCESS", "Success"),
|
||||||
|
("FAILURE", "Failure"),
|
||||||
|
("REVOKED", "Revoked"),
|
||||||
|
("IGNORED", "Ignored"),
|
||||||
|
],
|
||||||
|
default="INITIAL",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("done_at", models.DateTimeField(blank=True, default=None, null=True)),
|
||||||
|
("task_result", models.TextField(blank=True, default=None, null=True)),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
epdb/migrations/0010_license_cc_string.py
Normal file
18
epdb/migrations/0010_license_cc_string.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-11 14:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0009_joblog"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="license",
|
||||||
|
name="cc_string",
|
||||||
|
field=models.TextField(default="by-nc-sa", verbose_name="CC string"),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
59
epdb/migrations/0011_auto_20251111_1413.py
Normal file
59
epdb/migrations/0011_auto_20251111_1413.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-11 14:13
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from django.contrib.postgres.aggregates import ArrayAgg
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db.models import Min
|
||||||
|
|
||||||
|
|
||||||
|
def set_cc(apps, schema_editor):
|
||||||
|
License = apps.get_model("epdb", "License")
|
||||||
|
|
||||||
|
# For all existing licenses extract cc_string from link
|
||||||
|
for license in License.objects.all():
|
||||||
|
pattern = r"/licenses/([^/]+)/4\.0"
|
||||||
|
match = re.search(pattern, license.link)
|
||||||
|
if match:
|
||||||
|
license.cc_string = match.group(1)
|
||||||
|
license.save()
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Could not find license for {license.link}")
|
||||||
|
|
||||||
|
# Ensure we have all licenses
|
||||||
|
cc_strings = ["by", "by-nc", "by-nc-nd", "by-nc-sa", "by-nd", "by-sa"]
|
||||||
|
for cc_string in cc_strings:
|
||||||
|
if not License.objects.filter(cc_string=cc_string).exists():
|
||||||
|
new_license = License()
|
||||||
|
new_license.cc_string = cc_string
|
||||||
|
new_license.link = f"https://creativecommons.org/licenses/{cc_string}/4.0/"
|
||||||
|
new_license.image_link = f"https://licensebuttons.net/l/{cc_string}/4.0/88x31.png"
|
||||||
|
new_license.save()
|
||||||
|
|
||||||
|
# As we might have existing Licenses representing the same License,
|
||||||
|
# get min pk and all pks as a list
|
||||||
|
license_lookup_qs = License.objects.values("cc_string").annotate(
|
||||||
|
lowest_pk=Min("id"), all_pks=ArrayAgg("id", order_by=("id",))
|
||||||
|
)
|
||||||
|
|
||||||
|
license_lookup = {
|
||||||
|
row["cc_string"]: (row["lowest_pk"], row["all_pks"]) for row in license_lookup_qs
|
||||||
|
}
|
||||||
|
|
||||||
|
Packages = apps.get_model("epdb", "Package")
|
||||||
|
|
||||||
|
for k, v in license_lookup.items():
|
||||||
|
# Set min pk to all packages pointing to any of the duplicates
|
||||||
|
Packages.objects.filter(pk__in=v[1]).update(license_id=v[0])
|
||||||
|
# remove the min pk from "other" pks as we use them for deletion
|
||||||
|
v[1].remove(v[0])
|
||||||
|
# Delete redundant License objects
|
||||||
|
License.objects.filter(pk__in=v[1]).delete()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0010_license_cc_string"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [migrations.RunPython(set_cc)]
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-12-02 13:09
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0011_auto_20251111_1413"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="node",
|
||||||
|
name="stereo_removed",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="pathway",
|
||||||
|
name="predicted",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
25
epdb/migrations/0013_setting_expansion_schema.py
Normal file
25
epdb/migrations/0013_setting_expansion_schema.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-12-14 11:30
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0012_node_stereo_removed_pathway_predicted"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="setting",
|
||||||
|
name="expansion_schema",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("BFS", "Breadth First Search"),
|
||||||
|
("DFS", "Depth First Search"),
|
||||||
|
("GREEDY", "Greedy"),
|
||||||
|
],
|
||||||
|
default="BFS",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-12-14 16:02
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0013_setting_expansion_schema"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="setting",
|
||||||
|
old_name="expansion_schema",
|
||||||
|
new_name="expansion_scheme",
|
||||||
|
),
|
||||||
|
]
|
||||||
17
epdb/migrations/0015_user_is_reviewer.py
Normal file
17
epdb/migrations/0015_user_is_reviewer.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-01-19 19:26
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0014_rename_expansion_schema_setting_expansion_scheme"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="is_reviewer",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
179
epdb/migrations/0016_remove_enviformer_model_status_and_more.py
Normal file
179
epdb/migrations/0016_remove_enviformer_model_status_and_more.py
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-12 09:38
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0015_user_is_reviewer"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="enviformer",
|
||||||
|
name="model_status",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="mlrelativereasoning",
|
||||||
|
name="model_status",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="rulebasedrelativereasoning",
|
||||||
|
name="model_status",
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="epmodel",
|
||||||
|
name="model_status",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("INITIAL", "Initial"),
|
||||||
|
("INITIALIZING", "Model is initializing."),
|
||||||
|
("BUILDING", "Model is building."),
|
||||||
|
(
|
||||||
|
"BUILT_NOT_EVALUATED",
|
||||||
|
"Model is built and can be used for predictions, Model is not evaluated yet.",
|
||||||
|
),
|
||||||
|
("EVALUATING", "Model is evaluating"),
|
||||||
|
("FINISHED", "Model has finished building and evaluation."),
|
||||||
|
("ERROR", "Model has failed."),
|
||||||
|
],
|
||||||
|
default="INITIAL",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="enviformer",
|
||||||
|
name="eval_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name="%(app_label)s_%(class)s_eval_packages",
|
||||||
|
to=settings.EPDB_PACKAGE_MODEL,
|
||||||
|
verbose_name="Evaluation Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="enviformer",
|
||||||
|
name="rule_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name="%(app_label)s_%(class)s_rule_packages",
|
||||||
|
to=settings.EPDB_PACKAGE_MODEL,
|
||||||
|
verbose_name="Rule Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="mlrelativereasoning",
|
||||||
|
name="eval_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name="%(app_label)s_%(class)s_eval_packages",
|
||||||
|
to=settings.EPDB_PACKAGE_MODEL,
|
||||||
|
verbose_name="Evaluation Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="mlrelativereasoning",
|
||||||
|
name="rule_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name="%(app_label)s_%(class)s_rule_packages",
|
||||||
|
to=settings.EPDB_PACKAGE_MODEL,
|
||||||
|
verbose_name="Rule Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="rulebasedrelativereasoning",
|
||||||
|
name="eval_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name="%(app_label)s_%(class)s_eval_packages",
|
||||||
|
to=settings.EPDB_PACKAGE_MODEL,
|
||||||
|
verbose_name="Evaluation Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="rulebasedrelativereasoning",
|
||||||
|
name="rule_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name="%(app_label)s_%(class)s_rule_packages",
|
||||||
|
to=settings.EPDB_PACKAGE_MODEL,
|
||||||
|
verbose_name="Rule Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="PropertyPluginModel",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"epmodel_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="epdb.epmodel",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("threshold", models.FloatField(default=0.5)),
|
||||||
|
("eval_results", models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
("multigen_eval", models.BooleanField(default=False)),
|
||||||
|
("plugin_identifier", models.CharField(max_length=255)),
|
||||||
|
(
|
||||||
|
"app_domain",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="epdb.applicabilitydomain",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"data_packages",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name="%(app_label)s_%(class)s_data_packages",
|
||||||
|
to=settings.EPDB_PACKAGE_MODEL,
|
||||||
|
verbose_name="Data Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"eval_packages",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name="%(app_label)s_%(class)s_eval_packages",
|
||||||
|
to=settings.EPDB_PACKAGE_MODEL,
|
||||||
|
verbose_name="Evaluation Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"rule_packages",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name="%(app_label)s_%(class)s_rule_packages",
|
||||||
|
to=settings.EPDB_PACKAGE_MODEL,
|
||||||
|
verbose_name="Rule Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
bases=("epdb.epmodel",),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="setting",
|
||||||
|
name="property_models",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name="settings",
|
||||||
|
to="epdb.propertypluginmodel",
|
||||||
|
verbose_name="Setting Property Models",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name="PluginModel",
|
||||||
|
),
|
||||||
|
]
|
||||||
93
epdb/migrations/0017_additionalinformation.py
Normal file
93
epdb/migrations/0017_additionalinformation.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-20 12:02
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("contenttypes", "0002_remove_content_type_name"),
|
||||||
|
("epdb", "0016_remove_enviformer_model_status_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="AdditionalInformation",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("uuid", models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||||
|
("url", models.TextField(null=True, unique=True, verbose_name="URL")),
|
||||||
|
("kv", models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
("type", models.TextField(verbose_name="Additional Information Type")),
|
||||||
|
("data", models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
("object_id", models.PositiveBigIntegerField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"content_type",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="contenttypes.contenttype",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"package",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to=settings.EPDB_PACKAGE_MODEL,
|
||||||
|
verbose_name="Package",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"scenario",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="scenario_additional_information",
|
||||||
|
to="epdb.scenario",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"indexes": [
|
||||||
|
models.Index(fields=["type"], name="epdb_additi_type_394349_idx"),
|
||||||
|
models.Index(
|
||||||
|
fields=["scenario", "type"], name="epdb_additi_scenari_a59edf_idx"
|
||||||
|
),
|
||||||
|
models.Index(
|
||||||
|
fields=["content_type", "object_id"], name="epdb_additi_content_44d4b4_idx"
|
||||||
|
),
|
||||||
|
models.Index(
|
||||||
|
fields=["scenario", "content_type", "object_id"],
|
||||||
|
name="epdb_additi_scenari_ef2bf5_idx",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
"constraints": [
|
||||||
|
models.CheckConstraint(
|
||||||
|
condition=models.Q(
|
||||||
|
models.Q(("content_type__isnull", True), ("object_id__isnull", True)),
|
||||||
|
models.Q(("content_type__isnull", False), ("object_id__isnull", False)),
|
||||||
|
_connector="OR",
|
||||||
|
),
|
||||||
|
name="ck_addinfo_gfk_pair",
|
||||||
|
),
|
||||||
|
models.CheckConstraint(
|
||||||
|
condition=models.Q(
|
||||||
|
("scenario__isnull", False),
|
||||||
|
("content_type__isnull", False),
|
||||||
|
_connector="OR",
|
||||||
|
),
|
||||||
|
name="ck_addinfo_not_both_null",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
132
epdb/migrations/0018_auto_20260220_1203.py
Normal file
132
epdb/migrations/0018_auto_20260220_1203.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-20 12:03
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def get_additional_information(scenario):
|
||||||
|
from envipy_additional_information import registry
|
||||||
|
from envipy_additional_information.parsers import TypeOfAerationParser
|
||||||
|
|
||||||
|
for k, vals in scenario.additional_information.items():
|
||||||
|
if k == "enzyme":
|
||||||
|
continue
|
||||||
|
|
||||||
|
if k == "SpikeConentration":
|
||||||
|
k = "SpikeConcentration"
|
||||||
|
|
||||||
|
if k == "AerationType":
|
||||||
|
k = "TypeOfAeration"
|
||||||
|
|
||||||
|
for v in vals:
|
||||||
|
# Per default additional fields are ignored
|
||||||
|
MAPPING = {c.__name__: c for c in registry.list_models().values()}
|
||||||
|
try:
|
||||||
|
inst = MAPPING[k](**v)
|
||||||
|
except Exception:
|
||||||
|
if k == "TypeOfAeration":
|
||||||
|
toa = TypeOfAerationParser()
|
||||||
|
inst = toa.from_string(v["type"])
|
||||||
|
|
||||||
|
# Add uuid to uniquely identify objects for manipulation
|
||||||
|
if "uuid" in v:
|
||||||
|
inst.__dict__["uuid"] = v["uuid"]
|
||||||
|
|
||||||
|
yield inst
|
||||||
|
|
||||||
|
|
||||||
|
def forward_func(apps, schema_editor):
|
||||||
|
Scenario = apps.get_model("epdb", "Scenario")
|
||||||
|
ContentType = apps.get_model("contenttypes", "ContentType")
|
||||||
|
AdditionalInformation = apps.get_model("epdb", "AdditionalInformation")
|
||||||
|
|
||||||
|
bulk = []
|
||||||
|
related = []
|
||||||
|
ctype = {o.model: o for o in ContentType.objects.all()}
|
||||||
|
parents = Scenario.objects.prefetch_related(
|
||||||
|
"compound_set",
|
||||||
|
"compoundstructure_set",
|
||||||
|
"reaction_set",
|
||||||
|
"rule_set",
|
||||||
|
"pathway_set",
|
||||||
|
"node_set",
|
||||||
|
"edge_set",
|
||||||
|
).filter(parent__isnull=True)
|
||||||
|
|
||||||
|
for i, scenario in enumerate(parents):
|
||||||
|
print(f"{i + 1}/{len(parents)}", end="\r")
|
||||||
|
if scenario.parent is not None:
|
||||||
|
related.append(scenario.parent)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for ai in get_additional_information(scenario):
|
||||||
|
bulk.append(
|
||||||
|
AdditionalInformation(
|
||||||
|
package=scenario.package,
|
||||||
|
scenario=scenario,
|
||||||
|
type=ai.__class__.__name__,
|
||||||
|
data=ai.model_dump(mode="json"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\n", len(bulk))
|
||||||
|
|
||||||
|
related = Scenario.objects.prefetch_related(
|
||||||
|
"compound_set",
|
||||||
|
"compoundstructure_set",
|
||||||
|
"reaction_set",
|
||||||
|
"rule_set",
|
||||||
|
"pathway_set",
|
||||||
|
"node_set",
|
||||||
|
"edge_set",
|
||||||
|
).filter(parent__isnull=False)
|
||||||
|
|
||||||
|
for i, scenario in enumerate(related):
|
||||||
|
print(f"{i + 1}/{len(related)}", end="\r")
|
||||||
|
parent = scenario.parent
|
||||||
|
# Check to which objects this scenario is attached to
|
||||||
|
for ai in get_additional_information(scenario):
|
||||||
|
rel_objs = [
|
||||||
|
"compound",
|
||||||
|
"compoundstructure",
|
||||||
|
"reaction",
|
||||||
|
"rule",
|
||||||
|
"pathway",
|
||||||
|
"node",
|
||||||
|
"edge",
|
||||||
|
]
|
||||||
|
for rel_obj in rel_objs:
|
||||||
|
for o in getattr(scenario, f"{rel_obj}_set").all():
|
||||||
|
bulk.append(
|
||||||
|
AdditionalInformation(
|
||||||
|
package=scenario.package,
|
||||||
|
scenario=parent,
|
||||||
|
type=ai.__class__.__name__,
|
||||||
|
data=ai.model_dump(mode="json"),
|
||||||
|
content_type=ctype[rel_obj],
|
||||||
|
object_id=o.pk,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Start creating additional information objects...")
|
||||||
|
AdditionalInformation.objects.bulk_create(bulk)
|
||||||
|
print("Done!")
|
||||||
|
print(len(bulk))
|
||||||
|
|
||||||
|
Scenario.objects.filter(parent__isnull=False).delete()
|
||||||
|
# Call ai save to fix urls
|
||||||
|
ais = AdditionalInformation.objects.all()
|
||||||
|
total = ais.count()
|
||||||
|
|
||||||
|
for i, ai in enumerate(ais):
|
||||||
|
print(f"{i + 1}/{total}", end="\r")
|
||||||
|
ai.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0017_additionalinformation"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(forward_func, reverse_code=migrations.RunPython.noop),
|
||||||
|
]
|
||||||
@ -1,741 +1,20 @@
|
|||||||
# Generated by Django 5.2.7 on 2026-03-06 10:51
|
# Generated by Django 5.2.7 on 2026-02-23 08:45
|
||||||
|
|
||||||
import django.contrib.auth.models
|
from django.db import migrations
|
||||||
import django.contrib.auth.validators
|
|
||||||
import django.contrib.postgres.fields
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
import model_utils.fields
|
|
||||||
import uuid
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('auth', '0012_alter_user_first_name_max_length'),
|
("epdb", "0018_auto_20260220_1203"),
|
||||||
('contenttypes', '0002_remove_content_type_name'),
|
|
||||||
migrations.swappable_dependency(settings.EPDB_PACKAGE_MODEL),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.RemoveField(
|
||||||
name='ApplicabilityDomain',
|
model_name="scenario",
|
||||||
fields=[
|
name="additional_information",
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('num_neighbours', models.IntegerField(default=5)),
|
|
||||||
('reliability_threshold', models.FloatField(default=0.5)),
|
|
||||||
('local_compatibilty_threshold', models.FloatField(default=0.5)),
|
|
||||||
('functional_groups', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.RemoveField(
|
||||||
name='Edge',
|
model_name="scenario",
|
||||||
fields=[
|
name="parent",
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='EPModel',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('model_status', models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL')),
|
|
||||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')),
|
|
||||||
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ExternalDatabase',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
|
||||||
('name', models.CharField(max_length=100, unique=True, verbose_name='Database Name')),
|
|
||||||
('full_name', models.CharField(blank=True, max_length=255, verbose_name='Full Database Name')),
|
|
||||||
('description', models.TextField(blank=True, verbose_name='Description')),
|
|
||||||
('base_url', models.URLField(blank=True, null=True, verbose_name='Base URL')),
|
|
||||||
('url_pattern', models.CharField(blank=True, help_text="URL pattern with {id} placeholder, e.g., 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'", max_length=500, verbose_name='URL Pattern')),
|
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='Is Active')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'External Database',
|
|
||||||
'verbose_name_plural': 'External Databases',
|
|
||||||
'db_table': 'epdb_external_database',
|
|
||||||
'ordering': ['name'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Permission',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('permission', models.CharField(choices=[('read', 'Read'), ('write', 'Write'), ('all', 'All')], max_length=32)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='License',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('cc_string', models.TextField(verbose_name='CC string')),
|
|
||||||
('link', models.URLField(verbose_name='link')),
|
|
||||||
('image_link', models.URLField(verbose_name='Image link')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Rule',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
|
||||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')),
|
|
||||||
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='User',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
|
||||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
|
||||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
|
||||||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
|
||||||
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
|
||||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
|
||||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
|
||||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
|
||||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
|
||||||
('email', models.EmailField(max_length=254, unique=True)),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('is_reviewer', models.BooleanField(default=False)),
|
|
||||||
('default_package', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Default Package')),
|
|
||||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
|
||||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'user',
|
|
||||||
'verbose_name_plural': 'users',
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
managers=[
|
|
||||||
('objects', django.contrib.auth.models.UserManager()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='APIToken',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('hashed_key', models.CharField(help_text='SHA-256 hash of the token key', max_length=128, unique=True)),
|
|
||||||
('expires_at', models.DateTimeField(blank=True, help_text='Token expiration time (null for no expiration)', null=True)),
|
|
||||||
('name', models.CharField(help_text='Descriptive name for this token', max_length=100)),
|
|
||||||
('is_active', models.BooleanField(default=True, help_text='Whether this token is active')),
|
|
||||||
('user', models.ForeignKey(help_text='User who owns this token', on_delete=django.db.models.deletion.CASCADE, related_name='api_tokens', to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'API Token',
|
|
||||||
'verbose_name_plural': 'API Tokens',
|
|
||||||
'db_table': 'epdb_api_token',
|
|
||||||
'ordering': ['-created'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Compound',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
|
||||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CompoundStructure',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
|
||||||
('smiles', models.TextField(verbose_name='SMILES')),
|
|
||||||
('canonical_smiles', models.TextField(verbose_name='Canonical SMILES')),
|
|
||||||
('inchikey', models.TextField(max_length=27, verbose_name='InChIKey')),
|
|
||||||
('normalized_structure', models.BooleanField(default=False)),
|
|
||||||
('compound', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.compound')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='compound',
|
|
||||||
name='default_structure',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='compound_default_structure', to='epdb.compoundstructure', verbose_name='Default Structure'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PropertyPluginModel',
|
|
||||||
fields=[
|
|
||||||
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
|
||||||
('threshold', models.FloatField(default=0.5)),
|
|
||||||
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('multigen_eval', models.BooleanField(default=False)),
|
|
||||||
('plugin_identifier', models.CharField(max_length=255)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('epdb.epmodel',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Group',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('name', models.TextField(verbose_name='Group name')),
|
|
||||||
('public', models.BooleanField(default=False, verbose_name='Public Group')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('group_member', models.ManyToManyField(blank=True, related_name='groups_in_group', to='epdb.group', verbose_name='Group member')),
|
|
||||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Group Owner')),
|
|
||||||
('user_member', models.ManyToManyField(related_name='users_in_group', to=settings.AUTH_USER_MODEL, verbose_name='User members')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='default_group',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_group', to='epdb.group', verbose_name='Default Group'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='JobLog',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('task_id', models.UUIDField(unique=True)),
|
|
||||||
('job_name', models.TextField()),
|
|
||||||
('status', models.CharField(choices=[('INITIAL', 'Initial'), ('SUCCESS', 'Success'), ('FAILURE', 'Failure'), ('REVOKED', 'Revoked'), ('IGNORED', 'Ignored')], default='INITIAL', max_length=20)),
|
|
||||||
('done_at', models.DateTimeField(blank=True, default=None, null=True)),
|
|
||||||
('task_result', models.TextField(blank=True, default=None, null=True)),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Package',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('reviewed', models.BooleanField(default=False, verbose_name='Reviewstatus')),
|
|
||||||
('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.license', verbose_name='License')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'swappable': 'EPDB_PACKAGE_MODEL',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Node',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
|
||||||
('depth', models.IntegerField(verbose_name='Node depth')),
|
|
||||||
('stereo_removed', models.BooleanField(default=False)),
|
|
||||||
('default_node_label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='default_node_structure', to='epdb.compoundstructure', verbose_name='Default Node Label')),
|
|
||||||
('node_labels', models.ManyToManyField(related_name='node_structures', to='epdb.compoundstructure', verbose_name='All Node Labels')),
|
|
||||||
('out_edges', models.ManyToManyField(to='epdb.edge', verbose_name='Outgoing Edges')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='edge',
|
|
||||||
name='end_nodes',
|
|
||||||
field=models.ManyToManyField(related_name='edge_products', to='epdb.node', verbose_name='End Nodes'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='edge',
|
|
||||||
name='start_nodes',
|
|
||||||
field=models.ManyToManyField(related_name='edge_educts', to='epdb.node', verbose_name='Start Nodes'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SequentialRule',
|
|
||||||
fields=[
|
|
||||||
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
bases=('epdb.rule',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SimpleRule',
|
|
||||||
fields=[
|
|
||||||
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
bases=('epdb.rule',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Pathway',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
|
||||||
('predicted', models.BooleanField(default=False)),
|
|
||||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='node',
|
|
||||||
name='pathway',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='edge',
|
|
||||||
name='pathway',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Reaction',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
|
||||||
('multi_step', models.BooleanField(verbose_name='Multistep Reaction')),
|
|
||||||
('medline_references', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), null=True, size=None, verbose_name='Medline References')),
|
|
||||||
('educts', models.ManyToManyField(related_name='reaction_educts', to='epdb.compoundstructure', verbose_name='Educts')),
|
|
||||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')),
|
|
||||||
('products', models.ManyToManyField(related_name='reaction_products', to='epdb.compoundstructure', verbose_name='Products')),
|
|
||||||
('rules', models.ManyToManyField(related_name='reaction_rule', to='epdb.rule', verbose_name='Rule')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='EnzymeLink',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('ec_number', models.TextField(verbose_name='EC Number')),
|
|
||||||
('classification_level', models.IntegerField(verbose_name='Classification Level')),
|
|
||||||
('linking_method', models.TextField(verbose_name='Linking Method')),
|
|
||||||
('edge_evidence', models.ManyToManyField(to='epdb.edge')),
|
|
||||||
('rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.rule')),
|
|
||||||
('reaction_evidence', models.ManyToManyField(to='epdb.reaction')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='edge',
|
|
||||||
name='edge_label',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.reaction', verbose_name='Edge label'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Scenario',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('scenario_date', models.CharField(default='No date', max_length=256)),
|
|
||||||
('scenario_type', models.CharField(default='Not specified', max_length=256)),
|
|
||||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='rule',
|
|
||||||
name='scenarios',
|
|
||||||
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='reaction',
|
|
||||||
name='scenarios',
|
|
||||||
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='pathway',
|
|
||||||
name='scenarios',
|
|
||||||
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='node',
|
|
||||||
name='scenarios',
|
|
||||||
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='edge',
|
|
||||||
name='scenarios',
|
|
||||||
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='compoundstructure',
|
|
||||||
name='scenarios',
|
|
||||||
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='compound',
|
|
||||||
name='scenarios',
|
|
||||||
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Setting',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('public', models.BooleanField(default=False)),
|
|
||||||
('global_default', models.BooleanField(default=False)),
|
|
||||||
('max_depth', models.IntegerField(default=5, verbose_name='Setting Max Depth')),
|
|
||||||
('max_nodes', models.IntegerField(default=30, verbose_name='Setting Max Number of Nodes')),
|
|
||||||
('model_threshold', models.FloatField(blank=True, default=0.25, null=True, verbose_name='Setting Model Threshold')),
|
|
||||||
('expansion_scheme', models.CharField(choices=[('BFS', 'Breadth First Search'), ('DFS', 'Depth First Search'), ('GREEDY', 'Greedy')], default='BFS', max_length=20)),
|
|
||||||
('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.epmodel', verbose_name='Setting EPModel')),
|
|
||||||
('rule_packages', models.ManyToManyField(blank=True, related_name='setting_rule_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Setting Rule Packages')),
|
|
||||||
('property_models', models.ManyToManyField(blank=True, related_name='settings', to='epdb.propertypluginmodel', verbose_name='Setting Property Models')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='pathway',
|
|
||||||
name='setting',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Setting'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='default_setting',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.setting', verbose_name='The users default settings'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='EnviFormer',
|
|
||||||
fields=[
|
|
||||||
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
|
||||||
('threshold', models.FloatField(default=0.5)),
|
|
||||||
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('multigen_eval', models.BooleanField(default=False)),
|
|
||||||
('app_domain', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain')),
|
|
||||||
('data_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Data Packages')),
|
|
||||||
('eval_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_eval_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Evaluation Packages')),
|
|
||||||
('rule_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_rule_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Rule Packages')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('epdb.epmodel',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='MLRelativeReasoning',
|
|
||||||
fields=[
|
|
||||||
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
|
||||||
('threshold', models.FloatField(default=0.5)),
|
|
||||||
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('multigen_eval', models.BooleanField(default=False)),
|
|
||||||
('app_domain', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain')),
|
|
||||||
('data_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Data Packages')),
|
|
||||||
('eval_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_eval_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Evaluation Packages')),
|
|
||||||
('rule_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_rule_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Rule Packages')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('epdb.epmodel',),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='applicabilitydomain',
|
|
||||||
name='model',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.mlrelativereasoning'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='propertypluginmodel',
|
|
||||||
name='app_domain',
|
|
||||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='propertypluginmodel',
|
|
||||||
name='data_packages',
|
|
||||||
field=models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_data_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Data Packages'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='propertypluginmodel',
|
|
||||||
name='eval_packages',
|
|
||||||
field=models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_eval_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Evaluation Packages'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='propertypluginmodel',
|
|
||||||
name='rule_packages',
|
|
||||||
field=models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_rule_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Rule Packages'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='RuleBasedRelativeReasoning',
|
|
||||||
fields=[
|
|
||||||
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
|
||||||
('threshold', models.FloatField(default=0.5)),
|
|
||||||
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('multigen_eval', models.BooleanField(default=False)),
|
|
||||||
('min_count', models.IntegerField(default=10)),
|
|
||||||
('max_count', models.IntegerField(default=0)),
|
|
||||||
('app_domain', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain')),
|
|
||||||
('data_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Data Packages')),
|
|
||||||
('eval_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_eval_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Evaluation Packages')),
|
|
||||||
('rule_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_rule_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Rule Packages')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('epdb.epmodel',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ExternalIdentifier',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
|
||||||
('object_id', models.IntegerField()),
|
|
||||||
('identifier_value', models.CharField(max_length=255, verbose_name='Identifier Value')),
|
|
||||||
('url', models.URLField(blank=True, null=True, verbose_name='Direct URL')),
|
|
||||||
('is_primary', models.BooleanField(default=False, help_text='Mark this as the primary identifier for this database', verbose_name='Is Primary')),
|
|
||||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
|
||||||
('database', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.externaldatabase', verbose_name='External Database')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'External Identifier',
|
|
||||||
'verbose_name_plural': 'External Identifiers',
|
|
||||||
'db_table': 'epdb_external_identifier',
|
|
||||||
'indexes': [models.Index(fields=['content_type', 'object_id'], name='epdb_extern_content_b76813_idx'), models.Index(fields=['database', 'identifier_value'], name='epdb_extern_databas_486422_idx')],
|
|
||||||
'unique_together': {('content_type', 'object_id', 'database', 'identifier_value')},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SimpleAmbitRule',
|
|
||||||
fields=[
|
|
||||||
('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')),
|
|
||||||
('smirks', models.TextField(verbose_name='SMIRKS')),
|
|
||||||
('reactant_filter_smarts', models.TextField(null=True, verbose_name='Reactant Filter SMARTS')),
|
|
||||||
('product_filter_smarts', models.TextField(null=True, verbose_name='Product Filter SMARTS')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
bases=('epdb.simplerule',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SimpleRDKitRule',
|
|
||||||
fields=[
|
|
||||||
('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')),
|
|
||||||
('reaction_smarts', models.TextField(verbose_name='SMIRKS')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
bases=('epdb.simplerule',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SequentialRuleOrdering',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('order_index', models.IntegerField()),
|
|
||||||
('sequential_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.sequentialrule')),
|
|
||||||
('simple_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.simplerule')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='sequentialrule',
|
|
||||||
name='simple_rules',
|
|
||||||
field=models.ManyToManyField(through='epdb.SequentialRuleOrdering', to='epdb.simplerule', verbose_name='Simple rules'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ParallelRule',
|
|
||||||
fields=[
|
|
||||||
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
|
||||||
('simple_rules', models.ManyToManyField(to='epdb.simplerule', verbose_name='Simple rules')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
bases=('epdb.rule',),
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='compound',
|
|
||||||
unique_together={('uuid', 'package')},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='AdditionalInformation',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
|
||||||
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('type', models.TextField(verbose_name='Additional Information Type')),
|
|
||||||
('data', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('object_id', models.PositiveBigIntegerField(blank=True, null=True)),
|
|
||||||
('content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
|
||||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')),
|
|
||||||
('scenario', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='scenario_additional_information', to='epdb.scenario')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'indexes': [models.Index(fields=['type'], name='epdb_additi_type_394349_idx'), models.Index(fields=['scenario', 'type'], name='epdb_additi_scenari_a59edf_idx'), models.Index(fields=['content_type', 'object_id'], name='epdb_additi_content_44d4b4_idx'), models.Index(fields=['scenario', 'content_type', 'object_id'], name='epdb_additi_scenari_ef2bf5_idx')],
|
|
||||||
'constraints': [models.CheckConstraint(condition=models.Q(models.Q(('content_type__isnull', True), ('object_id__isnull', True)), models.Q(('content_type__isnull', False), ('object_id__isnull', False)), _connector='OR'), name='ck_addinfo_gfk_pair'), models.CheckConstraint(condition=models.Q(('scenario__isnull', False), ('content_type__isnull', False), _connector='OR'), name='ck_addinfo_not_both_null')],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='GroupPackagePermission',
|
|
||||||
fields=[
|
|
||||||
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
|
||||||
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.group', verbose_name='Permission to')),
|
|
||||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Permission on')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'unique_together': {('package', 'group')},
|
|
||||||
},
|
|
||||||
bases=('epdb.permission',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='UserPackagePermission',
|
|
||||||
fields=[
|
|
||||||
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
|
||||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Permission on')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'unique_together': {('package', 'user')},
|
|
||||||
},
|
|
||||||
bases=('epdb.permission',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='UserSettingPermission',
|
|
||||||
fields=[
|
|
||||||
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
|
||||||
('setting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Permission on')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'unique_together': {('setting', 'user')},
|
|
||||||
},
|
|
||||||
bases=('epdb.permission',),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -46,9 +46,4 @@ class Migration(migrations.Migration):
|
|||||||
name="molfile",
|
name="molfile",
|
||||||
field=models.TextField(blank=True, null=True, verbose_name="Molfile"),
|
field=models.TextField(blank=True, null=True, verbose_name="Molfile"),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
|
||||||
model_name="group",
|
|
||||||
name="secret",
|
|
||||||
field=models.BooleanField(default=False, verbose_name="Secret Group"),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|||||||
150
epdb/migrations/0027_alter_compound_aliases_and_more.py
Normal file
150
epdb/migrations/0027_alter_compound_aliases_and_more.py
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
# Generated by Django 6.0.3 on 2026-07-01 20:59
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0026_auto_20260602_1718"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="compound",
|
||||||
|
name="aliases",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(), blank=True, default=list, verbose_name="Aliases"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="compound",
|
||||||
|
name="default_structure",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="compound_default_structure",
|
||||||
|
to="epdb.compoundstructure",
|
||||||
|
verbose_name="Default Structure",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="compound",
|
||||||
|
name="scenarios",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, to="epdb.scenario", verbose_name="Attached Scenarios"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="compoundstructure",
|
||||||
|
name="aliases",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(), blank=True, default=list, verbose_name="Aliases"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="compoundstructure",
|
||||||
|
name="scenarios",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, to="epdb.scenario", verbose_name="Attached Scenarios"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="edge",
|
||||||
|
name="aliases",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(), blank=True, default=list, verbose_name="Aliases"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="edge",
|
||||||
|
name="edge_label",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="epdb.reaction",
|
||||||
|
verbose_name="Edge label",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="edge",
|
||||||
|
name="scenarios",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, to="epdb.scenario", verbose_name="Attached Scenarios"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="node",
|
||||||
|
name="aliases",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(), blank=True, default=list, verbose_name="Aliases"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="node",
|
||||||
|
name="scenarios",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, to="epdb.scenario", verbose_name="Attached Scenarios"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="pathway",
|
||||||
|
name="aliases",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(), blank=True, default=list, verbose_name="Aliases"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="pathway",
|
||||||
|
name="scenarios",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, to="epdb.scenario", verbose_name="Attached Scenarios"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="reaction",
|
||||||
|
name="aliases",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(), blank=True, default=list, verbose_name="Aliases"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="reaction",
|
||||||
|
name="medline_references",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(),
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Medline References",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="reaction",
|
||||||
|
name="rules",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, related_name="reaction_rule", to="epdb.rule", verbose_name="Rule"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="reaction",
|
||||||
|
name="scenarios",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, to="epdb.scenario", verbose_name="Attached Scenarios"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="rule",
|
||||||
|
name="aliases",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(), blank=True, default=list, verbose_name="Aliases"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="rule",
|
||||||
|
name="scenarios",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, to="epdb.scenario", verbose_name="Attached Scenarios"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -31,6 +31,7 @@ from sklearn.model_selection import ShuffleSplit
|
|||||||
|
|
||||||
from bridge.contracts import Property
|
from bridge.contracts import Property
|
||||||
from bridge.dto import RunResult, PropertyPrediction
|
from bridge.dto import RunResult, PropertyPrediction
|
||||||
|
from epdb.exceptions import InvalidSMILESException
|
||||||
from utilities.chem import FormatConverter, IndigoUtils, PredictionResult, ProductSet
|
from utilities.chem import FormatConverter, IndigoUtils, PredictionResult, ProductSet
|
||||||
from utilities.ml import (
|
from utilities.ml import (
|
||||||
ApplicabilityDomainPCA,
|
ApplicabilityDomainPCA,
|
||||||
@ -204,7 +205,6 @@ class Group(TimeStampedModel):
|
|||||||
name = models.TextField(blank=False, null=False, verbose_name="Group name")
|
name = models.TextField(blank=False, null=False, verbose_name="Group name")
|
||||||
owner = models.ForeignKey("User", verbose_name="Group Owner", on_delete=models.CASCADE)
|
owner = models.ForeignKey("User", verbose_name="Group Owner", on_delete=models.CASCADE)
|
||||||
public = models.BooleanField(verbose_name="Public Group", default=False)
|
public = models.BooleanField(verbose_name="Public Group", default=False)
|
||||||
secret = models.BooleanField(verbose_name="Secret Group", default=False)
|
|
||||||
description = models.TextField(
|
description = models.TextField(
|
||||||
blank=False, null=False, verbose_name="Descriptions", default="no description"
|
blank=False, null=False, verbose_name="Descriptions", default="no description"
|
||||||
)
|
)
|
||||||
@ -632,7 +632,7 @@ class EnviPathModel(TimeStampedModel):
|
|||||||
|
|
||||||
class AliasMixin(models.Model):
|
class AliasMixin(models.Model):
|
||||||
aliases = ArrayField(
|
aliases = ArrayField(
|
||||||
models.TextField(blank=False, null=False), verbose_name="Aliases", default=list
|
models.TextField(blank=False, null=False), verbose_name="Aliases", default=list, blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@ -655,7 +655,9 @@ class AliasMixin(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class ScenarioMixin(models.Model):
|
class ScenarioMixin(models.Model):
|
||||||
scenarios = models.ManyToManyField("epdb.Scenario", verbose_name="Attached Scenarios")
|
scenarios = models.ManyToManyField(
|
||||||
|
"epdb.Scenario", verbose_name="Attached Scenarios", blank=True
|
||||||
|
)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def set_scenarios(self, scenarios: List["Scenario"]):
|
def set_scenarios(self, scenarios: List["Scenario"]):
|
||||||
@ -781,7 +783,7 @@ class Compound(
|
|||||||
"CompoundStructure",
|
"CompoundStructure",
|
||||||
verbose_name="Default Structure",
|
verbose_name="Default Structure",
|
||||||
related_name="compound_default_structure",
|
related_name="compound_default_structure",
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -858,35 +860,28 @@ class Compound(
|
|||||||
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 InvalidSMILESException("SMILES is required")
|
||||||
|
|
||||||
smiles = smiles.strip()
|
smiles = smiles.strip()
|
||||||
|
|
||||||
parsed = FormatConverter.from_smiles(smiles)
|
parsed = FormatConverter.from_smiles(smiles)
|
||||||
if parsed is None:
|
if parsed is None:
|
||||||
raise ValueError("Given SMILES is invalid")
|
raise InvalidSMILESException("Given SMILES is invalid")
|
||||||
|
|
||||||
standardized_smiles = FormatConverter.standardize(smiles, remove_stereo=True)
|
standardized_smiles = FormatConverter.standardize(smiles, remove_stereo=True)
|
||||||
|
|
||||||
subclasses = CompoundStructure.__subclasses__()
|
|
||||||
|
|
||||||
qs = CompoundStructure.objects.filter(smiles=smiles, compound__package=package)
|
|
||||||
if subclasses:
|
|
||||||
qs = qs.not_instance_of(*subclasses)
|
|
||||||
|
|
||||||
# Check if we find a direct match for a given SMILES
|
# Check if we find a direct match for a given SMILES
|
||||||
if qs.exists():
|
if CompoundStructure.objects.filter(smiles=smiles, compound__package=package).exists():
|
||||||
return qs.first().compound
|
return CompoundStructure.objects.get(smiles=smiles, compound__package=package).compound
|
||||||
|
|
||||||
|
|
||||||
qs = CompoundStructure.objects.filter(smiles=standardized_smiles, compound__package=package)
|
|
||||||
if subclasses:
|
|
||||||
qs = qs.not_instance_of(*subclasses)
|
|
||||||
|
|
||||||
# Check if we can find the standardized one
|
# Check if we can find the standardized one
|
||||||
if qs.exists():
|
if CompoundStructure.objects.filter(
|
||||||
|
smiles=standardized_smiles, compound__package=package
|
||||||
|
).exists():
|
||||||
# TODO should we add a structure?
|
# TODO should we add a structure?
|
||||||
return qs.first().compound
|
return CompoundStructure.objects.get(
|
||||||
|
smiles=standardized_smiles, compound__package=package
|
||||||
|
).compound
|
||||||
|
|
||||||
# Generate Compound
|
# Generate Compound
|
||||||
c = Compound()
|
c = Compound()
|
||||||
@ -1182,6 +1177,8 @@ class CompoundStructure(
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def as_svg(self, width: int = 800, height: int = 400):
|
def as_svg(self, width: int = 800, height: int = 400):
|
||||||
|
if self.molfile is not None and self.molfile.strip() != "":
|
||||||
|
return IndigoUtils.mol_to_svg(self.molfile, width=width, height=height)
|
||||||
return IndigoUtils.mol_to_svg(self.smiles, width=width, height=height)
|
return IndigoUtils.mol_to_svg(self.smiles, width=width, height=height)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -1618,10 +1615,15 @@ class Reaction(
|
|||||||
products = models.ManyToManyField(
|
products = models.ManyToManyField(
|
||||||
"epdb.CompoundStructure", verbose_name="Products", related_name="reaction_products"
|
"epdb.CompoundStructure", verbose_name="Products", related_name="reaction_products"
|
||||||
)
|
)
|
||||||
rules = models.ManyToManyField("epdb.Rule", verbose_name="Rule", related_name="reaction_rule")
|
rules = models.ManyToManyField(
|
||||||
|
"epdb.Rule", verbose_name="Rule", related_name="reaction_rule", blank=True
|
||||||
|
)
|
||||||
multi_step = models.BooleanField(verbose_name="Multistep Reaction")
|
multi_step = models.BooleanField(verbose_name="Multistep Reaction")
|
||||||
medline_references = ArrayField(
|
medline_references = ArrayField(
|
||||||
models.TextField(blank=False, null=False), null=True, verbose_name="Medline References"
|
models.TextField(blank=False, null=False),
|
||||||
|
null=True,
|
||||||
|
verbose_name="Medline References",
|
||||||
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
external_identifiers = GenericRelation("ExternalIdentifier")
|
external_identifiers = GenericRelation("ExternalIdentifier")
|
||||||
@ -1638,7 +1640,7 @@ class Reaction(
|
|||||||
educts: Union[List[str], List[CompoundStructure]] = None,
|
educts: Union[List[str], List[CompoundStructure]] = None,
|
||||||
products: Union[List[str], List[CompoundStructure]] = None,
|
products: Union[List[str], List[CompoundStructure]] = None,
|
||||||
rules: Union[Rule | List[Rule]] = None,
|
rules: Union[Rule | List[Rule]] = None,
|
||||||
multi_step: bool = True,
|
multi_step: bool = False,
|
||||||
):
|
):
|
||||||
_educts = []
|
_educts = []
|
||||||
_products = []
|
_products = []
|
||||||
@ -2205,10 +2207,12 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
|
|||||||
depth_map[0] = list()
|
depth_map[0] = list()
|
||||||
processed = set()
|
processed = set()
|
||||||
|
|
||||||
data_driven_root_nodes = self.node_set.all().annotate(
|
data_driven_root_nodes = (
|
||||||
prod_cnt=Count('edge_products'),
|
self.node_set.all()
|
||||||
educt_cnt=Count('edge_educts')
|
.annotate(prod_cnt=Count("edge_products"), educt_cnt=Count("edge_educts"))
|
||||||
).filter(prod_cnt=0, educt_cnt__gt=0).distinct()
|
.filter(prod_cnt=0, educt_cnt__gt=0)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
|
||||||
# Eval QuerySet
|
# Eval QuerySet
|
||||||
root_nodes_by_depth = list(self.root_nodes)
|
root_nodes_by_depth = list(self.root_nodes)
|
||||||
@ -2301,7 +2305,12 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
|
|||||||
"node_label_id": self.default_node_label.url,
|
"node_label_id": self.default_node_label.url,
|
||||||
"image": f"{self.url}?image=svg",
|
"image": f"{self.url}?image=svg",
|
||||||
"image_svg": IndigoUtils.mol_to_svg(
|
"image_svg": IndigoUtils.mol_to_svg(
|
||||||
self.default_node_label.smiles, width=40, height=40
|
self.default_node_label.molfile
|
||||||
|
if self.default_node_label.molfile is not None
|
||||||
|
and self.default_node_label.molfile.strip()
|
||||||
|
else self.default_node_label.smiles,
|
||||||
|
width=40,
|
||||||
|
height=40,
|
||||||
),
|
),
|
||||||
"image_type": "svg",
|
"image_type": "svg",
|
||||||
"name": self.get_name(),
|
"name": self.get_name(),
|
||||||
@ -2355,12 +2364,17 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def as_svg(self):
|
def as_svg(self):
|
||||||
|
if (
|
||||||
|
self.default_node_label.molfile is not None
|
||||||
|
and self.default_node_label.molfile.strip() != ""
|
||||||
|
):
|
||||||
|
return IndigoUtils.mol_to_svg(self.default_node_label.molfile)
|
||||||
return IndigoUtils.mol_to_svg(self.default_node_label.smiles)
|
return IndigoUtils.mol_to_svg(self.default_node_label.smiles)
|
||||||
|
|
||||||
def get_timeseries_data(self):
|
def get_timeseries_data(self):
|
||||||
for ai in self.additional_information.all():
|
for ai in self.additional_information.all():
|
||||||
if ai.__class__.__name__ == "OECD301FTimeSeries":
|
if ai.type == "OECD301FTimeSeries":
|
||||||
return ai.model_dump(mode="json")
|
return ai.get().model_dump(mode="json")
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ from sentry_sdk import capture_exception
|
|||||||
|
|
||||||
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 .exceptions import InvalidSMILESException
|
||||||
|
|
||||||
from .logic import (
|
from .logic import (
|
||||||
EPDBURLParser,
|
EPDBURLParser,
|
||||||
@ -388,9 +389,6 @@ def get_base_context(request, for_user=None) -> Dict[str, Any]:
|
|||||||
"debug": s.DEBUG,
|
"debug": s.DEBUG,
|
||||||
"external_databases": ExternalDatabase.get_databases(),
|
"external_databases": ExternalDatabase.get_databases(),
|
||||||
"site_id": s.MATOMO_SITE_ID,
|
"site_id": s.MATOMO_SITE_ID,
|
||||||
# EDIT START
|
|
||||||
"secret_groups": Group.objects.filter(secret=True),
|
|
||||||
# EDIT END
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -590,38 +588,10 @@ def packages(request):
|
|||||||
"package-description", s.DEFAULT_VALUES["description"]
|
"package-description", s.DEFAULT_VALUES["description"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# EDIT START
|
|
||||||
data_pool = None
|
|
||||||
package_classification = request.POST.get("package-classification")
|
|
||||||
classification = Package.Classification(int(package_classification))
|
|
||||||
# For SECRET we'll need a data pool which will be an additional perm check later
|
|
||||||
if classification == Package.Classification.SECRET:
|
|
||||||
package_data_pool = request.POST.get("package-data-pool")
|
|
||||||
|
|
||||||
if package_data_pool is None:
|
|
||||||
return error(request, "Invalid data pool.", "Data Pool is required!")
|
|
||||||
|
|
||||||
data_pool = GroupManager.get_group_by_url(current_user, package_data_pool)
|
|
||||||
|
|
||||||
if data_pool is None:
|
|
||||||
return error(request, "Invalid data pool.", "Data Pool does not exist or no access!")
|
|
||||||
|
|
||||||
if not data_pool.secret:
|
|
||||||
return error(request, "Invalid data pool.", "Data Pool is not a secret group!")
|
|
||||||
|
|
||||||
created_package = PackageManager.create_package(
|
created_package = PackageManager.create_package(
|
||||||
current_user, package_name, package_description
|
current_user, package_name, package_description
|
||||||
)
|
)
|
||||||
|
|
||||||
created_package.classification_level = classification
|
|
||||||
|
|
||||||
# Set previously determined data pool
|
|
||||||
if classification == Package.Classification.SECRET:
|
|
||||||
created_package.data_pool = data_pool
|
|
||||||
|
|
||||||
created_package.save()
|
|
||||||
# EDIT END
|
|
||||||
|
|
||||||
return redirect(created_package.url)
|
return redirect(created_package.url)
|
||||||
|
|
||||||
elif request.method == "OPTIONS":
|
elif request.method == "OPTIONS":
|
||||||
@ -785,6 +755,11 @@ def models(request):
|
|||||||
{"Model": s.SERVER_URL + "/model"},
|
{"Model": s.SERVER_URL + "/model"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
context["entity_type"] = "model"
|
||||||
|
context["api_endpoint"] = f"{s.SERVER_PATH}/api/v1/models/"
|
||||||
|
context["per_page"] = s.API_PAGINATION_DEFAULT_PAGE_SIZE
|
||||||
|
context["list_title"] = "models"
|
||||||
|
|
||||||
# Keep model_types for potential modal/action use
|
# Keep model_types for potential modal/action use
|
||||||
context["model_types"] = {
|
context["model_types"] = {
|
||||||
"ML Relative Reasoning": {
|
"ML Relative Reasoning": {
|
||||||
@ -797,12 +772,16 @@ def models(request):
|
|||||||
"requires_rule_packages": True,
|
"requires_rule_packages": True,
|
||||||
"requires_data_packages": True,
|
"requires_data_packages": True,
|
||||||
},
|
},
|
||||||
"EnviFormer": {
|
}
|
||||||
|
|
||||||
|
if s.ENVIFORMER_PRESENT:
|
||||||
|
context["model_types"]["EnviFormer"] = (
|
||||||
|
{
|
||||||
"type": "enviformer",
|
"type": "enviformer",
|
||||||
"requires_rule_packages": False,
|
"requires_rule_packages": False,
|
||||||
"requires_data_packages": True,
|
"requires_data_packages": True,
|
||||||
},
|
},
|
||||||
}
|
)
|
||||||
|
|
||||||
if s.FLAGS.get("PLUGINS", False):
|
if s.FLAGS.get("PLUGINS", False):
|
||||||
for k, v in s.CLASSIFIER_PLUGINS.items():
|
for k, v in s.CLASSIFIER_PLUGINS.items():
|
||||||
@ -810,6 +789,9 @@ def models(request):
|
|||||||
"type": k,
|
"type": k,
|
||||||
"requires_rule_packages": v.requires_rule_packages(),
|
"requires_rule_packages": v.requires_rule_packages(),
|
||||||
"requires_data_packages": v.requires_data_packages(),
|
"requires_data_packages": v.requires_data_packages(),
|
||||||
|
"additional_parameters": v.Config.__name__.lower()
|
||||||
|
if v.Config.__name__ != ""
|
||||||
|
else None,
|
||||||
}
|
}
|
||||||
for k, v in s.PROPERTY_PLUGINS.items():
|
for k, v in s.PROPERTY_PLUGINS.items():
|
||||||
context["model_types"][v.display()] = {
|
context["model_types"][v.display()] = {
|
||||||
@ -818,12 +800,6 @@ def models(request):
|
|||||||
"requires_data_packages": v.requires_data_packages(),
|
"requires_data_packages": v.requires_data_packages(),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Context for paginated template
|
|
||||||
context["entity_type"] = "model"
|
|
||||||
context["api_endpoint"] = f"{s.SERVER_PATH}/api/v1/models/"
|
|
||||||
context["per_page"] = s.API_PAGINATION_DEFAULT_PAGE_SIZE
|
|
||||||
context["list_title"] = "models"
|
|
||||||
|
|
||||||
return render(request, "collections/models_paginated.html", context)
|
return render(request, "collections/models_paginated.html", context)
|
||||||
|
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
@ -938,13 +914,12 @@ def package_models(request, package_uuid):
|
|||||||
"requires_data_packages": True,
|
"requires_data_packages": True,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.ENVIFORMER_PRESENT:
|
if s.ENVIFORMER_PRESENT:
|
||||||
context["model_types"]["EnviFormer"] = {
|
context["model_types"]["EnviFormer"] = {
|
||||||
"type": "enviformer",
|
"type": "enviformer",
|
||||||
"requires_rule_packages": False,
|
"requires_rule_packages": False,
|
||||||
"requires_data_packages": True,
|
"requires_data_packages": True,
|
||||||
},
|
}
|
||||||
|
|
||||||
if s.FLAGS.get("PLUGINS", False):
|
if s.FLAGS.get("PLUGINS", False):
|
||||||
for k, v in s.CLASSIFIER_PLUGINS.items():
|
for k, v in s.CLASSIFIER_PLUGINS.items():
|
||||||
@ -1120,6 +1095,11 @@ def package_model(request, package_uuid, model_uuid):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Sort data by prob desc
|
||||||
|
res["pred"] = sorted(
|
||||||
|
res["pred"], key=lambda x: x["probability"], reverse=True
|
||||||
|
)
|
||||||
|
|
||||||
return JsonResponse(res, safe=False)
|
return JsonResponse(res, safe=False)
|
||||||
|
|
||||||
elif half_life:
|
elif half_life:
|
||||||
@ -1223,9 +1203,7 @@ def package(request, package_uuid):
|
|||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
if request.GET.get("export", False) == "true":
|
if request.GET.get("export", False) == "true":
|
||||||
filename = f"{current_package.get_name().replace(' ', '_')}_{current_package.uuid}.json"
|
filename = f"{current_package.get_name().replace(' ', '_')}_{current_package.uuid}.json"
|
||||||
pack_json = PackageManager.export_package(
|
pack_json = PackageManager.export_package(current_package)
|
||||||
current_package, include_models=False, include_external_identifiers=False
|
|
||||||
)
|
|
||||||
response = JsonResponse(pack_json, content_type="application/json")
|
response = JsonResponse(pack_json, content_type="application/json")
|
||||||
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
||||||
|
|
||||||
@ -2361,7 +2339,13 @@ def package_pathway_nodes(request, package_uuid, pathway_uuid):
|
|||||||
node_description = request.POST.get("node-description")
|
node_description = request.POST.get("node-description")
|
||||||
|
|
||||||
node_smiles = request.POST.get("node-smiles").strip()
|
node_smiles = request.POST.get("node-smiles").strip()
|
||||||
|
|
||||||
|
try:
|
||||||
current_pathway.add_node(node_smiles, name=node_name, description=node_description)
|
current_pathway.add_node(node_smiles, name=node_name, description=node_description)
|
||||||
|
except InvalidSMILESException:
|
||||||
|
return error(
|
||||||
|
request, "Node creation failed!", f"Given SMILES ({node_smiles}) is invalid"
|
||||||
|
)
|
||||||
|
|
||||||
return redirect(current_pathway.url)
|
return redirect(current_pathway.url)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -65,6 +65,25 @@ def run_both_engines(SMILES, SMIRKS):
|
|||||||
|
|
||||||
|
|
||||||
def migration(request):
|
def migration(request):
|
||||||
|
accepted_diffs = [
|
||||||
|
"bt0055-3469.1",
|
||||||
|
"bt0056-2685",
|
||||||
|
"bt0193-4263",
|
||||||
|
"bt0231-1871.1",
|
||||||
|
"bt0242-3803",
|
||||||
|
"bt0254-4224.1",
|
||||||
|
"bt0254-4224.2",
|
||||||
|
"bt0291-1129",
|
||||||
|
"bt0337-3543",
|
||||||
|
"bt0391-4285",
|
||||||
|
"bt0402-3576",
|
||||||
|
"bt0404-3928",
|
||||||
|
"bt0416-4269",
|
||||||
|
"bt0432-4254",
|
||||||
|
"bt0337-4117",
|
||||||
|
"bt0322-3393",
|
||||||
|
]
|
||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
|
|
||||||
@ -109,11 +128,11 @@ def migration(request):
|
|||||||
),
|
),
|
||||||
"id": str(r.uuid),
|
"id": str(r.uuid),
|
||||||
"url": r.url,
|
"url": r.url,
|
||||||
"status": res,
|
"status": res or r.name in accepted_diffs,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if res:
|
if res or r.name in accepted_diffs:
|
||||||
success += 1
|
success += 1
|
||||||
else:
|
else:
|
||||||
error += 1
|
error += 1
|
||||||
@ -135,7 +154,16 @@ 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)
|
||||||
|
if r["name"] in accepted_diffs:
|
||||||
|
r["status"] = True
|
||||||
|
|
||||||
|
migration_status["results"] = sorted(
|
||||||
|
migration_status["results"], key=lambda x: (x["status"], x["name"])
|
||||||
|
)
|
||||||
|
|
||||||
|
num_success = sum([int(x["status"]) for x in migration_status["results"]])
|
||||||
|
migration_status["success"] = num_success
|
||||||
|
migration_status["error"] = migration_status["total"] - num_success
|
||||||
context.update(**migration_status)
|
context.update(**migration_status)
|
||||||
|
|
||||||
return render(request, "migration.html", context)
|
return render(request, "migration.html", context)
|
||||||
|
|||||||
@ -34,3 +34,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@import "./daisyui-theme.css";
|
@import "./daisyui-theme.css";
|
||||||
|
|
||||||
|
select.select[multiple] {
|
||||||
|
display: block;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
@ -1,30 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<svg version="1.1" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
viewBox="0 0 76 76" style="enable-background:new 0 0 76 76;" xml:space="preserve">
|
|
||||||
<style type="text/css">
|
|
||||||
.st0{fill:#10384F;}
|
|
||||||
.st1{fill:#89D329;}
|
|
||||||
.st2{fill:#00BCFF;}
|
|
||||||
</style>
|
|
||||||
<g id="Bayer_Cross_1_">
|
|
||||||
<path class="st0" d="M35.9,11.3h4.4c0.5,0,0.9-0.4,0.9-0.9c0-0.5-0.4-0.9-0.9-0.9h-4.4V11.3z M35.9,15.5h4.5c0.6,0,1-0.4,1-1
|
|
||||||
c0-0.6-0.4-1-1-1h-4.5V15.5z M43,12.3c0.6,0.6,1,1.4,1,2.3c0,1.8-1.4,3.2-3.2,3.2h-7.3V7.3l7.2,0c1.7,0,3.1,1.4,3.1,3.1
|
|
||||||
C43.7,11.1,43.4,11.8,43,12.3z M44.7,30.3H42l-0.8-1.8h-5.9l-0.8,1.8h-2.7L37,19.8h2.4L44.7,30.3z M38.2,22.5l-1.8,3.8H40
|
|
||||||
L38.2,22.5z M41.8,32.6h3l-5.3,6.8v3.7h-2.5v-3.7l-5.3-6.8h3l3.6,4.8L41.8,32.6z M55.7,32.6v2.3h-7v1.8l6.8,0v2.3h-6.8v2h7v2.3
|
|
||||||
h-9.5V32.6H55.7z M63.4,39.1h-1.9v4h-2.5V32.6h6.4c1.8,0,3.2,1.5,3.2,3.3c0,1.5-1,2.7-2.3,3.1l3.1,4.1h-3L63.4,39.1z M65.2,34.8
|
|
||||||
h-3.6v2h3.6c0.6,0,1-0.5,1-1C66.2,35.3,65.7,34.8,65.2,34.8z M32.8,43.1h-2.7l-0.8-1.8h-5.9l-0.8,1.8h-2.7l5.3-10.5h2.4L32.8,43.1z
|
|
||||||
M26.3,35.3l-1.8,3.8h3.7L26.3,35.3z M10.4,36.6h4.4c0.5,0,0.9-0.4,0.9-0.9c0-0.5-0.4-0.9-0.9-0.9l-4.4,0V36.6z M10.4,40.8h4.5
|
|
||||||
c0.6,0,1-0.4,1-1c0-0.6-0.4-1-1-1h-4.5V40.8z M17.5,37.6c0.6,0.6,1,1.4,1,2.3c0,1.8-1.4,3.2-3.2,3.2H7.9V32.6h7.2
|
|
||||||
c1.7,0,3.1,1.4,3.1,3.1C18.2,36.4,17.9,37.1,17.5,37.6z M43,45.3v2.3h-7v1.8l6.8,0v2.3h-6.8v2h7v2.3h-9.5V45.3H43z M41.2,61.6
|
|
||||||
c0-0.6-0.4-1-1-1h-4.3v2h4.3C40.8,62.6,41.2,62.2,41.2,61.6z M33.4,68.9V58.4h7c1.8,0,3.2,1.5,3.2,3.3c0,1.4-0.8,2.5-2,3l3.2,4.2
|
|
||||||
h-3l-3-4h-2.9v4H33.4z"/>
|
|
||||||
<path class="st1" d="M76.1,35.6C74.9,15.8,58.4,0,38.2,0C18,0,1.5,15.8,0.3,35.6c0,0.8,0.1,1.6,0.2,2.4c0.8,6.6,3.3,12.7,7.1,17.8
|
|
||||||
c6.9,9.4,18,15.5,30.6,15.5c-17.6,0-32-13.7-33.2-30.9c-0.1-0.8-0.1-1.6-0.1-2.4c0-0.8,0-1.6,0.1-2.4C6.2,18.4,20.6,4.7,38.2,4.7
|
|
||||||
c12.6,0,23.7,6.1,30.6,15.5c3.8,5.1,6.3,11.2,7.1,17.8c0.1,0.8,0.2,1.6,0.2,2.3c0-0.8,0.1-1.6,0.1-2.4
|
|
||||||
C76.2,37.2,76.2,36.4,76.1,35.6"/>
|
|
||||||
<path class="st2" d="M0.3,40.4C1.5,60.2,18,76,38.2,76c20.2,0,36.7-15.8,37.9-35.6c0-0.8-0.1-1.6-0.2-2.4
|
|
||||||
c-0.8-6.6-3.3-12.7-7.1-17.8c-6.9-9.4-18-15.5-30.6-15.5c17.6,0,32,13.7,33.2,30.9c0.1,0.8,0.1,1.6,0.1,2.4c0,0.8,0,1.6-0.1,2.4
|
|
||||||
c-1.2,17.3-15.6,30.9-33.2,30.9c-12.6,0-23.7-6.1-30.6-15.5C3.8,50.7,1.3,44.6,0.5,38c-0.1-0.8-0.2-1.6-0.2-2.3
|
|
||||||
c0,0.8-0.1,1.6-0.1,2.4C0.2,38.8,0.2,39.6,0.3,40.4"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.2 KiB |
@ -1,5 +1,37 @@
|
|||||||
console.log("loaded pw.js")
|
console.log("loaded pw.js")
|
||||||
|
|
||||||
|
function findPaths(source_idx, links) {
|
||||||
|
const resultLinks = new Set();
|
||||||
|
const visited = new Set();
|
||||||
|
// Helper function for depth-first search
|
||||||
|
function dfs(current) {
|
||||||
|
|
||||||
|
if (visited.has(current)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (links.filter(link => link.target.id === current).length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
visited.add(current);
|
||||||
|
links.forEach(link => {
|
||||||
|
if (
|
||||||
|
link.target.id === current &&
|
||||||
|
!visited.has(link.source.id) &&
|
||||||
|
link.source.id !== link.target.id // Avoid self-loops
|
||||||
|
) {
|
||||||
|
resultLinks.add(link); // Add link to result
|
||||||
|
dfs(link.source.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
visited.delete(current);
|
||||||
|
}
|
||||||
|
// Start DFS
|
||||||
|
dfs(source_idx);
|
||||||
|
return Array.from(resultLinks);
|
||||||
|
}
|
||||||
|
|
||||||
function predictFromNode(url) {
|
function predictFromNode(url) {
|
||||||
fetch("", {
|
fetch("", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -22,6 +54,7 @@ function predictFromNode(url) {
|
|||||||
// data = {{ pathway.d3_json | safe }};
|
// data = {{ pathway.d3_json | safe }};
|
||||||
// elem = 'vizdiv'
|
// elem = 'vizdiv'
|
||||||
function draw(pathway, elem) {
|
function draw(pathway, elem) {
|
||||||
|
|
||||||
const initialzoom = 2.5
|
const initialzoom = 2.5
|
||||||
const nodeRadius = 20;
|
const nodeRadius = 20;
|
||||||
const linkDistance = 100;
|
const linkDistance = 100;
|
||||||
@ -572,12 +605,13 @@ function draw(pathway, elem) {
|
|||||||
.scaleExtent([0.5, 5])
|
.scaleExtent([0.5, 5])
|
||||||
.on("zoom", (event) => {
|
.on("zoom", (event) => {
|
||||||
zoomable.attr("transform", event.transform);
|
zoomable.attr("transform", event.transform);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
// Apply zoom to the SVG element - this enables wheel zoom
|
// Apply zoom to the SVG element - this enables wheel zoom
|
||||||
svg.call(zoom);
|
svg.call(zoom);
|
||||||
|
|
||||||
svg.call(zoom.scaleBy, initialzoom);
|
svg.call(zoom.scaleBy, initialzoom);
|
||||||
|
|
||||||
// Also apply zoom to container to catch events that might not reach SVG
|
// Also apply zoom to container to catch events that might not reach SVG
|
||||||
// This ensures drag-to-pan works even when clicking on empty space
|
// This ensures drag-to-pan works even when clicking on empty space
|
||||||
container.call(zoom);
|
container.call(zoom);
|
||||||
@ -627,9 +661,7 @@ function draw(pathway, elem) {
|
|||||||
.attr("fill", "none")
|
.attr("fill", "none")
|
||||||
.on("click", function(event, d) {
|
.on("click", function(event, d) {
|
||||||
const wasHighlighted = d3.select(this).classed("highlighted");
|
const wasHighlighted = d3.select(this).classed("highlighted");
|
||||||
|
|
||||||
d3.selectAll("path").classed("highlighted", false);
|
d3.selectAll("path").classed("highlighted", false);
|
||||||
|
|
||||||
if (!wasHighlighted) {
|
if (!wasHighlighted) {
|
||||||
const toHighlight = [];
|
const toHighlight = [];
|
||||||
toHighlight.push(d.el);
|
toHighlight.push(d.el);
|
||||||
@ -654,7 +686,7 @@ function draw(pathway, elem) {
|
|||||||
d3.select(e).classed("highlighted", true);
|
d3.select(e).classed("highlighted", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
// add element to links array
|
// add element to links array
|
||||||
link.each(function (d) {
|
link.each(function (d) {
|
||||||
@ -671,16 +703,41 @@ function draw(pathway, elem) {
|
|||||||
.call(d3.drag()
|
.call(d3.drag()
|
||||||
.on("start", dragstarted)
|
.on("start", dragstarted)
|
||||||
.on("drag", dragged)
|
.on("drag", dragged)
|
||||||
.on("end", dragended))
|
.on("end", dragended)
|
||||||
|
)
|
||||||
.on("click", function (event, d) {
|
.on("click", function (event, d) {
|
||||||
const wasHighlighted = d3.select(this).select("circle").classed("highlighted");
|
const wasHighlighted = d3.select(this).select("circle").classed("highlighted");
|
||||||
|
|
||||||
d3.selectAll('circle.highlighted').classed('highlighted', false);
|
d3.selectAll('circle.highlighted').classed('highlighted', false);
|
||||||
|
d3.selectAll("path").classed("inedge", false);
|
||||||
|
d3.selectAll("path").classed("outedge", false);
|
||||||
|
|
||||||
if (!wasHighlighted) {
|
if (!wasHighlighted) {
|
||||||
d3.select(this).select("circle").classed("highlighted", !d3.select(this).select("circle").classed("highlighted"));
|
d3.select(this).select("circle").classed("highlighted", !d3.select(this).select("circle").classed("highlighted"));
|
||||||
|
const inEdges = findPaths(d.id, links);
|
||||||
|
const outEdges = []
|
||||||
|
// Colorize out edges green
|
||||||
|
for (const l of links) {
|
||||||
|
if (l.source.id === d.id) {
|
||||||
|
outEdges.push(l);
|
||||||
|
if (l.target.pseudo) {
|
||||||
|
for (const l2 of links) {
|
||||||
|
if (l.target.id === l2.source.id) {
|
||||||
|
outEdges.push(l2);
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const e of inEdges) {
|
||||||
|
d3.select(e.el).classed("inedge", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const e of outEdges) {
|
||||||
|
d3.select(e.el).classed("outedge", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Kreise für die Knoten hinzufügen
|
// Kreise für die Knoten hinzufügen
|
||||||
node.append("circle")
|
node.append("circle")
|
||||||
@ -754,14 +811,14 @@ function draw(pathway, elem) {
|
|||||||
|
|
||||||
function serializeSVG(svgElement) {
|
function serializeSVG(svgElement) {
|
||||||
|
|
||||||
svgElement.querySelectorAll("line.link").forEach(line => {
|
svgElement.querySelectorAll("path.link").forEach(line => {
|
||||||
const style = getComputedStyle(line);
|
const style = getComputedStyle(line);
|
||||||
line.setAttribute("stroke", style.stroke);
|
line.setAttribute("stroke", style.stroke);
|
||||||
line.setAttribute("stroke-width", style.strokeWidth);
|
line.setAttribute("stroke-width", style.strokeWidth);
|
||||||
line.setAttribute("fill", style.fill);
|
line.setAttribute("fill", style.fill);
|
||||||
});
|
});
|
||||||
|
|
||||||
svgElement.querySelectorAll("line.link_no_arrow").forEach(line => {
|
svgElement.querySelectorAll("path.link_no_arrow").forEach(line => {
|
||||||
const style = getComputedStyle(line);
|
const style = getComputedStyle(line);
|
||||||
line.setAttribute("stroke", style.stroke);
|
line.setAttribute("stroke", style.stroke);
|
||||||
line.setAttribute("stroke-width", style.strokeWidth);
|
line.setAttribute("stroke-width", style.strokeWidth);
|
||||||
|
|||||||
@ -35,7 +35,6 @@
|
|||||||
<use href="{% static "/images/logo-name.svg" %}#ep-logo-name" />
|
<use href="{% static "/images/logo-name.svg" %}#ep-logo-name" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<img src="{% static 'images/bayer-logo.svg' %}" width="40">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if not public_mode %}
|
{% if not public_mode %}
|
||||||
@ -89,23 +88,12 @@
|
|||||||
>Scenario</a
|
>Scenario</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<hr/>
|
|
||||||
<li>
|
|
||||||
<a href="{{ meta.server_url }}/group" id="scenarioLink"
|
|
||||||
>Group</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
{% if meta.url_contains_package and meta.current_package.get_classification_level_display == "Restricted" %}
|
|
||||||
<img src="{% static 'images/restricted_mid.png' %}" width="200">
|
|
||||||
{% elif meta.url_contains_package and meta.current_package.get_classification_level_display == "Secret" %}
|
|
||||||
<img src="{% static 'images/secret_mid.png' %}" width="120">
|
|
||||||
{% endif %}
|
|
||||||
{% if not public_mode %}
|
{% if not public_mode %}
|
||||||
<a id="search-trigger" role="button" class="cursor-pointer">
|
<a id="search-trigger" role="button" class="cursor-pointer">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
x-data="modalForm({ state: { selectedEdge: '', imageUrl: '' } })"
|
x-data="modalForm({ state: { selectedEdge: '', imageUrl: '' } })"
|
||||||
@modal-opened.window="
|
@modal-opened.window="
|
||||||
const links = d3.selectAll('path.highlighted');
|
const links = d3.selectAll('path.highlighted');
|
||||||
|
|
||||||
if (!links.empty()) {
|
if (!links.empty()) {
|
||||||
const el = links.node();
|
const el = links.node();
|
||||||
const selectElement = document.getElementById('delete_pathway_edge_edges');
|
const selectElement = document.getElementById('delete_pathway_edge_edges');
|
||||||
@ -18,9 +17,7 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectElement.dispatchEvent(new Event('change'));
|
selectElement.dispatchEvent(new Event('change'));
|
||||||
|
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@close="reset()"
|
@close="reset()"
|
||||||
|
|||||||
@ -6,17 +6,14 @@
|
|||||||
x-data="modalForm({ state: { selectedNode: '', imageUrl: '' } })"
|
x-data="modalForm({ state: { selectedNode: '', imageUrl: '' } })"
|
||||||
@modal-opened.window="
|
@modal-opened.window="
|
||||||
const el = d3.select('circle.highlighted').node();
|
const el = d3.select('circle.highlighted').node();
|
||||||
|
|
||||||
if (el !== null) {
|
if (el !== null) {
|
||||||
const selectElement = document.getElementById('delete_pathway_node_nodes');
|
const selectElement = document.getElementById('delete_pathway_node_nodes');
|
||||||
|
|
||||||
for (let option of selectElement.options) {
|
for (let option of selectElement.options) {
|
||||||
if (option.value === el.__data__.url) {
|
if (option.value === el.__data__.url) {
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectElement.dispatchEvent(new Event('change'));
|
selectElement.dispatchEvent(new Event('change'));
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
|||||||
@ -92,7 +92,7 @@
|
|||||||
<div class="flex flex-wrap items-center justify-center gap-4">
|
<div class="flex flex-wrap items-center justify-center gap-4">
|
||||||
{% for educt in edge.start_nodes.all %}
|
{% for educt in edge.start_nodes.all %}
|
||||||
<a href="{{ educt.url }}" class="btn btn-outline btn-sm"
|
<a href="{{ educt.url }}" class="btn btn-outline btn-sm"
|
||||||
>{{ educt.name }}</a
|
>{{ educt.get_name }}</a
|
||||||
>
|
>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<svg
|
<svg
|
||||||
@ -112,7 +112,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
{% for product in edge.end_nodes.all %}
|
{% for product in edge.end_nodes.all %}
|
||||||
<a href="{{ product.url }}" class="btn btn-outline btn-sm"
|
<a href="{{ product.url }}" class="btn btn-outline btn-sm"
|
||||||
>{{ product.name }}</a
|
>{{ product.get_name }}</a
|
||||||
>
|
>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -56,7 +56,9 @@
|
|||||||
<ul class="menu bg-base-200 rounded-box">
|
<ul class="menu bg-base-200 rounded-box">
|
||||||
{% for um in group.user_member.all %}
|
{% for um in group.user_member.all %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% if not user.is_superuser %}{{ um.url }}{% else %}{{ "#" }}{% endif %}" class="hover:bg-base-300"
|
<a
|
||||||
|
href="{% if user.is_superuser %}{{ um.url }}{% else %}{{ "#" }}{% endif %}"
|
||||||
|
class="hover:bg-base-300"
|
||||||
>{{ um.username }}
|
>{{ um.username }}
|
||||||
{% if not um.is_active %}<i>(inactive)</i>{% endif %}</a
|
{% if not um.is_active %}<i>(inactive)</i>{% endif %}</a
|
||||||
>
|
>
|
||||||
|
|||||||
@ -72,6 +72,15 @@
|
|||||||
stroke: red;
|
stroke: red;
|
||||||
stroke-width: 3px;
|
stroke-width: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inedge {
|
||||||
|
stroke: red;
|
||||||
|
stroke-width: 3px;
|
||||||
|
}
|
||||||
|
.outedge {
|
||||||
|
stroke: green;
|
||||||
|
stroke-width: 3px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<script src="{% static 'js/pw.js' %}"></script>
|
<script src="{% static 'js/pw.js' %}"></script>
|
||||||
|
|
||||||
@ -111,7 +120,7 @@
|
|||||||
<div class="collapse-title text-xl font-medium">
|
<div class="collapse-title text-xl font-medium">
|
||||||
Graphical Representation
|
Graphical Representation
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse-content ">
|
<div class="collapse-content">
|
||||||
<div class="bg-base-100 mb-2 rounded-lg p-2">
|
<div class="bg-base-100 mb-2 rounded-lg p-2">
|
||||||
<div class="navbar bg-base-100 rounded-lg">
|
<div class="navbar bg-base-100 rounded-lg">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
|
|||||||
@ -158,7 +158,6 @@
|
|||||||
document.getElementById("predict-submit-button").innerText = "Predict";
|
document.getElementById("predict-submit-button").innerText = "Predict";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const radioButtons = document.querySelectorAll('input[name="predict"]');
|
const radioButtons = document.querySelectorAll('input[name="predict"]');
|
||||||
radioButtons.forEach(radio => {
|
radioButtons.forEach(radio => {
|
||||||
radio.addEventListener('change', radioChange);
|
radio.addEventListener('change', radioChange);
|
||||||
@ -213,7 +212,13 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const button = this;
|
const button = this;
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
|
|
||||||
|
// Set text depending on mode
|
||||||
|
if (document.getElementById("predict-submit-button").innerText === "Build") {
|
||||||
|
button.textContent = "Building...";
|
||||||
|
} else {
|
||||||
button.textContent = "Predicting...";
|
button.textContent = "Predicting...";
|
||||||
|
}
|
||||||
|
|
||||||
// Get SMILES from either input or Ketcher
|
// Get SMILES from either input or Ketcher
|
||||||
const smilesInput = document.getElementById("predict-smiles");
|
const smilesInput = document.getElementById("predict-smiles");
|
||||||
|
|||||||
@ -110,6 +110,8 @@
|
|||||||
<div
|
<div
|
||||||
class="text-base-content/50 flex items-center justify-center space-x-6 text-sm"
|
class="text-base-content/50 flex items-center justify-center space-x-6 text-sm"
|
||||||
>
|
>
|
||||||
|
<a href="/legal" class="link link-hover">Legal</a>
|
||||||
|
<span class="text-base-content/30">•</span>
|
||||||
<a href="/terms" class="link link-hover">Terms of Use</a>
|
<a href="/terms" class="link link-hover">Terms of Use</a>
|
||||||
<span class="text-base-content/30">•</span>
|
<span class="text-base-content/30">•</span>
|
||||||
<a href="/privacy" class="link link-hover">Privacy Policy</a>
|
<a href="/privacy" class="link link-hover">Privacy Policy</a>
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
|
|
||||||
|
from epdb.exceptions import InvalidSMILESException
|
||||||
from epdb.logic import PackageManager
|
from epdb.logic import PackageManager
|
||||||
from epdb.models import Compound, User, CompoundStructure
|
from epdb.models import Compound, User, CompoundStructure
|
||||||
|
|
||||||
@ -33,13 +35,13 @@ class CompoundTest(TestCase):
|
|||||||
self.assertEqual(c.description, "No Desc")
|
self.assertEqual(c.description, "No Desc")
|
||||||
|
|
||||||
def test_missing_smiles(self):
|
def test_missing_smiles(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(InvalidSMILESException):
|
||||||
_ = Compound.create(self.package, smiles=None, name="Afoxolaner", description="No Desc")
|
_ = Compound.create(self.package, smiles=None, name="Afoxolaner", description="No Desc")
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(InvalidSMILESException):
|
||||||
_ = Compound.create(self.package, smiles="", name="Afoxolaner", description="No Desc")
|
_ = Compound.create(self.package, smiles="", name="Afoxolaner", description="No Desc")
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(InvalidSMILESException):
|
||||||
_ = Compound.create(self.package, smiles=" ", name="Afoxolaner", description="No Desc")
|
_ = Compound.create(self.package, smiles=" ", name="Afoxolaner", description="No Desc")
|
||||||
|
|
||||||
def test_smiles_are_trimmed(self):
|
def test_smiles_are_trimmed(self):
|
||||||
@ -96,7 +98,7 @@ class CompoundTest(TestCase):
|
|||||||
self.assertEqual(len(self.package.compounds), 1)
|
self.assertEqual(len(self.package.compounds), 1)
|
||||||
|
|
||||||
def test_wrong_smiles(self):
|
def test_wrong_smiles(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(InvalidSMILESException):
|
||||||
_ = Compound.create(
|
_ = Compound.create(
|
||||||
self.package,
|
self.package,
|
||||||
smiles="C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F",
|
smiles="C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F",
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
|
|
||||||
|
from epdb.exceptions import InvalidSMILESException
|
||||||
from epdb.logic import PackageManager
|
from epdb.logic import PackageManager
|
||||||
from epdb.models import Compound, User, Reaction, Rule
|
from epdb.models import Compound, User, Reaction, Rule
|
||||||
|
|
||||||
@ -163,7 +164,7 @@ class ReactionTest(TestCase):
|
|||||||
self.assertEqual(len(self.package.reactions), 1)
|
self.assertEqual(len(self.package.reactions), 1)
|
||||||
|
|
||||||
def test_wrong_smiles(self):
|
def test_wrong_smiles(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(InvalidSMILESException):
|
||||||
_ = Reaction.create(
|
_ = Reaction.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name="Eawag BBD reaction r0001",
|
name="Eawag BBD reaction r0001",
|
||||||
|
|||||||
@ -46,6 +46,14 @@ class ModelViewTest(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
expected = [
|
expected = [
|
||||||
|
{
|
||||||
|
"products": [["CCN(CC)C(=O)C1=CC(C=O)=CC=C1"]],
|
||||||
|
"probability": 0.75,
|
||||||
|
"btrule": {
|
||||||
|
"url": "http://localhost:8000/package/1869d3f0-60bb-41fd-b6f8-afa75ffb09d3/simple-ambit-rule/2f2e0c39-e109-4836-959f-2bda2524f022",
|
||||||
|
"name": "bt0001-3568",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"products": [["O=C(O)C1=CC(CO)=CC=C1", "CCNCC"]],
|
"products": [["O=C(O)C1=CC(CO)=CC=C1", "CCNCC"]],
|
||||||
"probability": 0.25,
|
"probability": 0.25,
|
||||||
@ -62,14 +70,6 @@ class ModelViewTest(TestCase):
|
|||||||
"name": "bt0243-4301",
|
"name": "bt0243-4301",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"products": [["CCN(CC)C(=O)C1=CC(C=O)=CC=C1"]],
|
|
||||||
"probability": 0.75,
|
|
||||||
"btrule": {
|
|
||||||
"url": "http://localhost:8000/package/1869d3f0-60bb-41fd-b6f8-afa75ffb09d3/simple-ambit-rule/2f2e0c39-e109-4836-959f-2bda2524f022",
|
|
||||||
"name": "bt0001-3568",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
actual = response.json()["pred"]
|
actual = response.json()["pred"]
|
||||||
|
|||||||
@ -6,12 +6,15 @@ import logging
|
|||||||
import uuid
|
import uuid
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Dict, List, TYPE_CHECKING
|
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from ninja import Schema
|
||||||
|
from pydantic import HttpUrl
|
||||||
|
|
||||||
from epdb.models import (
|
from epdb.models import (
|
||||||
|
AdditionalInformation,
|
||||||
Compound,
|
Compound,
|
||||||
CompoundStructure,
|
CompoundStructure,
|
||||||
Edge,
|
Edge,
|
||||||
@ -28,7 +31,6 @@ from epdb.models import (
|
|||||||
Rule,
|
Rule,
|
||||||
RuleBasedRelativeReasoning,
|
RuleBasedRelativeReasoning,
|
||||||
Scenario,
|
Scenario,
|
||||||
SequentialRule,
|
|
||||||
Setting,
|
Setting,
|
||||||
SimpleAmbitRule,
|
SimpleAmbitRule,
|
||||||
SimpleRDKitRule,
|
SimpleRDKitRule,
|
||||||
@ -43,408 +45,235 @@ if TYPE_CHECKING:
|
|||||||
from epdb.logic import SPathway
|
from epdb.logic import SPathway
|
||||||
|
|
||||||
|
|
||||||
class PackageExporter:
|
class LicenseExportSchema(Schema):
|
||||||
def __init__(
|
cc_string: str
|
||||||
self,
|
link: HttpUrl
|
||||||
package: Package,
|
image_link: HttpUrl
|
||||||
include_models: bool = False,
|
|
||||||
include_external_identifiers: bool = True,
|
|
||||||
):
|
|
||||||
self._raw_package = package
|
|
||||||
self.include_modes = include_models
|
|
||||||
self.include_external_identifiers = include_external_identifiers
|
|
||||||
|
|
||||||
def do_export(self):
|
|
||||||
return PackageExporter._export_package_as_json(
|
##############
|
||||||
self._raw_package, self.include_modes, self.include_external_identifiers
|
# RefSchemas #
|
||||||
)
|
##############
|
||||||
|
class RefExportSchema(Schema):
|
||||||
|
uuid: str
|
||||||
|
url: str
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _export_package_as_json(
|
def resolve_uuid(obj):
|
||||||
package: Package, include_models: bool = False, include_external_identifiers: bool = True
|
value = obj.get("uuid") if isinstance(obj, dict) else obj.uuid
|
||||||
) -> Dict[str, Any]:
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
class RefCompoundExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RefCompoundStructureExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RefReactionExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RefRuleExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RefNodeExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RefEdgeExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RefPathwayExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RefScenarioExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
############
|
||||||
|
# Compound #
|
||||||
|
############
|
||||||
|
class CompoundExportSchema(RefCompoundExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
default_structure: RefCompoundStructureExportSchema
|
||||||
|
structures: List["CompoundStructureExportSchema"]
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundStructureExportSchema(RefCompoundStructureExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
smiles: str
|
||||||
|
molfile: Optional[str]
|
||||||
|
normalized_structure: bool
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
############
|
||||||
|
# Reaction #
|
||||||
|
############
|
||||||
|
class ReactionExportSchema(RefReactionExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
educts: List[RefCompoundStructureExportSchema]
|
||||||
|
products: List[RefCompoundStructureExportSchema]
|
||||||
|
rules: List[RefRuleExportSchema]
|
||||||
|
multi_step: bool
|
||||||
|
medline_references: List[str] | None
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
#########
|
||||||
|
# Rules #
|
||||||
|
#########
|
||||||
|
class RuleExportSchema(RefRuleExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
smirks: str
|
||||||
|
reactant_filter_smarts: Optional[str]
|
||||||
|
product_filter_smarts: Optional[str]
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class ParallelRuleExportSchema(RefRuleExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
simple_rules: List[RefRuleExportSchema]
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
###########################
|
||||||
|
# Pathway / Nodes / Edges #
|
||||||
|
###########################
|
||||||
|
class NodeExportSchema(RefNodeExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
default_node_label: RefCompoundStructureExportSchema
|
||||||
|
node_labels: List[RefCompoundStructureExportSchema]
|
||||||
|
depth: int
|
||||||
|
stereo_removed: bool
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class EdgeExportSchema(RefEdgeExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
edge_label: RefReactionExportSchema
|
||||||
|
start_nodes: List[RefNodeExportSchema]
|
||||||
|
end_nodes: List[RefNodeExportSchema]
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class PathwayExportSchema(RefPathwayExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
predicted: bool
|
||||||
|
nodes: List[NodeExportSchema]
|
||||||
|
edges: List[EdgeExportSchema]
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class ScenarioExportSchema(RefScenarioExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
scenario_date: str
|
||||||
|
scenario_type: str
|
||||||
|
|
||||||
|
|
||||||
|
class AdditionalInformationExportSchema(RefExportSchema):
|
||||||
|
type: str
|
||||||
|
data: dict
|
||||||
|
scenario: RefScenarioExportSchema | None = None
|
||||||
|
attach_object: RefExportSchema | None = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_attach_object(obj):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
if obj.get("attach_object") is None:
|
||||||
|
return None
|
||||||
|
return RefExportSchema.model_validate(obj["attach_object"])
|
||||||
|
return obj.content_object
|
||||||
|
|
||||||
|
|
||||||
|
###########
|
||||||
|
# Package #
|
||||||
|
###########
|
||||||
|
class PackageExportSchema(Schema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
uuid: str
|
||||||
|
reviewed: bool
|
||||||
|
license: LicenseExportSchema | None
|
||||||
|
compounds: List[CompoundExportSchema]
|
||||||
|
reactions: List[ReactionExportSchema]
|
||||||
|
simple_rules: List[RuleExportSchema]
|
||||||
|
composite_rules: List[ParallelRuleExportSchema]
|
||||||
|
pathways: List[PathwayExportSchema]
|
||||||
|
scenarios: List[ScenarioExportSchema]
|
||||||
|
additional_information: List[AdditionalInformationExportSchema]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_uuid(obj):
|
||||||
|
value = obj.get("uuid") if isinstance(obj, dict) else obj.uuid
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_simple_rules(obj):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
result = []
|
||||||
|
for r in obj.get("simple_rules", []):
|
||||||
|
result.append(RuleExportSchema.model_validate(r))
|
||||||
|
return result
|
||||||
|
return SimpleAmbitRule.objects.filter(package=obj)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_composite_rules(obj):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
result = []
|
||||||
|
for r in obj.get("composite_rules", []):
|
||||||
|
result.append(ParallelRuleExportSchema.model_validate(r))
|
||||||
|
return result
|
||||||
|
return ParallelRule.objects.filter(package=obj)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_additional_information(obj):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
result = []
|
||||||
|
for ai in obj.get("additional_information", []):
|
||||||
|
result.append(AdditionalInformationExportSchema.model_validate(ai))
|
||||||
|
return result
|
||||||
|
return AdditionalInformation.objects.filter(package=obj)
|
||||||
|
|
||||||
|
|
||||||
|
class PackageExporter:
|
||||||
|
def __init__(self, package: Package):
|
||||||
|
self._raw_package = package
|
||||||
|
|
||||||
|
def do_export(self):
|
||||||
|
return PackageExporter._export_package_as_json(self._raw_package)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _export_package_as_json(package: Package) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Dumps a Package and all its related objects as JSON.
|
Dumps a Package and all its related objects as JSON.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
package: The Package instance to dump
|
package: The Package instance to dump
|
||||||
include_models: Whether to include EPModel objects
|
|
||||||
include_external_identifiers: Whether to include external identifiers
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict containing the complete package data as JSON-serializable structure
|
Dict containing the complete package data as JSON-serializable structure
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def serialize_base_object(
|
data = PackageExportSchema.from_orm(package)
|
||||||
obj, include_aliases: bool = True, include_scenarios: bool = True
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""Serialize common EnviPathModel fields"""
|
|
||||||
base_dict = {
|
|
||||||
"uuid": str(obj.uuid),
|
|
||||||
"name": obj.name,
|
|
||||||
"description": obj.description,
|
|
||||||
"url": obj.url,
|
|
||||||
"kv": obj.kv,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add aliases if the object has them
|
return data.model_dump(mode="json")
|
||||||
if include_aliases and hasattr(obj, "aliases"):
|
|
||||||
base_dict["aliases"] = obj.aliases
|
|
||||||
|
|
||||||
# Add scenarios if the object has them
|
|
||||||
if include_scenarios and hasattr(obj, "scenarios"):
|
|
||||||
base_dict["scenarios"] = [
|
|
||||||
{"uuid": str(s.uuid), "url": s.url} for s in obj.scenarios.all()
|
|
||||||
]
|
|
||||||
|
|
||||||
return base_dict
|
|
||||||
|
|
||||||
def serialize_external_identifiers(obj) -> List[Dict[str, Any]]:
|
|
||||||
"""Serialize external identifiers for an object"""
|
|
||||||
if not include_external_identifiers or not hasattr(obj, "external_identifiers"):
|
|
||||||
return []
|
|
||||||
|
|
||||||
identifiers = []
|
|
||||||
for ext_id in obj.external_identifiers.all():
|
|
||||||
identifier_dict = {
|
|
||||||
"uuid": str(ext_id.uuid),
|
|
||||||
"database": {
|
|
||||||
"uuid": str(ext_id.database.uuid),
|
|
||||||
"name": ext_id.database.name,
|
|
||||||
"base_url": ext_id.database.base_url,
|
|
||||||
},
|
|
||||||
"identifier_value": ext_id.identifier_value,
|
|
||||||
"url": ext_id.url,
|
|
||||||
"is_primary": ext_id.is_primary,
|
|
||||||
}
|
|
||||||
identifiers.append(identifier_dict)
|
|
||||||
return identifiers
|
|
||||||
|
|
||||||
# Start with the package itself
|
|
||||||
result = serialize_base_object(package, include_aliases=True, include_scenarios=True)
|
|
||||||
result["reviewed"] = package.reviewed
|
|
||||||
|
|
||||||
# # Add license information
|
|
||||||
# if package.license:
|
|
||||||
# result['license'] = {
|
|
||||||
# 'uuid': str(package.license.uuid),
|
|
||||||
# 'name': package.license.name,
|
|
||||||
# 'link': package.license.link,
|
|
||||||
# 'image_link': package.license.image_link
|
|
||||||
# }
|
|
||||||
# else:
|
|
||||||
# result['license'] = None
|
|
||||||
|
|
||||||
# Initialize collections
|
|
||||||
result.update(
|
|
||||||
{
|
|
||||||
"compounds": [],
|
|
||||||
"structures": [],
|
|
||||||
"rules": {"simple_rules": [], "parallel_rules": [], "sequential_rules": []},
|
|
||||||
"reactions": [],
|
|
||||||
"pathways": [],
|
|
||||||
"nodes": [],
|
|
||||||
"edges": [],
|
|
||||||
"scenarios": [],
|
|
||||||
"models": [],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"Exporting package: {package.name}")
|
|
||||||
|
|
||||||
# Export compounds
|
|
||||||
print("Exporting compounds...")
|
|
||||||
for compound in package.compounds.prefetch_related("default_structure").order_by("url"):
|
|
||||||
compound_dict = serialize_base_object(
|
|
||||||
compound, include_aliases=True, include_scenarios=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if compound.default_structure:
|
|
||||||
compound_dict["default_structure"] = {
|
|
||||||
"uuid": str(compound.default_structure.uuid),
|
|
||||||
"url": compound.default_structure.url,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
compound_dict["default_structure"] = None
|
|
||||||
|
|
||||||
compound_dict["external_identifiers"] = serialize_external_identifiers(compound)
|
|
||||||
result["compounds"].append(compound_dict)
|
|
||||||
|
|
||||||
# Export compound structures
|
|
||||||
print("Exporting compound structures...")
|
|
||||||
compound_structures = (
|
|
||||||
CompoundStructure.objects.filter(compound__package=package)
|
|
||||||
.select_related("compound")
|
|
||||||
.order_by("url")
|
|
||||||
)
|
|
||||||
|
|
||||||
for structure in compound_structures:
|
|
||||||
structure_dict = serialize_base_object(
|
|
||||||
structure, include_aliases=True, include_scenarios=True
|
|
||||||
)
|
|
||||||
structure_dict.update(
|
|
||||||
{
|
|
||||||
"compound": {
|
|
||||||
"uuid": str(structure.compound.uuid),
|
|
||||||
"url": structure.compound.url,
|
|
||||||
},
|
|
||||||
"smiles": structure.smiles,
|
|
||||||
"canonical_smiles": structure.canonical_smiles,
|
|
||||||
"inchikey": structure.inchikey,
|
|
||||||
"normalized_structure": structure.normalized_structure,
|
|
||||||
"external_identifiers": serialize_external_identifiers(structure),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
result["structures"].append(structure_dict)
|
|
||||||
|
|
||||||
# Export rules
|
|
||||||
print("Exporting rules...")
|
|
||||||
|
|
||||||
# Simple rules (including SimpleAmbitRule and SimpleRDKitRule)
|
|
||||||
for rule in SimpleRule.objects.filter(package=package).order_by("url"):
|
|
||||||
rule_dict = serialize_base_object(rule, include_aliases=True, include_scenarios=True)
|
|
||||||
|
|
||||||
# Add specific fields for SimpleAmbitRule
|
|
||||||
if isinstance(rule, SimpleAmbitRule):
|
|
||||||
rule_dict.update(
|
|
||||||
{
|
|
||||||
"rule_type": "SimpleAmbitRule",
|
|
||||||
"smirks": rule.smirks,
|
|
||||||
"reactant_filter_smarts": rule.reactant_filter_smarts or "",
|
|
||||||
"product_filter_smarts": rule.product_filter_smarts or "",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
elif isinstance(rule, SimpleRDKitRule):
|
|
||||||
rule_dict.update(
|
|
||||||
{"rule_type": "SimpleRDKitRule", "reaction_smarts": rule.reaction_smarts}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
rule_dict["rule_type"] = "SimpleRule"
|
|
||||||
|
|
||||||
result["rules"]["simple_rules"].append(rule_dict)
|
|
||||||
|
|
||||||
# Parallel rules
|
|
||||||
for rule in (
|
|
||||||
ParallelRule.objects.filter(package=package)
|
|
||||||
.prefetch_related("simple_rules")
|
|
||||||
.order_by("url")
|
|
||||||
):
|
|
||||||
rule_dict = serialize_base_object(rule, include_aliases=True, include_scenarios=True)
|
|
||||||
rule_dict["rule_type"] = "ParallelRule"
|
|
||||||
rule_dict["simple_rules"] = [
|
|
||||||
{"uuid": str(sr.uuid), "url": sr.url} for sr in rule.simple_rules.all()
|
|
||||||
]
|
|
||||||
result["rules"]["parallel_rules"].append(rule_dict)
|
|
||||||
|
|
||||||
# Sequential rules
|
|
||||||
for rule in (
|
|
||||||
SequentialRule.objects.filter(package=package)
|
|
||||||
.prefetch_related("simple_rules")
|
|
||||||
.order_by("url")
|
|
||||||
):
|
|
||||||
rule_dict = serialize_base_object(rule, include_aliases=True, include_scenarios=True)
|
|
||||||
rule_dict["rule_type"] = "SequentialRule"
|
|
||||||
rule_dict["simple_rules"] = [
|
|
||||||
{
|
|
||||||
"uuid": str(sr.uuid),
|
|
||||||
"url": sr.url,
|
|
||||||
"order_index": sr.sequentialruleordering_set.get(
|
|
||||||
sequential_rule=rule
|
|
||||||
).order_index,
|
|
||||||
}
|
|
||||||
for sr in rule.simple_rules.all()
|
|
||||||
]
|
|
||||||
result["rules"]["sequential_rules"].append(rule_dict)
|
|
||||||
|
|
||||||
# Export reactions
|
|
||||||
print("Exporting reactions...")
|
|
||||||
for reaction in package.reactions.prefetch_related("educts", "products", "rules").order_by(
|
|
||||||
"url"
|
|
||||||
):
|
|
||||||
reaction_dict = serialize_base_object(
|
|
||||||
reaction, include_aliases=True, include_scenarios=True
|
|
||||||
)
|
|
||||||
reaction_dict.update(
|
|
||||||
{
|
|
||||||
"educts": [{"uuid": str(e.uuid), "url": e.url} for e in reaction.educts.all()],
|
|
||||||
"products": [
|
|
||||||
{"uuid": str(p.uuid), "url": p.url} for p in reaction.products.all()
|
|
||||||
],
|
|
||||||
"rules": [{"uuid": str(r.uuid), "url": r.url} for r in reaction.rules.all()],
|
|
||||||
"multi_step": reaction.multi_step,
|
|
||||||
"medline_references": reaction.medline_references,
|
|
||||||
"external_identifiers": serialize_external_identifiers(reaction),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
result["reactions"].append(reaction_dict)
|
|
||||||
|
|
||||||
# Export pathways
|
|
||||||
print("Exporting pathways...")
|
|
||||||
for pathway in package.pathways.order_by("url"):
|
|
||||||
pathway_dict = serialize_base_object(
|
|
||||||
pathway, include_aliases=True, include_scenarios=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add setting reference if exists
|
|
||||||
if hasattr(pathway, "setting") and pathway.setting:
|
|
||||||
pathway_dict["setting"] = {
|
|
||||||
"uuid": str(pathway.setting.uuid),
|
|
||||||
"url": pathway.setting.url,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
pathway_dict["setting"] = None
|
|
||||||
|
|
||||||
result["pathways"].append(pathway_dict)
|
|
||||||
|
|
||||||
# Export nodes
|
|
||||||
print("Exporting nodes...")
|
|
||||||
pathway_nodes = (
|
|
||||||
Node.objects.filter(pathway__package=package)
|
|
||||||
.select_related("pathway", "default_node_label")
|
|
||||||
.prefetch_related("node_labels", "out_edges")
|
|
||||||
.order_by("url")
|
|
||||||
)
|
|
||||||
|
|
||||||
for node in pathway_nodes:
|
|
||||||
node_dict = serialize_base_object(node, include_aliases=True, include_scenarios=True)
|
|
||||||
node_dict.update(
|
|
||||||
{
|
|
||||||
"pathway": {"uuid": str(node.pathway.uuid), "url": node.pathway.url},
|
|
||||||
"default_node_label": {
|
|
||||||
"uuid": str(node.default_node_label.uuid),
|
|
||||||
"url": node.default_node_label.url,
|
|
||||||
},
|
|
||||||
"node_labels": [
|
|
||||||
{"uuid": str(label.uuid), "url": label.url}
|
|
||||||
for label in node.node_labels.all()
|
|
||||||
],
|
|
||||||
"out_edges": [
|
|
||||||
{"uuid": str(edge.uuid), "url": edge.url} for edge in node.out_edges.all()
|
|
||||||
],
|
|
||||||
"depth": node.depth,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
result["nodes"].append(node_dict)
|
|
||||||
|
|
||||||
# Export edges
|
|
||||||
print("Exporting edges...")
|
|
||||||
pathway_edges = (
|
|
||||||
Edge.objects.filter(pathway__package=package)
|
|
||||||
.select_related("pathway", "edge_label")
|
|
||||||
.prefetch_related("start_nodes", "end_nodes")
|
|
||||||
.order_by("url")
|
|
||||||
)
|
|
||||||
|
|
||||||
for edge in pathway_edges:
|
|
||||||
edge_dict = serialize_base_object(edge, include_aliases=True, include_scenarios=True)
|
|
||||||
edge_dict.update(
|
|
||||||
{
|
|
||||||
"pathway": {"uuid": str(edge.pathway.uuid), "url": edge.pathway.url},
|
|
||||||
"edge_label": {"uuid": str(edge.edge_label.uuid), "url": edge.edge_label.url},
|
|
||||||
"start_nodes": [
|
|
||||||
{"uuid": str(node.uuid), "url": node.url} for node in edge.start_nodes.all()
|
|
||||||
],
|
|
||||||
"end_nodes": [
|
|
||||||
{"uuid": str(node.uuid), "url": node.url} for node in edge.end_nodes.all()
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
result["edges"].append(edge_dict)
|
|
||||||
|
|
||||||
# Export scenarios
|
|
||||||
print("Exporting scenarios...")
|
|
||||||
for scenario in package.scenarios.order_by("url"):
|
|
||||||
scenario_dict = serialize_base_object(
|
|
||||||
scenario, include_aliases=False, include_scenarios=False
|
|
||||||
)
|
|
||||||
scenario_dict.update(
|
|
||||||
{
|
|
||||||
"scenario_date": scenario.scenario_date,
|
|
||||||
"scenario_type": scenario.scenario_type,
|
|
||||||
"parent": {"uuid": str(scenario.parent.uuid), "url": scenario.parent.url}
|
|
||||||
if scenario.parent
|
|
||||||
else None,
|
|
||||||
"additional_information": scenario.additional_information,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
result["scenarios"].append(scenario_dict)
|
|
||||||
|
|
||||||
# Export models
|
|
||||||
if include_models:
|
|
||||||
print("Exporting models...")
|
|
||||||
package_models = (
|
|
||||||
package.models.select_related("app_domain")
|
|
||||||
.prefetch_related("rule_packages", "data_packages", "eval_packages")
|
|
||||||
.order_by("url")
|
|
||||||
)
|
|
||||||
|
|
||||||
for model in package_models:
|
|
||||||
model_dict = serialize_base_object(
|
|
||||||
model, include_aliases=True, include_scenarios=False
|
|
||||||
)
|
|
||||||
|
|
||||||
# Common fields for PackageBasedModel
|
|
||||||
if hasattr(model, "rule_packages"):
|
|
||||||
model_dict.update(
|
|
||||||
{
|
|
||||||
"rule_packages": [
|
|
||||||
{"uuid": str(p.uuid), "url": p.url}
|
|
||||||
for p in model.rule_packages.all()
|
|
||||||
],
|
|
||||||
"data_packages": [
|
|
||||||
{"uuid": str(p.uuid), "url": p.url}
|
|
||||||
for p in model.data_packages.all()
|
|
||||||
],
|
|
||||||
"eval_packages": [
|
|
||||||
{"uuid": str(p.uuid), "url": p.url}
|
|
||||||
for p in model.eval_packages.all()
|
|
||||||
],
|
|
||||||
"threshold": model.threshold,
|
|
||||||
"eval_results": model.eval_results,
|
|
||||||
"model_status": model.model_status,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if model.app_domain:
|
|
||||||
model_dict["app_domain"] = {
|
|
||||||
"uuid": str(model.app_domain.uuid),
|
|
||||||
"url": model.app_domain.url,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
model_dict["app_domain"] = None
|
|
||||||
|
|
||||||
# Specific fields for different model types
|
|
||||||
if isinstance(model, RuleBasedRelativeReasoning):
|
|
||||||
model_dict.update(
|
|
||||||
{
|
|
||||||
"model_type": "RuleBasedRelativeReasoning",
|
|
||||||
"min_count": model.min_count,
|
|
||||||
"max_count": model.max_count,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
elif isinstance(model, MLRelativeReasoning):
|
|
||||||
model_dict["model_type"] = "MLRelativeReasoning"
|
|
||||||
elif isinstance(model, EnviFormer):
|
|
||||||
model_dict["model_type"] = "EnviFormer"
|
|
||||||
else:
|
|
||||||
model_dict["model_type"] = "EPModel"
|
|
||||||
|
|
||||||
result["models"].append(model_dict)
|
|
||||||
|
|
||||||
print(f"Export completed for package: {package.name}")
|
|
||||||
print(f"- Compounds: {len(result['compounds'])}")
|
|
||||||
print(f"- Structures: {len(result['structures'])}")
|
|
||||||
print(f"- Simple rules: {len(result['rules']['simple_rules'])}")
|
|
||||||
print(f"- Parallel rules: {len(result['rules']['parallel_rules'])}")
|
|
||||||
print(f"- Sequential rules: {len(result['rules']['sequential_rules'])}")
|
|
||||||
print(f"- Reactions: {len(result['reactions'])}")
|
|
||||||
print(f"- Pathways: {len(result['pathways'])}")
|
|
||||||
print(f"- Nodes: {len(result['nodes'])}")
|
|
||||||
print(f"- Edges: {len(result['edges'])}")
|
|
||||||
print(f"- Scenarios: {len(result['scenarios'])}")
|
|
||||||
print(f"- Models: {len(result['models'])}")
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class PackageImporter:
|
class PackageImporter:
|
||||||
|
|||||||
Reference in New Issue
Block a user