diff --git a/sbas/templates/sbas/base.html b/sbas/templates/sbas/base.html index f08cd894..12e426ff 100644 --- a/sbas/templates/sbas/base.html +++ b/sbas/templates/sbas/base.html @@ -24,6 +24,7 @@ diff --git a/sbas/templates/sbas/generate_exam.html b/sbas/templates/sbas/generate_exam.html new file mode 100644 index 00000000..0cd441ff --- /dev/null +++ b/sbas/templates/sbas/generate_exam.html @@ -0,0 +1,46 @@ +{% extends 'sbas/base.html' %} + +{% block title %}Generate Exam{% endblock %} + +{% block content %} +
+

Generate Personal SBA Exam

+

Choose filters and provide a name for your exam. The created exam will be active and available for you to take.

+ +
+ {% csrf_token %} +
+ + +
+ +
+ + +
+ +
+

Only questions with an "Accepted" review will be used to build the exam.

+
+ +
+ + +
+ +
+ + +
+ + +
+
+{% endblock %} diff --git a/sbas/templates/sbas/question_review_start.html b/sbas/templates/sbas/question_review_start.html index f9f4f4be..e7bb0fbd 100644 --- a/sbas/templates/sbas/question_review_start.html +++ b/sbas/templates/sbas/question_review_start.html @@ -59,37 +59,6 @@ })(); -
-
-
Create your personal exam
-

Generate a personal SBA exam from the filtered question bank. Optionally limit the number of questions.

- -
- {% csrf_token %} - - -
- - -
- -
-
- - {% endblock %} \ No newline at end of file diff --git a/sbas/urls.py b/sbas/urls.py index 28c3614d..1b32f189 100644 --- a/sbas/urls.py +++ b/sbas/urls.py @@ -99,10 +99,16 @@ urlpatterns = [ views.import_llm_confirm, name="import_llm_confirm", ), + path( - "exam/create_from_filters/", - views.create_personal_exam, - name="exam_create_from_filters", + "exam/create_from_filters_with_name/", + views.create_personal_exam_with_name, + name="exam_create_from_filters_with_name", + ), + path( + "question/generate/", + views.generate_exam, + name="generate_exam", ), #path( # "exam//scores///", diff --git a/sbas/views.py b/sbas/views.py index d2d7404e..0d12b3ec 100644 --- a/sbas/views.py +++ b/sbas/views.py @@ -383,12 +383,12 @@ class QuestionClone(QuestionCreateBase): class UserAnswerView(LoginRequiredMixin, DetailView): model = UserAnswer - @login_required -def create_personal_exam(request): - """Create a personal SBA exam from question filters and redirect to start page. +def create_personal_exam_with_name(request): + """Create a personal SBA exam from filters, using a provided name. POST parameters expected: + - name (optional): exam name to use - category (optional) - status (optional) - count (optional): number of questions to include (0 or missing = all) @@ -396,8 +396,13 @@ def create_personal_exam(request): if request.method != "POST": return HttpResponseBadRequest("POST required") + name = request.POST.get("name") or None + # reuse existing create_personal_exam filtering logic but allow custom name category = request.POST.get("category") or None - status = request.POST.get("status") or "ANY" + # For generated personal exams we only include accepted questions + status = QuestionReview.StatusChoices.ACCEPTED + # Optionally exclude questions the current user has already answered + exclude_answered = request.POST.get("exclude_answered") in ("on", "true", "True", "1") try: count = int(request.POST.get("count") or 0) if count < 0: @@ -413,6 +418,14 @@ def create_personal_exam(request): except Exception: pass + # If requested, remove questions the current user has already answered + if exclude_answered: + try: + answered_qs = UserAnswer.objects.filter(user=request.user).values_list("question_id", flat=True) + qs = qs.exclude(pk__in=list(answered_qs)) + except Exception: + pass + # Permission filter: non-checkers see open_access or their own questions try: if not request.user.groups.filter(name="sbas_checker").exists(): @@ -459,7 +472,9 @@ def create_personal_exam(request): matches = matches[:count] # Create the Exam - name = f"{request.user.get_full_name() or request.user.username} - Personal SBA Exam" + if name is None or name.strip() == "": + name = f"{request.user.get_full_name() or request.user.username} - Personal SBA Exam" + exam = Exam(name=name, active=True, candidates_only=True, exam_mode=True) exam.save() # Add author and allow this user to take the exam @@ -481,6 +496,21 @@ def create_personal_exam(request): return redirect("sbas:exam_start", pk=exam.pk) +@login_required +def generate_exam(request): + """Render a small form allowing the user to name and generate a personal exam from filters.""" + # Prepare categories and status options for the template + categories = Category.objects.all().order_by("category") + + # Build status options: ANY, UNREVIEWED, plus choices from QuestionReview + status_options = [("ANY", "Any")] + status_options.append(("UNREVIEWED", "Unreviewed")) + for v, label in QuestionReview.StatusChoices.choices: + status_options.append((v, label)) + + return render(request, "sbas/generate_exam.html", {"categories": categories, "status_options": status_options}) + + class UserAnswerTableView(LoginRequiredMixin, SingleTableMixin, FilterView): model = UserAnswer table_class = UserAnswerTable