forked from enviPath/enviPy
Compare commits
37 Commits
1e43c298d2
...
develop-ba
| Author | SHA1 | Date | |
|---|---|---|---|
| 57f5ce089c | |||
| 28e5dbbcf5 | |||
| 8620f98082 | |||
| 4ee37b5c71 | |||
| c65be30b6b | |||
| b5e3c1957b | |||
| 3bb9220b22 | |||
| d06ed33196 | |||
| ea672ec91a | |||
| dfc1309fd3 | |||
| 6e2dbccd6c | |||
| b77fbc9b90 | |||
| ce1d709715 | |||
| b0004ea66e | |||
| 2fbffb4b3b | |||
| 365af14448 | |||
| 8017070de8 | |||
| a0ac79a897 | |||
| caeec2ab79 | |||
| 3525621281 | |||
| 9bc9f86ff1 | |||
| dba6514013 | |||
| a092d4a558 | |||
| 2502c020f7 | |||
| 6ab9180291 | |||
| 3657d14659 | |||
| ef6091d416 | |||
| 14cfc1e4d7 | |||
| 868bbf5c05 | |||
| be5ee1d1d7 | |||
| 20fd949dfd | |||
| c9b643fe6e | |||
| 1a9f1cf9af | |||
| c7c7e17e43 | |||
| 674e10c7fa | |||
| 8079b80d57 | |||
| 76e63fda2c |
17
Dockerfile
17
Dockerfile
@ -6,18 +6,23 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
build-essential \
|
build-essential \
|
||||||
libpq-dev \
|
libpq-dev \
|
||||||
curl \
|
curl \
|
||||||
openssh-client \
|
openssh-client \
|
||||||
git \
|
git \
|
||||||
nodejs \
|
ca-certificates \
|
||||||
npm \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install pnpm
|
# Install Node 22 + pnpm
|
||||||
RUN npm install -g pnpm
|
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends nodejs \
|
||||||
|
&& corepack enable \
|
||||||
|
&& corepack prepare pnpm@latest --activate \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
ENV PATH="/root/.local/bin:${PATH}"
|
ENV PATH="/root/.local/bin:${PATH}"
|
||||||
@ -30,13 +35,15 @@ 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:1.0 .
|
# docker build --ssh default -t envipath/envipy-bayer:1.0 .
|
||||||
RUN --mount=type=ssh \
|
RUN --mount=type=ssh \
|
||||||
uv sync --locked --extra ms-login --extra pepper-plugin
|
uv sync --locked --extra ms-login --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
|
||||||
|
|||||||
0
bayer/__init__.py
Normal file
0
bayer/__init__.py
Normal file
178
bayer/additional_information/__init__.py
Normal file
178
bayer/additional_information/__init__.py
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
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;;;;;;"))
|
||||||
19
bayer/admin.py
Normal file
19
bayer/admin.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
from .models import (
|
||||||
|
PESCompound,
|
||||||
|
PESStructure
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PESCompoundAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PESStructureAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(PESCompound, PESCompoundAdmin)
|
||||||
|
admin.site.register(PESStructure, PESStructureAdmin)
|
||||||
6
bayer/apps.py
Normal file
6
bayer/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class BayerConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'bayer'
|
||||||
40
bayer/epdb_hooks.py
Normal file
40
bayer/epdb_hooks.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
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",
|
||||||
|
)
|
||||||
35
bayer/migrations/0001_initial.py
Normal file
35
bayer/migrations/0001_initial.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-03-06 10:51
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Package',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('reviewed', models.BooleanField(default=False, verbose_name='Reviewstatus')),
|
||||||
|
('classification_level', models.IntegerField(choices=[(0, 'Internal'), (10, 'Restricted'), (20, 'Secret')], default=10)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'epdb_package',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
22
bayer/migrations/0002_initial.py
Normal file
22
bayer/migrations/0002_initial.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-03-06 10:51
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('bayer', '0001_initial'),
|
||||||
|
('epdb', '0019_remove_scenario_additional_information_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='package',
|
||||||
|
name='license',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.license', verbose_name='License'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
# Generated by Django 6.0.3 on 2026-04-17 21:22
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('bayer', '0002_initial'),
|
||||||
|
('epdb', '0023_alter_compoundstructure_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PESCompound',
|
||||||
|
fields=[
|
||||||
|
('compound_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.compound')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('epdb.compound',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PESStructure',
|
||||||
|
fields=[
|
||||||
|
('compoundstructure_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.compoundstructure')),
|
||||||
|
('pes_link', models.URLField(verbose_name='PES Link')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('epdb.compoundstructure',),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='package',
|
||||||
|
name='data_pool',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.group', verbose_name='Data pool'),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
bayer/migrations/__init__.py
Normal file
0
bayer/migrations/__init__.py
Normal file
237
bayer/models.py
Normal file
237
bayer/models.py
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
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,
|
||||||
|
molfile: 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.molfile = molfile
|
||||||
|
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",
|
||||||
|
}
|
||||||
9
bayer/templates/actions/collections/new_pes.html
Normal file
9
bayer/templates/actions/collections/new_pes.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% 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 %}
|
||||||
8
bayer/templates/actions/objects/pathway_add_pes.html
Normal file
8
bayer/templates/actions/objects/pathway_add_pes.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<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>
|
||||||
175
bayer/templates/modals/collections/new_package_modal.html
Normal file
175
bayer/templates/modals/collections/new_package_modal.html
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
{% 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>
|
||||||
174
bayer/templates/modals/collections/new_pes_modal.html
Normal file
174
bayer/templates/modals/collections/new_pes_modal.html
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
{% 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>
|
||||||
174
bayer/templates/modals/objects/add_pathway_pes_node_modal.html
Normal file
174
bayer/templates/modals/objects/add_pathway_pes_node_modal.html
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
{% 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>
|
||||||
23
bayer/templates/objects/compound_structure_viz.html
Normal file
23
bayer/templates/objects/compound_structure_viz.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{% 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">
|
||||||
|
<p>
|
||||||
|
<a href="{{ compound_structure.pes_link }}" class="hover:bg-base-200">{{ compound_structure.pes_link }}</a>
|
||||||
|
</p>
|
||||||
|
</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 %}
|
||||||
23
bayer/templates/objects/compound_viz.html
Normal file
23
bayer/templates/objects/compound_viz.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{% 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">
|
||||||
|
<p>
|
||||||
|
<a href="{{ compound.default_structure.pes_link }}" class="hover:bg-base-200">{{ compound.default_structure.pes_link }}</a>
|
||||||
|
</p>
|
||||||
|
</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 %}
|
||||||
23
bayer/templates/objects/node_viz.html
Normal file
23
bayer/templates/objects/node_viz.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{% 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">
|
||||||
|
<p>
|
||||||
|
<a href="{{ node.default_node_label.pes_link }}" class="hover:bg-base-200">{{ node.default_node_label.pes_link }}</a>
|
||||||
|
</p>
|
||||||
|
</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 %}
|
||||||
97
bayer/templates/objects/package.html
Normal file
97
bayer/templates/objects/package.html
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
{% extends "framework_modern.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% block action_modals %}
|
||||||
|
{% include "modals/objects/edit_package_modal.html" %}
|
||||||
|
{% include "modals/objects/edit_package_permissions_modal.html" %}
|
||||||
|
{% include "modals/objects/publish_package_modal.html" %}
|
||||||
|
{% include "modals/objects/set_license_modal.html" %}
|
||||||
|
{% include "modals/objects/export_package_modal.html" %}
|
||||||
|
{% include "modals/objects/generic_delete_modal.html" %}
|
||||||
|
{% endblock action_modals %}
|
||||||
|
|
||||||
|
<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 %}
|
||||||
154
bayer/templates/static/login.html
Normal file
154
bayer/templates/static/login.html
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
{% 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 %}
|
||||||
3
bayer/tests.py
Normal file
3
bayer/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
0
bayer/tests/__init__.py
Normal file
0
bayer/tests/__init__.py
Normal file
0
bayer/tests/pes/__init__.py
Normal file
0
bayer/tests/pes/__init__.py
Normal file
174
bayer/tests/pes/test_pes.py
Normal file
174
bayer/tests/pes/test_pes.py
Normal file
File diff suppressed because one or more lines are too long
19
bayer/urls.py
Normal file
19
bayer/urls.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from django.urls import re_path
|
||||||
|
|
||||||
|
from . import views as v
|
||||||
|
|
||||||
|
UUID = "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
re_path(r"^depict_pes$", v.visualize_pes, name="depict_pes"),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<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",
|
||||||
|
),
|
||||||
|
]
|
||||||
177
bayer/views.py
Normal file
177
bayer/views.py
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
import base64
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from django.conf import settings as s
|
||||||
|
from django.http import HttpResponse, HttpResponseBadRequest
|
||||||
|
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, error
|
||||||
|
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:
|
||||||
|
return error(
|
||||||
|
request,
|
||||||
|
f'Creation of PESs for package {current_package.name} failed!',
|
||||||
|
"Creating PESs for internal packages is not allowed.",
|
||||||
|
)
|
||||||
|
|
||||||
|
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 HttpResponseBadRequest(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 HttpResponseBadRequest("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 HttpResponseBadRequest(
|
||||||
|
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 HttpResponseBadRequest("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:
|
||||||
|
return error(
|
||||||
|
request,
|
||||||
|
f'Creation of PESs for package {current_package.name} failed!',
|
||||||
|
"Creating PESs for internal packages is not allowed.",
|
||||||
|
)
|
||||||
|
|
||||||
|
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 HttpResponseBadRequest(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 HttpResponseBadRequest("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 HttpResponseBadRequest(
|
||||||
|
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 HttpResponseBadRequest("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 is None:
|
||||||
|
token = pes_url.split('/')[-1] == 'dummy'
|
||||||
|
|
||||||
|
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")
|
||||||
198
bb4g/__init__.py
Normal file
198
bb4g/__init__.py
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
while not started:
|
||||||
|
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]:
|
||||||
|
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 < 100:
|
||||||
|
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(3)
|
||||||
|
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
|
||||||
@ -254,7 +254,14 @@ class Classifier(Plugin):
|
|||||||
def parse_config(cls, data: dict | None = None) -> EnviPyModel | None:
|
def parse_config(cls, data: dict | None = None) -> EnviPyModel | None:
|
||||||
if cls.Config is None:
|
if cls.Config is None:
|
||||||
return None
|
return None
|
||||||
return cls.Config(**(data or {}))
|
|
||||||
|
# remove empty strings a.k.a unset params to not overwrite defaults
|
||||||
|
cpy = {}
|
||||||
|
if data is not None:
|
||||||
|
for k, v in data.items():
|
||||||
|
if v != "":
|
||||||
|
cpy[k] = v
|
||||||
|
return cls.Config(**cpy)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, data: dict | None = None):
|
def create(cls, data: dict | None = None):
|
||||||
|
|||||||
@ -1,26 +1,43 @@
|
|||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: postgres:18
|
image: postgres:18
|
||||||
container_name: envipath-postgres
|
container_name: eppostgres
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
POSTGRES_DB: envipath
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql
|
- ep_bayer_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: envipath-redis
|
container_name: epredis
|
||||||
ports:
|
ports:
|
||||||
- "6379:6379"
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- ep_bayer_redis_data:/data
|
||||||
|
|
||||||
|
biotransformer3:
|
||||||
|
image: git.envipath.com/envipath/biotransformer3:1.0
|
||||||
|
container_name: epbiotransformer3
|
||||||
|
|
||||||
|
celery_worker:
|
||||||
|
image: git.envipath.com/envipath/envipy-bayer:1.2
|
||||||
|
container_name: epcelery
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
command: celery -A envipath worker --concurrency=6 -Q model,predict,background --pool threads
|
||||||
|
volumes:
|
||||||
|
- ep_bayer_data:/opt/enviPy/
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
ep_bayer_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_postgres_data:/var/lib/postgresql
|
- ep_bayer_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_redis_data:/data
|
- ep_bayer_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:1.0
|
image: envipath/envipy-bayer: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_data:/opt/enviPy/
|
- ep_bayer_data:/opt/enviPy/
|
||||||
|
|
||||||
celery_worker:
|
celery_worker:
|
||||||
image: envipath/envipy:1.0
|
image: envipath/envipy-bayer: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_data:/opt/enviPy/
|
- ep_bayer_data:/opt/enviPy/
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
ep_postgres_data:
|
ep_bayer_postgres_data:
|
||||||
ep_redis_data:
|
ep_bayer_redis_data:
|
||||||
ep_data:
|
ep_bayer_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")
|
ENV_PATH = os.environ.get("ENV_PATH", BASE_DIR / ".env.dev")
|
||||||
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,6 +143,12 @@ 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
|
||||||
@ -269,6 +275,12 @@ 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__)
|
||||||
@ -289,6 +301,11 @@ 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"),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,7 +354,7 @@ DEFAULT_MODEL_PARAMS = {
|
|||||||
"num_chains": 10,
|
"num_chains": 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_MAX_NUMBER_OF_NODES = 50
|
DEFAULT_MAX_NUMBER_OF_NODES = 9999
|
||||||
DEFAULT_MAX_DEPTH = 8
|
DEFAULT_MAX_DEPTH = 8
|
||||||
DEFAULT_MODEL_THRESHOLD = 0.25
|
DEFAULT_MODEL_THRESHOLD = 0.25
|
||||||
|
|
||||||
@ -442,3 +459,48 @@ 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"
|
||||||
26
epapi/v1/endpoints/joblogs.py
Normal file
26
epapi/v1/endpoints/joblogs.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from django.conf import settings as s
|
||||||
|
from ninja import Router
|
||||||
|
from ninja_extra.pagination import paginate
|
||||||
|
|
||||||
|
from epdb.models import JobLog
|
||||||
|
from ..pagination import EnhancedPageNumberPagination
|
||||||
|
from ..schemas import JobLogOutSchema
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/joblog/", response=EnhancedPageNumberPagination.Output[JobLogOutSchema])
|
||||||
|
@paginate(
|
||||||
|
EnhancedPageNumberPagination,
|
||||||
|
page_size=s.API_PAGINATION_DEFAULT_PAGE_SIZE,
|
||||||
|
)
|
||||||
|
def list_all_joblogs(request):
|
||||||
|
"""
|
||||||
|
List all JobLogs from reviewed packages.
|
||||||
|
"""
|
||||||
|
current_user = request.user
|
||||||
|
|
||||||
|
if current_user.is_superuser:
|
||||||
|
return JobLog.objects.all().order_by("-created")
|
||||||
|
else:
|
||||||
|
return JobLog.objects.filter(user=current_user).order_by("-created")
|
||||||
@ -15,6 +15,7 @@ from .endpoints import (
|
|||||||
additional_information,
|
additional_information,
|
||||||
settings,
|
settings,
|
||||||
groups,
|
groups,
|
||||||
|
joblogs,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Main router with authentication
|
# Main router with authentication
|
||||||
@ -37,6 +38,7 @@ router.add_router("", structure.router)
|
|||||||
router.add_router("", additional_information.router)
|
router.add_router("", additional_information.router)
|
||||||
router.add_router("", settings.router)
|
router.add_router("", settings.router)
|
||||||
router.add_router("", groups.router)
|
router.add_router("", groups.router)
|
||||||
|
router.add_router("", joblogs.router)
|
||||||
|
|
||||||
if s.IUCLID_EXPORT_ENABLED:
|
if s.IUCLID_EXPORT_ENABLED:
|
||||||
from epiuclid.api import router as iuclid_router
|
from epiuclid.api import router as iuclid_router
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
from ninja import FilterSchema, FilterLookup, Schema
|
from datetime import datetime
|
||||||
from typing import Annotated, Optional, List, Dict, Any
|
from typing import Annotated, Optional, List, Dict, Any
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
from django.urls import reverse
|
||||||
|
from ninja import Field, FilterSchema, FilterLookup, Schema
|
||||||
|
|
||||||
|
|
||||||
# Filter schema for query parameters
|
# Filter schema for query parameters
|
||||||
class ReviewStatusFilter(FilterSchema):
|
class ReviewStatusFilter(FilterSchema):
|
||||||
@ -133,3 +136,23 @@ class GroupOutSchema(Schema):
|
|||||||
url: str = ""
|
url: str = ""
|
||||||
name: str
|
name: str
|
||||||
description: str
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleUserOutSchema(Schema):
|
||||||
|
uuid: UUID
|
||||||
|
url: str
|
||||||
|
name: str = Field(alias="username")
|
||||||
|
|
||||||
|
|
||||||
|
class JobLogOutSchema(Schema):
|
||||||
|
user: SimpleUserOutSchema
|
||||||
|
id: UUID = Field(alias="task_id")
|
||||||
|
url: str
|
||||||
|
name: str = Field(alias="job_name")
|
||||||
|
created: datetime = Field(alias="created")
|
||||||
|
status: str = Field(alias="status")
|
||||||
|
done: Optional[datetime] = Field(None, alias="done_at")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_url(obj):
|
||||||
|
return reverse("job detail", kwargs={"job_uuid": obj.task_id})
|
||||||
|
|||||||
@ -5,4 +5,5 @@ 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,9 +2,11 @@ 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
|
from epdb.logic import UserManager, GroupManager
|
||||||
|
from epdb.models import Group
|
||||||
|
|
||||||
|
|
||||||
def get_msal_app_with_cache(request):
|
def get_msal_app_with_cache(request):
|
||||||
@ -80,6 +82,33 @@ 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
|
||||||
|
|
||||||
|
|
||||||
@ -87,6 +116,11 @@ 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
|
||||||
|
|
||||||
@ -128,3 +162,9 @@ 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')
|
||||||
|
|||||||
5
epdb/exceptions.py
Normal file
5
epdb/exceptions.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class InvalidSMILESException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PackageImportException(Exception):
|
||||||
|
pass
|
||||||
@ -1,17 +1,20 @@
|
|||||||
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 SessionAuth
|
from ninja.security import HttpBearer
|
||||||
|
|
||||||
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,
|
||||||
@ -46,6 +49,26 @@ 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):
|
||||||
@ -59,7 +82,52 @@ def _anonymous_or_real(request):
|
|||||||
return get_user_model().objects.get(username="anonymous")
|
return get_user_model().objects.get(username="anonymous")
|
||||||
|
|
||||||
|
|
||||||
router = Router(auth=SessionAuth(csrf=False))
|
def validate_token(token: str) -> dict:
|
||||||
|
TENANT_ID = s.MS_ENTRA_TENANT_ID
|
||||||
|
CLIENT_ID = s.MS_ENTRA_CLIENT_ID
|
||||||
|
|
||||||
|
jwks = get_cached_jwks(TENANT_ID)
|
||||||
|
|
||||||
|
header = jwt.get_unverified_header(token)
|
||||||
|
|
||||||
|
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(
|
||||||
|
next(k for k in jwks["keys"] if k["kid"] == header["kid"])
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle V1 and V2 tokens
|
||||||
|
try:
|
||||||
|
claims = jwt.decode(
|
||||||
|
token,
|
||||||
|
public_key,
|
||||||
|
algorithms=["RS256"],
|
||||||
|
audience=[CLIENT_ID, f"api://{CLIENT_ID}"],
|
||||||
|
issuer=[
|
||||||
|
f"https://sts.windows.net/{TENANT_ID}/",
|
||||||
|
f"https://login.microsoftonline.com/{TENANT_ID}/v2.0"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"Token verification failed! - {e}")
|
||||||
|
|
||||||
|
return claims
|
||||||
|
|
||||||
|
|
||||||
|
class MSBearerTokenAuth(HttpBearer):
|
||||||
|
|
||||||
|
def authenticate(self, request, token):
|
||||||
|
if token is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
claims = validate_token(token)
|
||||||
|
|
||||||
|
if not User.objects.filter(uuid=claims['oid']).exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
request.user = User.objects.get(uuid=claims['oid'])
|
||||||
|
return request.user
|
||||||
|
|
||||||
|
|
||||||
|
router = Router(auth=MSBearerTokenAuth())
|
||||||
|
|
||||||
|
|
||||||
class Error(Schema):
|
class Error(Schema):
|
||||||
@ -153,21 +221,6 @@ 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 #
|
||||||
@ -386,23 +439,50 @@ class PackageSchema(Schema):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_readers(obj: Package):
|
def resolve_readers(obj: Package):
|
||||||
users = User.objects.filter(
|
readers = []
|
||||||
id__in=UserPackagePermission.objects.filter(
|
|
||||||
package=obj, permission=UserPackagePermission.READ[0]
|
|
||||||
).values_list("user", flat=True)
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
return [{u.id: u.get_name()} for u in users]
|
user_ids = UserPackagePermission.objects.filter(package=obj).values_list("user", flat=True)
|
||||||
|
|
||||||
|
users = User.objects.filter(id__in=user_ids).distinct()
|
||||||
|
|
||||||
|
for u in users:
|
||||||
|
readers.append({"id": str(u.url), "identifier": "user", "name": u.get_name()})
|
||||||
|
|
||||||
|
group_ids = GroupPackagePermission.objects.filter(package=obj).values_list(
|
||||||
|
"group", flat=True
|
||||||
|
)
|
||||||
|
|
||||||
|
groups = Group.objects.filter(id__in=group_ids).distinct()
|
||||||
|
|
||||||
|
for g in groups:
|
||||||
|
readers.append({"id": str(g.url), "identifier": "group", "name": g.get_name()})
|
||||||
|
|
||||||
|
return readers
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_writers(obj: Package):
|
def resolve_writers(obj: Package):
|
||||||
users = User.objects.filter(
|
writers = []
|
||||||
id__in=UserPackagePermission.objects.filter(
|
|
||||||
package=obj, permission=UserPackagePermission.WRITE[0]
|
|
||||||
).values_list("user", flat=True)
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
return [{u.id: u.get_name()} for u in users]
|
user_ids = UserPackagePermission.objects.filter(
|
||||||
|
package=obj,
|
||||||
|
permission__in=[UserPackagePermission.WRITE[0], UserPackagePermission.ALL[0]],
|
||||||
|
).values_list("user", flat=True)
|
||||||
|
|
||||||
|
users = User.objects.filter(id__in=user_ids).distinct()
|
||||||
|
|
||||||
|
for u in users:
|
||||||
|
writers.append({"id": str(u.url), "identifier": "user", "name": u.get_name()})
|
||||||
|
|
||||||
|
group_ids = GroupPackagePermission.objects.filter(
|
||||||
|
package=obj, permission=[UserPackagePermission.WRITE[0], UserPackagePermission.ALL[0]]
|
||||||
|
).values_list("group", flat=True)
|
||||||
|
|
||||||
|
groups = Group.objects.filter(id__in=group_ids).distinct()
|
||||||
|
|
||||||
|
for g in groups:
|
||||||
|
writers.append({"id": str(g.url), "identifier": "group", "name": g.get_name()})
|
||||||
|
|
||||||
|
return writers
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_review_comment(obj):
|
def resolve_review_comment(obj):
|
||||||
@ -553,9 +633,14 @@ class CompoundSchema(Schema):
|
|||||||
reviewStatus: str = Field(False, alias="review_status")
|
reviewStatus: str = Field(False, alias="review_status")
|
||||||
scenarios: List["SimpleScenario"] = Field([], alias="scenarios")
|
scenarios: List["SimpleScenario"] = Field([], alias="scenarios")
|
||||||
structures: List["CompoundStructureSchema"] = []
|
structures: List["CompoundStructureSchema"] = []
|
||||||
|
pesLink: str | None = Field(None, alias="pes_link")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_review_status(obj: CompoundStructure):
|
def resolve_pes_link(obj: Compound):
|
||||||
|
return getattr(obj.default_structure, "pes_link", None)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_review_status(obj: Compound):
|
||||||
return "reviewed" if obj.package.reviewed else "unreviewed"
|
return "reviewed" if obj.package.reviewed else "unreviewed"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -629,6 +714,7 @@ class CompoundStructureSchema(Schema):
|
|||||||
reviewStatus: str = Field(None, alias="review_status")
|
reviewStatus: str = Field(None, alias="review_status")
|
||||||
scenarios: List["SimpleScenario"] = Field([], alias="scenarios")
|
scenarios: List["SimpleScenario"] = Field([], alias="scenarios")
|
||||||
smiles: str = Field(None, alias="smiles")
|
smiles: str = Field(None, alias="smiles")
|
||||||
|
pesLink: str | None = Field(None, alias="pes_link")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_review_status(obj: CompoundStructure):
|
def resolve_review_status(obj: CompoundStructure):
|
||||||
@ -767,6 +853,7 @@ 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")
|
||||||
@ -778,9 +865,32 @@ 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
|
||||||
c = Compound.create(
|
|
||||||
p, c.compoundSmiles, c.compoundName, c.compoundDescription, inchi=c.inchi
|
if c.pesLink is not None:
|
||||||
)
|
from bayer.views import fetch_pes
|
||||||
|
from bayer.models import PESCompound
|
||||||
|
|
||||||
|
try:
|
||||||
|
pes_data = fetch_pes(request, c.pesLink)
|
||||||
|
except ValueError as e:
|
||||||
|
return 400, {"message": f"Could not fetch PES data for {c.pesLink}"}
|
||||||
|
|
||||||
|
classification = pes_data.get("classificationLevel", "")
|
||||||
|
if "secret" == classification.lower():
|
||||||
|
|
||||||
|
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(
|
||||||
|
p, c.compoundSmiles, c.compoundName, c.compoundDescription, inchi=c.inchi
|
||||||
|
)
|
||||||
return redirect(c.url)
|
return redirect(c.url)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return 400, {"message": str(e)}
|
return 400, {"message": str(e)}
|
||||||
@ -799,6 +909,27 @@ def delete_compound(request, package_uuid, compound_uuid):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateCompoundStructure(Schema):
|
||||||
|
smiles: str
|
||||||
|
name: str | None = None
|
||||||
|
description: str | None = None
|
||||||
|
inchi: str | None = None
|
||||||
|
molfile: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}/structure")
|
||||||
|
def create_package_compound_structure(
|
||||||
|
request, package_uuid, compound_uuid, structure: Form[CreateCompoundStructure]
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
p = get_package_for_write(request.user, package_uuid)
|
||||||
|
c = Compound.objects.get(package=p, uuid=compound_uuid)
|
||||||
|
cs = CompoundStructure.create(c, structure.smiles, structure.name, structure.description)
|
||||||
|
return redirect(cs.url)
|
||||||
|
except ValueError as e:
|
||||||
|
return 400, {"message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
"/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}/structure/{uuid:structure_uuid}"
|
"/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}/structure/{uuid:structure_uuid}"
|
||||||
)
|
)
|
||||||
@ -1325,6 +1456,7 @@ class ScenarioSchema(Schema):
|
|||||||
aliases: List[str] = Field([], alias="aliases")
|
aliases: List[str] = Field([], alias="aliases")
|
||||||
collection: Dict["str", List[Dict[str, Any]]] = Field([], alias="collection")
|
collection: Dict["str", List[Dict[str, Any]]] = Field([], alias="collection")
|
||||||
collectionID: Optional[str] = None
|
collectionID: Optional[str] = None
|
||||||
|
date: str = Field(None, alias="scenario_date")
|
||||||
description: str = Field(None, alias="description")
|
description: str = Field(None, alias="description")
|
||||||
id: str = Field(None, alias="url")
|
id: str = Field(None, alias="url")
|
||||||
identifier: str = "scenario"
|
identifier: str = "scenario"
|
||||||
@ -1468,28 +1600,56 @@ def create_package_additional_information(request, package_uuid):
|
|||||||
scen = request.POST.get("scenario")
|
scen = request.POST.get("scenario")
|
||||||
scenario = Scenario.objects.get(package=p, url=scen)
|
scenario = Scenario.objects.get(package=p, url=scen)
|
||||||
|
|
||||||
url_parser = EPDBURLParser(request.POST.get("attach_obj"))
|
if request.POST.get("adInfoTypes[]"):
|
||||||
attach_obj = url_parser.get_object()
|
url_parser = EPDBURLParser(request.POST.get("attach_obj"))
|
||||||
|
attach_obj = url_parser.get_object()
|
||||||
|
|
||||||
if not hasattr(attach_obj, "additional_information"):
|
if not hasattr(attach_obj, "additional_information"):
|
||||||
raise ValueError("Can't attach additional information to this object!")
|
raise ValueError("Can't attach additional information to this object!")
|
||||||
|
|
||||||
if not attach_obj.url.startswith(p.url):
|
if not attach_obj.url.startswith(p.url):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Additional Information can only be set to objects stored in the same package!"
|
"Additional Information can only be set to objects stored in the same package!"
|
||||||
)
|
)
|
||||||
|
|
||||||
types = request.POST.get("adInfoTypes[]", "").split(",")
|
types = request.POST.get("adInfoTypes[]", "").split(",")
|
||||||
|
|
||||||
for t in types:
|
for t in types:
|
||||||
ai = build_additional_information_from_request(request, t)
|
ai = build_additional_information_from_request(request, t)
|
||||||
|
|
||||||
AdditionalInformation.create(
|
AdditionalInformation.create(
|
||||||
p,
|
p,
|
||||||
ai,
|
ai,
|
||||||
scenario=scenario,
|
scenario=scenario,
|
||||||
content_object=attach_obj,
|
content_object=attach_obj,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
elif request.POST.get("ais"):
|
||||||
|
import json
|
||||||
|
|
||||||
|
parsed_ais = json.loads(request.POST.get("ais"))
|
||||||
|
|
||||||
|
for ai_type, ais in parsed_ais.items():
|
||||||
|
for ai in ais:
|
||||||
|
attach_obj = None
|
||||||
|
if ai.get("related"):
|
||||||
|
url_parser = EPDBURLParser(ai.get("related").get("url"))
|
||||||
|
attach_obj = url_parser.get_object()
|
||||||
|
|
||||||
|
if not hasattr(attach_obj, "additional_information"):
|
||||||
|
raise ValueError("Can't attach additional information to this object!")
|
||||||
|
|
||||||
|
if not attach_obj.url.startswith(p.url):
|
||||||
|
raise ValueError(
|
||||||
|
"Additional Information can only be set to objects stored in the same package!"
|
||||||
|
)
|
||||||
|
|
||||||
|
AdditionalInformation.create(
|
||||||
|
p,
|
||||||
|
AdditionalInformation.from_dict(ai_type, ai),
|
||||||
|
scenario=scenario,
|
||||||
|
content_object=attach_obj,
|
||||||
|
)
|
||||||
|
|
||||||
# TODO implement additional information endpoint ?
|
# TODO implement additional information endpoint ?
|
||||||
return redirect(f"{scenario.url}")
|
return redirect(f"{scenario.url}")
|
||||||
@ -1533,13 +1693,15 @@ 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")
|
||||||
|
pesLink: str | None = Field(None, alias="pes_link")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_atom_count(obj: Node):
|
def resolve_atom_count(obj: Node):
|
||||||
@ -1552,24 +1714,10 @@ class PathwayNode(Schema):
|
|||||||
# TODO
|
# TODO
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def resolve_engineered_intermediate(obj: Node):
|
|
||||||
# TODO
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def resolve_image(obj: Node):
|
|
||||||
return f"{obj.default_node_label.url}?image=svg"
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_image_size(obj: Node):
|
def resolve_image_size(obj: Node):
|
||||||
return 400
|
return 400
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def resolve_proposed_intermediate(obj: Node):
|
|
||||||
# TODO
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
class PathwaySchema(Schema):
|
class PathwaySchema(Schema):
|
||||||
aliases: List[str] = Field([], alias="aliases")
|
aliases: List[str] = Field([], alias="aliases")
|
||||||
@ -1693,6 +1841,29 @@ def create_package_pathway(
|
|||||||
return 403, {"message": str(e)}
|
return 403, {"message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}")
|
||||||
|
def update_pathway(request, package_uuid, pathway_uuid):
|
||||||
|
try:
|
||||||
|
p = get_package_for_write(request.user, package_uuid)
|
||||||
|
|
||||||
|
if request.POST.get("scenario"):
|
||||||
|
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
||||||
|
scen = Scenario.objects.get(package=p, url=request.POST.get("scenario"))
|
||||||
|
|
||||||
|
pw.scenarios.add(scen)
|
||||||
|
pw.save()
|
||||||
|
|
||||||
|
return redirect(f"{pw.url}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
return 400, {"message": "No scenario specified!"}
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Deleting Pathway with id {pathway_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}")
|
@router.delete("/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}")
|
||||||
def delete_pathway(request, package_uuid, pathway_uuid):
|
def delete_pathway(request, package_uuid, pathway_uuid):
|
||||||
try:
|
try:
|
||||||
@ -1801,25 +1972,67 @@ 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, 403: Error},
|
response={200: str | Any, 400: Error, 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)
|
||||||
|
|
||||||
if n.nodeDepth is not None and n.nodeDepth.strip() != "":
|
# TODO Code Dup from bayer.views
|
||||||
node_depth = int(n.nodeDepth)
|
|
||||||
|
if n.pesLink:
|
||||||
|
from bayer.views import fetch_pes
|
||||||
|
from bayer.models import PESCompound
|
||||||
|
|
||||||
|
try:
|
||||||
|
pes_data = fetch_pes(request, 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:
|
else:
|
||||||
node_depth = -1
|
if n.nodeDepth is not None and n.nodeDepth.strip() != "":
|
||||||
|
node_depth = int(n.nodeDepth)
|
||||||
|
else:
|
||||||
|
node_depth = -1
|
||||||
|
|
||||||
n = Node.create(pw, n.nodeAsSmiles, node_depth, n.nodeName, n.nodeReason)
|
node = Node.create(pw, n.nodeAsSmiles, node_depth, n.nodeName, n.nodeReason)
|
||||||
|
|
||||||
return redirect(n.url)
|
return redirect(node.url)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return 403, {"message": "Adding node failed!"}
|
return 403, {"message": "Adding node failed!"}
|
||||||
|
|
||||||
@ -1929,13 +2142,16 @@ 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.get(
|
default_node_label=CompoundStructure.objects.not_instance_of(*subclasses).
|
||||||
|
get(
|
||||||
compound__package=p, smiles=stand_ed
|
compound__package=p, smiles=stand_ed
|
||||||
).compound.default_structure,
|
).compound.default_structure,
|
||||||
)
|
)
|
||||||
@ -1946,7 +2162,8 @@ 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.get(
|
default_node_label=CompoundStructure.objects.not_instance_of(*subclasses).
|
||||||
|
get(
|
||||||
compound__package=p, smiles=stand_pr
|
compound__package=p, smiles=stand_pr
|
||||||
).compound.default_structure,
|
).compound.default_structure,
|
||||||
)
|
)
|
||||||
@ -1958,6 +2175,10 @@ def add_pathway_edge(request, package_uuid, pathway_uuid, e: Form[CreateEdge]):
|
|||||||
for pr in e.products.split(","):
|
for pr in e.products.split(","):
|
||||||
products.append(Node.objects.get(pathway=pw, url=pr.strip()))
|
products.append(Node.objects.get(pathway=pw, url=pr.strip()))
|
||||||
|
|
||||||
|
multi_step = False
|
||||||
|
if e.multistep and e.multistep.strip() == "true":
|
||||||
|
multi_step = True
|
||||||
|
|
||||||
new_e = Edge.create(
|
new_e = Edge.create(
|
||||||
pathway=pw,
|
pathway=pw,
|
||||||
start_nodes=educts,
|
start_nodes=educts,
|
||||||
@ -1965,6 +2186,7 @@ def add_pathway_edge(request, package_uuid, pathway_uuid, e: Form[CreateEdge]):
|
|||||||
rule=None,
|
rule=None,
|
||||||
name=None,
|
name=None,
|
||||||
description=e.edgeReason,
|
description=e.edgeReason,
|
||||||
|
multi_step=multi_step,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update depths as sideeffect of above operation
|
# Update depths as sideeffect of above operation
|
||||||
@ -2163,3 +2385,31 @@ def get_setting(request, setting_uuid):
|
|||||||
return 403, {
|
return 403, {
|
||||||
"message": f"Getting Setting with id {setting_uuid} failed due to insufficient rights!"
|
"message": f"Getting Setting with id {setting_uuid} failed due to insufficient rights!"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
########
|
||||||
|
# Util #
|
||||||
|
########
|
||||||
|
class NonPersistent(Schema):
|
||||||
|
smiles: str
|
||||||
|
setting_url: str = Field(..., alias="settingUri")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/util", response={200: Any, 403: Error})
|
||||||
|
def predict(request, np: Form[NonPersistent]):
|
||||||
|
try:
|
||||||
|
print(request.user)
|
||||||
|
print(np.setting_url)
|
||||||
|
setting = SettingManager.get_setting_by_url(request.user, np.setting_url)
|
||||||
|
|
||||||
|
from epdb.logic import SPathway
|
||||||
|
|
||||||
|
spw = SPathway(prediction_setting=setting, root_nodes=[np.smiles])
|
||||||
|
spw.predict()
|
||||||
|
|
||||||
|
return spw.to_json()
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Getting Setting with id {np.setting_url} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ 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 (
|
||||||
@ -364,6 +365,14 @@ 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)
|
||||||
@ -406,6 +415,7 @@ 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
|
||||||
@ -415,6 +425,37 @@ 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...
|
||||||
@ -441,6 +482,10 @@ 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
|
||||||
@ -487,11 +532,11 @@ class PackageManager(object):
|
|||||||
|
|
||||||
qs = qs.distinct()
|
qs = qs.distinct()
|
||||||
|
|
||||||
return qs
|
# EDIT START
|
||||||
|
qs = PackageManager.check_package_classifications(user, qs)
|
||||||
|
# EDIT END
|
||||||
|
|
||||||
@staticmethod
|
return qs
|
||||||
def get_packages():
|
|
||||||
return Package.objects.all()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@ -596,6 +641,25 @@ 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()
|
||||||
|
|
||||||
@ -681,7 +745,13 @@ class PackageManager(object):
|
|||||||
default_structure = None
|
default_structure = None
|
||||||
|
|
||||||
for structure in compound["structures"]:
|
for structure in compound["structures"]:
|
||||||
struc = CompoundStructure()
|
if structure.get("pesLink"):
|
||||||
|
from bayer.models import PESStructure
|
||||||
|
struc = PESStructure()
|
||||||
|
struc.pes_link = structure["pesLink"]
|
||||||
|
else:
|
||||||
|
struc = CompoundStructure()
|
||||||
|
|
||||||
# struc.object_url = Command.get_id(structure, keep_ids)
|
# struc.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()
|
||||||
@ -1008,10 +1078,8 @@ class PackageManager(object):
|
|||||||
data: Dict[str, Any],
|
data: Dict[str, Any],
|
||||||
owner: User,
|
owner: User,
|
||||||
preserve_uuids=False,
|
preserve_uuids=False,
|
||||||
add_import_timestamp=True,
|
|
||||||
trust_reviewed=False,
|
|
||||||
) -> Package:
|
) -> Package:
|
||||||
importer = PackageImporter(data, preserve_uuids, add_import_timestamp, trust_reviewed)
|
importer = PackageImporter(data, preserve_uuids)
|
||||||
imported_package = importer.do_import()
|
imported_package = importer.do_import()
|
||||||
|
|
||||||
up = UserPackagePermission()
|
up = UserPackagePermission()
|
||||||
@ -1828,6 +1896,12 @@ class SPathway(object):
|
|||||||
"to": to_indices,
|
"to": to_indices,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if edge.rule:
|
||||||
|
e["rule"] = edge.rule.simple_json()
|
||||||
|
|
||||||
|
if edge.probability:
|
||||||
|
e["probability"] = edge.probability
|
||||||
|
|
||||||
edges.append(e)
|
edges.append(e)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -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"]])
|
||||||
|
|||||||
@ -1,594 +0,0 @@
|
|||||||
# Generated by Django 5.2.1 on 2025-07-22 20:58
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import django.contrib.auth.models
|
|
||||||
import django.contrib.auth.validators
|
|
||||||
import django.contrib.postgres.fields
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
import model_utils.fields
|
|
||||||
import uuid
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('auth', '0012_alter_user_first_name_max_length'),
|
|
||||||
('contenttypes', '0002_remove_content_type_name'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Compound',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='EPModel',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Permission',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('permission', models.CharField(choices=[('read', 'Read'), ('write', 'Write'), ('all', 'All')], max_length=32)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='License',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('link', models.URLField(verbose_name='link')),
|
|
||||||
('image_link', models.URLField(verbose_name='Image link')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Rule',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='User',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
|
||||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
|
||||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
|
||||||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
|
||||||
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
|
||||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
|
||||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
|
||||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
|
||||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
|
||||||
('email', models.EmailField(max_length=254, unique=True)),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
|
||||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'user',
|
|
||||||
'verbose_name_plural': 'users',
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
managers=[
|
|
||||||
('objects', django.contrib.auth.models.UserManager()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='APIToken',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('hashed_key', models.CharField(max_length=128, unique=True)),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('expires_at', models.DateTimeField(blank=True, default=datetime.datetime(2025, 10, 20, 20, 58, 48, 351675, tzinfo=datetime.timezone.utc), null=True)),
|
|
||||||
('name', models.CharField(blank=True, help_text='Optional name for the token', max_length=100)),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CompoundStructure',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
|
||||||
('smiles', models.TextField(verbose_name='SMILES')),
|
|
||||||
('canonical_smiles', models.TextField(verbose_name='Canonical SMILES')),
|
|
||||||
('inchikey', models.TextField(max_length=27, verbose_name='InChIKey')),
|
|
||||||
('normalized_structure', models.BooleanField(default=False)),
|
|
||||||
('compound', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.compound')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='compound',
|
|
||||||
name='default_structure',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='compound_default_structure', to='epdb.compoundstructure', verbose_name='Default Structure'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Edge',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
|
||||||
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='EnviFormer',
|
|
||||||
fields=[
|
|
||||||
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
|
||||||
('threshold', models.FloatField(default=0.5)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
bases=('epdb.epmodel',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PluginModel',
|
|
||||||
fields=[
|
|
||||||
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
bases=('epdb.epmodel',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='RuleBaseRelativeReasoning',
|
|
||||||
fields=[
|
|
||||||
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
bases=('epdb.epmodel',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Group',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(verbose_name='Group name')),
|
|
||||||
('public', models.BooleanField(default=False, verbose_name='Public Group')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('group_member', models.ManyToManyField(related_name='groups_in_group', to='epdb.group', verbose_name='Group member')),
|
|
||||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Group Owner')),
|
|
||||||
('user_member', models.ManyToManyField(related_name='users_in_group', to=settings.AUTH_USER_MODEL, verbose_name='User members')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='default_group',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_group', to='epdb.group', verbose_name='Default Group'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Node',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
|
||||||
('depth', models.IntegerField(verbose_name='Node depth')),
|
|
||||||
('default_node_label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='default_node_structure', to='epdb.compoundstructure', verbose_name='Default Node Label')),
|
|
||||||
('node_labels', models.ManyToManyField(related_name='node_structures', to='epdb.compoundstructure', verbose_name='All Node Labels')),
|
|
||||||
('out_edges', models.ManyToManyField(to='epdb.edge', verbose_name='Outgoing Edges')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='edge',
|
|
||||||
name='end_nodes',
|
|
||||||
field=models.ManyToManyField(related_name='edge_products', to='epdb.node', verbose_name='End Nodes'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='edge',
|
|
||||||
name='start_nodes',
|
|
||||||
field=models.ManyToManyField(related_name='edge_educts', to='epdb.node', verbose_name='Start Nodes'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Package',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('reviewed', models.BooleanField(default=False, verbose_name='Reviewstatus')),
|
|
||||||
('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.license', verbose_name='License')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='epmodel',
|
|
||||||
name='package',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='compound',
|
|
||||||
name='package',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='default_package',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.package', verbose_name='Default Package'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SequentialRule',
|
|
||||||
fields=[
|
|
||||||
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
bases=('epdb.rule',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SimpleRule',
|
|
||||||
fields=[
|
|
||||||
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
bases=('epdb.rule',),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='rule',
|
|
||||||
name='package',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='rule',
|
|
||||||
name='polymorphic_ctype',
|
|
||||||
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Pathway',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
|
||||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='node',
|
|
||||||
name='pathway',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='edge',
|
|
||||||
name='pathway',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Reaction',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
|
||||||
('multi_step', models.BooleanField(verbose_name='Multistep Reaction')),
|
|
||||||
('medline_references', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), null=True, size=None, verbose_name='Medline References')),
|
|
||||||
('educts', models.ManyToManyField(related_name='reaction_educts', to='epdb.compoundstructure', verbose_name='Educts')),
|
|
||||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')),
|
|
||||||
('products', models.ManyToManyField(related_name='reaction_products', to='epdb.compoundstructure', verbose_name='Products')),
|
|
||||||
('rules', models.ManyToManyField(related_name='reaction_rule', to='epdb.rule', verbose_name='Rule')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='edge',
|
|
||||||
name='edge_label',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.reaction', verbose_name='Edge label'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Scenario',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('scenario_date', models.CharField(default='No date', max_length=256)),
|
|
||||||
('scenario_type', models.CharField(default='Not specified', max_length=256)),
|
|
||||||
('additional_information', models.JSONField(verbose_name='Additional Information')),
|
|
||||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')),
|
|
||||||
('parent', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.scenario')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='rule',
|
|
||||||
name='scenarios',
|
|
||||||
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='reaction',
|
|
||||||
name='scenarios',
|
|
||||||
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='pathway',
|
|
||||||
name='scenarios',
|
|
||||||
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='node',
|
|
||||||
name='scenarios',
|
|
||||||
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='edge',
|
|
||||||
name='scenarios',
|
|
||||||
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='compoundstructure',
|
|
||||||
name='scenarios',
|
|
||||||
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='compound',
|
|
||||||
name='scenarios',
|
|
||||||
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Setting',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('public', models.BooleanField(default=False)),
|
|
||||||
('global_default', models.BooleanField(default=False)),
|
|
||||||
('max_depth', models.IntegerField(default=5, verbose_name='Setting Max Depth')),
|
|
||||||
('max_nodes', models.IntegerField(default=30, verbose_name='Setting Max Number of Nodes')),
|
|
||||||
('model_threshold', models.FloatField(blank=True, default=0.25, null=True, verbose_name='Setting Model Threshold')),
|
|
||||||
('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.epmodel', verbose_name='Setting EPModel')),
|
|
||||||
('rule_packages', models.ManyToManyField(blank=True, related_name='setting_rule_packages', to='epdb.package', verbose_name='Setting Rule Packages')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='pathway',
|
|
||||||
name='setting',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Setting'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='default_setting',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.setting', verbose_name='The users default settings'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='MLRelativeReasoning',
|
|
||||||
fields=[
|
|
||||||
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
|
||||||
('threshold', models.FloatField(default=0.5)),
|
|
||||||
('model_status', models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL')),
|
|
||||||
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('data_packages', models.ManyToManyField(related_name='data_packages', to='epdb.package', verbose_name='Data Packages')),
|
|
||||||
('eval_packages', models.ManyToManyField(related_name='eval_packages', to='epdb.package', verbose_name='Evaluation Packages')),
|
|
||||||
('rule_packages', models.ManyToManyField(related_name='rule_packages', to='epdb.package', verbose_name='Rule Packages')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
bases=('epdb.epmodel',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ApplicabilityDomain',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
|
||||||
('name', models.TextField(default='no name', verbose_name='Name')),
|
|
||||||
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
|
||||||
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('num_neighbours', models.FloatField(default=5)),
|
|
||||||
('reliability_threshold', models.FloatField(default=0.5)),
|
|
||||||
('local_compatibilty_threshold', models.FloatField(default=0.5)),
|
|
||||||
('model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.mlrelativereasoning')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SimpleAmbitRule',
|
|
||||||
fields=[
|
|
||||||
('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')),
|
|
||||||
('smirks', models.TextField(verbose_name='SMIRKS')),
|
|
||||||
('reactant_filter_smarts', models.TextField(null=True, verbose_name='Reactant Filter SMARTS')),
|
|
||||||
('product_filter_smarts', models.TextField(null=True, verbose_name='Product Filter SMARTS')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
bases=('epdb.simplerule',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SimpleRDKitRule',
|
|
||||||
fields=[
|
|
||||||
('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')),
|
|
||||||
('reaction_smarts', models.TextField(verbose_name='SMIRKS')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
bases=('epdb.simplerule',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SequentialRuleOrdering',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('order_index', models.IntegerField()),
|
|
||||||
('sequential_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.sequentialrule')),
|
|
||||||
('simple_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.simplerule')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='sequentialrule',
|
|
||||||
name='simple_rules',
|
|
||||||
field=models.ManyToManyField(through='epdb.SequentialRuleOrdering', to='epdb.simplerule', verbose_name='Simple rules'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ParallelRule',
|
|
||||||
fields=[
|
|
||||||
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
|
||||||
('simple_rules', models.ManyToManyField(to='epdb.simplerule', verbose_name='Simple rules')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
'base_manager_name': 'objects',
|
|
||||||
},
|
|
||||||
bases=('epdb.rule',),
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='compound',
|
|
||||||
unique_together={('uuid', 'package')},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='GroupPackagePermission',
|
|
||||||
fields=[
|
|
||||||
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
|
||||||
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.group', verbose_name='Permission to')),
|
|
||||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Permission on')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'unique_together': {('package', 'group')},
|
|
||||||
},
|
|
||||||
bases=('epdb.permission',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='UserPackagePermission',
|
|
||||||
fields=[
|
|
||||||
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
|
||||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Permission on')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'unique_together': {('package', 'user')},
|
|
||||||
},
|
|
||||||
bases=('epdb.permission',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='UserSettingPermission',
|
|
||||||
fields=[
|
|
||||||
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
|
||||||
('setting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Permission on')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'unique_together': {('setting', 'user')},
|
|
||||||
},
|
|
||||||
bases=('epdb.permission',),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,128 +0,0 @@
|
|||||||
# Generated by Django 5.2.1 on 2025-08-25 18:07
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
import model_utils.fields
|
|
||||||
import uuid
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('contenttypes', '0002_remove_content_type_name'),
|
|
||||||
('epdb', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ExternalDatabase',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
|
||||||
('name', models.CharField(max_length=100, unique=True, verbose_name='Database Name')),
|
|
||||||
('full_name', models.CharField(blank=True, max_length=255, verbose_name='Full Database Name')),
|
|
||||||
('description', models.TextField(blank=True, verbose_name='Description')),
|
|
||||||
('base_url', models.URLField(blank=True, null=True, verbose_name='Base URL')),
|
|
||||||
('url_pattern', models.CharField(blank=True, help_text="URL pattern with {id} placeholder, e.g., 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'", max_length=500, verbose_name='URL Pattern')),
|
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='Is Active')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'External Database',
|
|
||||||
'verbose_name_plural': 'External Databases',
|
|
||||||
'db_table': 'epdb_external_database',
|
|
||||||
'ordering': ['name'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='apitoken',
|
|
||||||
options={'ordering': ['-created'], 'verbose_name': 'API Token', 'verbose_name_plural': 'API Tokens'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='edge',
|
|
||||||
options={},
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='edge',
|
|
||||||
name='polymorphic_ctype',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='apitoken',
|
|
||||||
name='is_active',
|
|
||||||
field=models.BooleanField(default=True, help_text='Whether this token is active'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='apitoken',
|
|
||||||
name='modified',
|
|
||||||
field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='applicabilitydomain',
|
|
||||||
name='functional_groups',
|
|
||||||
field=models.JSONField(blank=True, default=dict, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='mlrelativereasoning',
|
|
||||||
name='app_domain',
|
|
||||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='apitoken',
|
|
||||||
name='created',
|
|
||||||
field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='apitoken',
|
|
||||||
name='expires_at',
|
|
||||||
field=models.DateTimeField(blank=True, help_text='Token expiration time (null for no expiration)', null=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='apitoken',
|
|
||||||
name='hashed_key',
|
|
||||||
field=models.CharField(help_text='SHA-256 hash of the token key', max_length=128, unique=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='apitoken',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(help_text='Descriptive name for this token', max_length=100),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='apitoken',
|
|
||||||
name='user',
|
|
||||||
field=models.ForeignKey(help_text='User who owns this token', on_delete=django.db.models.deletion.CASCADE, related_name='api_tokens', to=settings.AUTH_USER_MODEL),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='applicabilitydomain',
|
|
||||||
name='num_neighbours',
|
|
||||||
field=models.IntegerField(default=5),
|
|
||||||
),
|
|
||||||
migrations.AlterModelTable(
|
|
||||||
name='apitoken',
|
|
||||||
table='epdb_api_token',
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ExternalIdentifier',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
|
||||||
('object_id', models.IntegerField()),
|
|
||||||
('identifier_value', models.CharField(max_length=255, verbose_name='Identifier Value')),
|
|
||||||
('url', models.URLField(blank=True, null=True, verbose_name='Direct URL')),
|
|
||||||
('is_primary', models.BooleanField(default=False, help_text='Mark this as the primary identifier for this database', verbose_name='Is Primary')),
|
|
||||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
|
||||||
('database', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.externaldatabase', verbose_name='External Database')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'External Identifier',
|
|
||||||
'verbose_name_plural': 'External Identifiers',
|
|
||||||
'db_table': 'epdb_external_identifier',
|
|
||||||
'indexes': [models.Index(fields=['content_type', 'object_id'], name='epdb_extern_content_b76813_idx'), models.Index(fields=['database', 'identifier_value'], name='epdb_extern_databas_486422_idx')],
|
|
||||||
'unique_together': {('content_type', 'object_id', 'database', 'identifier_value')},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,228 +0,0 @@
|
|||||||
# Generated by Django 5.2.1 on 2025-08-26 17:05
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
def populate_url(apps, schema_editor):
|
|
||||||
MODELS = [
|
|
||||||
'User',
|
|
||||||
'Group',
|
|
||||||
'Package',
|
|
||||||
'Compound',
|
|
||||||
'CompoundStructure',
|
|
||||||
'Pathway',
|
|
||||||
'Edge',
|
|
||||||
'Node',
|
|
||||||
'Reaction',
|
|
||||||
'SimpleAmbitRule',
|
|
||||||
'SimpleRDKitRule',
|
|
||||||
'ParallelRule',
|
|
||||||
'SequentialRule',
|
|
||||||
'Scenario',
|
|
||||||
'Setting',
|
|
||||||
'MLRelativeReasoning',
|
|
||||||
'EnviFormer',
|
|
||||||
'ApplicabilityDomain',
|
|
||||||
]
|
|
||||||
for model in MODELS:
|
|
||||||
obj_cls = apps.get_model("epdb", model)
|
|
||||||
for obj in obj_cls.objects.all():
|
|
||||||
obj.url = assemble_url(obj)
|
|
||||||
if obj.url is None:
|
|
||||||
raise ValueError(f"Could not assemble url for {obj}")
|
|
||||||
obj.save()
|
|
||||||
|
|
||||||
|
|
||||||
def assemble_url(obj):
|
|
||||||
from django.conf import settings as s
|
|
||||||
match obj.__class__.__name__:
|
|
||||||
case 'User':
|
|
||||||
return '{}/user/{}'.format(s.SERVER_URL, obj.uuid)
|
|
||||||
case 'Group':
|
|
||||||
return '{}/group/{}'.format(s.SERVER_URL, obj.uuid)
|
|
||||||
case 'Package':
|
|
||||||
return '{}/package/{}'.format(s.SERVER_URL, obj.uuid)
|
|
||||||
case 'Compound':
|
|
||||||
return '{}/compound/{}'.format(obj.package.url, obj.uuid)
|
|
||||||
case 'CompoundStructure':
|
|
||||||
return '{}/structure/{}'.format(obj.compound.url, obj.uuid)
|
|
||||||
case 'SimpleAmbitRule':
|
|
||||||
return '{}/simple-ambit-rule/{}'.format(obj.package.url, obj.uuid)
|
|
||||||
case 'SimpleRDKitRule':
|
|
||||||
return '{}/simple-rdkit-rule/{}'.format(obj.package.url, obj.uuid)
|
|
||||||
case 'ParallelRule':
|
|
||||||
return '{}/parallel-rule/{}'.format(obj.package.url, obj.uuid)
|
|
||||||
case 'SequentialRule':
|
|
||||||
return '{}/sequential-rule/{}'.format(obj.compound.url, obj.uuid)
|
|
||||||
case 'Reaction':
|
|
||||||
return '{}/reaction/{}'.format(obj.package.url, obj.uuid)
|
|
||||||
case 'Pathway':
|
|
||||||
return '{}/pathway/{}'.format(obj.package.url, obj.uuid)
|
|
||||||
case 'Node':
|
|
||||||
return '{}/node/{}'.format(obj.pathway.url, obj.uuid)
|
|
||||||
case 'Edge':
|
|
||||||
return '{}/edge/{}'.format(obj.pathway.url, obj.uuid)
|
|
||||||
case 'MLRelativeReasoning':
|
|
||||||
return '{}/model/{}'.format(obj.package.url, obj.uuid)
|
|
||||||
case 'EnviFormer':
|
|
||||||
return '{}/model/{}'.format(obj.package.url, obj.uuid)
|
|
||||||
case 'ApplicabilityDomain':
|
|
||||||
return '{}/model/{}/applicability-domain/{}'.format(obj.model.package.url, obj.model.uuid, obj.uuid)
|
|
||||||
case 'Scenario':
|
|
||||||
return '{}/scenario/{}'.format(obj.package.url, obj.uuid)
|
|
||||||
case 'Setting':
|
|
||||||
return '{}/setting/{}'.format(s.SERVER_URL, obj.uuid)
|
|
||||||
case _:
|
|
||||||
raise ValueError(f"Unknown model {obj.__class__.__name__}")
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('epdb', '0002_externaldatabase_alter_apitoken_options_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='applicabilitydomain',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='compound',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='compoundstructure',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='edge',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='epmodel',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='group',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='node',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='package',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='pathway',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='reaction',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='rule',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='scenario',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='setting',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
|
|
||||||
migrations.RunPython(populate_url, reverse_code=migrations.RunPython.noop),
|
|
||||||
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='applicabilitydomain',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='compound',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='compoundstructure',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='edge',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='epmodel',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='group',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='node',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='package',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='pathway',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='reaction',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='rule',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='scenario',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='setting',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='user',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
# Generated by Django 5.2.1 on 2025-09-09 09:21
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('epdb', '0001_squashed_0003_applicabilitydomain_url_compound_url_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='mlrelativereasoning',
|
|
||||||
options={},
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='mlrelativereasoning',
|
|
||||||
name='data_packages',
|
|
||||||
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to='epdb.package', verbose_name='Data Packages'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='mlrelativereasoning',
|
|
||||||
name='eval_packages',
|
|
||||||
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_eval_packages', to='epdb.package', verbose_name='Evaluation Packages'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='mlrelativereasoning',
|
|
||||||
name='rule_packages',
|
|
||||||
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_rule_packages', to='epdb.package', verbose_name='Rule Packages'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='RuleBasedRelativeReasoning',
|
|
||||||
fields=[
|
|
||||||
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
|
||||||
('threshold', models.FloatField(default=0.5)),
|
|
||||||
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('model_status', models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL')),
|
|
||||||
('min_count', models.IntegerField(default=10)),
|
|
||||||
('max_count', models.IntegerField(default=0)),
|
|
||||||
('app_domain', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain')),
|
|
||||||
('data_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to='epdb.package', verbose_name='Data Packages')),
|
|
||||||
('eval_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_eval_packages', to='epdb.package', verbose_name='Evaluation Packages')),
|
|
||||||
('rule_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_rule_packages', to='epdb.package', verbose_name='Rule Packages')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('epdb.epmodel',),
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='RuleBaseRelativeReasoning',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.1 on 2025-09-11 06:21
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('epdb', '0004_alter_mlrelativereasoning_options_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='group',
|
|
||||||
name='group_member',
|
|
||||||
field=models.ManyToManyField(blank=True, related_name='groups_in_group', to='epdb.group', verbose_name='Group member'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 5.2.1 on 2025-09-18 06:42
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('epdb', '0005_alter_group_group_member'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='mlrelativereasoning',
|
|
||||||
name='multigen_eval',
|
|
||||||
field=models.BooleanField(default=False),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='rulebasedrelativereasoning',
|
|
||||||
name='multigen_eval',
|
|
||||||
field=models.BooleanField(default=False),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
# Generated by Django 5.2.1 on 2025-10-07 08:19
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('epdb', '0006_mlrelativereasoning_multigen_eval_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='enviformer',
|
|
||||||
options={},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='enviformer',
|
|
||||||
name='app_domain',
|
|
||||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='enviformer',
|
|
||||||
name='data_packages',
|
|
||||||
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to='epdb.package', verbose_name='Data Packages'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='enviformer',
|
|
||||||
name='eval_packages',
|
|
||||||
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_eval_packages', to='epdb.package', verbose_name='Evaluation Packages'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='enviformer',
|
|
||||||
name='eval_results',
|
|
||||||
field=models.JSONField(blank=True, default=dict, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='enviformer',
|
|
||||||
name='model_status',
|
|
||||||
field=models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='enviformer',
|
|
||||||
name='multigen_eval',
|
|
||||||
field=models.BooleanField(default=False),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='enviformer',
|
|
||||||
name='rule_packages',
|
|
||||||
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_rule_packages', to='epdb.package', verbose_name='Rule Packages'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-10-10 06:58
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
import model_utils.fields
|
|
||||||
import uuid
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("epdb", "0007_alter_enviformer_options_enviformer_app_domain_and_more"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="EnzymeLink",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"created",
|
|
||||||
model_utils.fields.AutoCreatedField(
|
|
||||||
default=django.utils.timezone.now, editable=False, verbose_name="created"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"modified",
|
|
||||||
model_utils.fields.AutoLastModifiedField(
|
|
||||||
default=django.utils.timezone.now, editable=False, verbose_name="modified"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"uuid",
|
|
||||||
models.UUIDField(
|
|
||||||
default=uuid.uuid4, unique=True, verbose_name="UUID of this object"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("name", models.TextField(default="no name", verbose_name="Name")),
|
|
||||||
(
|
|
||||||
"description",
|
|
||||||
models.TextField(default="no description", verbose_name="Descriptions"),
|
|
||||||
),
|
|
||||||
("url", models.TextField(null=True, unique=True, verbose_name="URL")),
|
|
||||||
("kv", models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
("ec_number", models.TextField(verbose_name="EC Number")),
|
|
||||||
("classification_level", models.IntegerField(verbose_name="Classification Level")),
|
|
||||||
("linking_method", models.TextField(verbose_name="Linking Method")),
|
|
||||||
("edge_evidence", models.ManyToManyField(to="epdb.edge")),
|
|
||||||
("reaction_evidence", models.ManyToManyField(to="epdb.reaction")),
|
|
||||||
(
|
|
||||||
"rule",
|
|
||||||
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="epdb.rule"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"abstract": False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-10-27 09:39
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
import model_utils.fields
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("epdb", "0008_enzymelink"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="JobLog",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"created",
|
|
||||||
model_utils.fields.AutoCreatedField(
|
|
||||||
default=django.utils.timezone.now, editable=False, verbose_name="created"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"modified",
|
|
||||||
model_utils.fields.AutoLastModifiedField(
|
|
||||||
default=django.utils.timezone.now, editable=False, verbose_name="modified"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("task_id", models.UUIDField(unique=True)),
|
|
||||||
("job_name", models.TextField()),
|
|
||||||
(
|
|
||||||
"status",
|
|
||||||
models.CharField(
|
|
||||||
choices=[
|
|
||||||
("INITIAL", "Initial"),
|
|
||||||
("SUCCESS", "Success"),
|
|
||||||
("FAILURE", "Failure"),
|
|
||||||
("REVOKED", "Revoked"),
|
|
||||||
("IGNORED", "Ignored"),
|
|
||||||
],
|
|
||||||
default="INITIAL",
|
|
||||||
max_length=20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("done_at", models.DateTimeField(blank=True, default=None, null=True)),
|
|
||||||
("task_result", models.TextField(blank=True, default=None, null=True)),
|
|
||||||
(
|
|
||||||
"user",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"abstract": False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-11 14:11
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("epdb", "0009_joblog"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="license",
|
|
||||||
name="cc_string",
|
|
||||||
field=models.TextField(default="by-nc-sa", verbose_name="CC string"),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-11 14:13
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from django.contrib.postgres.aggregates import ArrayAgg
|
|
||||||
from django.db import migrations
|
|
||||||
from django.db.models import Min
|
|
||||||
|
|
||||||
|
|
||||||
def set_cc(apps, schema_editor):
|
|
||||||
License = apps.get_model("epdb", "License")
|
|
||||||
|
|
||||||
# For all existing licenses extract cc_string from link
|
|
||||||
for license in License.objects.all():
|
|
||||||
pattern = r"/licenses/([^/]+)/4\.0"
|
|
||||||
match = re.search(pattern, license.link)
|
|
||||||
if match:
|
|
||||||
license.cc_string = match.group(1)
|
|
||||||
license.save()
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Could not find license for {license.link}")
|
|
||||||
|
|
||||||
# Ensure we have all licenses
|
|
||||||
cc_strings = ["by", "by-nc", "by-nc-nd", "by-nc-sa", "by-nd", "by-sa"]
|
|
||||||
for cc_string in cc_strings:
|
|
||||||
if not License.objects.filter(cc_string=cc_string).exists():
|
|
||||||
new_license = License()
|
|
||||||
new_license.cc_string = cc_string
|
|
||||||
new_license.link = f"https://creativecommons.org/licenses/{cc_string}/4.0/"
|
|
||||||
new_license.image_link = f"https://licensebuttons.net/l/{cc_string}/4.0/88x31.png"
|
|
||||||
new_license.save()
|
|
||||||
|
|
||||||
# As we might have existing Licenses representing the same License,
|
|
||||||
# get min pk and all pks as a list
|
|
||||||
license_lookup_qs = License.objects.values("cc_string").annotate(
|
|
||||||
lowest_pk=Min("id"), all_pks=ArrayAgg("id", order_by=("id",))
|
|
||||||
)
|
|
||||||
|
|
||||||
license_lookup = {
|
|
||||||
row["cc_string"]: (row["lowest_pk"], row["all_pks"]) for row in license_lookup_qs
|
|
||||||
}
|
|
||||||
|
|
||||||
Packages = apps.get_model("epdb", "Package")
|
|
||||||
|
|
||||||
for k, v in license_lookup.items():
|
|
||||||
# Set min pk to all packages pointing to any of the duplicates
|
|
||||||
Packages.objects.filter(pk__in=v[1]).update(license_id=v[0])
|
|
||||||
# remove the min pk from "other" pks as we use them for deletion
|
|
||||||
v[1].remove(v[0])
|
|
||||||
# Delete redundant License objects
|
|
||||||
License.objects.filter(pk__in=v[1]).delete()
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("epdb", "0010_license_cc_string"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [migrations.RunPython(set_cc)]
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-12-02 13:09
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("epdb", "0011_auto_20251111_1413"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="node",
|
|
||||||
name="stereo_removed",
|
|
||||||
field=models.BooleanField(default=False),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="pathway",
|
|
||||||
name="predicted",
|
|
||||||
field=models.BooleanField(default=False),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-12-14 11:30
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("epdb", "0012_node_stereo_removed_pathway_predicted"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="setting",
|
|
||||||
name="expansion_schema",
|
|
||||||
field=models.CharField(
|
|
||||||
choices=[
|
|
||||||
("BFS", "Breadth First Search"),
|
|
||||||
("DFS", "Depth First Search"),
|
|
||||||
("GREEDY", "Greedy"),
|
|
||||||
],
|
|
||||||
default="BFS",
|
|
||||||
max_length=20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-12-14 16:02
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("epdb", "0013_setting_expansion_schema"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name="setting",
|
|
||||||
old_name="expansion_schema",
|
|
||||||
new_name="expansion_scheme",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2026-01-19 19:26
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("epdb", "0014_rename_expansion_schema_setting_expansion_scheme"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="user",
|
|
||||||
name="is_reviewer",
|
|
||||||
field=models.BooleanField(default=False),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,179 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2026-02-12 09:38
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("epdb", "0015_user_is_reviewer"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="enviformer",
|
|
||||||
name="model_status",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="mlrelativereasoning",
|
|
||||||
name="model_status",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="rulebasedrelativereasoning",
|
|
||||||
name="model_status",
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="epmodel",
|
|
||||||
name="model_status",
|
|
||||||
field=models.CharField(
|
|
||||||
choices=[
|
|
||||||
("INITIAL", "Initial"),
|
|
||||||
("INITIALIZING", "Model is initializing."),
|
|
||||||
("BUILDING", "Model is building."),
|
|
||||||
(
|
|
||||||
"BUILT_NOT_EVALUATED",
|
|
||||||
"Model is built and can be used for predictions, Model is not evaluated yet.",
|
|
||||||
),
|
|
||||||
("EVALUATING", "Model is evaluating"),
|
|
||||||
("FINISHED", "Model has finished building and evaluation."),
|
|
||||||
("ERROR", "Model has failed."),
|
|
||||||
],
|
|
||||||
default="INITIAL",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="enviformer",
|
|
||||||
name="eval_packages",
|
|
||||||
field=models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
related_name="%(app_label)s_%(class)s_eval_packages",
|
|
||||||
to=settings.EPDB_PACKAGE_MODEL,
|
|
||||||
verbose_name="Evaluation Packages",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="enviformer",
|
|
||||||
name="rule_packages",
|
|
||||||
field=models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
related_name="%(app_label)s_%(class)s_rule_packages",
|
|
||||||
to=settings.EPDB_PACKAGE_MODEL,
|
|
||||||
verbose_name="Rule Packages",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="mlrelativereasoning",
|
|
||||||
name="eval_packages",
|
|
||||||
field=models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
related_name="%(app_label)s_%(class)s_eval_packages",
|
|
||||||
to=settings.EPDB_PACKAGE_MODEL,
|
|
||||||
verbose_name="Evaluation Packages",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="mlrelativereasoning",
|
|
||||||
name="rule_packages",
|
|
||||||
field=models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
related_name="%(app_label)s_%(class)s_rule_packages",
|
|
||||||
to=settings.EPDB_PACKAGE_MODEL,
|
|
||||||
verbose_name="Rule Packages",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="rulebasedrelativereasoning",
|
|
||||||
name="eval_packages",
|
|
||||||
field=models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
related_name="%(app_label)s_%(class)s_eval_packages",
|
|
||||||
to=settings.EPDB_PACKAGE_MODEL,
|
|
||||||
verbose_name="Evaluation Packages",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="rulebasedrelativereasoning",
|
|
||||||
name="rule_packages",
|
|
||||||
field=models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
related_name="%(app_label)s_%(class)s_rule_packages",
|
|
||||||
to=settings.EPDB_PACKAGE_MODEL,
|
|
||||||
verbose_name="Rule Packages",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="PropertyPluginModel",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"epmodel_ptr",
|
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="epdb.epmodel",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("threshold", models.FloatField(default=0.5)),
|
|
||||||
("eval_results", models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
("multigen_eval", models.BooleanField(default=False)),
|
|
||||||
("plugin_identifier", models.CharField(max_length=255)),
|
|
||||||
(
|
|
||||||
"app_domain",
|
|
||||||
models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
default=None,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.SET_NULL,
|
|
||||||
to="epdb.applicabilitydomain",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"data_packages",
|
|
||||||
models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
related_name="%(app_label)s_%(class)s_data_packages",
|
|
||||||
to=settings.EPDB_PACKAGE_MODEL,
|
|
||||||
verbose_name="Data Packages",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"eval_packages",
|
|
||||||
models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
related_name="%(app_label)s_%(class)s_eval_packages",
|
|
||||||
to=settings.EPDB_PACKAGE_MODEL,
|
|
||||||
verbose_name="Evaluation Packages",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"rule_packages",
|
|
||||||
models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
related_name="%(app_label)s_%(class)s_rule_packages",
|
|
||||||
to=settings.EPDB_PACKAGE_MODEL,
|
|
||||||
verbose_name="Rule Packages",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"abstract": False,
|
|
||||||
},
|
|
||||||
bases=("epdb.epmodel",),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="setting",
|
|
||||||
name="property_models",
|
|
||||||
field=models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
related_name="settings",
|
|
||||||
to="epdb.propertypluginmodel",
|
|
||||||
verbose_name="Setting Property Models",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name="PluginModel",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2026-02-20 12:02
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("contenttypes", "0002_remove_content_type_name"),
|
|
||||||
("epdb", "0016_remove_enviformer_model_status_and_more"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="AdditionalInformation",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("uuid", models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
|
||||||
("url", models.TextField(null=True, unique=True, verbose_name="URL")),
|
|
||||||
("kv", models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
("type", models.TextField(verbose_name="Additional Information Type")),
|
|
||||||
("data", models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
("object_id", models.PositiveBigIntegerField(blank=True, null=True)),
|
|
||||||
(
|
|
||||||
"content_type",
|
|
||||||
models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="contenttypes.contenttype",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"package",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to=settings.EPDB_PACKAGE_MODEL,
|
|
||||||
verbose_name="Package",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"scenario",
|
|
||||||
models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="scenario_additional_information",
|
|
||||||
to="epdb.scenario",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"indexes": [
|
|
||||||
models.Index(fields=["type"], name="epdb_additi_type_394349_idx"),
|
|
||||||
models.Index(
|
|
||||||
fields=["scenario", "type"], name="epdb_additi_scenari_a59edf_idx"
|
|
||||||
),
|
|
||||||
models.Index(
|
|
||||||
fields=["content_type", "object_id"], name="epdb_additi_content_44d4b4_idx"
|
|
||||||
),
|
|
||||||
models.Index(
|
|
||||||
fields=["scenario", "content_type", "object_id"],
|
|
||||||
name="epdb_additi_scenari_ef2bf5_idx",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
"constraints": [
|
|
||||||
models.CheckConstraint(
|
|
||||||
condition=models.Q(
|
|
||||||
models.Q(("content_type__isnull", True), ("object_id__isnull", True)),
|
|
||||||
models.Q(("content_type__isnull", False), ("object_id__isnull", False)),
|
|
||||||
_connector="OR",
|
|
||||||
),
|
|
||||||
name="ck_addinfo_gfk_pair",
|
|
||||||
),
|
|
||||||
models.CheckConstraint(
|
|
||||||
condition=models.Q(
|
|
||||||
("scenario__isnull", False),
|
|
||||||
("content_type__isnull", False),
|
|
||||||
_connector="OR",
|
|
||||||
),
|
|
||||||
name="ck_addinfo_not_both_null",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2026-02-20 12:03
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def get_additional_information(scenario):
|
|
||||||
from envipy_additional_information import registry
|
|
||||||
from envipy_additional_information.parsers import TypeOfAerationParser
|
|
||||||
|
|
||||||
for k, vals in scenario.additional_information.items():
|
|
||||||
if k == "enzyme":
|
|
||||||
continue
|
|
||||||
|
|
||||||
if k == "SpikeConentration":
|
|
||||||
k = "SpikeConcentration"
|
|
||||||
|
|
||||||
if k == "AerationType":
|
|
||||||
k = "TypeOfAeration"
|
|
||||||
|
|
||||||
for v in vals:
|
|
||||||
# Per default additional fields are ignored
|
|
||||||
MAPPING = {c.__name__: c for c in registry.list_models().values()}
|
|
||||||
try:
|
|
||||||
inst = MAPPING[k](**v)
|
|
||||||
except Exception:
|
|
||||||
if k == "TypeOfAeration":
|
|
||||||
toa = TypeOfAerationParser()
|
|
||||||
inst = toa.from_string(v["type"])
|
|
||||||
|
|
||||||
# Add uuid to uniquely identify objects for manipulation
|
|
||||||
if "uuid" in v:
|
|
||||||
inst.__dict__["uuid"] = v["uuid"]
|
|
||||||
|
|
||||||
yield inst
|
|
||||||
|
|
||||||
|
|
||||||
def forward_func(apps, schema_editor):
|
|
||||||
Scenario = apps.get_model("epdb", "Scenario")
|
|
||||||
ContentType = apps.get_model("contenttypes", "ContentType")
|
|
||||||
AdditionalInformation = apps.get_model("epdb", "AdditionalInformation")
|
|
||||||
|
|
||||||
bulk = []
|
|
||||||
related = []
|
|
||||||
ctype = {o.model: o for o in ContentType.objects.all()}
|
|
||||||
parents = Scenario.objects.prefetch_related(
|
|
||||||
"compound_set",
|
|
||||||
"compoundstructure_set",
|
|
||||||
"reaction_set",
|
|
||||||
"rule_set",
|
|
||||||
"pathway_set",
|
|
||||||
"node_set",
|
|
||||||
"edge_set",
|
|
||||||
).filter(parent__isnull=True)
|
|
||||||
|
|
||||||
for i, scenario in enumerate(parents):
|
|
||||||
print(f"{i + 1}/{len(parents)}", end="\r")
|
|
||||||
if scenario.parent is not None:
|
|
||||||
related.append(scenario.parent)
|
|
||||||
continue
|
|
||||||
|
|
||||||
for ai in get_additional_information(scenario):
|
|
||||||
bulk.append(
|
|
||||||
AdditionalInformation(
|
|
||||||
package=scenario.package,
|
|
||||||
scenario=scenario,
|
|
||||||
type=ai.__class__.__name__,
|
|
||||||
data=ai.model_dump(mode="json"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
print("\n", len(bulk))
|
|
||||||
|
|
||||||
related = Scenario.objects.prefetch_related(
|
|
||||||
"compound_set",
|
|
||||||
"compoundstructure_set",
|
|
||||||
"reaction_set",
|
|
||||||
"rule_set",
|
|
||||||
"pathway_set",
|
|
||||||
"node_set",
|
|
||||||
"edge_set",
|
|
||||||
).filter(parent__isnull=False)
|
|
||||||
|
|
||||||
for i, scenario in enumerate(related):
|
|
||||||
print(f"{i + 1}/{len(related)}", end="\r")
|
|
||||||
parent = scenario.parent
|
|
||||||
# Check to which objects this scenario is attached to
|
|
||||||
for ai in get_additional_information(scenario):
|
|
||||||
rel_objs = [
|
|
||||||
"compound",
|
|
||||||
"compoundstructure",
|
|
||||||
"reaction",
|
|
||||||
"rule",
|
|
||||||
"pathway",
|
|
||||||
"node",
|
|
||||||
"edge",
|
|
||||||
]
|
|
||||||
for rel_obj in rel_objs:
|
|
||||||
for o in getattr(scenario, f"{rel_obj}_set").all():
|
|
||||||
bulk.append(
|
|
||||||
AdditionalInformation(
|
|
||||||
package=scenario.package,
|
|
||||||
scenario=parent,
|
|
||||||
type=ai.__class__.__name__,
|
|
||||||
data=ai.model_dump(mode="json"),
|
|
||||||
content_type=ctype[rel_obj],
|
|
||||||
object_id=o.pk,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
print("Start creating additional information objects...")
|
|
||||||
AdditionalInformation.objects.bulk_create(bulk)
|
|
||||||
print("Done!")
|
|
||||||
print(len(bulk))
|
|
||||||
|
|
||||||
Scenario.objects.filter(parent__isnull=False).delete()
|
|
||||||
# Call ai save to fix urls
|
|
||||||
ais = AdditionalInformation.objects.all()
|
|
||||||
total = ais.count()
|
|
||||||
|
|
||||||
for i, ai in enumerate(ais):
|
|
||||||
print(f"{i + 1}/{total}", end="\r")
|
|
||||||
ai.save()
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("epdb", "0017_additionalinformation"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(forward_func, reverse_code=migrations.RunPython.noop),
|
|
||||||
]
|
|
||||||
@ -1,20 +1,741 @@
|
|||||||
# Generated by Django 5.2.7 on 2026-02-23 08:45
|
# Generated by Django 5.2.7 on 2026-03-06 10:51
|
||||||
|
|
||||||
from django.db import migrations
|
import django.contrib.auth.models
|
||||||
|
import django.contrib.auth.validators
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
import uuid
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("epdb", "0018_auto_20260220_1203"),
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
migrations.swappable_dependency(settings.EPDB_PACKAGE_MODEL),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RemoveField(
|
migrations.CreateModel(
|
||||||
model_name="scenario",
|
name='ApplicabilityDomain',
|
||||||
name="additional_information",
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('num_neighbours', models.IntegerField(default=5)),
|
||||||
|
('reliability_threshold', models.FloatField(default=0.5)),
|
||||||
|
('local_compatibilty_threshold', models.FloatField(default=0.5)),
|
||||||
|
('functional_groups', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.RemoveField(
|
migrations.CreateModel(
|
||||||
model_name="scenario",
|
name='Edge',
|
||||||
name="parent",
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EPModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('model_status', models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')),
|
||||||
|
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ExternalDatabase',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||||
|
('name', models.CharField(max_length=100, unique=True, verbose_name='Database Name')),
|
||||||
|
('full_name', models.CharField(blank=True, max_length=255, verbose_name='Full Database Name')),
|
||||||
|
('description', models.TextField(blank=True, verbose_name='Description')),
|
||||||
|
('base_url', models.URLField(blank=True, null=True, verbose_name='Base URL')),
|
||||||
|
('url_pattern', models.CharField(blank=True, help_text="URL pattern with {id} placeholder, e.g., 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'", max_length=500, verbose_name='URL Pattern')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Is Active')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'External Database',
|
||||||
|
'verbose_name_plural': 'External Databases',
|
||||||
|
'db_table': 'epdb_external_database',
|
||||||
|
'ordering': ['name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Permission',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('permission', models.CharField(choices=[('read', 'Read'), ('write', 'Write'), ('all', 'All')], max_length=32)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='License',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('cc_string', models.TextField(verbose_name='CC string')),
|
||||||
|
('link', models.URLField(verbose_name='link')),
|
||||||
|
('image_link', models.URLField(verbose_name='Image link')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Rule',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')),
|
||||||
|
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='User',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||||
|
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||||
|
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||||
|
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||||
|
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||||
|
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||||
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||||
|
('email', models.EmailField(max_length=254, unique=True)),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('is_reviewer', models.BooleanField(default=False)),
|
||||||
|
('default_package', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Default Package')),
|
||||||
|
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||||
|
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'user',
|
||||||
|
'verbose_name_plural': 'users',
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
managers=[
|
||||||
|
('objects', django.contrib.auth.models.UserManager()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='APIToken',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('hashed_key', models.CharField(help_text='SHA-256 hash of the token key', max_length=128, unique=True)),
|
||||||
|
('expires_at', models.DateTimeField(blank=True, help_text='Token expiration time (null for no expiration)', null=True)),
|
||||||
|
('name', models.CharField(help_text='Descriptive name for this token', max_length=100)),
|
||||||
|
('is_active', models.BooleanField(default=True, help_text='Whether this token is active')),
|
||||||
|
('user', models.ForeignKey(help_text='User who owns this token', on_delete=django.db.models.deletion.CASCADE, related_name='api_tokens', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'API Token',
|
||||||
|
'verbose_name_plural': 'API Tokens',
|
||||||
|
'db_table': 'epdb_api_token',
|
||||||
|
'ordering': ['-created'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Compound',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CompoundStructure',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('smiles', models.TextField(verbose_name='SMILES')),
|
||||||
|
('canonical_smiles', models.TextField(verbose_name='Canonical SMILES')),
|
||||||
|
('inchikey', models.TextField(max_length=27, verbose_name='InChIKey')),
|
||||||
|
('normalized_structure', models.BooleanField(default=False)),
|
||||||
|
('compound', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.compound')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compound',
|
||||||
|
name='default_structure',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='compound_default_structure', to='epdb.compoundstructure', verbose_name='Default Structure'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PropertyPluginModel',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
('threshold', models.FloatField(default=0.5)),
|
||||||
|
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('multigen_eval', models.BooleanField(default=False)),
|
||||||
|
('plugin_identifier', models.CharField(max_length=255)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Group',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('name', models.TextField(verbose_name='Group name')),
|
||||||
|
('public', models.BooleanField(default=False, verbose_name='Public Group')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('group_member', models.ManyToManyField(blank=True, related_name='groups_in_group', to='epdb.group', verbose_name='Group member')),
|
||||||
|
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Group Owner')),
|
||||||
|
('user_member', models.ManyToManyField(related_name='users_in_group', to=settings.AUTH_USER_MODEL, verbose_name='User members')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='default_group',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_group', to='epdb.group', verbose_name='Default Group'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='JobLog',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('task_id', models.UUIDField(unique=True)),
|
||||||
|
('job_name', models.TextField()),
|
||||||
|
('status', models.CharField(choices=[('INITIAL', 'Initial'), ('SUCCESS', 'Success'), ('FAILURE', 'Failure'), ('REVOKED', 'Revoked'), ('IGNORED', 'Ignored')], default='INITIAL', max_length=20)),
|
||||||
|
('done_at', models.DateTimeField(blank=True, default=None, null=True)),
|
||||||
|
('task_result', models.TextField(blank=True, default=None, null=True)),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Package',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('reviewed', models.BooleanField(default=False, verbose_name='Reviewstatus')),
|
||||||
|
('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.license', verbose_name='License')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'swappable': 'EPDB_PACKAGE_MODEL',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Node',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('depth', models.IntegerField(verbose_name='Node depth')),
|
||||||
|
('stereo_removed', models.BooleanField(default=False)),
|
||||||
|
('default_node_label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='default_node_structure', to='epdb.compoundstructure', verbose_name='Default Node Label')),
|
||||||
|
('node_labels', models.ManyToManyField(related_name='node_structures', to='epdb.compoundstructure', verbose_name='All Node Labels')),
|
||||||
|
('out_edges', models.ManyToManyField(to='epdb.edge', verbose_name='Outgoing Edges')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='end_nodes',
|
||||||
|
field=models.ManyToManyField(related_name='edge_products', to='epdb.node', verbose_name='End Nodes'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='start_nodes',
|
||||||
|
field=models.ManyToManyField(related_name='edge_educts', to='epdb.node', verbose_name='Start Nodes'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SequentialRule',
|
||||||
|
fields=[
|
||||||
|
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.rule',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SimpleRule',
|
||||||
|
fields=[
|
||||||
|
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.rule',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Pathway',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('predicted', models.BooleanField(default=False)),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='node',
|
||||||
|
name='pathway',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='pathway',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Reaction',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('multi_step', models.BooleanField(verbose_name='Multistep Reaction')),
|
||||||
|
('medline_references', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), null=True, size=None, verbose_name='Medline References')),
|
||||||
|
('educts', models.ManyToManyField(related_name='reaction_educts', to='epdb.compoundstructure', verbose_name='Educts')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')),
|
||||||
|
('products', models.ManyToManyField(related_name='reaction_products', to='epdb.compoundstructure', verbose_name='Products')),
|
||||||
|
('rules', models.ManyToManyField(related_name='reaction_rule', to='epdb.rule', verbose_name='Rule')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EnzymeLink',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('ec_number', models.TextField(verbose_name='EC Number')),
|
||||||
|
('classification_level', models.IntegerField(verbose_name='Classification Level')),
|
||||||
|
('linking_method', models.TextField(verbose_name='Linking Method')),
|
||||||
|
('edge_evidence', models.ManyToManyField(to='epdb.edge')),
|
||||||
|
('rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.rule')),
|
||||||
|
('reaction_evidence', models.ManyToManyField(to='epdb.reaction')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='edge_label',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.reaction', verbose_name='Edge label'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Scenario',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('scenario_date', models.CharField(default='No date', max_length=256)),
|
||||||
|
('scenario_type', models.CharField(default='Not specified', max_length=256)),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rule',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='reaction',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pathway',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='node',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compoundstructure',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compound',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Setting',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('public', models.BooleanField(default=False)),
|
||||||
|
('global_default', models.BooleanField(default=False)),
|
||||||
|
('max_depth', models.IntegerField(default=5, verbose_name='Setting Max Depth')),
|
||||||
|
('max_nodes', models.IntegerField(default=30, verbose_name='Setting Max Number of Nodes')),
|
||||||
|
('model_threshold', models.FloatField(blank=True, default=0.25, null=True, verbose_name='Setting Model Threshold')),
|
||||||
|
('expansion_scheme', models.CharField(choices=[('BFS', 'Breadth First Search'), ('DFS', 'Depth First Search'), ('GREEDY', 'Greedy')], default='BFS', max_length=20)),
|
||||||
|
('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.epmodel', verbose_name='Setting EPModel')),
|
||||||
|
('rule_packages', models.ManyToManyField(blank=True, related_name='setting_rule_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Setting Rule Packages')),
|
||||||
|
('property_models', models.ManyToManyField(blank=True, related_name='settings', to='epdb.propertypluginmodel', verbose_name='Setting Property Models')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pathway',
|
||||||
|
name='setting',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Setting'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='default_setting',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.setting', verbose_name='The users default settings'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EnviFormer',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
('threshold', models.FloatField(default=0.5)),
|
||||||
|
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('multigen_eval', models.BooleanField(default=False)),
|
||||||
|
('app_domain', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain')),
|
||||||
|
('data_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Data Packages')),
|
||||||
|
('eval_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_eval_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Evaluation Packages')),
|
||||||
|
('rule_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_rule_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Rule Packages')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MLRelativeReasoning',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
('threshold', models.FloatField(default=0.5)),
|
||||||
|
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('multigen_eval', models.BooleanField(default=False)),
|
||||||
|
('app_domain', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain')),
|
||||||
|
('data_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Data Packages')),
|
||||||
|
('eval_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_eval_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Evaluation Packages')),
|
||||||
|
('rule_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_rule_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Rule Packages')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applicabilitydomain',
|
||||||
|
name='model',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.mlrelativereasoning'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='propertypluginmodel',
|
||||||
|
name='app_domain',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='propertypluginmodel',
|
||||||
|
name='data_packages',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_data_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Data Packages'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='propertypluginmodel',
|
||||||
|
name='eval_packages',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_eval_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Evaluation Packages'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='propertypluginmodel',
|
||||||
|
name='rule_packages',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_rule_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Rule Packages'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RuleBasedRelativeReasoning',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
('threshold', models.FloatField(default=0.5)),
|
||||||
|
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('multigen_eval', models.BooleanField(default=False)),
|
||||||
|
('min_count', models.IntegerField(default=10)),
|
||||||
|
('max_count', models.IntegerField(default=0)),
|
||||||
|
('app_domain', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain')),
|
||||||
|
('data_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Data Packages')),
|
||||||
|
('eval_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_eval_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Evaluation Packages')),
|
||||||
|
('rule_packages', models.ManyToManyField(blank=True, related_name='%(app_label)s_%(class)s_rule_packages', to=settings.EPDB_PACKAGE_MODEL, verbose_name='Rule Packages')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ExternalIdentifier',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||||
|
('object_id', models.IntegerField()),
|
||||||
|
('identifier_value', models.CharField(max_length=255, verbose_name='Identifier Value')),
|
||||||
|
('url', models.URLField(blank=True, null=True, verbose_name='Direct URL')),
|
||||||
|
('is_primary', models.BooleanField(default=False, help_text='Mark this as the primary identifier for this database', verbose_name='Is Primary')),
|
||||||
|
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
||||||
|
('database', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.externaldatabase', verbose_name='External Database')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'External Identifier',
|
||||||
|
'verbose_name_plural': 'External Identifiers',
|
||||||
|
'db_table': 'epdb_external_identifier',
|
||||||
|
'indexes': [models.Index(fields=['content_type', 'object_id'], name='epdb_extern_content_b76813_idx'), models.Index(fields=['database', 'identifier_value'], name='epdb_extern_databas_486422_idx')],
|
||||||
|
'unique_together': {('content_type', 'object_id', 'database', 'identifier_value')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SimpleAmbitRule',
|
||||||
|
fields=[
|
||||||
|
('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')),
|
||||||
|
('smirks', models.TextField(verbose_name='SMIRKS')),
|
||||||
|
('reactant_filter_smarts', models.TextField(null=True, verbose_name='Reactant Filter SMARTS')),
|
||||||
|
('product_filter_smarts', models.TextField(null=True, verbose_name='Product Filter SMARTS')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.simplerule',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SimpleRDKitRule',
|
||||||
|
fields=[
|
||||||
|
('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')),
|
||||||
|
('reaction_smarts', models.TextField(verbose_name='SMIRKS')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.simplerule',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SequentialRuleOrdering',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('order_index', models.IntegerField()),
|
||||||
|
('sequential_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.sequentialrule')),
|
||||||
|
('simple_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.simplerule')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sequentialrule',
|
||||||
|
name='simple_rules',
|
||||||
|
field=models.ManyToManyField(through='epdb.SequentialRuleOrdering', to='epdb.simplerule', verbose_name='Simple rules'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ParallelRule',
|
||||||
|
fields=[
|
||||||
|
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
||||||
|
('simple_rules', models.ManyToManyField(to='epdb.simplerule', verbose_name='Simple rules')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.rule',),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='compound',
|
||||||
|
unique_together={('uuid', 'package')},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AdditionalInformation',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||||
|
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('type', models.TextField(verbose_name='Additional Information Type')),
|
||||||
|
('data', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('object_id', models.PositiveBigIntegerField(blank=True, null=True)),
|
||||||
|
('content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Package')),
|
||||||
|
('scenario', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='scenario_additional_information', to='epdb.scenario')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'indexes': [models.Index(fields=['type'], name='epdb_additi_type_394349_idx'), models.Index(fields=['scenario', 'type'], name='epdb_additi_scenari_a59edf_idx'), models.Index(fields=['content_type', 'object_id'], name='epdb_additi_content_44d4b4_idx'), models.Index(fields=['scenario', 'content_type', 'object_id'], name='epdb_additi_scenari_ef2bf5_idx')],
|
||||||
|
'constraints': [models.CheckConstraint(condition=models.Q(models.Q(('content_type__isnull', True), ('object_id__isnull', True)), models.Q(('content_type__isnull', False), ('object_id__isnull', False)), _connector='OR'), name='ck_addinfo_gfk_pair'), models.CheckConstraint(condition=models.Q(('scenario__isnull', False), ('content_type__isnull', False), _connector='OR'), name='ck_addinfo_not_both_null')],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GroupPackagePermission',
|
||||||
|
fields=[
|
||||||
|
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
||||||
|
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.group', verbose_name='Permission to')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Permission on')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('package', 'group')},
|
||||||
|
},
|
||||||
|
bases=('epdb.permission',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserPackagePermission',
|
||||||
|
fields=[
|
||||||
|
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.EPDB_PACKAGE_MODEL, verbose_name='Permission on')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('package', 'user')},
|
||||||
|
},
|
||||||
|
bases=('epdb.permission',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserSettingPermission',
|
||||||
|
fields=[
|
||||||
|
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
||||||
|
('setting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Permission on')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('setting', 'user')},
|
||||||
|
},
|
||||||
|
bases=('epdb.permission',),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -46,4 +46,9 @@ 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"),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -23,6 +23,7 @@ MAPPING = {
|
|||||||
"true": HalfLifeModel.SFO,
|
"true": HalfLifeModel.SFO,
|
||||||
"SFO-SFO": HalfLifeModel.SFO_SFO,
|
"SFO-SFO": HalfLifeModel.SFO_SFO,
|
||||||
"DFOP-SFO": HalfLifeModel.DFOP_SFO,
|
"DFOP-SFO": HalfLifeModel.DFOP_SFO,
|
||||||
|
"other": HalfLifeModel.OTHER,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
48
epdb/migrations/0026_auto_20260602_1718.py
Normal file
48
epdb/migrations/0026_auto_20260602_1718.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Generated by Django 6.0.3 on 2026-06-02 17:18
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from envipy_additional_information import DOI
|
||||||
|
|
||||||
|
|
||||||
|
def forward_func(apps, schema_editor):
|
||||||
|
AdditionalInformation = apps.get_model("epdb", "AdditionalInformation")
|
||||||
|
|
||||||
|
refs = AdditionalInformation.objects.filter(type="Reference")
|
||||||
|
|
||||||
|
remaining = []
|
||||||
|
|
||||||
|
for ref in refs:
|
||||||
|
r = ref.data["reference"]
|
||||||
|
try:
|
||||||
|
# PubMed IDs are plain ints, try parsing
|
||||||
|
_ = int(r)
|
||||||
|
# Nothing to do
|
||||||
|
except ValueError:
|
||||||
|
DOMAINS = [
|
||||||
|
"http://dx.doi.org/",
|
||||||
|
"https://dx.doi.org/",
|
||||||
|
"http://doi.org/",
|
||||||
|
"https://doi.org/",
|
||||||
|
]
|
||||||
|
for d in DOMAINS:
|
||||||
|
r = r.replace(d, "")
|
||||||
|
|
||||||
|
if r.startswith("10."):
|
||||||
|
ref.type = DOI.__name__
|
||||||
|
ref.data = {"doi": r}
|
||||||
|
ref.save()
|
||||||
|
else:
|
||||||
|
remaining.append(ref)
|
||||||
|
|
||||||
|
if len(remaining) > 0:
|
||||||
|
raise ValueError(f"Could not parse {len(remaining)} references")
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0025_auto_20260511_2025"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(forward_func, reverse_code=migrations.RunPython.noop),
|
||||||
|
]
|
||||||
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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
243
epdb/models.py
243
epdb/models.py
@ -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,6 +205,7 @@ 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"
|
||||||
)
|
)
|
||||||
@ -575,6 +577,10 @@ class ReactionIdentifierMixin(ExternalIdentifierMixin):
|
|||||||
def get_uniprot_identifiers(self):
|
def get_uniprot_identifiers(self):
|
||||||
return self.get_external_identifier("UniProt")
|
return self.get_external_identifier("UniProt")
|
||||||
|
|
||||||
|
def contains_pes(self):
|
||||||
|
from bayer.models import PESStructure
|
||||||
|
return any([isinstance(o, PESStructure) for o in self.educts.all()]) or any(
|
||||||
|
[isinstance(o, PESStructure) for o in self.products.all()])
|
||||||
|
|
||||||
##############
|
##############
|
||||||
# EP Objects #
|
# EP Objects #
|
||||||
@ -631,7 +637,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
|
||||||
@ -654,7 +660,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"]):
|
||||||
@ -780,7 +788,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,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -857,37 +865,58 @@ 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)
|
|
||||||
|
|
||||||
# Check if we find a direct match for a given SMILES
|
|
||||||
if CompoundStructure.objects.filter(smiles=smiles, compound__package=package).exists():
|
|
||||||
return CompoundStructure.objects.get(smiles=smiles, compound__package=package).compound
|
|
||||||
|
|
||||||
# Check if we can find the standardized one
|
|
||||||
if CompoundStructure.objects.filter(
|
|
||||||
smiles=standardized_smiles, compound__package=package
|
|
||||||
).exists():
|
|
||||||
# TODO should we add a structure?
|
|
||||||
return CompoundStructure.objects.get(
|
|
||||||
smiles=standardized_smiles, compound__package=package
|
|
||||||
).compound
|
|
||||||
|
|
||||||
# Generate Compound
|
|
||||||
c = Compound()
|
|
||||||
c.package = package
|
|
||||||
|
|
||||||
if name is not None:
|
if name is not None:
|
||||||
# Clean for potential XSS
|
# Clean for potential XSS
|
||||||
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
|
standardized_smiles = FormatConverter.standardize(smiles, remove_stereo=True)
|
||||||
|
|
||||||
|
subclasses = CompoundStructure.__subclasses__()
|
||||||
|
|
||||||
|
qs = CompoundStructure.objects.filter(smiles=smiles, compound__package=package)
|
||||||
|
if subclasses:
|
||||||
|
qs = qs.not_instance_of(*subclasses)
|
||||||
|
|
||||||
|
# Check if we find a direct match for a given SMILES
|
||||||
|
if qs.exists():
|
||||||
|
found_structure = qs.first()
|
||||||
|
found_compound = found_structure.compound
|
||||||
|
|
||||||
|
if name:
|
||||||
|
found_structure.add_alias(name)
|
||||||
|
found_compound.add_alias(name)
|
||||||
|
|
||||||
|
return found_compound
|
||||||
|
|
||||||
|
|
||||||
|
qs = CompoundStructure.objects.filter(smiles=standardized_smiles, compound__package=package)
|
||||||
|
if subclasses:
|
||||||
|
qs = qs.not_instance_of(*subclasses)
|
||||||
|
|
||||||
|
# Check if we can find the standardized one
|
||||||
|
if qs.exists():
|
||||||
|
# TODO should we add a structure?
|
||||||
|
found_structure = qs.first()
|
||||||
|
found_compound = found_structure.compound
|
||||||
|
|
||||||
|
if name:
|
||||||
|
found_structure.add_alias(name)
|
||||||
|
found_compound.add_alias(name)
|
||||||
|
|
||||||
|
return found_compound
|
||||||
|
|
||||||
|
# Generate Compound
|
||||||
|
c = Compound()
|
||||||
|
c.package = package
|
||||||
|
|
||||||
if name is None or name == "":
|
if name is None or name == "":
|
||||||
name = f"Compound {Compound.objects.filter(package=package).count() + 1}"
|
name = f"Compound {Compound.objects.filter(package=package).count() + 1}"
|
||||||
|
|
||||||
@ -1138,18 +1167,27 @@ class CompoundStructure(
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create(
|
def create(
|
||||||
compound: Compound, smiles: str, name: str = None, description: str = None, *args, **kwargs
|
compound: Compound, smiles: str, name: str = None, description: str = None, molfile: str = None, *args, **kwargs
|
||||||
):
|
):
|
||||||
|
# Clean for potential XSS
|
||||||
|
if name is not None:
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if CompoundStructure.objects.filter(compound=compound, smiles=smiles).exists():
|
if CompoundStructure.objects.filter(compound=compound, smiles=smiles).exists():
|
||||||
return CompoundStructure.objects.get(compound=compound, smiles=smiles)
|
found_cs = CompoundStructure.objects.get(compound=compound, smiles=smiles)
|
||||||
|
|
||||||
|
if name:
|
||||||
|
found_cs.add_alias(name)
|
||||||
|
|
||||||
|
return found_cs
|
||||||
|
|
||||||
if compound.pk is None:
|
if compound.pk is None:
|
||||||
raise ValueError("Unpersisted Compound! Persist compound first!")
|
raise ValueError("Unpersisted Compound! Persist compound first!")
|
||||||
|
|
||||||
cs = CompoundStructure()
|
cs = CompoundStructure()
|
||||||
# Clean for potential XSS
|
|
||||||
if name is not None:
|
if name is not None:
|
||||||
cs.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
cs.name = name
|
||||||
|
|
||||||
if description is not None:
|
if description is not None:
|
||||||
cs.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
cs.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
@ -1157,6 +1195,10 @@ class CompoundStructure(
|
|||||||
cs.smiles = smiles
|
cs.smiles = smiles
|
||||||
cs.compound = compound
|
cs.compound = compound
|
||||||
|
|
||||||
|
# Check if molfile is present and valid
|
||||||
|
if molfile is not None and FormatConverter.from_molfile(molfile) is not None:
|
||||||
|
cs.molfile = molfile
|
||||||
|
|
||||||
if "normalized_structure" in kwargs:
|
if "normalized_structure" in kwargs:
|
||||||
cs.normalized_structure = kwargs["normalized_structure"]
|
cs.normalized_structure = kwargs["normalized_structure"]
|
||||||
|
|
||||||
@ -1174,6 +1216,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
|
||||||
@ -1371,6 +1415,9 @@ class SimpleAmbitRule(SimpleRule):
|
|||||||
if not FormatConverter.is_valid_smirks(smirks):
|
if not FormatConverter.is_valid_smirks(smirks):
|
||||||
raise ValueError(f'SMIRKS "{smirks}" is invalid!')
|
raise ValueError(f'SMIRKS "{smirks}" is invalid!')
|
||||||
|
|
||||||
|
if name is not None:
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
query = SimpleAmbitRule.objects.filter(package=package, smirks=smirks)
|
query = SimpleAmbitRule.objects.filter(package=package, smirks=smirks)
|
||||||
|
|
||||||
if reactant_filter_smarts is not None and reactant_filter_smarts.strip() != "":
|
if reactant_filter_smarts is not None and reactant_filter_smarts.strip() != "":
|
||||||
@ -1382,14 +1429,17 @@ class SimpleAmbitRule(SimpleRule):
|
|||||||
if query.exists():
|
if query.exists():
|
||||||
if query.count() > 1:
|
if query.count() > 1:
|
||||||
logger.error(f"More than one rule matched this one! {query}")
|
logger.error(f"More than one rule matched this one! {query}")
|
||||||
return query.first()
|
|
||||||
|
found_rule = query.first()
|
||||||
|
|
||||||
|
if name:
|
||||||
|
found_rule.add_alias(name)
|
||||||
|
|
||||||
|
return found_rule
|
||||||
|
|
||||||
r = SimpleAmbitRule()
|
r = SimpleAmbitRule()
|
||||||
r.package = package
|
r.package = package
|
||||||
|
|
||||||
if name is not None:
|
|
||||||
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
|
||||||
|
|
||||||
if name is None or name == "":
|
if name is None or name == "":
|
||||||
name = f"Rule {Rule.objects.filter(package=package).count() + 1}"
|
name = f"Rule {Rule.objects.filter(package=package).count() + 1}"
|
||||||
|
|
||||||
@ -1610,10 +1660,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")
|
||||||
@ -1630,8 +1685,13 @@ 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,
|
||||||
):
|
):
|
||||||
|
|
||||||
|
# Clean for potential XSS
|
||||||
|
if name is not None and name.strip() != "":
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
_educts = []
|
_educts = []
|
||||||
_products = []
|
_products = []
|
||||||
|
|
||||||
@ -1686,16 +1746,21 @@ class Reaction(
|
|||||||
logger.error(
|
logger.error(
|
||||||
f"Found more than one reaction for given input! {existing_reaction_qs}"
|
f"Found more than one reaction for given input! {existing_reaction_qs}"
|
||||||
)
|
)
|
||||||
return existing_reaction_qs.first()
|
|
||||||
|
found_reaction = existing_reaction_qs.first()
|
||||||
|
|
||||||
|
if name:
|
||||||
|
found_reaction.add_alias(name)
|
||||||
|
|
||||||
|
return found_reaction
|
||||||
|
|
||||||
r = Reaction()
|
r = Reaction()
|
||||||
r.package = package
|
r.package = package
|
||||||
|
|
||||||
# Clean for potential XSS
|
if r is not None:
|
||||||
if name is not None and name.strip() != "":
|
r.name = name
|
||||||
r.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
|
||||||
|
|
||||||
if description is not None and name.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
r.multi_step = multi_step
|
r.multi_step = multi_step
|
||||||
@ -1773,7 +1838,7 @@ class Reaction(
|
|||||||
return new_reaction
|
return new_reaction
|
||||||
|
|
||||||
def smirks(self):
|
def smirks(self):
|
||||||
return f"{'.'.join([cs.smiles for cs in self.educts.all()])}>>{'.'.join([cs.smiles for cs in self.products.all()])}"
|
return f"{'.'.join([cs.smiles for cs in self.educts.all().order_by('-pk')])}>>{'.'.join([cs.smiles for cs in self.products.all().order_by('-pk')])}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def as_svg(self):
|
def as_svg(self):
|
||||||
@ -1886,6 +1951,9 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
|
|||||||
if n not in queue:
|
if n not in queue:
|
||||||
queue.append(n)
|
queue.append(n)
|
||||||
|
|
||||||
|
for i in queue:
|
||||||
|
processed.add(i)
|
||||||
|
|
||||||
while len(queue):
|
while len(queue):
|
||||||
current = queue.pop()
|
current = queue.pop()
|
||||||
processed.add(current)
|
processed.add(current)
|
||||||
@ -2163,7 +2231,7 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
|
|||||||
smiles: str,
|
smiles: str,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
depth: Optional[int] = 0,
|
depth: Optional[int] = -1,
|
||||||
):
|
):
|
||||||
return Node.create(self, smiles, depth, name=name, description=description)
|
return Node.create(self, smiles, depth, name=name, description=description)
|
||||||
|
|
||||||
@ -2192,18 +2260,28 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
|
|||||||
|
|
||||||
depth_map = {}
|
depth_map = {}
|
||||||
depth_map[0] = list()
|
depth_map[0] = list()
|
||||||
|
processed = set()
|
||||||
|
|
||||||
for n in self.nodes:
|
data_driven_root_nodes = (
|
||||||
num_parents = in_count[str(n.uuid)]
|
self.node_set.all()
|
||||||
if num_parents == 0:
|
.annotate(prod_cnt=Count("edge_products"), educt_cnt=Count("edge_educts"))
|
||||||
# must be a root node or unconnected node
|
.filter(prod_cnt=0, educt_cnt__gt=0)
|
||||||
if n.depth != 0:
|
.distinct()
|
||||||
n.depth = 0
|
)
|
||||||
n.save()
|
|
||||||
|
|
||||||
# Only root node may have children
|
# Eval QuerySet
|
||||||
if out_count[str(n.uuid)] > 0:
|
root_nodes_by_depth = list(self.root_nodes)
|
||||||
depth_map[0].append(n)
|
|
||||||
|
data_driven_root_nodes.update(depth=0)
|
||||||
|
root_nodes = [n for n in data_driven_root_nodes]
|
||||||
|
|
||||||
|
for n in root_nodes_by_depth:
|
||||||
|
if n not in root_nodes:
|
||||||
|
if len(n.edge_products.all()) == 0:
|
||||||
|
root_nodes.append(n)
|
||||||
|
|
||||||
|
for n in root_nodes:
|
||||||
|
depth_map[0].append(n)
|
||||||
|
|
||||||
# At most depth len(nodes) is possible
|
# At most depth len(nodes) is possible
|
||||||
for i in range(self.nodes.count()):
|
for i in range(self.nodes.count()):
|
||||||
@ -2214,17 +2292,19 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
|
|||||||
|
|
||||||
unique_next_level = set()
|
unique_next_level = set()
|
||||||
for n in level_nodes:
|
for n in level_nodes:
|
||||||
|
processed.add(n)
|
||||||
for e in self.edges:
|
for e in self.edges:
|
||||||
if n in e.start_nodes.all():
|
if n in e.start_nodes.all():
|
||||||
for p in e.end_nodes.all():
|
for p in e.end_nodes.all():
|
||||||
unique_next_level.add(p)
|
if p not in processed:
|
||||||
|
unique_next_level.add(p)
|
||||||
|
|
||||||
if len(unique_next_level) > 0:
|
if len(unique_next_level) > 0:
|
||||||
depth_map[i + 1] = list(unique_next_level)
|
depth_map[i + 1] = list(unique_next_level)
|
||||||
|
|
||||||
for depth, nodes in depth_map.items():
|
for depth, nodes in depth_map.items():
|
||||||
for n in nodes:
|
for n in nodes:
|
||||||
if n.depth != depth:
|
if n.depth != depth and depth != 0:
|
||||||
n.depth = depth
|
n.depth = depth
|
||||||
n.save()
|
n.save()
|
||||||
|
|
||||||
@ -2280,7 +2360,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(),
|
||||||
@ -2294,6 +2379,7 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
|
|||||||
},
|
},
|
||||||
"predicted_properties": predicted_properties,
|
"predicted_properties": predicted_properties,
|
||||||
"is_engineered_intermediate": self.kv.get("is_engineered_intermediate", False),
|
"is_engineered_intermediate": self.kv.get("is_engineered_intermediate", False),
|
||||||
|
"proposed": self.is_proposed_intermediate(),
|
||||||
"timeseries": self.get_timeseries_data(),
|
"timeseries": self.get_timeseries_data(),
|
||||||
**structure_data,
|
**structure_data,
|
||||||
}
|
}
|
||||||
@ -2334,12 +2420,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
|
||||||
|
|
||||||
@ -2362,6 +2453,26 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def is_proposed_intermediate(self):
|
||||||
|
collected = defaultdict(dict)
|
||||||
|
for ai in self.additional_information.filter(
|
||||||
|
type__in=["ProposedIntermediate", "TransformationProductImportance", "Confidence"]
|
||||||
|
, scenario__isnull=False
|
||||||
|
):
|
||||||
|
collected[str(ai.scenario.uuid)]["scenarioId"] = ai.scenario.url
|
||||||
|
collected[str(ai.scenario.uuid)]["scenarioName"] = ai.scenario.name
|
||||||
|
|
||||||
|
if ai.type == "ProposedIntermediate":
|
||||||
|
collected[str(ai.scenario.uuid)]["proposed"] = True
|
||||||
|
|
||||||
|
if ai.type == "Confidence":
|
||||||
|
collected[str(ai.scenario.uuid)]["Confidence"] = ai.get().level
|
||||||
|
|
||||||
|
if ai.type == "TransformationProductImportance":
|
||||||
|
collected[str(ai.scenario.uuid)]["Transformation product importance"] = ai.get().importance.value
|
||||||
|
|
||||||
|
return list(collected.values())
|
||||||
|
|
||||||
def simple_json(self, include_description=False):
|
def simple_json(self, include_description=False):
|
||||||
res = super().simple_json()
|
res = super().simple_json()
|
||||||
name = res.get("name", None)
|
name = res.get("name", None)
|
||||||
@ -2376,7 +2487,7 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
|
|||||||
"epdb.Pathway", verbose_name="belongs to", on_delete=models.CASCADE, db_index=True
|
"epdb.Pathway", verbose_name="belongs to", on_delete=models.CASCADE, db_index=True
|
||||||
)
|
)
|
||||||
edge_label = models.ForeignKey(
|
edge_label = models.ForeignKey(
|
||||||
"epdb.Reaction", verbose_name="Edge label", null=True, on_delete=models.SET_NULL
|
"epdb.Reaction", verbose_name="Edge label", null=True, on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
start_nodes = models.ManyToManyField(
|
start_nodes = models.ManyToManyField(
|
||||||
"epdb.Node", verbose_name="Start Nodes", related_name="edge_educts"
|
"epdb.Node", verbose_name="Start Nodes", related_name="edge_educts"
|
||||||
@ -2455,6 +2566,8 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
|
|||||||
rule: Optional[Rule] = None,
|
rule: Optional[Rule] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
):
|
):
|
||||||
e = Edge()
|
e = Edge()
|
||||||
e.pathway = pathway
|
e.pathway = pathway
|
||||||
@ -2484,7 +2597,7 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
|
|||||||
educts=[n.default_node_label for n in e.start_nodes.all()],
|
educts=[n.default_node_label for n in e.start_nodes.all()],
|
||||||
products=[n.default_node_label for n in e.end_nodes.all()],
|
products=[n.default_node_label for n in e.end_nodes.all()],
|
||||||
rules=rule,
|
rules=rule,
|
||||||
multi_step=False,
|
multi_step=kwargs.get("multi_step", False),
|
||||||
)
|
)
|
||||||
|
|
||||||
e.edge_label = r
|
e.edge_label = r
|
||||||
@ -4492,18 +4605,22 @@ class AdditionalInformation(models.Model):
|
|||||||
|
|
||||||
return f"{self.scenario.url}/additional-information/{self.uuid}"
|
return f"{self.scenario.url}/additional-information/{self.uuid}"
|
||||||
|
|
||||||
def get(self) -> "EnviPyModel":
|
@staticmethod
|
||||||
|
def from_dict(ai_type: str, ai_data: Dict[str, Any]):
|
||||||
from envipy_additional_information import registry
|
from envipy_additional_information import registry
|
||||||
|
|
||||||
MAPPING = {c.__name__: c for c in registry.list_models().values()}
|
MAPPING = {c.__name__: c for c in registry.list_models().values()}
|
||||||
try:
|
try:
|
||||||
inst = MAPPING[self.type](**self.data)
|
inst = MAPPING[ai_type](**ai_data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading {self.type}: {e}")
|
print(f"Error loading {ai_type}: {e}")
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
inst.__dict__["uuid"] = str(self.uuid)
|
return inst
|
||||||
|
|
||||||
|
def get(self) -> "EnviPyModel":
|
||||||
|
inst = AdditionalInformation.from_dict(self.type, self.data)
|
||||||
|
inst.__dict__["uuid"] = str(self.uuid)
|
||||||
return inst
|
return inst
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
|||||||
123
epdb/views.py
123
epdb/views.py
@ -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,6 +389,9 @@ 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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -587,10 +591,38 @@ 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":
|
||||||
@ -754,6 +786,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": {
|
||||||
@ -766,12 +803,14 @@ 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():
|
||||||
@ -779,6 +818,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()] = {
|
||||||
@ -787,12 +829,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":
|
||||||
@ -906,12 +942,13 @@ def package_models(request, package_uuid):
|
|||||||
"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():
|
||||||
@ -1087,6 +1124,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:
|
||||||
@ -1190,9 +1232,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}"'
|
||||||
|
|
||||||
@ -2328,7 +2368,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()
|
||||||
current_pathway.add_node(node_smiles, name=node_name, description=node_description)
|
|
||||||
|
try:
|
||||||
|
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)
|
||||||
|
|
||||||
@ -2436,7 +2482,22 @@ def package_pathway_node(request, package_uuid, pathway_uuid, node_uuid):
|
|||||||
|
|
||||||
return JsonResponse({"success": current_node.url})
|
return JsonResponse({"success": current_node.url})
|
||||||
|
|
||||||
return HttpResponseBadRequest()
|
new_node_name = request.POST.get("node-name")
|
||||||
|
new_node_description = request.POST.get("node-description")
|
||||||
|
|
||||||
|
if any([new_node_name, new_node_description]):
|
||||||
|
if new_node_name is not None and new_node_name.strip() != "":
|
||||||
|
new_node_name = nh3.clean(new_node_name.strip(), tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
current_node.name = new_node_name
|
||||||
|
|
||||||
|
if new_node_description is not None and new_node_description.strip() != "":
|
||||||
|
new_node_description = nh3.clean(new_node_description.strip(), tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
current_node.description = new_node_description
|
||||||
|
|
||||||
|
current_node.save()
|
||||||
|
return redirect(current_node.url)
|
||||||
|
|
||||||
|
return error(request, "Node update failed!", "No changes were made to the node")
|
||||||
else:
|
else:
|
||||||
return HttpResponseNotAllowed(["GET", "POST"])
|
return HttpResponseNotAllowed(["GET", "POST"])
|
||||||
|
|
||||||
@ -2968,9 +3029,15 @@ def settings(request):
|
|||||||
new_default = request.POST.get("prediction-setting-new-default", "off") == "on"
|
new_default = request.POST.get("prediction-setting-new-default", "off") == "on"
|
||||||
|
|
||||||
# min 2, max s.DEFAULT_MAX_NUMBER_OF_NODES
|
# min 2, max s.DEFAULT_MAX_NUMBER_OF_NODES
|
||||||
|
temp_max_nodes = request.POST.get("prediction-setting-max-nodes")
|
||||||
|
if temp_max_nodes is None or temp_max_nodes == "" or int(temp_max_nodes) == -1:
|
||||||
|
temp_max_nodes = s.DEFAULT_MAX_NUMBER_OF_NODES
|
||||||
|
else:
|
||||||
|
temp_max_nodes = int(request.POST.get("prediction-setting-max-nodes", 1))
|
||||||
|
|
||||||
max_nodes = min(
|
max_nodes = min(
|
||||||
max(
|
max(
|
||||||
int(request.POST.get("prediction-setting-max-nodes", 1)),
|
temp_max_nodes,
|
||||||
2,
|
2,
|
||||||
),
|
),
|
||||||
s.DEFAULT_MAX_NUMBER_OF_NODES,
|
s.DEFAULT_MAX_NUMBER_OF_NODES,
|
||||||
@ -2991,6 +3058,7 @@ def settings(request):
|
|||||||
|
|
||||||
model_uuid = model_url.split("/")[-1]
|
model_uuid = model_url.split("/")[-1]
|
||||||
params["model"] = EPModel.objects.get(uuid=model_uuid)
|
params["model"] = EPModel.objects.get(uuid=model_uuid)
|
||||||
|
# TODO Check if removed if request contains "" or not at all
|
||||||
params["model_threshold"] = request.POST.get(
|
params["model_threshold"] = request.POST.get(
|
||||||
"model-based-prediction-setting-threshold", s.DEFAULT_MODEL_THRESHOLD
|
"model-based-prediction-setting-threshold", s.DEFAULT_MODEL_THRESHOLD
|
||||||
)
|
)
|
||||||
@ -3080,12 +3148,21 @@ def jobs(request):
|
|||||||
{"Home": s.SERVER_URL},
|
{"Home": s.SERVER_URL},
|
||||||
{"Jobs": s.SERVER_URL + "/jobs"},
|
{"Jobs": s.SERVER_URL + "/jobs"},
|
||||||
]
|
]
|
||||||
if current_user.is_superuser:
|
# if current_user.is_superuser:
|
||||||
context["jobs"] = JobLog.objects.all().order_by("-created")
|
# context["jobs"] = JobLog.objects.all().order_by("-created")
|
||||||
else:
|
# else:
|
||||||
context["jobs"] = JobLog.objects.filter(user=current_user).order_by("-created")
|
# context["jobs"] = JobLog.objects.filter(user=current_user).order_by("-created")
|
||||||
|
|
||||||
return render(request, "collections/joblog.html", context)
|
# Context for paginated template
|
||||||
|
context["entity_type"] = "joblog"
|
||||||
|
context["api_endpoint"] = f"{s.SERVER_PATH}/api/v1/joblog/"
|
||||||
|
context["per_page"] = s.API_PAGINATION_DEFAULT_PAGE_SIZE
|
||||||
|
context["list_title"] = "joblog"
|
||||||
|
context["list_mode"] = "combined"
|
||||||
|
|
||||||
|
return render(request, "collections/joblog_paginated.html", context)
|
||||||
|
|
||||||
|
# return render(request, "collections/joblog.html", context)
|
||||||
|
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
job_name = request.POST.get("job-name")
|
job_name = request.POST.get("job-name")
|
||||||
|
|||||||
1610504
fixtures/SOIL_ engineered soil degradation pathways for ML 20251103.json
Normal file
1610504
fixtures/SOIL_ engineered soil degradation pathways for ML 20251103.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
154
fixtures/pes.json
Normal file
154
fixtures/pes.json
Normal file
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)
|
||||||
|
|||||||
@ -21,5 +21,11 @@
|
|||||||
"django",
|
"django",
|
||||||
"tailwindcss",
|
"tailwindcss",
|
||||||
"daisyui"
|
"daisyui"
|
||||||
]
|
],
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"@parcel/watcher",
|
||||||
|
"@tailwindcss/oxide"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
allowBuilds:
|
||||||
|
'@parcel/watcher': true
|
||||||
onlyBuiltDependencies:
|
onlyBuiltDependencies:
|
||||||
- '@parcel/watcher'
|
- '@parcel/watcher'
|
||||||
- '@tailwindcss/oxide'
|
- '@tailwindcss/oxide'
|
||||||
@ -34,3 +34,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@import "./daisyui-theme.css";
|
@import "./daisyui-theme.css";
|
||||||
|
|
||||||
|
select.select[multiple] {
|
||||||
|
display: block;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
p a {
|
||||||
|
@apply underline;
|
||||||
|
}
|
||||||
|
|||||||
BIN
static/images/Restricted.gif
Normal file
BIN
static/images/Restricted.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
30
static/images/bayer-logo.svg
Normal file
30
static/images/bayer-logo.svg
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?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>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
BIN
static/images/restricted_mid.png
Normal file
BIN
static/images/restricted_mid.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
static/images/secret_mid.png
Normal file
BIN
static/images/secret_mid.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
static/images/secret_small.png
Normal file
BIN
static/images/secret_small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
@ -59,6 +59,9 @@ document.addEventListener("alpine:init", () => {
|
|||||||
get isEditMode() {
|
get isEditMode() {
|
||||||
return this.mode === "edit";
|
return this.mode === "edit";
|
||||||
},
|
},
|
||||||
|
get isRequired() {
|
||||||
|
return (this.schema.required || []).indexOf(this.fieldName) > -1
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Text widget
|
// Text widget
|
||||||
@ -293,6 +296,34 @@ document.addEventListener("alpine:init", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// PubMed link widget
|
||||||
|
Alpine.data(
|
||||||
|
"doiWidget",
|
||||||
|
(fieldName, data, schema, uiSchema, mode, debugErrors, context = null) => ({
|
||||||
|
...baseWidget(
|
||||||
|
fieldName,
|
||||||
|
data,
|
||||||
|
schema,
|
||||||
|
uiSchema,
|
||||||
|
mode,
|
||||||
|
debugErrors,
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
|
||||||
|
get value() {
|
||||||
|
return this.data[this.fieldName] || "";
|
||||||
|
},
|
||||||
|
set value(v) {
|
||||||
|
this.data[this.fieldName] = v;
|
||||||
|
},
|
||||||
|
get doiUrl() {
|
||||||
|
return this.value
|
||||||
|
? `https://dx.doi.org/${this.value}`
|
||||||
|
: null;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// Compound link widget
|
// Compound link widget
|
||||||
Alpine.data(
|
Alpine.data(
|
||||||
"compoundWidget",
|
"compoundWidget",
|
||||||
|
|||||||
@ -5,6 +5,125 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
document.addEventListener('alpine:init', () => {
|
document.addEventListener('alpine:init', () => {
|
||||||
|
|
||||||
|
const basePagination = (
|
||||||
|
items,
|
||||||
|
currentPage,
|
||||||
|
totalPages,
|
||||||
|
totalItems,
|
||||||
|
perPage,
|
||||||
|
isReviewed,
|
||||||
|
instanceId
|
||||||
|
) => ({
|
||||||
|
items,
|
||||||
|
currentPage,
|
||||||
|
totalPages,
|
||||||
|
totalItems,
|
||||||
|
perPage,
|
||||||
|
isReviewed,
|
||||||
|
instanceId,
|
||||||
|
|
||||||
|
get paginatedItems() {
|
||||||
|
return this.items;
|
||||||
|
},
|
||||||
|
|
||||||
|
get showingStart() {
|
||||||
|
if (this.totalItems === 0) return 0;
|
||||||
|
return (this.currentPage - 1) * this.perPage + 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
get showingEnd() {
|
||||||
|
if (this.totalItems === 0) return 0;
|
||||||
|
return Math.min((this.currentPage - 1) * this.perPage + this.items.length, this.totalItems);
|
||||||
|
},
|
||||||
|
|
||||||
|
nextPage() {
|
||||||
|
if (this.currentPage < this.totalPages) {
|
||||||
|
this.fetchPage(this.currentPage + 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
prevPage() {
|
||||||
|
if (this.currentPage > 1) {
|
||||||
|
this.fetchPage(this.currentPage - 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
goToPage(page) {
|
||||||
|
if (page >= 1 && page <= this.totalPages) {
|
||||||
|
this.fetchPage(page);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
get pageNumbers() {
|
||||||
|
const pages = [];
|
||||||
|
const total = this.totalPages;
|
||||||
|
const current = this.currentPage;
|
||||||
|
|
||||||
|
if (total === 0) {
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total <= 7) {
|
||||||
|
for (let i = 1; i <= total; i++) {
|
||||||
|
pages.push({ page: i, isEllipsis: false, key: `${this.instanceId}-page-${i}` });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pages.push({ page: 1, isEllipsis: false, key: `${this.instanceId}-page-1` });
|
||||||
|
|
||||||
|
let rangeStart;
|
||||||
|
let rangeEnd;
|
||||||
|
|
||||||
|
if (current <= 4) {
|
||||||
|
rangeStart = 2;
|
||||||
|
rangeEnd = 5;
|
||||||
|
} else if (current >= total - 3) {
|
||||||
|
rangeStart = total - 4;
|
||||||
|
rangeEnd = total - 1;
|
||||||
|
} else {
|
||||||
|
rangeStart = current - 1;
|
||||||
|
rangeEnd = current + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangeStart > 2) {
|
||||||
|
pages.push({ page: '...', isEllipsis: true, key: `${this.instanceId}-ellipsis-start` });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = rangeStart; i <= rangeEnd; i++) {
|
||||||
|
pages.push({ page: i, isEllipsis: false, key: `${this.instanceId}-page-${i}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangeEnd < total - 1) {
|
||||||
|
pages.push({ page: '...', isEllipsis: true, key: `${this.instanceId}-ellipsis-end` });
|
||||||
|
}
|
||||||
|
|
||||||
|
pages.push({ page: total, isEllipsis: false, key: `${this.instanceId}-page-${total}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Alpine.data('paginatedList',
|
||||||
|
(items, options = {}) => ({
|
||||||
|
...basePagination(items,1, 0, 0, options.perPage || 50, options.isReviewed || false, options.instanceId || Math.random().toString(36).substring(2, 9),),
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.fetchPage(1);
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchPage(page) {
|
||||||
|
this.totalItems = this.items.length;
|
||||||
|
this.totalPages = Math.ceil(this.items.length / this.perPage);
|
||||||
|
this.currentPage = page
|
||||||
|
|
||||||
|
const start = page * this.perPage - this.perPage;
|
||||||
|
const end = page * this.perPage;
|
||||||
|
return this.items.slice(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
Alpine.data('remotePaginatedList', (options = {}) => ({
|
Alpine.data('remotePaginatedList', (options = {}) => ({
|
||||||
items: [],
|
items: [],
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
|
|||||||
187
static/js/pw.js
187
static/js/pw.js
@ -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",
|
||||||
@ -23,6 +55,7 @@ function predictFromNode(url) {
|
|||||||
// elem = 'vizdiv'
|
// elem = 'vizdiv'
|
||||||
function draw(pathway, elem) {
|
function draw(pathway, elem) {
|
||||||
|
|
||||||
|
const initialzoom = 2.5
|
||||||
const nodeRadius = 20;
|
const nodeRadius = 20;
|
||||||
const linkDistance = 100;
|
const linkDistance = 100;
|
||||||
const chargeStrength = -200;
|
const chargeStrength = -200;
|
||||||
@ -34,6 +67,9 @@ function draw(pathway, elem) {
|
|||||||
const horizontalSpacing = 75; // horizontal space between nodes
|
const horizontalSpacing = 75; // horizontal space between nodes
|
||||||
const depthMap = new Map();
|
const depthMap = new Map();
|
||||||
|
|
||||||
|
// Avoid leaving unconnected Nodes leaving the viewport
|
||||||
|
nodes.forEach(node => {if (node.depth < 0) node.depth = 0;});
|
||||||
|
|
||||||
// Sort nodes by depth first to minimize crossings
|
// Sort nodes by depth first to minimize crossings
|
||||||
const sortedNodes = [...nodes].sort((a, b) => a.depth - b.depth);
|
const sortedNodes = [...nodes].sort((a, b) => a.depth - b.depth);
|
||||||
|
|
||||||
@ -63,7 +99,7 @@ function draw(pathway, elem) {
|
|||||||
node.fx = width / 2 + depthMap.get(node.depth) * horizontalSpacing - ((nodesInLevel - 1) * horizontalSpacing) / 2;
|
node.fx = width / 2 + depthMap.get(node.depth) * horizontalSpacing - ((nodesInLevel - 1) * horizontalSpacing) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
node.fy = node.depth * levelSpacing + 50;
|
node.fy = (node.depth + initialzoom + 0.5) * levelSpacing + 50;
|
||||||
depthMap.set(node.depth, depthMap.get(node.depth) + 1);
|
depthMap.set(node.depth, depthMap.get(node.depth) + 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -101,12 +137,30 @@ function draw(pathway, elem) {
|
|||||||
// Update pseudo node positions first
|
// Update pseudo node positions first
|
||||||
updatePseudoNodePositions();
|
updatePseudoNodePositions();
|
||||||
|
|
||||||
link.attr("x1", d => d.source.x)
|
link.attr("d", d => {
|
||||||
.attr("y1", d => d.source.y)
|
// Check if it's a self-loop (source equals target)
|
||||||
.attr("x2", d => d.target.x)
|
if (d.source.id === d.target.id) {
|
||||||
.attr("y2", d => d.target.y);
|
// Create a bezier curve for self-loops
|
||||||
|
const x = d.source.x;
|
||||||
|
const y = d.source.y;
|
||||||
|
const loopRadius = nodeRadius * 2; // Adjust size of the loop
|
||||||
|
|
||||||
|
// Create a circular path to the left of the node
|
||||||
|
return `M ${x},${y - nodeRadius}
|
||||||
|
C ${x - loopRadius},${y - nodeRadius - loopRadius}
|
||||||
|
${x - loopRadius},${y + nodeRadius + loopRadius}
|
||||||
|
${x},${y + nodeRadius}`;
|
||||||
|
} else {
|
||||||
|
// Regular straight line for normal edges
|
||||||
|
return `M ${d.source.x},${d.source.y} L ${d.target.x},${d.target.y}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
node.attr("transform", d => `translate(${d.x},${d.y})`);
|
node.attr("transform", d => `translate(${d.x},${d.y})`);
|
||||||
|
|
||||||
|
linkText
|
||||||
|
.attr("x", d => (d.source.x + d.target.x) / 2)
|
||||||
|
.attr("y", d => (d.source.y + d.target.y) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
function dragstarted(event, d) {
|
function dragstarted(event, d) {
|
||||||
@ -209,7 +263,7 @@ function draw(pathway, elem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait before showing popup (ms)
|
// Wait before showing popup (ms)
|
||||||
var popupWaitBeforeShow = 1000;
|
var popupWaitBeforeShow = 500;
|
||||||
|
|
||||||
// Custom popover element
|
// Custom popover element
|
||||||
let popoverTimeout = null;
|
let popoverTimeout = null;
|
||||||
@ -463,7 +517,7 @@ function draw(pathway, elem) {
|
|||||||
// TODO needs to be generic once we store it as AddInf
|
// TODO needs to be generic once we store it as AddInf
|
||||||
for (var s of n.predicted_properties["PepperPrediction"]) {
|
for (var s of n.predicted_properties["PepperPrediction"]) {
|
||||||
if (s["mean"] != null) {
|
if (s["mean"] != null) {
|
||||||
tempContent += "<b>DT50 predicted via Pepper:</b> " + s["mean"].toFixed(2) + "<br>"
|
tempContent += "<b>DT50 predicted via Pepper:</b> " + s["mean"].toFixed(2) + " days<br>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -563,6 +617,8 @@ function draw(pathway, elem) {
|
|||||||
// 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);
|
||||||
|
|
||||||
// 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);
|
||||||
@ -581,11 +637,11 @@ function draw(pathway, elem) {
|
|||||||
for (idx in parents) {
|
for (idx in parents) {
|
||||||
p = nodes[parents[idx]]
|
p = nodes[parents[idx]]
|
||||||
// console.log(p.depth)
|
// console.log(p.depth)
|
||||||
if (p.depth >= n.depth) {
|
// if (p.depth >= n.depth) {
|
||||||
// keep the .5 steps for pseudo nodes
|
// // keep the .5 steps for pseudo nodes
|
||||||
n.depth = n.pseudo ? p.depth + 1 : Math.floor(p.depth + 1);
|
// n.depth = n.pseudo ? p.depth + 1 : Math.floor(p.depth + 1);
|
||||||
// console.log("Adjusting", orig_depth, Math.floor(p.depth + 1));
|
// // console.log("Adjusting", orig_depth, Math.floor(p.depth + 1));
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -598,14 +654,68 @@ function draw(pathway, elem) {
|
|||||||
.on("tick", ticked);
|
.on("tick", ticked);
|
||||||
|
|
||||||
// Kanten zeichnen
|
// Kanten zeichnen
|
||||||
const link = zoomable.append("g")
|
const linkGroup = zoomable.append("g")
|
||||||
.selectAll("line")
|
.selectAll("g")
|
||||||
.data(links)
|
.data(links)
|
||||||
.enter().append("line")
|
.enter()
|
||||||
// Check if target is pseudo and draw marker only if not pseudo
|
.append("g")
|
||||||
.attr("class", d => d.target.pseudo ? "link_no_arrow" : "link")
|
.attr("class", "link-group");
|
||||||
.attr("marker-end", d => d.target.pseudo ? '' : d.multi_step ? 'url(#doublearrow)' : 'url(#arrow)')
|
|
||||||
|
|
||||||
|
const link = linkGroup
|
||||||
|
.append("path")
|
||||||
|
.attr("class", d => d.target.pseudo ? "link_no_arrow" : "link")
|
||||||
|
.attr("marker-end", d => d.target.pseudo ? "" : "url(#arrow)")
|
||||||
|
.attr("fill", "none")
|
||||||
|
|
||||||
|
// Check if target is pseudo and draw marker only if not pseudo
|
||||||
|
.attr("class", d => d.target.pseudo ? "link_no_arrow" : "link")
|
||||||
|
.attr("marker-end", d => {
|
||||||
|
if (d.target.pseudo) return '';
|
||||||
|
if (d.source.id === d.target.id) return 'url(#curve-arrow)'; // Use curve arrow for self-loops
|
||||||
|
return d.multi_step ? 'url(#doublearrow)' : 'url(#arrow)';
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.attr("fill", "none")
|
||||||
|
.on("click", function(event, d) {
|
||||||
|
const wasHighlighted = d3.select(this).classed("highlighted");
|
||||||
|
d3.selectAll("path").classed("highlighted", false);
|
||||||
|
if (!wasHighlighted) {
|
||||||
|
const toHighlight = [];
|
||||||
|
toHighlight.push(d.el);
|
||||||
|
|
||||||
|
if (d.source.pseudo || d.target.pseudo) {
|
||||||
|
if (d.target.pseudo) {
|
||||||
|
d3.selectAll("path").each(e => {
|
||||||
|
if (e !== undefined && e.source.id === d.target.id) {
|
||||||
|
toHighlight.push(e.el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
d3.selectAll("path").each(e => {
|
||||||
|
if (e !== undefined && (e.target.id === d.source.id || e.source.id === d.source.id)) {
|
||||||
|
toHighlight.push(e.el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const e of toHighlight) {
|
||||||
|
d3.select(e).classed("highlighted", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const linkText = linkGroup
|
||||||
|
.append("text")
|
||||||
|
.attr("class", "link-label")
|
||||||
|
.attr("text-anchor", "middle")
|
||||||
|
.attr("dy", -6)
|
||||||
|
.style("font-size", "4px")
|
||||||
|
.style("pointer-events", "none")
|
||||||
|
.style("display", "none")
|
||||||
|
.text(d => d.name)
|
||||||
|
.attr("x", d => (d.source.x + d.target.x) / 2)
|
||||||
|
.attr("y", d => (d.source.y + d.target.y) / 2);
|
||||||
|
|
||||||
// add element to links array
|
// add element to links array
|
||||||
link.each(function (d) {
|
link.each(function (d) {
|
||||||
@ -622,10 +732,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");
|
||||||
|
d3.selectAll('circle.highlighted').classed('highlighted', false);
|
||||||
|
d3.selectAll("path").classed("inedge", false);
|
||||||
|
d3.selectAll("path").classed("outedge", false);
|
||||||
|
|
||||||
|
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")
|
||||||
@ -699,14 +840,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);
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
{% for tpl in action_button_templates %}
|
{% for tpl in action_button_templates %}
|
||||||
{% include tpl %}
|
{% include tpl %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<li role="separator" class="divider"></li>
|
<li role="separator" class="divider h-px"></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
@ -74,7 +74,7 @@
|
|||||||
Rules</a
|
Rules</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li role="separator" class="divider"></li>
|
<li role="separator" class="divider h-px"></li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
@ -99,11 +99,16 @@
|
|||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li role="separator" class="divider"></li>
|
<li role="separator" class="divider h-px"></li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
onclick="document.getElementById('delete_pathway_node_modal').showModal(); return false;"
|
onclick="
|
||||||
|
const modal = document.getElementById('delete_pathway_node_modal');
|
||||||
|
modal.showModal();
|
||||||
|
window.dispatchEvent(new Event('modal-opened'));
|
||||||
|
return false;
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a
|
||||||
>
|
>
|
||||||
@ -111,7 +116,12 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
onclick="document.getElementById('delete_pathway_edge_modal').showModal(); return false;"
|
onclick="
|
||||||
|
const modal = document.getElementById('delete_pathway_edge_modal');
|
||||||
|
modal.showModal();
|
||||||
|
window.dispatchEvent(new Event('modal-opened'));
|
||||||
|
return false;
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
|
||||||
>
|
>
|
||||||
|
|||||||
102
templates/collections/_joblog_paginated_list_partial.html
Normal file
102
templates/collections/_joblog_paginated_list_partial.html
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
{# Partial for paginated list content - expects to be inside a remotePaginatedList Alpine.js context #}
|
||||||
|
{# Variables: empty_text (string), show_review_badge (bool), always_show_badge (bool) #}
|
||||||
|
{% load envipytags %}
|
||||||
|
{# Loading state #}
|
||||||
|
<div
|
||||||
|
x-show="isLoading"
|
||||||
|
class="mx-auto flex h-32 w-32 items-center justify-center"
|
||||||
|
>
|
||||||
|
{% include "components/loading-spinner.html" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Error state #}
|
||||||
|
<div
|
||||||
|
x-show="!isLoading && error"
|
||||||
|
class="alert alert-error/50 text-sm"
|
||||||
|
x-text="error"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
{# Content #}
|
||||||
|
<template x-if="!isLoading && !error">
|
||||||
|
<div>
|
||||||
|
{# Empty state #}
|
||||||
|
<div
|
||||||
|
x-show="totalItems === 0"
|
||||||
|
class="text-base-content/70 py-8 text-center"
|
||||||
|
>
|
||||||
|
<p>No {{ empty_text|default:"items" }} found.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Items list #}
|
||||||
|
<ul class="menu bg-base-100 rounded-box w-full" x-show="totalItems > 0">
|
||||||
|
<table class="table-zebra table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>User</th>
|
||||||
|
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Queued At</th>
|
||||||
|
<th>Done At</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<template x-for="obj in paginatedItems" :key="obj.url">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a :href="obj.user.url"><span x-text="obj.user.name"></span></a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a :href="obj.url"><span x-text="obj.id"></span></a>
|
||||||
|
</td>
|
||||||
|
<td><span x-text="obj.name"></span></td>
|
||||||
|
<td><span x-text="obj.status"></span></td>
|
||||||
|
<td><span x-text="obj.created"></span></td>
|
||||||
|
<td><span x-text="obj.done"></span></td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{# Pagination controls #}
|
||||||
|
<div
|
||||||
|
x-show="totalPages > 1"
|
||||||
|
class="mt-4 flex items-center justify-between px-2"
|
||||||
|
>
|
||||||
|
<span class="text-base-content/70 text-sm">
|
||||||
|
Showing <span x-text="showingStart"></span>-<span
|
||||||
|
x-text="showingEnd"
|
||||||
|
></span>
|
||||||
|
of <span x-text="totalItems"></span>
|
||||||
|
</span>
|
||||||
|
<div class="join">
|
||||||
|
<button
|
||||||
|
class="join-item btn btn-sm"
|
||||||
|
:disabled="currentPage === 1"
|
||||||
|
@click="prevPage()"
|
||||||
|
>
|
||||||
|
«
|
||||||
|
</button>
|
||||||
|
<template x-for="item in pageNumbers" :key="item.key">
|
||||||
|
<button
|
||||||
|
class="join-item btn btn-sm"
|
||||||
|
:class="{ 'btn-active': item.page === currentPage }"
|
||||||
|
:disabled="item.isEllipsis"
|
||||||
|
@click="!item.isEllipsis && goToPage(item.page)"
|
||||||
|
x-text="item.page"
|
||||||
|
></button>
|
||||||
|
</template>
|
||||||
|
<button
|
||||||
|
class="join-item btn btn-sm"
|
||||||
|
:disabled="currentPage === totalPages"
|
||||||
|
@click="nextPage()"
|
||||||
|
>
|
||||||
|
»
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
{% if meta.can_edit %}
|
{% if meta.can_edit or not meta.url_contains_package %}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
|
|||||||
13
templates/collections/joblog_paginated.html
Normal file
13
templates/collections/joblog_paginated.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{% extends "collections/paginated_base.html" %}
|
||||||
|
|
||||||
|
{% block page_title %}Jobs{% endblock %}
|
||||||
|
|
||||||
|
{% block action_button %}
|
||||||
|
{% endblock action_button %}
|
||||||
|
|
||||||
|
{% block action_modals %}
|
||||||
|
{% endblock action_modals %}
|
||||||
|
|
||||||
|
{% block description %}
|
||||||
|
<p>List of Jobs submitted.</p>
|
||||||
|
{% endblock description %}
|
||||||
@ -3,14 +3,16 @@
|
|||||||
{% block page_title %}Models{% endblock %}
|
{% block page_title %}Models{% endblock %}
|
||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
{% if meta.can_edit and meta.enabled_features.MODEL_BUILDING %}
|
{% if meta.can_edit or not meta.url_contains_package %}
|
||||||
<button
|
{% if meta.enabled_features.MODEL_BUILDING %}
|
||||||
type="button"
|
<button
|
||||||
class="btn btn-primary btn-sm"
|
type="button"
|
||||||
onclick="document.getElementById('new_model_modal').showModal(); return false;"
|
class="btn btn-primary btn-sm"
|
||||||
>
|
onclick="document.getElementById('new_model_modal').showModal(); return false;"
|
||||||
New Model
|
>
|
||||||
</button>
|
New Model
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock action_button %}
|
{% endblock action_button %}
|
||||||
|
|
||||||
|
|||||||
@ -3,75 +3,73 @@
|
|||||||
{% block page_title %}Packages{% endblock %}
|
{% block page_title %}Packages{% endblock %}
|
||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
{% if meta.can_edit %}
|
<div class="flex items-center gap-2">
|
||||||
<div class="flex items-center gap-2">
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
class="btn btn-primary btn-sm"
|
||||||
class="btn btn-primary btn-sm"
|
id="new-package-button"
|
||||||
id="new-package-button"
|
onclick="document.getElementById('new_package_modal').showModal(); return false;"
|
||||||
onclick="document.getElementById('new_package_modal').showModal(); return false;"
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="lucide lucide-folder-plus-icon lucide-folder-plus"
|
||||||
>
|
>
|
||||||
|
<path d="M12 10v6" />
|
||||||
|
<path d="M9 13h6" />
|
||||||
|
<path
|
||||||
|
d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown dropdown-end">
|
||||||
|
<div tabindex="0" role="button" class="btn btn-sm">
|
||||||
|
Import
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="24"
|
width="16"
|
||||||
height="24"
|
height="16"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
class="lucide lucide-folder-plus-icon lucide-folder-plus"
|
class="lucide lucide-chevron-down ml-1"
|
||||||
>
|
>
|
||||||
<path d="M12 10v6" />
|
<path d="m6 9 6 6 6-6" />
|
||||||
<path d="M9 13h6" />
|
|
||||||
<path
|
|
||||||
d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
|
||||||
<div class="dropdown dropdown-end">
|
|
||||||
<div tabindex="0" role="button" class="btn btn-sm">
|
|
||||||
Import
|
|
||||||
<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-chevron-down ml-1"
|
|
||||||
>
|
|
||||||
<path d="m6 9 6 6 6-6" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<ul
|
|
||||||
tabindex="-1"
|
|
||||||
class="dropdown-content menu bg-base-100 rounded-box z-50 w-56 p-2"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
role="button"
|
|
||||||
onclick="document.getElementById('import_package_modal').showModal(); return false;"
|
|
||||||
>
|
|
||||||
Import Package from JSON
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
role="button"
|
|
||||||
onclick="document.getElementById('import_legacy_package_modal').showModal(); return false;"
|
|
||||||
>
|
|
||||||
Import Package from legacy JSON
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ul
|
||||||
|
tabindex="-1"
|
||||||
|
class="dropdown-content menu bg-base-100 rounded-box z-50 w-56 p-2"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('import_package_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
Import Package from JSON
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('import_legacy_package_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
Import Package from legacy JSON
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
{% endblock action_button %}
|
{% endblock action_button %}
|
||||||
|
|
||||||
{% block action_modals %}
|
{% block action_modals %}
|
||||||
|
|||||||
@ -37,7 +37,11 @@
|
|||||||
perPage: {{ per_page|default:50 }}
|
perPage: {{ per_page|default:50 }}
|
||||||
})"
|
})"
|
||||||
>
|
>
|
||||||
{% include "collections/_paginated_list_partial.html" with empty_text=list_title|default:"items" show_review_badge=True %}
|
{% if entity_type == 'joblog' %}
|
||||||
|
{% include "collections/_joblog_paginated_list_partial.html" with empty_text=list_title|default:"items" show_review_badge=True %}
|
||||||
|
{% else %}
|
||||||
|
{% include "collections/_paginated_list_partial.html" with empty_text=list_title|default:"items" show_review_badge=True %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{# ===== TABBED MODE: Reviewed/Unreviewed tabs (default) ===== #}
|
{# ===== TABBED MODE: Reviewed/Unreviewed tabs (default) ===== #}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
{% block page_title %}Pathways{% endblock %}
|
{% block page_title %}Pathways{% endblock %}
|
||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
{% if meta.can_edit %}
|
{% if meta.can_edit or not meta.url_contains_package %}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<a
|
<a
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
{% block page_title %}Reactions{% endblock %}
|
{% block page_title %}Reactions{% endblock %}
|
||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
{% if meta.can_edit %}
|
{% if meta.can_edit or not meta.url_contains_package %}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
{% block page_title %}Rules{% endblock %}
|
{% block page_title %}Rules{% endblock %}
|
||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
{% if meta.can_edit %}
|
{% if meta.can_edit or not meta.url_contains_package %}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
{% endblock action_modals %}
|
{% endblock action_modals %}
|
||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
{% if meta.can_edit %}
|
{% if meta.can_edit or not meta.url_contains_package %}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
{% block page_title %}{{ page_title|default:"Structures" }}{% endblock %}
|
{% block page_title %}{{ page_title|default:"Structures" }}{% endblock %}
|
||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
{% if meta.can_edit %}
|
{% if meta.can_edit or not meta.url_contains_package %}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
|
|||||||
@ -35,6 +35,7 @@
|
|||||||
<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 %}
|
||||||
@ -88,12 +89,23 @@
|
|||||||
>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
|
||||||
|
|||||||
@ -123,6 +123,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- DOI link widget -->
|
||||||
|
<template
|
||||||
|
x-if="getWidget(fieldName, schema.properties[fieldName]) === 'doi-link'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
x-data="doiWidget(fieldName, data, schema, uiSchema, mode, debugErrors, context)"
|
||||||
|
>
|
||||||
|
{% include "components/widgets/doi_link_widget.html" %}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- Compound link widget -->
|
<!-- Compound link widget -->
|
||||||
<template
|
<template
|
||||||
x-if="getWidget(fieldName, schema.properties[fieldName]) === 'compound-link'"
|
x-if="getWidget(fieldName, schema.properties[fieldName]) === 'compound-link'"
|
||||||
|
|||||||
69
templates/components/widgets/doi_link_widget.html
Normal file
69
templates/components/widgets/doi_link_widget.html
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{# DOI link widget - pure HTML template #}
|
||||||
|
<div class="form-control">
|
||||||
|
<div class="flex flex-col gap-2 sm:flex-row sm:items-baseline">
|
||||||
|
<!-- Label -->
|
||||||
|
<label class="label sm:w-48 sm:shrink-0">
|
||||||
|
<span
|
||||||
|
class="label-text"
|
||||||
|
:class="{
|
||||||
|
'text-error': $store.validationErrors.hasError(fieldName, context),
|
||||||
|
'text-sm text-base-content/60': isViewMode
|
||||||
|
}"
|
||||||
|
x-text="label"
|
||||||
|
></span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<!-- Input column -->
|
||||||
|
<div class="flex-1">
|
||||||
|
<!-- Help text -->
|
||||||
|
<template x-if="helpText">
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class="label-text-alt text-base-content/60"
|
||||||
|
x-text="helpText"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- View mode: display as link -->
|
||||||
|
<template x-if="isViewMode">
|
||||||
|
<div class="mt-1">
|
||||||
|
<template x-if="value && doiUrl">
|
||||||
|
<a
|
||||||
|
:href="doiUrl"
|
||||||
|
class="link link-primary"
|
||||||
|
target="_blank"
|
||||||
|
x-text="value"
|
||||||
|
></a>
|
||||||
|
</template>
|
||||||
|
<template x-if="!value">
|
||||||
|
<span class="text-base-content/50">—</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Edit mode -->
|
||||||
|
<template x-if="isEditMode">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
:class="{ 'input-error': $store.validationErrors.hasError(fieldName, context) }"
|
||||||
|
placeholder="DOI e.g. 10.1016/j.jhazmat.2016.08.036"
|
||||||
|
x-model="value"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Errors -->
|
||||||
|
<template x-if="$store.validationErrors.hasError(fieldName, context)">
|
||||||
|
<div class="label">
|
||||||
|
<template
|
||||||
|
x-for="errMsg in $store.validationErrors.getErrors(fieldName, context)"
|
||||||
|
:key="errMsg"
|
||||||
|
>
|
||||||
|
<span class="label-text-alt text-error" x-text="errMsg"></span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -44,6 +44,7 @@
|
|||||||
:class="{ 'select-error': $store.validationErrors.hasError(fieldName, context) }"
|
:class="{ 'select-error': $store.validationErrors.hasError(fieldName, context) }"
|
||||||
x-model="value"
|
x-model="value"
|
||||||
:multiple="multiple"
|
:multiple="multiple"
|
||||||
|
:required="isRequired"
|
||||||
>
|
>
|
||||||
<option value="" :selected="!value">Select...</option>
|
<option value="" :selected="!value">Select...</option>
|
||||||
|
|
||||||
|
|||||||
@ -65,11 +65,11 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
get showMlrr() {
|
get showMlrr() {
|
||||||
return this.selectedType === 'mlrr';
|
return this.selectedType === 'ml-relative-reasoning';
|
||||||
},
|
},
|
||||||
|
|
||||||
get showRbrr() {
|
get showRbrr() {
|
||||||
return this.selectedType === 'rbrr';
|
return this.selectedType === 'rule-based-relative-reasoning';
|
||||||
},
|
},
|
||||||
|
|
||||||
get showEnviformer() {
|
get showEnviformer() {
|
||||||
|
|||||||
@ -203,11 +203,11 @@
|
|||||||
id="model-based-prediction-setting-threshold"
|
id="model-based-prediction-setting-threshold"
|
||||||
name="model-based-prediction-setting-threshold"
|
name="model-based-prediction-setting-threshold"
|
||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
placeholder="0.25"
|
value="0.25"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
max="1"
|
max="1"
|
||||||
step="0.05"
|
step="any"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user