diff --git a/generic/urls.py b/generic/urls.py index 77cf91fb..444b8e92 100755 --- a/generic/urls.py +++ b/generic/urls.py @@ -39,6 +39,11 @@ urlpatterns = [ views.generic_exam_htmx, name="generic_exam_htmx", ), + path( + "generic_exam_set_open_access", + views.generic_exam_set_open_access, + name="generic_exam_set_open_access", + ), path("bulk_delete_questions/", views.bulk_delete_questions, name="bulk_delete_questions"), path( "examination-autocomplete", diff --git a/generic/views.py b/generic/views.py index f7f73b14..cb43c3e8 100644 --- a/generic/views.py +++ b/generic/views.py @@ -635,6 +635,71 @@ def generic_exam_htmx(request): return HttpResponse(status=405) +@login_required +def generic_exam_set_open_access(request): + """HTMX endpoint to set open_access on questions in an exam. + + POST params: + - type: app name (sbas, rapids, anatomy, physics, shorts, longs) + - exam_id: id of the exam + - set_open_access: 'true' or 'false' + - selection / selection[]: optional list of question ids to apply to (if omitted apply to all) + + Only questions that request.user can edit (question.can_edit(user)) or superusers will be modified. + Returns a small HTML fragment summarising the count of changed questions. + """ + if request.method != "POST": + return HttpResponse(status=405) + + question_type = request.POST.get("type") + if not question_type: + return JsonResponse({"status": "error", "message": "missing type"}, status=400) + + try: + Exam = get_exam_model_from_app_name(question_type) + except Exception: + return JsonResponse({"status": "error", "message": "unknown type"}, status=400) + + exam = get_object_or_404(Exam, pk=request.POST.get("exam_id")) + + # permission to edit exam required + # Many exam edits are limited to exam authors / checkers; reuse existing check where possible + # For now require the user to have edit access (ExamViews.check_user_edit_access equivalent) + # We'll do a lightweight check: if user is superuser OR user in exam.get_author_objects() + if not (request.user.is_superuser or request.user in exam.get_author_objects()): + return HttpResponse(format_html('
Permission denied
')) + + set_val = True if request.POST.get("set_open_access") == "true" else False + + sel = request.POST.getlist("selection") or request.POST.getlist("selection[]") + if sel: + try: + ids = [int(s) for s in sel] + except Exception: + ids = [] + qs = exam.exam_questions.filter(pk__in=ids) + else: + qs = exam.exam_questions.all() + + changed = 0 + for q in qs: + try: + # prefer per-question permission check if available + can_edit = request.user.is_superuser or getattr(q, 'can_edit', lambda u: False)(request.user) + except Exception: + can_edit = request.user.is_superuser + + if not can_edit: + continue + + if q.open_access != set_val: + q.open_access = set_val + q.save() + changed += 1 + + return render(request, "generic/partials/set_open_access_result.html", {"changed": changed, "exam": exam, "state": set_val}) + + class ExamViews(View, LoginRequiredMixin): def __init__( self, diff --git a/templates/generic/partials/set_open_access_result.html b/templates/generic/partials/set_open_access_result.html new file mode 100644 index 00000000..d64503b3 --- /dev/null +++ b/templates/generic/partials/set_open_access_result.html @@ -0,0 +1,10 @@ +
+ {% if changed %} + Updated {{ changed }} question{{ changed|pluralize }} in "{{ exam.name }}". Open access set to {{ state|yesno:"true,false" }}. + {% else %} + No questions were updated. + {% endif %} +
+ +
+