def _get_case(session): session = XFormsSession.get(session.get_id) self.assertTrue(session.submission_id) instance = XFormInstance.get(session.submission_id) case_id = instance.xpath("form/case/@case_id") self.assertTrue(case_id) return CommCareCase.get(case_id)
def test_basic_form_playing(self): # load the app with open( os.path.join(os.path.dirname(__file__), "data", "demo_app.json")) as f: app_json = json.loads(f.read()) app = import_app(app_json, self.domain) # start form session session, responses = start_session(self.domain, self.contact, app, app.get_module(0), app.get_module(0).get_form(0)) [answer] = responses self.assertEqual("what is your name?", answer) # check state of model self.assertEqual(session.start_time, session.modified_time) self.assertEqual("http://www.commcarehq.org/tests/smsforms", session.form_xmlns) self.assertFalse(session.end_time) self.assertEqual(False, session.completed) self.assertEqual(self.domain, session.domain) self.assertEqual(self.contact.get_id, session.user_id) self.assertEqual(app.get_id, session.app_id) self.assertFalse(session.submission_id) # play through the form, checking answers q_and_a(self, "sms contact", "how old are you, sms contact?", self.domain) q_and_a(self, "29", "what is your gender? 1:male, 2:female", self.domain) q_and_a(self, "2", "thanks for submitting!", self.domain) # check the instance session = XFormsSession.get(session.get_id) self.assertTrue(session.submission_id) instance = XFormInstance.get(session.submission_id) self.assertEqual("sms contact", instance.xpath("form/name")) self.assertEqual("29", instance.xpath("form/age")) self.assertEqual("f", instance.xpath("form/gender")) self.assertEqual(self.domain, instance.domain)
def test_reverse_sync(self): properties = _arbitrary_session_properties() couch_session = XFormsSession(**properties) couch_session.save() sql_session = SQLXFormsSession.objects.get(couch_id=couch_session._id) for prop, value in properties.items(): self.assertEqual(getattr(sql_session, prop), value) # make sure we didn't do any excess saves self.assertTrue(XFormsSession.get_db().get_rev(couch_session._id).startswith('1-')) updated_properties = _arbitrary_session_properties() for prop, value in updated_properties.items(): setattr(sql_session, prop, value) sql_session.save() couch_session = XFormsSession.get(couch_session._id) for prop, value in updated_properties.items(): self.assertEqual(getattr(couch_session, prop), value) self.assertTrue(couch_session._rev.startswith('2-'))
def test_basic_form_playing(self): # load the app with open(os.path.join(os.path.dirname(__file__), "data", "demo_app.json")) as f: app_json = json.loads(f.read()) app = import_app(app_json, self.domain) # start form session session, responses = start_session(self.domain, self.contact, app, app.get_module(0), app.get_module(0).get_form(0)) [answer] = responses self.assertEqual("what is your name?", answer) # check state of model self.assertEqual(session.start_time, session.modified_time) self.assertEqual("http://www.commcarehq.org/tests/smsforms", session.form_xmlns) self.assertFalse(session.end_time) self.assertEqual(False, session.completed) self.assertEqual(self.domain, session.domain) self.assertEqual(self.contact.get_id, session.user_id) self.assertEqual(app.get_id, session.app_id) self.assertFalse(session.submission_id) # play through the form, checking answers q_and_a(self, "sms contact", "how old are you, sms contact?", self.domain) q_and_a(self, "29", "what is your gender? 1:male, 2:female", self.domain) q_and_a(self, "2", "thanks for submitting!", self.domain) # check the instance session = XFormsSession.get(session.get_id) self.assertTrue(session.submission_id) instance = XFormInstance.get(session.submission_id) self.assertEqual("sms contact", instance.xpath("form/name")) self.assertEqual("29", instance.xpath("form/age")) self.assertEqual("f", instance.xpath("form/gender")) self.assertEqual(self.domain, instance.domain)
def structured_sms_handler(verified_number, text): # Circular Import from corehq.apps.reminders.models import SurveyKeyword, FORM_TYPE_ALL_AT_ONCE text = text.strip() if text == "": return False for survey_keyword in SurveyKeyword.get_all(verified_number.domain): if survey_keyword.form_type == FORM_TYPE_ALL_AT_ONCE: if survey_keyword.delimiter is not None: args = text.split(survey_keyword.delimiter) else: args = text.split() keyword = args[0].strip().upper() if keyword != survey_keyword.keyword.upper(): continue try: error_occurred = False error_msg = "" form_complete = False # Close any open sessions close_open_sessions(verified_number.domain, verified_number.owner_id) # Start the session form = Form.get_form(survey_keyword.form_unique_id) app = form.get_app() module = form.get_module() if verified_number.owner_doc_type == "CommCareCase": case_id = verified_number.owner_id else: # TODO: Need a way to choose the case when it's a user that's playing the form case_id = None session, responses = start_session( verified_number.domain, verified_number.owner, app, module, form, case_id=case_id, yield_responses=True, ) assert len(responses) > 0, "There should be at least one response." current_question = responses[-1] form_complete = is_form_complete(current_question) if not form_complete: if survey_keyword.use_named_args: # Arguments in the sms are named xpath_answer = {} # Dictionary of {xpath : answer} for answer in args[1:]: answer = answer.strip() answer_upper = answer.upper() if survey_keyword.named_args_separator is not None: # A separator is used for naming arguments; for example, the "=" in "register name=joe age=25" answer_parts = answer.partition(survey_keyword.named_args_separator) if answer_parts[1] != survey_keyword.named_args_separator: error_occurred = True error_msg = "ERROR: Expected name and value to be joined by" + ( " '%s'" % survey_keyword.named_args_separator ) break else: arg_name = answer_parts[0].upper().strip() xpath = survey_keyword.named_args.get(arg_name, None) if xpath is not None: if xpath in xpath_answer: error_occurred = True error_msg = "ERROR: More than one answer found for" + (" '%s'" % arg_name) break xpath_answer[xpath] = answer_parts[2].strip() else: # Ignore unexpected named arguments pass else: # No separator is used for naming arguments; for example, "update a100 b34 c5" matches = 0 for k, v in survey_keyword.named_args.items(): if answer_upper.startswith(k): matches += 1 if matches > 1: error_occurred = True error_msg = "ERROR: More than one question matches" + (" '%s'" % answer) break if v in xpath_answer: error_occurred = True error_msg = "ERROR: More than one answer found for" + (" '%s'" % k) break xpath_answer[v] = answer[len(k) :].strip() if matches == 0: # Ignore unexpected named arguments pass if error_occurred: break # Go through each question in the form, answering only the questions that the sms has answers for while not form_complete and not error_occurred: if current_question.is_error: error_occurred = True error_msg = current_question.text_prompt or "ERROR: Internal server error" break xpath = current_question.event._dict["binding"] if xpath in xpath_answer: valid, answer, _error_msg = validate_answer(current_question.event, xpath_answer[xpath]) if not valid: error_occurred = True error_msg = "ERROR: " + _error_msg break responses = _get_responses( verified_number.domain, verified_number.owner_id, answer, yield_responses=True ) else: responses = _get_responses( verified_number.domain, verified_number.owner_id, "", yield_responses=True ) current_question = responses[-1] if is_form_complete(current_question): form_complete = True else: # Arguments in the sms are not named; pass each argument to each question in order for answer in args[1:]: if form_complete: # Form is complete, ignore remaining answers break if current_question.is_error: error_occurred = True error_msg = current_question.text_prompt or "ERROR: Internal server error" break valid, answer, _error_msg = validate_answer(current_question.event, answer.strip()) if not valid: error_occurred = True error_msg = "ERROR: " + _error_msg break responses = _get_responses( verified_number.domain, verified_number.owner_id, answer, yield_responses=True ) current_question = responses[-1] form_complete = is_form_complete(current_question) # If the form isn't finished yet but we're out of arguments, try to leave each remaining question blank and continue while not form_complete and not error_occurred: responses = _get_responses( verified_number.domain, verified_number.owner_id, "", yield_responses=True ) current_question = responses[-1] if current_question.is_error: error_occurred = True error_msg = current_question.text_prompt or "ERROR: Internal server error" if is_form_complete(current_question): form_complete = True except Exception: logging.exception( "Could not process structured sms for verified number %s, domain %s, keyword %s" % (verified_number._id, verified_number.domain, keyword) ) error_occurred = True error_msg = "ERROR: Internal server error" if error_occurred: send_sms_to_verified_number(verified_number, error_msg) if error_occurred or not form_complete: session = XFormsSession.get(session._id) session.end(False) session.save() return True return False
def handle_structured_sms(survey_keyword, survey_keyword_action, contact, verified_number, text, send_response=False, msg=None, case=None, text_args=None): domain = contact.domain contact_doc_type = contact.doc_type contact_id = contact._id if text_args is not None: args = text_args else: args = split_args(text, survey_keyword) args = args[1:] keyword = survey_keyword.keyword.upper() error_occurred = False error_msg = None session = None try: # Start the session app, module, form = get_form( survey_keyword_action.form_unique_id, include_app_module=True) if case: case_id = case._id elif contact_doc_type == "CommCareCase": case_id = contact_id else: case_id = None session, responses = start_session(domain, contact, app, module, form, case_id=case_id, yield_responses=True) session.workflow = WORKFLOW_KEYWORD session.save() assert len(responses) > 0, "There should be at least one response." first_question = responses[-1] if not is_form_complete(first_question): if survey_keyword_action.use_named_args: # Arguments in the sms are named xpath_answer = parse_structured_sms_named_args(args, survey_keyword_action, verified_number) _handle_structured_sms(domain, args, contact_id, session, first_question, verified_number, xpath_answer) else: # Arguments in the sms are not named; pass each argument to # each question in order _handle_structured_sms(domain, args, contact_id, session, first_question, verified_number) except StructuredSMSException as sse: error_occurred = True error_msg = "" if sse.xformsresponse and sse.xformsresponse.event: xpath_arg = None if survey_keyword_action.use_named_args: xpath_arg = \ {v: k for k, v in survey_keyword_action.named_args.items()} field_name = get_question_id(sse.xformsresponse, xpath_arg) error_msg = get_message(MSG_FIELD_DESCRIPTOR, verified_number, (field_name,)) error_msg = "%s%s" % (error_msg, sse.response_text) except Exception: logging.exception("Could not process structured sms for contact %s, " "domain %s, keyword %s" % (contact_id, domain, keyword)) error_occurred = True error_msg = get_message(MSG_TOUCHFORMS_ERROR, verified_number) if session is not None: session = XFormsSession.get(session._id) if session.is_open: session.end(False) session.save() metadata = MessageMetadata( workflow=WORKFLOW_KEYWORD, xforms_session_couch_id=session._id if session else None, ) if msg: add_msg_tags(msg, metadata) if error_occurred and verified_number is not None and send_response: send_sms_to_verified_number(verified_number, error_msg, metadata=metadata) return not error_occurred
def handle_structured_sms( survey_keyword, survey_keyword_action, contact, verified_number, text, send_response=False, msg=None ): domain = contact.domain contact_doc_type = contact.doc_type contact_id = contact._id text = text.strip() if survey_keyword.delimiter is not None: args = text.split(survey_keyword.delimiter) else: args = text.split() keyword = args[0].strip().upper() error_occurred = False error_msg = None session = None try: # Start the session form = Form.get_form(survey_keyword_action.form_unique_id) app = form.get_app() module = form.get_module() if contact_doc_type == "CommCareCase": case_id = contact_id else: # TODO: Need a way to choose the case when it's a user that's playing the form case_id = None session, responses = start_session(domain, contact, app, module, form, case_id=case_id, yield_responses=True) session.workflow = WORKFLOW_KEYWORD session.save() if msg is not None: msg.workflow = WORKFLOW_KEYWORD msg.xforms_session_couch_id = session._id msg.save() assert len(responses) > 0, "There should be at least one response." current_question = responses[-1] form_complete = is_form_complete(current_question) if not form_complete: if survey_keyword_action.use_named_args: # Arguments in the sms are named xpath_answer = {} # Dictionary of {xpath : answer} for answer in args[1:]: answer = answer.strip() answer_upper = answer.upper() if survey_keyword_action.named_args_separator is not None: # A separator is used for naming arguments; for example, the "=" in "register name=joe age=25" answer_parts = answer.partition(survey_keyword_action.named_args_separator) if answer_parts[1] != survey_keyword_action.named_args_separator: raise StructuredSMSException( response_text="ERROR: Expected name and value to be joined by '%(separator)s'" % {"separator": survey_keyword_action.named_args_separator} ) else: arg_name = answer_parts[0].upper().strip() xpath = survey_keyword_action.named_args.get(arg_name, None) if xpath is not None: if xpath in xpath_answer: raise StructuredSMSException( response_text="ERROR: More than one answer found for '%(arg_name)s'" % {"arg_name": arg_name} ) xpath_answer[xpath] = answer_parts[2].strip() else: # Ignore unexpected named arguments pass else: # No separator is used for naming arguments; for example, "update a100 b34 c5" matches = 0 for k, v in survey_keyword_action.named_args.items(): if answer_upper.startswith(k): matches += 1 if matches > 1: raise StructuredSMSException( response_text="ERROR: More than one question matches '%(answer)s'" % {"answer": answer} ) if v in xpath_answer: raise StructuredSMSException( response_text="ERROR: More than one answer found for '%(named_arg)s'" % {"named_arg": k} ) xpath_answer[v] = answer[len(k) :].strip() if matches == 0: # Ignore unexpected named arguments pass # Go through each question in the form, answering only the questions that the sms has answers for while not form_complete: if current_question.is_error: raise StructuredSMSException( response_text=(current_question.text_prompt or "ERROR: Internal server error") ) xpath = current_question.event._dict["binding"] if xpath in xpath_answer: valid, answer, _error_msg = validate_answer(current_question.event, xpath_answer[xpath]) if not valid: raise StructuredSMSException(response_text=_error_msg) responses = _get_responses( domain, contact_id, answer, yield_responses=True, session_id=session.session_id, update_timestamp=False, ) else: responses = _get_responses( domain, contact_id, "", yield_responses=True, session_id=session.session_id, update_timestamp=False, ) current_question = responses[-1] if is_form_complete(current_question): form_complete = True else: # Arguments in the sms are not named; pass each argument to each question in order for answer in args[1:]: if form_complete: # Form is complete, ignore remaining answers break if current_question.is_error: raise StructuredSMSException( response_text=(current_question.text_prompt or "ERROR: Internal server error") ) valid, answer, _error_msg = validate_answer(current_question.event, answer.strip()) if not valid: raise StructuredSMSException(response_text=_error_msg) responses = _get_responses( domain, contact_id, answer, yield_responses=True, session_id=session.session_id, update_timestamp=False, ) current_question = responses[-1] form_complete = is_form_complete(current_question) # If the form isn't finished yet but we're out of arguments, try to leave each remaining question blank and continue while not form_complete: responses = _get_responses( domain, contact_id, "", yield_responses=True, session_id=session.session_id, update_timestamp=False, ) current_question = responses[-1] if current_question.is_error: raise StructuredSMSException( response_text=(current_question.text_prompt or "ERROR: Internal server error") ) if is_form_complete(current_question): form_complete = True except StructuredSMSException as sse: error_occurred = True error_msg = sse.response_text except Exception: logging.exception( "Could not process structured sms for contact %s, domain %s, keyword %s" % (contact_id, domain, keyword) ) error_occurred = True error_msg = "ERROR: Internal server error" if session is not None: session = XFormsSession.get(session._id) if session.is_open: session.end(False) session.save() message_tags = { "workflow": WORKFLOW_KEYWORD, "xforms_session_couch_id": session._id if session is not None else None, } if msg is not None: msg.workflow = message_tags["workflow"] msg.xforms_session_couch_id = message_tags["xforms_session_couch_id"] msg.save() if error_occurred and verified_number is not None and send_response: send_sms_to_verified_number(verified_number, error_msg, **message_tags)
xpath_arg = None if survey_keyword_action.use_named_args: xpath_arg = \ {v: k for k, v in survey_keyword_action.named_args.items()} field_name = get_question_id(sse.xformsresponse, xpath_arg) error_msg = get_message(MSG_FIELD_DESCRIPTOR, verified_number, (field_name,)) error_msg = "%s%s" % (error_msg, sse.response_text) except Exception: notify_exception(None, message=("Could not process structured sms for" "contact %s, domain %s, keyword %s" % (contact_id, domain, keyword))) error_occurred = True error_msg = get_message(MSG_TOUCHFORMS_ERROR, verified_number) if session is not None: session = XFormsSession.get(session._id) if session.is_open: session.end(False) session.save() metadata = MessageMetadata( workflow=WORKFLOW_KEYWORD, xforms_session_couch_id=session._id if session else None, ) if msg: add_msg_tags(msg, metadata) if error_occurred and verified_number is not None and send_response: send_sms_to_verified_number(verified_number, error_msg, metadata=metadata)
def structured_sms_handler(verified_number, text): # Circular Import from corehq.apps.reminders.models import SurveyKeyword, FORM_TYPE_ALL_AT_ONCE text = text.strip() if text == "": return False for survey_keyword in SurveyKeyword.get_all(verified_number.domain): if survey_keyword.form_type == FORM_TYPE_ALL_AT_ONCE: if survey_keyword.delimiter is not None: args = text.split(survey_keyword.delimiter) else: args = text.split() keyword = args[0].strip().upper() if keyword != survey_keyword.keyword.upper(): continue try: error_occurred = False error_msg = "" form_complete = False # Close any open sessions close_open_sessions(verified_number.domain, verified_number.owner_id) # Start the session form = Form.get_form(survey_keyword.form_unique_id) app = form.get_app() module = form.get_module() if verified_number.owner_doc_type == "CommCareCase": case_id = verified_number.owner_id else: #TODO: Need a way to choose the case when it's a user that's playing the form case_id = None session, responses = start_session(verified_number.domain, verified_number.owner, app, module, form, case_id=case_id, yield_responses=True) assert len( responses) > 0, "There should be at least one response." current_question = responses[-1] form_complete = is_form_complete(current_question) if not form_complete: if survey_keyword.use_named_args: # Arguments in the sms are named xpath_answer = {} # Dictionary of {xpath : answer} for answer in args[1:]: answer = answer.strip() answer_upper = answer.upper() if survey_keyword.named_args_separator is not None: # A separator is used for naming arguments; for example, the "=" in "register name=joe age=25" answer_parts = answer.partition( survey_keyword.named_args_separator) if answer_parts[ 1] != survey_keyword.named_args_separator: error_occurred = True error_msg = "ERROR: Expected name and value to be joined by" + ( " '%s'" % survey_keyword.named_args_separator) break else: arg_name = answer_parts[0].upper().strip() xpath = survey_keyword.named_args.get( arg_name, None) if xpath is not None: if xpath in xpath_answer: error_occurred = True error_msg = "ERROR: More than one answer found for" + ( " '%s'" % arg_name) break xpath_answer[xpath] = answer_parts[ 2].strip() else: # Ignore unexpected named arguments pass else: # No separator is used for naming arguments; for example, "update a100 b34 c5" matches = 0 for k, v in survey_keyword.named_args.items(): if answer_upper.startswith(k): matches += 1 if matches > 1: error_occurred = True error_msg = "ERROR: More than one question matches" + ( " '%s'" % answer) break if v in xpath_answer: error_occurred = True error_msg = "ERROR: More than one answer found for" + ( " '%s'" % k) break xpath_answer[v] = answer[ len(k):].strip() if matches == 0: # Ignore unexpected named arguments pass if error_occurred: break # Go through each question in the form, answering only the questions that the sms has answers for while not form_complete and not error_occurred: if current_question.is_error: error_occurred = True error_msg = current_question.text_prompt or "ERROR: Internal server error" break xpath = current_question.event._dict["binding"] if xpath in xpath_answer: valid, answer, _error_msg = validate_answer( current_question.event, xpath_answer[xpath]) if not valid: error_occurred = True error_msg = "ERROR: " + _error_msg break responses = _get_responses( verified_number.domain, verified_number.owner_id, answer, yield_responses=True) else: responses = _get_responses( verified_number.domain, verified_number.owner_id, "", yield_responses=True) current_question = responses[-1] if is_form_complete(current_question): form_complete = True else: # Arguments in the sms are not named; pass each argument to each question in order for answer in args[1:]: if form_complete: # Form is complete, ignore remaining answers break if current_question.is_error: error_occurred = True error_msg = current_question.text_prompt or "ERROR: Internal server error" break valid, answer, _error_msg = validate_answer( current_question.event, answer.strip()) if not valid: error_occurred = True error_msg = "ERROR: " + _error_msg break responses = _get_responses( verified_number.domain, verified_number.owner_id, answer, yield_responses=True) current_question = responses[-1] form_complete = is_form_complete(current_question) # If the form isn't finished yet but we're out of arguments, try to leave each remaining question blank and continue while not form_complete and not error_occurred: responses = _get_responses( verified_number.domain, verified_number.owner_id, "", yield_responses=True) current_question = responses[-1] if current_question.is_error: error_occurred = True error_msg = current_question.text_prompt or "ERROR: Internal server error" if is_form_complete(current_question): form_complete = True except Exception: logging.exception( "Could not process structured sms for verified number %s, domain %s, keyword %s" % (verified_number._id, verified_number.domain, keyword)) error_occurred = True error_msg = "ERROR: Internal server error" if error_occurred: send_sms_to_verified_number(verified_number, error_msg) if error_occurred or not form_complete: session = XFormsSession.get(session._id) session.end(False) session.save() return True return False