Skip to content

Commit

Permalink
Enforce negative mastery levels for all quizzes. Add tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
rtibbles committed Nov 16, 2021
1 parent 7bb8c24 commit 4a2c610
Show file tree
Hide file tree
Showing 2 changed files with 410 additions and 17 deletions.
38 changes: 28 additions & 10 deletions kolibri/core/logger/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from django_filters.rest_framework import UUIDFilter
from le_utils.constants import content_kinds
from le_utils.constants import exercises
from le_utils.constants import modalities
from rest_framework import filters
from rest_framework import serializers
from rest_framework import viewsets
Expand Down Expand Up @@ -229,7 +230,9 @@ def _get_context(self, user, validated_data):
).values_list("mastery_model", flat=True)[:1]
)
)
.values("content_id", "channel_id", "kind", "mastery_model")
.values(
"content_id", "channel_id", "kind", "mastery_model", "options"
)
.get(id=node_id)
)
mastery_model = node["mastery_model"]
Expand All @@ -240,11 +243,16 @@ def _get_context(self, user, validated_data):
if lesson_id:
self._check_lesson_permissions(user, lesson_id)
context["lesson_id"] = lesson_id
if (
node["options"]
and node["options"].get("modality") == modalities.QUIZ
):
mastery_model = {"type": exercises.QUIZ}
except ContentNode.DoesNotExist:
raise ValidationError("Invalid node_id")
elif quiz_id is not None:
self._check_quiz_permissions(user, quiz_id)
mastery_model = {"type": "quiz", "coach_assigned": True}
mastery_model = {"type": exercises.QUIZ, "coach_assigned": True}
content_id = quiz_id
channel_id = None
kind = content_kinds.QUIZ
Expand Down Expand Up @@ -431,15 +439,22 @@ def _get_or_create_masterylog(
start_timestamp,
context,
):
masterylog = (
MasteryLog.objects.filter(
summarylog=summarylog,
user=user,
)
.order_by("-complete", "-end_timestamp")
.first()
is_quiz = mastery_model["type"] == exercises.QUIZ
masterylogs = MasteryLog.objects.filter(
summarylog=summarylog,
user=user,
)

# Just in case there is an exercise that might have the same content_id
# and hence the same SummaryLog as a practice quiz, we filter masterylogs
# here by whether they have a negative mastery_level or not, depending
# on whether this is a quiz or an exercise.
if is_quiz:
masterylogs = masterylogs.filter(mastery_level__lt=0)
else:
masterylogs = masterylogs.filter(mastery_level__gt=0)
masterylog = masterylogs.order_by("-complete", "-end_timestamp").first()

if masterylog is None or (masterylog.complete and repeat):
# There is no previous masterylog, or the previous masterylog
# is complete, and the request is requesting a new attempt.
Expand All @@ -449,9 +464,12 @@ def _get_or_create_masterylog(
# identifier being created. So if the same user engages with the same assessment on different
# devices, when the data synchronizes, if the mastery_level is the same, this data will be
# unified under a single try.
if mastery_model.get("coach_assigned"):
if is_quiz:
# To prevent coach assigned quiz mastery logs from propagating to older
# Kolibri versions, we use negative mastery levels for these.
# Also, to prevent collisions between mastery logs for practice quizzes
# and mastery logs for exercises with the same content_id we also
# use negative mastery levels for practice quizzes.
# In older versions of Kolibri the mastery_level is validated to be
# between 1 and 10 - so these values will fail validation and hence will
# not be deserialized from the morango store.
Expand Down
Loading

0 comments on commit 4a2c610

Please sign in to comment.