Beispiel #1
0
    def get_subsection_grade(self, user_id, course_key_or_id, usage_key_or_id):
        """
        Finds and returns the earned subsection grade for user
        """
        course_key = _get_key(course_key_or_id, CourseKey)
        usage_key = _get_key(usage_key_or_id, UsageKey)

        return PersistentSubsectionGrade.objects.get(
            user_id=user_id,
            course_id=course_key,
            usage_key=usage_key
        )
Beispiel #2
0
def override_subsection_grade(
        user_id,
        course_key_or_id,
        usage_key_or_id,
        overrider=None,
        earned_all=None,
        earned_graded=None,
        feature=constants.GradeOverrideFeatureEnum.proctoring):
    """
    Creates a PersistentSubsectionGradeOverride corresponding to the given
    user, course, and usage_key.
    Will also create a ``PersistentSubsectionGrade`` for this (user, course, usage_key)
    if none currently exists.

    Fires off a recalculate_subsection_grade async task to update the PersistentCourseGrade table.
    Will not override ``earned_all`` or ``earned_graded`` value if they are ``None``.
    Both of these parameters have ``None`` as their default value.
    """
    course_key = _get_key(course_key_or_id, CourseKey)
    usage_key = _get_key(usage_key_or_id, UsageKey)

    try:
        grade = get_subsection_grade(user_id, usage_key.course_key, usage_key)
    except ObjectDoesNotExist:
        grade = _create_subsection_grade(user_id, course_key, usage_key)

    override = update_or_create_override(
        grade,
        requesting_user=overrider,
        subsection_grade_model=grade,
        feature=feature,
        earned_all_override=earned_all,
        earned_graded_override=earned_graded,
    )

    # Cache a new event id and event type which the signal handler will use to emit a tracking log event.
    create_new_event_transaction_id()
    set_event_transaction_type(events.SUBSECTION_OVERRIDE_EVENT_TYPE)

    # This will eventually trigger a re-computation of the course grade,
    # taking the new PersistentSubsectionGradeOverride into account.
    signals.SUBSECTION_OVERRIDE_CHANGED.send(
        sender=None,
        user_id=user_id,
        course_id=text_type(course_key),
        usage_id=text_type(usage_key),
        only_if_higher=False,
        modified=override.modified,
        score_deleted=False,
        score_db_table=constants.ScoreDatabaseTableEnum.overrides)
Beispiel #3
0
    def override_subsection_grade(
            self, user_id, course_key_or_id, usage_key_or_id, earned_all=None, earned_graded=None
    ):
        """
        Creates a PersistentSubsectionGradeOverride corresponding to the given
        user, course, and usage_key.
        Will also create a ``PersistentSubsectionGrade`` for this (user, course, usage_key)
        if none currently exists.

        Fires off a recalculate_subsection_grade async task to update the PersistentCourseGrade table.
        Will not override ``earned_all`` or ``earned_graded`` value if they are ``None``.
        Both of these parameters have ``None`` as their default value.
        """
        course_key = _get_key(course_key_or_id, CourseKey)
        usage_key = _get_key(usage_key_or_id, UsageKey)

        try:
            grade = PersistentSubsectionGrade.read_grade(
                user_id=user_id,
                usage_key=usage_key
            )
        except PersistentSubsectionGrade.DoesNotExist:
            grade = self._create_subsection_grade(user_id, course_key, usage_key)

        override = PersistentSubsectionGradeOverride.update_or_create_override(
            requesting_user=None,
            subsection_grade_model=grade,
            feature=GradeOverrideFeatureEnum.proctoring,
            earned_all_override=earned_all,
            earned_graded_override=earned_graded,
        )

        # Cache a new event id and event type which the signal handler will use to emit a tracking log event.
        create_new_event_transaction_id()
        set_event_transaction_type(SUBSECTION_OVERRIDE_EVENT_TYPE)

        # This will eventually trigger a re-computation of the course grade,
        # taking the new PersistentSubsectionGradeOverride into account.
        SUBSECTION_OVERRIDE_CHANGED.send(
            sender=None,
            user_id=user_id,
            course_id=text_type(course_key),
            usage_id=text_type(usage_key),
            only_if_higher=False,
            modified=override.modified,
            score_deleted=False,
            score_db_table=ScoreDatabaseTableEnum.overrides
        )
