Пример #1
0
    def test_credit_request(self):
        # Initiate a credit request
        request = api.create_credit_request(self.course_key, self.PROVIDER_ID,
                                            self.USER_INFO['username'])

        # Validate the URL and method
        self.assertIn('url', request)
        self.assertEqual(request['url'], self.PROVIDER_URL)
        self.assertIn('method', request)
        self.assertEqual(request['method'], "POST")

        self.assertIn('parameters', request)
        parameters = request['parameters']

        # Validate the UUID
        self.assertIn('request_uuid', parameters)
        self.assertEqual(len(parameters['request_uuid']), 32)

        # Validate the timestamp
        self.assertIn('timestamp', parameters)
        parsed_date = from_timestamp(parameters['timestamp'])
        self.assertTrue(parsed_date < datetime.datetime.now(pytz.UTC))

        # Validate course information
        self.assertEqual(parameters['course_org'], self.course_key.org)
        self.assertEqual(parameters['course_num'], self.course_key.course)
        self.assertEqual(parameters['course_run'], self.course_key.run)
        self.assertEqual(parameters['final_grade'], unicode(self.FINAL_GRADE))

        # Validate user information
        for key in self.USER_INFO.keys():
            param_key = 'user_{key}'.format(key=key)
            self.assertIn(param_key, parameters)
            expected = '' if key == 'mailing_address' else self.USER_INFO[key]
            self.assertEqual(parameters[param_key], expected)
Пример #2
0
    def test_credit_request(self):
        # Initiate a credit request
        request = api.create_credit_request(self.course_key, self.PROVIDER_ID, self.USER_INFO['username'])

        # Validate the URL and method
        self.assertIn('url', request)
        self.assertEqual(request['url'], self.PROVIDER_URL)
        self.assertIn('method', request)
        self.assertEqual(request['method'], "POST")

        self.assertIn('parameters', request)
        parameters = request['parameters']

        # Validate the UUID
        self.assertIn('request_uuid', parameters)
        self.assertEqual(len(parameters['request_uuid']), 32)

        # Validate the timestamp
        self.assertIn('timestamp', parameters)
        parsed_date = from_timestamp(parameters['timestamp'])
        self.assertLess(parsed_date, datetime.datetime.now(pytz.UTC))

        # Validate course information
        self.assertEqual(parameters['course_org'], self.course_key.org)
        self.assertEqual(parameters['course_num'], self.course_key.course)
        self.assertEqual(parameters['course_run'], self.course_key.run)
        self.assertEqual(parameters['final_grade'], unicode(self.FINAL_GRADE))

        # Validate user information
        for key in self.USER_INFO.keys():
            param_key = 'user_{key}'.format(key=key)
            self.assertIn(param_key, parameters)
            expected = '' if key == 'mailing_address' else self.USER_INFO[key]
            self.assertEqual(parameters[param_key], expected)
Пример #3
0
def _validate_timestamp(timestamp_value, provider_id):
    """
    Check that the timestamp of the request is recent.

    Arguments:
        timestamp (int or string): Number of seconds since Jan. 1, 1970 UTC.
            If specified as a string, it will be converted to an integer.
        provider_id (unicode): Identifier for the credit provider.

    Returns:
        HttpResponse or None

    """
    timestamp = from_timestamp(timestamp_value)
    if timestamp is None:
        msg = u'"{timestamp}" is not a valid timestamp'.format(
            timestamp=timestamp_value)
        log.warning(msg)
        return HttpResponseBadRequest(msg)

    # Check that the timestamp is recent
    elapsed_seconds = (datetime.datetime.now(pytz.UTC) -
                       timestamp).total_seconds()
    if elapsed_seconds > settings.CREDIT_PROVIDER_TIMESTAMP_EXPIRATION:
        log.warning(
            (u'Timestamp %s is too far in the past (%s seconds), '
             u'so we are rejecting the notification from the credit provider "%s".'
             ),
            timestamp_value,
            elapsed_seconds,
            provider_id,
        )
        return HttpResponseForbidden(u"Timestamp is too far in the past.")
Пример #4
0
def _validate_timestamp(timestamp_value, provider_id):
    """
    Check that the timestamp of the request is recent.

    Arguments:
        timestamp (int or string): Number of seconds since Jan. 1, 1970 UTC.
            If specified as a string, it will be converted to an integer.
        provider_id (unicode): Identifier for the credit provider.

    Returns:
        HttpResponse or None

    """
    timestamp = from_timestamp(timestamp_value)
    if timestamp is None:
        msg = u'"{timestamp}" is not a valid timestamp'.format(timestamp=timestamp_value)
        log.warning(msg)
        return HttpResponseBadRequest(msg)

    # Check that the timestamp is recent
    elapsed_seconds = (datetime.datetime.now(pytz.UTC) - timestamp).total_seconds()
    if elapsed_seconds > settings.CREDIT_PROVIDER_TIMESTAMP_EXPIRATION:
        log.warning(
            (
                u'Timestamp %s is too far in the past (%s seconds), '
                u'so we are rejecting the notification from the credit provider "%s".'
            ),
            timestamp_value, elapsed_seconds, provider_id,
        )
        return HttpResponseForbidden(u"Timestamp is too far in the past.")
