Refactor case import functionality and enhance logging in views
This commit is contained in:
@@ -190,82 +190,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Handle selecting a case from the modal results to import selected series into it
|
|
||||||
document.body.addEventListener('click', async function (e) {
|
|
||||||
// Only handle clicks inside the case select modal results, but ignore clicks on interactive controls inside the item
|
|
||||||
const anchor = e.target.closest('#case-search-results-modal .list-group-item, #case-search-results-modal .list-group-item-action');
|
|
||||||
if (!anchor) return;
|
|
||||||
if (e.target.closest('#case-search-results-modal .list-group-item a, #case-search-results-modal .list-group-item button, #case-search-results-modal .list-group-item form, #case-search-results-modal .list-group-item input')) {
|
|
||||||
// Let buttons/links/forms behave normally (e.g. View link)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
// Determine case pk: prefer data-case-pk, else parse from href
|
|
||||||
let casePk = anchor.getAttribute('data-case-pk');
|
|
||||||
if (!casePk) {
|
|
||||||
const href = anchor.getAttribute('href') || '';
|
|
||||||
const m = href.match(/case\/(\d+)\/?$/);
|
|
||||||
if (m) casePk = m[1];
|
|
||||||
}
|
|
||||||
if (!casePk) {
|
|
||||||
console.error('Could not determine case id for import');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a display title for confirmation
|
|
||||||
let caseTitle = anchor.querySelector('h6') ? anchor.querySelector('h6').textContent.trim() : null;
|
|
||||||
if (!caseTitle) caseTitle = `#${casePk}`;
|
|
||||||
|
|
||||||
// Confirm with the user before importing
|
|
||||||
const checkboxes = Array.from(document.querySelectorAll('input[name="selection"]:checked'));
|
|
||||||
const allCheckboxes = Array.from(document.querySelectorAll('input[name="selection"]'));
|
|
||||||
const selectable = allCheckboxes.filter(cb => !cb.disabled).map(cb => cb.value);
|
|
||||||
const selectionList = checkboxes.length ? checkboxes.map(cb => cb.value) : selectable;
|
|
||||||
if (!selectionList.length) {
|
|
||||||
alert('Please select at least one series to import into the case.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const confirmMsg = `Import ${selectionList.length} series into case "${caseTitle}" (ID ${casePk})?`;
|
|
||||||
if (!window.confirm(confirmMsg)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build import URL template and replace trailing 0 with casePk
|
|
||||||
const importTemplate = "{% url 'api-1:import_dicoms_case' 0 %}";
|
|
||||||
const importUrl = importTemplate.replace(/0\/?$/, casePk + '/');
|
|
||||||
|
|
||||||
const form = new URLSearchParams();
|
|
||||||
for (const s of selectionList) form.append('selection', s);
|
|
||||||
const orderSeriesInput = document.querySelector('input[name="order-series"]:checked');
|
|
||||||
if (orderSeriesInput) form.append('order-series', orderSeriesInput.value);
|
|
||||||
|
|
||||||
const csrfToken = '{{ csrf_token }}';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resp = await fetch(importUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'X-CSRFToken': csrfToken,
|
|
||||||
},
|
|
||||||
body: form.toString(),
|
|
||||||
credentials: 'same-origin'
|
|
||||||
});
|
|
||||||
const text = await resp.text();
|
|
||||||
// Close modal and show result in import-status
|
|
||||||
const modalEl = document.getElementById('caseSelectModal');
|
|
||||||
if (modalEl && window.bootstrap && bootstrap.Modal) {
|
|
||||||
bootstrap.Modal.getInstance(modalEl)?.hide();
|
|
||||||
}
|
|
||||||
const statusDiv = document.getElementById('import-status');
|
|
||||||
if (statusDiv) statusDiv.innerHTML = `<div>Imported into case ${casePk}: ${text}</div>`;
|
|
||||||
// Optionally reload to reflect imported state
|
|
||||||
setTimeout(function(){ location.reload(); }, 1200);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Import into case failed', err);
|
|
||||||
alert('Import failed — check console for details.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Update the selected count shown beside action buttons
|
// Update the selected count shown beside action buttons
|
||||||
function updateSelectionCount() {
|
function updateSelectionCount() {
|
||||||
const all = Array.from(document.querySelectorAll('input[name="selection"]'));
|
const all = Array.from(document.querySelectorAll('input[name="selection"]'));
|
||||||
@@ -314,7 +238,7 @@
|
|||||||
|
|
||||||
const importButton = document.getElementById('import-dicoms-sequential-button');
|
const importButton = document.getElementById('import-dicoms-sequential-button');
|
||||||
if (importButton) {
|
if (importButton) {
|
||||||
document.getElementById('import-dicoms-sequential-button').addEventListener('click', async function() {
|
importButton.addEventListener('click', async function() {
|
||||||
const checkboxes = Array.from(document.querySelectorAll('input[name="selection"]:checked'));
|
const checkboxes = Array.from(document.querySelectorAll('input[name="selection"]:checked'));
|
||||||
const allCheckboxes = Array.from(document.querySelectorAll('input[name="selection"]'));
|
const allCheckboxes = Array.from(document.querySelectorAll('input[name="selection"]'));
|
||||||
// Only include checkboxes that are not disabled (i.e., not already imported)
|
// Only include checkboxes that are not disabled (i.e., not already imported)
|
||||||
@@ -322,7 +246,60 @@
|
|||||||
const toImport = checkboxes.length
|
const toImport = checkboxes.length
|
||||||
? checkboxes.filter(cb => !cb.disabled)
|
? checkboxes.filter(cb => !cb.disabled)
|
||||||
: selectableCheckboxes;
|
: selectableCheckboxes;
|
||||||
<script>
|
if (toImport.length === 0) {
|
||||||
|
alert("Please select at least one series to import (not already imported).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const statusDiv = document.getElementById('import-status');
|
||||||
|
if (statusDiv) statusDiv.innerHTML = '';
|
||||||
|
const orderSeries = (document.querySelector('input[name="order-series"]:checked') || {}).value || '';
|
||||||
|
const csrfToken = '{{ csrf_token }}';
|
||||||
|
{% if case %}
|
||||||
|
const importUrl = "{% url 'api-1:import_dicoms_case' case.id %}";
|
||||||
|
{% else %}
|
||||||
|
const importUrl = "{% url 'api-1:import_dicoms' %}";
|
||||||
|
{% endif %}
|
||||||
|
for (let i = 0; i < toImport.length; i++) {
|
||||||
|
const seriesId = toImport[i].value;
|
||||||
|
statusDiv.innerHTML += `<div id="import-status-${seriesId}">Importing series ${seriesId}...</div>`;
|
||||||
|
try {
|
||||||
|
const response = await fetch(importUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
|
},
|
||||||
|
body: `selection=${encodeURIComponent(seriesId)}&order-series=${encodeURIComponent(orderSeries)}`
|
||||||
|
});
|
||||||
|
const text = await response.text();
|
||||||
|
document.getElementById(`import-status-${seriesId}`).innerHTML = `Series ${seriesId}: ${text}`;
|
||||||
|
// Mark as imported in the UI and make unselectable
|
||||||
|
const li = document.querySelector(`#series-list input[value="${seriesId}"]`)?.closest('li');
|
||||||
|
if (li && !li.classList.contains('imported')) {
|
||||||
|
li.classList.add('imported');
|
||||||
|
if (!li.querySelector('.badge.bg-success')) {
|
||||||
|
const badge = document.createElement('span');
|
||||||
|
badge.className = 'badge bg-success ms-2';
|
||||||
|
badge.textContent = 'Imported';
|
||||||
|
li.appendChild(badge);
|
||||||
|
}
|
||||||
|
const checkbox = li.querySelector('input[name="selection"]');
|
||||||
|
if (checkbox) {
|
||||||
|
checkbox.disabled = true;
|
||||||
|
checkbox.checked = false;
|
||||||
|
}
|
||||||
|
li.querySelector('span')?.onclick = null;
|
||||||
|
li.style.pointerEvents = "none";
|
||||||
|
li.style.opacity = "0.7";
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
document.getElementById(`import-status-${seriesId}`).innerHTML = `Series ${seriesId}: <span style="color:red;">Failed</span>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
statusDiv.innerHTML += "<div>All done.</div>";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Listen for `case:selected` events dispatched by the case-search widget and perform the import
|
// Listen for `case:selected` events dispatched by the case-search widget and perform the import
|
||||||
document.body.addEventListener('case:selected', async function (e) {
|
document.body.addEventListener('case:selected', async function (e) {
|
||||||
try {
|
try {
|
||||||
@@ -379,20 +356,6 @@
|
|||||||
alert('Import failed — check console for details.');
|
alert('Import failed — check console for details.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
|
||||||
if (!detail) return;
|
|
||||||
const inputs = detail.querySelectorAll('input[name="selection"]');
|
|
||||||
inputs.forEach(cb => {
|
|
||||||
if (!cb.disabled) {
|
|
||||||
cb.checked = false;
|
|
||||||
const li = cb.closest('li');
|
|
||||||
if (li) li.classList.remove('selected');
|
|
||||||
cb.dispatchEvent(new Event('change', { bubbles: true }));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (typeof updateSelectionCount === 'function') updateSelectionCount();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Row click toggles selection (avoid when clicking interactive elements inside)
|
// Row click toggles selection (avoid when clicking interactive elements inside)
|
||||||
document.body.addEventListener('click', function (e) {
|
document.body.addEventListener('click', function (e) {
|
||||||
|
|||||||
+4
-2
@@ -982,6 +982,7 @@ def case_inline_field(request, pk, field_name):
|
|||||||
return HttpResponse(html)
|
return HttpResponse(html)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@require_POST
|
@require_POST
|
||||||
def toggle_case_normal(request, pk):
|
def toggle_case_normal(request, pk):
|
||||||
@@ -1170,6 +1171,7 @@ def case_normal_form(request, pk):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Log the rendered form HTML so we can verify inputs/names reach the client
|
# Log the rendered form HTML so we can verify inputs/names reach the client
|
||||||
|
from loguru import logger
|
||||||
try:
|
try:
|
||||||
form_html = form.as_p()
|
form_html = form.as_p()
|
||||||
logger.debug("case_normal_form rendered form HTML: {}", form_html)
|
logger.debug("case_normal_form rendered form HTML: {}", form_html)
|
||||||
@@ -1192,6 +1194,7 @@ def create_case_normal(request, pk):
|
|||||||
return HttpResponse(status=403)
|
return HttpResponse(status=403)
|
||||||
|
|
||||||
from .forms import NormalCaseForm
|
from .forms import NormalCaseForm
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
form = NormalCaseForm(request.POST)
|
form = NormalCaseForm(request.POST)
|
||||||
logger.debug("create_case_normal POST data: {}", dict(request.POST))
|
logger.debug("create_case_normal POST data: {}", dict(request.POST))
|
||||||
@@ -1972,11 +1975,10 @@ def case_search(request):
|
|||||||
|
|
||||||
cases = cases_qs.order_by("title")[:30]
|
cases = cases_qs.order_by("title")[:30]
|
||||||
|
|
||||||
multi = request.GET.get('multi') in ('1', 'true', 'True')
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"atlas/partials/case_search_results.html",
|
"atlas/partials/case_search_results.html",
|
||||||
{"cases": cases, "collection": collection, "recent_cases": recent_cases, "q": q, "multi_select": multi},
|
{"cases": cases, "collection": collection, "recent_cases": recent_cases, "q": q},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user