def _has_database_updated_with_new_score( user_id, scored_block_usage_key, expected_modified_time, score_deleted, ): """ Returns whether the database has been updated with the expected new score values for the given problem and user. """ score = get_score(user_id, scored_block_usage_key) if score is None: # score should be None only if it was deleted. # Otherwise, it hasn't yet been saved. return score_deleted elif score.module_type == 'openassessment': anon_id = anonymous_id_for_user(User.objects.get(id=user_id), scored_block_usage_key.course_key) course_id = unicode(scored_block_usage_key.course_key) item_id = unicode(scored_block_usage_key) api_score = sub_api.get_score({ "student_id": anon_id, "course_id": course_id, "item_id": item_id, "item_type": "openassessment" }) reported_modified_time = api_score.created_at else: reported_modified_time = score.modified return reported_modified_time >= expected_modified_time
def _has_database_updated_with_new_score( user_id, scored_block_usage_key, expected_modified_time, score_deleted, ): """ Returns whether the database has been updated with the expected new score values for the given problem and user. """ score = get_score(user_id, scored_block_usage_key) if score is None: # score should be None only if it was deleted. # Otherwise, it hasn't yet been saved. return score_deleted elif score.module_type == 'openassessment': anon_id = anonymous_id_for_user(User.objects.get(id=user_id), scored_block_usage_key.course_key) course_id = unicode(scored_block_usage_key.course_key) item_id = unicode(scored_block_usage_key) api_score = sub_api.get_score( { "student_id": anon_id, "course_id": course_id, "item_id": item_id, "item_type": "openassessment" } ) reported_modified_time = api_score.created_at else: reported_modified_time = score.modified return reported_modified_time >= expected_modified_time
def score_published_handler(sender, block, user, raw_earned, raw_possible, only_if_higher, **kwargs): # pylint: disable=unused-argument """ Handles whenever a block's score is published. Returns whether the score was actually updated. """ update_score = True if only_if_higher: previous_score = get_score(user.id, block.location) if previous_score is not None: prev_raw_earned, prev_raw_possible = previous_score # pylint: disable=unpacking-non-sequence if not is_score_higher(prev_raw_earned, prev_raw_possible, raw_earned, raw_possible): update_score = False log.warning( u"Grades: Rescore is not higher than previous: " u"user: {}, block: {}, previous: {}/{}, new: {}/{} ".format( user, block.location, prev_raw_earned, prev_raw_possible, raw_earned, raw_possible, ) ) if update_score: set_score(user.id, block.location, raw_earned, raw_possible) PROBLEM_SCORE_CHANGED.send( sender=None, points_earned=raw_earned, points_possible=raw_possible, user_id=user.id, course_id=unicode(block.location.course_key), usage_id=unicode(block.location), only_if_higher=only_if_higher, ) return update_score
def score_published_handler(sender, block, user, raw_earned, raw_possible, only_if_higher, **kwargs): # pylint: disable=unused-argument """ Handles whenever a block's score is published. Returns whether the score was actually updated. """ update_score = True if only_if_higher: previous_score = get_score(user.id, block.location) if previous_score is not None: prev_raw_earned, prev_raw_possible = (previous_score.grade, previous_score.max_grade) if not is_score_higher(prev_raw_earned, prev_raw_possible, raw_earned, raw_possible): update_score = False log.warning( u"Grades: Rescore is not higher than previous: " u"user: {}, block: {}, previous: {}/{}, new: {}/{} ".format( user, block.location, prev_raw_earned, prev_raw_possible, raw_earned, raw_possible, ) ) if update_score: score_modified_time = set_score(user.id, block.location, raw_earned, raw_possible) PROBLEM_RAW_SCORE_CHANGED.send( sender=None, raw_earned=raw_earned, raw_possible=raw_possible, weight=getattr(block, 'weight', None), user_id=user.id, course_id=unicode(block.location.course_key), usage_id=unicode(block.location), only_if_higher=only_if_higher, modified=score_modified_time, ) return update_score
def recalculate_subsection_grade(user_id, course_id, usage_id, only_if_higher, raw_earned, raw_possible): """ Updates a saved subsection grade. This method expects the following parameters: - user_id: serialized id of applicable User object - course_id: Unicode string identifying the course - usage_id: Unicode string identifying the course block - only_if_higher: boolean indicating whether grades should be updated only if the new raw_earned is higher than the previous value. - raw_earned: the raw points the learner earned on the problem that triggered the update - raw_possible: the max raw points the leaner could have earned on the problem """ course_key = CourseLocator.from_string(course_id) if not PersistentGradesEnabledFlag.feature_enabled(course_key): return scored_block_usage_key = UsageKey.from_string(usage_id).replace(course_key=course_key) score = get_score(user_id, scored_block_usage_key) # If the score is None, it has not been saved at all yet # and we need to retry until it has been saved. if score is None: raise _retry_recalculate_subsection_grade( user_id, course_id, usage_id, only_if_higher, raw_earned, raw_possible, ) else: module_raw_earned, module_raw_possible = score # pylint: disable=unpacking-non-sequence # Validate that the retrieved scores match the scores when the task was created. # This race condition occurs if the transaction in the task creator's process hasn't # committed before the task initiates in the worker process. grades_match = module_raw_earned == raw_earned and module_raw_possible == raw_possible # We have to account for the situation where a student's state # has been deleted- in this case, get_score returns (None, None), but # the score change signal will contain 0 for raw_earned. state_deleted = module_raw_earned is None and module_raw_possible is None and raw_earned == 0 if not (state_deleted or grades_match): raise _retry_recalculate_subsection_grade( user_id, course_id, usage_id, only_if_higher, raw_earned, raw_possible, ) _update_subsection_grades( course_key, scored_block_usage_key, only_if_higher, course_id, user_id, usage_id, raw_earned, raw_possible, )
def score_published_handler(sender, block, user, raw_earned, raw_possible, only_if_higher, **kwargs): # pylint: disable=unused-argument """ Handles whenever a block's score is published. Returns whether the score was actually updated. """ update_score = True if only_if_higher: previous_score = get_score(user.id, block.location) if previous_score is not None: prev_raw_earned, prev_raw_possible = (previous_score.grade, previous_score.max_grade) if not is_score_higher_or_equal(prev_raw_earned, prev_raw_possible, raw_earned, raw_possible): update_score = False log.warning( u"Grades: Rescore is not higher than previous: " u"user: {}, block: {}, previous: {}/{}, new: {}/{} ". format( user, block.location, prev_raw_earned, prev_raw_possible, raw_earned, raw_possible, )) if update_score: # Set the problem score in CSM. score_modified_time = set_score(user.id, block.location, raw_earned, raw_possible) # Set the problem score on the xblock. if isinstance(block, ScorableXBlockMixin): block.set_score( Score(raw_earned=raw_earned, raw_possible=raw_possible)) # Fire a signal (consumed by enqueue_subsection_update, below) PROBLEM_RAW_SCORE_CHANGED.send( sender=None, raw_earned=raw_earned, raw_possible=raw_possible, weight=getattr(block, 'weight', None), user_id=user.id, course_id=unicode(block.location.course_key), usage_id=unicode(block.location), only_if_higher=only_if_higher, modified=score_modified_time, score_db_table=ScoreDatabaseTableEnum.courseware_student_module, score_deleted=kwargs.get('score_deleted', False), ) return update_score
def _has_db_updated_with_new_score(self, scored_block_usage_key, **kwargs): """ Returns whether the database has been updated with the expected new score values for the given problem and user. """ if kwargs[ 'score_db_table'] == ScoreDatabaseTableEnum.courseware_student_module: score = get_score(kwargs['user_id'], scored_block_usage_key) found_modified_time = score.modified if score is not None else None elif kwargs['score_db_table'] == ScoreDatabaseTableEnum.submissions: score = sub_api.get_score({ "student_id": kwargs['anonymous_user_id'], "course_id": six.text_type(scored_block_usage_key.course_key), "item_id": six.text_type(scored_block_usage_key), "item_type": scored_block_usage_key.block_type, }) found_modified_time = score['created_at'] if score is not None else None else: assert kwargs['score_db_table'] == ScoreDatabaseTableEnum.overrides from . import api score = api.get_subsection_grade_override( user_id=kwargs['user_id'], course_key_or_id=kwargs['course_id'], usage_key_or_id=kwargs['usage_id']) found_modified_time = score.modified if score is not None else None if score is None: # score should be None only if it was deleted. # Otherwise, it hasn't yet been saved. db_is_updated = kwargs['score_deleted'] else: db_is_updated = found_modified_time >= from_timestamp( kwargs['expected_modified_time']) if not db_is_updated: log.info( u"Grades: tasks._has_database_updated_with_new_score is False. Task ID: {}. Kwargs: {}. Found " u"modified time: {}".format( self.request.id, kwargs, found_modified_time, )) return db_is_updated
def _has_db_updated_with_new_score(self, scored_block_usage_key, **kwargs): """ Returns whether the database has been updated with the expected new score values for the given problem and user. """ if kwargs['score_db_table'] == ScoreDatabaseTableEnum.courseware_student_module: score = get_score(kwargs['user_id'], scored_block_usage_key) found_modified_time = score.modified if score is not None else None elif kwargs['score_db_table'] == ScoreDatabaseTableEnum.submissions: score = sub_api.get_score( { "student_id": kwargs['anonymous_user_id'], "course_id": unicode(scored_block_usage_key.course_key), "item_id": unicode(scored_block_usage_key), "item_type": scored_block_usage_key.block_type, } ) found_modified_time = score['created_at'] if score is not None else None else: assert kwargs['score_db_table'] == ScoreDatabaseTableEnum.overrides score = GradesService().get_subsection_grade_override( user_id=kwargs['user_id'], course_key_or_id=kwargs['course_id'], usage_key_or_id=kwargs['usage_id'] ) found_modified_time = score.modified if score is not None else None if score is None: # score should be None only if it was deleted. # Otherwise, it hasn't yet been saved. db_is_updated = kwargs['score_deleted'] else: db_is_updated = found_modified_time >= from_timestamp(kwargs['expected_modified_time']) if not db_is_updated: log.info( u"Grades: tasks._has_database_updated_with_new_score is False. Task ID: {}. Kwargs: {}. Found " u"modified time: {}".format( self.request.id, kwargs, found_modified_time, ) ) return db_is_updated
def score_published_handler(sender, block, user, raw_earned, raw_possible, only_if_higher, **kwargs): # pylint: disable=unused-argument """ Handles whenever a block's score is published. Returns whether the score was actually updated. """ update_score = True if only_if_higher: previous_score = get_score(user.id, block.location) if previous_score is not None: prev_raw_earned, prev_raw_possible = (previous_score.grade, previous_score.max_grade) if not is_score_higher_or_equal(prev_raw_earned, prev_raw_possible, raw_earned, raw_possible): update_score = False log.warning( u"Grades: Rescore is not higher than previous: " u"user: {}, block: {}, previous: {}/{}, new: {}/{} ".format( user, block.location, prev_raw_earned, prev_raw_possible, raw_earned, raw_possible, ) ) if update_score: # Set the problem score in CSM. score_modified_time = set_score(user.id, block.location, raw_earned, raw_possible) # Set the problem score on the xblock. if isinstance(block, ScorableXBlockMixin): block.set_score(Score(raw_earned=raw_earned, raw_possible=raw_possible)) # Fire a signal (consumed by enqueue_subsection_update, below) PROBLEM_RAW_SCORE_CHANGED.send( sender=None, raw_earned=raw_earned, raw_possible=raw_possible, weight=getattr(block, 'weight', None), user_id=user.id, course_id=six.text_type(block.location.course_key), usage_id=six.text_type(block.location), only_if_higher=only_if_higher, modified=score_modified_time, score_db_table=ScoreDatabaseTableEnum.courseware_student_module, score_deleted=kwargs.get('score_deleted', False), grader_response=kwargs.get('grader_response', False) ) return update_score
def _has_database_updated_with_new_score( user_id, scored_block_usage_key, expected_raw_earned, expected_raw_possible, score_deleted, ): """ Returns whether the database has been updated with the expected new score values for the given problem and user. """ score = get_score(user_id, scored_block_usage_key) if score is None: # score should be None only if it was deleted. # Otherwise, it hasn't yet been saved. return score_deleted found_raw_earned, found_raw_possible = score # pylint: disable=unpacking-non-sequence return ( found_raw_earned == expected_raw_earned and found_raw_possible == expected_raw_possible )
def score_published_handler(sender, block, user, raw_earned, raw_possible, only_if_higher, **kwargs): # pylint: disable=unused-argument """ Handles whenever a block's score is published. Returns whether the score was actually updated. """ update_score = True if only_if_higher: previous_score = get_score(user.id, block.location) if previous_score: prev_raw_earned, prev_raw_possible = previous_score # pylint: disable=unpacking-non-sequence if not is_score_higher(prev_raw_earned, prev_raw_possible, raw_earned, raw_possible): update_score = False log.warning( u"Grades: Rescore is not higher than previous: " u"user: {}, block: {}, previous: {}/{}, new: {}/{} ". format( user, block.location, prev_raw_earned, prev_raw_possible, raw_earned, raw_possible, )) if update_score: set_score(user.id, block.location, raw_earned, raw_possible) SCORE_CHANGED.send( sender=None, points_earned=raw_earned, points_possible=raw_possible, user_id=user.id, course_id=unicode(block.location.course_key), usage_id=unicode(block.location), only_if_higher=only_if_higher, ) return update_score
def _has_database_updated_with_new_score( user_id, scored_block_usage_key, expected_raw_earned, expected_raw_possible, score_deleted, ): """ Returns whether the database has been updated with the expected new score values for the given problem and user. """ score = get_score(user_id, scored_block_usage_key) if score is None: # score should be None only if it was deleted. # Otherwise, it hasn't yet been saved. return score_deleted found_raw_earned, found_raw_possible = score # pylint: disable=unpacking-non-sequence return (found_raw_earned == expected_raw_earned and found_raw_possible == expected_raw_possible)
def recalculate_subsection_grade(user_id, course_id, usage_id, only_if_higher, raw_earned, raw_possible): """ Updates a saved subsection grade. This method expects the following parameters: - user_id: serialized id of applicable User object - course_id: Unicode string identifying the course - usage_id: Unicode string identifying the course block - only_if_higher: boolean indicating whether grades should be updated only if the new raw_earned is higher than the previous value. - raw_earned: the raw points the learner earned on the problem that triggered the update - raw_possible: the max raw points the leaner could have earned on the problem """ course_key = CourseLocator.from_string(course_id) if not PersistentGradesEnabledFlag.feature_enabled(course_key): return scored_block_usage_key = UsageKey.from_string(usage_id).replace( course_key=course_key) score = get_score(user_id, scored_block_usage_key) # If the score is None, it has not been saved at all yet # and we need to retry until it has been saved. if score is None: raise _retry_recalculate_subsection_grade( user_id, course_id, usage_id, only_if_higher, raw_earned, raw_possible, ) else: module_raw_earned, module_raw_possible = score # pylint: disable=unpacking-non-sequence # Validate that the retrieved scores match the scores when the task was created. # This race condition occurs if the transaction in the task creator's process hasn't # committed before the task initiates in the worker process. grades_match = module_raw_earned == raw_earned and module_raw_possible == raw_possible # We have to account for the situation where a student's state # has been deleted- in this case, get_score returns (None, None), but # the score change signal will contain 0 for raw_earned. state_deleted = module_raw_earned is None and module_raw_possible is None and raw_earned == 0 if not (state_deleted or grades_match): raise _retry_recalculate_subsection_grade( user_id, course_id, usage_id, only_if_higher, raw_earned, raw_possible, ) _update_subsection_grades( course_key, scored_block_usage_key, only_if_higher, course_id, user_id, usage_id, raw_earned, raw_possible, )