Beispiel #4
0
    def invalidate_certificate(self, user_id, course_key_or_id):
        """
        Invalidate the user certificate in a given course if it exists and the user is not on the allowlist for this
        course run.
        """
        course_key = _get_key(course_key_or_id, CourseKey)
        if is_on_certificate_allowlist(user_id, course_key):
            log.info(f'User {user_id} is on the allowlist for {course_key}. The certificate will not be invalidated.')
            return False

        try:
            generated_certificate = GeneratedCertificate.objects.get(
                user=user_id,
                course_id=course_key
            )
            generated_certificate.invalidate()
        except ObjectDoesNotExist:
            log.warning(
                'Invalidation failed because a certificate for user %d in course %s does not exist.',
                user_id,
                course_key
            )
            return False

        return True
Beispiel #5
0
def should_override_grade_on_rejected_exam(course_key_or_id):
    """
    Convenience function to return the state of the CourseWaffleFlag REJECTED_EXAM_OVERRIDES_GRADE
    """
    from .config.waffle import REJECTED_EXAM_OVERRIDES_GRADE
    course_key = _get_key(course_key_or_id, CourseKey)
    return REJECTED_EXAM_OVERRIDES_GRADE.is_enabled(course_key)
Beispiel #6
0
    def get_subsection_grade_override(self, user_id, course_key_or_id,
                                      usage_key_or_id):
        """
        Finds the subsection grade for user and returns the override for that grade if it exists

        If override does not exist, returns None. If subsection grade does not exist, will raise an exception.
        """
        course_key = _get_key(course_key_or_id, CourseKey)
        usage_key = _get_key(usage_key_or_id, UsageKey)

        grade = self.get_subsection_grade(user_id, course_key, usage_key)

        try:
            return PersistentSubsectionGradeOverride.objects.get(grade=grade)
        except PersistentSubsectionGradeOverride.DoesNotExist:
            return None
Beispiel #7
0
    def override_subsection_grade(self, user_id, course_key_or_id, usage_key_or_id, earned_all=None,
                                  earned_graded=None):
        """
        Override subsection grade (the PersistentSubsectionGrade model must already exist)

        Fires off a recalculate_subsection_grade async task to update the PersistentSubsectionGrade table. Will not
        override earned_all or earned_graded value if they are None. Both default to None.
        """
        course_key = _get_key(course_key_or_id, CourseKey)
        usage_key = _get_key(usage_key_or_id, UsageKey)

        grade = PersistentSubsectionGrade.objects.get(
            user_id=user_id,
            course_id=course_key,
            usage_key=usage_key
        )

        # Create override that will prevent any future updates to grade
        override, _ = PersistentSubsectionGradeOverride.objects.update_or_create(
            grade=grade,
            earned_all_override=earned_all,
            earned_graded_override=earned_graded
        )

        _ = PersistentSubsectionGradeOverrideHistory.objects.create(
            override_id=override.id,
            feature=PersistentSubsectionGradeOverrideHistory.PROCTORING,
            action=PersistentSubsectionGradeOverrideHistory.CREATE_OR_UPDATE
        )

        # Cache a new event id and event type which the signal handler will use to emit a tracking log event.
        create_new_event_transaction_id()
        set_event_transaction_type(SUBSECTION_OVERRIDE_EVENT_TYPE)

        # Signal will trigger subsection recalculation which will call PersistentSubsectionGrade.update_or_create_grade
        # which will use the above override to update the grade before writing to the table.
        SUBSECTION_OVERRIDE_CHANGED.send(
            sender=None,
            user_id=user_id,
            course_id=unicode(course_key),
            usage_id=unicode(usage_key),
            only_if_higher=False,
            modified=override.modified,
            score_deleted=False,
            score_db_table=ScoreDatabaseTableEnum.overrides
        )
