Exemplo n.º 1
0
def grade_updated(**kwargs):
    """
    Emits the appropriate grade-related event after checking for which
    event-transaction is active.

    Emits a problem.submitted event only if there is no current event
    transaction type, i.e. we have not reached this point in the code via
    an outer event type (such as problem.rescored or score_overridden).
    """
    root_type = get_event_transaction_type()

    if not root_type:
        root_id = get_event_transaction_id()
        if not root_id:
            root_id = create_new_event_transaction_id()
        set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE)
        tracker.emit(
            str(PROBLEM_SUBMITTED_EVENT_TYPE),
            {
                'user_id': str(kwargs['user_id']),
                'course_id': str(kwargs['course_id']),
                'problem_id': str(kwargs['usage_id']),
                'event_transaction_id': str(root_id),
                'event_transaction_type': str(PROBLEM_SUBMITTED_EVENT_TYPE),
                'weighted_earned': kwargs.get('weighted_earned'),
                'weighted_possible': kwargs.get('weighted_possible'),
            }
        )

    elif root_type in [GRADES_RESCORE_EVENT_TYPE, GRADES_OVERRIDE_EVENT_TYPE]:
        current_user = get_current_user()
        instructor_id = getattr(current_user, 'id', None)
        tracker.emit(
            str(root_type),
            {
                'course_id': str(kwargs['course_id']),
                'user_id': str(kwargs['user_id']),
                'problem_id': str(kwargs['usage_id']),
                'new_weighted_earned': kwargs.get('weighted_earned'),
                'new_weighted_possible': kwargs.get('weighted_possible'),
                'only_if_higher': kwargs.get('only_if_higher'),
                'instructor_id': str(instructor_id),
                'event_transaction_id': str(get_event_transaction_id()),
                'event_transaction_type': str(root_type),
            }
        )

    elif root_type in [SUBSECTION_OVERRIDE_EVENT_TYPE]:
        tracker.emit(
            str(root_type),
            {
                'course_id': str(kwargs['course_id']),
                'user_id': str(kwargs['user_id']),
                'problem_id': str(kwargs['usage_id']),
                'only_if_higher': kwargs.get('only_if_higher'),
                'override_deleted': kwargs.get('score_deleted', False),
                'event_transaction_id': str(get_event_transaction_id()),
                'event_transaction_type': str(root_type),
            }
        )
Exemplo n.º 2
0
 def _reset_or_delete_module(studentmodule):
     if delete_module:
         studentmodule.delete()
         create_new_event_transaction_id()
         set_event_transaction_type(grades_events.STATE_DELETED_EVENT_TYPE)
         tracker.emit(
             six.text_type(grades_events.STATE_DELETED_EVENT_TYPE),
             {
                 'user_id': six.text_type(student.id),
                 'course_id': six.text_type(course_id),
                 'problem_id': six.text_type(module_state_key),
                 'instructor_id': six.text_type(requesting_user.id),
                 'event_transaction_id': six.text_type(get_event_transaction_id()),
                 'event_transaction_type': six.text_type(grades_events.STATE_DELETED_EVENT_TYPE),
             }
         )
         if not submission_cleared:
             _fire_score_changed_for_block(
                 course_id,
                 student,
                 block,
                 module_state_key,
             )
     else:
         _reset_module_attempts(studentmodule)
Exemplo n.º 3
0
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,
    )
