def test_learning_context_key(self): """ Test CourseKey """ key = LearningContextKey.from_string('org.id/course_id/run') self.assertEqual(key.org, 'org.id') self.assertIsInstance(key, CourseKey) key = LearningContextKey.from_string('course-v1:org.id+course_id+run') self.assertEqual(key.org, 'org.id') self.assertIsInstance(key, CourseKey)
def enqueue_subsection_update(sender, **kwargs): # pylint: disable=unused-argument """ Handles the PROBLEM_WEIGHTED_SCORE_CHANGED or SUBSECTION_OVERRIDE_CHANGED signals by enqueueing a subsection update operation to occur asynchronously. """ events.grade_updated(**kwargs) context_key = LearningContextKey.from_string(kwargs['course_id']) if not context_key.is_course: return # If it's not a course, it has no subsections, so skip the subsection grading update recalculate_subsection_grade_v3.apply_async( kwargs=dict( user_id=kwargs['user_id'], anonymous_user_id=kwargs.get('anonymous_user_id'), course_id=kwargs['course_id'], usage_id=kwargs['usage_id'], only_if_higher=kwargs.get('only_if_higher'), expected_modified_time=to_timestamp(kwargs['modified']), score_deleted=kwargs.get('score_deleted', False), event_transaction_id=six.text_type(get_event_transaction_id()), event_transaction_type=six.text_type(get_event_transaction_type()), score_db_table=kwargs['score_db_table'], force_update_subsections=kwargs.get('force_update_subsections', False), ), countdown=RECALCULATE_GRADE_DELAY_SECONDS, )
def test_render_blockcompletion(self): """ Test get data with block completion """ context_key = LearningContextKey.from_string(str(self.course.id)) for item in self.items: usage_key = item.scope_ids.usage_id completion = models.BlockCompletion.objects.create( user=self.student, context_key=context_key, block_key=usage_key, completion=1.0, ) url = '{}?is_bigcourse=0'.format( reverse('completion_data_view', kwargs={'course_id': self.course.id})) self.response = self.staff_client.get(url) data = json.loads(self.response.content.decode()) self.assertEqual(data['data'], [[False]]) self.response = self.staff_client.get(url) self.assertEqual(self.response.status_code, 200) data = json.loads(self.response.content.decode()) self.assertEqual(len(data['data']), 12) self.assertEqual( data['data'][-1], ['*****@*****.**', 'student', '', '✔', '1/1', '1/1', 'No'])
def test_render_data_big_course(self): """ Test get data normal process when is big course """ context_key = LearningContextKey.from_string(str(self.course.id)) for item in self.items: usage_key = item.scope_ids.usage_id completion = models.BlockCompletion.objects.create( user=self.student, context_key=context_key, block_key=usage_key, completion=1.0, ) url = '{}?is_bigcourse=1'.format( reverse('completion_data_view', kwargs={'course_id': self.course.id})) self.response = self.staff_client.get(url) data = json.loads(self.response.content.decode()) self.assertEqual(data['data'], [[False]]) self.response = self.staff_client.get(url) self.assertEqual(self.response.status_code, 200) data = json.loads(self.response.content.decode()) self.assertEqual(len(data['data']), 12) self.assertEqual(data['data'][-1][0], self.student.username) self.assertEqual(data['data'][-1][1], '') self.assertEqual(data['data'][-1][2], self.student.email) self.assertEqual(data['data'][-1][3], completion.modified.strftime("%d/%m/%Y, %H:%M:%S"))
def _validate_and_parse_context_key(self, context_key): """ Returns a validated parsed LearningContextKey deserialized from the given context_key. """ try: return LearningContextKey.from_string(context_key) except InvalidKeyError: raise ValidationError(_("Invalid learning context key: {}").format(context_key))
def test_pathway_key_parsing(self): """ Test parsing of pathway keys """ key_str = 'lx-pathway:00000000-e4fe-47af-8ff6-123456789000' key = LearningContextKey.from_string(key_str) self.assertEqual(str(key), key_str) self.assertIsInstance(key, PathwayLocator)
def get(self, request, username, course_key, subsection_id): """ Returns completion for a (user, subsection, course). """ def get_completion(course_completions, all_blocks, block_id): """ Recursively get the aggregate completion for a subsection, given the subsection block and a list of all blocks. Parameters: course_completions: a dictionary of completion values by block IDs all_blocks: a dictionary of the block structure for a subsection block_id: an ID of a block for which to get completion """ block = all_blocks.get(block_id) child_ids = block.get('children', []) if not child_ids: return course_completions.get(block.serializer.instance, 0) completion = 0 total_children = 0 for child_id in child_ids: completion += get_completion(course_completions, all_blocks, child_id) total_children += 1 return int(completion == total_children) user_id = User.objects.get(username=username).id block_types_filter = [ 'course', 'chapter', 'sequential', 'vertical', 'html', 'problem', 'video', 'discussion', 'drag-and-drop-v2' ] blocks = get_blocks( request, UsageKey.from_string(subsection_id), nav_depth=2, requested_fields=[ 'children' ], block_types_filter=block_types_filter ) course_key = LearningContextKey.from_string(course_key) context_completions = BlockCompletion.get_learning_context_completions(user_id, course_key) aggregated_completion = get_completion(context_completions, blocks['blocks'], blocks['root']) return Response({"completion": aggregated_completion}, status=status.HTTP_200_OK)
def test_from_string_inheritance(self): """ Test that CourseKey.from_string(...) will never give you a library key unexpectedly, but LearningContextKey.from_string(...) will give you either. """ lib_string = 'lib:MITx:reallyhardproblems' course_string = 'course-v1:org+course+run' # This should not work because lib_string is not a course key: with self.assertRaises(InvalidKeyError): CourseKey.from_string(lib_string) # But this should work: self.assertIsInstance( LearningContextKey.from_string(lib_string), LibraryLocatorV2, ) # And this should work: self.assertIsInstance( LearningContextKey.from_string(course_string), CourseLocator, )
def get_block(self, students_id, course_key): """ Get all completed students block """ context_key = LearningContextKey.from_string(str(course_key)) aux_blocks = BlockCompletion.objects.filter( user_id__in=students_id, context_key=context_key, completion=1.0).values( 'user_id', 'block_key') blocks = defaultdict(list) for b in aux_blocks: blocks[b['user_id']].append(b['block_key']) return blocks
def score_changed_handler(sender, **kwargs): # pylint: disable=unused-argument """ Consume signals that indicate score changes. See the definition of PROBLEM_WEIGHTED_SCORE_CHANGED for a description of the signal. """ points_possible = kwargs.get('weighted_possible', None) points_earned = kwargs.get('weighted_earned', None) user_id = kwargs.get('user_id', None) course_id = kwargs.get('course_id', None) usage_id = kwargs.get('usage_id', None) # Make sure this came from a course because this code only works with courses if not course_id: return context_key = LearningContextKey.from_string(course_id) if not context_key.is_course: return # This is a content library or something else... if None not in (points_earned, points_possible, user_id, course_id): course_key, usage_key = parse_course_and_usage_keys( course_id, usage_id) assignments = increment_assignment_versions(course_key, usage_key, user_id) for assignment in assignments: if assignment.usage_key == usage_key: send_leaf_outcome.delay(assignment.id, points_earned, points_possible) else: send_composite_outcome.apply_async( (user_id, course_id, assignment.id, assignment.version_number), countdown=settings.LTI_AGGREGATE_SCORE_PASSBACK_DELAY) else: log.error( u"Outcome Service: Required signal parameter is None. " u"points_possible: %s, points_earned: %s, user_id: %s, " u"course_id: %s, usage_id: %s", points_possible, points_earned, user_id, course_id, usage_id)
def get_context_big_course(self, course_key): """ Return eol completion data """ context_key = LearningContextKey.from_string(str(course_key)) aux_block_completions = BlockCompletion.objects.filter(context_key=context_key).values('user').annotate(last_completed=Max('modified')) last_block_completions= {} for x in aux_block_completions: last_block_completions[x['user']] = x['last_completed'] try: enrolled_students = User.objects.filter( courseenrollment__course_id=course_key, courseenrollment__is_active=1, courseenrollment__mode='honor' ).order_by('username').values('id', 'username', 'email', 'edxloginuser__run', 'last_login') context = [ [x['username'], x['edxloginuser__run'] if x['edxloginuser__run'] else '', x['email'], last_block_completions[x['id']].strftime("%d/%m/%Y, %H:%M:%S") if x['id'] in last_block_completions else '', x['last_login'].strftime("%d/%m/%Y, %H:%M:%S") if x['last_login'] else ''] for x in enrolled_students ] except FieldError: enrolled_students = User.objects.filter( courseenrollment__course_id=course_key, courseenrollment__is_active=1, courseenrollment__mode='honor' ).order_by('username').values('id', 'username', 'email', 'last_login') context = [ [x['username'], x['email'], last_block_completions[x['id']].strftime("%d/%m/%Y, %H:%M:%S") if x['id'] in last_block_completions else '', x['last_login'].strftime("%d/%m/%Y, %H:%M:%S") if x['last_login'] else ''] for x in enrolled_students ] if len(context) == 0: context = [[True]] return {'data': context}
def test_usage_key_parsing(self): """ Test parsing of pathway usage keys """ parent_key = LearningContextKey.from_string('lx-pathway:00000000-e4fe-47af-8ff6-123456789000') # Key of a normal top-level block in a pathway: key_str = 'lx-pb:00000000-e4fe-47af-8ff6-123456789000:unit:0ff24589' key = UsageKey.from_string(key_str) self.assertEqual(str(key), key_str) self.assertIsInstance(key, PathwayUsageLocator) self.assertEqual(key.context_key, parent_key) self.assertEqual(key, UsageKey.from_string(key_str)) # self equality # Key of a child block in a pathway: key_str = 'lx-pb:00000000-e4fe-47af-8ff6-123456789000:problem:0ff24589:1-2' key = UsageKey.from_string(key_str) self.assertEqual(str(key), key_str) self.assertIsInstance(key, PathwayUsageLocator) self.assertEqual(key.context_key, parent_key) self.assertEqual(key, UsageKey.from_string(key_str)) # self equality self.assertNotEqual( UsageKey.from_string('lx-pb:00000000-e4fe-47af-8ff6-123456789000:unit:0ff24589'), UsageKey.from_string('lx-pb:00000000-e4fe-47af-8ff6-123456789000:unit:0ff24589:1-2'), )
def scorable_block_completion(sender, **kwargs): # pylint: disable=unused-argument """ When a problem is scored, submit a new BlockCompletion for that block. """ if not waffle.waffle().is_enabled(waffle.ENABLE_COMPLETION_TRACKING): return try: block_key = UsageKey.from_string(kwargs['usage_id']) except InvalidKeyError: log.exception("Unable to parse XBlock usage_id for completion: %s", block_key) return if block_key.context_key.is_course and block_key.context_key.run is None: # In the case of old mongo courses, the context_key cannot be derived # from the block key alone since it will be missing run info: course_key_with_run = LearningContextKey.from_string( kwargs['course_id']) block_key = block_key.replace(course_key=course_key_with_run) block_cls = XBlock.load_class(block_key.block_type) if XBlockCompletionMode.get_mode( block_cls) != XBlockCompletionMode.COMPLETABLE: return if getattr(block_cls, 'has_custom_completion', False): return user = User.objects.get(id=kwargs['user_id']) if kwargs.get('score_deleted'): completion = 0.0 else: completion = 1.0 if not kwargs.get('grader_response'): BlockCompletion.objects.submit_completion( user=user, block_key=block_key, completion=completion, )
def test_render_data_with_rut_big_course(self): """ Test get data normal process with edxloginuser when is big course """ try: from unittest.case import SkipTest from uchileedxlogin.models import EdxLoginUser except ImportError: self.skipTest("import error uchileedxlogin") edxlogin = EdxLoginUser.objects.create(user=self.student, run='000000001K') context_key = LearningContextKey.from_string(str(self.course.id)) for item in self.items: usage_key = item.scope_ids.usage_id completion = models.BlockCompletion.objects.create( user=self.student, context_key=context_key, block_key=usage_key, completion=1.0, ) url = '{}?is_bigcourse=1'.format( reverse('completion_data_view', kwargs={'course_id': self.course.id})) self.response = self.staff_client.get(url) data = json.loads(self.response.content.decode()) self.assertEqual(data['data'], [[False]]) self.response = self.staff_client.get(url) self.assertEqual(self.response.status_code, 200) data = json.loads(self.response.content.decode()) self.assertEqual(len(data['data']), 12) self.assertEqual(data['data'][-1][0], self.student.username) self.assertEqual(data['data'][-1][1], edxlogin.run) self.assertEqual(data['data'][-1][2], self.student.email) self.assertEqual(data['data'][-1][3], completion.modified.strftime("%d/%m/%Y, %H:%M:%S"))
def test_roundtrip_from_key(self, key_args): key = LibraryLocatorV2(**key_args) serialized = str(key) deserialized = LearningContextKey.from_string(serialized) self.assertEqual(key, deserialized)
def test_roundtrip_from_string(self, key): lib_key = LearningContextKey.from_string(key) serialized = str(lib_key) self.assertEqual(key, serialized)