1 Commits

Author SHA1 Message Date
e5e0dcee3b adjusted migration
Some checks failed
API CI / api-tests (pull_request) Failing after 30s
CI / test (pull_request) Failing after 33s
Initial bayer app

Show Pack Classification

Adjusted docker compose to bayer specifics

Adjusted Dockerfile for Bayer

Adding secret flags to group, add secret pools to packages

Adjusted View for Package creation

Prep configs, added Package Create Modal

wip

More on PES

wip

wip

Wip

minor

PW interactions

API PES

wip

Make Select Widget reflect required

make required generallay available

Update UI if pathway mode is set to build

Added ais

circle adjustments

Initial Zoom, fix AD Creation

wip

auth log, bb4g fix

missing import

Added viz hint if PES is part of reaction

Add Edge check for pes

flip boolean

...

pes

Added extra

...

In / Out Edges Viz, Submitting Button Text

...

Make PES Link clickable

Return proper http response instead of error

Fixed error return, removed unused options

Fix PES Link HTML for other entities

Fixed molfile assignment, adjusted Export

Package Export/Import cycle

highlight Description links

implemented non persistent

Harmonised proposed field in Json output

Added pesLink field to PW Api output

PES Fields in API Output

removed debug

Fix Classification import, Fix PES Deserialization

underline pes link in templates

Fix alter name/desc for node, make /node /edge funcitonal

provide setting link and copy button

Implemented Compound Names / Reaction Names View Option

Unconnected Nodes

Make links thicker, reduce timeout trigger time

Show proposed info in popover

Pathway Build no stereo removal

Include probs in reaction name option viz

Detect clicks outside nodes/edges
2026-07-16 22:26:23 +02:00
14 changed files with 41 additions and 622 deletions

View File

@ -4,7 +4,6 @@
{% block action_modals %}
{% include "modals/objects/edit_package_modal.html" %}
{% include "modals/objects/view_package_permissions_modal.html" %}
{% include "modals/objects/edit_package_permissions_modal.html" %}
{% include "modals/objects/publish_package_modal.html" %}
{% include "modals/objects/set_license_modal.html" %}

View File

@ -1,9 +1,8 @@
import base64
import logging
import requests
from django.conf import settings as s
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed
from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import redirect
from bayer.models import PESCompound
@ -15,9 +14,6 @@ from utilities.decorators import package_permission_required
Package = s.GET_PACKAGE_MODEL()
logger = logging.getLogger(__name__)
@package_permission_required()
def create_pes(request, package_uuid):
current_user = _anonymous_or_real(request)
@ -40,41 +36,27 @@ def create_pes(request, package_uuid):
try:
pes_data = fetch_pes(request, pes_link)
except ValueError as e:
return error(
request,
"Could not fetch PES",
f"Could not fetch PES data for {pes_link}"
)
return HttpResponseBadRequest(f"Could not fetch PES data for {pes_link}")
classification = pes_data.get("classificationLevel", "")
if "secret" == classification.lower():
if current_package.classification_level != Package.Classification.SECRET:
return error(
request,
"Classification Mismatch!",
"Cannot create secret PESs in non-secret packages."
)
return HttpResponseBadRequest("Cannot create PESs for non-secret packages.")
if not current_package.data_pool or not current_package.data_pool.secret:
logger.info(f"The current package does not have a secret data pool.")
return error(
request,
"The current package does not have a secret data pool.",
"Cannot create secret PESs in package without a secret data pool."
)
data_pools = pes_data.get("dataPools")
if data_pools:
if s.DATA_POOL_MAPPING[current_package.data_pool.name] not in data_pools:
return HttpResponseBadRequest(
f"PES data pool {s.DATA_POOL_MAPPING[current_package.data_pool.name]} not found in PES data")
pes = PESCompound.create(current_package, pes_data, compound_name, compound_description)
return redirect(pes.url)
else:
return error(
request,
"No PES link received",
"Please provide a PES link."
)
return HttpResponseBadRequest("Please provide a PES link.")
else:
return HttpResponseNotAllowed(["POST"])
pass
@package_permission_required()
@ -100,37 +82,23 @@ def create_pes_node(request, package_uuid, pathway_uuid):
try:
pes_data = fetch_pes(request, pes_link)
except ValueError as e:
return error(
request,
"Could not fetch PES",
f"Could not fetch PES data for {pes_link}"
)
return HttpResponseBadRequest(f"Could not fetch PES data for {pes_link}")
classification = pes_data.get("classificationLevel", "")
if "secret" == classification.lower():
if current_package.classification_level != Package.Classification.SECRET:
return error(
request,
"Classification Mismatch!",
"Cannot create secret PESs in non-secret packages."
)
return HttpResponseBadRequest("Cannot create PESs for non-secret packages.")
if not current_package.data_pool or not current_package.data_pool.secret:
logger.info(f"The current package does not have a secret data pool.")
return error(
request,
"The current package does not have a secret data pool.",
"Cannot create secret PESs in package without a secret data pool."
)
data_pools = pes_data.get("dataPools")
if data_pools:
if s.DATA_POOL_MAPPING[current_package.data_pool.name] not in data_pools:
return HttpResponseBadRequest(
f"PES data pool {s.DATA_POOL_MAPPING[current_package.data_pool.name]} not found in PES data")
pes = PESCompound.create(current_package, pes_data, compound_name, compound_description)
node_qs = Node.objects.filter(
pathway=current_pathway,
default_node_label=pes.default_structure
)
node_qs = Node.objects.filter(pathway=current_pathway, default_node_label=pes.default_structure)
if node_qs.exists():
return redirect(current_pathway.url)
@ -148,13 +116,9 @@ def create_pes_node(request, package_uuid, pathway_uuid):
return redirect(current_pathway.url)
else:
return error(
request,
"No PES link received",
"Please provide a PES link."
)
return HttpResponseBadRequest("Please provide a PES link.")
else:
return HttpResponseNotAllowed(["POST"])
pass
def fetch_pes(request, pes_url) -> dict:

