diff --git a/generic/templates/generic/partials/bulk_delete_result.html b/generic/templates/generic/partials/bulk_delete_result.html index 9ff82ee7..20d82742 100644 --- a/generic/templates/generic/partials/bulk_delete_result.html +++ b/generic/templates/generic/partials/bulk_delete_result.html @@ -2,3 +2,12 @@ {% if errors %}
Errors: {{ errors }}
{% endif %} + +{% if deleted == 0 and debug_post %} +
+ Debug: POST data received +
{{ debug_post|pprint }}
+

Computed ids: {{ debug_ids }}

+

App: {{ debug_app }}

+
+{% endif %} diff --git a/generic/views.py b/generic/views.py index 78506bc4..e504e835 100644 --- a/generic/views.py +++ b/generic/views.py @@ -161,19 +161,34 @@ def bulk_delete_questions(request): return HttpResponse(status=405) 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") ids = set() if selected_list: for s in selected_list: + # some checkbox libraries may give empty strings or 'on' for checked boxes try: ids.add(int(s)) except Exception: + # ignore non-int vals continue elif selected_raw: for part in selected_raw.split(','): try: + part = part.strip() + if not part: + continue ids.add(int(part)) except Exception: continue @@ -196,6 +211,8 @@ def bulk_delete_questions(request): deleted = 0 errors = [] + # For debugging: capture what was received + received_post = {k: request.POST.getlist(k) for k in request.POST.keys()} try: qs = Model.objects.filter(pk__in=list(ids)) deleted = qs.count() @@ -204,8 +221,13 @@ def bulk_delete_questions(request): logger.exception("bulk_delete failed for app=%s ids=%s", app, ids) return JsonResponse({"ok": False, "error": str(e)}, status=500) - # Render a small fragment - return render(request, "generic/partials/bulk_delete_result.html", {"deleted": deleted, "errors": errors}) + # Render a small fragment. Include debug info when nothing was deleted to aid diagnosis. + 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: diff --git a/templates/question_table_view.html b/templates/question_table_view.html index 6f45d0b4..7f69d021 100644 --- a/templates/question_table_view.html +++ b/templates/question_table_view.html @@ -18,9 +18,9 @@ - {% render_table table %} +
+ {% render_table table %} - {% csrf_token %} @@ -33,10 +33,6 @@ 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 exam - -
{% endblock %} @@ -45,11 +41,35 @@ // When the user submits the bulk-delete form, gather selected checkboxes from the table document.getElementById('bulk-delete-form').addEventListener('submit', function(e){ 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){ - // many tables render checkbox inputs with value attribute as pk + // Try common patterns: name="selection", name="selection[]", name="selection-", data-pk, or any checked checkbox inside the table + // 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); else if(cb.dataset && cb.dataset.pk) selected.push(cb.dataset.pk); }); + + // 2) inputs with name like selection- or selection_ + 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 if(selected.length === 0){ // try to find inputs inside table @@ -57,7 +77,10 @@ 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 });