2 Commits

Author SHA1 Message Date
f7c45b8015 [Feature] Add legacy api endpoint to mimic ReferringScenarios (#362)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#362
2026-03-17 19:44:47 +13:00
68aea97013 [Feature] Simple template extension mechanism (#361)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#361
2026-03-16 21:06:20 +13:00
10 changed files with 142 additions and 15 deletions

View File

@ -16,6 +16,10 @@ class EPDBConfig(AppConfig):
model_name = getattr(settings, "EPDB_PACKAGE_MODEL", "epdb.Package") model_name = getattr(settings, "EPDB_PACKAGE_MODEL", "epdb.Package")
logger.info(f"Using Package model: {model_name}") logger.info(f"Using Package model: {model_name}")
from .autodiscovery import autodiscover
autodiscover()
if settings.PLUGINS_ENABLED: if settings.PLUGINS_ENABLED:
from bridge.contracts import Property from bridge.contracts import Property
from utilities.plugin import discover_plugins from utilities.plugin import discover_plugins

5
epdb/autodiscovery.py Normal file
View File

@ -0,0 +1,5 @@
from django.utils.module_loading import autodiscover_modules
def autodiscover():
autodiscover_modules("epdb_hooks")

View File

@ -1,3 +1,4 @@
from collections import defaultdict
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
import nh3 import nh3
@ -11,8 +12,16 @@ from ninja.security import SessionAuth
from utilities.chem import FormatConverter from utilities.chem import FormatConverter
from utilities.misc import PackageExporter from utilities.misc import PackageExporter
from .logic import GroupManager, PackageManager, SearchManager, SettingManager, UserManager from .logic import (
EPDBURLParser,
GroupManager,
PackageManager,
SearchManager,
SettingManager,
UserManager,
)
from .models import ( from .models import (
AdditionalInformation,
Compound, Compound,
CompoundStructure, CompoundStructure,
Edge, Edge,
@ -1329,7 +1338,14 @@ class ScenarioSchema(Schema):
@staticmethod @staticmethod
def resolve_collection(obj: Scenario): def resolve_collection(obj: Scenario):
return obj.additional_information res = defaultdict(list)
for ai in obj.get_additional_information(direct_only=False):
data = ai.data
data["related"] = ai.content_object.simple_json() if ai.content_object else None
res[ai.type].append(data)
return res
@staticmethod @staticmethod
def resolve_review_status(obj: Rule): def resolve_review_status(obj: Rule):
@ -1394,7 +1410,11 @@ def create_package_scenario(request, package_uuid):
study_type = request.POST.get("type") study_type = request.POST.get("type")
ais = [] ais = []
types = request.POST.get("adInfoTypes[]", "").split(",") types = request.POST.get("adInfoTypes[]", [])
if types:
types = types.split(",")
for t in types: for t in types:
ais.append(build_additional_information_from_request(request, t)) ais.append(build_additional_information_from_request(request, t))
@ -1436,6 +1456,49 @@ def delete_scenario(request, package_uuid, scenario_uuid):
} }
@router.post(
"/package/{uuid:package_uuid}/additional-information", response={200: str | Any, 403: Error}
)
def create_package_additional_information(request, package_uuid):
from utilities.legacy import build_additional_information_from_request
try:
p = get_package_for_write(request.user, package_uuid)
scen = request.POST.get("scenario")
scenario = Scenario.objects.get(package=p, url=scen)
url_parser = EPDBURLParser(request.POST.get("attach_obj"))
attach_obj = url_parser.get_object()
if not hasattr(attach_obj, "additional_information"):
raise ValueError("Can't attach additional information to this object!")
if not attach_obj.url.startswith(p.url):
raise ValueError(
"Additional Information can only be set to objects stored in the same package!"
)
types = request.POST.get("adInfoTypes[]", "").split(",")
for t in types:
ai = build_additional_information_from_request(request, t)
AdditionalInformation.create(
p,
ai,
scenario=scenario,
content_object=attach_obj,
)
# TODO implement additional information endpoint ?
return redirect(f"{scenario.url}")
except ValueError:
return 403, {
"message": f"Getting Package with id {package_uuid} failed due to insufficient rights!"
}
########### ###########
# Pathway # # Pathway #
########### ###########

View File

@ -4191,7 +4191,6 @@ class AdditionalInformation(models.Model):
ai: "EnviPyModel", ai: "EnviPyModel",
scenario=None, scenario=None,
content_object=None, content_object=None,
skip_cleaning=False,
): ):
add_inf = AdditionalInformation() add_inf = AdditionalInformation()
add_inf.package = package add_inf.package = package

