[Chore] Leftovers (#426)

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#426
This commit is contained in:
2026-07-16 21:33:12 +12:00
parent 72a63b4876
commit 3566571b42
6 changed files with 109 additions and 46 deletions

View File

@ -1620,7 +1620,7 @@ class PathwayNode(Schema):
image: str = Field(None, alias="image")
imageSize: int = Field(None, alias="image_size")
name: str = Field(None, alias="name")
proposed: List[Dict[str, str]] = Field([], alias="proposed_intermediate")
proposed: List[Dict[str, str]] = []
smiles: str = Field(None, alias="smiles")
pseudo: bool = Field(False, alias="pseudo")
@ -1910,7 +1910,7 @@ def add_pathway_node(request, package_uuid, pathway_uuid, n: Form[CreateNode]):
else:
node_depth = -1
n = Node.create(
node = Node.create(
pw,
n.nodeAsSmiles,
node_depth,
@ -1919,7 +1919,7 @@ def add_pathway_node(request, package_uuid, pathway_uuid, n: Form[CreateNode]):
description=n.nodeReason,
)
return redirect(n.url)
return redirect(node.url)
except ValueError:
return 403, {"message": "Adding node failed!"}

View File

@ -1008,8 +1008,6 @@ class PackageManager(object):
data: Dict[str, Any],
owner: User,
preserve_uuids=False,
add_import_timestamp=True,
trust_reviewed=False,
) -> Package:
importer = PackageImporter(data, preserve_uuids)
imported_package = importer.do_import()
@ -1830,6 +1828,7 @@ class SPathway(object):
if edge.rule:
e["rule"] = edge.rule.simple_json()
if edge.probability:
e["probability"] = edge.probability

View File

@ -894,34 +894,46 @@ class Compound(
if parsed is None:
raise InvalidSMILESException("Given SMILES is invalid")
if name is not None:
# Clean for potential XSS
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
standardized_smiles = FormatConverter.standardize(smiles, remove_stereo=True)
qs = CompoundStructure.objects.filter(smiles=smiles, compound__package=package)
# 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
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)
# Check if we can find the standardized one
if CompoundStructure.objects.filter(
smiles=standardized_smiles, compound__package=package
).exists():
found_c = CompoundStructure.objects.get(
smiles=standardized_smiles, compound__package=package
).compound
if qs.exists():
found_structure = qs.first()
found_compound = found_structure.compound
# As a direct match wasn't found add a new structure
# TODO return newly created structure
_ = found_c.add_structure(smiles, molfile=molfile, name=name, description=description)
# We've only found the standardized one, create the very structure
_ = found_compound.add_structure(
smiles, molfile=molfile, name=name, description=description
)
return found_c
if name:
found_compound.add_alias(name)
return found_compound
# Generate Compound
c = Compound()
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}"
@ -1218,8 +1230,15 @@ class CompoundStructure(
# Overwrite SMILES from molfile
smiles = FormatConverter.to_smiles(mol)
# 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():
found_cs = CompoundStructure.objects.get(smiles=smiles, compound=compound)
found_cs = CompoundStructure.objects.get(compound=compound, smiles=smiles)
if name:
found_cs.add_alias(name)
if found_cs.molfile is None and (molfile is not None and molfile.strip() != ""):
logger.info(
@ -1236,16 +1255,17 @@ class CompoundStructure(
cs = CompoundStructure()
# Clean for potential XSS
if name is not None:
cs.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
cs.name = name
if description is not None:
# We have a default here only set the value if it carries some payload
if description is not None and description.strip() != "":
cs.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
cs.compound = compound
cs.smiles = smiles
# Check if molfile is present and valid
if molfile is not None and molfile.strip() != "":
# If molfile is not None, it hase to be a valid Molfile as we've survived the parsing check
if molfile is not None:
cs.molfile = molfile
if "normalized_structure" in kwargs:
@ -1464,6 +1484,9 @@ class SimpleAmbitRule(SimpleRule):
if not FormatConverter.is_valid_smirks(smirks):
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)
if reactant_filter_smarts is not None and reactant_filter_smarts.strip() != "":
@ -1475,14 +1498,17 @@ class SimpleAmbitRule(SimpleRule):
if query.exists():
if query.count() > 1:
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.package = package
if name is not None:
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
if name is None or name == "":
name = f"Rule {Rule.objects.filter(package=package).count() + 1}"
@ -1615,6 +1641,9 @@ class ParallelRule(Rule):
f"Simple rule {sr.uuid} does not belong to package {package.uuid}!"
)
if name is not None:
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
# Deduplication check
query = ParallelRule.objects.annotate(
srs_count=Count("simple_rules", filter=Q(simple_rules__in=simple_rules), distinct=True)
@ -1626,15 +1655,19 @@ class ParallelRule(Rule):
if existing_rule_qs.exists():
if existing_rule_qs.count() > 1:
logger.error(f"Found more than one reaction for given input! {existing_rule_qs}")
return existing_rule_qs.first()
logger.error(
f"Found more than one ParallelRule for given input! {existing_rule_qs}"
)
found_rule = existing_rule_qs.first()
if name:
found_rule.add_alias(name)
return found_rule
r = ParallelRule()
r.package = package
if name is not None:
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
if name is None or name == "":
name = f"Rule {Rule.objects.filter(package=package).count() + 1}"
@ -1730,6 +1763,10 @@ class Reaction(
rules: Union[Rule | List[Rule]] = None,
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 = []
_products = []
@ -1784,14 +1821,21 @@ class Reaction(
logger.error(
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.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 name is None or name == "":
name = f"Reaction {Reaction.objects.filter(package=package).count() + 1}"
r.name = name
if description is not None and description.strip() != "":
r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
@ -2420,6 +2464,7 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
},
"predicted_properties": predicted_properties,
"is_engineered_intermediate": self.kv.get("is_engineered_intermediate", False),
"proposed": self.get_proposed_info(),
"timeseries": self.get_timeseries_data(),
**structure_data,
}
@ -2516,7 +2561,7 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
return res
def is_proposed_intermediate(self):
def get_proposed_info(self):
collected = defaultdict(dict)
for ai in self.additional_information.filter(
type__in=["ProposedIntermediate", "TransformationProductImportance", "Confidence"],

View File

@ -134,7 +134,7 @@
<!-- Reactions -->
{% if compound.related_reactions %}
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<input type="checkbox" />
<div class="collapse-title text-xl font-medium">Reactions</div>
<div class="collapse-content">
<ul class="menu bg-base-100 rounded-box">
@ -153,7 +153,7 @@
<!-- Pathways -->
{% if compound.related_pathways %}
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<input type="checkbox" />
<div class="collapse-title text-xl font-medium">Pathways</div>
<div class="collapse-content">
<ul class="menu bg-base-100 rounded-box">
@ -172,7 +172,7 @@
<!-- Scenarios -->
{% if compound.scenarios.all %}
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<input type="checkbox" />
<div class="collapse-title text-xl font-medium">Scenarios</div>
<div class="collapse-content">
<ul class="menu bg-base-100 rounded-box">
@ -240,7 +240,7 @@
<!-- External Identifiers -->
{% if compound.get_external_identifiers %}
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<input type="checkbox" />
<div class="collapse-title text-xl font-medium">
External Identifier
</div>

View File

@ -92,6 +92,25 @@
</div>
{% endif %}
<!-- Pathways -->
{% if compound.related_pathways %}
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" />
<div class="collapse-title text-xl font-medium">Pathways</div>
<div class="collapse-content">
<ul class="menu bg-base-100 rounded-box">
{% for r in compound.related_pathways %}
<li>
<a href="{{ r.url }}" class="hover:bg-base-200"
>{{ r.name }} <i>({{ r.package.name }})</i></a
>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
{% if compound_structure.half_lifes %}
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" />
@ -144,7 +163,7 @@
{% if compound_structure.scenarios.all %}
<!-- Scenarios -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<input type="checkbox" />
<div class="collapse-title text-xl font-medium">Scenarios</div>
<div class="collapse-content">
<ul class="menu bg-base-100 rounded-box">

View File

@ -106,7 +106,7 @@ class ReactionTest(TestCase):
multi_step=False,
)
self.assertEqual(r.name, "no name")
self.assertEqual(r.name, "Reaction 1")
self.assertEqual(r.description, "no description")
def test_deduplication(self):