add simple survey support

This commit is contained in:
Ross
2026-06-29 09:04:28 +01:00
parent d07d88bdbf
commit 79ba4db4bf
33 changed files with 1687 additions and 2 deletions
+2
View File
@@ -361,6 +361,8 @@ class CaseCollectionForm(ModelForm):
"question_time_limit",
"answer_entry_grace_period",
"prerequisites",
"pre_survey",
"post_survey",
Fieldset(
"Custom Start Screen",
"start_screen_content",
@@ -0,0 +1,25 @@
# Generated by Django 6.0.1 on 2026-06-28 20:59
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('atlas', '0108_multiusersession'),
('survey', '__first__'),
]
operations = [
migrations.AddField(
model_name='casecollection',
name='post_survey',
field=models.ForeignKey(blank=True, help_text='Survey to be filled out AFTER completing this collection/exam.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(app_label)s_%(class)s_post_surveys', to='survey.survey'),
),
migrations.AddField(
model_name='casecollection',
name='pre_survey',
field=models.ForeignKey(blank=True, help_text='Survey to be filled out BEFORE taking this collection/exam.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(app_label)s_%(class)s_pre_surveys', to='survey.survey'),
),
]
@@ -51,6 +51,18 @@
</div>
</div>
{% if post_survey_url %}
<div class="alert alert-info border-info bg-dark bg-opacity-25 my-3 d-flex align-items-center justify-content-between" role="alert">
<div>
<h5 class="alert-heading text-info mb-1"><i class="bi bi-patch-question me-2"></i>Post-Collection Feedback</h5>
<p class="mb-0 small text-muted">Please take a moment to complete the feedback survey for this case collection.</p>
</div>
<a href="{{ post_survey_url }}" class="btn btn-info btn-sm text-dark fw-semibold px-4">
Take Survey <i class="bi bi-arrow-right ms-1"></i>
</a>
</div>
{% endif %}
<div class="list-group mb-3">
{% for question, answer, self_review, review_stats, timing_state in question_answer_tuples %}
<div class="list-group-item {% if review_stats.has_outstanding_feedback %}border-warning border-2{% elif not answer %}bg-danger-subtle border-danger-subtle{% endif %}">
+20
View File
@@ -75,5 +75,25 @@
<h3>Surveys & Feedback</h3>
<p>The platform supports reusable and shareable feedback forms and surveys that can be configured by administrators and staff to assess educational improvement and collect feedback.</p>
<h5>Managing Surveys (Staff/Admins)</h5>
<ul>
<li><strong>Create Surveys:</strong> Navigate to the <em>Survey Dashboard</em> to create a survey, define its name, description, and decide if responses should be anonymous.</li>
<li><strong>Question Types:</strong> You can add questions of three types:
<ul>
<li><em>Free Text:</em> For qualitative comments.</li>
<li><em>Multiple Choice:</em> For single-select questions (entered one option per line).</li>
<li><em>Rating Scale:</em> 1-to-5 Likert scale ratings.</li>
</ul>
</li>
<li><strong>Question Reordering:</strong> Adjust question ordering using the numeric inputs and click <em>Update Order</em>.</li>
<li><strong>Linking to Case Collections & Exams:</strong> Expose surveys by assigning them as <em>Pre-take</em> or <em>Post-take</em> surveys in the collection/exam edit screens. Candidates are automatically redirected to complete the Pre-take survey before they can begin, and are prompted to complete the Post-take survey once they finish.</li>
<li><strong>Analytics & CSV Export:</strong> Click <em>Analytics</em> on the dashboard to view aggregated rating averages, response counts, graphical progress bar breakdowns, and free-text replies. You can export complete response details to CSV format by clicking <em>Export CSV</em>.</li>
</ul>
<h5>Taking Standalone/General Surveys</h5>
<p>Surveys can also be distributed as standalone links (e.g. <code>/survey/take/&lt;id&gt;/</code>) for general platform feedback or course evaluation not tied to a specific exam.</p>
{% endblock %}
+39 -1
View File
@@ -112,6 +112,7 @@ from .forms import (
UserQuestionAnswerForm,
UserReportAnswerForm,
)
from survey.views import check_survey_interception
from generic.models import UserHiddenItem
from .models import (
Case,
@@ -6930,7 +6931,6 @@ def collection_take_start(request, pk, cid=None, passcode=None):
_type_: _description_
"""
collection = get_object_or_404(CaseCollection, pk=pk)
try:
collection.check_user_can_take(cid, passcode, request.user)
valid_user = True
@@ -6947,6 +6947,11 @@ def collection_take_start(request, pk, cid=None, passcode=None):
{'message': str(e), 'prereq': prereq, 'cid': cid, 'passcode': passcode},
)
# Check if pre-survey is required and not yet taken
response = check_survey_interception(request, collection, cid, passcode)
if response:
return response
#valid_user = collection.check_logged_in_user(request)
@@ -7729,6 +7734,32 @@ def collection_take_overview(
else:
question_answer_tuples.append((cd, None, self_review, review_stats, timing_state))
post_survey_url = None
if collection.post_survey:
user_param = request.user if request.user.is_authenticated else None
cid_param = int(cid) if cid and str(cid).isdigit() else None
from django.contrib.contenttypes.models import ContentType
from survey.models import SurveyResponse
ct = ContentType.objects.get_for_model(collection)
has_responded = SurveyResponse.objects.filter(
survey=collection.post_survey,
user=user_param,
cid=cid_param,
content_type=ct,
object_id=collection.pk,
pre_or_post=SurveyResponse.SurveyContext.POST
).exists()
if not has_responded:
from django.urls import reverse
survey_url = reverse("survey:survey_take", kwargs={"pk": collection.post_survey.pk})
params = f"?pre_or_post=POST&content_type_id={ct.id}&object_id={collection.pk}&next={request.get_full_path()}"
if cid and passcode:
params += f"&cid={cid}&passcode={passcode}"
post_survey_url = f"{survey_url}{params}"
return render(
request,
"atlas/collection_take_overview.html",
@@ -7743,6 +7774,7 @@ def collection_take_overview(
"outstanding_case_count": outstanding_case_count,
"total_outstanding_feedback": total_outstanding_feedback,
"end_overview_resources": collection.end_overview_resources.all(),
"post_survey_url": post_survey_url,
},
)
@@ -8368,6 +8400,12 @@ def collection_case_view_take(
"""
collection = get_object_or_404(CaseCollection, pk=pk)
# Check if pre-survey is required and not yet taken
from survey.views import check_survey_interception
response = check_survey_interception(request, collection, cid, passcode)
if response:
return response
# If caller provided a case_id (case PK) instead of a case_number (index),
# translate it into the index used by collection.get_case_by_index().
if case_id is not None and case_number is None: