From 7d690dde8a9e879c1444d8df832847063f07854f Mon Sep 17 00:00:00 2001 From: Sanford Student Date: Mon, 25 Jul 2016 12:50:02 -0400 Subject: [PATCH 1/5] adding request cache for milestones --- common/djangoapps/util/milestones_helpers.py | 58 ++++++++++++-------- lms/djangoapps/gating/api.py | 7 +-- openedx/core/lib/gating/api.py | 1 - 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/common/djangoapps/util/milestones_helpers.py b/common/djangoapps/util/milestones_helpers.py index b30c4a21d9..c4c3b9fe5b 100644 --- a/common/djangoapps/util/milestones_helpers.py +++ b/common/djangoapps/util/milestones_helpers.py @@ -9,13 +9,19 @@ from django.utils.translation import ugettext as _ from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey +from milestones import api as milestones_api +from milestones.exceptions import InvalidMilestoneRelationshipTypeException +from milestones.models import MilestoneRelationshipType from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from xmodule.modulestore.django import modulestore +import request_cache NAMESPACE_CHOICES = { 'ENTRANCE_EXAM': 'entrance_exams' } +REQUEST_CACHE_NAME = "milestones" + def get_namespace_choices(): """ @@ -49,7 +55,6 @@ def add_prerequisite_course(course_key, prerequisite_course_key): """ if not is_prerequisite_courses_enabled(): return None - from milestones import api as milestones_api milestone_name = _('Course {course_id} requires {prerequisite_course_id}').format( course_id=unicode(course_key), prerequisite_course_id=unicode(prerequisite_course_key) @@ -73,7 +78,6 @@ def remove_prerequisite_course(course_key, milestone): """ if not is_prerequisite_courses_enabled(): return None - from milestones import api as milestones_api milestones_api.remove_course_milestone( course_key, milestone, @@ -89,7 +93,6 @@ def set_prerequisite_courses(course_key, prerequisite_course_keys): """ if not is_prerequisite_courses_enabled(): return None - from milestones import api as milestones_api #remove any existing requirement milestones with this pre-requisite course as requirement course_milestones = milestones_api.get_course_milestones(course_key=course_key, relationship="requires") if course_milestones: @@ -123,7 +126,6 @@ def get_pre_requisite_courses_not_completed(user, enrolled_courses): # pylint: if not is_prerequisite_courses_enabled(): return {} - from milestones import api as milestones_api pre_requisite_courses = {} for course_key in enrolled_courses: @@ -185,8 +187,6 @@ def fulfill_course_milestone(course_key, user): """ if not settings.FEATURES.get('MILESTONES_APP', False): return None - from milestones import api as milestones_api - from milestones.exceptions import InvalidMilestoneRelationshipTypeException try: course_milestones = milestones_api.get_course_milestones(course_key=course_key, relationship="fulfills") except InvalidMilestoneRelationshipTypeException: @@ -203,7 +203,6 @@ def remove_course_milestones(course_key, user, relationship): """ if not settings.FEATURES.get('MILESTONES_APP', False): return None - from milestones import api as milestones_api course_milestones = milestones_api.get_course_milestones(course_key=course_key, relationship=relationship) for milestone in course_milestones: milestones_api.remove_user_milestone({'id': user.id}, milestone) @@ -216,7 +215,6 @@ def get_required_content(course, user): """ required_content = [] if settings.FEATURES.get('MILESTONES_APP', False): - from milestones.exceptions import InvalidMilestoneRelationshipTypeException # Get all of the outstanding milestones for this course, for this user try: milestone_paths = get_course_milestones_fulfillment_paths( @@ -241,7 +239,6 @@ def milestones_achieved_by_user(user, namespace): """ if not settings.FEATURES.get('MILESTONES_APP', False): return None - from milestones import api as milestones_api return milestones_api.get_user_milestones({'id': user.id}, namespace) @@ -262,7 +259,6 @@ def seed_milestone_relationship_types(): """ if not settings.FEATURES.get('MILESTONES_APP', False): return None - from milestones.models import MilestoneRelationshipType MilestoneRelationshipType.objects.create(name='requires') MilestoneRelationshipType.objects.create(name='fulfills') @@ -291,7 +287,6 @@ def add_milestone(milestone_data): """ if not settings.FEATURES.get('MILESTONES_APP', False): return None - from milestones import api as milestones_api return milestones_api.add_milestone(milestone_data) @@ -301,7 +296,6 @@ def get_milestones(namespace): """ if not settings.FEATURES.get('MILESTONES_APP', False): return [] - from milestones import api as milestones_api return milestones_api.get_milestones(namespace) @@ -311,7 +305,6 @@ def get_milestone_relationship_types(): """ if not settings.FEATURES.get('MILESTONES_APP', False): return {} - from milestones import api as milestones_api return milestones_api.get_milestone_relationship_types() @@ -321,7 +314,6 @@ def add_course_milestone(course_id, relationship, milestone): """ if not settings.FEATURES.get('MILESTONES_APP', False): return None - from milestones import api as milestones_api return milestones_api.add_course_milestone(course_id, relationship, milestone) @@ -331,7 +323,6 @@ def get_course_milestones(course_id): """ if not settings.FEATURES.get('MILESTONES_APP', False): return [] - from milestones import api as milestones_api return milestones_api.get_course_milestones(course_id) @@ -341,7 +332,6 @@ def add_course_content_milestone(course_id, content_id, relationship, milestone) """ if not settings.FEATURES.get('MILESTONES_APP', False): return None - from milestones import api as milestones_api return milestones_api.add_course_content_milestone(course_id, content_id, relationship, milestone) @@ -351,12 +341,31 @@ def get_course_content_milestones(course_id, content_id, relationship, user_id=N """ if not settings.FEATURES.get('MILESTONES_APP', False): return [] - from milestones import api as milestones_api + + if user_id is None: + return milestones_api.get_course_content_milestones(course_id, content_id, relationship) + + request_cache_dict = request_cache.get_cache(REQUEST_CACHE_NAME) + if user_id not in request_cache_dict: + request_cache_dict[user_id] = milestones_api.get_course_content_milestones( + course_key=course_id, + user={"id": user_id} + ) + milestones_for_content = [] + if relationship == "requires": + for milestone in request_cache_dict[user_id]: + if milestone["content_id"] == content_id and milestone["requirements"]: + milestones_for_content.append(milestone) + if relationship == "fulfills": + for milestone in request_cache_dict[user_id]: + if milestone["namespace"].contains(content_id) and milestone["requirements"]: + milestones_for_content.append(milestone) + return milestones_api.get_course_content_milestones( course_id, content_id, relationship, - {'id': user_id} if user_id else None + user={"id": user_id} ) @@ -366,7 +375,6 @@ def remove_course_content_user_milestones(course_key, content_key, user, relatio """ if not settings.FEATURES.get('MILESTONES_APP', False): return [] - from milestones import api as milestones_api course_content_milestones = milestones_api.get_course_content_milestones(course_key, content_key, relationship) for milestone in course_content_milestones: @@ -379,7 +387,6 @@ def remove_content_references(content_id): """ if not settings.FEATURES.get('MILESTONES_APP', False): return None - from milestones import api as milestones_api return milestones_api.remove_content_references(content_id) @@ -398,7 +405,6 @@ def get_course_milestones_fulfillment_paths(course_id, user_id): """ if not settings.FEATURES.get('MILESTONES_APP', False): return None - from milestones import api as milestones_api return milestones_api.get_course_milestones_fulfillment_paths( course_id, user_id @@ -411,5 +417,13 @@ def add_user_milestone(user, milestone): """ if not settings.FEATURES.get('MILESTONES_APP', False): return None - from milestones import api as milestones_api return milestones_api.add_user_milestone(user, milestone) + + +def remove_user_milestone(user, milestone): + """ + Client API operation adapter/wrapper + """ + if not settings.FEATURES.get('MILESTONES_APP', False): + return None + return milestones_api.remove_user_milestone(user, milestone) diff --git a/lms/djangoapps/gating/api.py b/lms/djangoapps/gating/api.py index 4781acf346..416d70d583 100644 --- a/lms/djangoapps/gating/api.py +++ b/lms/djangoapps/gating/api.py @@ -7,10 +7,9 @@ import json from collections import defaultdict from django.contrib.auth.models import User from xmodule.modulestore.django import modulestore -from milestones import api as milestones_api from openedx.core.lib.gating import api as gating_api from lms.djangoapps.grades.module_grades import get_module_score - +from util import milestones_helpers log = logging.getLogger(__name__) @@ -81,6 +80,6 @@ def evaluate_prerequisite(course, prereq_content_key, user_id): ) if score >= min_score: - milestones_api.add_user_milestone({'id': user_id}, prereq_milestone) + milestones_helpers.add_user_milestone({'id': user_id}, prereq_milestone) else: - milestones_api.remove_user_milestone({'id': user_id}, prereq_milestone) + milestones_helpers.remove_user_milestone({'id': user_id}, prereq_milestone) diff --git a/openedx/core/lib/gating/api.py b/openedx/core/lib/gating/api.py index c4acebc1fa..77506444e9 100644 --- a/openedx/core/lib/gating/api.py +++ b/openedx/core/lib/gating/api.py @@ -9,7 +9,6 @@ from opaque_keys.edx.keys import UsageKey from xmodule.modulestore.django import modulestore from openedx.core.lib.gating.exceptions import GatingValidationError - log = logging.getLogger(__name__) # This is used to namespace gating-specific milestones From d9e0ba45cc7fd4d85d0d26e807b30f6ddb370fc4 Mon Sep 17 00:00:00 2001 From: Sanford Student Date: Tue, 26 Jul 2016 10:50:02 -0400 Subject: [PATCH 2/5] first round of self review and code review --- common/djangoapps/util/milestones_helpers.py | 24 +++++++------------ .../transformers/tests/test_milestones.py | 4 ++++ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/common/djangoapps/util/milestones_helpers.py b/common/djangoapps/util/milestones_helpers.py index c4c3b9fe5b..2610fa485c 100644 --- a/common/djangoapps/util/milestones_helpers.py +++ b/common/djangoapps/util/milestones_helpers.py @@ -338,6 +338,8 @@ def add_course_content_milestone(course_id, content_id, relationship, milestone) def get_course_content_milestones(course_id, content_id, relationship, user_id=None): """ Client API operation adapter/wrapper + Uses the request cache to store all of a user's + milestones """ if not settings.FEATURES.get('MILESTONES_APP', False): return [] @@ -347,26 +349,16 @@ def get_course_content_milestones(course_id, content_id, relationship, user_id=N request_cache_dict = request_cache.get_cache(REQUEST_CACHE_NAME) if user_id not in request_cache_dict: - request_cache_dict[user_id] = milestones_api.get_course_content_milestones( + request_cache_dict[user_id] = {} + + if relationship not in request_cache_dict[user_id]: + request_cache_dict[user_id]['requires'] = milestones_api.get_course_content_milestones( course_key=course_id, + relationship=relationship, user={"id": user_id} ) - milestones_for_content = [] - if relationship == "requires": - for milestone in request_cache_dict[user_id]: - if milestone["content_id"] == content_id and milestone["requirements"]: - milestones_for_content.append(milestone) - if relationship == "fulfills": - for milestone in request_cache_dict[user_id]: - if milestone["namespace"].contains(content_id) and milestone["requirements"]: - milestones_for_content.append(milestone) - return milestones_api.get_course_content_milestones( - course_id, - content_id, - relationship, - user={"id": user_id} - ) + return [m for m in request_cache_dict[user_id][relationship] if m['content_id'] == content_id] def remove_course_content_user_milestones(course_key, content_key, user, relationship): diff --git a/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py b/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py index a2084cbcf6..21aa6a3c77 100644 --- a/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py +++ b/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py @@ -10,6 +10,7 @@ from lms.djangoapps.course_blocks.transformers.tests.helpers import CourseStruct from milestones.tests.utils import MilestonesTestCaseMixin from opaque_keys.edx.keys import UsageKey from openedx.core.lib.gating import api as gating_api +from request_cache.middleware import RequestCache from student.tests.factories import CourseEnrollmentFactory from ..milestones import MilestonesTransformer @@ -160,6 +161,9 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM self.setup_gated_section(self.blocks[gated_block_ref], self.blocks[gating_block_ref]) self.get_blocks_and_check_against_expected(self.user, expected_blocks_before_completion) + # We clear the request cache to simulate a new request in the LMS. + RequestCache.clear_request_cache() + # mock the api that the lms gating api calls to get the score for each block to always return 1 (ie 100%) with patch('gating.api.get_module_score', Mock(return_value=1)): From c3d829f2c5cbd39cd7532d2b7e4a423d4436eceb Mon Sep 17 00:00:00 2001 From: Sanford Student Date: Tue, 26 Jul 2016 11:11:59 -0400 Subject: [PATCH 3/5] adding query counts --- .../blocks/transformers/tests/test_milestones.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py b/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py index 21aa6a3c77..6af2d15c77 100644 --- a/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py +++ b/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py @@ -10,8 +10,8 @@ from lms.djangoapps.course_blocks.transformers.tests.helpers import CourseStruct from milestones.tests.utils import MilestonesTestCaseMixin from opaque_keys.edx.keys import UsageKey from openedx.core.lib.gating import api as gating_api -from request_cache.middleware import RequestCache from student.tests.factories import CourseEnrollmentFactory +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from ..milestones import MilestonesTransformer from ...api import get_course_blocks @@ -20,7 +20,7 @@ from ...api import get_course_blocks @attr('shard_3') @ddt.ddt @patch.dict('django.conf.settings.FEATURES', {'ENABLE_SPECIAL_EXAMS': True, 'MILESTONES_APP': True}) -class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseMixin): +class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseMixin, ModuleStoreTestCase): """ Test behavior of ProctoredExamTransformer """ @@ -159,10 +159,12 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM """ self.course.enable_subsection_gating = True self.setup_gated_section(self.blocks[gated_block_ref], self.blocks[gating_block_ref]) - self.get_blocks_and_check_against_expected(self.user, expected_blocks_before_completion) - # We clear the request cache to simulate a new request in the LMS. - RequestCache.clear_request_cache() + with self.assertNumQueries(3): + self.get_blocks_and_check_against_expected(self.user, expected_blocks_before_completion) + + # clear the request cache to simulate a new request + self.clear_caches() # mock the api that the lms gating api calls to get the score for each block to always return 1 (ie 100%) with patch('gating.api.get_module_score', Mock(return_value=1)): @@ -173,8 +175,8 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM self.course, UsageKey.from_string(unicode(self.blocks[gating_block_child].location)), self.user.id) - - self.get_blocks_and_check_against_expected(self.user, self.ALL_BLOCKS_EXCEPT_SPECIAL) + with self.assertNumQueries(2): + self.get_blocks_and_check_against_expected(self.user, self.ALL_BLOCKS_EXCEPT_SPECIAL) def test_staff_access(self): """ From d996c2a3cdee4c5be5845ac2f1be0bb25dbe1ad5 Mon Sep 17 00:00:00 2001 From: Sanford Student Date: Tue, 26 Jul 2016 11:39:19 -0400 Subject: [PATCH 4/5] per cliff's review --- common/djangoapps/util/milestones_helpers.py | 43 ++++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/common/djangoapps/util/milestones_helpers.py b/common/djangoapps/util/milestones_helpers.py index 2610fa485c..8b79d29344 100644 --- a/common/djangoapps/util/milestones_helpers.py +++ b/common/djangoapps/util/milestones_helpers.py @@ -35,15 +35,14 @@ def is_entrance_exams_enabled(): Checks to see if the Entrance Exams feature is enabled Use this operation instead of checking the feature flag all over the place """ - return settings.FEATURES.get('ENTRANCE_EXAMS', False) + return settings.FEATURES.get('ENTRANCE_EXAMS') def is_prerequisite_courses_enabled(): """ Returns boolean indicating prerequisite courses enabled system wide or not. """ - return settings.FEATURES.get('ENABLE_PREREQUISITE_COURSES', False) \ - and settings.FEATURES.get('MILESTONES_APP', False) + return settings.FEATURES.get('ENABLE_PREREQUISITE_COURSES') and settings.FEATURES.get('MILESTONES_APP') def add_prerequisite_course(course_key, prerequisite_course_key): @@ -185,7 +184,7 @@ def fulfill_course_milestone(course_key, user): Marks the course specified by the given course_key as complete for the given user. If any other courses require this course as a prerequisite, their milestones will be appropriately updated. """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return None try: course_milestones = milestones_api.get_course_milestones(course_key=course_key, relationship="fulfills") @@ -201,7 +200,7 @@ def remove_course_milestones(course_key, user, relationship): """ Remove all user milestones for the course specified by course_key. """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return None course_milestones = milestones_api.get_course_milestones(course_key=course_key, relationship=relationship) for milestone in course_milestones: @@ -214,7 +213,7 @@ def get_required_content(course, user): and if those milestones can be fulfilled via completion of a particular course content module """ required_content = [] - if settings.FEATURES.get('MILESTONES_APP', False): + if settings.FEATURES.get('MILESTONES_APP'): # Get all of the outstanding milestones for this course, for this user try: milestone_paths = get_course_milestones_fulfillment_paths( @@ -237,7 +236,7 @@ def milestones_achieved_by_user(user, namespace): """ It would fetch list of milestones completed by user """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return None return milestones_api.get_user_milestones({'id': user.id}, namespace) @@ -257,7 +256,7 @@ def seed_milestone_relationship_types(): """ Helper method to pre-populate MRTs so the tests can run """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return None MilestoneRelationshipType.objects.create(name='requires') MilestoneRelationshipType.objects.create(name='fulfills') @@ -285,7 +284,7 @@ def add_milestone(milestone_data): """ Client API operation adapter/wrapper """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return None return milestones_api.add_milestone(milestone_data) @@ -294,7 +293,7 @@ def get_milestones(namespace): """ Client API operation adapter/wrapper """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return [] return milestones_api.get_milestones(namespace) @@ -303,7 +302,7 @@ def get_milestone_relationship_types(): """ Client API operation adapter/wrapper """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return {} return milestones_api.get_milestone_relationship_types() @@ -312,7 +311,7 @@ def add_course_milestone(course_id, relationship, milestone): """ Client API operation adapter/wrapper """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return None return milestones_api.add_course_milestone(course_id, relationship, milestone) @@ -321,7 +320,7 @@ def get_course_milestones(course_id): """ Client API operation adapter/wrapper """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return [] return milestones_api.get_course_milestones(course_id) @@ -330,7 +329,7 @@ def add_course_content_milestone(course_id, content_id, relationship, milestone) """ Client API operation adapter/wrapper """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return None return milestones_api.add_course_content_milestone(course_id, content_id, relationship, milestone) @@ -341,7 +340,7 @@ def get_course_content_milestones(course_id, content_id, relationship, user_id=N Uses the request cache to store all of a user's milestones """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return [] if user_id is None: @@ -352,7 +351,7 @@ def get_course_content_milestones(course_id, content_id, relationship, user_id=N request_cache_dict[user_id] = {} if relationship not in request_cache_dict[user_id]: - request_cache_dict[user_id]['requires'] = milestones_api.get_course_content_milestones( + request_cache_dict[user_id][relationship] = milestones_api.get_course_content_milestones( course_key=course_id, relationship=relationship, user={"id": user_id} @@ -365,7 +364,7 @@ def remove_course_content_user_milestones(course_key, content_key, user, relatio """ Removes the specified User-Milestone link from the system for the specified course content module. """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return [] course_content_milestones = milestones_api.get_course_content_milestones(course_key, content_key, relationship) @@ -377,14 +376,14 @@ def remove_content_references(content_id): """ Client API operation adapter/wrapper """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return None return milestones_api.remove_content_references(content_id) def any_unfulfilled_milestones(course_id, user_id): """ Returns a boolean if user has any unfulfilled milestones """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return False return bool( get_course_milestones_fulfillment_paths(course_id, {"id": user_id}) @@ -395,7 +394,7 @@ def get_course_milestones_fulfillment_paths(course_id, user_id): """ Client API operation adapter/wrapper """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return None return milestones_api.get_course_milestones_fulfillment_paths( course_id, @@ -407,7 +406,7 @@ def add_user_milestone(user, milestone): """ Client API operation adapter/wrapper """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return None return milestones_api.add_user_milestone(user, milestone) @@ -416,6 +415,6 @@ def remove_user_milestone(user, milestone): """ Client API operation adapter/wrapper """ - if not settings.FEATURES.get('MILESTONES_APP', False): + if not settings.FEATURES.get('MILESTONES_APP'): return None return milestones_api.remove_user_milestone(user, milestone) From f02889e8d2001712df950b3c9df24efed1e0cf27 Mon Sep 17 00:00:00 2001 From: Sanford Student Date: Tue, 26 Jul 2016 14:11:50 -0400 Subject: [PATCH 5/5] removing redundant inherit --- .../course_api/blocks/transformers/tests/test_milestones.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py b/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py index 6af2d15c77..92c0e81fe5 100644 --- a/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py +++ b/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py @@ -11,7 +11,6 @@ from milestones.tests.utils import MilestonesTestCaseMixin from opaque_keys.edx.keys import UsageKey from openedx.core.lib.gating import api as gating_api from student.tests.factories import CourseEnrollmentFactory -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from ..milestones import MilestonesTransformer from ...api import get_course_blocks @@ -20,7 +19,7 @@ from ...api import get_course_blocks @attr('shard_3') @ddt.ddt @patch.dict('django.conf.settings.FEATURES', {'ENABLE_SPECIAL_EXAMS': True, 'MILESTONES_APP': True}) -class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseMixin, ModuleStoreTestCase): +class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseMixin): """ Test behavior of ProctoredExamTransformer """