def test_entrance_exam_milestone_removal(self): """ Unit Test: test removal of entrance exam milestone content """ parent_locator = unicode(self.course.location) created_block = create_xblock( parent_locator=parent_locator, user=self.user, category='chapter', display_name=('Entrance Exam'), is_entrance_exam=True ) add_entrance_exam_milestone(self.course.id, created_block) content_milestones = milestones_helpers.get_course_content_milestones( unicode(self.course.id), unicode(created_block.location), self.milestone_relationship_types['FULFILLS'] ) self.assertEqual(len(content_milestones), 1) user = UserFactory() request = RequestFactory().request() request.user = user remove_entrance_exam_milestone_reference(request, self.course.id) content_milestones = milestones_helpers.get_course_content_milestones( unicode(self.course.id), unicode(created_block.location), self.milestone_relationship_types['FULFILLS'] ) self.assertEqual(len(content_milestones), 0)
def test_entrance_exam_milestone_removal(self): """ Unit Test: test removal of entrance exam milestone content """ parent_locator = six.text_type(self.course.location) created_block = create_xblock(parent_locator=parent_locator, user=self.user, category='chapter', display_name=('Entrance Exam'), is_entrance_exam=True) add_entrance_exam_milestone(self.course.id, created_block) content_milestones = milestones_helpers.get_course_content_milestones( six.text_type(self.course.id), six.text_type(created_block.location), self.milestone_relationship_types['FULFILLS']) self.assertEqual(len(content_milestones), 1) user = UserFactory() request = RequestFactory().request() request.user = user remove_entrance_exam_milestone_reference(request, self.course.id) content_milestones = milestones_helpers.get_course_content_milestones( six.text_type(self.course.id), six.text_type(created_block.location), self.milestone_relationship_types['FULFILLS']) self.assertEqual(len(content_milestones), 0)
def get_content_milestones(self, request, course_key): """ Returns dict of subsections with prerequisites and whether the prerequisite has been completed or not """ def _get_key_of_prerequisite(namespace): return re.sub('.gating', '', namespace) all_course_milestones = get_course_content_milestones(course_key) uncompleted_prereqs = { milestone['content_id'] for milestone in get_course_content_milestones( course_key, user_id=request.user.id) } gated_content = { milestone['content_id']: { 'completed_prereqs': milestone['content_id'] not in uncompleted_prereqs, 'prerequisite': _get_key_of_prerequisite(milestone['namespace']) } for milestone in all_course_milestones } return gated_content
def evaluate_entrance_exam(course, block, user_id): """ Update milestone fulfillments for the specified content module """ # Fulfillment Use Case: Entrance Exam # If this module is part of an entrance exam, we'll need to see if the student # has reached the point at which they can collect the associated milestone if milestones_helpers.is_entrance_exams_enabled(): entrance_exam_enabled = getattr(course, 'entrance_exam_enabled', False) in_entrance_exam = getattr(block, 'in_entrance_exam', False) if entrance_exam_enabled and in_entrance_exam: # We don't have access to the true request object in this context, but we can use a mock request = RequestFactory().request() request.user = User.objects.get(id=user_id) exam_pct = get_entrance_exam_score(request, course) if exam_pct >= course.entrance_exam_minimum_score_pct: exam_key = UsageKey.from_string(course.entrance_exam_id) relationship_types = milestones_helpers.get_milestone_relationship_types( ) content_milestones = milestones_helpers.get_course_content_milestones( course.id, exam_key, relationship=relationship_types['FULFILLS']) # Add each milestone to the user's set... user = {'id': request.user.id} for milestone in content_milestones: milestones_helpers.add_user_milestone(user, milestone)
def _fulfill_content_milestones(user, course_key, content_key): """ Internal helper to handle milestone fulfillments for the specified content module """ # Fulfillment Use Case: Entrance Exam # If this module is part of an entrance exam, we'll need to see if the student # has reached the point at which they can collect the associated milestone if settings.FEATURES.get('ENTRANCE_EXAMS', False): course = modulestore().get_course(course_key) content = modulestore().get_item(content_key) entrance_exam_enabled = getattr(course, 'entrance_exam_enabled', False) in_entrance_exam = getattr(content, 'in_entrance_exam', False) if entrance_exam_enabled and in_entrance_exam: # We don't have access to the true request object in this context, but we can use a mock request = RequestFactory().request() request.user = user exam_pct = get_entrance_exam_score(request, course) if exam_pct >= course.entrance_exam_minimum_score_pct: exam_key = UsageKey.from_string(course.entrance_exam_id) relationship_types = milestones_helpers.get_milestone_relationship_types() content_milestones = milestones_helpers.get_course_content_milestones( course_key, exam_key, relationship=relationship_types['FULFILLS'] ) # Add each milestone to the user's set... user = {'id': request.user.id} for milestone in content_milestones: milestones_helpers.add_user_milestone(user, milestone)
def evaluate_entrance_exam(course_grade, user): """ Evaluates any entrance exam milestone relationships attached to the given course. If the course_grade meets the minimum score required, the dependent milestones will be marked fulfilled for the user. """ course = course_grade.course_data.course if ENTRANCE_EXAMS.is_enabled() and getattr(course, 'entrance_exam_enabled', False): if get_entrance_exam_content(user, course): exam_chapter_key = get_entrance_exam_usage_key(course) exam_score_ratio = get_entrance_exam_score_ratio( course_grade, exam_chapter_key) if exam_score_ratio >= course.entrance_exam_minimum_score_pct: relationship_types = milestones_helpers.get_milestone_relationship_types( ) content_milestones = milestones_helpers.get_course_content_milestones( course.id, exam_chapter_key, relationship=relationship_types['FULFILLS']) # Mark each entrance exam dependent milestone as fulfilled by the user. for milestone in content_milestones: milestones_helpers.add_user_milestone({'id': user.id}, milestone)
def evaluate_entrance_exam(course, block, user_id): """ Update milestone fulfillments for the specified content module """ # Fulfillment Use Case: Entrance Exam # If this module is part of an entrance exam, we'll need to see if the student # has reached the point at which they can collect the associated milestone if milestones_helpers.is_entrance_exams_enabled(): entrance_exam_enabled = getattr(course, 'entrance_exam_enabled', False) in_entrance_exam = getattr(block, 'in_entrance_exam', False) if entrance_exam_enabled and in_entrance_exam: # We don't have access to the true request object in this context, but we can use a mock request = RequestFactory().request() request.user = User.objects.get(id=user_id) exam_pct = get_entrance_exam_score(request, course) if exam_pct >= course.entrance_exam_minimum_score_pct: exam_key = UsageKey.from_string(course.entrance_exam_id) relationship_types = milestones_helpers.get_milestone_relationship_types() content_milestones = milestones_helpers.get_course_content_milestones( course.id, exam_key, relationship=relationship_types['FULFILLS'] ) # Add each milestone to the user's set... user = {'id': request.user.id} for milestone in content_milestones: milestones_helpers.add_user_milestone(user, milestone)
def test_import_delete_pre_exiting_entrance_exam(self): """ Check that pre existed entrance exam content should be overwrite with the imported course. """ exam_url = '/course/{}/entrance_exam/'.format(unicode(self.course.id)) resp = self.client.post(exam_url, {'entrance_exam_minimum_score_pct': 0.5}, http_accept='application/json') self.assertEqual(resp.status_code, 201) # Reload the test course now that the exam module has been added self.course = modulestore().get_course(self.course.id) metadata = CourseMetadata.fetch_all(self.course) self.assertTrue(metadata['entrance_exam_enabled']) self.assertIsNotNone(metadata['entrance_exam_minimum_score_pct']) self.assertEqual(metadata['entrance_exam_minimum_score_pct']['value'], 0.5) self.assertTrue(len(milestones_helpers.get_course_milestones(unicode(self.course.id)))) content_milestones = milestones_helpers.get_course_content_milestones( unicode(self.course.id), metadata['entrance_exam_id']['value'], milestones_helpers.get_milestone_relationship_types()['FULFILLS'] ) self.assertTrue(len(content_milestones)) # Now import entrance exam course with open(self.entrance_exam_tar) as gtar: args = {"name": self.entrance_exam_tar, "course-data": [gtar]} resp = self.client.post(self.url, args) self.assertEquals(resp.status_code, 200) course = self.store.get_course(self.course.id) self.assertIsNotNone(course) self.assertEquals(course.entrance_exam_enabled, True) self.assertEquals(course.entrance_exam_minimum_score_pct, 0.7)
def test_get_course_content_milestones_returns_none_when_app_disabled(self): response = milestones_helpers.get_course_content_milestones( unicode(self.course.id), 'i4x://doesnt/matter/for/this/test', 'requires' ) self.assertEqual(len(response), 0)
def test_import_delete_pre_exiting_entrance_exam(self): """ Check that pre existed entrance exam content should be overwrite with the imported course. """ exam_url = '/course/{}/entrance_exam/'.format(unicode(self.course.id)) resp = self.client.post(exam_url, {'entrance_exam_minimum_score_pct': 0.5}, http_accept='application/json') self.assertEqual(resp.status_code, 201) # Reload the test course now that the exam module has been added self.course = modulestore().get_course(self.course.id) metadata = CourseMetadata.fetch_all(self.course) self.assertTrue(metadata['entrance_exam_enabled']) self.assertIsNotNone(metadata['entrance_exam_minimum_score_pct']) self.assertEqual(metadata['entrance_exam_minimum_score_pct']['value'], 0.5) self.assertTrue( len( milestones_helpers.get_course_milestones( unicode(self.course.id)))) content_milestones = milestones_helpers.get_course_content_milestones( unicode(self.course.id), metadata['entrance_exam_id']['value'], milestones_helpers.get_milestone_relationship_types()['FULFILLS']) self.assertTrue(len(content_milestones)) # Now import entrance exam course with open(self.entrance_exam_tar) as gtar: args = {"name": self.entrance_exam_tar, "course-data": [gtar]} resp = self.client.post(self.url, args) self.assertEquals(resp.status_code, 200) course = self.store.get_course(self.course.id) self.assertIsNotNone(course) self.assertEquals(course.entrance_exam_enabled, True) self.assertEquals(course.entrance_exam_minimum_score_pct, 0.7)
def test_contentstore_views_entrance_exam_post(self): """ Unit Test: test_contentstore_views_entrance_exam_post """ resp = self.client.post(self.exam_url, {}, http_accept='application/json') self.assertEqual(resp.status_code, 201) resp = self.client.get(self.exam_url) self.assertEqual(resp.status_code, 200) # Reload the test course now that the exam module has been added self.course = modulestore().get_course(self.course.id) metadata = CourseMetadata.fetch_all(self.course) self.assertTrue(metadata['entrance_exam_enabled']) self.assertIsNotNone(metadata['entrance_exam_minimum_score_pct']) self.assertIsNotNone(metadata['entrance_exam_id']['value']) self.assertTrue( len( milestones_helpers.get_course_milestones( six.text_type(self.course.id)))) content_milestones = milestones_helpers.get_course_content_milestones( six.text_type(self.course.id), metadata['entrance_exam_id']['value'], self.milestone_relationship_types['FULFILLS']) self.assertTrue(len(content_milestones))
def has_pending_milestones_for_user(block_key, usage_info): """ Test whether the current user has any unfulfilled milestones preventing them from accessing this block. """ return bool( milestones_helpers.get_course_content_milestones( unicode(block_key.course_key), unicode(block_key), 'requires', usage_info.user.id))
def get_content_milestones_old(self, request, course_key): """ Returns dict of subsections with prerequisites and whether the prerequisite has been completed or not """ all_course_prereqs = get_course_content_milestones(course_key) content_ids_of_unfulfilled_prereqs = { milestone['content_id'] for milestone in get_course_content_milestones(course_key, user_id=request.user.id) } course_content_milestones = { milestone['content_id']: { 'completed_prereqs': milestone['content_id'] not in content_ids_of_unfulfilled_prereqs } for milestone in all_course_prereqs } return course_content_milestones
def has_pending_milestones_for_user(block_key, usage_info): """ Test whether the current user has any unfulfilled milestones preventing them from accessing this block. """ return bool(milestones_helpers.get_course_content_milestones( unicode(block_key.course_key), unicode(block_key), 'requires', usage_info.user.id ))
def compute_is_prereq_met(content_id, user_id, recalc_on_unmet=False): """ Returns true if the prequiste has been met for a given milestone. Will recalculate the subsection grade if specified and prereq unmet Arguments: content_id (BlockUsageLocator): BlockUsageLocator for the content user_id: The id of the user recalc_on_unmet: Recalculate the grade if prereq has not yet been met Returns: tuple: True|False, prereq_meta_info = { 'url': prereq_url|None, 'display_name': prereq_name|None} """ course_key = content_id.course_key # if unfullfilled milestones exist it means prereq has not been met unfulfilled_milestones = milestones_helpers.get_course_content_milestones( course_key, content_id, 'requires', user_id ) prereq_met = not unfulfilled_milestones prereq_meta_info = {'url': None, 'display_name': None} if prereq_met or not recalc_on_unmet: return prereq_met, prereq_meta_info milestone = unfulfilled_milestones[0] student = User.objects.get(id=user_id) store = modulestore() with store.bulk_operations(course_key): subsection_usage_key = UsageKey.from_string(_get_gating_block_id(milestone)) subsection = store.get_item(subsection_usage_key) prereq_meta_info = { 'url': reverse('jump_to', kwargs={'course_id': course_key, 'location': subsection_usage_key}), 'display_name': subsection.display_name } try: subsection_structure = get_course_blocks(student, subsection_usage_key) if any(subsection_structure): subsection_grade_factory = SubsectionGradeFactory(student, course_structure=subsection_structure) if subsection_usage_key in subsection_structure: # this will force a recalcuation of the subsection grade subsection_grade = subsection_grade_factory.update(subsection_structure[subsection_usage_key], persist_grade=False) prereq_met = update_milestone(milestone, subsection_grade, milestone, user_id) except ItemNotFoundError as err: log.warning("Could not find course_block for subsection=%s error=%s", subsection_usage_key, err) return prereq_met, prereq_meta_info
def get_content_milestones(self, request, course_key): """ Returns dict of subsections with prerequisites and whether the prerequisite has been completed or not """ def _get_key_of_prerequisite(namespace): return re.sub('.gating', '', namespace) all_course_milestones = get_course_content_milestones(course_key) uncompleted_prereqs = { milestone['content_id'] for milestone in get_course_content_milestones(course_key, user_id=request.user.id) } gated_content = { milestone['content_id']: { 'completed_prereqs': milestone['content_id'] not in uncompleted_prereqs, 'prerequisite': _get_key_of_prerequisite(milestone['namespace']) } for milestone in all_course_milestones } return gated_content
def compute_is_prereq_met(content_id, user_id, recalc_on_unmet=False): """ Returns true if the prequiste has been met for a given milestone. Will recalculate the subsection grade if specified and prereq unmet Arguments: content_id (BlockUsageLocator): BlockUsageLocator for the content user_id: The id of the user recalc_on_unmet: Recalculate the grade if prereq has not yet been met Returns: tuple: True|False, prereq_meta_info = { 'url': prereq_url|None, 'display_name': prereq_name|None} """ course_key = content_id.course_key # if unfullfilled milestones exist it means prereq has not been met unfulfilled_milestones = milestones_helpers.get_course_content_milestones( course_key, content_id, 'requires', user_id) prereq_met = not unfulfilled_milestones prereq_meta_info = {'url': None, 'display_name': None} if prereq_met or not recalc_on_unmet: return prereq_met, prereq_meta_info milestone = unfulfilled_milestones[0] student = User.objects.get(id=user_id) store = modulestore() with store.bulk_operations(course_key): subsection_usage_key = UsageKey.from_string( _get_gating_block_id(milestone)) subsection = store.get_item(subsection_usage_key) prereq_meta_info = { 'url': reverse('jump_to', kwargs={ 'course_id': course_key, 'location': subsection_usage_key }), 'display_name': subsection.display_name, 'id': str(subsection_usage_key) } prereq_met = update_milestone(milestone, subsection_usage_key, milestone, student) return prereq_met, prereq_meta_info
def _can_access_descriptor_with_milestones(user, descriptor, course_key): """ Returns if the object is blocked by an unfulfilled milestone. Args: user: the user trying to access this content descriptor: the object being accessed course_key: key for the course for this descriptor """ if milestones_helpers.get_course_content_milestones(course_key, unicode(descriptor.location), 'requires', user.id): debug("Deny: user has not completed all milestones for content") return ACCESS_DENIED else: return ACCESS_GRANTED
def test_entrance_exam_milestone_addition(self): """ Unit Test: test addition of entrance exam milestone content """ parent_locator = unicode(self.course.location) created_block = create_xblock(parent_locator=parent_locator, user=self.user, category='chapter', display_name=('Entrance Exam'), is_entrance_exam=True) add_entrance_exam_milestone(self.course.id, created_block) content_milestones = milestones_helpers.get_course_content_milestones( unicode(self.course.id), unicode(created_block.location), self.milestone_relationship_types['FULFILLS']) self.assertTrue(len(content_milestones)) self.assertEqual( len(milestones_helpers.get_course_milestones(self.course.id)), 1)
def compute_is_prereq_met(content_id, user_id, recalc_on_unmet=False): """ Returns true if the prequiste has been met for a given milestone. Will recalculate the subsection grade if specified and prereq unmet Arguments: content_id (BlockUsageLocator): BlockUsageLocator for the content user_id: The id of the user recalc_on_unmet: Recalculate the grade if prereq has not yet been met Returns: tuple: True|False, prereq_meta_info = { 'url': prereq_url|None, 'display_name': prereq_name|None} """ course_key = content_id.course_key # if unfullfilled milestones exist it means prereq has not been met unfulfilled_milestones = milestones_helpers.get_course_content_milestones( course_key, content_id, 'requires', user_id ) prereq_met = not unfulfilled_milestones prereq_meta_info = {'url': None, 'display_name': None} if prereq_met or not recalc_on_unmet: return prereq_met, prereq_meta_info milestone = unfulfilled_milestones[0] student = User.objects.get(id=user_id) store = modulestore() with store.bulk_operations(course_key): subsection_usage_key = UsageKey.from_string(_get_gating_block_id(milestone)) subsection = store.get_item(subsection_usage_key) prereq_meta_info = { 'url': reverse('jump_to', kwargs={'course_id': course_key, 'location': subsection_usage_key}), 'display_name': subsection.display_name } prereq_met = update_milestone(milestone, subsection_usage_key, milestone, student) return prereq_met, prereq_meta_info
def test_entrance_exam_milestone_addition(self): """ Unit Test: test addition of entrance exam milestone content """ parent_locator = unicode(self.course.location) created_block = create_xblock( parent_locator=parent_locator, user=self.user, category='chapter', display_name=('Entrance Exam'), is_entrance_exam=True ) add_entrance_exam_milestone(self.course.id, created_block) content_milestones = milestones_helpers.get_course_content_milestones( unicode(self.course.id), unicode(created_block.location), self.milestone_relationship_types['FULFILLS'] ) self.assertTrue(len(content_milestones)) self.assertEqual(len(milestones_helpers.get_course_milestones(self.course.id)), 1)
def evaluate_entrance_exam(course_grade, user): """ Evaluates any entrance exam milestone relationships attached to the given course. If the course_grade meets the minimum score required, the dependent milestones will be marked fulfilled for the user. """ course = course_grade.course_data.course if milestones_helpers.is_entrance_exams_enabled() and getattr(course, 'entrance_exam_enabled', False): if get_entrance_exam_content(user, course): exam_chapter_key = get_entrance_exam_usage_key(course) exam_score_ratio = get_entrance_exam_score_ratio(course_grade, exam_chapter_key) if exam_score_ratio >= course.entrance_exam_minimum_score_pct: relationship_types = milestones_helpers.get_milestone_relationship_types() content_milestones = milestones_helpers.get_course_content_milestones( course.id, exam_chapter_key, relationship=relationship_types['FULFILLS'] ) # Mark each entrance exam dependent milestone as fulfilled by the user. for milestone in content_milestones: milestones_helpers.add_user_milestone({'id': user.id}, milestone)
def test_contentstore_views_entrance_exam_post(self): """ Unit Test: test_contentstore_views_entrance_exam_post """ resp = self.client.post(self.exam_url, {}, http_accept='application/json') self.assertEqual(resp.status_code, 201) resp = self.client.get(self.exam_url) self.assertEqual(resp.status_code, 200) # Reload the test course now that the exam module has been added self.course = modulestore().get_course(self.course.id) metadata = CourseMetadata.fetch_all(self.course) self.assertTrue(metadata['entrance_exam_enabled']) self.assertIsNotNone(metadata['entrance_exam_minimum_score_pct']) self.assertIsNotNone(metadata['entrance_exam_id']['value']) self.assertTrue(len(milestones_helpers.get_course_milestones(unicode(self.course.id)))) content_milestones = milestones_helpers.get_course_content_milestones( unicode(self.course.id), metadata['entrance_exam_id']['value'], self.milestone_relationship_types['FULFILLS'] ) self.assertTrue(len(content_milestones))
def compute_is_prereq_met(content_id, user_id, recalc_on_unmet=False): """ Returns true if the prequiste has been met for a given milestone. Will recalculate the subsection grade if specified and prereq unmet Arguments: content_id (BlockUsageLocator): BlockUsageLocator for the content user_id: The id of the user recalc_on_unmet: Recalculate the grade if prereq has not yet been met Returns: tuple: True|False, prereq_meta_info = { 'url': prereq_url|None, 'display_name': prereq_name|None} """ course_key = content_id.course_key # if unfullfilled milestones exist it means prereq has not been met unfulfilled_milestones = milestones_helpers.get_course_content_milestones( course_key, content_id, 'requires', user_id) prereq_met = not unfulfilled_milestones prereq_meta_info = {'url': None, 'display_name': None} if prereq_met or not recalc_on_unmet: return prereq_met, prereq_meta_info milestone = unfulfilled_milestones[0] student = User.objects.get(id=user_id) store = modulestore() with store.bulk_operations(course_key): subsection_usage_key = UsageKey.from_string( _get_gating_block_id(milestone)) subsection = store.get_item(subsection_usage_key) prereq_meta_info = { 'url': reverse('jump_to', kwargs={ 'course_id': course_key, 'location': subsection_usage_key }), 'display_name': subsection.display_name } try: subsection_structure = get_course_blocks(student, subsection_usage_key) if any(subsection_structure): subsection_grade_factory = SubsectionGradeFactory( student, course_structure=subsection_structure) if subsection_usage_key in subsection_structure: # this will force a recalcuation of the subsection grade subsection_grade = subsection_grade_factory.update( subsection_structure[subsection_usage_key], persist_grade=False) prereq_met = update_milestone(milestone, subsection_grade, milestone, user_id) except ItemNotFoundError as err: log.warning( "Could not find course_block for subsection=%s error=%s", subsection_usage_key, err) return prereq_met, prereq_meta_info
def has_pending_milestones(self, usage_key): return bool( milestones_helpers.get_course_content_milestones( str(self.course_key), str(usage_key), 'requires', self.user.id))