Beispiel #8
0
    def get_subsection_grade_override(self, user_id, course_key_or_id, usage_key_or_id):
        """
        Finds the subsection grade for user and returns the override for that grade if it exists

        If override does not exist, returns None. If subsection grade does not exist, will raise an exception.
        """
        course_key = _get_key(course_key_or_id, CourseKey)
        usage_key = _get_key(usage_key_or_id, UsageKey)

        grade = self.get_subsection_grade(user_id, course_key, usage_key)

        try:
            return PersistentSubsectionGradeOverride.objects.get(
                grade=grade
            )
        except PersistentSubsectionGradeOverride.DoesNotExist:
            return None
Beispiel #9
0
def undo_override_subsection_grade(user_id,
                                   course_key_or_id,
                                   usage_key_or_id,
                                   feature=''):
    """
    Delete the override subsection grade row (the PersistentSubsectionGrade model must already exist)

    Fires off a recalculate_subsection_grade async task to update the PersistentSubsectionGrade table. If the
    override does not exist, no error is raised, it just triggers the recalculation.

    feature: if specified, the deletion will only occur if the
             override to be deleted was created by the corresponding
             subsystem
    """
    course_key = _get_key(course_key_or_id, CourseKey)
    usage_key = _get_key(usage_key_or_id, UsageKey)

    try:
        override = get_subsection_grade_override(user_id, course_key,
                                                 usage_key)
    except ObjectDoesNotExist:
        return

    if override is not None and (not feature or not override.system
                                 or feature == override.system):
        override.delete()
    else:
        return

    # Cache a new event id and event type which the signal handler will use to emit a tracking log event.
    create_new_event_transaction_id()
    set_event_transaction_type(events.SUBSECTION_OVERRIDE_EVENT_TYPE)

    # Signal will trigger subsection recalculation which will call PersistentSubsectionGrade.update_or_create_grade
    # which will no longer use the above deleted override, and instead return the grade to the original score from
    # the actual problem responses before writing to the table.
    signals.SUBSECTION_OVERRIDE_CHANGED.send(
        sender=None,
        user_id=user_id,
        course_id=text_type(course_key),
        usage_id=text_type(usage_key),
        only_if_higher=False,
        modified=datetime.now().replace(
            tzinfo=pytz.UTC),  # Not used when score_deleted=True
        score_deleted=True,
        score_db_table=constants.ScoreDatabaseTableEnum.overrides)
Beispiel #10
0
    def override_subsection_grade(self,
                                  user_id,
                                  course_key_or_id,
                                  usage_key_or_id,
                                  earned_all=None,
                                  earned_graded=None):
        """
        Override subsection grade (the PersistentSubsectionGrade model must already exist)

        Fires off a recalculate_subsection_grade async task to update the PersistentSubsectionGrade table. Will not
        override earned_all or earned_graded value if they are None. Both default to None.
        """
        course_key = _get_key(course_key_or_id, CourseKey)
        usage_key = _get_key(usage_key_or_id, UsageKey)

        grade = PersistentSubsectionGrade.objects.get(user_id=user_id,
                                                      course_id=course_key,
                                                      usage_key=usage_key)

        # Create override that will prevent any future updates to grade
        override, _ = PersistentSubsectionGradeOverride.objects.update_or_create(
            grade=grade,
            earned_all_override=earned_all,
            earned_graded_override=earned_graded)

        _ = PersistentSubsectionGradeOverrideHistory.objects.create(
            override_id=override.id,
            feature=PersistentSubsectionGradeOverrideHistory.PROCTORING,
            action=PersistentSubsectionGradeOverrideHistory.CREATE_OR_UPDATE)

        # Cache a new event id and event type which the signal handler will use to emit a tracking log event.
        create_new_event_transaction_id()
        set_event_transaction_type(SUBSECTION_OVERRIDE_EVENT_TYPE)

        # Signal will trigger subsection recalculation which will call PersistentSubsectionGrade.update_or_create_grade
        # which will use the above override to update the grade before writing to the table.
        SUBSECTION_OVERRIDE_CHANGED.send(
            sender=None,
            user_id=user_id,
            course_id=unicode(course_key),
            usage_id=unicode(usage_key),
            only_if_higher=False,
            modified=override.modified,
            score_deleted=False,
            score_db_table=ScoreDatabaseTableEnum.overrides)
