コード例 #1
0
ファイル: learning_records.py プロジェクト: ubc/compair
    def post(self):
        if not XAPI.enabled():
            # this should silently fail
            abort(404)

        raw_params = request.get_json(force=True)
        params = {}

        course_uuid = raw_params.get('course_id')
        course = _get_valid_course(course_uuid)

        # add required params
        for param in ['verb', 'object']:
            if not raw_params.get(param):
                abort(400)
            params[param] = raw_params.get(param)

        # add optional params
        for param in ['context', 'result', 'timestamp']:
            if raw_params.get(param):
                params[param] = raw_params.get(param)

        statement = XAPIStatement.generate_from_params(current_user,
                                                       params,
                                                       course=course)
        XAPI.emit(statement)

        return {'success': True}
コード例 #2
0
ファイル: emit_learning_record.py プロジェクト: ubc/compair
def emit_lrs_xapi_statement(self, xapi_log_id):
    from compair.learning_records import XAPI

    xapi_log = XAPILog.query \
        .filter_by(
            id=xapi_log_id,
            transmitted=False
        ) \
        .one_or_none()

    if xapi_log:
        try:
            XAPI._emit_to_lrs(json.loads(xapi_log.statement))
        except socket.error as error:
            # don't raise connection refused error when in eager mode
            if error.errno != socket.errno.ECONNREFUSED:
                current_app.logger.error(
                    "emit_lrs_xapi_statement connection refused: " +
                    socket.error.strerror)
                return
            raise error

        XAPILog.query \
            .filter_by(id=xapi_log_id) \
            .delete()
        db.session.commit()
コード例 #3
0
ファイル: emit_learning_record.py プロジェクト: ubc/compair
def resend_learning_records(self):
    from compair.learning_records import XAPI, CaliperSensor

    # only re-send learning records that have last started over an hour ago
    # (this is to try and prevent sending duplicates if possible)
    one_hour_ago = datetime.datetime.utcnow() - datetime.timedelta(hours=1)

    if XAPI.enabled() and not XAPI.storing_locally():
        xapi_logs = XAPILog.query \
            .filter(and_(
                XAPILog.transmitted == False,
                XAPILog.modified <= one_hour_ago
            )) \
            .all()

        for xapi_log in xapi_logs:
            emit_lrs_xapi_statement(xapi_log.id)

    if CaliperSensor.enabled() and not CaliperSensor.storing_locally():
        caliper_logs = CaliperLog.query \
            .filter(and_(
                CaliperLog.transmitted == False,
                CaliperLog.modified <= one_hour_ago
            )) \
            .all()

        for caliper_log in caliper_logs:
            emit_lrs_caliper_event(caliper_log.id)
コード例 #4
0
def resend_learning_records(self):
    from compair.learning_records import XAPI, CaliperSensor

    # only re-send learning records that have last started over an hour ago
    # (this is to try and prevent sending duplicates if possible)
    one_hour_ago = datetime.datetime.utcnow() - datetime.timedelta(hours=1)

    if XAPI.enabled() and not XAPI.storing_locally():
        xapi_logs = XAPILog.query \
            .filter(and_(
                XAPILog.transmitted == False,
                XAPILog.modified <= one_hour_ago
            )) \
            .all()

        for xapi_log in xapi_logs:
            emit_lrs_xapi_statement.delay(xapi_log.id)

    if CaliperSensor.enabled() and not CaliperSensor.storing_locally():
        caliper_logs = CaliperLog.query \
            .filter(and_(
                CaliperLog.transmitted == False,
                CaliperLog.modified <= one_hour_ago
            )) \
            .all()

        for caliper_log in caliper_logs:
            emit_lrs_caliper_event.delay(caliper_log.id)
コード例 #5
0
ファイル: learning_records.py プロジェクト: ubc/acj-versus
    def post(self):
        if not XAPI.enabled():
            # this should silently fail
            abort(404)

        params = xapi_statement_parser.parse_args()
        course_uuid = params.pop('course_id')
        course = _get_valid_course(course_uuid)

        statement = XAPIStatement.generate_from_params(current_user, params, course=course)
        XAPI.emit(statement)

        return { 'success': True }