View File

@ -9,8 +9,8 @@ from envipy_additional_information import registry
from envipy_additional_information.groups import GroupEnum
from epapi.utils.schema_transformers import build_rjsf_output
from epapi.utils.validation_errors import handle_validation_error
from epdb.models import AdditionalInformation, Scenario, Node
from ..dal import get_scenario_for_read, get_scenario_for_write, get_package_for_write
from epdb.models import AdditionalInformation
from ..dal import get_scenario_for_read, get_scenario_for_write
logger = logging.getLogger(__name__)
@ -58,61 +58,6 @@ def list_scenario_info(request, scenario_uuid: UUID):
return result
@router.post("/information/{model_name}/")
def add_object_info(request, model_name: str, payload: Dict[str, Any] = Body(...)):
from epdb.views import EPDBURLParser
cls = registry.get_model(model_name.lower())
if not cls:
raise HttpError(404, f"Unknown model: {model_name}")
try:
instance = cls(**payload) # Pydantic validates
except ValidationError as e:
handle_validation_error(e)
if "attach_obj_url" in payload:
url_parser = EPDBURLParser(payload["attach_obj_url"])
if url_parser.contains_package_url():
package = get_package_for_write(request.user, url_parser.get_objects()[0].uuid)
attach_obj = url_parser.get_object()
if "scenario_uuid" in payload:
scenario = get_scenario_for_read(request.user, payload["scenario_uuid"])
else:
scenario = Scenario.create(
package,
name=f"Scenario {Scenario.objects.filter(package=package).count() + 1}",
description="no description",
scenario_date=None,
scenario_type=None,
additional_information=[],
)
if isinstance(attach_obj, Node):
ai = add_info_to_node(package, instance, scenario, attach_obj)
else:
raise HttpError(404, f"Bad request - Not implemented for {type(attach_obj)}!")
return {"status": "created", "uuid": ai.uuid}
raise HttpError(404, "Bad request!")
def add_info_to_node(package, add_inf, scenario, node):
ai = AdditionalInformation.create(
package,
add_inf,
scenario=scenario,
content_object=node,
)
node.pathway.scenarios.add(scenario)
return ai
@router.post("/scenario/{uuid:scenario_uuid}/information/{model_name}/")
def add_scenario_info(
request, scenario_uuid: UUID, model_name: str, payload: Dict[str, Any] = Body(...)

View File

@ -881,11 +881,12 @@ def create_package_compound(
if "secret" == classification.lower():
if p.classification_level != Package.Classification.SECRET:
return 400, {"message": "Cannot create secret PESs in non-secret packages."}
if not p.data_pool or not p.data_pool.secret:
return 400, {"message": "Cannot create secret PESs in package without a secret data pool."}
return 400, {"Cannot create PESs for non-secret packages."}
data_pools = pes_data.get("dataPools")
if data_pools:
if s.DATA_POOL_MAPPING[p.data_pool.name] not in data_pools:
return 400, { "messsage": f"PES data pool {s.DATA_POOL_MAPPING[p.data_pool.name]} not found in PES data"}
c = PESCompound.create(p, pes_data, c.compoundName, c.compoundDescription)
else:
@ -1704,7 +1705,7 @@ class PathwayNode(Schema):
image: str = Field(None, alias="image")
imageSize: int = Field(None, alias="image_size")
name: str = Field(None, alias="name")
proposed: List[Dict[str, Any]] = []
proposed: List[Dict[str, str]] = []
smiles: str = Field(None, alias="smiles")
pseudo: bool = Field(False, alias="pseudo")
pesLink: str | None = Field(None, alias="pes_link")
@ -1974,7 +1975,7 @@ def get_package_pathway_node(request, package_uuid, pathway_uuid, node_uuid):
class CreateNode(Schema):
nodeAsSmiles: str | None = None
nodeAsSmiles: str
nodeAsMolFile: str | None = None
nodeName: str | None = None
nodeReason: str | None = None
@ -2006,10 +2007,14 @@ def add_pathway_node(request, package_uuid, pathway_uuid, n: Form[CreateNode]):
if "secret" == classification.lower():
if p.classification_level != Package.Classification.SECRET:
return 400, {"message": "Cannot create secret PESs in non-secret packages."}
return 400, "Cannot create PESs for non-secret packages."
if not p.data_pool or not p.data_pool.secret:
return 400, {"message": "Cannot create secret PESs in package without a secret data pool."}
data_pools = pes_data.get("dataPools")
if data_pools:
if s.DATA_POOL_MAPPING[p.data_pool.name] not in data_pools:
return 400, {
"messsage": f"PES data pool {s.DATA_POOL_MAPPING[p.data_pool.name]} not found in PES data"
}
c = PESCompound.create(p, pes_data, n.nodeName, n.nodeReason)

View File

@ -2466,7 +2466,7 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
"name": self.get_name(),
"plain_name": self.get_name(include_suffix=False),
"smiles": self.default_node_label.smiles,
"scenarios": [{"name": s.get_name(), "url": s.url} for s in self.get_scenarios()],
"scenarios": [{"name": s.get_name(), "url": s.url} for s in self.scenarios.all()],
"app_domain": {
"inside_app_domain": app_domain_data["assessment"]["inside_app_domain"]
if app_domain_data
@ -2594,15 +2594,6 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
return list(collected.values())
def get_scenarios(self):
qs = self.scenarios.all()
qs |= Scenario.objects.filter(
id__in=self.additional_information.filter(scenario__isnull=False)
.values_list("scenario", flat=True)
.distinct()
)
return qs.distinct()
class Edge(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin):
pathway = models.ForeignKey(

View File

@ -186,40 +186,6 @@ window.AdditionalInformationApi = {
return this._handleResponse(response, "createItem");
},
/**
* Create new additional information and attach it to an object.
* @param {string} modelName - Name/type of the additional information model
* @param {Object} data - Data for the new item
* @param {string} attachObjectUrl - UUID of the object this data should be attached to
* @param {string} scenarioUuid - UUID of the scenario
* @returns {Promise<{status: string, uuid: string}>}
*/
async createItemOnNonScenarioObject(modelName, data, attachObjectUrl, scenarioUuid) {
const sanitizedData = this.sanitizePayload(data);
this._log("createItemOnNonScenarioObject", { modelName, data: sanitizedData, attachObjectUrl, scenarioUuid });
sanitizedData.attach_obj_url = attachObjectUrl;
if (scenarioUuid) {
sanitizedData.scenario_uuid = scenarioUuid;
}
// Normalize model name to lowercase
const normalizedName = modelName.toLowerCase();
const response = await fetch(
`/api/v1/information/${normalizedName}/`,
{
method: "POST",
headers: this._buildHeaders(),
body: JSON.stringify(sanitizedData),
},
);
return this._handleResponse(response, "createItemOnNonScenarioObject");
},
/**
* Delete additional information from a scenario
* @param {string} scenarioUuid - UUID of the scenario

View File

@ -23,7 +23,6 @@
<i class="glyphicon glyphicon-bullhorn"></i> Publish Package</a
>
</li>
{% endif %}
<li>
<a
role="button"
@ -32,7 +31,6 @@
<i class="glyphicon glyphicon-bullhorn"></i> Export Package as JSON</a
>
</li>
{% if meta.can_edit %}
<li>
<a
role="button"
@ -50,13 +48,3 @@
>
</li>
{% endif %}
{% if not meta.can_edit %}
<li>
<a
role="button"
onclick="document.getElementById('view_package_permissions_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-user"></i> View Permissions</a
>
</li>
{% endif %}

View File

@ -83,19 +83,6 @@
<i class="glyphicon glyphicon-edit"></i> Edit Pathway</a
>
</li>
<li>
<a
class="button"
onclick="
const modal = document.getElementById('edit_pathway_node_modal');
modal.showModal();
window.dispatchEvent(new Event('modal-opened'));
return false;
"
>
<i class="glyphicon glyphicon-edit"></i> Edit Compound</a
>
</li>
<li>
<a
role="button"

View File

@ -10,7 +10,7 @@
<li>
<a
class="button"
onclick="document.getElementById('add_scenario_additional_information_modal').showModal(); return false;"
onclick="document.getElementById('add_additional_information_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Add Additional Information</a
>

View File

@ -1,7 +1,7 @@
{% load static %}
<!-- Add Additional Information -->
<dialog
id="add_scenario_additional_information_modal"
id="add_additional_information_modal"
class="modal"
x-data="{
isSubmitting: false,
@ -98,7 +98,7 @@
);
// Close modal and reload page to show new item
document.getElementById('add_scenario_additional_information_modal').close();
document.getElementById('add_additional_information_modal').close();
window.location.reload();
} catch (err) {
if (err.isValidationError && err.fieldErrors) {

View File

@ -1,276 +0,0 @@
{% load static %}
<!-- Add Additional Information -->
<dialog
id="edit_pathway_node_modal"
class="modal"
x-data="{
isSubmitting: false,
selectedType: '',
selectedScenario: '',
selectedNode: '',
schemas: {},
loadingSchemas: false,
error: null,
formData: null, // Store reference to form data
formRenderKey: 0, // Counter to force form re-render
allowedTypes: ['halflife', 'halflifews', 'proposedintermediate', 'transformationproductimportance', 'confidence'],
scenarios: [],
scenariosLoaded: false,
// Get sorted unique schema names for dropdown, excluding already-added types
get sortedSchemaNames() {
const names = Object.keys(this.schemas);
// Remove duplicates, exclude existing types, and sort alphabetically by display title
const unique = [...new Set(names)];
const available = unique.filter(name =>
this.allowedTypes.includes(name)
);
return available.sort((a, b) => {
const titleA = (this.schemas[a]?.schema?.['x-title'] || a).toLowerCase();
const titleB = (this.schemas[b]?.schema?.['x-title'] || b).toLowerCase();
return titleA.localeCompare(titleB);
});
},
async init() {
// Watch for selectedType changes
this.$watch('selectedType', (value) => {
// Reset formData when type changes and increment key to force re-render
this.formData = null;
this.formRenderKey++;
// Clear previous errors
this.error = null;
Alpine.store('validationErrors').clearErrors(); // No context - clears all
});
// Load schemas and existing items
try {
this.loadingSchemas = true;
const [schemasRes, scenarioRes] = await Promise.all([
fetch('/api/v1/information/schema/'),
fetch('{% url "package scenario list" meta.current_package.uuid %}', { headers: {'Accept': 'application/json' }}),
]);
if (!schemasRes.ok) throw new Error('Failed to load schemas');
if (!scenarioRes.ok) throw new Error('Failed to load scenarios');
this.schemas = await schemasRes.json();
this.scenarios = await scenarioRes.json();
// Get unique existing types (normalize to lowercase)
} catch (err) {
this.error = err.message;
} finally {
this.loadingSchemas = false;
}
},
reset() {
this.isSubmitting = false;
this.selectedType = '';
this.error = null;
this.formData = null;
this.selectedScenario = '';
Alpine.store('validationErrors').clearErrors(); // No context - clears all
},
setFormData(data) {
// Fired from schemaRenderer
this.formData = data;
},
async submit() {
if (!this.selectedType) return;
const payload = window.AdditionalInformationApi.sanitizePayload(this.formData || {});
// Validate that form has data
if (!payload || Object.keys(payload).length === 0) {
// proposedintermediate is parameterless
if (!this.selectedType === 'proposedintermediate') {
this.error = 'Please fill in at least one field';
return;
}
}
this.isSubmitting = true;
this.error = null;
try {
// TODO
await window.AdditionalInformationApi.createItemOnNonScenarioObject(
this.selectedType,
payload,
this.selectedNode,
this.selectedScenario
);
// Close modal and reload page to show new item
document.getElementById('edit_pathway_node_modal').close();
window.location.reload();
} catch (err) {
if (err.isValidationError && err.fieldErrors) {
// No context for add modal - simple flat errors
Alpine.store('validationErrors').setErrors(err.fieldErrors);
this.error = err.message || 'Please correct the errors in the form';
} else {
this.error = err.message || 'An error occurred. Please try again.';
}
} finally {
this.isSubmitting = false;
}
}
}"
@close="reset()"
@form-data-ready="setFormData($event.detail)"
@modal-opened.window="
const el = d3.select('circle.highlighted').node();
if (el !== null) {
const selectElement = document.getElementById('edit_pathway_nodes_node');
for (let option of selectElement.options) {
if (option.value === el.__data__.url) {
option.selected = true;
break;
}
}
selectElement.dispatchEvent(new Event('change'));
}
"
>
<div class="modal-box max-w-2xl">
<!-- Header -->
<h3 class="text-lg font-bold">Edit Compound</h3>
<!-- Close button (X) -->
<form method="dialog">
<button
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
:disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<!-- Loading state -->
<template x-if="loadingSchemas">
<div class="flex items-center justify-center p-4">
<span class="loading loading-spinner loading-md"></span>
</div>
</template>
<!-- Error state -->
<template x-if="error">
<div class="alert alert-error mb-4">
<span x-text="error"></span>
</div>
</template>
<div class="form-control">
<p>
If no Scenario is selected a new Scenario will be created and attached
to this Pathway
</p>
<label class="label" for="edit_pathway_nodes_node">
<span class="label-text">Select Node</span>
</label>
<select
id="edit_pathway_nodes_node"
name="node"
class="select select-bordered w-full"
x-model="selectedNode"
>
{% for n in pathway.nodes %}
<option value="{{ n.url }}">
{{ n.default_node_label.name|safe }}
</option>
{% endfor %}
</select>
<label class="label" for="scenario-select">
<span class="label-text">Scenarios</span>
</label>
<select
id="scenario-select"
name="selected-scenarios"
class="select select-bordered w-full"
x-model="selectedScenario"
>
<option value="" selected disabled>Select Scenario</option>
<template x-for="scenario in scenarios" :key="scenario.url">
<option :value="scenario.url" x-text="scenario.name"></option>
</template>
</select>
</div>
<!-- Schema selection -->
<template x-if="!loadingSchemas">
<div>
<div class="form-control mb-4">
<label class="label" for="select-additional-information-type">
<span class="label-text">Select the type to add</span>
</label>
<select
id="select-additional-information-type"
class="select select-bordered w-full"
x-model="selectedType"
>
<option value="" selected disabled>Select the type to add</option>
<template x-for="name in sortedSchemaNames" :key="name">
<option
:value="name"
x-text="(schemas[name].schema && (schemas[name].schema['x-title'] || schemas[name].schema.title)) || name"
></option>
</template>
</select>
</div>
<!-- Form renderer for selected type -->
<!-- Use unique key per type to force re-render -->
<template x-for="renderKey in [formRenderKey]" :key="renderKey">
<div x-show="selectedType && schemas[selectedType]">
<div
x-data="schemaRenderer({
rjsf: schemas[selectedType],
mode: 'edit'
// No context - single form, backward compatible
})"
x-init="await init(); $dispatch('form-data-ready', data)"
>
{% include "components/schema_form.html" %}
</div>
</div>
</template>
</div>
</template>
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit()"
:disabled="isSubmitting || loadingSchemas || !selectedType || !selectedNode"
>
<span x-show="!isSubmitting">Add</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Adding...</span>
</button>
</div>
</div>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -1,147 +0,0 @@
{% load static %}
<!-- Edit Package Permissions -->
<dialog
id="view_package_permissions_modal"
class="modal"
x-data="{}"
>
<div class="modal-box max-w-2xl">
<!-- Header -->
<h3 class="text-lg font-bold">Current Permissions</h3>
<!-- Close button (X) -->
<form method="dialog">
<button class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2">
</button>
</form>
<!-- Body -->
<div class="py-4">
<p class="mb-4">
Current permissions for this package.
</p>
<!-- User Permissions -->
{% if user_permissions %}
<div class="divider">User Permissions</div>
<div class="space-y-2">
<div class="grid grid-cols-12 gap-2 items-center">
<div class="col-span-5 truncate"></div>
<div class="col-span-2 text-center">Read</div>
<div class="col-span-2 text-center">Write</div>
<div class="col-span-2 text-center">Owner</div>
<div class="col-span-1"></div>
</div>
{% for up in user_permissions %}
<div class="grid grid-cols-12 gap-2 items-center">
<div class="col-span-5 truncate">
{{ up.user.username }}
{% if not up.user.is_active %}<i>(inactive)</i>{% endif %}
</div>
<div class="col-span-2 text-center">
<input
type="checkbox"
name="read"
id="read_{{ up.user.uuid }}"
class="checkbox"
{% if up.has_read %}checked{% endif %}
onclick="return false;"
/>
</div>
<div class="col-span-2 text-center">
<input
type="checkbox"
name="write"
id="write_{{ up.user.uuid }}"
class="checkbox"
{% if up.has_write %}checked{% endif %}
onclick="return false;"
/>
</div>
<div class="col-span-2 text-center">
<input
type="checkbox"
name="owner"
id="owner_{{ up.user.uuid }}"
class="checkbox"
{% if up.has_all %}checked{% endif %}
onclick="return false;"
/>
</div>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Group Permissions -->
{% if group_permissions %}
<div class="divider">Group Permissions</div>
<div class="space-y-2">
<div class="grid grid-cols-12 gap-2 items-center">
<div class="col-span-5 truncate"></div>
<div class="col-span-2 text-center">Read</div>
<div class="col-span-2 text-center">Write</div>
<div class="col-span-2 text-center">Owner</div>
<div class="col-span-1"></div>
</div>
{% for gp in group_permissions %}
<div class="grid grid-cols-12 gap-2 items-center">
<div class="col-span-5 truncate">
{{ gp.group.name|safe }}
</div>
<div class="col-span-2 text-center">
<input
type="checkbox"
name="read"
id="read_{{ gp.group.uuid }}"
class="checkbox"
{% if gp.has_read %}checked{% endif %}
onclick="return false;"
/>
</div>
<div class="col-span-2 text-center">
<input
type="checkbox"
name="write"
id="write_{{ gp.group.uuid }}"
class="checkbox"
{% if gp.has_write %}checked{% endif %}
onclick="return false;"
/>
</div>
<div class="col-span-2 text-center">
<input
type="checkbox"
name="owner"
id="owner_{{ gp.group.uuid }}"
class="checkbox"
{% if gp.has_all %}checked{% endif %}
onclick="return false;"
/>
</div>
</div>
{% endfor %}
</div>
{% endif %}
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
>
Close
</button>
</div>
</div>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>

View File

@ -4,8 +4,6 @@
{% block content %}
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="{% static 'js/api/additional-information.js' %}"></script>
<style>
#vizdiv {
width: 100%;
@ -99,7 +97,6 @@
{% include "modals/objects/identify_missing_rules_modal.html" %}
{% include "modals/objects/generic_copy_object_modal.html" %}
{% include "modals/objects/edit_pathway_modal.html" %}
{% include "modals/objects/edit_pathway_node_modal.html" %}
{% include "modals/objects/generic_set_aliases_modal.html" %}
{% include "modals/objects/generic_set_scenario_modal.html" %}
{% include "modals/objects/delete_pathway_node_modal.html" %}

View File

@ -7,7 +7,7 @@
{% block action_modals %}
{% include "modals/objects/edit_scenario_modal.html" %}
{% include "modals/objects/add_scenario_additional_information_modal.html" %}
{% include "modals/objects/add_additional_information_modal.html" %}
{% include "modals/objects/update_scenario_additional_information_modal.html" %}
{% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %}