Skip to content

Commit

Permalink
Merge pull request #3426 from ozer550/Consolidate_Mastery_Criteria
Browse files Browse the repository at this point in the history
Consolidate mastery criteria
  • Loading branch information
rtibbles authored Jun 16, 2022
2 parents e3823d7 + 23675f6 commit 0fbb763
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 32 deletions.
111 changes: 89 additions & 22 deletions contentcuration/contentcuration/tests/viewsets/test_contentnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from django_concurrent_tests.helpers import make_concurrent_calls
from le_utils.constants import completion_criteria
from le_utils.constants import content_kinds
from le_utils.constants import exercises
from le_utils.constants import roles
from le_utils.constants.labels.accessibility_categories import ACCESSIBILITYCATEGORIESLIST
from le_utils.constants.labels.subjects import SUBJECTSLIST
Expand Down Expand Up @@ -366,6 +367,65 @@ def test_public_get_contentnode__unauthenticated(self):
)
self.assertEqual(response.status_code, 403, response.content)

def test_consolidate_extra_fields(self):

user = testdata.user()
channel = testdata.channel()
channel.public = True
channel.save()
contentnode = models.ContentNode.objects.create(
title="Ozer's cool contentnode",
id=uuid.uuid4().hex,
kind_id=content_kinds.EXERCISE,
description="coolest contentnode this side of the Pacific",
parent_id=channel.main_tree_id,
extra_fields={
"m": 3,
"n": 6,
"mastery_model": exercises.M_OF_N,
}
)

self.client.force_authenticate(user=user)
with self.settings(TEST_ENV=False):
response = self.client.get(
self.viewset_url(pk=contentnode.id), format="json",
)
self.assertEqual(response.status_code, 200, response.content)
print(response.data["extra_fields"])
self.assertEqual(response.data["extra_fields"]["options"]["completion_criteria"]["threshold"]["m"], 3)
self.assertEqual(response.data["extra_fields"]["options"]["completion_criteria"]["threshold"]["n"], 6)
self.assertEqual(response.data["extra_fields"]["options"]["completion_criteria"]["threshold"]["mastery_model"], exercises.M_OF_N)
self.assertEqual(response.data["extra_fields"]["options"]["completion_criteria"]["model"], completion_criteria.MASTERY)

def test_consolidate_extra_fields_with_mastrey_model_none(self):

user = testdata.user()
channel = testdata.channel()
channel.public = True
channel.save()
contentnode = models.ContentNode.objects.create(
title="Aron's cool contentnode",
id=uuid.uuid4().hex,
kind_id=content_kinds.EXERCISE,
description="India is the hottest country in the world",
parent_id=channel.main_tree_id,
extra_fields={

"m": None,
"n": None,
"mastery_model": None,
}
)

self.client.force_authenticate(user=user)
with self.settings(TEST_ENV=False):
response = self.client.get(
self.viewset_url(pk=contentnode.id), format="json",
)
self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(response.data["extra_fields"], {})


class SyncTestCase(StudioAPITestCase):
@property
Expand Down Expand Up @@ -591,32 +651,28 @@ def test_update_contentnode_extra_fields(self):
user = testdata.user()
contentnode = models.ContentNode.objects.create(**self.contentnode_db_metadata)

# Update extra_fields.m
# Update m and n fields
m = 5
n = 10
self.client.force_authenticate(user=user)
response = self.client.post(
self.sync_url,
[generate_update_event(contentnode.id, CONTENTNODE, {"extra_fields.m": m})],
format="json",
)
self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(
models.ContentNode.objects.get(id=contentnode.id).extra_fields["m"], m
)

# Update extra_fields.m
n = 10
response = self.client.post(
self.sync_url,
[generate_update_event(contentnode.id, CONTENTNODE, {"extra_fields.n": n})],
[generate_update_event(contentnode.id, CONTENTNODE, {
"extra_fields.options.completion_criteria.threshold.m": m,
"extra_fields.options.completion_criteria.threshold.n": n,
"extra_fields.options.completion_criteria.threshold.mastery_model": exercises.M_OF_N,
"extra_fields.options.completion_criteria.model": completion_criteria.MASTERY}
)],
format="json",
)

self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(
models.ContentNode.objects.get(id=contentnode.id).extra_fields["m"], m
models.ContentNode.objects.get(id=contentnode.id).extra_fields["options"]["completion_criteria"]["threshold"]["m"], m
)
self.assertEqual(
models.ContentNode.objects.get(id=contentnode.id).extra_fields["n"], n
models.ContentNode.objects.get(id=contentnode.id).extra_fields["options"]["completion_criteria"]["threshold"]["n"], n
)

