forked from enviPath/enviPy
Compare commits
4 Commits
develop-ba
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| f7c45b8015 | |||
| 68aea97013 | |||
| 3cc7fa9e8b | |||
| 21f3390a43 |
@ -408,3 +408,9 @@ if MS_ENTRA_ENABLED:
|
|||||||
|
|
||||||
# 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")
|
||||||
|
|
||||||
|
# CAP
|
||||||
|
CAP_ENABLED = os.environ.get("CAP_ENABLED", "False") == "True"
|
||||||
|
CAP_API_BASE = os.environ.get("CAP_API_BASE", None)
|
||||||
|
CAP_SITE_KEY = os.environ.get("CAP_SITE_KEY", None)
|
||||||
|
CAP_SECRET_KEY = os.environ.get("CAP_SECRET_KEY", None)
|
||||||
|
|||||||
@ -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
5
epdb/autodiscovery.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.utils.module_loading import autodiscover_modules
|
||||||
|
|
||||||
|
|
||||||
|
def autodiscover():
|
||||||
|
autodiscover_modules("epdb_hooks")
|
||||||
@ -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 #
|
||||||
###########
|
###########
|
||||||
|
|||||||
@ -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
17
epdb/template_registry.py
Normal 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, [])]
|
||||||
@ -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)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import logging
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Dict, List, Iterable
|
from typing import Any, Dict, List, Iterable
|
||||||
|
|
||||||
|
import requests
|
||||||
import nh3
|
import nh3
|
||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
@ -147,6 +148,11 @@ def handler500(request):
|
|||||||
def login(request):
|
def login(request):
|
||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
|
|
||||||
|
if s.CAP_ENABLED:
|
||||||
|
context["CAP_ENABLED"] = s.CAP_ENABLED
|
||||||
|
context["CAP_API_BASE"] = s.CAP_API_BASE
|
||||||
|
context["CAP_SITE_KEY"] = s.CAP_SITE_KEY
|
||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
context["title"] = "enviPath"
|
context["title"] = "enviPath"
|
||||||
context["next"] = request.GET.get("next", "")
|
context["next"] = request.GET.get("next", "")
|
||||||
@ -224,6 +230,11 @@ def logout(request):
|
|||||||
def register(request):
|
def register(request):
|
||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
|
|
||||||
|
if s.CAP_ENABLED:
|
||||||
|
context["CAP_ENABLED"] = s.CAP_ENABLED
|
||||||
|
context["CAP_API_BASE"] = s.CAP_API_BASE
|
||||||
|
context["CAP_SITE_KEY"] = s.CAP_SITE_KEY
|
||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
# Redirect to unified login page with signup tab
|
# Redirect to unified login page with signup tab
|
||||||
next_url = request.GET.get("next", "")
|
next_url = request.GET.get("next", "")
|
||||||
@ -238,6 +249,33 @@ def register(request):
|
|||||||
if next := request.POST.get("next"):
|
if next := request.POST.get("next"):
|
||||||
context["next"] = next
|
context["next"] = next
|
||||||
|
|
||||||
|
# Catpcha
|
||||||
|
if s.CAP_ENABLED:
|
||||||
|
cap_token = request.POST.get("cap-token")
|
||||||
|
|
||||||
|
if not cap_token:
|
||||||
|
context["message"] = "Missing CAP Token."
|
||||||
|
return render(request, "static/login.html", context)
|
||||||
|
|
||||||
|
verify_url = f"{s.CAP_API_BASE}/{s.CAP_SITE_KEY}/siteverify"
|
||||||
|
payload = {
|
||||||
|
"secret": s.CAP_SECRET_KEY,
|
||||||
|
"response": cap_token,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = requests.post(verify_url, json=payload, timeout=10)
|
||||||
|
resp.raise_for_status()
|
||||||
|
verify_data = resp.json()
|
||||||
|
except requests.RequestException:
|
||||||
|
context["message"] = "Captcha verification failed."
|
||||||
|
return render(request, "static/login.html", context)
|
||||||
|
|
||||||
|
if not verify_data.get("success"):
|
||||||
|
context["message"] = "Captcha check failed. Please try again."
|
||||||
|
return render(request, "static/login.html", context)
|
||||||
|
# End Captcha
|
||||||
|
|
||||||
username = request.POST.get("username", "").strip()
|
username = request.POST.get("username", "").strip()
|
||||||
email = request.POST.get("email", "").strip()
|
email = request.POST.get("email", "").strip()
|
||||||
password = request.POST.get("password", "").strip()
|
password = request.POST.get("password", "").strip()
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
{% 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 %}
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -12,10 +14,21 @@
|
|||||||
New Compound
|
New Compound
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% 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 %}
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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()"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -218,6 +218,12 @@
|
|||||||
|
|
||||||
<input type="hidden" name="next" value="{{ next }}" />
|
<input type="hidden" name="next" value="{{ next }}" />
|
||||||
|
|
||||||
|
{% if CAP_ENABLED %}
|
||||||
|
<cap-widget
|
||||||
|
data-cap-api-endpoint="{{ CAP_API_BASE }}/{{ CAP_SITE_KEY }}/"
|
||||||
|
></cap-widget>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- ToS and Academic Use Notice -->
|
<!-- ToS and Academic Use Notice -->
|
||||||
<div class="text-xs text-base-content/70 mt-2">
|
<div class="text-xs text-base-content/70 mt-2">
|
||||||
<p>
|
<p>
|
||||||
@ -233,7 +239,6 @@
|
|||||||
enviPath is free for academic and non-commercial use only.
|
enviPath is free for academic and non-commercial use only.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" name="confirmsignup" class="btn btn-success w-full">
|
<button type="submit" name="confirmsignup" class="btn btn-success w-full">
|
||||||
Sign Up
|
Sign Up
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -19,7 +19,16 @@
|
|||||||
type="text/css"
|
type="text/css"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{% block extra_styles %}{% endblock %}
|
{% if CAP_ENABLED %}
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget@0.1.41/cap.min.js"></script>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/@cap.js/widget@0.1.41/src/cap.min.css"
|
||||||
|
/>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% block extra_styles %}
|
||||||
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-base-100">
|
<body class="bg-base-100">
|
||||||
<div class="flex h-screen">
|
<div class="flex h-screen">
|
||||||
|
|||||||
Reference in New Issue
Block a user