[Chore] Package Export, Blanks for fields, Styling (#421)

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#421
This commit is contained in:
2026-07-02 20:12:26 +12:00
parent a092d4a558
commit dba6514013
21 changed files with 647 additions and 450 deletions

View File

@ -31,6 +31,7 @@ from sklearn.model_selection import ShuffleSplit
from bridge.contracts import Property
from bridge.dto import RunResult, PropertyPrediction
from epdb.exceptions import InvalidSMILESException
from utilities.chem import FormatConverter, IndigoUtils, PredictionResult, ProductSet
from utilities.ml import (
ApplicabilityDomainPCA,
@ -631,7 +632,7 @@ class EnviPathModel(TimeStampedModel):
class AliasMixin(models.Model):
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
@ -654,7 +655,9 @@ class AliasMixin(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
def set_scenarios(self, scenarios: List["Scenario"]):
@ -780,7 +783,7 @@ class Compound(
"CompoundStructure",
verbose_name="Default Structure",
related_name="compound_default_structure",
on_delete=models.CASCADE,
on_delete=models.SET_NULL,
null=True,
)
@ -857,13 +860,13 @@ class Compound(
package: "Package", smiles: str, name: str = None, description: str = None, *args, **kwargs
) -> "Compound":
if smiles is None or smiles.strip() == "":
raise ValueError("SMILES is required")
raise InvalidSMILESException("SMILES is required")
smiles = smiles.strip()
parsed = FormatConverter.from_smiles(smiles)
if parsed is None:
raise ValueError("Given SMILES is invalid")
raise InvalidSMILESException("Given SMILES is invalid")
standardized_smiles = FormatConverter.standardize(smiles, remove_stereo=True)
@ -1174,6 +1177,8 @@ class CompoundStructure(
@property
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)
@property
@ -1610,10 +1615,15 @@ class Reaction(
products = models.ManyToManyField(
"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")
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")
@ -1630,7 +1640,7 @@ class Reaction(
educts: Union[List[str], List[CompoundStructure]] = None,
products: Union[List[str], List[CompoundStructure]] = None,
rules: Union[Rule | List[Rule]] = None,
multi_step: bool = True,
multi_step: bool = False,
):
_educts = []
_products = []
@ -2197,7 +2207,25 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
depth_map[0] = list()
processed = set()
for n in self.root_nodes:
data_driven_root_nodes = (
self.node_set.all()
.annotate(prod_cnt=Count("edge_products"), educt_cnt=Count("edge_educts"))
.filter(prod_cnt=0, educt_cnt__gt=0)
.distinct()
)
# Eval QuerySet
root_nodes_by_depth = list(self.root_nodes)
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
@ -2277,7 +2305,12 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
"node_label_id": self.default_node_label.url,
"image": f"{self.url}?image=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",
"name": self.get_name(),
@ -2331,6 +2364,11 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
@property
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)
def get_timeseries_data(self):