Skip to content

Commit

Permalink
Merge pull request learningequality#11674 from ozer550/update-ExamSer…
Browse files Browse the repository at this point in the history
…ializer-for-question_sources

Update ExamSerializers according to v3 model
  • Loading branch information
rtibbles authored Jan 9, 2024
2 parents dad9787 + 88c35cb commit a5d1bbe
Show file tree
Hide file tree
Showing 9 changed files with 351 additions and 31 deletions.
44 changes: 34 additions & 10 deletions kolibri/core/content/test/utils/test_assignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,13 +474,23 @@ def test_on_downloadable_assignment__individual_syncable_lesson(self):
def test_on_downloadable_assignment__exam(self):
callable_mock = mock.MagicMock()
Exam.content_assignments.on_downloadable_assignment(callable_mock)

resources = [
questions = [
{
"exercise_id": uuid.uuid4().hex,
"question_id": uuid.uuid4().hex,
"title": "Test questions",
"counter_in_exercise": 1,
"title": "Test question Title 1",
"counter_in_exercise": 0,
}
]
resources = [
{
"section_id": uuid.uuid4().hex,
"section_title": "Test Section Title",
"description": "Test descripton for Section",
"questions": questions,
"resource_pool": [],
"question_count": len(questions),
"learners_see_fixed_order": False,
}
]

Expand All @@ -503,7 +513,9 @@ def test_on_downloadable_assignment__exam(self):
assignments = list(callable_mock.call_args[0][1])
self.assertEqual(len(assignments), 1)
self.assertIsInstance(assignments[0], ContentAssignment)
self.assertEqual(assignments[0].contentnode_id, resources[0]["exercise_id"])
self.assertEqual(
assignments[0].contentnode_id, resources[0]["questions"][0]["exercise_id"]
)
self.assertEqual(assignments[0].source_id, exam.id)
self.assertEqual(assignments[0].source_model, Exam.morango_model_name)
self.assertEqual(assignments[0].metadata, None)
Expand All @@ -513,13 +525,23 @@ def test_on_downloadable_assignment__individual_syncable_exam(self):
IndividualSyncableExam.content_assignments.on_downloadable_assignment(
callable_mock
)

resources = [
questions = [
{
"exercise_id": uuid.uuid4().hex,
"question_id": uuid.uuid4().hex,
"title": "Test questions",
"counter_in_exercise": 1,
"title": "Test question Title 1",
"counter_in_exercise": 0,
}
]
resources = [
{
"section_id": uuid.uuid4().hex,
"section_title": "Test Section Title",
"description": "Test descripton for Section",
"questions": questions,
"resource_pool": [],
"question_count": len(questions),
"learners_see_fixed_order": False,
}
]

Expand Down Expand Up @@ -547,7 +569,9 @@ def test_on_downloadable_assignment__individual_syncable_exam(self):
assignments = list(callable_mock.call_args[0][1])
self.assertEqual(len(assignments), 1)
self.assertIsInstance(assignments[0], ContentAssignment)
self.assertEqual(assignments[0].contentnode_id, resources[0]["exercise_id"])
self.assertEqual(
assignments[0].contentnode_id, resources[0]["questions"][0]["exercise_id"]
)
self.assertEqual(assignments[0].source_id, syncable_exam.id)
self.assertEqual(
assignments[0].source_model, IndividualSyncableExam.morango_model_name
Expand Down
6 changes: 4 additions & 2 deletions kolibri/core/exams/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ class ExamViewset(ValuesViewset):
values = (
"id",
"title",
"question_count",
"question_sources",
"seed",
"active",
"collection",
"question_count",
"archive",
"date_archived",
"date_activated",
Expand Down Expand Up @@ -141,9 +141,11 @@ def size(self, request, **kwargs):
exams_sizes_set = []
for exam in exams:
quiz_size = {}

quiz_nodes = ContentNode.objects.filter(
id__in={source["exercise_id"] for source in exam.question_sources}
id__in=[question["exercise_id"] for question in exam.get_questions()]
)

quiz_size[exam.id] = total_file_size(quiz_nodes)
exams_sizes_set.append(quiz_size)

Expand Down
22 changes: 21 additions & 1 deletion kolibri/core/exams/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ def exam_assignment_lookup(question_sources):
:return: a tuple of contentnode_id and metadata
"""
for question_source in question_sources:
yield (question_source["exercise_id"], None)
if "section_id" in question_source:
questions = question_source.get("questions")
if questions is not None:
for question in question_source["questions"]:
yield (question["exercise_id"], None)
else:
yield (question_source["exercise_id"], None)


class Exam(AbstractFacilityDataModel):
Expand Down Expand Up @@ -224,6 +230,20 @@ def calculate_partition(self):
def __str__(self):
return self.title

def get_questions(self):
"""
Returns a list of all questions from all sections in the exam.
"""
questions = []
if self.data_model_version == 3:
for section in self.question_sources:
for question in section.get("questions", []):
questions.append(question)
else:
for question in self.question_sources:
questions.append(question)
return questions


class ExamAssignment(AbstractFacilityDataModel):
"""
Expand Down
33 changes: 29 additions & 4 deletions kolibri/core/exams/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from collections import OrderedDict

from rest_framework.serializers import BooleanField
from rest_framework.serializers import CharField
from rest_framework.serializers import IntegerField
from rest_framework.serializers import ListField
Expand Down Expand Up @@ -30,12 +31,22 @@ class QuestionSourceSerializer(Serializer):
exercise_id = HexUUIDField(format="hex")
# V0 need not have question_id that is why required=False
question_id = HexUUIDField(format="hex", required=False)
title = CharField()
title = CharField(default="")
counter_in_exercise = IntegerField()
missing_resources = BooleanField(default=False)


class ExamSerializer(ModelSerializer):
class QuizSectionSerializer(Serializer):
section_id = HexUUIDField(format="hex")
description = CharField(required=False, allow_blank=True)
section_title = CharField(allow_blank=True, required=False)
resource_pool = ListField(child=HexUUIDField(format="hex"))
question_count = IntegerField()
learners_see_fixed_order = BooleanField(default=False)
questions = ListField(child=QuestionSourceSerializer(), required=False)


class ExamSerializer(ModelSerializer):
assignments = ListField(
child=PrimaryKeyRelatedField(read_only=False, queryset=Collection.objects.all())
)
Expand All @@ -45,10 +56,11 @@ class ExamSerializer(ModelSerializer):
),
required=False,
)
question_sources = ListField(child=QuestionSourceSerializer(), required=False)
question_sources = ListField(child=QuizSectionSerializer(), required=False)
creator = PrimaryKeyRelatedField(
read_only=False, queryset=FacilityUser.objects.all()
)
question_count = IntegerField()
date_archived = DateTimeTzField(allow_null=True)
date_activated = DateTimeTzField(allow_null=True)

Expand All @@ -57,7 +69,6 @@ class Meta:
fields = (
"id",
"title",
"question_count",
"question_sources",
"seed",
"active",
Expand All @@ -67,6 +78,7 @@ class Meta:
"date_activated",
"assignments",
"creator",
"question_count",
"data_model_version",
"learners_see_fixed_order",
"learner_ids",
Expand Down Expand Up @@ -103,6 +115,19 @@ def validate(self, attrs):
code=error_constants.UNIQUE,
)

def to_representation(self, instance):
data = super().to_representation(instance)
if "question_sources" in data and data["question_sources"]:
if data["data_model_version"] == 3:
data["question_sources"] = QuizSectionSerializer(
instance.question_sources, many=True
).data
else:
data["question_sources"] = QuestionSourceSerializer(
instance.question_sources, many=True
).data
return data

def to_internal_value(self, data):
# Make a new OrderedDict from the input, which could be an immutable QueryDict
data = OrderedDict(data)
Expand Down
Loading

0 comments on commit a5d1bbe

Please sign in to comment.