improve findings
This commit is contained in:
@@ -12,6 +12,10 @@ window.control_pressed = false;
|
|||||||
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
// Select all text input fields and textareas with the clear button
|
||||||
|
clearableTextAreaSetup();
|
||||||
|
|
||||||
|
|
||||||
document.body.addEventListener("htmx:responseError", function(e) {
|
document.body.addEventListener("htmx:responseError", function(e) {
|
||||||
let el = document.getElementById("htmx-error");
|
let el = document.getElementById("htmx-error");
|
||||||
var error;
|
var error;
|
||||||
@@ -193,6 +197,40 @@ window.addEventListener('loadDicomViewerUrls', function (e) {
|
|||||||
loadDicomViewer(e.detail.images, e.detail.annotations);
|
loadDicomViewer(e.detail.images, e.detail.annotations);
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
function clearableTextAreaSetup() {
|
||||||
|
const textFields = document.querySelectorAll(".position-relative input[type='text'], .position-relative textarea");
|
||||||
|
|
||||||
|
textFields.forEach((field) => {
|
||||||
|
const wrapper = field.closest(".position-relative");
|
||||||
|
const clearButton = wrapper.querySelector(".clear-input-button");
|
||||||
|
|
||||||
|
// Function to toggle the visibility of the clear button
|
||||||
|
const toggleClearButton = () => {
|
||||||
|
if (field.value.trim() !== "" && wrapper.matches(":hover")) {
|
||||||
|
clearButton.style.display = "inline-block"; // Show the button
|
||||||
|
} else {
|
||||||
|
clearButton.style.display = "none"; // Hide the button
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial check
|
||||||
|
toggleClearButton();
|
||||||
|
|
||||||
|
// Add event listeners to monitor changes in the input field or textarea
|
||||||
|
field.addEventListener("input", toggleClearButton);
|
||||||
|
|
||||||
|
// Add hover event listeners to the wrapper
|
||||||
|
wrapper.addEventListener("mouseenter", toggleClearButton);
|
||||||
|
wrapper.addEventListener("mouseleave", toggleClearButton);
|
||||||
|
|
||||||
|
// Add click event to clear the field or textarea
|
||||||
|
clearButton.addEventListener("click", function () {
|
||||||
|
field.value = ""; // Clear the field
|
||||||
|
toggleClearButton();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function loadDicomViewer(images_to_load, annotations_to_load) {
|
function loadDicomViewer(images_to_load, annotations_to_load) {
|
||||||
console.log("loadDicomViewer", images_to_load);
|
console.log("loadDicomViewer", images_to_load);
|
||||||
let single_dicom = document.getElementById("single-dicom-viewer");
|
let single_dicom = document.getElementById("single-dicom-viewer");
|
||||||
|
|||||||
+18
-8
@@ -49,7 +49,7 @@ from generic.models import Examination#, Sign
|
|||||||
|
|
||||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||||
from django.forms.widgets import RadioSelect, TextInput, Textarea, Select
|
from django.forms.widgets import RadioSelect, TextInput, Textarea, Select
|
||||||
from crispy_forms.layout import Submit, Layout, Fieldset, Field, Div
|
from crispy_forms.layout import Submit, Layout, Fieldset, Field, Div, HTML
|
||||||
from crispy_forms.bootstrap import Accordion, AccordionGroup
|
from crispy_forms.bootstrap import Accordion, AccordionGroup
|
||||||
|
|
||||||
from tinymce.widgets import TinyMCE
|
from tinymce.widgets import TinyMCE
|
||||||
@@ -71,6 +71,12 @@ from typing import Any
|
|||||||
from helpers.cimar import CimarAPI, NotFoundError
|
from helpers.cimar import CimarAPI, NotFoundError
|
||||||
from rad.settings import CIMAR_USERNAME, CIMAR_PASSWORD
|
from rad.settings import CIMAR_USERNAME, CIMAR_PASSWORD
|
||||||
|
|
||||||
|
class ClearableInput(TextInput):
|
||||||
|
template_name = "crispy_forms/widgets/clearable_input_single_line.html"
|
||||||
|
|
||||||
|
class ClearableTextarea(Textarea):
|
||||||
|
template_name = "crispy_forms/widgets/clearable_input.html"
|
||||||
|
|
||||||
class CaseSelect(Select):
|
class CaseSelect(Select):
|
||||||
template_name = "atlas/case_select_widget.html"
|
template_name = "atlas/case_select_widget.html"
|
||||||
|
|
||||||
@@ -295,14 +301,16 @@ class SeriesForm(ModelForm):
|
|||||||
|
|
||||||
class CaseForm(ModelForm):
|
class CaseForm(ModelForm):
|
||||||
|
|
||||||
# exams = ModelMultipleChoiceField(required=False, queryset=Exam.objects.all(),widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False))
|
|
||||||
|
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
# Django also includes a few javascript files necessary
|
# Django also includes a few javascript files necessary
|
||||||
# for the operation of this form element. You need to
|
# for the operation of this form element. You need to
|
||||||
# include <script src="/admin/jsi18n"></script>
|
# include <script src="/admin/jsi18n"></script>
|
||||||
# in the template.
|
# in the template.
|
||||||
|
#history = forms.CharField(
|
||||||
|
# widget=ClearableTextarea(attrs={"maxlength": 1000, "rows": 5, "cols": 80}),
|
||||||
|
# required=False,
|
||||||
|
#)
|
||||||
|
|
||||||
css = {
|
css = {
|
||||||
"all": ["css/widgets.css"],
|
"all": ["css/widgets.css"],
|
||||||
}
|
}
|
||||||
@@ -367,7 +375,7 @@ class CaseForm(ModelForm):
|
|||||||
"open_access",
|
"open_access",
|
||||||
"previous_case",
|
"previous_case",
|
||||||
"diagnostic_certainty",
|
"diagnostic_certainty",
|
||||||
"cimar_uuid"
|
"cimar_uuid",
|
||||||
),
|
),
|
||||||
|
|
||||||
)
|
)
|
||||||
@@ -410,9 +418,11 @@ class CaseForm(ModelForm):
|
|||||||
# (False, 'No')])
|
# (False, 'No')])
|
||||||
# "findings": TinyMCE(attrs={"cols": 80, "rows": 20}),
|
# "findings": TinyMCE(attrs={"cols": 80, "rows": 20}),
|
||||||
# "mark_scheme": TinyMCE(attrs={"cols": 80, "rows": 30}),
|
# "mark_scheme": TinyMCE(attrs={"cols": 80, "rows": 30}),
|
||||||
"description": Textarea(attrs={"cols": 80, "rows": 5}),
|
"title": ClearableInput(attrs={"rows": 1}),
|
||||||
"history": Textarea(attrs={"cols": 80, "rows": 5}),
|
"description": ClearableTextarea(attrs={"cols": 80, "rows": 5}),
|
||||||
"discussion": Textarea(attrs={"cols": 80, "rows": 5}),
|
"history": ClearableTextarea(attrs={"maxlength": 2000, "rows": 5}),
|
||||||
|
"discussion": ClearableTextarea(attrs={"maxlength": 2000, "rows": 5}),
|
||||||
|
"report": ClearableTextarea(attrs={"maxlength": 2000, "rows": 5}),
|
||||||
"condition": autocomplete.ModelSelect2Multiple(
|
"condition": autocomplete.ModelSelect2Multiple(
|
||||||
url="atlas:condition-autocomplete"
|
url="atlas:condition-autocomplete"
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -774,6 +774,21 @@ class Series(SeriesBase):
|
|||||||
def get_link_headers(self):
|
def get_link_headers(self):
|
||||||
return "atlas/series_headers.html"
|
return "atlas/series_headers.html"
|
||||||
|
|
||||||
|
def get_related_findings(self):
|
||||||
|
"""Returns the related findings for the series, including findings from other series in the same case."""
|
||||||
|
# Get findings directly related to this series
|
||||||
|
findings = list(self.findings.all())
|
||||||
|
|
||||||
|
# Get findings from other series in the same case
|
||||||
|
if hasattr(self, "case") and self.case.exists():
|
||||||
|
for case in self.case.all():
|
||||||
|
for other_series in case.series.exclude(pk=self.pk):
|
||||||
|
findings.extend(other_series.findings.all())
|
||||||
|
|
||||||
|
# Remove duplicates (by pk)
|
||||||
|
unique_findings = {f.pk: f for f in findings}.values()
|
||||||
|
return list(unique_findings)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CaseCollection(ExamOrCollectionGenericBase):
|
class CaseCollection(ExamOrCollectionGenericBase):
|
||||||
|
|||||||
@@ -214,8 +214,6 @@
|
|||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<a href="{{series.get_absolute_url}}?show_finding={{finding.pk}}"><button class="btn btn-primary btn-sm">View</button></a>
|
<a href="{{series.get_absolute_url}}?show_finding={{finding.pk}}"><button class="btn btn-primary btn-sm">View</button></a>
|
||||||
<a target="_blank" href="{{series.get_absolute_url}}?show_finding={{finding.pk}}"><button class="btn btn-primary btn-sm">Popup</button></a>
|
|
||||||
|
|
||||||
<button class="btn btn-secondary btn-sm view-finding-modal" data-finding-id="{{ finding.pk }}" data-series-id="{{ series.pk }}">
|
<button class="btn btn-secondary btn-sm view-finding-modal" data-finding-id="{{ finding.pk }}" data-series-id="{{ series.pk }}">
|
||||||
View in Modal
|
View in Modal
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -51,8 +51,70 @@
|
|||||||
$('#add_more_resource').click(() => { add_resource_input_form() });
|
$('#add_more_resource').click(() => { add_resource_input_form() });
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
{% comment %} document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
// Select all text input fields and textareas
|
||||||
|
const textFields = document.querySelectorAll("input[type='text'], textarea");
|
||||||
|
|
||||||
|
textFields.forEach((field) => {
|
||||||
|
// Create a clear button for each text field or textarea
|
||||||
|
const clearButton = document.createElement("button");
|
||||||
|
clearButton.type = "button";
|
||||||
|
clearButton.className = "btn btn-danger btn-sm clear-input-button";
|
||||||
|
clearButton.textContent = "×";
|
||||||
|
|
||||||
|
// Wrap the input field or textarea in a container with position-relative
|
||||||
|
const wrapper = document.createElement("div");
|
||||||
|
wrapper.className = "position-relative";
|
||||||
|
field.parentNode.insertBefore(wrapper, field);
|
||||||
|
wrapper.appendChild(field);
|
||||||
|
wrapper.appendChild(clearButton);
|
||||||
|
|
||||||
|
// Function to toggle the visibility of the clear button
|
||||||
|
const toggleClearButton = () => {
|
||||||
|
if (field.value.trim() !== "") {
|
||||||
|
clearButton.classList.add("show"); // Add the "show" class
|
||||||
|
} else {
|
||||||
|
clearButton.classList.remove("show"); // Remove the "show" class
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial check
|
||||||
|
toggleClearButton();
|
||||||
|
|
||||||
|
// Add event listeners to monitor changes in the input field or textarea
|
||||||
|
field.addEventListener("input", toggleClearButton);
|
||||||
|
|
||||||
|
// Add click event to clear the field or textarea
|
||||||
|
clearButton.addEventListener("click", function () {
|
||||||
|
field.value = ""; // Clear the field
|
||||||
|
toggleClearButton(); // Update the button visibility
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
{% endcomment %}
|
||||||
|
</script>
|
||||||
|
{% comment %} <style>
|
||||||
|
/* Ensure the parent container is positioned relatively */
|
||||||
|
.position-relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the clear button */
|
||||||
|
.clear-input-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
display: none; /* Hidden by default */
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show the clear button only when the input field or textarea is hovered and contains text */
|
||||||
|
.position-relative:hover .clear-input-button.show {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style> {% endcomment %}
|
||||||
<!-- {{ form.media }} -->
|
<!-- {{ form.media }} -->
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<div class="modal-dialog modal-dialog-centered related-finding-modal bg-dark">
|
||||||
|
<div class="modal-content bg-dark text-light border-secondary">
|
||||||
|
<div class="modal-content bg-dark text-light">
|
||||||
|
<h5 class="modal-title">Related findings from case</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<details class="help-text">
|
||||||
|
<summary><i class="bi bi-info-circle"></i> Help</summary>
|
||||||
|
<p>This form is designed to quickly populate the same finding across different series.</p>
|
||||||
|
<p>Cloning a finding from this form will populate the details with a blank viewport (you will need to add visual annotation and set the display as intended).</p>
|
||||||
|
</details>
|
||||||
|
<ul class="list-group list-group-flush bg-dark">
|
||||||
|
{% for finding in findings %}
|
||||||
|
<li class="list-group-item bg-dark text-light border-secondary">
|
||||||
|
<div class="fw-bold mb-1 text-info">{{ finding.title }}</div>
|
||||||
|
{% if finding.description %}
|
||||||
|
Description:
|
||||||
|
<div class="text-muted small mb-1" style="color:#b0b0b0 !important;">{{ finding.description|truncatechars:100 }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if finding.findings.all %}
|
||||||
|
<div class="mb-1">
|
||||||
|
<span class="fw-semibold text-warning">Findings:</span>
|
||||||
|
{% for subfinding in finding.findings.all %}
|
||||||
|
{{ subfinding.get_link }}{% if not forloop.last %}, {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if finding.structures.all %}
|
||||||
|
<div class="mb-1">
|
||||||
|
<span class="fw-semibold text-warning">Structures:</span>
|
||||||
|
{% for structure in finding.structures.all %}
|
||||||
|
{{ structure.get_link }}{% if not forloop.last %}, {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if finding.conditions.all %}
|
||||||
|
<div class="mb-1">
|
||||||
|
<span class="fw-semibold text-warning">Conditions:</span>
|
||||||
|
{% for condition in finding.conditions.all %}
|
||||||
|
{{ condition.get_link }}{% if not forloop.last %}, {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="mt-2">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-primary text-light use-finding-base-btn"
|
||||||
|
type="button"
|
||||||
|
data-finding-id="{{ finding.pk }}"
|
||||||
|
data-finding-description="{{ finding.description|default_if_none:''|escapejs }}"
|
||||||
|
{% if finding.conditions.all %}
|
||||||
|
data-finding-conditions="{% for c in finding.conditions.all %}{{ c.pk }}{% if not forloop.last %},{% endif %}{% endfor %}"
|
||||||
|
data-finding-conditions-names="{% for c in finding.conditions.all %}{{ c }}{% if not forloop.last %}|{% endif %}{% endfor %}"
|
||||||
|
{% endif %}
|
||||||
|
{% if finding.structures.all %}
|
||||||
|
data-finding-structures="{% for s in finding.structures.all %}{{ s.pk }}{% if not forloop.last %},{% endif %}{% endfor %}"
|
||||||
|
data-finding-structures-names="{% for s in finding.structures.all %}{{ s }}{% if not forloop.last %}|{% endif %}{% endfor %}"
|
||||||
|
{% endif %}
|
||||||
|
{% if finding.findings.all %}
|
||||||
|
data-finding-findings="{% for f in finding.findings.all %}{{ f.pk }}{% if not forloop.last %},{% endif %}{% endfor %}"
|
||||||
|
data-finding-findings-names="{% for f in finding.findings.all %}{{ f }}{% if not forloop.last %}|{% endif %}{% endfor %}"
|
||||||
|
{% endif %}
|
||||||
|
>Use as finding base</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.querySelectorAll('.use-finding-base-btn').forEach(function(btn) {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
console.log('Button clicked:', btn);
|
||||||
|
// Example: populate #hidden-form fields with finding data
|
||||||
|
const form = window.parent.document.querySelector('#hidden-form');
|
||||||
|
if (!form) return;
|
||||||
|
|
||||||
|
form.style.display = 'block'; // Show the form if hidden
|
||||||
|
document.querySelector('#add-finding-button').style.display = 'none'; // Hide the button
|
||||||
|
|
||||||
|
|
||||||
|
// Set values (adjust field names as needed)
|
||||||
|
form.querySelector('[name="description"]').value = btn.getAttribute('data-finding-description');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function setSelect2Values(fieldName, dataAttr, namesAttr) {
|
||||||
|
const values = btn.getAttribute(dataAttr);
|
||||||
|
const names = btn.getAttribute(namesAttr);
|
||||||
|
const select = form.querySelector(`[name="${fieldName}"]`);
|
||||||
|
if (values && select) {
|
||||||
|
const valueArr = values.split(',').map(v => v.trim()).filter(Boolean);
|
||||||
|
const nameArr = names ? names.split('|') : [];
|
||||||
|
valueArr.forEach((val, idx) => {
|
||||||
|
if (val && !Array.from(select.options).some(opt => opt.value == val)) {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = val;
|
||||||
|
opt.text = nameArr[idx] || val;
|
||||||
|
opt.selected = true;
|
||||||
|
select.appendChild(opt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$(select).val(valueArr).trigger('change');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage inside your click handler:
|
||||||
|
setSelect2Values('conditions', 'data-finding-conditions', 'data-finding-conditions-names');
|
||||||
|
setSelect2Values('structures', 'data-finding-structures', 'data-finding-structures-names');
|
||||||
|
setSelect2Values('findings', 'data-finding-findings', 'data-finding-findings-names');
|
||||||
|
|
||||||
|
|
||||||
|
// Optionally, close the modal (if using Bootstrap modal JS)
|
||||||
|
const modal = window.parent.document.querySelector('#clone-findings-modal');
|
||||||
|
if (modal) {
|
||||||
|
const modalInstance = bootstrap.Modal.getInstance(modal);
|
||||||
|
if (modalInstance) modalInstance.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -47,6 +47,13 @@
|
|||||||
{% if editing_finding < 1 %}
|
{% if editing_finding < 1 %}
|
||||||
<button id="add-finding-button">Add finding</button>
|
<button id="add-finding-button">Add finding</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<button id="clone-finding-button" title="Click to copy the details of a finding that is already associated with the series/case"
|
||||||
|
hx-get="{% url 'atlas:series_finding_related' series.pk %}"
|
||||||
|
hx-target="#clone-findings-modal"
|
||||||
|
hx-trigger="click"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#clone-findings-modal"
|
||||||
|
>Clone existing finding</button>
|
||||||
<button id="reset-viewport-button">Reset viewport</button>
|
<button id="reset-viewport-button">Reset viewport</button>
|
||||||
<div id="finding-form">
|
<div id="finding-form">
|
||||||
<div class="hide" id="hidden-form">
|
<div class="hide" id="hidden-form">
|
||||||
@@ -182,6 +189,16 @@
|
|||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<div id="clone-findings-modal"
|
||||||
|
class="modal modal-blur fade"
|
||||||
|
style="display: none"
|
||||||
|
aria-hidden="false"
|
||||||
|
tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered related-finding-modal" style="max-width: 700px;">
|
||||||
|
<div class="modal-content"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
|||||||
@@ -324,6 +324,7 @@ urlpatterns = [
|
|||||||
# TODO: case context series viewing (so that we can view series in the context of a case)
|
# TODO: case context series viewing (so that we can view series in the context of a case)
|
||||||
path("series/<int:pk>", views.series_detail, name="series_detail"),
|
path("series/<int:pk>", views.series_detail, name="series_detail"),
|
||||||
path("series/<int:pk>/authors", views.SeriesAuthorUpdate.as_view(), name="series_authors"),
|
path("series/<int:pk>/authors", views.SeriesAuthorUpdate.as_view(), name="series_authors"),
|
||||||
|
path("series/<int:series_id>/finding/related", views.series_finding_related, name="series_finding_related"),
|
||||||
path("series/", views.SeriesView.as_view(), name="series_view"),
|
path("series/", views.SeriesView.as_view(), name="series_view"),
|
||||||
path("series_image/<int:pk>/dicom", views.series_image_dicom, name="series_image_dicom"),
|
path("series_image/<int:pk>/dicom", views.series_image_dicom, name="series_image_dicom"),
|
||||||
path(
|
path(
|
||||||
|
|||||||
+8
-2
@@ -1148,7 +1148,7 @@ def add_collection_to_case_form(request, case_id):
|
|||||||
|
|
||||||
|
|
||||||
class AtlasClone(AtlasCreateBase, AuthorOrCheckerRequiredMixin, CreateView):
|
class AtlasClone(AtlasCreateBase, AuthorOrCheckerRequiredMixin, CreateView):
|
||||||
"""Clones a existing atlas"""
|
"""Clones a existing atlas case"""
|
||||||
|
|
||||||
# fields = '__all__'
|
# fields = '__all__'
|
||||||
# #fields = [ 'condition' ]
|
# #fields = [ 'condition' ]
|
||||||
@@ -1169,7 +1169,7 @@ class AtlasClone(AtlasCreateBase, AuthorOrCheckerRequiredMixin, CreateView):
|
|||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
initial_data = model_to_dict(old_object, exclude=["id", "previous_case"])
|
initial_data = model_to_dict(old_object, exclude=["id", "previous_case", "history", "discussion", "report"])
|
||||||
return initial_data
|
return initial_data
|
||||||
|
|
||||||
|
|
||||||
@@ -3140,3 +3140,9 @@ def remove_series_from_case(request, series_pk, case_pk):
|
|||||||
def series_finding_details(request, finding_id):
|
def series_finding_details(request, finding_id):
|
||||||
finding = get_object_or_404(SeriesFinding, pk=finding_id)
|
finding = get_object_or_404(SeriesFinding, pk=finding_id)
|
||||||
return render(request, 'atlas/series_finding_details.html', {'finding': finding})
|
return render(request, 'atlas/series_finding_details.html', {'finding': finding})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def series_finding_related(request, series_id):
|
||||||
|
series = get_object_or_404(Series, pk=series_id)
|
||||||
|
findings = series.get_related_findings()
|
||||||
|
return render(request, 'atlas/series_finding_related.html', {'series': series, 'findings': findings})
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<div class="position-relative">
|
||||||
|
{% include "django/forms/widgets/textarea.html" %}
|
||||||
|
<button type="button" class="btn btn-danger btn-sm clear-input-button" style="position: absolute; right: 10px; top: 50%; transform: translateY(-50%); display: none;">×</button>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<div class="position-relative">
|
||||||
|
{% include "django/forms/widgets/input.html" %}
|
||||||
|
<button type="button" class="btn btn-danger btn-sm clear-input-button" style="position: absolute; right: 10px; top: 50%; transform: translateY(-50%); display: none;">×</button>
|
||||||
|
</div>
|
||||||
@@ -307,6 +307,10 @@ ADMINS = [("Ross","ross@xkjq.uk")]
|
|||||||
|
|
||||||
CRISPY_ALLOWED_TEMPLATE_PACKS = ('bootstrap5',)
|
CRISPY_ALLOWED_TEMPLATE_PACKS = ('bootstrap5',)
|
||||||
CRISPY_TEMPLATE_PACK = 'bootstrap5'
|
CRISPY_TEMPLATE_PACK = 'bootstrap5'
|
||||||
|
CRISPY_CLASS_CONVERTERS = {
|
||||||
|
"textinput": "clearable_input", # Use clearable_input.html for TextInput
|
||||||
|
"textarea": "clearable_input", # Use clearable_input.html for Textarea
|
||||||
|
}
|
||||||
|
|
||||||
DEBUG_CONTAINER = False
|
DEBUG_CONTAINER = False
|
||||||
|
|
||||||
|
|||||||
@@ -1356,3 +1356,35 @@ span#user-id {
|
|||||||
.remove-button:hover {
|
.remove-button:hover {
|
||||||
opacity: 100%;
|
opacity: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Crispy forms clear button */
|
||||||
|
.position-relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the clear button */
|
||||||
|
.clear-input-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
display: none; /* Hidden by default */
|
||||||
|
z-index: 1;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-input-button:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show the clear button only when the input field or textarea is hovered and contains text */
|
||||||
|
.position-relative:hover .clear-input-button {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-finding-modal {
|
||||||
|
max-width: 600px;
|
||||||
|
width: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user