Exemplo n.º 4
0
    def _create_override(self, request_user, subsection_grade_model,
                         **override_data):
        """
        Helper method to create a `PersistentSubsectionGradeOverride` object
        and send a `SUBSECTION_OVERRIDE_CHANGED` signal.
        """
        override_data[
            'system'] = grades_constants.GradeOverrideFeatureEnum.gradebook
        override = PersistentSubsectionGradeOverride.update_or_create_override(
            requesting_user=request_user,
            subsection_grade_model=subsection_grade_model,
            feature=grades_constants.GradeOverrideFeatureEnum.gradebook,
            **override_data)

        set_event_transaction_type(grades_events.SUBSECTION_GRADE_CALCULATED)
        create_new_event_transaction_id()

        recalculate_subsection_grade_v3.apply(kwargs=dict(
            user_id=subsection_grade_model.user_id,
            anonymous_user_id=None,
            course_id=text_type(subsection_grade_model.course_id),
            usage_id=text_type(subsection_grade_model.usage_key),
            only_if_higher=False,
            expected_modified_time=to_timestamp(override.modified),
            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=grades_constants.ScoreDatabaseTableEnum.overrides,
            force_update_subsections=True,
        ))
        # Emit events to let our tracking system to know we updated subsection grade
        grades_events.subsection_grade_calculated(subsection_grade_model)
        return override
Exemplo n.º 5
0
def subsection_grade_calculated(subsection_grade):
    """
    Emits an edx.grades.subsection.grade_calculated event
    with data from the passed subsection_grade.
    """
    event_name = SUBSECTION_GRADE_CALCULATED
    context = contexts.course_context_from_course_id(subsection_grade.course_id)
    # TODO (AN-6134): remove this context manager
    with tracker.get_tracker().context(event_name, context):
        tracker.emit(
            event_name,
            {
                'user_id': str(subsection_grade.user_id),
                'course_id': str(subsection_grade.course_id),
                'block_id': str(subsection_grade.usage_key),
                'course_version': str(subsection_grade.course_version),
                'weighted_total_earned': subsection_grade.earned_all,
                'weighted_total_possible': subsection_grade.possible_all,
                'weighted_graded_earned': subsection_grade.earned_graded,
                'weighted_graded_possible': subsection_grade.possible_graded,
                'first_attempted': str(subsection_grade.first_attempted),
                'subtree_edited_timestamp': str(subsection_grade.subtree_edited_timestamp),
                'event_transaction_id': str(get_event_transaction_id()),
                'event_transaction_type': str(get_event_transaction_type()),
                'visible_blocks_hash': str(subsection_grade.visible_blocks_id),
            }
        )
Exemplo n.º 6
0
 def _assert_tracker_emitted_event(self, tracker_mock, grade):
     """
     Helper function to ensure that the mocked event tracker
     was called with the expected info based on the passed grade.
     """
     tracker_mock.emit.assert_called_with(
         u'edx.grades.subsection.grade_calculated', {
             'user_id':
             six.text_type(grade.user_id),
             'course_id':
             six.text_type(grade.course_id),
             'block_id':
             six.text_type(grade.usage_key),
             'course_version':
             six.text_type(grade.course_version),
             'weighted_total_earned':
             grade.earned_all,
             'weighted_total_possible':
             grade.possible_all,
             'weighted_graded_earned':
             grade.earned_graded,
             'weighted_graded_possible':
             grade.possible_graded,
             'first_attempted':
             six.text_type(grade.first_attempted),
             'subtree_edited_timestamp':
             six.text_type(grade.subtree_edited_timestamp),
             'event_transaction_id':
             six.text_type(get_event_transaction_id()),
             'event_transaction_type':
             six.text_type(get_event_transaction_type()),
             'visible_blocks_hash':
             six.text_type(grade.visible_blocks_id),
         })
Exemplo n.º 7
0
 def _assert_tracker_emitted_event(self, tracker_mock, grade):
     """
     Helper function to ensure that the mocked event tracker
     was called with the expected info based on the passed grade.
     """
     tracker_mock.emit.assert_called_with(
         u'edx.grades.course.grade_calculated', {
             'user_id':
             six.text_type(grade.user_id),
             'course_id':
             six.text_type(grade.course_id),
             'course_version':
             six.text_type(grade.course_version),
             'percent_grade':
             grade.percent_grade,
             'letter_grade':
             six.text_type(grade.letter_grade),
             'course_edited_timestamp':
             six.text_type(grade.course_edited_timestamp),
             'event_transaction_id':
             six.text_type(get_event_transaction_id()),
             'event_transaction_type':
             six.text_type(get_event_transaction_type()),
             'grading_policy_hash':
             six.text_type(grade.grading_policy_hash),
         })