# Update extra_fields.randomize
Expand All @@ -628,10 +684,10 @@ def test_update_contentnode_extra_fields(self):
)
self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(
models.ContentNode.objects.get(id=contentnode.id).extra_fields["m"], m
models.ContentNode.objects.get(id=contentnode.id).extra_fields["options"]["completion_criteria"]["threshold"]["m"], m
)
self.assertEqual(
models.ContentNode.objects.get(id=contentnode.id).extra_fields["n"], n
models.ContentNode.objects.get(id=contentnode.id).extra_fields["options"]["completion_criteria"]["threshold"]["n"], n
)
self.assertEqual(
models.ContentNode.objects.get(id=contentnode.id).extra_fields["randomize"], randomize
Expand All @@ -641,19 +697,30 @@ def test_update_contentnode_remove_from_extra_fields(self):
user = testdata.user()
metadata = self.contentnode_db_metadata
metadata["extra_fields"] = {
"m": 5,
"options": {
"threshold": {
"m": 5,
"n": None,
"mastery_model": exercises.M_OF_N,
},
"model": completion_criteria.MASTERY,
}
}
contentnode = models.ContentNode.objects.create(**metadata)
self.client.force_authenticate(user=user)
# Remove extra_fields.m
# Remove m from extra_fields
response = self.client.post(
self.sync_url,
[generate_update_event(contentnode.id, CONTENTNODE, {"extra_fields.m": None})],
[generate_update_event(contentnode.id, CONTENTNODE, {
"extra_fields.options.completion_criteria.threshold.m": None,
"extra_fields.options.completion_criteria.threshold.n": None,
"extra_fields.options.completion_criteria.threshold.mastery_model": exercises.M_OF_N,
"extra_fields.options.completion_criteria.model": completion_criteria.MASTERY}
)],
format="json",
)
self.assertEqual(response.status_code, 200, response.content)
with self.assertRaises(KeyError):
models.ContentNode.objects.get(id=contentnode.id).extra_fields["m"]
self.assertEqual(models.ContentNode.objects.get(id=contentnode.id).extra_fields["options"]["completion_criteria"]["threshold"]["m"], None)

def test_update_contentnode_add_to_extra_fields_nested(self):
user = testdata.user()
Expand Down
1 change: 1 addition & 0 deletions contentcuration/contentcuration/utils/db_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ def create_exercise(title, parent, license_id, description="", user=None, empty=
"m": 3,
"n": 5,
}

exercise = ContentNode.objects.create(
title=title,
description=description,
Expand Down
35 changes: 25 additions & 10 deletions contentcuration/contentcuration/viewsets/contentnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
from django_cte import CTEQuerySet
from django_filters.rest_framework import CharFilter
from django_filters.rest_framework import UUIDFilter
from le_utils.constants import completion_criteria
from le_utils.constants import content_kinds
from le_utils.constants import exercises
from le_utils.constants import roles
from le_utils.constants.labels import accessibility_categories
from le_utils.constants.labels import learning_activities
Expand All @@ -35,7 +35,7 @@
from rest_framework.serializers import CharField
from rest_framework.serializers import ChoiceField
from rest_framework.serializers import DictField
from rest_framework.serializers import IntegerField
from rest_framework.serializers import Field
from rest_framework.serializers import ValidationError
from rest_framework.viewsets import ViewSet

Expand Down Expand Up @@ -254,15 +254,14 @@ def update(self, queryset, all_validated_data):
return all_objects


class ThresholdField(CharField):
class ThresholdField(Field):
def to_representation(self, value):
return value

def to_internal_value(self, data):
data = super(ThresholdField, self).to_internal_value(data)
try:
data = int(data)
except ValueError:
except(ValueError, TypeError):
pass
return data

Expand All @@ -287,12 +286,7 @@ class ExtraFieldsOptionsSerializer(JSONFieldDictSerializer):


class ExtraFieldsSerializer(JSONFieldDictSerializer):
mastery_model = ChoiceField(
choices=exercises.MASTERY_MODELS, allow_null=True, required=False
)
randomize = BooleanField()
m = IntegerField(allow_null=True, required=False)
n = IntegerField(allow_null=True, required=False)
options = ExtraFieldsOptionsSerializer(required=False)


Expand Down Expand Up @@ -455,6 +449,26 @@ def get_title(item):
return item["title"] if item["parent_id"] else item["original_channel_name"]


def consolidate_extra_fields(item):
extra_fields = item.get("extra_fields")
if item["kind"] == content_kinds.EXERCISE:
m = extra_fields.pop("m", None)
n = extra_fields.pop("n", None)
mastery_model = extra_fields.pop("mastery_model", None)
if not extra_fields.get("options", {}).get("completion_criteria", {}) and mastery_model is not None:
extra_fields["options"] = extra_fields.get("options", {})
extra_fields["options"]["completion_criteria"] = {
"threshold": {
"m": m,
"n": n,
"mastery_model": mastery_model,
},
"model": completion_criteria.MASTERY,
}

return extra_fields


class PrerequisitesUpdateHandler(ViewSet):
"""
Dummy viewset for handling create and delete changes for prerequisites
Expand Down Expand Up @@ -683,6 +697,7 @@ class ContentNodeViewSet(BulkUpdateMixin, ChangeEventMixin, ValuesViewset):
"accessibility_labels": partial(dict_if_none, field_name="accessibility_labels"),
"categories": partial(dict_if_none, field_name="categories"),
"learner_needs": partial(dict_if_none, field_name="learner_needs"),
"extra_fields": consolidate_extra_fields,
}

def _annotate_channel_id(self, queryset):
Expand Down

0 comments on commit 0fbb763

Please sign in to comment.