Compare commits

..
11 Commits
Author SHA1 Message Date
Tim Lorsbach 8dc0e5e7a3 exclude groups
CI / test (pull_request) Failing after 28s
API CI / api-tests (pull_request) Failing after 35s
2026-09-23 20:08:35 +02:00
Tim Lorsbach ff37a17edb ...
CI / test (pull_request) Failing after 28s
API CI / api-tests (pull_request) Failing after 31s
2026-09-23 10:12:48 +02:00
Tim Lorsbach c60eac879e wip 2026-09-15 23:13:21 +02:00
Tim Lorsbach b269b7dfd8 ... 2026-09-08 21:49:54 +02:00
Tim Lorsbach b12c2dda69 wip
CI / test (pull_request) Failing after 27s
API CI / api-tests (pull_request) Failing after 31s
2026-09-08 15:11:47 +02:00
Tim Lorsbach 0b325a30b7 updated add if dep
CI / test (pull_request) Failing after 28s
API CI / api-tests (pull_request) Failing after 43s
2026-09-03 12:04:14 +02:00
Tim Lorsbach d8741c5375 wip
CI / test (pull_request) Failing after 29s
API CI / api-tests (pull_request) Failing after 42s
2026-09-02 21:34:26 +02:00
Tim Lorsbach 887de03a19 reactionSmarts 2026-09-02 21:30:53 +02:00
Tim Lorsbach b54c8eaab6 minor
CI / test (pull_request) Failing after 28s
API CI / api-tests (pull_request) Failing after 44s
2026-08-28 13:14:43 +02:00
Tim Lorsbach f885745127 Fix import
CI / test (pull_request) Failing after 27s
API CI / api-tests (pull_request) Failing after 41s
2026-08-27 11:46:08 +02:00
Tim Lorsbach 48d2bce52a minor 2026-08-20 09:28:23 +02:00
19 changed files with 206 additions and 42 deletions
@@ -0,0 +1,18 @@
# Generated by Django 6.0.3 on 2026-09-08 08:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bayer', '0003_pescompound_pesstructure_package_data_pool'),
]
operations = [
migrations.AddField(
model_name='package',
name='shareable',
field=models.BooleanField(default=True, verbose_name='Shareable'),
),
]
+1
View File
@@ -23,6 +23,7 @@ class Package(EnviPathModel):
license = models.ForeignKey( license = models.ForeignKey(
"epdb.License", on_delete=models.SET_NULL, blank=True, null=True, verbose_name="License" "epdb.License", on_delete=models.SET_NULL, blank=True, null=True, verbose_name="License"
) )
shareable = models.BooleanField(verbose_name="Shareable", default=True)
class Classification(models.IntegerChoices): class Classification(models.IntegerChoices):
INTERNAL = 0, "Internal" INTERNAL = 0, "Internal"
@@ -36,8 +36,8 @@
img.onerror = () => { img.onerror = () => {
this.pesVizHtml = ` this.pesVizHtml = `
<div class='alert alert-error' role='alert'> <div class='alert alert-error' role='alert'>
<h4 class='alert-heading'>Could not render PES!</h4> <h4 class='alert-heading'>Failed to fetch this PES!</h4>
<p>Could not render PES - Do you have access?</p> <p>Either the PES-ID is incorrect, or you're missing sufficient permissions to access this (potentially secret) item. In case you're missing permissions you can request the entitlement cs.u.enviPath_secret_data_user_group on go/idnow to gain access.</p>
</div>`; </div>`;
}; };
}, },
+3 -3
View File
@@ -119,8 +119,8 @@ def create_pes_node(request, package_uuid, pathway_uuid):
except ValueError as e: except ValueError as e:
return error( return error(
request, request,
"Could not fetch PES", "Failed to fetch this PES",
f"Could not fetch PES data for {pes_link}" "Either the PES-ID is incorrect, or you're missing sufficient permissions to access this (potentially secret) item. In case you're missing permissions you can request the entitlement cs.u.enviPath_secret_data_user_group on go/idnow to gain access."
) )
classification = pes_data.get("classificationLevel", "") classification = pes_data.get("classificationLevel", "")
@@ -177,7 +177,7 @@ def create_pes_node(request, package_uuid, pathway_uuid):
def get_application_token(prod: bool) -> str: def get_application_token(prod: bool) -> str:
scope = f"{s.PROD_PES_SCOPE if prod else s.NON_PROD_PES_SCOPE}/.default" scope = f"{s.PROD_PES_SCOPE if prod else s.NON_PROD_PES_SCOPE}/.default"
url = f"https://login.microsoftonline.com/{s.MS_TENANT_ID}/oauth2/v2.0/token" url = f"https://login.microsoftonline.com/{s.MS_ENTRA_TENANT_ID}/oauth2/v2.0/token"
data = { data = {
"grant_type": "client_credentials", "grant_type": "client_credentials",
"client_id": s.MS_ENTRA_CLIENT_ID, "client_id": s.MS_ENTRA_CLIENT_ID,
+2
View File
@@ -445,6 +445,8 @@ if MS_ENTRA_ENABLED:
MS_ENTRA_AUTHORITY = f"https://login.microsoftonline.com/{MS_ENTRA_TENANT_ID}" MS_ENTRA_AUTHORITY = f"https://login.microsoftonline.com/{MS_ENTRA_TENANT_ID}"
MS_ENTRA_REDIRECT_URI = os.environ["MS_REDIRECT_URI"] MS_ENTRA_REDIRECT_URI = os.environ["MS_REDIRECT_URI"]
MS_ENTRA_SCOPES = os.environ.get("MS_SCOPES", "").split(",") MS_ENTRA_SCOPES = os.environ.get("MS_SCOPES", "").split(",")
NON_PROD_PES_SCOPE = os.environ.get("NON_PROD_PES_SCOPE")
PROD_PES_SCOPE = os.environ.get("PROD_PES_SCOPE")
# Site ID 10 -> beta.envipath.org # Site ID 10 -> beta.envipath.org
MATOMO_SITE_ID = os.environ.get("MATOMO_SITE_ID", "10") MATOMO_SITE_ID = os.environ.get("MATOMO_SITE_ID", "10")
+9 -3
View File
@@ -77,7 +77,7 @@ def entra_callback(request):
return error( return error(
request, request,
"Login Failed", "Login Failed",
"The user is not authenticated. A reason for this might be a missing assignment to the respective enviPath group.", "Request access to role 1230/MON/APPS/Envipath/Envipath_Prod on go/idnow",
403, 403,
) )
@@ -102,7 +102,7 @@ def entra_callback(request):
else: else:
auth_log.info(f"Registering {user_name} with OID {user_oid}") auth_log.info(f"Registering {user_name} with OID {user_oid}")
u = UserManager.create_user(user_name, user_email, None, uuid=user_oid, is_active=True) u = UserManager.create_user(user_name, user_email, None, uuid=user_oid, is_active=True, add_to_group=False)
registered = True registered = True
auth_log.info(f"User {user_name} {'(admin) ' if u.is_superuser else ''}with OID {user_oid} successfully logged in as {u.username} from {get_remote_address(request)}") auth_log.info(f"User {user_name} {'(admin) ' if u.is_superuser else ''}with OID {user_oid} successfully logged in as {u.username} from {get_remote_address(request)}")
@@ -139,10 +139,16 @@ def entra_callback(request):
auth_log.info(f"Login Group Sync: Adding {u.username} to Group {g.name} ({ uuid })") auth_log.info(f"Login Group Sync: Adding {u.username} to Group {g.name} ({ uuid })")
else: else:
g = Group.objects.get(uuid=uuid) g = Group.objects.get(uuid=uuid)
if g.user_member.contains(u): # Do not remove users from All enviPath Users
if g.user_member.contains(u) and g.name != 'All enviPath Users':
g.user_member.remove(u) g.user_member.remove(u)
auth_log.info(f"Login Group Sync: Removing {u.username} from Group {g.name} ({ uuid })") auth_log.info(f"Login Group Sync: Removing {u.username} from Group {g.name} ({ uuid })")
# Ensure people are part of All enviPath Users -> they have to as, envipath_registered_user is granted
all_envipath_users = Group.objects.get(name="All enviPath Users")
if not all_envipath_users.user_member.contains(u):
all_envipath_users.user_member.add(u)
if registered: if registered:
# #72 make package secret if user is part of a secret group # #72 make package secret if user is part of a secret group
for id, name in s.ENTRA_SECRET_GROUPS.items(): for id, name in s.ENTRA_SECRET_GROUPS.items():
+6
View File
@@ -11,6 +11,7 @@ from .models import (
CompoundStructure, CompoundStructure,
Edge, Edge,
EnviFormer, EnviFormer,
EnzymeLink,
ExternalDatabase, ExternalDatabase,
ExternalIdentifier, ExternalIdentifier,
Group, Group,
@@ -212,6 +213,10 @@ class CompoundStructureAdmin(EPAdmin):
pass pass
class EnzymeLinkAdmin(EPAdmin):
pass
class SimpleAmbitRuleAdmin(EPAdmin): class SimpleAmbitRuleAdmin(EPAdmin):
pass pass
@@ -266,6 +271,7 @@ admin.site.register(License, LicenseAdmin)
admin.site.register(ClassifierPluginModel, ClassifierPluginModelAdmin) admin.site.register(ClassifierPluginModel, ClassifierPluginModelAdmin)
admin.site.register(Compound, CompoundAdmin) admin.site.register(Compound, CompoundAdmin)
admin.site.register(CompoundStructure, CompoundStructureAdmin) admin.site.register(CompoundStructure, CompoundStructureAdmin)
admin.site.register(EnzymeLink, EnzymeLinkAdmin)
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin) admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
admin.site.register(ParallelRule, ParallelRuleAdmin) admin.site.register(ParallelRule, ParallelRuleAdmin)
admin.site.register(Reaction, ReactionAdmin) admin.site.register(Reaction, ReactionAdmin)
+23 -6
View File
@@ -211,7 +211,7 @@ class UserManager(object):
# Create package # Create package
package_name = f"{u.username}{'' if u.username[-1] in 'sxzß' else 's'} Package" package_name = f"{u.username}{'' if u.username[-1] in 'sxzß' else 's'} Package"
package_description = "This package was generated during registration." package_description = "This package was generated during registration."
p = PackageManager.create_package(u, package_name, package_description) p = PackageManager.create_package(u, package_name, package_description, shareable=False)
u.default_package = p u.default_package = p
u.save() u.save()
@@ -547,7 +547,7 @@ class PackageManager(object):
@staticmethod @staticmethod
@transaction.atomic @transaction.atomic
def create_package(current_user, name: str, description: str = None): def create_package(current_user, name: str, description: str = None, *args, **kwargs):
p = Package() p = Package()
# Clean for potential XSS # Clean for potential XSS
@@ -556,6 +556,9 @@ class PackageManager(object):
if description is not None and description.strip() != "": if description is not None and description.strip() != "":
p.description = nh3.clean(description.strip(), tags=s.ALLOWED_HTML_TAGS).strip() p.description = nh3.clean(description.strip(), tags=s.ALLOWED_HTML_TAGS).strip()
if "shareable" in kwargs:
p.shareable = kwargs["shareable"]
p.save() p.save()
up = UserPackagePermission() up = UserPackagePermission()
@@ -578,6 +581,9 @@ class PackageManager(object):
if caller_perm != Permission.ALL[0] and not caller.is_superuser: if caller_perm != Permission.ALL[0] and not caller.is_superuser:
raise ValueError("Only owner are allowed to modify permissions") raise ValueError("Only owner are allowed to modify permissions")
if isinstance(grantee, Group) and package.classification_level == package.Classification.SECRET:
raise ValueError("Cannot grant permissions to a Group on a secret package")
data = { data = {
"package": package, "package": package,
} }
@@ -623,6 +629,10 @@ class PackageManager(object):
def grant_write(caller: User, package: Package, grantee: Union[User, Group]): def grant_write(caller: User, package: Package, grantee: Union[User, Group]):
PackageManager.update_permissions(caller, package, grantee, Permission.WRITE[0]) PackageManager.update_permissions(caller, package, grantee, Permission.WRITE[0])
@staticmethod
def grant_owner(caller: User, package: Package, grantee: Union[User, Group]):
PackageManager.update_permissions(caller, package, grantee, Permission.ALL[0])
@staticmethod @staticmethod
@transaction.atomic @transaction.atomic
def import_legacy_package( def import_legacy_package(
@@ -665,11 +675,11 @@ class PackageManager(object):
# EDIT START # EDIT START
if data.get("classification"): if data.get("classification"):
if data["classification"] == "INTERNAL": if data["classification"] == "INTERNAL":
pack.classification = Package.Classification.RESTRICTED pack.classification_level = Package.Classification.RESTRICTED
elif data["classification"] == "RESTRICTED": elif data["classification"] == "RESTRICTED":
pack.classification = Package.Classification.RESTRICTED pack.classification_level = Package.Classification.RESTRICTED
elif data["classification"] == "SECRET": elif data["classification"] == "SECRET":
pack.classification = Package.Classification.SECRET pack.classification_level = Package.Classification.SECRET
if not "datapool" in data: if not "datapool" in data:
raise ValueError("Missing datapool in package") raise ValueError("Missing datapool in package")
@@ -821,7 +831,14 @@ class PackageManager(object):
r.name = rule["name"] r.name = rule["name"]
r.description = rule["description"] r.description = rule["description"]
r.aliases = rule.get("aliases", []) r.aliases = rule.get("aliases", [])
r.smirks = rule["smirks"]
if rule.get("smirks") is not None:
r.smirks = rule["smirks"]
elif rule.get("reactionSmarts") is not None:
r.smirks = rule["reactionSmarts"]
else:
raise ValueError(f"No SMIRKS or reactionSmarts found for rule {rule['id']}")
r.reactant_filter_smarts = rule.get("reactantFilterSmarts", None) r.reactant_filter_smarts = rule.get("reactantFilterSmarts", None)
r.product_filter_smarts = rule.get("productFilterSmarts", None) r.product_filter_smarts = rule.get("productFilterSmarts", None)
r.save() r.save()
+28
View File
@@ -2456,6 +2456,20 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
else: else:
return self.default_node_label.name return self.default_node_label.name
def get_description(self, include_suffix=True):
default_desc = False
if self.description == "no description":
default_desc = True
if not default_desc:
return self.description
else:
if include_suffix:
return f"{self.default_node_label.description} (taken from underlying structure)"
else:
return self.default_node_label.description
def d3_json(self): def d3_json(self):
app_domain_data = self.get_app_domain_assessment_data() app_domain_data = self.get_app_domain_assessment_data()
@@ -2773,6 +2787,20 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
else: else:
return self.edge_label.name return self.edge_label.name
def get_description(self, include_suffix=True):
default_desc = False
if self.description == "no description":
default_desc = True
if not default_desc:
return self.description
else:
if include_suffix:
return f"{self.edge_label.description} (taken from underlying reaction)"
else:
return self.edge_label.description
class EPModel(PolymorphicModel, EnviPathModel, AdditionalInformationMixin): class EPModel(PolymorphicModel, EnviPathModel, AdditionalInformationMixin):
package = models.ForeignKey( package = models.ForeignKey(
+20 -5
View File
@@ -1292,14 +1292,18 @@ def package(request, package_uuid):
"user_id", flat=True "user_id", flat=True
) )
) )
users = users.filter(is_active=True) users = users.filter(is_active=True).exclude(username="anonymous")
group_perms = GroupPackagePermission.objects.filter(package=current_package) group_perms = GroupPackagePermission.objects.filter(package=current_package)
groups = Group.objects.exclude(
id__in=GroupPackagePermission.objects.filter(package=current_package).values_list( if current_package.classification_level == current_package.Classification.SECRET:
"group_id", flat=True groups = Group.objects.none()
else:
groups = Group.objects.exclude(
id__in=GroupPackagePermission.objects.filter(package=current_package).values_list(
"group_id", flat=True
)
) )
)
context["users"] = users context["users"] = users
context["groups"] = groups context["groups"] = groups
@@ -1324,6 +1328,12 @@ def package(request, package_uuid):
return redirect(s.SERVER_URL + "/package") return redirect(s.SERVER_URL + "/package")
elif hidden == "publish-package": elif hidden == "publish-package":
if current_package.classification_level == current_package.Classification.SECRET:
return error(
request,
"Cannot publish a secret package",
"You cannot publish a secret package.",
)
for g in Group.objects.filter(public=True): for g in Group.objects.filter(public=True):
PackageManager.grant_read(current_user, current_package, g) PackageManager.grant_read(current_user, current_package, g)
return redirect(current_package.url) return redirect(current_package.url)
@@ -3314,6 +3324,11 @@ def jobs(request):
"This Package was generated automatically for the batch prediction task.", "This Package was generated automatically for the batch prediction task.",
) )
if current_user.default_package.classification_level == current_user.default_package.Classification.SECRET:
target_package.classification_level = current_user.default_package.Classification.SECRET
target_package.data_pool = current_user.default_package.data_pool
target_package.save()
from .tasks import batch_predict, dispatch from .tasks import batch_predict, dispatch
res = dispatch( res = dispatch(
+3 -1
View File
@@ -7,6 +7,7 @@
<i class="glyphicon glyphicon-edit"></i> Edit Package</a <i class="glyphicon glyphicon-edit"></i> Edit Package</a
> >
</li> </li>
{% if meta.current_package.shareable %}
<li> <li>
<a <a
role="button" role="button"
@@ -15,6 +16,7 @@
<i class="glyphicon glyphicon-user"></i> Edit Permissions</a <i class="glyphicon glyphicon-user"></i> Edit Permissions</a
> >
</li> </li>
{% endif %}
{% if meta.current_package.get_classification_level_display != "Secret" %} {% if meta.current_package.get_classification_level_display != "Secret" %}
<li> <li>
<a <a
@@ -52,7 +54,7 @@
> >
</li> </li>
{% endif %} {% endif %}
{% if not meta.can_edit %} {% if not meta.can_edit and meta.current_package.shareable %}
<li> <li>
<a <a
role="button" role="button"
+2
View File
@@ -7,6 +7,7 @@
<i class="glyphicon glyphicon-edit"></i> Update</a <i class="glyphicon glyphicon-edit"></i> Update</a
> >
</li> </li>
{% if 1 == 0 %}
<li> <li>
<a <a
role="button" role="button"
@@ -15,6 +16,7 @@
<i class="glyphicon glyphicon-lock"></i> Update Password</a <i class="glyphicon glyphicon-lock"></i> Update Password</a
> >
</li> </li>
{% endif %}
<li> <li>
<a <a
role="button" role="button"
+36 -8
View File
@@ -180,12 +180,25 @@
>Aspartame</a >Aspartame</a
> >
</div> </div>
<a
class="absolute top-0 left-[calc(100%-5.4rem)]"
href="/predict"
>Advanced</a
>
</div> </div>
{% if meta.current_package %}
<div
class="mt-4 rounded-lg border border-base-300 bg-base-50 px-4 py-2.5 text-sm text-base-content/70"
>
Prediction will be stored in Package
<strong class="text-base-content"
><a class="link" href="{{ meta.current_package.url }}">{{ meta.current_package.name|safe }}</a></strong
>
{% if meta.user.default_setting %}
using setting
<strong class="text-base-content"
><a class="link" href="{{ meta.user.default_setting.url}}">{{ meta.user.default_setting.name|safe }}</a></strong
>
{% endif %}
To use a different setting click
<a class="link" href="/predict">here</a>
</div>
{% endif %}
</div> </div>
<div <div
id="ketcher-container" id="ketcher-container"
@@ -208,9 +221,24 @@
> >
Predict! Predict!
</button> </button>
<div class="mt-1 flex w-full justify-end"> {% if meta.current_package %}
<a class="label justify-end" href="/predict">Advanced</a> <div
</div> class="mt-4 rounded-lg border border-base-300 bg-base-50 px-4 py-2.5 text-sm text-base-content/70"
>
Prediction will be stored in Package
<strong class="text-base-content"
><a class="link" href="{{ meta.current_package.url }}">{{ meta.current_package.name|safe }}</a></strong
>
{% if meta.user.default_setting %}
using setting
<strong class="text-base-content"
><a class="link" href="{{ meta.user.default_setting.url}}">{{ meta.user.default_setting.name|safe }}</a></strong
>
{% endif %}
To use a different setting click
<a class="link" href="/predict">here</a>
</div>
{% endif %}
</div> </div>
<input <input
type="hidden" type="hidden"
@@ -31,6 +31,7 @@
{% csrf_token %} {% csrf_token %}
<div class="form-control mb-3"> <div class="form-control mb-3">
{% if 1 == 0 %}
<label class="label" for="default-package"> <label class="label" for="default-package">
<span class="label-text">Default Package</span> <span class="label-text">Default Package</span>
</label> </label>
@@ -50,7 +51,7 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
{% endif %}
<div class="form-control mb-3"> <div class="form-control mb-3">
<label class="label" for="default-group"> <label class="label" for="default-group">
<span class="label-text">Default Group</span> <span class="label-text">Default Group</span>
+1 -1
View File
@@ -57,7 +57,7 @@
<div class="collapse-arrow bg-base-200 collapse"> <div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked /> <input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Description</div> <div class="collapse-title text-xl font-medium">Description</div>
<div class="collapse-content">{{ edge.description }}</div> <div class="collapse-content">{{ edge.get_description }}</div>
</div> </div>
{% if edge.aliases %} {% if edge.aliases %}
+1 -1
View File
@@ -60,7 +60,7 @@
<div class="collapse-arrow bg-base-200 collapse"> <div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked /> <input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Description</div> <div class="collapse-title text-xl font-medium">Description</div>
<div class="collapse-content">{{ node.description }}</div> <div class="collapse-content">{{ node.get_description }}</div>
</div> </div>
{% endif %} {% endif %}
{% epdb_slot_templates "epdb.objects.node.viz" as viz_templates %} {% epdb_slot_templates "epdb.objects.node.viz" as viz_templates %}
+15 -3
View File
@@ -2,11 +2,23 @@
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="mx-auto w-full p-8"> <div class="mx-auto w-full p-8">
<h1 class="h1 mb-4 text-3xl font-bold"> <h1 class="h1 mb-4 flex items-center gap-2 text-3xl font-bold">
Predict a Pathway Predict a Pathway
<span class="text-base-content/50 text-xs" <span class="inline-flex items-center gap-1 text-base-content/50 text-xs"
>in <strong>{{ meta.current_package.name|safe }}</strong> >in <strong>{{ meta.current_package.name|safe }}</strong>{% if meta.url_contains_package %}
{% if meta.current_package.get_classification_level_display == "Restricted" %}
<img src="{% static 'images/restricted_mid.png' %}" width="100">
{% elif meta.current_package.get_classification_level_display == "Secret" %}
<img src="{% static 'images/secret_mid.png' %}" width="60">
{% endif %}
{% else %}
{% if meta.user.default_package.get_classification_level_display == "Restricted" %}
<img src="{% static 'images/restricted_mid.png' %}" width="100">
{% elif meta.user.default_package.get_classification_level_display == "Secret" %}
<img src="{% static 'images/secret_mid.png' %}" width="60">
{% endif %}
{% endif %}
</span> </span>
</h1> </h1>
+33 -7
View File
@@ -21,6 +21,8 @@ from epdb.models import (
Compound, Compound,
CompoundStructure, CompoundStructure,
Edge, Edge,
EnzymeLink,
Group,
License, License,
Node, Node,
ParallelRule, ParallelRule,
@@ -150,6 +152,8 @@ class ReactionExportSchema(RefReactionExportSchema):
# Rules # # Rules #
######### #########
class EnzymeExportSchema(RefEnzymeExportSchema): class EnzymeExportSchema(RefEnzymeExportSchema):
name: str
description: str
ec_number: str ec_number: str
classification_level: int classification_level: int
linking_method: str linking_method: str
@@ -162,12 +166,12 @@ class EnzymeRuleExportSchema(RefRuleExportSchema):
@staticmethod @staticmethod
def resolve_enzymes(obj): def resolve_enzymes(obj):
if isinstance(obj, dict): if isinstance(obj, EnzymeRuleExportSchema):
res = [] return obj.enzymes
for e in obj.get("enzymes", []): elif isinstance(obj, dict):
res.append(EnzymeExportSchema.model_validate(e)) return obj.get("enzymes", [])
return res else:
return obj.enzymelink_set.all() return obj.enzymelink_set.all()
class RuleExportSchema(EnzymeRuleExportSchema): class RuleExportSchema(EnzymeRuleExportSchema):
@@ -271,7 +275,7 @@ class PackageExportSchema(Schema):
@staticmethod @staticmethod
def resolve_classification_level(obj): def resolve_classification_level(obj):
if isinstance(obj, dict): if isinstance(obj, dict):
return obj["classification_level"] return obj.get("classification_level", "Internal")
return obj.Classification(obj.classification_level).name return obj.Classification(obj.classification_level).name
@@ -679,6 +683,27 @@ class PackageImporter:
for scen in elem.scenarios: for scen in elem.scenarios:
elem_obj.scenarios.add(self._cache[scen.uuid]) elem_obj.scenarios.add(self._cache[scen.uuid])
def _import_enzyme(self, rule: Rule, enzyme: EnzymeExportSchema):
e = EnzymeLink()
e.uuid = str(uuid.uuid4()) if not self.preserve_uuids else enzyme.uuid
e.rule = rule
e.name = enzyme.name
e.description = enzyme.description
e.ec_number = enzyme.ec_number
e.classification_level = enzyme.classification_level
e.linking_method = enzyme.linking_method
e.save()
for reaction in enzyme.reaction_evidence:
e.reaction_evidence.add(self._cache[reaction.uuid])
for edge in enzyme.edge_evidence:
e.edge_evidence.add(self._cache[edge.uuid])
self._cache[enzyme.uuid] = e
def _import_and_link_enzymes(self, data: PackageExportSchema):
for rule in data.composite_rules:
for enzyme in rule.enzymes:
self._import_enzyme(self._cache[rule.uuid], enzyme)
def _import_package_from_json( def _import_package_from_json(
self, self,
) -> Package: ) -> Package:
@@ -717,6 +742,7 @@ class PackageImporter:
self._import_scenarios(package, parsed.scenarios) self._import_scenarios(package, parsed.scenarios)
self._import_additional_information(package, parsed.additional_information) self._import_additional_information(package, parsed.additional_information)
self._link_scenarios_after_import(parsed) self._link_scenarios_after_import(parsed)
self._import_and_link_enzymes(parsed)
return package return package
Generated
+1 -1
View File
@@ -894,7 +894,7 @@ provides-extras = ["ms-login", "dev", "pepper-plugin"]
[[package]] [[package]]
name = "envipy-additional-information" name = "envipy-additional-information"
version = "0.4.2" version = "0.4.2"
source = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git?branch=develop#ad825570480bbe2f1a35c04923fede756c450751" } source = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git?branch=develop#4909e35aac5a0a3969ac2a01f8d4ad8bdadae04c" }
dependencies = [ dependencies = [
{ name = "pydantic" }, { name = "pydantic" },
] ]