improve surverys
This commit is contained in:
+102
-13
@@ -159,6 +159,9 @@ def survey_take(request, pk):
|
||||
object_id = request.GET.get("object_id") or request.POST.get("object_id")
|
||||
next_url = request.GET.get("next") or request.POST.get("next") or "/"
|
||||
|
||||
mandatory = request.GET.get("mandatory") or request.POST.get("mandatory") or "true"
|
||||
skip_key = request.GET.get("skip_key") or request.POST.get("skip_key") or ""
|
||||
|
||||
# Handle CID auth
|
||||
cid = request.GET.get("cid") or request.POST.get("cid")
|
||||
passcode = request.GET.get("passcode") or request.POST.get("passcode")
|
||||
@@ -189,7 +192,7 @@ def survey_take(request, pk):
|
||||
object_id=int(object_id) if object_id and str(object_id).isdigit() else None,
|
||||
pre_or_post=pre_or_post
|
||||
)
|
||||
return render(request, "survey/survey_thanks.html", {"next_url": next_url})
|
||||
return render(request, "survey/survey_thanks.html", {"next_url": next_url, "after_text": survey.after_text})
|
||||
else:
|
||||
form = SurveyTakeForm(survey=survey)
|
||||
|
||||
@@ -202,6 +205,8 @@ def survey_take(request, pk):
|
||||
"content_type_id": content_type_id,
|
||||
"object_id": object_id,
|
||||
"next": next_url,
|
||||
"mandatory": mandatory,
|
||||
"skip_key": skip_key,
|
||||
})
|
||||
|
||||
@login_required
|
||||
@@ -210,26 +215,63 @@ def survey_results(request, pk):
|
||||
"""
|
||||
Scope: Staff/Admin.
|
||||
Functionality: Renders survey response metrics, analytics, and text answers.
|
||||
Supports filtering by source context and target collections/exams.
|
||||
"""
|
||||
survey = get_object_or_404(Survey, pk=pk)
|
||||
if not can_manage_survey(request.user, survey):
|
||||
raise PermissionDenied("You do not have permission to manage this survey.")
|
||||
responses_count = survey.responses.count()
|
||||
|
||||
questions = survey.questions.all()
|
||||
responses = survey.responses.all()
|
||||
|
||||
# Compile list of distinct exams/collections that have used this survey to build the filter dropdown
|
||||
targets = []
|
||||
seen_targets = set()
|
||||
for resp in responses:
|
||||
if resp.content_type_id and resp.object_id:
|
||||
key = (resp.content_type_id, resp.object_id)
|
||||
if key not in seen_targets:
|
||||
seen_targets.add(key)
|
||||
try:
|
||||
obj = resp.content_object
|
||||
if obj:
|
||||
targets.append({
|
||||
"content_type_id": resp.content_type_id,
|
||||
"object_id": resp.object_id,
|
||||
"name": str(obj)
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Apply GET filters
|
||||
filter_context = request.GET.get("filter_context")
|
||||
if filter_context in [SurveyResponse.SurveyContext.PRE, SurveyResponse.SurveyContext.POST, SurveyResponse.SurveyContext.GENERAL]:
|
||||
responses = responses.filter(pre_or_post=filter_context)
|
||||
|
||||
filter_target = request.GET.get("filter_target")
|
||||
if filter_target:
|
||||
try:
|
||||
ct_id, obj_id = filter_target.split(":")
|
||||
responses = responses.filter(content_type_id=int(ct_id), object_id=int(obj_id))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
responses_count = responses.count()
|
||||
|
||||
analytics = []
|
||||
for q in questions:
|
||||
answers = q.answers.filter(response__in=responses)
|
||||
q_data = {
|
||||
"question": q,
|
||||
"answers_count": q.answers.count()
|
||||
"answers_count": answers.count()
|
||||
}
|
||||
|
||||
if q.question_type == SurveyQuestion.QuestionType.TEXT:
|
||||
q_data["text_answers"] = q.answers.values_list("text_answer", flat=True).exclude(text_answer__isnull=True).exclude(text_answer="")
|
||||
q_data["text_answers"] = answers.values_list("text_answer", flat=True).exclude(text_answer__isnull=True).exclude(text_answer="")
|
||||
|
||||
elif q.question_type == SurveyQuestion.QuestionType.CHOICE:
|
||||
# Count choices
|
||||
choice_counts = q.answers.values("choice_answer").annotate(count=Count("id")).order_by("-count")
|
||||
choice_counts = answers.values("choice_answer").annotate(count=Count("id")).order_by("-count")
|
||||
q_data["choice_counts"] = []
|
||||
for cc in choice_counts:
|
||||
ans_text = cc["choice_answer"]
|
||||
@@ -243,11 +285,11 @@ def survey_results(request, pk):
|
||||
|
||||
elif q.question_type == SurveyQuestion.QuestionType.RATING:
|
||||
# Stats for rating
|
||||
avg = q.answers.aggregate(avg=Avg("rating_answer"))["avg"]
|
||||
avg = answers.aggregate(avg=Avg("rating_answer"))["avg"]
|
||||
q_data["average_rating"] = round(avg, 2) if avg is not None else "N/A"
|
||||
|
||||
# Rating counts
|
||||
rating_counts = q.answers.values("rating_answer").annotate(count=Count("id"))
|
||||
rating_counts = answers.values("rating_answer").annotate(count=Count("id"))
|
||||
counts_dict = {i: 0 for i in range(1, 6)}
|
||||
for rc in rating_counts:
|
||||
r = rc["rating_answer"]
|
||||
@@ -264,12 +306,23 @@ def survey_results(request, pk):
|
||||
"percentage": pct
|
||||
})
|
||||
|
||||
elif q.question_type == SurveyQuestion.QuestionType.INTEGER:
|
||||
avg = answers.aggregate(avg=Avg("integer_answer"))["avg"]
|
||||
q_data["average_integer"] = round(avg, 2) if avg is not None else "N/A"
|
||||
q_data["integer_answers"] = list(answers.values_list("integer_answer", flat=True).exclude(integer_answer__isnull=True))
|
||||
|
||||
elif q.question_type == SurveyQuestion.QuestionType.DATE:
|
||||
q_data["date_answers"] = list(answers.values_list("date_answer", flat=True).exclude(date_answer__isnull=True))
|
||||
|
||||
analytics.append(q_data)
|
||||
|
||||
return render(request, "survey/survey_results.html", {
|
||||
"survey": survey,
|
||||
"responses_count": responses_count,
|
||||
"analytics": analytics
|
||||
"analytics": analytics,
|
||||
"targets": targets,
|
||||
"filter_context": filter_context,
|
||||
"filter_target": filter_target,
|
||||
})
|
||||
|
||||
@login_required
|
||||
@@ -277,13 +330,28 @@ def survey_results(request, pk):
|
||||
def survey_export_csv(request, pk):
|
||||
"""
|
||||
Scope: Staff/Admin.
|
||||
Functionality: Exports all responses for a survey as a downloadable CSV.
|
||||
Functionality: Exports filtered responses for a survey as a downloadable CSV.
|
||||
"""
|
||||
survey = get_object_or_404(Survey, pk=pk)
|
||||
if not can_manage_survey(request.user, survey):
|
||||
raise PermissionDenied("You do not have permission to manage this survey.")
|
||||
questions = list(survey.questions.all().order_by("position", "id"))
|
||||
|
||||
questions = list(survey.questions.all().order_by("position", "id"))
|
||||
responses = survey.responses.all()
|
||||
|
||||
# Apply GET filters to match CSV export output
|
||||
filter_context = request.GET.get("filter_context")
|
||||
if filter_context in [SurveyResponse.SurveyContext.PRE, SurveyResponse.SurveyContext.POST, SurveyResponse.SurveyContext.GENERAL]:
|
||||
responses = responses.filter(pre_or_post=filter_context)
|
||||
|
||||
filter_target = request.GET.get("filter_target")
|
||||
if filter_target:
|
||||
try:
|
||||
ct_id, obj_id = filter_target.split(":")
|
||||
responses = responses.filter(content_type_id=int(ct_id), object_id=int(obj_id))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
response = HttpResponse(content_type="text/csv")
|
||||
response["Content-Disposition"] = f'attachment; filename="survey_{survey.pk}_responses.csv"'
|
||||
|
||||
@@ -296,7 +364,7 @@ def survey_export_csv(request, pk):
|
||||
writer.writerow(headers)
|
||||
|
||||
# Data rows
|
||||
for resp in survey.responses.all():
|
||||
for resp in responses:
|
||||
user_str = resp.user.username if resp.user else ""
|
||||
cid_str = str(resp.cid) if resp.cid else ""
|
||||
ct_str = f"{resp.content_type.app_label}.{resp.content_type.model}" if resp.content_type else ""
|
||||
@@ -324,6 +392,10 @@ def survey_export_csv(request, pk):
|
||||
row.append(ans.choice_answer or "")
|
||||
elif q.question_type == SurveyQuestion.QuestionType.RATING:
|
||||
row.append(str(ans.rating_answer) if ans.rating_answer is not None else "")
|
||||
elif q.question_type == SurveyQuestion.QuestionType.INTEGER:
|
||||
row.append(str(ans.integer_answer) if ans.integer_answer is not None else "")
|
||||
elif q.question_type == SurveyQuestion.QuestionType.DATE:
|
||||
row.append(ans.date_answer.strftime("%Y-%m-%d") if ans.date_answer else "")
|
||||
|
||||
writer.writerow(row)
|
||||
|
||||
@@ -332,9 +404,13 @@ def survey_export_csv(request, pk):
|
||||
def check_survey_interception(request, exam_or_collection, cid=None, passcode=None):
|
||||
"""
|
||||
Checks if a pre_survey is configured for this collection/exam and if the user/cid
|
||||
has already responded to it. If not, redirects them to take the survey.
|
||||
has already responded to it or skipped it. If not, redirects them to take the survey.
|
||||
"""
|
||||
if exam_or_collection.pre_survey:
|
||||
session_key = f"skipped_pre_survey_{exam_or_collection.pk}"
|
||||
if request.session.get(session_key):
|
||||
return None
|
||||
|
||||
user_param = request.user if request.user.is_authenticated else None
|
||||
cid_param = int(cid) if cid and str(cid).isdigit() else None
|
||||
ct = ContentType.objects.get_for_model(exam_or_collection)
|
||||
@@ -351,8 +427,21 @@ def check_survey_interception(request, exam_or_collection, cid=None, passcode=No
|
||||
if not has_responded:
|
||||
from django.urls import reverse
|
||||
survey_url = reverse("survey:survey_take", kwargs={"pk": exam_or_collection.pre_survey.pk})
|
||||
params = f"?pre_or_post=PRE&content_type_id={ct.id}&object_id={exam_or_collection.pk}&next={request.get_full_path()}"
|
||||
is_mandatory = getattr(exam_or_collection, "pre_survey_mandatory", False)
|
||||
mandatory_str = "true" if is_mandatory else "false"
|
||||
params = f"?pre_or_post=PRE&content_type_id={ct.id}&object_id={exam_or_collection.pk}&next={request.get_full_path()}&mandatory={mandatory_str}&skip_key={session_key}"
|
||||
if cid and passcode:
|
||||
params += f"&cid={cid}&passcode={passcode}"
|
||||
return redirect(f"{survey_url}{params}")
|
||||
return None
|
||||
|
||||
def survey_skip(request):
|
||||
"""
|
||||
Scope: Candidates/Users.
|
||||
Functionality: Sets a session variable to bypass an optional survey, then redirects.
|
||||
"""
|
||||
next_url = request.GET.get("next") or "/"
|
||||
key = request.GET.get("key")
|
||||
if key:
|
||||
request.session[key] = True
|
||||
return redirect(next_url)
|
||||
|
||||
Reference in New Issue
Block a user