コード例 #6
0
    def post(self):
        if not XAPI.enabled():
            # this should silently fail
            abort(404)

        params = xapi_statement_parser.parse_args()
        course_uuid = params.pop('course_id')
        course = _get_valid_course(course_uuid)

        statement = XAPIStatement.generate_from_params(current_user,
                                                       params,
                                                       course=course)
        XAPI.emit(statement)

        return {'success': True}
コード例 #7
0
def emit_lrs_xapi_statement(self, xapi_log_id):
    from compair.learning_records import XAPI

    xapi_log = XAPILog.query.filter_by(id=xapi_log_id).one_or_none()

    if xapi_log:
        try:
            XAPI._emit_to_lrs(json.loads(xapi_log.statement))
        except socket.error as error:
            # don't raise connection refused error when in eager mode
            if error.errno != socket.errno.ECONNREFUSED:
                return
            raise error

        xapi_log.transmitted = True
        db.session.commit()
コード例 #8
0
ファイル: test_remote.py プロジェクト: vishnu-meera/ACJ
    def test_send_remote_xapi_statement(self, mocked_save_statement):
        self.app.config['CALIPER_ENABLED'] = False

        def save_statement_override(statement):
            self.sent_xapi_statement = json.loads(
                statement.to_json(XAPI._version))

            return LRSResponse(
                success=True,
                request=None,
                response=None,
                data=json.dumps(["123"]),
            )

        mocked_save_statement.side_effect = save_statement_override

        expected_course = {
            'id': "https://localhost:8888/app/course/" + self.course.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/course',
                'name': {
                    'en-US': self.course.name
                }
            },
            'objectType': 'Activity'
        }

        expected_sis_course = {
            'id': 'https://localhost:8888/course/' +
            self.lti_context.lis_course_offering_sourcedid,
            'objectType': 'Activity'
        }

        expected_sis_section = {
            'id':
            'https://localhost:8888/course/' +
            self.lti_context.lis_course_offering_sourcedid + '/section/' +
            self.lti_context.lis_course_section_sourcedid,
            'objectType':
            'Activity'
        }

        expected_assignment = {
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/assessment',
                'name': {
                    'en-US': self.assignment.name
                },
                'description': {
                    'en-US': self.assignment.description
                },
            },
            'objectType':
            'Activity'
        }

        expected_assignment_question = {
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid + "/question",
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/question',
                'name': {
                    'en-US': self.assignment.name
                },
                'description': {
                    'en-US': self.assignment.description
                },
            },
            'objectType':
            'Activity'
        }

        expected_attempt = {
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid + "/question/attempt/" +
            self.answer.attempt_uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/attempt',
                'extensions': {
                    'http://id.tincanapi.com/extension/attempt': {
                        'duration':
                        "PT05M00S",
                        'startedAtTime':
                        self.answer.attempt_started.replace(
                            tzinfo=pytz.utc).isoformat(),
                        'endedAtTime':
                        self.answer.attempt_ended.replace(
                            tzinfo=pytz.utc).isoformat(),
                    }
                }
            },
            'objectType':
            'Activity'
        }

        expected_answer = {
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid + "/answer/" +
            self.answer.uuid,
            'definition': {
                'type': 'http://id.tincanapi.com/activitytype/solution',
                'extensions': {
                    'http://id.tincanapi.com/extension/isDraft': False
                }
            },
            'objectType':
            'Activity'
        }

        expected_answer_comment = {
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid + "/answer/" +
            self.answer.uuid + "/comment/" + self.answer_comment.uuid,
            'definition': {
                'type': 'http://activitystrea.ms/schema/1.0/comment',
                'name': {
                    'en-US': "Assignment answer comment"
                },
                'extensions': {
                    'http://id.tincanapi.com/extension/isDraft':
                    False,
                    'http://id.tincanapi.com/extension/type':
                    self.answer_comment.comment_type.value
                }
            },
            'objectType':
            'Activity'
        }

        expected_verb = {
            'id': 'http://activitystrea.ms/schema/1.0/submit',
            'display': {
                'en-US': 'submitted'
            }
        }

        expected_context = {
            'contextActivities': {
                'parent': [expected_assignment_question, expected_attempt],
                'grouping': [
                    expected_assignment, expected_course, expected_sis_course,
                    expected_sis_section
                ]
            },
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info':
                self.get_xapi_session_info()
            }
        }

        expected_result = {
            'success': True,
            'response': self.answer.content,
            'extensions': {
                'http://xapi.learninganalytics.ubc.ca/extension/character-count':
                len(self.answer.content),
                'http://xapi.learninganalytics.ubc.ca/extension/word-count':
                len(self.answer.content.split(" "))
            }
        }

        expected_statement = {
            "actor": self.get_compair_xapi_actor(self.user),
            "verb": expected_verb,
            "object": expected_answer,
            "context": expected_context,
            "result": expected_result
        }

        # test with answer normal content
        statement = XAPIStatement.generate(
            user=self.user,
            verb=XAPIVerb.generate('submitted'),
            object=XAPIObject.answer(self.answer),
            context=XAPIContext.answer(self.answer),
            result=XAPIResult.basic_content(self.answer.content, success=True))

        XAPI._emit_to_lrs(json.loads(statement.to_json(XAPI._version)))
        self._validate_and_cleanup_xapi_statement(self.sent_xapi_statement)
        self.assertEqual(self.sent_xapi_statement, expected_statement)

        # test with extremely long answer content

        # content should be ~ LRS_USER_INPUT_FIELD_SIZE_LIMIT bytes long + 100 characters
        content = "c" * (self.character_limit + 100)
        self.answer.content = content
        db.session.commit()

        # expected_answer content should be <= LRS_USER_INPUT_FIELD_SIZE_LIMIT bytes long + " [TEXT TRIMMED]..."
        expected_result['response'] = (
            "c" * self.character_limit) + " [TEXT TRIMMED]..."
        expected_result['extensions'][
            'http://xapi.learninganalytics.ubc.ca/extension/word-count'] = 1
        expected_result['extensions'][
            'http://xapi.learninganalytics.ubc.ca/extension/character-count'] = len(
                content)

        statement = XAPIStatement.generate(
            user=self.user,
            verb=XAPIVerb.generate('submitted'),
            object=XAPIObject.answer(self.answer),
            context=XAPIContext.answer(self.answer),
            result=XAPIResult.basic_content(self.answer.content, success=True))

        XAPI._emit_to_lrs(json.loads(statement.to_json(XAPI._version)))
        self._validate_and_cleanup_xapi_statement(self.sent_xapi_statement)
        self.assertEqual(self.sent_xapi_statement, expected_statement)

        expected_verb = {
            'id': 'http://activitystrea.ms/schema/1.0/update',
            'display': {
                'en-US': 'updated'
            }
        }

        expected_result = {
            'response': self.answer_comment.content,
            'extensions': {
                'http://xapi.learninganalytics.ubc.ca/extension/character-count':
                len(self.answer_comment.content),
                'http://xapi.learninganalytics.ubc.ca/extension/word-count':
                len(self.answer_comment.content.split(" "))
            }
        }

        expected_context = {
            'contextActivities': {
                'parent': [expected_answer],
                'grouping': [
                    expected_assignment_question, expected_assignment,
                    expected_course, expected_sis_course, expected_sis_section
                ]
            },
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info':
                self.get_xapi_session_info()
            }
        }

        expected_statement = {
            "actor": self.get_compair_xapi_actor(self.user),
            "verb": expected_verb,
            "object": expected_answer_comment,
            "context": expected_context,
            "result": expected_result
        }

        # test with answer comment normal content
        statement = XAPIStatement.generate(
            user=self.user,
            verb=XAPIVerb.generate('updated'),
            object=XAPIObject.answer_comment(self.answer_comment),
            context=XAPIContext.answer_comment(self.answer_comment),
            result=XAPIResult.basic_content(self.answer_comment.content))

        XAPI._emit_to_lrs(json.loads(statement.to_json(XAPI._version)))
        self._validate_and_cleanup_xapi_statement(self.sent_xapi_statement)
        self.assertEqual(self.sent_xapi_statement, expected_statement)

        # test with extremely long answer comment content

        # content should be ~ LRS_USER_INPUT_FIELD_SIZE_LIMIT bytes long + 100 characters
        content = "d" * (self.character_limit + 100)

        self.answer_comment.content = content
        db.session.commit()

        # expected_assignment name and description should be <= LRS_USER_INPUT_FIELD_SIZE_LIMIT bytes long + " [TEXT TRIMMED]..."
        expected_result['response'] = (
            "d" * self.character_limit) + " [TEXT TRIMMED]..."
        expected_result['extensions'][
            'http://xapi.learninganalytics.ubc.ca/extension/word-count'] = 1
        expected_result['extensions'][
            'http://xapi.learninganalytics.ubc.ca/extension/character-count'] = len(
                content)

        statement = XAPIStatement.generate(
            user=self.user,
            verb=XAPIVerb.generate('updated'),
            object=XAPIObject.answer_comment(self.answer_comment),
            context=XAPIContext.answer_comment(self.answer_comment),
            result=XAPIResult.basic_content(self.answer_comment.content))

        XAPI._emit_to_lrs(json.loads(statement.to_json(XAPI._version)))
        self._validate_and_cleanup_xapi_statement(self.sent_xapi_statement)
        self.assertEqual(self.sent_xapi_statement, expected_statement)

        expected_context = {
            'contextActivities': {
                'parent': [expected_course],
                'grouping': [expected_sis_course, expected_sis_section]
            },
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info':
                self.get_xapi_session_info()
            }
        }

        expected_statement = {
            "actor": self.get_compair_xapi_actor(self.user),
            "verb": expected_verb,
            "object": expected_assignment,
            "context": expected_context
        }

        # test with assignment normal content
        statement = XAPIStatement.generate(
            user=self.user,
            verb=XAPIVerb.generate('updated'),
            object=XAPIObject.assignment(self.assignment),
            context=XAPIContext.assignment(self.assignment))

        XAPI._emit_to_lrs(json.loads(statement.to_json(XAPI._version)))
        self._validate_and_cleanup_xapi_statement(self.sent_xapi_statement)
        self.assertEqual(self.sent_xapi_statement, expected_statement)

        # test with extremely long answer content

        # content should be ~ LRS_USER_INPUT_FIELD_SIZE_LIMIT bytes long + 100 characters
        name = "a" * (self.character_limit + 100)
        description = "b" * (self.character_limit + 100)

        self.assignment.name = name
        self.assignment.description = description
        db.session.commit()

        # expected_assignment name and description should be <= LRS_USER_INPUT_FIELD_SIZE_LIMIT bytes long + " [TEXT TRIMMED]..."
        expected_assignment['definition']['name']['en-US'] = (
            "a" * self.character_limit) + " [TEXT TRIMMED]..."
        expected_assignment['definition']['description']['en-US'] = (
            "b" * self.character_limit) + " [TEXT TRIMMED]..."

        statement = XAPIStatement.generate(
            user=self.user,
            verb=XAPIVerb.generate('updated'),
            object=XAPIObject.assignment(self.assignment),
            context=XAPIContext.assignment(self.assignment))

        XAPI._emit_to_lrs(json.loads(statement.to_json(XAPI._version)))
        self._validate_and_cleanup_xapi_statement(self.sent_xapi_statement)
        self.assertEqual(self.sent_xapi_statement, expected_statement)