Exemplo n.º 8
0
    def _run_command_and_check_output(self,
                                      task_mock,
                                      score_db_table,
                                      include_anonymous_id=False):
        self.command.handle(modified_start='2016-08-25 16:42',
                            modified_end='2018-08-25 16:44')
        kwargs = {
            "user_id":
            "ID",
            "course_id":
            u'course-v1:x+y+z',
            "usage_id":
            u'abc',
            "only_if_higher":
            False,
            "expected_modified_time":
            to_timestamp(
                utc.localize(datetime.strptime('2016-08-23 16:43',
                                               DATE_FORMAT))),
            "score_deleted":
            False,
            "event_transaction_id":
            six.text_type(get_event_transaction_id()),
            "event_transaction_type":
            u'edx.grades.problem.submitted',
            "score_db_table":
            score_db_table,
        }

        if include_anonymous_id:
            kwargs['anonymous_user_id'] = 'anonymousID'

        task_mock.apply_async.assert_called_with(kwargs=kwargs)
def course_grade_calculated(course_grade):
    """
    Emits an edx.grades.course.grade_calculated event
    with data from the passed course_grade.
    """
    event_name = COURSE_GRADE_CALCULATED
    context = contexts.course_context_from_course_id(course_grade.course_id)
    # TODO (AN-6134): remove this context manager
    with tracker.get_tracker().context(event_name, context):
        tracker.emit(
            event_name, {
                'user_id':
                str(course_grade.user_id),
                'course_id':
                str(course_grade.course_id),
                'course_version':
                str(course_grade.course_version),
                'percent_grade':
                course_grade.percent_grade,
                'letter_grade':
                str(course_grade.letter_grade),
                'course_edited_timestamp':
                str(course_grade.course_edited_timestamp),
                'event_transaction_id':
                str(get_event_transaction_id()),
                'event_transaction_type':
                str(get_event_transaction_type()),
                'grading_policy_hash':
                str(course_grade.grading_policy_hash),
            })
Exemplo n.º 10
0
    def set_up_course(self, enable_persistent_grades=True, create_multiple_subsections=False, course_end=None):
        """
        Configures the course for this test.
        """
        self.course = CourseFactory.create(
            org='edx',
            name='course',
            run='run',
            end=course_end
        )
        if not enable_persistent_grades:
            PersistentGradesEnabledFlag.objects.create(enabled=False)

        self.chapter = ItemFactory.create(parent=self.course, category="chapter", display_name="Chapter")
        self.sequential = ItemFactory.create(parent=self.chapter, category='sequential', display_name="Sequential1")
        self.problem = ItemFactory.create(parent=self.sequential, category='problem', display_name='Problem')

        if create_multiple_subsections:
            seq2 = ItemFactory.create(parent=self.chapter, category='sequential')
            ItemFactory.create(parent=seq2, category='problem')

        self.frozen_now_datetime = datetime.now().replace(tzinfo=pytz.UTC)
        self.frozen_now_timestamp = to_timestamp(self.frozen_now_datetime)

        self.problem_weighted_score_changed_kwargs = OrderedDict([
            ('weighted_earned', 1.0),
            ('weighted_possible', 2.0),
            ('user_id', self.user.id),
            ('anonymous_user_id', 5),
            ('course_id', six.text_type(self.course.id)),
            ('usage_id', six.text_type(self.problem.location)),
            ('only_if_higher', None),
            ('modified', self.frozen_now_datetime),
            ('score_db_table', ScoreDatabaseTableEnum.courseware_student_module),
        ])

        create_new_event_transaction_id()

        self.recalculate_subsection_grade_kwargs = OrderedDict([
            ('user_id', self.user.id),
            ('course_id', six.text_type(self.course.id)),
            ('usage_id', six.text_type(self.problem.location)),
            ('anonymous_user_id', 5),
            ('only_if_higher', None),
            ('expected_modified_time', self.frozen_now_timestamp),
            ('score_deleted', False),
            ('event_transaction_id', six.text_type(get_event_transaction_id())),
            ('event_transaction_type', u'edx.grades.problem.submitted'),
            ('score_db_table', ScoreDatabaseTableEnum.courseware_student_module),
        ])

        # this call caches the anonymous id on the user object, saving 4 queries in all happy path tests
        _ = anonymous_id_for_user(self.user, self.course.id)