Beispiel #11
0
def get_subsection_grades(user_id, course_key_or_id):
    """
    Return dictionary of grades for user_id.
    """
    course_key = _get_key(course_key_or_id, CourseKey)
    grades = {}
    for grade in _PersistentSubsectionGrade.bulk_read_grades(user_id, course_key):
        grades[grade.usage_key] = grade
    return grades
Beispiel #12
0
    def undo_override_subsection_grade(self, user_id, course_key_or_id,
                                       usage_key_or_id):
        """
        Delete the override subsection grade row (the PersistentSubsectionGrade model must already exist)

        Fires off a recalculate_subsection_grade async task to update the PersistentSubsectionGrade table. If the
        override does not exist, no error is raised, it just triggers the recalculation.
        """
        course_key = _get_key(course_key_or_id, CourseKey)
        usage_key = _get_key(usage_key_or_id, UsageKey)

        try:
            override = self.get_subsection_grade_override(
                user_id, course_key, usage_key)
        except PersistentSubsectionGrade.DoesNotExist:
            return

        # Older rejected exam attempts that transition to verified might not have an override created
        if override is not None:
            _ = PersistentSubsectionGradeOverrideHistory.objects.create(
                override_id=override.id,
                feature=PersistentSubsectionGradeOverrideHistory.PROCTORING,
                action=PersistentSubsectionGradeOverrideHistory.DELETE)
            override.delete()

        # Cache a new event id and event type which the signal handler will use to emit a tracking log event.
        create_new_event_transaction_id()
        set_event_transaction_type(SUBSECTION_OVERRIDE_EVENT_TYPE)

        # Signal will trigger subsection recalculation which will call PersistentSubsectionGrade.update_or_create_grade
        # which will no longer use the above deleted override, and instead return the grade to the original score from
        # the actual problem responses before writing to the table.
        SUBSECTION_OVERRIDE_CHANGED.send(
            sender=None,
            user_id=user_id,
            course_id=unicode(course_key),
            usage_id=unicode(usage_key),
            only_if_higher=False,
            modified=datetime.now().replace(
                tzinfo=pytz.UTC),  # Not used when score_deleted=True
            score_deleted=True,
            score_db_table=ScoreDatabaseTableEnum.overrides)
Beispiel #13
0
    def undo_override_subsection_grade(self, user_id, course_key_or_id, usage_key_or_id):
        """
        Delete the override subsection grade row (the PersistentSubsectionGrade model must already exist)

        Fires off a recalculate_subsection_grade async task to update the PersistentSubsectionGrade table. If the
        override does not exist, no error is raised, it just triggers the recalculation.
        """
        course_key = _get_key(course_key_or_id, CourseKey)
        usage_key = _get_key(usage_key_or_id, UsageKey)

        try:
            override = self.get_subsection_grade_override(user_id, course_key, usage_key)
        except PersistentSubsectionGrade.DoesNotExist:
            return

        # Older rejected exam attempts that transition to verified might not have an override created
        if override is not None:
            _ = PersistentSubsectionGradeOverrideHistory.objects.create(
                override_id=override.id,
                feature=GradeOverrideFeatureEnum.proctoring,
                action=PersistentSubsectionGradeOverrideHistory.DELETE
            )
            override.delete()

        # Cache a new event id and event type which the signal handler will use to emit a tracking log event.
        create_new_event_transaction_id()
        set_event_transaction_type(SUBSECTION_OVERRIDE_EVENT_TYPE)

        # Signal will trigger subsection recalculation which will call PersistentSubsectionGrade.update_or_create_grade
        # which will no longer use the above deleted override, and instead return the grade to the original score from
        # the actual problem responses before writing to the table.
        SUBSECTION_OVERRIDE_CHANGED.send(
            sender=None,
            user_id=user_id,
            course_id=text_type(course_key),
            usage_id=text_type(usage_key),
            only_if_higher=False,
            modified=datetime.now().replace(tzinfo=pytz.UTC),  # Not used when score_deleted=True
            score_deleted=True,
            score_db_table=ScoreDatabaseTableEnum.overrides
        )
