diff --git a/kolibri/core/exams/api.py b/kolibri/core/exams/api.py
index 51fde4ff0c8..0dd8f3fbf30 100644
--- a/kolibri/core/exams/api.py
+++ b/kolibri/core/exams/api.py
@@ -69,6 +69,7 @@ class ExamViewset(ValuesViewset):
"creator",
"data_model_version",
"learners_see_fixed_order",
+ "instant_report_visibility",
"date_created",
)
@@ -82,7 +83,12 @@ class ExamViewset(ValuesViewset):
draft_values = common_values + ("assignments", "learner_ids")
- field_map = {"assignments": "assignment_collections"}
+ field_map = {
+ "assignments": "assignment_collections",
+ "instant_report_visibility": lambda x: True
+ if x["instant_report_visibility"] is None
+ else x["instant_report_visibility"],
+ }
def get_draft_queryset(self):
return models.DraftExam.objects.all()
diff --git a/kolibri/core/exams/migrations/0010_add_exam_report_visibility_field.py b/kolibri/core/exams/migrations/0010_add_exam_report_visibility_field.py
new file mode 100644
index 00000000000..7e925db2879
--- /dev/null
+++ b/kolibri/core/exams/migrations/0010_add_exam_report_visibility_field.py
@@ -0,0 +1,33 @@
+# Generated by Django 3.2.25 on 2025-02-20 17:37
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("exams", "0009_alter_exam_date_created"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="draftexam",
+ name="instant_report_visibility",
+ field=models.BooleanField(null=True),
+ ),
+ migrations.AddField(
+ model_name="exam",
+ name="instant_report_visibility",
+ field=models.BooleanField(null=True),
+ ),
+ migrations.AlterField(
+ model_name="draftexam",
+ name="instant_report_visibility",
+ field=models.BooleanField(default=True, null=True),
+ ),
+ migrations.AlterField(
+ model_name="exam",
+ name="instant_report_visibility",
+ field=models.BooleanField(default=True, null=True),
+ ),
+ ]
diff --git a/kolibri/core/exams/models.py b/kolibri/core/exams/models.py
index 7b7a191d2be..f72f03105fc 100644
--- a/kolibri/core/exams/models.py
+++ b/kolibri/core/exams/models.py
@@ -158,6 +158,10 @@ class Meta:
"""
data_model_version = models.SmallIntegerField(default=3)
+ # If True, learners have instant access to exam reports after submission.
+ # Otherwise, reports are visible only after the coach ends the exam.
+ instant_report_visibility = models.BooleanField(null=True, default=True)
+
def __str__(self):
return self.title
@@ -209,6 +213,7 @@ def to_exam(self):
collection=self.collection,
creator=self.creator,
data_model_version=self.data_model_version,
+ instant_report_visibility=self.instant_report_visibility,
date_created=self.date_created,
)
return exam
diff --git a/kolibri/core/exams/serializers.py b/kolibri/core/exams/serializers.py
index f3de2931040..a5c79507864 100644
--- a/kolibri/core/exams/serializers.py
+++ b/kolibri/core/exams/serializers.py
@@ -70,6 +70,7 @@ class Meta:
"archive",
"assignments",
"learners_see_fixed_order",
+ "instant_report_visibility",
"learner_ids",
"draft",
)
@@ -270,6 +271,10 @@ def update(self, instance, validated_data): # noqa
instance.learners_see_fixed_order = validated_data.pop(
"learners_see_fixed_order", instance.learners_see_fixed_order
)
+ instance.instant_report_visibility = validated_data.pop(
+ "instant_report_visibility",
+ instance.instant_report_visibility,
+ )
if not instance_is_draft:
# Update the non-draft specific fields
instance.active = validated_data.pop("active", instance.active)
diff --git a/kolibri/core/exams/test/test_exam_api.py b/kolibri/core/exams/test/test_exam_api.py
index 8e7d42fe9b7..88f451b6835 100644
--- a/kolibri/core/exams/test/test_exam_api.py
+++ b/kolibri/core/exams/test/test_exam_api.py
@@ -84,6 +84,7 @@ def setUpTestData(cls):
}
],
"learners_see_fixed_order": False,
+ "instant_report_visibility": True,
}
],
)
@@ -98,6 +99,7 @@ def make_basic_exam(self):
"draft": self.draft,
"collection": self.classroom.id,
"learners_see_fixed_order": False,
+ "instant_report_visibility": True,
"question_sources": sections,
"assignments": [],
}
@@ -421,6 +423,7 @@ def test_retrieve_exam(self):
"creator",
"data_model_version",
"learners_see_fixed_order",
+ "instant_report_visibility",
"date_created",
]:
self.assertIn(field, response.data)
@@ -433,6 +436,7 @@ def test_post_exam_v2_model_fails(self):
"active": True,
"collection": self.classroom.id,
"learners_see_fixed_order": False,
+ "instant_report_visibility": True,
"question_sources": [],
"assignments": [],
"date_activated": None,
@@ -503,6 +507,14 @@ def test_admin_can_update_learner_sees_fixed_order(self):
self.assertEqual(response.status_code, 200)
self.assertExamExists(id=self.exam.id, learners_see_fixed_order=True)
+ def test_admin_can_update_instant_report_visibility(self):
+ self.login_as_admin()
+ response = self.patch_updated_exam(
+ self.exam.id, {"instant_report_visibility": False}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertExamExists(id=self.exam.id, instant_report_visibility=False)
+
class ExamAPITestCase(BaseExamTest, APITestCase):
class_object = models.Exam
diff --git a/kolibri/plugins/coach/assets/src/composables/quizCreationSpecs.js b/kolibri/plugins/coach/assets/src/composables/quizCreationSpecs.js
index 495bb93b5f0..a80292036c6 100644
--- a/kolibri/plugins/coach/assets/src/composables/quizCreationSpecs.js
+++ b/kolibri/plugins/coach/assets/src/composables/quizCreationSpecs.js
@@ -191,4 +191,9 @@ export const Quiz = {
type: Boolean,
default: true,
},
+ // Default to quiz reports being visible immediately after learner submits quiz
+ instant_report_visibility: {
+ type: Boolean,
+ default: true,
+ },
};
diff --git a/kolibri/plugins/coach/assets/src/composables/useQuizCreation.js b/kolibri/plugins/coach/assets/src/composables/useQuizCreation.js
index d28586e2c0e..7b35a3448cb 100644
--- a/kolibri/plugins/coach/assets/src/composables/useQuizCreation.js
+++ b/kolibri/plugins/coach/assets/src/composables/useQuizCreation.js
@@ -26,6 +26,7 @@ const fieldsToSave = [
'learner_ids',
'collection',
'learners_see_fixed_order',
+ 'instant_report_visibility',
'draft',
'active',
'archive',
diff --git a/kolibri/plugins/coach/assets/src/modules/examShared/exams.js b/kolibri/plugins/coach/assets/src/modules/examShared/exams.js
index 3b7f55623a6..8303f081bb2 100644
--- a/kolibri/plugins/coach/assets/src/modules/examShared/exams.js
+++ b/kolibri/plugins/coach/assets/src/modules/examShared/exams.js
@@ -15,6 +15,7 @@ export function examState(exam) {
questionSources: exam.question_sources,
assignments: exam.assignments,
learnersSeeFixedOrder: exam.learners_see_fixed_order,
+ instantReportVisibility: exam.instant_report_visibility,
dataModelVersion: exam.data_model_version,
seed: exam.seed,
};
diff --git a/kolibri/plugins/coach/assets/src/views/common/QuizStatus.vue b/kolibri/plugins/coach/assets/src/views/common/QuizStatus.vue
index 7f85a71396c..d7c4edfa2f8 100644
--- a/kolibri/plugins/coach/assets/src/views/common/QuizStatus.vue
+++ b/kolibri/plugins/coach/assets/src/views/common/QuizStatus.vue
@@ -90,7 +90,7 @@
{{ $tr('reportVisibleToLearnersLabel') }}