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")
logger.info(f"Using Package model: {model_name}")
from .autodiscovery import autodiscover
autodiscover()
if settings.PLUGINS_ENABLED:
from bridge.contracts import Property
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
import nh3
@ -11,8 +12,16 @@ from ninja.security import SessionAuth
from utilities.chem import FormatConverter
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 (
AdditionalInformation,
Compound,
CompoundStructure,
Edge,
@ -1329,7 +1338,14 @@ class ScenarioSchema(Schema):
@staticmethod
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
def resolve_review_status(obj: Rule):
@ -1394,7 +1410,11 @@ def create_package_scenario(request, package_uuid):
study_type = request.POST.get("type")
ais = []
types = request.POST.get("adInfoTypes[]", "").split(",")
types = request.POST.get("adInfoTypes[]", [])
if types:
types = types.split(",")
for t in types:
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 #
###########

View File

@ -4191,7 +4191,6 @@ class AdditionalInformation(models.Model):
ai: "EnviPyModel",
scenario=None,
content_object=None,
skip_cleaning=False,
):
add_inf = AdditionalInformation()
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.type_adapter import TypeAdapter
from epdb.template_registry import get_templates
register = template.Library()
url_adapter = TypeAdapter(AnyHttpUrl)
@ -19,3 +21,8 @@ def is_url(value):
return True
except ValidationError:
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 ??
(typeof window !== "undefined" &&
window.location?.search?.includes("debugErrors=1")),
attach_object: options.attach_object || null,
async init() {
if (options.schemaUrl) {

View File

@ -1,8 +1,10 @@
{% extends "collections/paginated_base.html" %}
{% load envipytags %}
{% block page_title %}Compounds{% endblock %}
{% block action_button %}
<div class="flex items-center gap-2">
{% if meta.can_edit %}
<button
type="button"
@ -12,10 +14,21 @@
New Compound
</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 %}
{% block action_modals %}
{% 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 %}
{% block description %}

View File

@ -18,8 +18,25 @@
<!-- Schema form -->
<template x-if="schema && !loading">
<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 -->
<template x-if="schema['x-title'] || schema.title">
<template x-if="(schema['x-title'] || schema.title) && !attach_object">
<h4
class="text-lg font-semibold"
x-text="data.name || schema['x-title'] || schema.title"

View File

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