Add merge category functionality for admin users with confirmation prompt
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
{% extends 'sbas/base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Merge Category</h2>
|
||||||
|
|
||||||
|
{% if error %}
|
||||||
|
<div class="alert alert-danger">{{ error }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if success %}
|
||||||
|
<div class="alert alert-success">{{ success }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<p>This admin tool will move all questions assigned to the <strong>Source</strong> category into the <strong>Target</strong> category. Optionally you may delete the Source category after the move.</p>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="id_source" class="form-label">Source category (to be merged)</label>
|
||||||
|
<select name="source" id="id_source" class="form-select">
|
||||||
|
<option value="">-- select source --</option>
|
||||||
|
{% for c in categories %}
|
||||||
|
<option value="{{ c.pk }}">{{ c }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="id_target" class="form-label">Target category (destination)</label>
|
||||||
|
<select name="target" id="id_target" class="form-select">
|
||||||
|
<option value="">-- select target --</option>
|
||||||
|
{% for c in categories %}
|
||||||
|
<option value="{{ c.pk }}">{{ c }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" value="on" id="id_delete_source" name="delete_source">
|
||||||
|
<label class="form-check-label" for="id_delete_source">
|
||||||
|
Delete source category after merge
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-danger" onclick="return confirm('Are you sure you want to move all questions from the source to the target category? This action cannot be undone.');">Merge categories</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -110,6 +110,7 @@ urlpatterns = [
|
|||||||
views.generate_exam,
|
views.generate_exam,
|
||||||
name="generate_exam",
|
name="generate_exam",
|
||||||
),
|
),
|
||||||
|
path("category/merge/", views.merge_category, name="merge_category"),
|
||||||
#path(
|
#path(
|
||||||
# "exam/<int:pk>/scores/<int:cid>/<str:passcode>/",
|
# "exam/<int:pk>/scores/<int:cid>/<str:passcode>/",
|
||||||
# views.exam_scores_cid_user,
|
# views.exam_scores_cid_user,
|
||||||
|
|||||||
@@ -1012,6 +1012,59 @@ def import_llm_questions(request):
|
|||||||
return JsonResponse({"ok": True, "report": report})
|
return JsonResponse({"ok": True, "report": report})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@user_passes_test(lambda u: u.is_superuser)
|
||||||
|
@require_http_methods(["GET", "POST"])
|
||||||
|
def merge_category(request):
|
||||||
|
"""Admin view: merge one Category into another.
|
||||||
|
|
||||||
|
GET: render form to choose source and target categories.
|
||||||
|
POST: expects 'source' and 'target' (category PKs) and optional 'delete_source' checkbox.
|
||||||
|
The view will reassign all Question.category==source -> target and optionally delete the source Category.
|
||||||
|
"""
|
||||||
|
categories = Category.objects.all().order_by('category')
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render(request, 'sbas/merge_category.html', {'categories': categories})
|
||||||
|
|
||||||
|
# POST
|
||||||
|
source_id = request.POST.get('source')
|
||||||
|
target_id = request.POST.get('target')
|
||||||
|
delete_source = request.POST.get('delete_source') in ('on', 'true', '1', 'True')
|
||||||
|
|
||||||
|
if not source_id or not target_id:
|
||||||
|
return render(request, 'sbas/merge_category.html', {'categories': categories, 'error': 'Both source and target categories must be selected.'})
|
||||||
|
|
||||||
|
try:
|
||||||
|
source = Category.objects.get(pk=int(source_id))
|
||||||
|
target = Category.objects.get(pk=int(target_id))
|
||||||
|
except Category.DoesNotExist:
|
||||||
|
return render(request, 'sbas/merge_category.html', {'categories': categories, 'error': 'Invalid category selected.'})
|
||||||
|
|
||||||
|
if source.pk == target.pk:
|
||||||
|
return render(request, 'sbas/merge_category.html', {'categories': categories, 'error': 'Source and target must be different.'})
|
||||||
|
|
||||||
|
# perform update inside a transaction
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
qs = Question.objects.filter(category=source)
|
||||||
|
count = qs.count()
|
||||||
|
qs.update(category=target)
|
||||||
|
|
||||||
|
deleted = False
|
||||||
|
if delete_source:
|
||||||
|
# safe to delete now (no FK references from Question)
|
||||||
|
source.delete()
|
||||||
|
deleted = True
|
||||||
|
|
||||||
|
msg = f"Moved {count} question{'s' if count != 1 else ''} from '{source}' to '{target}'."
|
||||||
|
if deleted:
|
||||||
|
msg += f" Deleted source category '{source}'."
|
||||||
|
|
||||||
|
return render(request, 'sbas/merge_category.html', {'categories': Category.objects.all().order_by('category'), 'success': msg})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@user_passes_test(lambda u: u.is_superuser)
|
@user_passes_test(lambda u: u.is_superuser)
|
||||||
@require_http_methods(["POST"])
|
@require_http_methods(["POST"])
|
||||||
|
|||||||
Reference in New Issue
Block a user