Beispiel #14
0
    def get_subsection_grade_override(self, user_id, course_key_or_id, usage_key_or_id):
        """
        Finds the subsection grade for user and returns the override for that grade if it exists

        If override does not exist, returns None. If subsection grade does not exist, will raise an exception.
        """
        usage_key = _get_key(usage_key_or_id, UsageKey)

        # Verify that a corresponding subsection grade exists for the given user and usage_key
        # Raises PersistentSubsectionGrade.DoesNotExist if it does not exist.
        _ = self.get_subsection_grade(user_id, course_key_or_id, usage_key_or_id)

        return PersistentSubsectionGradeOverride.get_override(user_id, usage_key)
Beispiel #15
0
def get_subsection_grade_override(user_id, course_key_or_id, usage_key_or_id):
    """
    Finds the subsection grade for user and returns the override for that grade if it exists

    If override does not exist, returns None. If subsection grade does not exist, will raise an exception.
    """
    usage_key = _get_key(usage_key_or_id, UsageKey)

    # Verify that a corresponding subsection grade exists for the given user and usage_key
    # Raises PersistentSubsectionGrade.DoesNotExist if it does not exist.
    _ = get_subsection_grade(user_id, course_key_or_id, usage_key_or_id)

    return _PersistentSubsectionGradeOverride.get_override(user_id, usage_key)
Beispiel #16
0
 def invalidate_certificate(self, user_id, course_key_or_id):
     """
     Invalidate the user certificate in a given course if it exists.
     """
     course_key = _get_key(course_key_or_id, CourseKey)
     try:
         generated_certificate = GeneratedCertificate.objects.get(
             user=user_id, course_id=course_key)
         generated_certificate.invalidate()
         log.info(u'Certificate invalidated for user %d in course %s',
                  user_id, course_key)
     except ObjectDoesNotExist:
         log.warning(
             u'Invalidation failed because a certificate for user %d in course %s does not exist.',
             user_id, course_key)
Beispiel #17
0
 def invalidate_certificate(self, user_id, course_key_or_id):
     """
     Invalidate the user certificate in a given course if it exists.
     """
     course_key = _get_key(course_key_or_id, CourseKey)
     try:
         generated_certificate = GeneratedCertificate.objects.get(
             user=user_id,
             course_id=course_key
         )
         generated_certificate.invalidate()
         log.info(
             u'Certificate invalidated for user %d in course %s',
             user_id,
             course_key
         )
     except ObjectDoesNotExist:
         log.warning(
             u'Invalidation failed because a certificate for user %d in course %s does not exist.',
             user_id,
             course_key
         )
Beispiel #18
0
 def should_override_grade_on_rejected_exam(self, course_key_or_id):
     """Convienence function to return the state of the CourseWaffleFlag REJECTED_EXAM_OVERRIDES_GRADE"""
     course_key = _get_key(course_key_or_id, CourseKey)
     return waffle_flags()[REJECTED_EXAM_OVERRIDES_GRADE].is_enabled(course_key)
Beispiel #19
0
 def test_get_key(self, input_key, output_key, key_cls):
     self.assertEqual(_get_key(input_key, key_cls), output_key)
Beispiel #20
0
 def test_get_key(self, input_key, output_key, key_cls):
     assert _get_key(input_key, key_cls) == output_key
Beispiel #21
0
 def should_override_grade_on_rejected_exam(self, course_key_or_id):
     """Convienence function to return the state of the CourseWaffleFlag REJECTED_EXAM_OVERRIDES_GRADE"""
     course_key = _get_key(course_key_or_id, CourseKey)
     return waffle_flags()[REJECTED_EXAM_OVERRIDES_GRADE].is_enabled(
         course_key)
Beispiel #22
0
 def test_get_key(self, input_key, output_key, key_cls):
     self.assertEqual(_get_key(input_key, key_cls), output_key)