Пример #5
0
def recalculate_subsection_grade_v2(**kwargs):
    """
    Updates a saved subsection grade.

    Arguments:
        user_id (int): id of applicable User object
        course_id (string): identifying the course
        usage_id (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.
        expected_modified_time (serialized timestamp): indicates when the task
            was queued so that we can verify the underlying data update.
        score_deleted (boolean): indicating whether the grade change is
            a result of the problem's score being deleted.
        event_transaction_id(string): uuid identifying the current
            event transaction.
        event_transaction_type(string): human-readable type of the
            event at the root of the current event transaction.
    """
    try:
        course_key = CourseLocator.from_string(kwargs['course_id'])
        if not PersistentGradesEnabledFlag.feature_enabled(course_key):
            return

        score_deleted = kwargs['score_deleted']
        scored_block_usage_key = UsageKey.from_string(kwargs['usage_id']).replace(course_key=course_key)
        expected_modified_time = from_timestamp(kwargs['expected_modified_time'])

        # The request cache is not maintained on celery workers,
        # where this code runs. So we take the values from the
        # main request cache and store them in the local request
        # cache. This correlates model-level grading events with
        # higher-level ones.
        set_event_transaction_id(kwargs.pop('event_transaction_id', None))
        set_event_transaction_type(kwargs.pop('event_transaction_type', None))

        # Verify the database has been updated with 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.
        if not _has_database_updated_with_new_score(
                kwargs['user_id'], scored_block_usage_key, expected_modified_time, score_deleted,
        ):
            raise _retry_recalculate_subsection_grade(**kwargs)

        _update_subsection_grades(
            course_key,
            scored_block_usage_key,
            kwargs['only_if_higher'],
            kwargs['user_id'],
        )

    except Exception as exc:   # pylint: disable=broad-except
        if not isinstance(exc, KNOWN_RETRY_ERRORS):
            log.info("tnl-6244 grades unexpected failure: {}. kwargs={}".format(
                repr(exc),
                kwargs
            ))
        raise _retry_recalculate_subsection_grade(exc=exc, **kwargs)
Пример #6
0
    def test_credit_request(self):
        # Initiate a credit request
        request = api.create_credit_request(self.course_key, self.PROVIDER_ID, self.USER_INFO["username"])

        # Validate the URL and method
        self.assertIn("url", request)
        self.assertEqual(request["url"], self.PROVIDER_URL)
        self.assertIn("method", request)
        self.assertEqual(request["method"], "POST")

        self.assertIn("parameters", request)
        parameters = request["parameters"]

        # Validate the UUID
        self.assertIn("request_uuid", parameters)
        self.assertEqual(len(parameters["request_uuid"]), 32)

        # Validate the timestamp
        self.assertIn("timestamp", parameters)
        parsed_date = from_timestamp(parameters["timestamp"])
        self.assertLess(parsed_date, datetime.datetime.now(pytz.UTC))

        # Validate course information
        self.assertEqual(parameters["course_org"], self.course_key.org)
        self.assertEqual(parameters["course_num"], self.course_key.course)
        self.assertEqual(parameters["course_run"], self.course_key.run)
        self.assertEqual(parameters["final_grade"], unicode(self.FINAL_GRADE))

        # Validate user information
        for key in self.USER_INFO.keys():
            param_key = "user_{key}".format(key=key)
            self.assertIn(param_key, parameters)
            expected = "" if key == "mailing_address" else self.USER_INFO[key]
            self.assertEqual(parameters[param_key], expected)
Пример #7
0
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
Пример #8
0
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
Пример #9
0
    def validate_timestamp(self, value):
        """ Ensure the request has been received in a timely manner. """
        date_time = from_timestamp(value)

        # Ensure we converted the timestamp to a datetime
        if not date_time:
            msg = '[{}] is not a valid timestamp'.format(value)
            log.warning(msg)
            raise serializers.ValidationError(msg)

        elapsed = (datetime.datetime.now(pytz.UTC) - date_time).total_seconds()
        if elapsed > settings.CREDIT_PROVIDER_TIMESTAMP_EXPIRATION:
            msg = '[{value}] is too far in the past (over [{elapsed}] seconds).'.format(value=value, elapsed=elapsed)
            log.warning(msg)
            raise serializers.ValidationError(msg)

        return value
Пример #10
0
    def validate_timestamp(self, value):
        """ Ensure the request has been received in a timely manner. """
        date_time = from_timestamp(value)

        # Ensure we converted the timestamp to a datetime
        if not date_time:
            msg = '[{}] is not a valid timestamp'.format(value)
            log.warning(msg)
            raise serializers.ValidationError(msg)

        elapsed = (datetime.datetime.now(pytz.UTC) - date_time).total_seconds()
        if elapsed > settings.CREDIT_PROVIDER_TIMESTAMP_EXPIRATION:
            msg = '[{value}] is too far in the past (over [{elapsed}] seconds).'.format(value=value, elapsed=elapsed)
            log.warning(msg)
            raise serializers.ValidationError(msg)

        return value