improve stats handling
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
# Current target
|
||||
- User management
|
||||
- SQUASH MIGRATIONS
|
||||
- Exam dates
|
||||
- Exam groups (and compare scores)
|
||||
- Exam results reviewing
|
||||
|
||||
|
||||
|
||||
|
||||
# Ongoing
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.1.4 on 2024-01-08 22:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('anatomy', '0004_exam_candidates_only_alter_exam_active_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='stats_graph',
|
||||
field=models.TextField(default=0),
|
||||
),
|
||||
]
|
||||
+29
-8
@@ -759,7 +759,7 @@ class ExamBase(ExamCollectionGenericBase):
|
||||
|
||||
stats_max_possible = models.FloatField(default=0)
|
||||
|
||||
# stats_graph = models.TextField(default=0)
|
||||
stats_graph = models.TextField(default=0)
|
||||
|
||||
user_scores = models.JSONField(default=dict)
|
||||
|
||||
@@ -868,29 +868,50 @@ class ExamBase(ExamCollectionGenericBase):
|
||||
stats = self.get_exam_stats(name=False)
|
||||
exam_text.append(f"Stats\n{'-'*len('Stats')}\n{stats}")
|
||||
|
||||
rapids_extra = ""
|
||||
if self.app_name == "rapids":
|
||||
exam_text.append(f"Answers breakdown: {callstate}")
|
||||
rapids_extra = f"Answers breakdown: {callstate}"
|
||||
exam_text.append(rapids_extra)
|
||||
|
||||
msg = "\n\n".join(exam_text)
|
||||
|
||||
return msg
|
||||
html_msg = f"""
|
||||
<p>Candidate: {user.first_name} [{user.email}]</p>
|
||||
<h2>{exam_name}</h2>
|
||||
|
||||
<h3>Results</h3>
|
||||
Score: {user_score} / {int(self.stats_max_possible)} ({percentage})
|
||||
|
||||
<h3>Stats</h3>
|
||||
{stats}<br/>
|
||||
<a href="{settings.REMOTE_URL}{reverse("{}:exam_stats".format(self.app_name), args=(self.pk,))}">View all stats (including graph) here</a>
|
||||
|
||||
"""
|
||||
if self.app_name == "rapids":
|
||||
html_msg =f"{html_msg}<br/>{rapids_extra}"
|
||||
|
||||
return msg, html_msg
|
||||
|
||||
def email_user_results(self, user, resend=False, additional_emails=None):
|
||||
# if self.results_email_sent and not resend:
|
||||
# return False, "Already sent."
|
||||
|
||||
# Get a list of taken exams
|
||||
msg = self.generate_user_report(user)
|
||||
msg, html_msg = self.generate_user_report(user)
|
||||
|
||||
if not user.email:
|
||||
return [False, "User has no email"]
|
||||
|
||||
emails = [user.email]
|
||||
|
||||
supervisor_email = user.userprofile.supervisor.email
|
||||
extra = ""
|
||||
|
||||
if supervisor_email:
|
||||
if user.userprofile.supervisor is not None:
|
||||
supervisor_email = user.userprofile.supervisor.email
|
||||
emails.append(supervisor_email)
|
||||
else:
|
||||
extra = "No supervisor"
|
||||
|
||||
|
||||
if additional_emails is not None:
|
||||
emails.extend(additional_emails)
|
||||
@@ -902,14 +923,14 @@ class ExamBase(ExamCollectionGenericBase):
|
||||
"no-reply@penracourses.org.uk",
|
||||
emails,
|
||||
fail_silently=False,
|
||||
# html_message=html_msg,
|
||||
html_message=html_msg,
|
||||
)
|
||||
except SMTPException as e:
|
||||
return [False, e]
|
||||
|
||||
# self.results_email_sent = True
|
||||
# self.save()
|
||||
return [True, ""]
|
||||
return [True, extra]
|
||||
|
||||
|
||||
class ExamUserStatus(models.Model):
|
||||
|
||||
@@ -225,6 +225,11 @@ def generic_exam_urls(generic_exam_view: GenericExamViews):
|
||||
generic_exam_view.exam_scores_all,
|
||||
name="exam_scores_all",
|
||||
),
|
||||
path(
|
||||
"exam/<int:exam_id>/stats",
|
||||
generic_exam_view.exam_stats,
|
||||
name="exam_stats",
|
||||
),
|
||||
path(
|
||||
"exam/<int:pk>/scores/refresh",
|
||||
generic_exam_view.exam_scores_refresh,
|
||||
|
||||
+17
-1
@@ -2162,7 +2162,7 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
exam.stats_min = min(user_scores_list)
|
||||
exam.stats_max = max(user_scores_list)
|
||||
|
||||
# exam.stats_graph = fig_html
|
||||
exam.stats_graph = fig_html
|
||||
|
||||
user_data = {}
|
||||
for u in cids:
|
||||
@@ -2225,6 +2225,22 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
template_variables,
|
||||
)
|
||||
|
||||
def exam_stats(self, request, exam_id):
|
||||
exam = get_object_or_404(self.Exam, pk=exam_id)
|
||||
|
||||
if not exam.exam_mode:
|
||||
raise Http404("Packet not in exam mode")
|
||||
|
||||
template_variables = {
|
||||
"exam": exam
|
||||
}
|
||||
|
||||
return render(
|
||||
request,
|
||||
"exam_stats.html",
|
||||
template_variables,
|
||||
)
|
||||
|
||||
|
||||
class GenericViewBase:
|
||||
def __init__(self, app_name, QuestionObject, UserAnswerObject, ExamObject):
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.1.4 on 2024-01-08 22:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('longs', '0013_alter_longseriesimage_image_blake3_hash'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='stats_graph',
|
||||
field=models.TextField(default=0),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.1.4 on 2024-01-08 22:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('physics', '0002_exam_candidates_only_alter_exam_active_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='stats_graph',
|
||||
field=models.TextField(default=0),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.1.4 on 2024-01-08 22:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('rapids', '0002_exam_candidates_only_alter_exam_active_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='stats_graph',
|
||||
field=models.TextField(default=0),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.1.4 on 2024-01-08 22:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sbas', '0003_alter_useranswer_answer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='stats_graph',
|
||||
field=models.TextField(default=0),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{{exam.name}}</h2>
|
||||
|
||||
|
||||
{% if exam.stats_candidates < 4 %}
|
||||
Not enough data points...
|
||||
{% else %}
|
||||
|
||||
<div id="stats-plot">{{exam.stats_graph|safe}}</div>
|
||||
|
||||
Questions: <span id="question-number">{{question_number}}</span><br />
|
||||
Available marks: {{exam_stats_max_possible}}<br />
|
||||
Mean: {{exam.stats_mean}}, Median {{exam.stats_median}}, Mode {{exam.stats_mode}}<br />
|
||||
Top score: {{exam.stats_max}}
|
||||
|
||||
<p class="small-gray">Note: scores < 10% of the total score are excluded from these statistics
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user