Exemplo n.º 11
0
 def _assert_tracker_emitted_course_now_failed_event(
         self, tracker_mock, user, course_id):
     """
     Helper function to ensure that the mocked event tracker
     was called with the expected info based on failed course.
     """
     tracker_mock.emit.assert_called_with(
         'edx.course.grade.now_failed', {
             'user_id': str(user.id),
             'course_id': str(course_id),
             'event_transaction_id': str(get_event_transaction_id()),
             'event_transaction_type': str(get_event_transaction_type()),
         })
Exemplo n.º 12
0
 def _assert_tracker_emitted_course_grade_passed_first_time_event(
         self, tracker_mock, user_id, course_id):
     """
     Helper function to ensure that the mocked event tracker
     was called with the expected info based on the course grade passed first time.
     """
     tracker_mock.emit.assert_called_with(
         'edx.course.grade.passed.first_time', {
             'user_id': str(user_id),
             'course_id': str(course_id),
             'event_transaction_id': str(get_event_transaction_id()),
             'event_transaction_type': str(get_event_transaction_type()),
         })
Exemplo n.º 13
0
def course_grade_now_failed(user, course_id):
    """
    Emits an edx.course.grade.now_failed event
    with data from the course and user failed now .
    """
    event_name = COURSE_GRADE_NOW_FAILED_EVENT_TYPE
    context = contexts.course_context_from_course_id(course_id)
    with tracker.get_tracker().context(event_name, context):
        tracker.emit(
            event_name,
            {
                'user_id': str(user.id),
                'course_id': str(course_id),
                'event_transaction_id': str(get_event_transaction_id()),
                'event_transaction_type': str(get_event_transaction_type())
            }
        )
Exemplo n.º 14
0
def handle_grading_policy_changed(sender, **kwargs):
    # pylint: disable=unused-argument
    """
    Receives signal and kicks off celery task to recalculate grades
    """
    kwargs = {
        'course_key': str(kwargs.get('course_key')),
        'grading_policy_hash': str(kwargs.get('grading_policy_hash')),
        'event_transaction_id': str(get_event_transaction_id()),
        'event_transaction_type': str(get_event_transaction_type()),
    }
    result = task_compute_all_grades_for_course.apply_async(kwargs=kwargs, countdown=GRADING_POLICY_COUNTDOWN_SECONDS)
    log.info("Grades: Created {task_name}[{task_id}] with arguments {kwargs}".format(
        task_name=task_compute_all_grades_for_course.name,
        task_id=result.task_id,
        kwargs=kwargs,
    ))
Exemplo n.º 15
0
def course_grade_passed_first_time(user_id, course_id):
    """
    Emits an event edx.course.grade.passed.first_time
    with data from the passed course_grade.
    """
    event_name = COURSE_GRADE_PASSED_FIRST_TIME_EVENT_TYPE
    context = contexts.course_context_from_course_id(course_id)
    # TODO (AN-6134): remove this context manager
    with tracker.get_tracker().context(event_name, context):
        tracker.emit(
            event_name,
            {
                'user_id': str(user_id),
                'course_id': str(course_id),
                'event_transaction_id': str(get_event_transaction_id()),
                'event_transaction_type': str(get_event_transaction_type())
            }
        )