Compare commits

..
4 Commits
Author SHA1 Message Date
Tim Lorsbach a6b901aa47 merged pathways
API CI / api-tests (pull_request) Failing after 39s
CI / test (pull_request) Failing after 42s
2026-11-10 07:30:27 +01:00
Tim Lorsbach 423ddf16d1 Adjust data_pool resolution for app integrations
API CI / api-tests (pull_request) Failing after 34s
CI / test (pull_request) Failing after 36s
2026-11-09 23:16:01 +01:00
Tim Lorsbach a7b2e60660 fixed width for news
API CI / api-tests (pull_request) Failing after 40s
CI / test (pull_request) Failing after 36s
2026-11-09 22:52:15 +01:00
jebusandTim Lorsbach ae3a164e74 Feat/bb4g (#2)
CI / test (pull_request) Failing after 40s
API CI / api-tests (pull_request) Failing after 41s
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: #2
2026-11-10 10:29:05 +13:00
6 changed files with 141 additions and 17 deletions
@@ -0,0 +1,19 @@
# Generated by Django 6.0.3 on 2026-11-09 21:43
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bayer', '0006_remove_newsentry_url'),
]
operations = [
migrations.AlterField(
model_name='newsentry',
name='publication_date',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date'),
),
]
+4
View File
@@ -378,6 +378,10 @@ class PackageManager(object):
# EDIT START
if package.classification_level == Package.Classification.SECRET:
if user.principal_type == user.PrincipalType.APP:
if not user.app_details.secret_allowed or package.data_pool != user.app_details.data_pool:
return False
if package.data_pool not in groups:
return False
+102 -15
View File
@@ -2071,7 +2071,10 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
def empty_due_to_threshold(self):
return self.kv.get("empty_due_to_threshold", False)
def d3_json(self):
def merged_d3_json(self):
return self.d3_json(merged=True)
def d3_json(self, merged=False):
# Ideally it would be something like this but
# to reduce crossing in edges do a DFS
# nodes = [n.d3_json() for n in self.nodes]
@@ -2079,15 +2082,68 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
nodes = []
processed = set()
structure_to_combined_nodes = dict()
node_to_combined_node = dict()
queue = list()
for n in self.root_nodes:
queue.append(n)
# Add unconnected nodes
for n in self.nodes.order_by("url"):
if len(n.out_edges.all()) == 0:
if n not in queue:
queue.append(n)
class CombinedNode(object):
def __init__(self, representative: "Node"):
self.representative = representative
self.nodes = set([self.representative])
def ids(self):
return [n.id for n in self.nodes]
def d3_json(self):
d3_json = self.representative.d3_json()
compound = self.representative.default_node_label.compound
d3_json["name"] = compound.get_name()
d3_json["smiles"] = compound.default_structure.smiles
d3_json["node_label_id"] = compound.default_structure.url
d3_json["image"] = f"{compound.default_structure.url}?image=svg",
d3_json["image_svg"] = IndigoUtils.mol_to_svg(
compound.default_structure.molfile
if compound.default_structure.molfile is not None
and compound.default_structure.molfile.strip()
else compound.default_structure.smiles,
width=40,
height=40,
),
return d3_json
def __repr__(self):
return f"CombinedNode({self.nodes})"
if merged:
for n in self.nodes:
def_struct = n.default_node_label.compound.default_structure
if def_struct in structure_to_combined_nodes:
structure_to_combined_nodes[def_struct].nodes.add(n)
else:
cn = CombinedNode(n)
structure_to_combined_nodes[def_struct] = cn
# for n in self.nodes.order_by("url"):
# if len(n.out_edges.all()) == 0:
# if n not in queue:
# queue.append(n)
for cn in structure_to_combined_nodes.values():
for n in cn.nodes:
node_to_combined_node[n] = cn
queue = list(structure_to_combined_nodes.values())
else:
for n in self.nodes:
queue.append(n)
# Add unconnected nodes
for n in self.nodes.order_by("url"):
if len(n.out_edges.all()) == 0:
if n not in queue:
queue.append(n)
for i in queue:
processed.add(i)
@@ -2097,25 +2153,56 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
processed.add(current)
nodes.append(current.d3_json())
for e in self.edges.filter(start_nodes=current).order_by("url").distinct():
if merged:
q = Q(start_nodes__in=current.ids())
else:
q = Q(start_nodes=current)
for e in self.edges.filter(q).order_by("url").distinct():
for prod in e.end_nodes.all().order_by("url"):
if prod not in queue and prod not in processed:
queue.append(prod)
if merged:
def_struct = n.default_node_label.compound.default_structure
if def_struct in structure_to_combined_nodes:
structure_to_combined_nodes[def_struct].nodes.add(n)
else:
cn = CombinedNode(n)
structure_to_combined_nodes[def_struct] = cn
if cn not in queue and cn not in processed:
queue.append(cn)
else:
if prod not in queue and prod not in processed:
queue.append(prod)
# We shouldn't lose or make up nodes...
if len(nodes) != len(self.nodes):
logger.debug(
f"{self.get_name()}: Num Nodes {len(nodes)} vs. DB Nodes {len(self.nodes)}"
)
if not merged:
if len(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]
# D3 links Nodes based on indices in nodes array
combined_node_depth_to_idx = dict()
node_url_to_idx = dict()
for i, n in enumerate(nodes):
n["id"] = i
node_url_to_idx[n["url"]] = i
# Add potential missing mappings
if merged:
cn_idx_mapping = dict()
for n in nodes:
for cn in processed:
for sub_n in cn.nodes:
if n["url"] == sub_n.url:
cn_idx_mapping[cn] = n["id"]
for n in processed:
for sub_n in n.nodes:
node_url_to_idx[sub_n.url] = cn_idx_mapping[n]
adjusted_links = []
for link in links:
# Check if we'll need pseudo nodes
+1
View File
@@ -2313,6 +2313,7 @@ def package_pathway(request, package_uuid, pathway_uuid):
context["pathway"] = current_pathway
context["current_object"] = current_pathway
context["merged"] = request.GET.get("merged", False) == "true"
context["breadcrumbs"] = [
{"Home": s.SERVER_URL},
+11 -1
View File
@@ -266,7 +266,7 @@
<div id="services-container" class="flex justify-center gap-4">
{% for ne in news_entries %}
<div class="card-body flex flex-col h-full justify-between p-5">
<div class="card-body flex flex-col h-full justify-between p-5 w-96"> <!-- Fixed width applied via w-96 -->
<div>
<div class="flex items-center gap-2 mb-2">
<div class="bg-primary/10 text-primary rounded-lg p-1.5">
@@ -283,10 +283,20 @@
{{ ne.content }}
</p>
</div>
<a href="{{ ne.url }}" class="btn btn-primary btn-sm w-full group-hover:btn-active mt-1">
Read Full
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
</a>
</div>
{% endfor %}
</div>
<div class="mt-6 text-right">
<a
href="https://community.envipath.org/c/announcements/10"
+4 -1
View File
@@ -544,8 +544,11 @@
</div>
{% endif %}
</div>
{% if merged %}
{{ pathway.merged_d3_json|json_script:"pathway" }}
{% else %}
{{ pathway.d3_json|json_script:"pathway" }}
{% endif %}
<script>
// Global switch for compound names view
var compoundNamesViewEnabled = false;