Refactor exam review fragment inclusion to support app-specific templates and improve HTMX handling

This commit is contained in:
Ross
2025-11-09 21:41:02 +00:00
parent 2142749db6
commit fd8edd3cc3
9 changed files with 260 additions and 3 deletions
@@ -0,0 +1,33 @@
<div class="card mb-3">
<div class="card-body">
<div class="mb-2"><span class="badge bg-info">Anatomy</span></div>
<h5 class="card-title">Question</h5>
<p class="lead">{{ question }}</p>
<p>
<a class="btn btn-sm btn-outline-secondary" href="{% url app_name|add:':question_detail' question.pk %}" target="_blank">View full question details</a>
</p>
<div class="d-flex justify-content-between mt-3">
{% if prev_index is not None %}
<a class="btn btn-outline-primary"
hx-get="{% url app_name|add:':exam_review_question' exam.pk prev_index %}"
hx-target="#review-content"
hx-swap="innerHTML"
hx-push-url="true">&larr; Previous</a>
{% else %}
<span></span>
{% endif %}
{% if next_index is not None %}
<a class="btn btn-primary"
hx-get="{% url app_name|add:':exam_review_question' exam.pk next_index %}"
hx-target="#review-content"
hx-swap="innerHTML"
hx-push-url="true">Next &rarr;</a>
{% else %}
<a class="btn btn-success" href="{% url app_name|add:':exam_overview' exam.pk %}">Complete review</a>
{% endif %}
</div>
</div>
</div>
@@ -0,0 +1,33 @@
<div class="card mb-3">
<div class="card-body">
<div class="mb-2"><span class="badge bg-secondary">Atlas</span></div>
<h5 class="card-title">Question</h5>
<p class="lead">{{ question }}</p>
<p>
<a class="btn btn-sm btn-outline-secondary" href="{% url app_name|add:':question_detail' question.pk %}" target="_blank">View full question details</a>
</p>
<div class="d-flex justify-content-between mt-3">
{% if prev_index is not None %}
<a class="btn btn-outline-primary"
hx-get="{% url app_name|add:':exam_review_question' exam.pk prev_index %}"
hx-target="#review-content"
hx-swap="innerHTML"
hx-push-url="true">&larr; Previous</a>
{% else %}
<span></span>
{% endif %}
{% if next_index is not None %}
<a class="btn btn-primary"
hx-get="{% url app_name|add:':exam_review_question' exam.pk next_index %}"
hx-target="#review-content"
hx-swap="innerHTML"
hx-push-url="true">Next &rarr;</a>
{% else %}
<a class="btn btn-success" href="{% url app_name|add:':exam_overview' exam.pk %}">Complete review</a>
{% endif %}
</div>
</div>
</div>
@@ -6,7 +6,10 @@
<div id="review-root">
{# The #review-content element is replaced by HTMX when navigating between questions. #}
<div id="review-content">
{% include 'generic/partials/exam_review_question_fragment.html' %}
{# The view selects an app-specific fragment if present and passes its name
in `fragment_template`. Include that fragment here so the full-page
render and the HTMX partials both use the same template. #}
{% include fragment_template %}
</div>
</div>
</div>
+25 -2
View File
@@ -1216,8 +1216,31 @@ class ExamViews(View, LoginRequiredMixin):
"can_edit": self.check_user_edit_access(request.user, exam_id=pk),
}
if request.htmx:
return render(request, "generic/partials/exam_review_question_fragment.html", context)
# Choose an app-specific fragment if present, otherwise fall back to
# the generic fragment. Expose the chosen fragment name in the
# context so the full-page render can include it; return the fragment
# directly for HTMX requests.
from django.template import loader
fragment_candidates = [
f"{self.app_name}/partials/exam_review_question_fragment.html",
"generic/partials/exam_review_question_fragment.html",
]
template = loader.select_template(fragment_candidates)
fragment_template_name = template.template.name
context["fragment_template"] = fragment_template_name
# Detect HTMX: prefer the request.htmx attribute when available, otherwise
# check the HX-Request header.
is_htmx = getattr(request, "htmx", False)
if not is_htmx:
try:
is_htmx = request.headers.get("HX-Request", "").lower() == "true"
except Exception:
is_htmx = request.META.get("HTTP_HX_REQUEST", "") == "true"
if is_htmx:
return render(request, fragment_template_name, context)
return render(request, "generic/exam_review_question.html", context)
@@ -0,0 +1,33 @@
<div class="card mb-3">
<div class="card-body">
<div class="mb-2"><span class="badge bg-secondary">Longs</span></div>
<h5 class="card-title">Question</h5>
<p class="lead">{{ question }}</p>
<p>
<a class="btn btn-sm btn-outline-secondary" href="{% url app_name|add:':question_detail' question.pk %}" target="_blank">View full question details</a>
</p>
<div class="d-flex justify-content-between mt-3">
{% if prev_index is not None %}
<a class="btn btn-outline-primary"
hx-get="{% url app_name|add:':exam_review_question' exam.pk prev_index %}"
hx-target="#review-content"
hx-swap="innerHTML"
hx-push-url="true">&larr; Previous</a>
{% else %}
<span></span>
{% endif %}
{% if next_index is not None %}
<a class="btn btn-primary"
hx-get="{% url app_name|add:':exam_review_question' exam.pk next_index %}"
hx-target="#review-content"
hx-swap="innerHTML"
hx-push-url="true">Next &rarr;</a>
{% else %}
<a class="btn btn-success" href="{% url app_name|add:':exam_overview' exam.pk %}">Complete review</a>
{% endif %}
</div>
</div>
</div>
@@ -0,0 +1,33 @@
<div class="card mb-3">
<div class="card-body">
<div class="mb-2"><span class="badge bg-primary">Physics</span></div>
<h5 class="card-title">Question</h5>
<p class="lead">{{ question }}</p>
<p>
<a class="btn btn-sm btn-outline-secondary" href="{% url app_name|add:':question_detail' question.pk %}" target="_blank">View full question details</a>
</p>
<div class="d-flex justify-content-between mt-3">
{% if prev_index is not None %}
<a class="btn btn-outline-primary"
hx-get="{% url app_name|add:':exam_review_question' exam.pk prev_index %}"
hx-target="#review-content"
hx-swap="innerHTML"
hx-push-url="true">&larr; Previous</a>
{% else %}
<span></span>
{% endif %}
{% if next_index is not None %}
<a class="btn btn-primary"
hx-get="{% url app_name|add:':exam_review_question' exam.pk next_index %}"
hx-target="#review-content"
hx-swap="innerHTML"
hx-push-url="true">Next &rarr;</a>
{% else %}
<a class="btn btn-success" href="{% url app_name|add:':exam_overview' exam.pk %}">Complete review</a>
{% endif %}
</div>
</div>
</div>
@@ -0,0 +1,33 @@
<div class="card mb-3">
<div class="card-body">
<div class="mb-2"><span class="badge bg-warning text-dark">Rapids</span></div>
<h5 class="card-title">Question</h5>
<p class="lead">{{ question }}</p>
<p>
<a class="btn btn-sm btn-outline-secondary" href="{% url app_name|add:':question_detail' question.pk %}" target="_blank">View full question details</a>
</p>
<div class="d-flex justify-content-between mt-3">
{% if prev_index is not None %}
<a class="btn btn-outline-primary"
hx-get="{% url app_name|add:':exam_review_question' exam.pk prev_index %}"
hx-target="#review-content"
hx-swap="innerHTML"
hx-push-url="true">&larr; Previous</a>
{% else %}
<span></span>
{% endif %}
{% if next_index is not None %}
<a class="btn btn-primary"
hx-get="{% url app_name|add:':exam_review_question' exam.pk next_index %}"
hx-target="#review-content"
hx-swap="innerHTML"
hx-push-url="true">Next &rarr;</a>
{% else %}
<a class="btn btn-success" href="{% url app_name|add:':exam_overview' exam.pk %}">Complete review</a>
{% endif %}
</div>
</div>
</div>
@@ -0,0 +1,33 @@
<div class="card mb-3">
<div class="card-body">
<div class="mb-2"><span class="badge bg-dark">SBAs</span></div>
<h5 class="card-title">Question</h5>
<p class="lead">{{ question }}</p>
<p>
<a class="btn btn-sm btn-outline-secondary" href="{% url app_name|add:':question_detail' question.pk %}" target="_blank">View full question details</a>
</p>
<div class="d-flex justify-content-between mt-3">
{% if prev_index is not None %}
<a class="btn btn-outline-primary"
hx-get="{% url app_name|add:':exam_review_question' exam.pk prev_index %}"
hx-target="#review-content"
hx-swap="innerHTML"
hx-push-url="true">&larr; Previous</a>
{% else %}
<span></span>
{% endif %}
{% if next_index is not None %}
<a class="btn btn-primary"
hx-get="{% url app_name|add:':exam_review_question' exam.pk next_index %}"
hx-target="#review-content"
hx-swap="innerHTML"
hx-push-url="true">Next &rarr;</a>
{% else %}
<a class="btn btn-success" href="{% url app_name|add:':exam_overview' exam.pk %}">Complete review</a>
{% endif %}
</div>
</div>
</div>
@@ -0,0 +1,33 @@
<div class="card mb-3">
<div class="card-body">
<div class="mb-2"><span class="badge bg-success">Shorts</span></div>
<h5 class="card-title">Question</h5>
<p class="lead">{{ question }}</p>
<p>
<a class="btn btn-sm btn-outline-secondary" href="{% url app_name|add:':question_detail' question.pk %}" target="_blank">View full question details</a>
</p>
<div class="d-flex justify-content-between mt-3">
{% if prev_index is not None %}
<a class="btn btn-outline-primary"
hx-get="{% url app_name|add:':exam_review_question' exam.pk prev_index %}"
hx-target="#review-content"
hx-swap="innerHTML"
hx-push-url="true">&larr; Previous</a>
{% else %}
<span></span>
{% endif %}
{% if next_index is not None %}
<a class="btn btn-primary"
hx-get="{% url app_name|add:':exam_review_question' exam.pk next_index %}"
hx-target="#review-content"
hx-swap="innerHTML"
hx-push-url="true">Next &rarr;</a>
{% else %}
<a class="btn btn-success" href="{% url app_name|add:':exam_overview' exam.pk %}">Complete review</a>
{% endif %}
</div>
</div>
</div>