Commit 9209eef7 by Sanford Student

add grading policy hash to transformer

parent 2ca7484c
...@@ -7,9 +7,11 @@ import pytz ...@@ -7,9 +7,11 @@ import pytz
import random import random
import ddt import ddt
from copy import deepcopy
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import check_mongo_calls from xmodule.modulestore.tests.factories import check_mongo_calls
...@@ -39,6 +41,27 @@ class GradesTransformerTestCase(CourseStructureTestCase): ...@@ -39,6 +41,27 @@ class GradesTransformerTestCase(CourseStructureTestCase):
self.student = UserFactory.create(is_staff=False, username=u'test_student', password=password) self.student = UserFactory.create(is_staff=False, username=u'test_student', password=password)
self.client.login(username=self.student.username, password=password) self.client.login(username=self.student.username, password=password)
def _update_course_grading_policy(self, course, grading_policy):
"""
Helper to update a course's grading policy in the modulestore.
"""
course.set_grading_policy(grading_policy)
modulestore().update_item(course, self.user.id)
def _validate_grading_policy_hash(self, course_location, grading_policy_hash):
"""
Helper to retrieve the course at the given course_location and
assert that its hashed grading policy (from the grades transformer)
is as expected.
"""
block_structure = get_course_blocks(self.student, course_location, self.transformers)
self.assert_collected_transformer_block_fields(
block_structure,
course_location,
self.TRANSFORMER_CLASS_TO_TEST,
grading_policy_hash=grading_policy_hash,
)
def assert_collected_xblock_fields(self, block_structure, usage_key, **expectations): def assert_collected_xblock_fields(self, block_structure, usage_key, **expectations):
""" """
Given a block structure, a block usage key, and a list of keyword Given a block structure, a block usage key, and a list of keyword
...@@ -330,6 +353,38 @@ class GradesTransformerTestCase(CourseStructureTestCase): ...@@ -330,6 +353,38 @@ class GradesTransformerTestCase(CourseStructureTestCase):
block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers) block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers)
self.assertIsNotNone(block_structure.get_xblock_field(blocks[u'course'].location, u'course_version')) self.assertIsNotNone(block_structure.get_xblock_field(blocks[u'course'].location, u'course_version'))
def test_grading_policy_collected(self):
# the calculated hash of the original and updated grading policies of the test course
original_grading_policy_hash = u'ChVp0lHGQGCevD0t4njna/C44zQ='
updated_grading_policy_hash = u'TsbX04qWOy1WRnC0NHy+94upPd4='
blocks = self.build_course_with_problems()
course_block = blocks[u'course']
self._validate_grading_policy_hash(
course_block.location,
original_grading_policy_hash
)
# make sure the hash changes when the course grading policy is edited
grading_policy_with_updates = course_block.grading_policy
original_grading_policy = deepcopy(grading_policy_with_updates)
for section in grading_policy_with_updates['GRADER']:
self.assertNotEqual(section['weight'], 0.25)
section['weight'] = 0.25
self._update_course_grading_policy(course_block, grading_policy_with_updates)
self._validate_grading_policy_hash(
course_block.location,
updated_grading_policy_hash
)
# reset the grading policy and ensure the hash matches the original
self._update_course_grading_policy(course_block, original_grading_policy)
self._validate_grading_policy_hash(
course_block.location,
original_grading_policy_hash
)
class MultiProblemModulestoreAccessTestCase(CourseStructureTestCase, SharedModuleStoreTestCase): class MultiProblemModulestoreAccessTestCase(CourseStructureTestCase, SharedModuleStoreTestCase):
""" """
......
""" """
Grades Transformer Grades Transformer
""" """
from base64 import b64encode
from django.test.client import RequestFactory from django.test.client import RequestFactory
from functools import reduce as functools_reduce from functools import reduce as functools_reduce
from hashlib import sha1
from logging import getLogger from logging import getLogger
import json
from courseware.model_data import FieldDataCache from courseware.model_data import FieldDataCache
from courseware.module_render import get_module_for_descriptor from courseware.module_render import get_module_for_descriptor
...@@ -64,6 +67,7 @@ class GradesTransformer(BlockStructureTransformer): ...@@ -64,6 +67,7 @@ class GradesTransformer(BlockStructureTransformer):
filter_by=lambda block_key: block_key.block_type == 'sequential', filter_by=lambda block_key: block_key.block_type == 'sequential',
) )
cls._collect_explicit_graded(block_structure) cls._collect_explicit_graded(block_structure)
cls._collect_grading_policy_hash(block_structure)
def transform(self, block_structure, usage_context): def transform(self, block_structure, usage_context):
""" """
...@@ -128,6 +132,35 @@ class GradesTransformer(BlockStructureTransformer): ...@@ -128,6 +132,35 @@ class GradesTransformer(BlockStructureTransformer):
if max_score is None: if max_score is None:
log.warning("GradesTransformer: max_score is None for {}".format(module.location)) log.warning("GradesTransformer: max_score is None for {}".format(module.location))
@classmethod
def _collect_grading_policy_hash(cls, block_structure):
"""
Collect a hash of the course's grading policy, storing it as a
`transformer_block_field` associated with the `GradesTransformer`.
"""
def _hash_grading_policy(policy):
"""
Creates a hash from the course grading policy.
The keys are sorted in order to make the hash
agnostic to the ordering of the policy coming in.
"""
ordered_policy = json.dumps(
policy,
separators=(',', ':'), # Remove spaces from separators for more compact representation
sort_keys=True,
)
return b64encode(sha1(ordered_policy).digest())
course_location = block_structure.root_block_usage_key
course_block = block_structure.get_xblock(course_location)
grading_policy = course_block.grading_policy
block_structure.set_transformer_block_field(
course_block.location,
cls,
"grading_policy_hash",
_hash_grading_policy(grading_policy)
)
@staticmethod @staticmethod
def _iter_scorable_xmodules(block_structure): def _iter_scorable_xmodules(block_structure):
""" """
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment