diff --git a/.gitea/actions/setup-envipy/action.yaml b/.gitea/actions/setup-envipy/action.yaml
index fa475d56..a05f8aa7 100644
--- a/.gitea/actions/setup-envipy/action.yaml
+++ b/.gitea/actions/setup-envipy/action.yaml
@@ -48,11 +48,6 @@ runs:
shell: bash
run: |
uv run python scripts/pnpm_wrapper.py install
- cat << 'EOF' > pnpm-workspace.yaml
- onlyBuiltDependencies:
- - '@parcel/watcher'
- - '@tailwindcss/oxide'
- EOF
uv run python scripts/pnpm_wrapper.py run build
- name: Wait for Postgres
diff --git a/epdb/legacy_api.py b/epdb/legacy_api.py
index d4cc4729..b6aab0bf 100644
--- a/epdb/legacy_api.py
+++ b/epdb/legacy_api.py
@@ -212,7 +212,7 @@ def get_user(request, user_uuid):
class GroupMember(Schema):
- id: str = Field(None, alias="url")
+ id: str
identifier: str
name: str
@@ -228,7 +228,7 @@ class GroupSchema(Schema):
members: List[GroupMember] = Field([], alias="members")
name: str = Field(None, alias="name")
ownerid: str = Field(None, alias="owner.url")
- ownername: str = Field(None, alias="owner.name")
+ ownername: str = Field(None, alias="owner.get_name")
packages: List["SimplePackage"] = Field([], alias="packages")
readers: List[GroupMember] = Field([], alias="readers")
writers: List[GroupMember] = Field([], alias="writers")
@@ -237,10 +237,10 @@ class GroupSchema(Schema):
def resolve_members(obj: Group):
res = []
for member in obj.user_member.all():
- res.append(GroupMember(id=member.url, identifier="usermember", name=member.username))
+ res.append(GroupMember(id=member.url, identifier="usermember", name=member.get_name()))
for member in obj.group_member.all():
- res.append(GroupMember(id=member.url, identifier="groupmember", name=member.name))
+ res.append(GroupMember(id=member.url, identifier="groupmember", name=member.get_name()))
return res
@@ -374,7 +374,7 @@ class PackageSchema(Schema):
).values_list("user", flat=True)
).distinct()
- return [{u.id: u.name} for u in users]
+ return [{u.id: u.get_name()} for u in users]
@staticmethod
def resolve_writers(obj: Package):
@@ -384,7 +384,7 @@ class PackageSchema(Schema):
).values_list("user", flat=True)
).distinct()
- return [{u.id: u.name} for u in users]
+ return [{u.id: u.get_name()} for u in users]
@staticmethod
def resolve_review_comment(obj):
@@ -966,7 +966,12 @@ def create_package_simple_rule(
raise ValueError("Not yet implemented!")
else:
sr = SimpleAmbitRule.create(
- p, r.name, r.description, r.smirks, r.reactantFilterSmarts, r.productFilterSmarts
+ p,
+ r.name,
+ r.description,
+ r.smirks,
+ r.reactantFilterSmarts,
+ r.productFilterSmarts,
)
return redirect(sr.url)
@@ -1119,7 +1124,7 @@ class ReactionSchema(Schema):
name: str = Field(None, alias="name")
pathways: List["SimplePathway"] = Field([], alias="related_pathways")
products: List["ReactionCompoundStructure"] = Field([], alias="products")
- references: List[Dict[str, List[str]]] = Field([], alias="references")
+ references: Dict[str, List[str]] = Field({}, alias="references")
reviewStatus: str = Field(None, alias="review_status")
scenarios: List["SimpleScenario"] = Field([], alias="scenarios")
smirks: str = Field("", alias="smirks")
@@ -1135,8 +1140,12 @@ class ReactionSchema(Schema):
@staticmethod
def resolve_references(obj: Reaction):
- # TODO
- return []
+ rhea_refs = []
+ for rhea in obj.get_rhea_identifiers():
+ rhea_refs.append(f"{rhea.identifier_value}")
+
+ # TODO UniProt
+ return {"rheaReferences": rhea_refs, "uniprotCount": []}
@staticmethod
def resolve_medline_references(obj: Reaction):
@@ -1715,7 +1724,7 @@ class EdgeSchema(Schema):
id: str = Field(None, alias="url")
identifier: str = "edge"
name: str = Field(None, alias="name")
- reactionName: str = Field(None, alias="edge_label.name")
+ reactionName: str = Field(None, alias="edge_label.get_name")
reactionURI: str = Field(None, alias="edge_label.url")
reviewStatus: str = Field(None, alias="review_status")
scenarios: List["SimpleScenario"] = Field([], alias="scenarios")
@@ -1764,7 +1773,7 @@ class CreateEdge(Schema):
@router.post(
- "/package/{uuid:package_uuid}/üathway/{uuid:pathway_uuid}/edge",
+ "/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}/edge",
response={200: str | Any, 403: Error},
)
def add_pathway_edge(request, package_uuid, pathway_uuid, e: Form[CreateEdge]):
@@ -1783,10 +1792,26 @@ def add_pathway_edge(request, package_uuid, pathway_uuid, e: Form[CreateEdge]):
if e.edgeAsSmirks:
for ed in e.edgeAsSmirks.split(">>")[0].split("\\."):
- educts.append(Node.objects.get(pathway=pw, default_node_label__smiles=ed))
+ stand_ed = FormatConverter.standardize(ed, remove_stereo=True)
+ educts.append(
+ Node.objects.get(
+ pathway=pw,
+ default_node_label=CompoundStructure.objects.get(
+ compound__package=p, smiles=stand_ed
+ ).compound.default_structure,
+ )
+ )
for pr in e.edgeAsSmirks.split(">>")[1].split("\\."):
- products.append(Node.objects.get(pathway=pw, default_node_label__smiles=pr))
+ stand_pr = FormatConverter.standardize(pr, remove_stereo=True)
+ products.append(
+ Node.objects.get(
+ pathway=pw,
+ default_node_label=CompoundStructure.objects.get(
+ compound__package=p, smiles=stand_pr
+ ).compound.default_structure,
+ )
+ )
else:
for ed in e.educts.split(","):
educts.append(Node.objects.get(pathway=pw, url=ed.strip()))
@@ -1799,7 +1824,7 @@ def add_pathway_edge(request, package_uuid, pathway_uuid, e: Form[CreateEdge]):
start_nodes=educts,
end_nodes=products,
rule=None,
- name=e.name,
+ name=None,
description=e.edgeReason,
)
@@ -1936,7 +1961,7 @@ def get_model(request, package_uuid, model_uuid, c: Query[Classify]):
if pr.rule:
res["id"] = pr.rule.url
res["identifier"] = pr.rule.get_rule_identifier()
- res["name"] = pr.rule.name
+ res["name"] = pr.rule.get_name()
res["reviewStatus"] = (
"reviewed" if pr.rule.package.reviewed else "unreviewed"
)
diff --git a/epdb/management/commands/bootstrap.py b/epdb/management/commands/bootstrap.py
index eb74385a..ccc02d14 100644
--- a/epdb/management/commands/bootstrap.py
+++ b/epdb/management/commands/bootstrap.py
@@ -8,7 +8,6 @@ from epdb.logic import UserManager, GroupManager, PackageManager, SettingManager
from epdb.models import (
UserSettingPermission,
MLRelativeReasoning,
- EnviFormer,
Permission,
User,
ExternalDatabase,
@@ -231,7 +230,6 @@ class Command(BaseCommand):
package=pack,
rule_packages=[mapping["EAWAG-BBD"]],
data_packages=[mapping["EAWAG-BBD"]],
- eval_packages=[],
threshold=0.5,
name="ECC - BBD - T0.5",
description="ML Relative Reasoning",
@@ -239,7 +237,3 @@ class Command(BaseCommand):
ml_model.build_dataset()
ml_model.build_model()
-
- # If available, create EnviFormerModel
- if s.ENVIFORMER_PRESENT:
- EnviFormer.create(pack, "EnviFormer - T0.5", "EnviFormer Model with Threshold 0.5", 0.5)
diff --git a/epdb/management/commands/dump_enviformer.py b/epdb/management/commands/dump_enviformer.py
index e333248a..8fe2bf33 100644
--- a/epdb/management/commands/dump_enviformer.py
+++ b/epdb/management/commands/dump_enviformer.py
@@ -47,7 +47,7 @@ class Command(BaseCommand):
"description": model.description,
"kv": model.kv,
"data_packages_uuids": [str(p.uuid) for p in model.data_packages.all()],
- "eval_packages_uuids": [str(p.uuid) for p in model.data_packages.all()],
+ "eval_packages_uuids": [str(p.uuid) for p in model.eval_packages.all()],
"threshold": model.threshold,
"eval_results": model.eval_results,
"multigen_eval": model.multigen_eval,
diff --git a/epdb/models.py b/epdb/models.py
index 900e1d4a..920fd911 100644
--- a/epdb/models.py
+++ b/epdb/models.py
@@ -77,6 +77,9 @@ class User(AbstractUser):
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["username"]
+ def get_name(self):
+ return self.username
+
def save(self, *args, **kwargs):
if not self.url:
self.url = self._url()
@@ -208,7 +211,10 @@ class Group(TimeStampedModel):
)
def __str__(self):
- return f"{self.name} (pk={self.pk})"
+ return f"{self.get_name()} (pk={self.pk})"
+
+ def get_name(self):
+ return self.name
def save(self, *args, **kwargs):
if not self.url:
@@ -596,7 +602,7 @@ class EnviPathModel(TimeStampedModel):
res = {
"url": self.url,
"uuid": str(self.uuid),
- "name": self.name,
+ "name": self.get_name(),
}
if include_description:
@@ -609,11 +615,14 @@ class EnviPathModel(TimeStampedModel):
return self.kv.get(k, default)
return default
+ def get_name(self):
+ return self.name
+
class Meta:
abstract = True
def __str__(self):
- return f"{self.name} (pk={self.pk})"
+ return f"{self.get_name()} (pk={self.pk})"
class AliasMixin(models.Model):
@@ -624,7 +633,7 @@ class AliasMixin(models.Model):
@transaction.atomic
def add_alias(self, new_alias, set_as_default=False):
if set_as_default:
- self.aliases.append(self.name)
+ self.aliases.append(self.get_name())
self.name = new_alias
if new_alias in self.aliases:
@@ -765,15 +774,17 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin
logger.debug(
f"#Structures: {num_structs} - #Standardized SMILES: {len(stand_smiles)}"
)
- logger.debug(f"Couldn't infer normalized structure for {self.name} - {self.url}")
+ logger.debug(
+ f"Couldn't infer normalized structure for {self.get_name()} - {self.url}"
+ )
raise ValueError(
- f"Couldn't find nor infer normalized structure for {self.name} ({self.url})"
+ f"Couldn't find nor infer normalized structure for {self.get_name()} ({self.url})"
)
else:
cs = CompoundStructure.create(
self,
stand_smiles.pop(),
- name="Normalized structure of {}".format(self.name),
+ name="Normalized structure of {}".format(self.get_name()),
description="{} (in its normalized form)".format(self.description),
normalized_structure=True,
)
@@ -848,8 +859,10 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin
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
@@ -978,7 +991,7 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin
cs = CompoundStructure.create(
existing_normalized_compound,
structure.smiles,
- name=structure.name,
+ name=structure.get_name(),
description=structure.description,
normalized_structure=structure.normalized_structure,
)
@@ -989,13 +1002,13 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin
else:
raise ValueError(
- f"Found a CompoundStructure for {default_structure_smiles} but not for {normalized_structure_smiles} in target package {target.name}"
+ f"Found a CompoundStructure for {default_structure_smiles} but not for {normalized_structure_smiles} in target package {target.get_name()}"
)
else:
# Here we can safely use Compound.objects.create as we won't end up in a duplicate
new_compound = Compound.objects.create(
package=target,
- name=self.name,
+ name=self.get_name(),
description=self.description,
kv=self.kv.copy() if self.kv else {},
)
@@ -1011,7 +1024,7 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin
canonical_smiles=structure.canonical_smiles,
inchikey=structure.inchikey,
normalized_structure=structure.normalized_structure,
- name=structure.name,
+ name=structure.get_name(),
description=structure.description,
kv=structure.kv.copy() if structure.kv else {},
)
@@ -1050,11 +1063,8 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin
def half_lifes(self):
hls: Dict[Scenario, List[HalfLife]] = defaultdict(list)
- for n in self.related_nodes:
- for scen in n.scenarios.all().order_by("name"):
- for ai in scen.get_additional_information():
- if isinstance(ai, HalfLife):
- hls[scen].append(ai)
+ for cs in self.structures:
+ hls.update(cs.half_lifes())
return dict(hls)
@@ -1104,6 +1114,7 @@ class CompoundStructure(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdenti
# 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()
@@ -1147,6 +1158,21 @@ class CompoundStructure(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdenti
def is_default_structure(self):
return self.compound.default_structure == self
+ @property
+ def related_nodes(self):
+ return Node.objects.filter(node_labels__in=[self], pathway__package=self.compound.package)
+
+ def half_lifes(self):
+ hls: Dict[Scenario, List[HalfLife]] = defaultdict(list)
+
+ for n in self.related_nodes:
+ for scen in n.scenarios.all().order_by("name"):
+ for ai in scen.get_additional_information():
+ if isinstance(ai, HalfLife):
+ hls[scen].append(ai)
+
+ return dict(hls)
+
class EnzymeLink(EnviPathModel, KEGGIdentifierMixin):
rule = models.ForeignKey("Rule", on_delete=models.CASCADE, db_index=True)
@@ -1218,7 +1244,7 @@ class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
if rule_type == SimpleAmbitRule:
new_rule = SimpleAmbitRule.create(
package=target,
- name=self.name,
+ name=self.get_name(),
description=self.description,
smirks=self.smirks,
reactant_filter_smarts=self.reactant_filter_smarts,
@@ -1232,7 +1258,7 @@ class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
elif rule_type == SimpleRDKitRule:
new_rule = SimpleRDKitRule.create(
package=target,
- name=self.name,
+ name=self.get_name(),
description=self.description,
reaction_smarts=self.reaction_smarts,
)
@@ -1250,7 +1276,7 @@ class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
new_rule = ParallelRule.create(
package=target,
simple_rules=new_srs,
- name=self.name,
+ name=self.get_name(),
description=self.description,
)
@@ -1624,9 +1650,11 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
r = Reaction()
r.package = package
+
# Clean for potential XSS
if name is not None and name.strip() != "":
r.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
+
if description is not None and name.strip() != "":
r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
@@ -1674,7 +1702,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
new_reaction = Reaction.create(
package=target,
- name=self.name,
+ name=self.get_name(),
description=self.description,
educts=copied_reaction_educts,
products=copied_reaction_products,
@@ -1830,7 +1858,9 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
# We shouldn't lose or make up nodes...
if len(nodes) != len(self.nodes):
- logger.debug(f"{self.name}: Num Nodes {len(nodes)} vs. DB Nodes {len(self.nodes)}")
+ logger.debug(
+ f"{self.get_name()}: Num Nodes {len(nodes)} vs. DB Nodes {len(self.nodes)}"
+ )
links = [e.d3_json() for e in self.edges]
@@ -1898,7 +1928,7 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
"isIncremental": self.kv.get("mode") == "incremental",
"isPredicted": self.kv.get("mode") == "predicted",
"lastModified": self.modified.strftime("%Y-%m-%d %H:%M:%S"),
- "pathwayName": self.name,
+ "pathwayName": self.get_name(),
"reviewStatus": "reviewed" if self.package.reviewed else "unreviewed",
"scenarios": [],
"upToDate": True,
@@ -1941,14 +1971,14 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
if include_pathway_url:
row.append(n.pathway.url)
- row += [cs.smiles, cs.name, n.depth]
+ row += [cs.smiles, cs.get_name(), n.depth]
edges = self.edges.filter(end_nodes__in=[n])
if len(edges):
for e in edges:
_row = row.copy()
_row.append(e.kv.get("probability"))
- _row.append(",".join([r.name for r in e.edge_label.rules.all()]))
+ _row.append(",".join([r.get_name() for r in e.edge_label.rules.all()]))
_row.append(",".join([r.url for r in e.edge_label.rules.all()]))
_row.append(e.start_nodes.all()[0].default_node_label.smiles)
rows.append(_row)
@@ -1979,12 +2009,14 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
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"Pathway {Pathway.objects.filter(package=package).count() + 1}"
pw.name = name
if description is not None and description.strip() != "":
pw.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
+
pw.predicted = predicted
pw.save()
@@ -2009,7 +2041,7 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
# deduplicated
new_pathway = Pathway.objects.create(
package=target,
- name=self.name,
+ name=self.get_name(),
description=self.description,
setting=self.setting, # TODO copy settings?
kv=self.kv.copy() if self.kv else {},
@@ -2039,7 +2071,7 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
pathway=new_pathway,
default_node_label=copied_structure,
depth=node.depth,
- name=node.name,
+ name=node.get_name(),
description=node.description,
kv=node.kv.copy() if node.kv else {},
)
@@ -2063,7 +2095,7 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
new_edge = Edge.objects.create(
pathway=new_pathway,
edge_label=copied_reaction,
- name=edge.name,
+ name=edge.get_name(),
description=edge.description,
kv=edge.kv.copy() if edge.kv else {},
)
@@ -2123,6 +2155,18 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin):
def _url(self):
return "{}/node/{}".format(self.pathway.url, self.uuid)
+ def get_name(self):
+ non_generic_name = True
+
+ if self.name == "no name":
+ non_generic_name = False
+
+ return (
+ self.name
+ if non_generic_name
+ else f"{self.default_node_label.name} (taken from underlying structure)"
+ )
+
def d3_json(self):
app_domain_data = self.get_app_domain_assessment_data()
@@ -2135,9 +2179,9 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin):
"image_svg": IndigoUtils.mol_to_svg(
self.default_node_label.smiles, width=40, height=40
),
- "name": self.default_node_label.name,
+ "name": self.get_name(),
"smiles": self.default_node_label.smiles,
- "scenarios": [{"name": s.name, "url": s.url} for s in self.scenarios.all()],
+ "scenarios": [{"name": s.get_name(), "url": s.url} for s in self.scenarios.all()],
"app_domain": {
"inside_app_domain": app_domain_data["assessment"]["inside_app_domain"]
if app_domain_data
@@ -2205,7 +2249,7 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin):
res = super().simple_json()
name = res.get("name", None)
if name == "no name":
- res["name"] = self.default_node_label.name
+ res["name"] = self.default_node_label.get_name()
return res
@@ -2229,18 +2273,24 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin):
def d3_json(self):
edge_json = {
- "name": self.name,
+ "name": self.get_name(),
"id": self.url,
"url": self.url,
"image": self.url + "?image=svg",
- "reaction": {"name": self.edge_label.name, "url": self.edge_label.url}
+ "reaction": {
+ "name": self.edge_label.get_name(),
+ "url": self.edge_label.url,
+ "rules": [
+ {"name": r.get_name(), "url": r.url} for r in self.edge_label.rules.all()
+ ],
+ }
if self.edge_label
else None,
"multi_step": self.edge_label.multi_step if self.edge_label else False,
"reaction_probability": self.kv.get("probability"),
"start_node_urls": [x.url for x in self.start_nodes.all()],
"end_node_urls": [x.url for x in self.end_nodes.all()],
- "scenarios": [{"name": s.name, "url": s.url} for s in self.scenarios.all()],
+ "scenarios": [{"name": s.get_name(), "url": s.url} for s in self.scenarios.all()],
}
for n in self.start_nodes.all():
@@ -2329,10 +2379,22 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin):
res = super().simple_json()
name = res.get("name", None)
if name == "no name":
- res["name"] = self.edge_label.name
+ res["name"] = self.edge_label.get_name()
return res
+ def get_name(self):
+ non_generic_name = True
+
+ if self.name == "no name":
+ non_generic_name = False
+
+ return (
+ self.name
+ if non_generic_name
+ else f"{self.edge_label.name} (taken from underlying reaction)"
+ )
+
class EPModel(PolymorphicModel, EnviPathModel):
package = models.ForeignKey(
@@ -2613,7 +2675,7 @@ class PackageBasedModel(EPModel):
root_compounds.append(pw.root_nodes[0].default_node_label)
else:
logger.info(
- f"Skipping MG Eval of Pathway {pw.name} ({pw.uuid}) as it has no root compounds!"
+ f"Skipping MG Eval of Pathway {pw.get_name()} ({pw.uuid}) as it has no root compounds!"
)
# As we need a Model Instance in our setting, get a fresh copy from db, overwrite the serialized mode and
@@ -2739,7 +2801,7 @@ class PackageBasedModel(EPModel):
pathways.append(pathway)
else:
logging.warning(
- f"No root compound in pathway {pathway.name}, excluding from multigen evaluation"
+ f"No root compound in pathway {pathway.get_name()}, excluding from multigen evaluation"
)
# build lookup reaction -> {uuid1, uuid2} for overlap check
@@ -3071,7 +3133,7 @@ class ApplicabilityDomain(EnviPathModel):
ad = ApplicabilityDomain()
ad.model = mlrr
# ad.uuid = mlrr.uuid
- ad.name = f"AD for {mlrr.name}"
+ ad.name = f"AD for {mlrr.get_name()}"
ad.num_neighbours = num_neighbours
ad.reliability_threshold = reliability_threshold
ad.local_compatibilty_threshold = local_compatibility_threshold
@@ -3355,7 +3417,7 @@ class EnviFormer(PackageBasedModel):
)
for smiles in smiles_list
]
- logger.info(f"Submitting {canon_smiles} to {self.name}")
+ logger.info(f"Submitting {canon_smiles} to {self.get_name()}")
start = datetime.now()
products_list = self.model.predict_batch(canon_smiles)
end = datetime.now()
@@ -3512,7 +3574,7 @@ class EnviFormer(PackageBasedModel):
root_node = p.root_nodes
if len(root_node) > 1:
logging.warning(
- f"Pathway {p.name} has more than one root compound, only {root_node[0]} will be used"
+ f"Pathway {p.get_name()} has more than one root compound, only {root_node[0]} will be used"
)
root_node = ".".join(
[
@@ -3632,7 +3694,7 @@ class EnviFormer(PackageBasedModel):
pathways.append(pathway)
else:
logging.warning(
- f"No root compound in pathway {pathway.name}, excluding from multigen evaluation"
+ f"No root compound in pathway {pathway.get_name()}, excluding from multigen evaluation"
)
# build lookup reaction -> {uuid1, uuid2} for overlap check
@@ -4038,6 +4100,6 @@ class JobLog(TimeStampedModel):
return self.task_result
def is_result_downloadable(self):
- downloadable = ["batch_predict"]
+ downloadable = ["batch_predict", "identify_missing_rules"]
return self.job_name in downloadable
diff --git a/epdb/tasks.py b/epdb/tasks.py
index ebffb5b7..b3aaa5e1 100644
--- a/epdb/tasks.py
+++ b/epdb/tasks.py
@@ -7,6 +7,7 @@ from uuid import uuid4
from celery import shared_task
from celery.utils.functional import LRUCache
from django.conf import settings as s
+from django.core.mail import EmailMultiAlternatives
from django.utils import timezone
from epdb.logic import SPathway
@@ -73,7 +74,31 @@ def predict_simple(model_pk: int, smiles: str):
@shared_task(queue="background")
def send_registration_mail(user_pk: int):
- pass
+ u = User.objects.get(id=user_pk)
+
+ tpl = """Welcome {username}!,
+
+Thank you for your interest in enviPath.
+
+The public system is intended for non-commercial use only.
+We will review your account details and usually activate your account within 24 hours.
+Once activated, you will be notified by email.
+
+If we have any questions, we will contact you at this email address.
+
+Best regards,
+
+enviPath team"""
+
+ msg = EmailMultiAlternatives(
+ "Your enviPath account",
+ tpl.format(username=u.username),
+ "admin@envipath.org",
+ [u.email],
+ bcc=["admin@envipath.org"],
+ )
+
+ msg.send(fail_silently=False)
@shared_task(bind=True, queue="model")
diff --git a/epdb/views.py b/epdb/views.py
index 2d1595f2..e657ff3f 100644
--- a/epdb/views.py
+++ b/epdb/views.py
@@ -1,7 +1,7 @@
import json
import logging
-from typing import Any, Dict, List
from datetime import datetime
+from typing import Any, Dict, List
import nh3
from django.conf import settings as s
@@ -13,6 +13,7 @@ from django.urls import reverse
from django.views.decorators.csrf import csrf_exempt
from envipy_additional_information import NAME_MAPPING
from oauth2_provider.decorators import protected_resource
+from sentry_sdk import capture_exception
from utilities.chem import FormatConverter, IndigoUtils
from utilities.decorators import package_permission_required
@@ -34,6 +35,7 @@ from .models import (
EnviFormer,
EnzymeLink,
EPModel,
+ ExpansionSchemeChoice,
ExternalDatabase,
ExternalIdentifier,
Group,
@@ -51,7 +53,6 @@ from .models import (
SimpleAmbitRule,
User,
UserPackagePermission,
- ExpansionSchemeChoice,
)
logger = logging.getLogger(__name__)
@@ -238,6 +239,15 @@ def register(request):
try:
u = UserManager.create_user(username, email, password)
logger.info(f"Created user {u.username} ({u.pk})")
+
+ try:
+ from .tasks import send_registration_mail
+
+ send_registration_mail.delay(u.pk)
+ except Exception as e:
+ logger.error(f"Failed to send registration mail to {u.email}: {e}")
+ capture_exception(e)
+
except Exception:
context["message"] = "Registration failed! Couldn't create User Account."
return render(request, "static/login.html", context)
@@ -339,7 +349,7 @@ def breadcrumbs(
{"Package": s.SERVER_URL + "/package"},
]
if first_level_object is not None:
- bread.append({first_level_object.name: first_level_object.url})
+ bread.append({first_level_object.get_name(): first_level_object.url})
if second_level_namespace is not None:
bread.append(
@@ -350,7 +360,7 @@ def breadcrumbs(
)
if second_level_object is not None:
- bread.append({second_level_object.name: second_level_object.url})
+ bread.append({second_level_object.get_name(): second_level_object.url})
if third_level_namespace is not None:
bread.append(
@@ -361,7 +371,7 @@ def breadcrumbs(
)
if third_level_object is not None:
- bread.append({third_level_object.name: third_level_object.url})
+ bread.append({third_level_object.get_name(): third_level_object.url})
return bread
@@ -462,7 +472,7 @@ def package_predict_pathway(request, package_uuid):
current_package = PackageManager.get_package_by_id(current_user, package_uuid)
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - Predict Pathway"
+ context["title"] = f"enviPath - {current_package.get_name()} - Predict Pathway"
context["meta"]["current_package"] = current_package
return render(request, "predict_pathway.html", context)
@@ -475,6 +485,10 @@ def packages(request):
context = get_base_context(request)
context["title"] = "enviPath - Packages"
context["meta"]["current_package"] = context["meta"]["user"].default_package
+ context["breadcrumbs"] = [
+ {"Home": s.SERVER_URL},
+ {"Package": s.SERVER_URL + "/package"},
+ ]
# Context for paginated template
context["entity_type"] = "package"
@@ -529,6 +543,10 @@ def compounds(request):
context = get_base_context(request)
context["title"] = "enviPath - Compounds"
context["meta"]["current_package"] = context["meta"]["user"].default_package
+ context["breadcrumbs"] = [
+ {"Home": s.SERVER_URL},
+ {"Compound": s.SERVER_URL + "/compound"},
+ ]
# Context for paginated template
context["entity_type"] = "compound"
@@ -759,7 +777,7 @@ def package_models(request, package_uuid):
if request.method == "GET":
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - Models"
+ context["title"] = f"enviPath - {current_package.get_name()} - Models"
context["meta"]["current_package"] = current_package
context["object_type"] = "model"
@@ -781,7 +799,7 @@ def package_models(request, package_uuid):
return JsonResponse(
{
"objects": [
- {"name": pw.name, "url": pw.url, "reviewed": current_package.reviewed}
+ {"name": pw.get_name(), "url": pw.url, "reviewed": current_package.reviewed}
for pw in (
reviewed_model_qs if current_package.reviewed else unreviewed_model_qs
)
@@ -931,7 +949,7 @@ def package_model(request, package_uuid, model_uuid):
return JsonResponse(app_domain_assessment, safe=False)
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - {current_model.name}"
+ context["title"] = f"enviPath - {current_package.get_name()} - {current_model.get_name()}"
context["meta"]["current_package"] = current_package
context["object_type"] = "model"
@@ -1009,7 +1027,7 @@ def package(request, package_uuid):
if request.method == "GET":
if request.GET.get("export", False) == "true":
- filename = f"{current_package.name.replace(' ', '_')}_{current_package.uuid}.json"
+ filename = f"{current_package.get_name().replace(' ', '_')}_{current_package.uuid}.json"
pack_json = PackageManager.export_package(
current_package, include_models=False, include_external_identifiers=False
)
@@ -1019,7 +1037,7 @@ def package(request, package_uuid):
return response
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name}"
+ context["title"] = f"enviPath - {current_package.get_name()}"
context["meta"]["current_package"] = current_package
context["object_type"] = "package"
@@ -1056,7 +1074,7 @@ def package(request, package_uuid):
if current_user.default_package == current_package:
return error(
request,
- f'Package "{current_package.name}" is the default and cannot be deleted!',
+ f'Package "{current_package.get_name()}" is the default and cannot be deleted!',
"You cannot delete the default package. If you want to delete this package you have to set another default package first.",
)
@@ -1154,7 +1172,7 @@ def package_compounds(request, package_uuid):
if request.method == "GET":
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - Compounds"
+ context["title"] = f"enviPath - {current_package.get_name()} - Compounds"
context["meta"]["current_package"] = current_package
context["object_type"] = "compound"
@@ -1179,7 +1197,7 @@ def package_compounds(request, package_uuid):
return JsonResponse(
{
"objects": [
- {"name": pw.name, "url": pw.url, "reviewed": current_package.reviewed}
+ {"name": pw.get_name(), "url": pw.url, "reviewed": current_package.reviewed}
for pw in (
reviewed_compound_qs
if current_package.reviewed
@@ -1216,7 +1234,9 @@ def package_compound(request, package_uuid, compound_uuid):
if request.method == "GET":
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - {current_compound.name}"
+ context["title"] = (
+ f"enviPath - {current_package.get_name()} - {current_compound.get_name()}"
+ )
context["meta"]["current_package"] = current_package
context["object_type"] = "compound"
@@ -1300,7 +1320,7 @@ def package_compound_structures(request, package_uuid, compound_uuid):
if request.method == "GET":
context = get_base_context(request)
context["title"] = (
- f"enviPath - {current_package.name} - {current_compound.name} - Structures"
+ f"enviPath - {current_package.get_name()} - {current_compound.get_name()} - Structures"
)
context["meta"]["current_package"] = current_package
@@ -1309,7 +1329,7 @@ def package_compound_structures(request, package_uuid, compound_uuid):
current_package, "compound", current_compound, "structure"
)
context["entity_type"] = "structure"
- context["page_title"] = f"{current_compound.name} - Structures"
+ context["page_title"] = f"{current_compound.get_name()} - Structures"
context["api_endpoint"] = (
f"/api/v1/package/{current_package.uuid}/compound/{current_compound.uuid}/structure/"
)
@@ -1362,7 +1382,7 @@ def package_compound_structure(request, package_uuid, compound_uuid, structure_u
context = get_base_context(request)
context["title"] = (
- f"enviPath - {current_package.name} - {current_compound.name} - {current_structure.name}"
+ f"enviPath - {current_package.get_name()} - {current_compound.get_name()} - {current_structure.get_name()}"
)
context["meta"]["current_package"] = current_package
@@ -1468,7 +1488,7 @@ def package_rules(request, package_uuid):
if request.method == "GET":
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - Rules"
+ context["title"] = f"enviPath - {current_package.get_name()} - Rules"
context["meta"]["current_package"] = current_package
context["object_type"] = "rule"
@@ -1490,7 +1510,7 @@ def package_rules(request, package_uuid):
return JsonResponse(
{
"objects": [
- {"name": pw.name, "url": pw.url, "reviewed": current_package.reviewed}
+ {"name": pw.get_name(), "url": pw.url, "reviewed": current_package.reviewed}
for pw in (
reviewed_rule_qs if current_package.reviewed else unreviewed_rule_qs
)
@@ -1580,7 +1600,7 @@ def package_rule(request, package_uuid, rule_uuid):
content_type="image/svg+xml",
)
- context["title"] = f"enviPath - {current_package.name} - {current_rule.name}"
+ context["title"] = f"enviPath - {current_package.get_name()} - {current_rule.get_name()}"
context["meta"]["current_package"] = current_package
context["object_type"] = "rule"
@@ -1653,7 +1673,7 @@ def package_rule_enzymelink(request, package_uuid, rule_uuid, enzymelink_uuid):
if request.method == "GET":
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - {current_rule.name}"
+ context["title"] = f"enviPath - {current_package.get_name()} - {current_rule.get_name()}"
context["meta"]["current_package"] = current_package
context["object_type"] = "enzyme"
@@ -1676,7 +1696,7 @@ def package_reactions(request, package_uuid):
if request.method == "GET":
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - Reactions"
+ context["title"] = f"enviPath - {current_package.get_name()} - Reactions"
context["meta"]["current_package"] = current_package
context["object_type"] = "reaction"
@@ -1700,7 +1720,7 @@ def package_reactions(request, package_uuid):
return JsonResponse(
{
"objects": [
- {"name": pw.name, "url": pw.url, "reviewed": current_package.reviewed}
+ {"name": pw.get_name(), "url": pw.url, "reviewed": current_package.reviewed}
for pw in (
reviewed_reaction_qs
if current_package.reviewed
@@ -1741,7 +1761,9 @@ def package_reaction(request, package_uuid, reaction_uuid):
if request.method == "GET":
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - {current_reaction.name}"
+ context["title"] = (
+ f"enviPath - {current_package.get_name()} - {current_reaction.get_name()}"
+ )
context["meta"]["current_package"] = current_package
context["object_type"] = "reaction"
@@ -1824,7 +1846,7 @@ def package_pathways(request, package_uuid):
if request.method == "GET":
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - Pathways"
+ context["title"] = f"enviPath - {current_package.get_name()} - Pathways"
context["meta"]["current_package"] = current_package
context["object_type"] = "pathway"
@@ -1846,7 +1868,7 @@ def package_pathways(request, package_uuid):
return JsonResponse(
{
"objects": [
- {"name": pw.name, "url": pw.url, "reviewed": current_package.reviewed}
+ {"name": pw.get_name(), "url": pw.url, "reviewed": current_package.reviewed}
for pw in (
reviewed_pathway_qs
if current_package.reviewed
@@ -1953,7 +1975,7 @@ def package_pathway(request, package_uuid, pathway_uuid):
)
if request.GET.get("download", False) == "true":
- filename = f"{current_pathway.name.replace(' ', '_')}_{current_pathway.uuid}.csv"
+ filename = f"{current_pathway.get_name().replace(' ', '_')}_{current_pathway.uuid}.csv"
csv_pw = current_pathway.to_csv()
response = HttpResponse(csv_pw, content_type="text/csv")
response["Content-Disposition"] = f'attachment; filename="{filename}"'
@@ -1973,7 +1995,7 @@ def package_pathway(request, package_uuid, pathway_uuid):
current_user, identify_missing_rules, [current_pathway.pk], rule_package.pk
)
- filename = f"{current_pathway.name.replace(' ', '_')}_{current_pathway.uuid}.csv"
+ filename = f"{current_pathway.get_name().replace(' ', '_')}_{current_pathway.uuid}.csv"
response = HttpResponse(res, content_type="text/csv")
response["Content-Disposition"] = f'attachment; filename="{filename}"'
@@ -1996,7 +2018,7 @@ def package_pathway(request, package_uuid, pathway_uuid):
).get(uuid=pathway_uuid)
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - {current_pathway.name}"
+ context["title"] = f"enviPath - {current_package.get_name()} - {current_pathway.get_name()}"
context["meta"]["current_package"] = current_package
context["object_type"] = "pathway"
@@ -2008,9 +2030,9 @@ def package_pathway(request, package_uuid, pathway_uuid):
context["breadcrumbs"] = [
{"Home": s.SERVER_URL},
{"Package": s.SERVER_URL + "/package"},
- {current_package.name: current_package.url},
+ {current_package.get_name(): current_package.url},
{"Pathway": current_package.url + "/pathway"},
- {current_pathway.name: current_pathway.url},
+ {current_pathway.get_name(): current_pathway.url},
]
return render(request, "objects/pathway.html", context)
@@ -2097,16 +2119,18 @@ def package_pathway_nodes(request, package_uuid, pathway_uuid):
if request.method == "GET":
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - {current_pathway.name} - Nodes"
+ context["title"] = (
+ f"enviPath - {current_package.get_name()} - {current_pathway.get_name()} - Nodes"
+ )
context["meta"]["current_package"] = current_package
context["object_type"] = "node"
context["breadcrumbs"] = [
{"Home": s.SERVER_URL},
{"Package": s.SERVER_URL + "/package"},
- {current_package.name: current_package.url},
+ {current_package.get_name(): current_package.url},
{"Pathway": current_package.url + "/pathway"},
- {current_pathway.name: current_pathway.url},
+ {current_pathway.get_name(): current_pathway.url},
{"Node": current_pathway.url + "/node"},
]
@@ -2122,7 +2146,7 @@ def package_pathway_nodes(request, package_uuid, pathway_uuid):
return JsonResponse(
{
"objects": [
- {"name": pw.name, "url": pw.url, "reviewed": current_package.reviewed}
+ {"name": pw.get_name(), "url": pw.url, "reviewed": current_package.reviewed}
for pw in (
reviewed_node_qs if current_package.reviewed else unreviewed_node_qs
)
@@ -2196,7 +2220,7 @@ def package_pathway_node(request, package_uuid, pathway_uuid, node_uuid):
return HttpResponse(svg_data, content_type="image/svg+xml")
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - {current_pathway.name}"
+ context["title"] = f"enviPath - {current_package.get_name()} - {current_pathway.get_name()}"
context["meta"]["current_package"] = current_package
context["object_type"] = "pathway"
@@ -2204,11 +2228,11 @@ def package_pathway_node(request, package_uuid, pathway_uuid, node_uuid):
context["breadcrumbs"] = [
{"Home": s.SERVER_URL},
{"Package": s.SERVER_URL + "/package"},
- {current_package.name: current_package.url},
+ {current_package.get_name(): current_package.url},
{"Pathway": current_package.url + "/pathway"},
- {current_pathway.name: current_pathway.url},
+ {current_pathway.get_name(): current_pathway.url},
{"Node": current_pathway.url + "/node"},
- {current_node.name: current_node.url},
+ {current_node.get_name(): current_node.url},
]
context["node"] = current_node
@@ -2261,16 +2285,18 @@ def package_pathway_edges(request, package_uuid, pathway_uuid):
if request.method == "GET":
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - {current_pathway.name} - Edges"
+ context["title"] = (
+ f"enviPath - {current_package.get_name()} - {current_pathway.get_name()} - Edges"
+ )
context["meta"]["current_package"] = current_package
context["object_type"] = "edge"
context["breadcrumbs"] = [
{"Home": s.SERVER_URL},
{"Package": s.SERVER_URL + "/package"},
- {current_package.name: current_package.url},
+ {current_package.get_name(): current_package.url},
{"Pathway": current_package.url + "/pathway"},
- {current_pathway.name: current_pathway.url},
+ {current_pathway.get_name(): current_pathway.url},
{"Edge": current_pathway.url + "/edge"},
]
@@ -2286,7 +2312,7 @@ def package_pathway_edges(request, package_uuid, pathway_uuid):
return JsonResponse(
{
"objects": [
- {"name": pw.name, "url": pw.url, "reviewed": current_package.reviewed}
+ {"name": pw.get_name(), "url": pw.url, "reviewed": current_package.reviewed}
for pw in (
reviewed_edge_qs if current_package.reviewed else unreviewed_edge_qs
)
@@ -2338,7 +2364,7 @@ def package_pathway_edge(request, package_uuid, pathway_uuid, edge_uuid):
context = get_base_context(request)
context["title"] = (
- f"enviPath - {current_package.name} - {current_pathway.name} - {current_edge.edge_label.name}"
+ f"enviPath - {current_package.get_name()} - {current_pathway.get_name()} - {current_edge.edge_label.get_name()}"
)
context["meta"]["current_package"] = current_package
@@ -2391,11 +2417,11 @@ def package_scenarios(request, package_uuid):
"all", False
):
scens = Scenario.objects.filter(package=current_package).order_by("name")
- res = [{"name": s_.name, "url": s_.url, "uuid": s_.uuid} for s_ in scens]
+ res = [{"name": s_.get_name(), "url": s_.url, "uuid": s_.uuid} for s_ in scens]
return JsonResponse(res, safe=False)
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - Scenarios"
+ context["title"] = f"enviPath - {current_package.get_name()} - Scenarios"
context["meta"]["current_package"] = current_package
context["object_type"] = "scenario"
@@ -2419,7 +2445,7 @@ def package_scenarios(request, package_uuid):
return JsonResponse(
{
"objects": [
- {"name": pw.name, "url": pw.url, "reviewed": current_package.reviewed}
+ {"name": pw.get_name(), "url": pw.url, "reviewed": current_package.reviewed}
for pw in (
reviewed_scenario_qs
if current_package.reviewed
@@ -2511,7 +2537,9 @@ def package_scenario(request, package_uuid, scenario_uuid):
if request.method == "GET":
context = get_base_context(request)
- context["title"] = f"enviPath - {current_package.name} - {current_scenario.name}"
+ context["title"] = (
+ f"enviPath - {current_package.get_name()} - {current_scenario.get_name()}"
+ )
context["meta"]["current_package"] = current_package
context["object_type"] = "scenario"
@@ -2748,13 +2776,13 @@ def group(request, group_uuid):
if request.method == "GET":
context = get_base_context(request)
- context["title"] = f"enviPath - {current_group.name}"
+ context["title"] = f"enviPath - {current_group.get_name()}"
context["object_type"] = "group"
context["breadcrumbs"] = [
{"Home": s.SERVER_URL},
{"Group": s.SERVER_URL + "/group"},
- {current_group.name: current_group.url},
+ {current_group.get_name(): current_group.url},
]
context["group"] = current_group
@@ -2909,13 +2937,13 @@ def setting(request, setting_uuid):
if request.method == "GET":
context = get_base_context(request)
- context["title"] = f"enviPath - {current_setting.name}"
+ context["title"] = f"enviPath - {current_setting.get_name()}"
context["object_type"] = "setting"
context["breadcrumbs"] = [
{"Home": s.SERVER_URL},
{"Setting": s.SERVER_URL + "/setting"},
- {f"{current_setting.name}": current_setting.url},
+ {f"{current_setting.get_name()}": current_setting.url},
]
context["setting"] = current_setting
@@ -2964,8 +2992,8 @@ def jobs(request):
target_package = PackageManager.create_package(
current_user,
- f"Autogenerated Package for Pathway Engineering of {pathway_to_engineer.name}",
- f"This Package was generated automatically for the engineering Task of {pathway_to_engineer.name}.",
+ f"Autogenerated Package for Pathway Engineering of {pathway_to_engineer.get_name()}",
+ f"This Package was generated automatically for the engineering Task of {pathway_to_engineer.get_name()}.",
)
from .tasks import dispatch, engineer_pathways
@@ -3019,7 +3047,7 @@ def jobs(request):
"This Package was generated automatically for the batch prediction task.",
)
- from .tasks import dispatch, batch_predict
+ from .tasks import batch_predict, dispatch
res = dispatch(
current_user,
@@ -3057,6 +3085,8 @@ def job(request, job_uuid):
if job.job_name == "batch_predict":
filename = f"{job.job_name.replace(' ', '_')}_{job.task_id}.csv"
+ elif job.job_name == "identify_missing_rules":
+ filename = f"{job.job_name.replace(' ', '_')}_{job.task_id}.csv"
else:
raise BadRequest("Result is not downloadable!")
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 00000000..bff00c56
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,3 @@
+onlyBuiltDependencies:
+ - '@parcel/watcher'
+ - '@tailwindcss/oxide'
diff --git a/static/js/pw.js b/static/js/pw.js
index 7320c05e..c06e8668 100644
--- a/static/js/pw.js
+++ b/static/js/pw.js
@@ -393,7 +393,14 @@ function draw(pathway, elem) {
}
function edge_popup(e) {
- popupContent = "" + e.name + "
";
+ popupContent = "" + e.name + "
";
+
+ if (e.reaction.rules) {
+ console.log(e.reaction.rules);
+ for (var rule of e.reaction.rules) {
+ popupContent += "Rule " + rule.name + "
";
+ }
+ }
if (e.app_domain) {
adcontent = "
";
diff --git a/templates/index/index.html b/templates/index/index.html
index 7e83ea3e..0f3e996e 100644
--- a/templates/index/index.html
+++ b/templates/index/index.html
@@ -176,8 +176,8 @@
href="#"
class="example-link hover:text-primary cursor-pointer"
title="load example"
- @click.prevent="loadExample('CC(C)CC1=CC=C(C=C1)C(C)C(=O)O', $el)"
- >IbuprofenAspartame
{% endif %}
+ {% if compound_structure.half_lifes %}
+
+ The underlying reaction can be found
+ here.
+
+
+
+
+
+
+
+ {% for scenario, half_lifes in compound_structure.half_lifes.items %}
+ Scenario
+ Values
+
+
+ {% endfor %}
+
+
+ {{ scenario.name }}
+ ({{ scenario.package.name }})
+
+
+
+
+
+
+
+
+ Scenario Type
+ {{ scenario.scenario_type }}
+
+
+ Half-life (days)
+ {{ half_lifes.0.dt50 }}
+
+
+
+ Model
+ {{ half_lifes.0.model }}
+ {{ edge.edge_label.name }}
+ {{ edge.get_name }}
+ {{ node.name }}
+ {{ node.get_name }}