def ivr_in(request): """ Handles tropo call requests """ if request.method == "POST": data = json.loads(request.body) phone_number = data["session"]["from"]["id"] # TODO: Implement tropo as an ivr backend. In the meantime, just log the call. if phone_number: cleaned_number = strip_plus(phone_number) v = VerifiedNumber.by_extensive_search(cleaned_number) else: v = None # Save the call entry msg = CallLog( phone_number=cleaned_number, direction=INCOMING, date=datetime.utcnow(), backend_api=SQLTropoBackend.get_api_id(), ) if v is not None: msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.save() t = Tropo() t.reject() return HttpResponse(t.RenderJson()) else: return HttpResponseBadRequest("Bad Request")
def incoming(phone_number, backend_api): cleaned_number = phone_number if len(cleaned_number) > 0 and cleaned_number[0] == "+": cleaned_number = cleaned_number[1:] # Try to look up the verified number entry v = VerifiedNumber.view("sms/verified_number_by_number", key=cleaned_number, include_docs=True ).one() # If none was found, try to match only the last digits of numbers in the database if v is None: v = VerifiedNumber.view("sms/verified_number_by_suffix", key=cleaned_number, include_docs=True ).one() # Save the call entry msg = CallLog( phone_number = cleaned_number, direction = INCOMING, date = datetime.utcnow(), backend_api = backend_api ) if v is not None: msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.save()
def ivr_in(request): """ Handles tropo call requests """ if request.method == "POST": data = json.loads(request.body) phone_number = data["session"]["from"]["id"] # TODO: Implement tropo as an ivr backend. In the meantime, just log the call. if phone_number: cleaned_number = strip_plus(phone_number) v = VerifiedNumber.by_extensive_search(cleaned_number) else: v = None # Save the call entry msg = CallLog( phone_number=cleaned_number, direction=INCOMING, date=datetime.utcnow(), backend_api=TropoBackend.get_api_id(), ) if v is not None: msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.save() t = Tropo() t.reject() return HttpResponse(t.RenderJson()) else: return HttpResponseBadRequest("Bad Request")
def testCallLogSync(self): self.deleteAllLogs() self.assertEqual(self.getCallLogCount(), 0) self.assertEqual(self.getCallCount(), 0) # Test Create calllog = CallLog() self.setRandomCallLogValues(calllog) calllog.save() sleep(1) self.assertEqual(self.getCallLogCount(), 1) self.assertEqual(self.getCallCount(), 1) call = Call.objects.get(couch_id=calllog._id) self.checkFieldValues(calllog, call, Call._migration_get_fields()) self.assertTrue(CallLog.get_db().get_rev(calllog._id).startswith('1-')) # Test Update self.setRandomCallLogValues(calllog) calllog.save() sleep(1) self.assertEqual(self.getCallLogCount(), 1) self.assertEqual(self.getCallCount(), 1) call = Call.objects.get(couch_id=calllog._id) self.checkFieldValues(calllog, call, Call._migration_get_fields()) self.assertTrue(CallLog.get_db().get_rev(calllog._id).startswith('2-'))
def initiate_outbound_call(recipient, form_unique_id, submit_partial_form, include_case_side_effects, max_question_retries, verified_number=None, unverified_number=None, case_id=None, case_for_case_submission=False, timestamp=None): """ Returns True if the call was queued successfully, or False if an error occurred. """ call_log_entry = None try: if not verified_number and not unverified_number: return False phone_number = (verified_number.phone_number if verified_number else unverified_number) call_log_entry = CallLog( couch_recipient_doc_type=recipient.doc_type, couch_recipient=recipient.get_id, phone_number="+%s" % str(phone_number), direction=OUTGOING, date=timestamp or datetime.utcnow(), domain=recipient.domain, form_unique_id=form_unique_id, submit_partial_form=submit_partial_form, include_case_side_effects=include_case_side_effects, max_question_retries=max_question_retries, current_question_retry_count=0, case_id=case_id, case_for_case_submission=case_for_case_submission, ) backend = get_ivr_backend(recipient, verified_number, unverified_number) if not backend: return False kwargs = backend.get_cleaned_outbound_params() module = __import__(backend.outbound_module, fromlist=["initiate_outbound_call"]) call_log_entry.backend_api = module.API_ID call_log_entry.save() return module.initiate_outbound_call(call_log_entry, **kwargs) except Exception: if call_log_entry: call_log_entry.error = True call_log_entry.error_message = "Internal Server Error" call_log_entry.save() raise
def log_call(phone_number, gateway_session_id, backend_module=None): cleaned_number = strip_plus(phone_number) v = VerifiedNumber.by_extensive_search(cleaned_number) call = CallLog( phone_number=cleaned_number, direction=INCOMING, date=datetime.utcnow(), backend_api=backend_module.API_ID if backend_module else None, gateway_session_id=gateway_session_id, ) if v: call.domain = v.domain call.couch_recipient_doc_type = v.owner_doc_type call.couch_recipient = v.owner_id call.save()
def log_call(phone_number, gateway_session_id, backend_module=None): cleaned_number = strip_plus(phone_number) v = VerifiedNumber.by_extensive_search(cleaned_number) call = CallLog( phone_number=cleaned_number, direction=INCOMING, date=datetime.utcnow(), backend_api=backend_module.API_ID if backend_module else None, gateway_session_id=gateway_session_id, ) if v: call.domain = v.domain call.couch_recipient_doc_type = v.owner_doc_type call.couch_recipient = v.owner_id call.save()
def ivr_in(request): """ Handles tropo call requests """ if request.method == "POST": data = json.loads(request.raw_post_data) phone_number = data["session"]["from"]["id"] #### # TODO: Implement tropo as an ivr backend. In the meantime, just log the call. cleaned_number = phone_number if cleaned_number is not None and len(cleaned_number) > 0 and cleaned_number[0] == "+": cleaned_number = cleaned_number[1:] # Try to look up the verified number entry v = VerifiedNumber.view("sms/verified_number_by_number", key=cleaned_number, include_docs=True ).one() # If none was found, try to match only the last digits of numbers in the database if v is None: v = VerifiedNumber.view("sms/verified_number_by_suffix", key=cleaned_number, include_docs=True ).one() # Save the call entry msg = CallLog( phone_number = cleaned_number, direction = INCOMING, date = datetime.utcnow(), backend_api = TropoBackend.get_api_id(), ) if v is not None: msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.save() #### t = Tropo() t.reject() return HttpResponse(t.RenderJson()) else: return HttpResponseBadRequest("Bad Request")
def initiate_outbound_call(recipient, form_unique_id, submit_partial_form, include_case_side_effects, max_question_retries, verified_number=None, unverified_number=None, case_id=None, case_for_case_submission=False, timestamp=None): """ Returns True if the call was queued successfully, or False if an error occurred. """ call_log_entry = None try: if not verified_number and not unverified_number: return False phone_number = (verified_number.phone_number if verified_number else unverified_number) call_log_entry = CallLog( couch_recipient_doc_type=recipient.doc_type, couch_recipient=recipient.get_id, phone_number="+%s" % str(phone_number), direction=OUTGOING, date=timestamp or datetime.utcnow(), domain=recipient.domain, form_unique_id=form_unique_id, submit_partial_form=submit_partial_form, include_case_side_effects=include_case_side_effects, max_question_retries=max_question_retries, current_question_retry_count=0, case_id=case_id, case_for_case_submission=case_for_case_submission, ) backend = get_ivr_backend(recipient, verified_number, unverified_number) if not backend: return False kwargs = backend.get_cleaned_outbound_params() module = __import__(backend.outbound_module, fromlist=["initiate_outbound_call"]) call_log_entry.backend_api = module.API_ID call_log_entry.save() return module.initiate_outbound_call(call_log_entry, **kwargs) except Exception: if call_log_entry: call_log_entry.error = True call_log_entry.error_message = "Internal Server Error" call_log_entry.save() raise
def ivr_in(request): """ Handles tropo call requests """ if request.method == "POST": data = json.loads(request.raw_post_data) phone_number = data["session"]["from"]["id"] #### # TODO: Implement tropo as an ivr backend. In the meantime, just log the call. cleaned_number = phone_number if cleaned_number is not None and len( cleaned_number) > 0 and cleaned_number[0] == "+": cleaned_number = cleaned_number[1:] # Try to look up the verified number entry v = VerifiedNumber.view("sms/verified_number_by_number", key=cleaned_number, include_docs=True).one() # If none was found, try to match only the last digits of numbers in the database if v is None: v = VerifiedNumber.view("sms/verified_number_by_suffix", key=cleaned_number, include_docs=True).one() # Save the call entry msg = CallLog(phone_number=cleaned_number, direction=INCOMING, date=datetime.utcnow(), backend_api=TROPO_BACKEND_API_ID) if v is not None: msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.save() #### t = Tropo() t.reject() return HttpResponse(t.RenderJson()) else: return HttpResponseBadRequest("Bad Request")
def initiate_outbound_call(verified_number, form_unique_id, submit_partial_form, include_case_side_effects, max_question_retries): call_log_entry = CallLog( couch_recipient_doc_type = verified_number.owner_doc_type, couch_recipient = verified_number.owner_id, phone_number = "+" + str(verified_number.phone_number), direction = OUTGOING, date = datetime.utcnow(), domain = verified_number.domain, form_unique_id = form_unique_id, submit_partial_form = submit_partial_form, include_case_side_effects = include_case_side_effects, max_question_retries = max_question_retries, current_question_retry_count = 0, ) backend = verified_number.ivr_backend kwargs = backend.get_cleaned_outbound_params() module = __import__(backend.outbound_module, fromlist=["initiate_outbound_call"]) call_log_entry.backend_api = module.API_ID call_log_entry.save() return module.initiate_outbound_call(call_log_entry, **kwargs)
def initiate_outbound_call(verified_number, form_unique_id, submit_partial_form, include_case_side_effects, max_question_retries): call_log_entry = CallLog( couch_recipient_doc_type = verified_number.owner_doc_type, couch_recipient = verified_number.owner_id, phone_number = "+" + str(verified_number.phone_number), direction = OUTGOING, date = datetime.utcnow(), domain = verified_number.domain, form_unique_id = form_unique_id, submit_partial_form = submit_partial_form, include_case_side_effects = include_case_side_effects, max_question_retries = max_question_retries, current_question_retry_count = 0, ) backend = verified_number.ivr_backend kwargs = backend.get_cleaned_outbound_params() module = __import__(backend.outbound_module, fromlist=["initiate_outbound_call"]) call_log_entry.backend_api = module.API_ID call_log_entry.save() return module.initiate_outbound_call(call_log_entry, **kwargs)
def test_ok(self): self.assertEqual(self.handler.get_reminder(self.case), None) # Spawn CaseReminder CaseReminderHandler.now = datetime(year=2011, month=12, day=31, hour=23, minute=0) self.case.set_case_property('start_sending', 'ok') self.case.save() reminder = self.handler.get_reminder(self.case) self.assertNotEqual(reminder, None) self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=1, hour=7, minute=0)) self.assertEqual(reminder.start_date, date(year=2012, month=1, day=1)) self.assertEqual(reminder.schedule_iteration_num, 1) self.assertEqual(reminder.current_event_sequence_num, 0) self.assertEqual(reminder.last_fired, None) ###################### # Day1, 10:00 reminder CaseReminderHandler.now = datetime(year=2012, month=1, day=1, hour=7, minute=0) CaseReminderHandler.fire_reminders() reminder = self.handler.get_reminder(self.case) self.assertNotEqual(reminder, None) self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=1, hour=8, minute=0)) self.assertEqual(reminder.schedule_iteration_num, 1) self.assertEqual(reminder.current_event_sequence_num, 1) self.assertEqual(reminder.last_fired, CaseReminderHandler.now) # Day1, 11:00 reminder CaseReminderHandler.now = datetime(year=2012, month=1, day=1, hour=8, minute=1) CaseReminderHandler.fire_reminders() reminder = self.handler.get_reminder(self.case) self.assertNotEqual(reminder, None) self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=1, hour=8, minute=15)) self.assertEqual(reminder.schedule_iteration_num, 1) self.assertEqual(reminder.current_event_sequence_num, 1) self.assertEqual(reminder.last_fired, CaseReminderHandler.now) event = ExpectedCallbackEventLog.view("sms/expected_callback_event", key=["test", json_format_datetime(datetime(year=2012, month=1, day=1, hour=8, minute=1)), self.user_id], include_docs=True).one() self.assertNotEqual(event, None) self.assertEqual(event.status, CALLBACK_PENDING) # Create a callback c = CallLog( couch_recipient_doc_type = "CommCareUser", couch_recipient = self.user_id, phone_number = "14445551234", direction = "I", date = datetime(year=2012, month=1, day=1, hour=8, minute=5) ) c.save() # Day1, 11:15 timeout (should move on to next event) CaseReminderHandler.now = datetime(year=2012, month=1, day=1, hour=8, minute=15) CaseReminderHandler.fire_reminders() reminder = self.handler.get_reminder(self.case) self.assertNotEqual(reminder, None) self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=1, hour=8, minute=45)) self.assertEqual(reminder.schedule_iteration_num, 1) self.assertEqual(reminder.current_event_sequence_num, 1) self.assertEqual(reminder.last_fired, CaseReminderHandler.now) # Day1, 11:45 timeout (should move on to next event) CaseReminderHandler.now = datetime(year=2012, month=1, day=1, hour=8, minute=45) CaseReminderHandler.fire_reminders() reminder = self.handler.get_reminder(self.case) self.assertNotEqual(reminder, None) self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=2, hour=7, minute=0)) self.assertEqual(reminder.schedule_iteration_num, 2) self.assertEqual(reminder.current_event_sequence_num, 0) self.assertEqual(reminder.last_fired, CaseReminderHandler.now) event = ExpectedCallbackEventLog.view("sms/expected_callback_event", key=["test", json_format_datetime(datetime(year=2012, month=1, day=1, hour=8, minute=1)), self.user_id], include_docs=True).one() self.assertNotEqual(event, None) self.assertEqual(event.status, CALLBACK_RECEIVED) ###################### # Day2, 10:00 reminder CaseReminderHandler.now = datetime(year=2012, month=1, day=2, hour=7, minute=0) CaseReminderHandler.fire_reminders() reminder = self.handler.get_reminder(self.case) self.assertNotEqual(reminder, None) self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=2, hour=8, minute=0)) self.assertEqual(reminder.schedule_iteration_num, 2) self.assertEqual(reminder.current_event_sequence_num, 1) self.assertEqual(reminder.last_fired, CaseReminderHandler.now) # Day2, 11:00 reminder CaseReminderHandler.now = datetime(year=2012, month=1, day=2, hour=8, minute=1) CaseReminderHandler.fire_reminders() reminder = self.handler.get_reminder(self.case) self.assertNotEqual(reminder, None) self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=2, hour=8, minute=15)) self.assertEqual(reminder.schedule_iteration_num, 2) self.assertEqual(reminder.current_event_sequence_num, 1) self.assertEqual(reminder.last_fired, CaseReminderHandler.now) event = ExpectedCallbackEventLog.view("sms/expected_callback_event", key=["test", json_format_datetime(datetime(year=2012, month=1, day=2, hour=8, minute=1)), self.user_id], include_docs=True).one() self.assertNotEqual(event, None) self.assertEqual(event.status, CALLBACK_PENDING) # Day2, 11:15 timeout (should move on to next timeout) CaseReminderHandler.now = datetime(year=2012, month=1, day=2, hour=8, minute=15) CaseReminderHandler.fire_reminders() reminder = self.handler.get_reminder(self.case) self.assertNotEqual(reminder, None) self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=2, hour=8, minute=45)) self.assertEqual(reminder.schedule_iteration_num, 2) self.assertEqual(reminder.current_event_sequence_num, 1) self.assertEqual(reminder.last_fired, CaseReminderHandler.now) event = ExpectedCallbackEventLog.view("sms/expected_callback_event", key=["test", json_format_datetime(datetime(year=2012, month=1, day=2, hour=8, minute=1)), self.user_id], include_docs=True).one() self.assertNotEqual(event, None) self.assertEqual(event.status, CALLBACK_PENDING) # Day2, 11:45 timeout (should move on to next day) CaseReminderHandler.now = datetime(year=2012, month=1, day=2, hour=8, minute=45) CaseReminderHandler.fire_reminders() reminder = self.handler.get_reminder(self.case) self.assertNotEqual(reminder, None) self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=3, hour=7, minute=0)) self.assertEqual(reminder.schedule_iteration_num, 3) self.assertEqual(reminder.current_event_sequence_num, 0) self.assertEqual(reminder.last_fired, CaseReminderHandler.now) event = ExpectedCallbackEventLog.view("sms/expected_callback_event", key=["test", json_format_datetime(datetime(year=2012, month=1, day=2, hour=8, minute=1)), self.user_id], include_docs=True).one() self.assertNotEqual(event, None) self.assertEqual(event.status, CALLBACK_MISSED) ###################### # Day3, 10:00 reminder CaseReminderHandler.now = datetime(year=2012, month=1, day=3, hour=7, minute=0) CaseReminderHandler.fire_reminders() reminder = self.handler.get_reminder(self.case) self.assertNotEqual(reminder, None) self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=3, hour=8, minute=0)) self.assertEqual(reminder.schedule_iteration_num, 3) self.assertEqual(reminder.current_event_sequence_num, 1) self.assertEqual(reminder.last_fired, CaseReminderHandler.now) # Day3, 11:00 reminder CaseReminderHandler.now = datetime(year=2012, month=1, day=3, hour=8, minute=1) CaseReminderHandler.fire_reminders() reminder = self.handler.get_reminder(self.case) self.assertNotEqual(reminder, None) self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=3, hour=8, minute=15)) self.assertEqual(reminder.schedule_iteration_num, 3) self.assertEqual(reminder.current_event_sequence_num, 1) self.assertEqual(reminder.last_fired, CaseReminderHandler.now) event = ExpectedCallbackEventLog.view("sms/expected_callback_event", key=["test", json_format_datetime(datetime(year=2012, month=1, day=3, hour=8, minute=1)), self.user_id], include_docs=True).one() self.assertNotEqual(event, None) self.assertEqual(event.status, CALLBACK_PENDING) # Day3, 11:15 timeout (should move on to next timeout) CaseReminderHandler.now = datetime(year=2012, month=1, day=3, hour=8, minute=15) CaseReminderHandler.fire_reminders() reminder = self.handler.get_reminder(self.case) self.assertNotEqual(reminder, None) self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=3, hour=8, minute=45)) self.assertEqual(reminder.schedule_iteration_num, 3) self.assertEqual(reminder.current_event_sequence_num, 1) self.assertEqual(reminder.last_fired, CaseReminderHandler.now) event = ExpectedCallbackEventLog.view("sms/expected_callback_event", key=["test", json_format_datetime(datetime(year=2012, month=1, day=3, hour=8, minute=1)), self.user_id], include_docs=True).one() self.assertNotEqual(event, None) self.assertEqual(event.status, CALLBACK_PENDING) # Create a callback (with phone_number missing country code) c = CallLog( couch_recipient_doc_type = "CommCareUser", couch_recipient = self.user_id, phone_number = "4445551234", direction = "I", date = datetime(year=2012, month=1, day=3, hour=8, minute=22) ) c.save() # Day3, 11:45 timeout (should deactivate the reminder) CaseReminderHandler.now = datetime(year=2012, month=1, day=3, hour=8, minute=45) CaseReminderHandler.fire_reminders() reminder = self.handler.get_reminder(self.case) self.assertNotEqual(reminder, None) self.assertEqual(reminder.schedule_iteration_num, 4) self.assertEqual(reminder.current_event_sequence_num, 0) self.assertEqual(reminder.last_fired, CaseReminderHandler.now) self.assertEqual(reminder.active, False) event = ExpectedCallbackEventLog.view("sms/expected_callback_event", key=["test", json_format_datetime(datetime(year=2012, month=1, day=3, hour=8, minute=1)), self.user_id], include_docs=True).one() self.assertNotEqual(event, None) self.assertEqual(event.status, CALLBACK_RECEIVED)
def incoming(phone_number, backend_module, gateway_session_id, ivr_event, input_data=None): # Look up the call if one already exists call_log_entry = CallLog.view("sms/call_by_session", startkey=[gateway_session_id, {}], endkey=[gateway_session_id], descending=True, include_docs=True, limit=1).one() answer_is_valid = False # This will be set to True if IVR validation passes error_occurred = False # This will be set to False if touchforms validation passes (i.e., no form constraints fail) if call_log_entry is not None and backend_module: if ivr_event == IVR_EVENT_NEW_CALL and call_log_entry.use_precached_first_response: return HttpResponse(call_log_entry.first_response) form = Form.get_form(call_log_entry.form_unique_id) app = form.get_app() module = form.get_module() recipient = call_log_entry.recipient if ivr_event == IVR_EVENT_NEW_CALL: case_id = call_log_entry.case_id case_for_case_submission = call_log_entry.case_for_case_submission session, responses = start_session(recipient.domain, recipient, app, module, form, case_id, yield_responses=True, session_type=XFORMS_SESSION_IVR, case_for_case_submission=case_for_case_submission) call_log_entry.xforms_session_id = session.session_id elif ivr_event == IVR_EVENT_INPUT: if call_log_entry.xforms_session_id is not None: current_q = current_question(call_log_entry.xforms_session_id) if validate_answer(input_data, current_q): answer_is_valid = True responses = _get_responses(recipient.domain, recipient._id, input_data, yield_responses=True, session_id=call_log_entry.xforms_session_id) else: call_log_entry.current_question_retry_count += 1 responses = [current_q] else: responses = [] else: responses = [] ivr_responses = [] hang_up = False for response in responses: if response.is_error: error_occurred = True call_log_entry.current_question_retry_count += 1 if response.text_prompt is None: ivr_responses = [] break else: ivr_responses.append(format_ivr_response(response.text_prompt, app)) elif response.event.type == "question": ivr_responses.append(format_ivr_response(response.event.caption, app)) elif response.event.type == "form-complete": hang_up = True if answer_is_valid and not error_occurred: call_log_entry.current_question_retry_count = 0 if call_log_entry.max_question_retries is not None and call_log_entry.current_question_retry_count > call_log_entry.max_question_retries: # Force hang-up ivr_responses = [] if len(ivr_responses) == 0: hang_up = True input_length = None if hang_up: if call_log_entry.xforms_session_id is not None: # Process disconnect session = get_session_by_session_id(call_log_entry.xforms_session_id) if session.end_time is None: if call_log_entry.submit_partial_form: submit_unfinished_form(session.session_id, call_log_entry.include_case_side_effects) else: session.end(completed=False) session.save() else: # Set input_length to let the ivr gateway know how many digits we need to collect. # Have to get the current question again, since the last XFormsResponse in responses # may not have an event if it was a response to a constraint error. if error_occurred: current_q = current_question(call_log_entry.xforms_session_id) else: current_q = responses[-1] input_length = get_input_length(current_q) call_log_entry.save() return HttpResponse(backend_module.get_http_response_string(gateway_session_id, ivr_responses, collect_input=(not hang_up), hang_up=hang_up, input_length=input_length)) # If not processed, just log the call if call_log_entry: # No need to log, already exists return HttpResponse("") cleaned_number = phone_number if cleaned_number is not None and len(cleaned_number) > 0 and cleaned_number[0] == "+": cleaned_number = cleaned_number[1:] # Try to look up the verified number entry v = VerifiedNumber.view("sms/verified_number_by_number", key=cleaned_number, include_docs=True ).one() # If none was found, try to match only the last digits of numbers in the database if v is None: v = VerifiedNumber.view("sms/verified_number_by_suffix", key=cleaned_number, include_docs=True ).one() # Save the call entry msg = CallLog( phone_number=cleaned_number, direction=INCOMING, date=datetime.utcnow(), backend_api=backend_module.API_ID if backend_module else None, gateway_session_id=gateway_session_id, ) if v is not None: msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.save() return HttpResponse("")
def initiate_outbound_call(recipient, form_unique_id, submit_partial_form, include_case_side_effects, max_question_retries, messaging_event_id, verified_number=None, unverified_number=None, case_id=None, case_for_case_submission=False, timestamp=None): """ Returns False if an error occurred and the call should be retried. Returns True if the call should not be retried (either because it was queued successfully or because an unrecoverable error occurred). """ call_log_entry = None logged_event = MessagingEvent.objects.get(pk=messaging_event_id) logged_subevent = logged_event.create_ivr_subevent(recipient, form_unique_id, case_id=case_id) if not verified_number and not unverified_number: log_error(MessagingEvent.ERROR_NO_PHONE_NUMBER, logged_subevent=logged_subevent) return True backend = get_ivr_backend(recipient, verified_number, unverified_number) if not backend: log_error(MessagingEvent.ERROR_NO_SUITABLE_GATEWAY, logged_subevent=logged_subevent) return True phone_number = (verified_number.phone_number if verified_number else unverified_number) call_log_entry = CallLog( couch_recipient_doc_type=recipient.doc_type, couch_recipient=recipient.get_id, phone_number='+%s' % str(phone_number), direction=OUTGOING, date=timestamp or datetime.utcnow(), domain=recipient.domain, form_unique_id=form_unique_id, submit_partial_form=submit_partial_form, include_case_side_effects=include_case_side_effects, max_question_retries=max_question_retries, current_question_retry_count=0, case_id=case_id, case_for_case_submission=case_for_case_submission, messaging_subevent_id=logged_subevent.pk, ) ivr_data, error = get_first_ivr_response_data(recipient, call_log_entry, logged_subevent) if error: return True if ivr_data: logged_subevent.xforms_session = ivr_data.session logged_subevent.save() try: kwargs = backend.get_cleaned_outbound_params() module = __import__(backend.outbound_module, fromlist=['initiate_outbound_call']) call_log_entry.backend_api = module.API_ID call_log_entry.save() result = module.initiate_outbound_call(call_log_entry, logged_subevent, ivr_data=ivr_data, **kwargs) logged_subevent.completed() return result except GatewayConnectionError: log_error(MessagingEvent.ERROR_GATEWAY_ERROR, call_log_entry, logged_subevent) raise except Exception: log_error(MessagingEvent.ERROR_INTERNAL_SERVER_ERROR, call_log_entry, logged_subevent) raise
def incoming(phone_number, backend_module, gateway_session_id, ivr_event, input_data=None): # Look up the call if one already exists call_log_entry = CallLog.view("sms/call_by_session", startkey=[gateway_session_id, {}], endkey=[gateway_session_id], descending=True, include_docs=True, limit=1).one() answer_is_valid = False # This will be set to True if IVR validation passes error_occurred = False # This will be set to False if touchforms validation passes (i.e., no form constraints fail) if call_log_entry is not None and backend_module: if ivr_event == IVR_EVENT_NEW_CALL and call_log_entry.use_precached_first_response: return HttpResponse(call_log_entry.first_response) form = Form.get_form(call_log_entry.form_unique_id) app = form.get_app() module = form.get_module() recipient = call_log_entry.recipient if ivr_event == IVR_EVENT_NEW_CALL: case_id = call_log_entry.case_id case_for_case_submission = call_log_entry.case_for_case_submission session, responses = start_session( recipient.domain, recipient, app, module, form, case_id, yield_responses=True, session_type=XFORMS_SESSION_IVR, case_for_case_submission=case_for_case_submission) call_log_entry.xforms_session_id = session.session_id elif ivr_event == IVR_EVENT_INPUT: if call_log_entry.xforms_session_id is not None: current_q = current_question(call_log_entry.xforms_session_id) if validate_answer(input_data, current_q): answer_is_valid = True responses = _get_responses( recipient.domain, recipient._id, input_data, yield_responses=True, session_id=call_log_entry.xforms_session_id) else: call_log_entry.current_question_retry_count += 1 responses = [current_q] else: responses = [] else: responses = [] ivr_responses = [] hang_up = False for response in responses: if response.is_error: error_occurred = True call_log_entry.current_question_retry_count += 1 if response.text_prompt is None: ivr_responses = [] break else: ivr_responses.append( format_ivr_response(response.text_prompt, app)) elif response.event.type == "question": ivr_responses.append( format_ivr_response(response.event.caption, app)) elif response.event.type == "form-complete": hang_up = True if answer_is_valid and not error_occurred: call_log_entry.current_question_retry_count = 0 if call_log_entry.max_question_retries is not None and call_log_entry.current_question_retry_count > call_log_entry.max_question_retries: # Force hang-up ivr_responses = [] if len(ivr_responses) == 0: hang_up = True input_length = None if hang_up: if call_log_entry.xforms_session_id is not None: # Process disconnect session = XFormsSession.latest_by_session_id( call_log_entry.xforms_session_id) if session.end_time is None: if call_log_entry.submit_partial_form: submit_unfinished_form( session.session_id, call_log_entry.include_case_side_effects) else: session.end(completed=False) session.save() else: # Set input_length to let the ivr gateway know how many digits we need to collect. # Have to get the current question again, since the last XFormsResponse in responses # may not have an event if it was a response to a constraint error. if error_occurred: current_q = current_question(call_log_entry.xforms_session_id) else: current_q = responses[-1] input_length = get_input_length(current_q) call_log_entry.save() return HttpResponse( backend_module.get_http_response_string( gateway_session_id, ivr_responses, collect_input=(not hang_up), hang_up=hang_up, input_length=input_length)) # If not processed, just log the call if call_log_entry: # No need to log, already exists return HttpResponse("") cleaned_number = phone_number if cleaned_number is not None and len( cleaned_number) > 0 and cleaned_number[0] == "+": cleaned_number = cleaned_number[1:] # Try to look up the verified number entry v = VerifiedNumber.view("sms/verified_number_by_number", key=cleaned_number, include_docs=True).one() # If none was found, try to match only the last digits of numbers in the database if v is None: v = VerifiedNumber.view("sms/verified_number_by_suffix", key=cleaned_number, include_docs=True).one() # Save the call entry msg = CallLog( phone_number=cleaned_number, direction=INCOMING, date=datetime.utcnow(), backend_api=backend_module.API_ID if backend_module else None, gateway_session_id=gateway_session_id, ) if v is not None: msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.save() return HttpResponse("")
def initiate_outbound_call(recipient, form_unique_id, submit_partial_form, include_case_side_effects, max_question_retries, messaging_event_id, verified_number=None, unverified_number=None, case_id=None, case_for_case_submission=False, timestamp=None): """ Returns False if an error occurred and the call should be retried. Returns True if the call should not be retried (either because it was queued successfully or because an unrecoverable error occurred). """ call_log_entry = None logged_event = MessagingEvent.objects.get(pk=messaging_event_id) logged_subevent = logged_event.create_ivr_subevent(recipient, form_unique_id, case_id=case_id) if not verified_number and not unverified_number: log_error(MessagingEvent.ERROR_NO_PHONE_NUMBER, logged_subevent=logged_subevent) return True backend = get_ivr_backend(recipient, verified_number, unverified_number) if not backend: log_error(MessagingEvent.ERROR_NO_SUITABLE_GATEWAY, logged_subevent=logged_subevent) return True phone_number = (verified_number.phone_number if verified_number else unverified_number) call_log_entry = CallLog( couch_recipient_doc_type=recipient.doc_type, couch_recipient=recipient.get_id, phone_number='+%s' % str(phone_number), direction=OUTGOING, date=timestamp or datetime.utcnow(), domain=recipient.domain, form_unique_id=form_unique_id, submit_partial_form=submit_partial_form, include_case_side_effects=include_case_side_effects, max_question_retries=max_question_retries, current_question_retry_count=0, case_id=case_id, case_for_case_submission=case_for_case_submission, messaging_subevent_id=logged_subevent.pk, ) ivr_data, error = get_first_ivr_response_data(recipient, call_log_entry, logged_subevent) if error: return True if ivr_data: logged_subevent.xforms_session = ivr_data.session logged_subevent.save() try: kwargs = backend.get_cleaned_outbound_params() module = __import__(backend.outbound_module, fromlist=['initiate_outbound_call']) call_log_entry.backend_api = module.API_ID call_log_entry.save() result = module.initiate_outbound_call(call_log_entry, logged_subevent, ivr_data=ivr_data, **kwargs) logged_subevent.completed() return result except GatewayConnectionError: log_error(MessagingEvent.ERROR_GATEWAY_ERROR, call_log_entry, logged_subevent) raise except Exception: log_error(MessagingEvent.ERROR_INTERNAL_SERVER_ERROR, call_log_entry, logged_subevent) raise