17
epdb/template_registry.py Normal file
View File

@ -0,0 +1,17 @@
from collections import defaultdict
from threading import Lock
_registry = defaultdict(list)
_lock = Lock()
def register_template(slot: str, template_name: str, *, order: int = 100):
item = (order, template_name)
with _lock:
if item not in _registry[slot]:
_registry[slot].append(item)
_registry[slot].sort(key=lambda x: x[0])
def get_templates(slot: str):
return [template_name for _, template_name in _registry.get(slot, [])]

View File

@ -2,6 +2,8 @@ from django import template
from pydantic import AnyHttpUrl, ValidationError from pydantic import AnyHttpUrl, ValidationError
from pydantic.type_adapter import TypeAdapter from pydantic.type_adapter import TypeAdapter
from epdb.template_registry import get_templates
register = template.Library() register = template.Library()
url_adapter = TypeAdapter(AnyHttpUrl) url_adapter = TypeAdapter(AnyHttpUrl)
@ -19,3 +21,8 @@ def is_url(value):
return True return True
except ValidationError: except ValidationError:
return False return False
@register.simple_tag
def epdb_slot_templates(slot):
return get_templates(slot)

View File

@ -88,6 +88,7 @@ document.addEventListener("alpine:init", () => {
options.debugErrors ?? options.debugErrors ??
(typeof window !== "undefined" && (typeof window !== "undefined" &&
window.location?.search?.includes("debugErrors=1")), window.location?.search?.includes("debugErrors=1")),
attach_object: options.attach_object || null,
async init() { async init() {
if (options.schemaUrl) { if (options.schemaUrl) {

View File

@ -1,21 +1,34 @@
{% extends "collections/paginated_base.html" %} {% extends "collections/paginated_base.html" %}
{% load envipytags %}
{% block page_title %}Compounds{% endblock %} {% block page_title %}Compounds{% endblock %}
{% block action_button %} {% block action_button %}
{% if meta.can_edit %} <div class="flex items-center gap-2">
<button {% if meta.can_edit %}
type="button" <button
class="btn btn-primary btn-sm" type="button"
onclick="document.getElementById('new_compound_modal').showModal(); return false;" class="btn btn-primary btn-sm"
> onclick="document.getElementById('new_compound_modal').showModal(); return false;"
New Compound >
</button> New Compound
{% endif %} </button>
{% endif %}
{% epdb_slot_templates "epdb.actions.collections.compound" as action_button_templates %}
{% for tpl in action_button_templates %}
{% include tpl %}
{% endfor %}
</div>
{% endblock action_button %} {% endblock action_button %}
{% block action_modals %} {% block action_modals %}
{% include "modals/collections/new_compound_modal.html" %} {% include "modals/collections/new_compound_modal.html" %}
{% epdb_slot_templates "modals.collections.compound" as action_modals_templates %}
{% for tpl in action_modals_templates %}
{% include tpl %}
{% endfor %}
{% endblock action_modals %} {% endblock action_modals %}
{% block description %} {% block description %}

View File

@ -18,8 +18,25 @@
<!-- Schema form --> <!-- Schema form -->
<template x-if="schema && !loading"> <template x-if="schema && !loading">
<div class="space-y-4"> <div class="space-y-4">
<template x-if="attach_object">
<div>
<h4>
<span
class="text-lg font-semibold"
x-text="schema['x-title'] + ' attached to'"
></span>
<a
class="text-lg font-semibold underline text-blue-600 hover:text-blue-800"
:href="attach_object.url"
x-text="attach_object.name"
target="_blank"
></a>
</h4>
</div>
</template>
<!-- Title from schema --> <!-- Title from schema -->
<template x-if="schema['x-title'] || schema.title"> <template x-if="(schema['x-title'] || schema.title) && !attach_object">
<h4 <h4
class="text-lg font-semibold" class="text-lg font-semibold"
x-text="data.name || schema['x-title'] || schema.title" x-text="data.name || schema['x-title'] || schema.title"

View File

@ -189,7 +189,8 @@
x-data="schemaRenderer({ x-data="schemaRenderer({
rjsf: schemas[item.type.toLowerCase()], rjsf: schemas[item.type.toLowerCase()],
data: item.data, data: item.data,
mode: 'view' mode: 'view',
attach_object: item.attach_object
})" })"
x-init="init()" x-init="init()"
> >