def do_submission_hooks(the_item: SurveyBase, submission_data: dict, actor: Party = None): assert not isinstance(the_item, Question) # no submission hooks for single questions hook_configuration = get_hook_configuration(the_item) if hook_configuration is None: return score = hook_configuration['scoring_strategy'].score_item(submission_data) actor = get_current_user_as_actor( ) if actor is None else get_party_as_actor(actor) verb = XApiVerb(XApiVerbs.Answered) activity = get_xapi_object(the_item) result = XApiScoredResult(score, hook_configuration['score_min'], hook_configuration['score_max'], 1) context = XApiSurveyContext(the_item) statement = XApiStatement(actor, verb, activity, result, context) questionnaire = get_questionnaire(the_item) XApiPublisher().enqueue_deferred([statement], get_xapi_target(the_item), questionnaire.id)
def enqueue_answered_xapi_statements(sender: QuestionResponse): question = sender.question subject = next(filter(lambda s: isinstance(s, DataSubject), sender.owners)) receiver = get_xapi_target(sender.question) statement = XApiAnswerSubmittedStatement(subject, question, sender.value) XApiPublisher().enqueue_deferred([statement], receiver, sender.id)
def publish_lti_launch_xapi_statement(sender: DataSubject, questionnaire=None): actor = get_party_as_actor(sender) verb = XApiVerb(XApiVerbs.Accessed) activity = get_xapi_object(questionnaire) context = XApiSt3k101Context() statement = XApiStatement(actor, verb, activity, xapi_context=context) XApiPublisher().enqueue([statement], g._config['XAPI_DEFAULT_ENDPOINT'])
def publish_questionnaire_retracted_xapi_statement(sender: Questionnaire): actor = get_current_user_as_actor() verb = XApiVerb(XApiVerbs.RetractedSurvey) activity = get_xapi_object(sender) context = XApiSt3k101Context() receiver = get_xapi_target(sender) statement = XApiStatement(actor, verb, activity, xapi_context=context) XApiPublisher().enqueue([statement], receiver)
def after_request(response: Response): """ Called before response is sent to client. We may transform the response before sending it out. :param response: Reponse That is about to be sent back :return: Response the response that is sent back to the client """ XApiPublisher().flush() # set the locale as cookie, keeps locale constant for a period of time if request.args.get('locale'): if request.args.get('locale_cookie', 1) == 1: response.set_cookie('locale', g._language.name) # we respond in the user's language response.headers['Content-Language'] = g._language.name return response
def publish_reference_id_updated_xapi_statement(sender: SurveyBase, previous_value: str = "Missing" ): actor = get_current_user_as_actor() verb = XApiVerb(XApiVerbs.ChangedReferenceId) activity = None if isinstance(sender, Question): activity = XApiActivities.Question elif isinstance(sender, Dimension): activity = XApiActivities.Dimension elif isinstance(sender, Questionnaire): activity = XApiActivities.Questionnaire assert activity is not None previous_value = "<{}>:{}".format(sender.owning_dataclient.email, previous_value) activity = XApiActivityObject(activity, previous_value, sender.name_translations) result = XApiResponseResult("<{}>:{}".format( sender.owning_dataclient.email, sender.reference_id)) context = XApiSt3k101Context() receiver = get_xapi_target(sender) statement = XApiStatement(actor, verb, activity, result, context) XApiPublisher().enqueue([statement], receiver)
def post(self, questionnaire_id: int = None): schema = SubmissionSchema() data, errors = schema.load(request.json) questionnaire = Questionnaire.query.get_or_404(questionnaire_id) if errors: return { 'message': 'Your request contained errors.', 'errors': errors }, 400 # survey lifecycle check if not questionnaire.published: abort(403) if not questionnaire.accepts_submissions: abort(403, message="Submissions are not accepted at this point") # captcha and challenges if not validate_captcha(data['captcha_token']): return { 'message': 'CAPTCHA confidence score too low. Please try again.' }, 403 challenge_errors = validate_challenges(questionnaire, data) if challenge_errors: return { 'message': 'Some challenges could not be completed.', 'errors': challenge_errors }, 403 verification_token = generate_verification_token() data_subject = DataSubject.get_or_create(data['data_subject']['email']) all_questions = { q.id for d in questionnaire.dimensions for q in d.questions } for dimension_data in data['dimensions']: dimension = next((d for d in questionnaire.dimensions if d.id == dimension_data['id']), None) if not dimension: return { 'message': 'Questionnaire has no dimension with id {}'.format( dimension_data['id']) }, 400 for question_data in dimension_data['questions']: question = next((q for q in dimension.questions if q.id == question_data['id']), None) # type: Question if not question: return { 'message': 'Questionnaire has no question with id {}'.format( question_data['id']) }, 400 question.add_question_result( question_data['value'], data_subject, verification_token=verification_token) all_questions.remove(question_data['id']) do_submission_hooks(dimension, dimension_data, data_subject) do_submission_hooks(questionnaire, data, data_subject) if all_questions: db.session.rollback() XApiPublisher().rollback() return { 'message': 'Missing questions.', 'missing': list(all_questions) }, 400 try: send_mail( data_subject.email, _("Please verify your survey submission"), construct_verification_email(questionnaire, verification_token)) except (SMTPRecipientsRefused, SMTPHeloError, SMTPSenderRefused, SMTPDataError, SMTPNotSupportedError) as e: db.session.rollback() XApiPublisher().rollback() return { 'message': 'Error while sending verification email.', 'errors': [str(e)] }, 500 db.session.commit() return { 'message': 'Submission successful. Please verify by email.' }, 200
def post(self, questionnaire_id: int = None): schema = SubmissionSchema(exclude=["data_subject"]) data, errors = schema.load(request.json) questionnaire = Questionnaire.query.get_or_404(questionnaire_id) if errors: return { 'message': 'Your request contained errors.', 'errors': errors }, 400 # survey lifecycle check if not questionnaire.published: abort(403, message="Survey was not published yet.") if not questionnaire.allow_embedded: abort(403) if not questionnaire.accepts_submissions: abort(403, message="Submissions are not accepted at this point.") all_questions = { q.id for d in questionnaire.dimensions for q in d.questions } responses = [] for dimension_data in data['dimensions']: dimension = next((d for d in questionnaire.dimensions if d.id == dimension_data['id']), None) if not dimension: return { 'message': 'Questionnaire has no dimension with id {}'.format( dimension_data['id']) }, 400 for question_data in dimension_data['questions']: question = next((q for q in dimension.questions if q.id == question_data['id']), None) # type: Question if not question: return { 'message': 'Questionnaire has no question with id {}'.format( question_data['id']) }, 400 responses.append( question.add_question_result(question_data['value'], current_user(), needs_verification=False)) all_questions.remove(question_data['id']) question.statistic.update() do_submission_hooks(dimension, dimension_data) do_submission_hooks(questionnaire, data) if all_questions: db.session.rollback() XApiPublisher().rollback() return { 'message': 'Missing questions.', 'missing': list(all_questions) }, 400 for response in responses: SIG_ANSWER_VERIFIED.send(response) db.session.commit() return {'message': 'Submission successful.'}, 200
def approve_pending_xapi_statements(sender: QuestionResponse): XApiPublisher().approve(sender.id)
def publish_logged_in_xapi_statement(sender: Party): statement = XApiLoggedInStatement(sender) XApiPublisher().enqueue([statement], g._config['XAPI_DEFAULT_ENDPOINT'])