Enhance bulk delete functionality to support multiple checkbox naming conventions and improve debug information display when no items are deleted.
This commit is contained in:
@@ -2,3 +2,12 @@
|
|||||||
{% if errors %}
|
{% if errors %}
|
||||||
<div class="alert alert-warning">Errors: {{ errors }}</div>
|
<div class="alert alert-warning">Errors: {{ errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if deleted == 0 and debug_post %}
|
||||||
|
<details>
|
||||||
|
<summary>Debug: POST data received</summary>
|
||||||
|
<pre>{{ debug_post|pprint }}</pre>
|
||||||
|
<p>Computed ids: {{ debug_ids }}</p>
|
||||||
|
<p>App: {{ debug_app }}</p>
|
||||||
|
</details>
|
||||||
|
{% endif %}
|
||||||
|
|||||||
+25
-3
@@ -161,19 +161,34 @@ def bulk_delete_questions(request):
|
|||||||
return HttpResponse(status=405)
|
return HttpResponse(status=405)
|
||||||
|
|
||||||
app = request.POST.get("app")
|
app = request.POST.get("app")
|
||||||
selected_list = request.POST.getlist("selected")
|
|
||||||
|
# Accept different submission patterns: checkboxes may be sent as 'selection' or 'selected'
|
||||||
|
selected_list = []
|
||||||
|
# common variants
|
||||||
|
for key in ("selected", "selection", "selected[]", "selection[]"):
|
||||||
|
vals = request.POST.getlist(key)
|
||||||
|
if vals:
|
||||||
|
# extend list; we'll parse ints below
|
||||||
|
selected_list.extend(vals)
|
||||||
|
|
||||||
|
# Also accept a raw CSV string under 'selected'
|
||||||
selected_raw = request.POST.get("selected")
|
selected_raw = request.POST.get("selected")
|
||||||
|
|
||||||
ids = set()
|
ids = set()
|
||||||
if selected_list:
|
if selected_list:
|
||||||
for s in selected_list:
|
for s in selected_list:
|
||||||
|
# some checkbox libraries may give empty strings or 'on' for checked boxes
|
||||||
try:
|
try:
|
||||||
ids.add(int(s))
|
ids.add(int(s))
|
||||||
except Exception:
|
except Exception:
|
||||||
|
# ignore non-int vals
|
||||||
continue
|
continue
|
||||||
elif selected_raw:
|
elif selected_raw:
|
||||||
for part in selected_raw.split(','):
|
for part in selected_raw.split(','):
|
||||||
try:
|
try:
|
||||||
|
part = part.strip()
|
||||||
|
if not part:
|
||||||
|
continue
|
||||||
ids.add(int(part))
|
ids.add(int(part))
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
@@ -196,6 +211,8 @@ def bulk_delete_questions(request):
|
|||||||
|
|
||||||
deleted = 0
|
deleted = 0
|
||||||
errors = []
|
errors = []
|
||||||
|
# For debugging: capture what was received
|
||||||
|
received_post = {k: request.POST.getlist(k) for k in request.POST.keys()}
|
||||||
try:
|
try:
|
||||||
qs = Model.objects.filter(pk__in=list(ids))
|
qs = Model.objects.filter(pk__in=list(ids))
|
||||||
deleted = qs.count()
|
deleted = qs.count()
|
||||||
@@ -204,8 +221,13 @@ def bulk_delete_questions(request):
|
|||||||
logger.exception("bulk_delete failed for app=%s ids=%s", app, ids)
|
logger.exception("bulk_delete failed for app=%s ids=%s", app, ids)
|
||||||
return JsonResponse({"ok": False, "error": str(e)}, status=500)
|
return JsonResponse({"ok": False, "error": str(e)}, status=500)
|
||||||
|
|
||||||
# Render a small fragment
|
# Render a small fragment. Include debug info when nothing was deleted to aid diagnosis.
|
||||||
return render(request, "generic/partials/bulk_delete_result.html", {"deleted": deleted, "errors": errors})
|
context = {"deleted": deleted, "errors": errors}
|
||||||
|
if deleted == 0:
|
||||||
|
context["debug_post"] = received_post
|
||||||
|
context["debug_ids"] = sorted(list(ids))
|
||||||
|
context["debug_app"] = app
|
||||||
|
return render(request, "generic/partials/bulk_delete_result.html", context)
|
||||||
|
|
||||||
|
|
||||||
class RedirectMixin:
|
class RedirectMixin:
|
||||||
|
|||||||
@@ -18,9 +18,9 @@
|
|||||||
</form>
|
</form>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
<form id="bulk-delete-form" method="post" action="{% url 'generic:bulk_delete_questions' %}" hx-post="{% url 'generic:bulk_delete_questions' %}" hx-target="#bulk-delete-result" hx-swap="innerHTML">
|
||||||
{% render_table table %}
|
{% render_table table %}
|
||||||
|
|
||||||
<form id="bulk-delete-form" method="post" action="{% url app_name|add:':bulk_delete_questions' %}" hx-post="{% url app_name|add:':bulk_delete_questions' %}" hx-target="#bulk-delete-result" hx-swap="innerHTML">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="app" value="{{ app_name }}">
|
<input type="hidden" name="app" value="{{ app_name }}">
|
||||||
<input type="hidden" name="selected" id="bulk-selected-input" value="">
|
<input type="hidden" name="selected" id="bulk-selected-input" value="">
|
||||||
@@ -33,10 +33,6 @@
|
|||||||
data-exam_list_url="{% url 'api-1:'|add:app_name|add:'_user_exams' %}"
|
data-exam_list_url="{% url 'api-1:'|add:app_name|add:'_user_exams' %}"
|
||||||
data-type={{app_name}} data-csrf="{{ csrf_token}}">Add selected questions to
|
data-type={{app_name}} data-csrf="{{ csrf_token}}">Add selected questions to
|
||||||
exam</button>
|
exam</button>
|
||||||
<button id="button-select-remove-questions"
|
|
||||||
data-question_bulk_delete_url="{% url app_name|add:':bulk_delete_questions' %}"
|
|
||||||
data-csrf="{{ csrf_token}}">Delete selected questions</button>
|
|
||||||
<div id="exam-options"></div>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@@ -45,11 +41,35 @@
|
|||||||
// When the user submits the bulk-delete form, gather selected checkboxes from the table
|
// When the user submits the bulk-delete form, gather selected checkboxes from the table
|
||||||
document.getElementById('bulk-delete-form').addEventListener('submit', function(e){
|
document.getElementById('bulk-delete-form').addEventListener('submit', function(e){
|
||||||
const selected = [];
|
const selected = [];
|
||||||
document.querySelectorAll('input[type=checkbox][name="selection"]:checked, input[type=checkbox][name="selection[]"]:checked, input[type=checkbox][name="selection\[\]"]:checked').forEach(function(cb){
|
// Try common patterns: name="selection", name="selection[]", name="selection-<pk>", data-pk, or any checked checkbox inside the table
|
||||||
// many tables render checkbox inputs with value attribute as pk
|
// 1) explicit selection name
|
||||||
|
document.querySelectorAll('input[type=checkbox][name="selection"]:checked, input[type=checkbox][name="selection[]"]:checked').forEach(function(cb){
|
||||||
if(cb.value) selected.push(cb.value);
|
if(cb.value) selected.push(cb.value);
|
||||||
else if(cb.dataset && cb.dataset.pk) selected.push(cb.dataset.pk);
|
else if(cb.dataset && cb.dataset.pk) selected.push(cb.dataset.pk);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 2) inputs with name like selection-<pk> or selection_<pk>
|
||||||
|
document.querySelectorAll('input[type=checkbox]').forEach(function(cb){
|
||||||
|
if(!cb.checked) return;
|
||||||
|
const n = cb.getAttribute('name') || '';
|
||||||
|
if(n.startsWith('selection-') || n.startsWith('selection_')){
|
||||||
|
// try extract trailing number
|
||||||
|
const m = n.match(/selection[-_](\d+)/);
|
||||||
|
if(m && m[1]){
|
||||||
|
selected.push(m[1]);
|
||||||
|
} else if(cb.value){
|
||||||
|
selected.push(cb.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3) fallback: any checked checkbox inside the rendered table
|
||||||
|
if(cb.closest('table') && cb.checked && cb.value){
|
||||||
|
selected.push(cb.value);
|
||||||
|
}
|
||||||
|
// 4) data-pk attribute
|
||||||
|
if(cb.checked && cb.dataset && cb.dataset.pk){
|
||||||
|
selected.push(cb.dataset.pk);
|
||||||
|
}
|
||||||
|
});
|
||||||
// fallback: django-tables2 renders checkboxes with accessor name 'selection' and value
|
// fallback: django-tables2 renders checkboxes with accessor name 'selection' and value
|
||||||
if(selected.length === 0){
|
if(selected.length === 0){
|
||||||
// try to find inputs inside table
|
// try to find inputs inside table
|
||||||
@@ -57,7 +77,10 @@
|
|||||||
if(cb.checked && cb.value) selected.push(cb.value);
|
if(cb.checked && cb.value) selected.push(cb.value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
document.getElementById('bulk-selected-input').value = selected.join(',');
|
// dedupe
|
||||||
|
const uniq = Array.from(new Set(selected.map(String)));
|
||||||
|
console.log('Bulk delete selected ids:', uniq);
|
||||||
|
document.getElementById('bulk-selected-input').value = uniq.join(',');
|
||||||
// allow HTMX to submit
|
// allow HTMX to submit
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user