def send_messages_for_config(config, actually_send=True): query_engine = QueryEngine(template_vars=config.template_variables) params = get_parsed_params(config.template) sent_messages = [] for user in config.group.get_users(): phone_number = get_preferred_phone_number_for_recipient(user) if phone_number: query_context = QueryContext( user, config.group, template_vars=config.template_variables) message_context = query_engine.get_context(params, query_context) message = config.template.format(**message_context) if actually_send: metadata = MessageMetadata(workflow=WORKFLOW_PERFORMANCE) if isinstance(phone_number, PhoneNumber): send_sms_to_verified_number(phone_number, message, metadata=metadata) else: send_sms(config.domain, user, phone_number, message, metadata=metadata) sent_messages.append(MessageResult(user, message)) return sent_messages
def send_verification(domain, user, phone_number, logged_event): backend = SQLMobileBackend.load_default_by_phone_and_domain( SQLMobileBackend.SMS, phone_number, domain=domain ) reply_phone = backend.reply_to_phone_number subevent = logged_event.create_subevent_for_single_sms( user.doc_type, user.get_id ) if reply_phone: message = messages.get_message( messages.MSG_VERIFICATION_START_WITH_REPLY, context=(user.raw_username, reply_phone), domain=domain, language=user.get_language_code() ) else: message = messages.get_message( messages.MSG_VERIFICATION_START_WITHOUT_REPLY, context=(user.raw_username,), domain=domain, language=user.get_language_code() ) send_sms(domain, user, phone_number, message, metadata=MessageMetadata(messaging_subevent_id=subevent.pk)) subevent.completed()
def __test_global_backend_map(self): with patch( 'corehq.messaging.smsbackends.test.models.SQLTestSMSBackend.send', autospec=True ) as mock_send: self.assertTrue(send_sms(self.domain, None, '15551234567', 'Test for BACKEND2')) self.assertEqual(mock_send.call_count, 1) self.assertEqual(mock_send.call_args[0][0].pk, self.backend2.pk) with patch( 'corehq.messaging.smsbackends.test.models.SQLTestSMSBackend.send', autospec=True ) as mock_send: self.assertTrue(send_sms(self.domain, None, '9100000000', 'Test for BACKEND3')) self.assertEqual(mock_send.call_count, 1) self.assertEqual(mock_send.call_args[0][0].pk, self.backend3.pk) with patch( 'corehq.messaging.smsbackends.test.models.SQLTestSMSBackend.send', autospec=True ) as mock_send: self.assertTrue(send_sms(self.domain, None, '26500000000', 'Test for BACKEND4')) self.assertEqual(mock_send.call_count, 1) self.assertEqual(mock_send.call_args[0][0].pk, self.backend4.pk) with patch( 'corehq.messaging.smsbackends.test.models.SQLTestSMSBackend.send', autospec=True ) as mock_send: self.assertTrue(send_sms(self.domain, None, '25800000000', 'Test for BACKEND1')) self.assertEqual(mock_send.call_count, 1) self.assertEqual(mock_send.call_args[0][0].pk, self.backend1.pk)
def test_outgoing_with_error(self, process_sms_delay_mock, enqueue_directly_mock): send_sms(self.domain, None, '+999123', 'test outgoing') self.assertEqual(enqueue_directly_mock.call_count, 1) self.assertEqual(self.queued_sms_count, 1) self.assertEqual(self.reporting_sms_count, 0) queued_sms = self.get_queued_sms() self.assertEqual(queued_sms.processed, False) self.assertEqual(queued_sms.error, False) couch_id = queued_sms.couch_id self.assertIsNotNone(couch_id) with patch_error_send() as send_mock: process_sms(queued_sms.pk) self.assertEqual(send_mock.call_count, 1) self.assertEqual(self.queued_sms_count, 0) self.assertEqual(self.reporting_sms_count, 1) reporting_sms = self.get_reporting_sms() self.assertEqual(reporting_sms.processed, False) self.assertEqual(reporting_sms.error, True) self.assertEqual(reporting_sms.couch_id, couch_id) self.assertEqual(reporting_sms.backend_api, self.backend.get_api_id()) self.assertEqual(reporting_sms.backend_id, self.backend.couch_id) self.assertEqual(process_sms_delay_mock.call_count, 0) self.assertBillableDoesNotExist(couch_id)
def fire_sms_event(reminder, handler, recipients, verified_numbers, logged_event, workflow=None): current_event = reminder.current_event case = reminder.case template_params = get_message_template_params(case) uses_custom_content_handler, content_handler = get_custom_content_handler( handler, logged_event) if uses_custom_content_handler and not content_handler: return domain_obj = Domain.get_by_name(reminder.domain, strict=True) for recipient in recipients: logged_subevent = logged_event.create_subevent(handler, reminder, recipient) try: lang = recipient.get_language_code() except Exception: lang = None if content_handler: message = content_handler(reminder, handler, recipient) else: message = current_event.message.get( lang, current_event.message[handler.default_lang]) try: message = Message.render(message, **template_params) except Exception: logged_subevent.error( MessagingEvent.ERROR_CANNOT_RENDER_MESSAGE) continue verified_number, unverified_number = get_recipient_phone_number( reminder, recipient, verified_numbers) if message: metadata = MessageMetadata( workflow=workflow or get_workflow(handler), reminder_id=reminder._id, messaging_subevent_id=logged_subevent.pk, ) if verified_number is not None: send_sms_to_verified_number(verified_number, message, metadata) elif isinstance(recipient, CouchUser) and unverified_number: send_sms(reminder.domain, recipient, unverified_number, message, metadata) elif (isinstance(recipient, CommCareCase) and unverified_number and domain_obj.send_to_duplicated_case_numbers): send_sms(reminder.domain, recipient, unverified_number, message, metadata) else: logged_subevent.error(MessagingEvent.ERROR_NO_PHONE_NUMBER) continue logged_subevent.completed()
def send_sms_message(self, domain, recipient, phone_number, message, logged_subevent): if not message: return metadata = self.get_sms_message_metadata(logged_subevent) send_sms(domain, recipient, phone_number, message, metadata=metadata)
def run_indicator(domain, user_id, indicator_class): """ Runs the given indicator for the given user and sends the SMS if needed. :param domain: The domain the indicator is being run for :param user_id: The id of either an AWW or LS CommCareUser :param indicator_class: a subclass of AWWIndicator or LSIndicator """ user = CommCareUser.get_by_user_id(user_id, domain=domain) indicator = indicator_class(domain, user) # The user's phone number and preferred language is stored on the usercase usercase = user.get_usercase() messages = indicator.get_messages( language_code=usercase.get_language_code()) if not isinstance(messages, list): raise ValueError("Expected a list of messages") if messages: phone_number = get_one_way_number_for_recipient(usercase) if not phone_number: return for message in messages: send_sms(domain, usercase, phone_number, message)
def send_verification(domain, user, phone_number, logged_event): backend = SQLMobileBackend.load_default_by_phone_and_domain( SQLMobileBackend.SMS, phone_number, domain=domain) reply_phone = backend.reply_to_phone_number subevent = logged_event.create_subevent_for_single_sms( user.doc_type, user.get_id) if reply_phone: message = messages.get_message( messages.MSG_VERIFICATION_START_WITH_REPLY, context=(user.raw_username, reply_phone), domain=domain, language=user.get_language_code()) else: message = messages.get_message( messages.MSG_VERIFICATION_START_WITHOUT_REPLY, context=(user.raw_username, ), domain=domain, language=user.get_language_code()) send_sms(domain, user, phone_number, message, metadata=MessageMetadata(messaging_subevent_id=subevent.pk)) subevent.completed()
def run_indicator(domain, user_id, indicator_class, language_code=None): """ Runs the given indicator for the given user and sends the SMS if needed. :param domain: The domain the indicator is being run for :param user_id: The id of either an AWW or LS CommCareUser :param indicator_class: a subclass of AWWIndicator or LSIndicator """ user = CommCareUser.get_by_user_id(user_id, domain=domain) # The user's phone number and preferred language is stored on the usercase usercase = user.get_usercase() phone_number = get_one_way_number_for_recipient(usercase) if not phone_number or phone_number == '91': # If there is no phone number, don't bother calculating the indicator return if not language_code: language_code = usercase.get_language_code() indicator = indicator_class(domain, user) messages = indicator.get_messages(language_code=language_code) if not isinstance(messages, list): raise ValueError("Expected a list of messages") metadata = MessageMetadata(custom_metadata={ 'icds_indicator': indicator_class.slug, }) for message in messages: send_sms(domain, usercase, phone_number, message, metadata=metadata)
def test_outgoing_failure_indefinite_retry(self, process_sms_delay_mock, enqueue_directly_mock): timestamp = datetime(2016, 1, 1, 12, 0) RETRY_SMS_INDEFINITELY.set(self.domain, True, NAMESPACE_DOMAIN) with patch_datetime_api(timestamp): send_sms(self.domain, None, '+999123', 'test outgoing') self.assertEqual(enqueue_directly_mock.call_count, 1) self.assertEqual(self.queued_sms_count, 1) self.assertEqual(self.reporting_sms_count, 0) for i in range(settings.SMS_QUEUE_MAX_PROCESSING_ATTEMPTS): queued_sms = self.get_queued_sms() self.assertEqual(queued_sms.domain, self.domain) self.assertEqual(queued_sms.phone_number, '+999123') self.assertEqual(queued_sms.text, 'test outgoing') self.assertEqual(queued_sms.datetime_to_process, timestamp) self.assertEqual(queued_sms.processed, False) self.assertEqual(queued_sms.error, False) self.assertEqual(queued_sms.num_processing_attempts, i) with patch_failed_send() as send_mock, patch_datetime_tasks(timestamp + timedelta(seconds=1)): process_sms(queued_sms.pk) self.assertEqual(process_sms_delay_mock.call_count, 0) self.assertBillableDoesNotExist(queued_sms.couch_id) self.assertEqual(send_mock.call_count, 1) self.assertEqual(self.queued_sms_count, 1) self.assertEqual(self.reporting_sms_count, 0) timestamp += timedelta(minutes=settings.SMS_QUEUE_REPROCESS_INTERVAL) timestamp += ( timedelta(minutes=settings.SMS_QUEUE_REPROCESS_INDEFINITELY_INTERVAL) - timedelta(minutes=settings.SMS_QUEUE_REPROCESS_INTERVAL) ) queued_sms = self.get_queued_sms() self.assertEqual(queued_sms.domain, self.domain) self.assertEqual(queued_sms.phone_number, '+999123') self.assertEqual(queued_sms.text, 'test outgoing') self.assertEqual(queued_sms.datetime_to_process, timestamp) self.assertEqual(queued_sms.processed, False) self.assertEqual(queued_sms.error, False) self.assertEqual(queued_sms.num_processing_attempts, settings.SMS_QUEUE_MAX_PROCESSING_ATTEMPTS) with patch_successful_send() as send_mock, patch_datetime_tasks(timestamp + timedelta(seconds=1)): process_sms(queued_sms.pk) self.assertEqual(send_mock.call_count, 1) self.assertEqual(self.queued_sms_count, 0) self.assertEqual(self.reporting_sms_count, 1) reporting_sms = self.get_reporting_sms() self.assertEqual(reporting_sms.processed, True) self.assertEqual(reporting_sms.error, False) self.assertEqual(reporting_sms.num_processing_attempts, settings.SMS_QUEUE_MAX_PROCESSING_ATTEMPTS + 1) self.assertEqual(process_sms_delay_mock.call_count, 0) self.assertBillableExists(reporting_sms.couch_id)
def respond(self, message, **kwargs): if self.verified_contact: with localize(self.user.get_language_code()): send_sms_to_verified_number(self.verified_contact, str(message % kwargs)) else: send_sms(self.domain, None, self.msg.phone_number, str(message % kwargs))
def send_first_message(domain, recipient, phone_entry_or_number, session, responses, logged_subevent, workflow): # This try/except section is just here (temporarily) to support future refactors # If any of these notify, they should be replaced with a comment as to why the two are different # so that someone refactoring in the future will know that this or that param is necessary. try: if session.workflow != workflow: # see if we can eliminate the workflow arg notify_error('Exploratory: session.workflow != workflow', details={ 'session.workflow': session.workflow, 'workflow': workflow}) if session.connection_id != recipient.get_id: # see if we can eliminate the recipient arg notify_error('Exploratory: session.connection_id != recipient.get_id', details={ 'session.connection_id': session.connection_id, 'recipient.get_id': recipient.get_id, 'recipient': recipient }) if session.related_subevent != logged_subevent: # see if we can eliminate the logged_subevent arg notify_error('Exploratory: session.related_subevent != logged_subevent', details={ 'session.connection_id': session.connection_id, 'logged_subevent': logged_subevent}) except Exception: # The above running is not mission critical, so if it errors just leave a message in the log # for us to follow up on. # Absence of the message below and messages above ever notifying # will indicate that we can remove these args. notify_exception(None, "Error in section of code that's just supposed help inform future refactors") if toggles.ONE_PHONE_NUMBER_MULTIPLE_CONTACTS.enabled(domain): if not XFormsSessionSynchronization.claim_channel_for_session(session): send_first_message.apply_async( args=(domain, recipient, phone_entry_or_number, session, responses, logged_subevent, workflow), countdown=60 ) return metrics_counter('commcare.smsforms.session_started', 1, tags={'domain': domain, 'workflow': workflow}) if len(responses) > 0: message = format_message_list(responses) metadata = MessageMetadata( workflow=workflow, xforms_session_couch_id=session.couch_id, ) if isinstance(phone_entry_or_number, PhoneNumber): send_sms_to_verified_number( phone_entry_or_number, message, metadata, logged_subevent=logged_subevent ) else: send_sms( domain, recipient, phone_entry_or_number, message, metadata ) logged_subevent.completed()
def send_verification(domain, user, phone_number): module = MobileBackend.auto_load(phone_number, domain).backend_module reply_phone = getattr(module, 'receive_phone_number', lambda: None)() message = OUTGOING % { 'name': user.username.split('@')[0], 'replyto': ' to %s' % util.clean_phone_number(reply_phone) if reply_phone else '', } api.send_sms(domain, user._id, phone_number, message)
def send_verification(domain, user, phone_number): backend = MobileBackend.auto_load(phone_number, domain) reply_phone = backend.reply_to_phone_number message = OUTGOING % { "name": user.username.split("@")[0], "replyto": " to %s" % util.clean_phone_number(reply_phone) if reply_phone else "", } send_sms(domain, user, phone_number, message)
def test_sending_to_opted_out_number(self): self.assertEqual(PhoneNumber.objects.count(), 0) self.assertTrue(send_sms(self.domain, None, "999123456789", "hello")) incoming("999123456789", "stop", "GVI") self.assertEqual(PhoneNumber.objects.count(), 1) phone_number = PhoneNumber.objects.get(phone_number="999123456789") self.assertFalse(phone_number.send_sms) self.assertFalse(send_sms(self.domain, None, "999123456789", "hello"))
def send_verification(domain, user, phone_number): backend = MobileBackend.auto_load(phone_number, domain) reply_phone = backend.reply_to_phone_number with localize(user.language): message = _(OUTGOING) % { 'name': user.username.split('@')[0], 'replyto': ' to %s' % util.clean_phone_number(reply_phone) if reply_phone else '', } send_sms(domain, user, phone_number, message)
def send_sms_message(self, domain, recipient, phone_entry_or_number, message, logged_subevent): if not message: return metadata = self.get_sms_message_metadata(logged_subevent) if isinstance(phone_entry_or_number, PhoneNumber): send_sms_to_verified_number(phone_entry_or_number, message, metadata=metadata, logged_subevent=logged_subevent) else: send_sms(domain, recipient, phone_entry_or_number, message, metadata=metadata)
def fire_sms_event(reminder, handler, recipients, verified_numbers, logged_event, workflow=None): current_event = reminder.current_event case = reminder.case template_params = get_message_template_params(case) uses_custom_content_handler, content_handler = get_custom_content_handler(handler, logged_event) if uses_custom_content_handler and not content_handler: return domain_obj = Domain.get_by_name(reminder.domain, strict=True) for recipient in recipients: logged_subevent = logged_event.create_subevent(handler, reminder, recipient) try: lang = recipient.get_language_code() except Exception: lang = None if content_handler: message = content_handler(reminder, handler, recipient) else: message = current_event.message.get(lang, current_event.message[handler.default_lang]) try: message = Message.render(message, **template_params) except Exception: logged_subevent.error(MessagingEvent.ERROR_CANNOT_RENDER_MESSAGE) continue verified_number, unverified_number = get_recipient_phone_number( reminder, recipient, verified_numbers) if message: metadata = MessageMetadata( workflow=workflow or get_workflow(handler), reminder_id=reminder._id, messaging_subevent_id=logged_subevent.pk, ) if verified_number is not None: send_sms_to_verified_number(verified_number, message, metadata, logged_subevent=logged_subevent) elif isinstance(recipient, CouchUser) and unverified_number: send_sms(reminder.domain, recipient, unverified_number, message, metadata) elif (is_commcarecase(recipient) and unverified_number and domain_obj.send_to_duplicated_case_numbers): send_sms(reminder.domain, recipient, unverified_number, message, metadata) else: logged_subevent.error(MessagingEvent.ERROR_NO_PHONE_NUMBER) continue logged_subevent.completed()
def _test_outbound_backend(self, backend, msg_text, mock_send): self.domain_obj.default_sms_backend_id = backend._id self.domain_obj.save() send_sms(self.domain_obj.name, None, self.test_phone_number, msg_text) sms = SMS.objects.get( domain=self.domain_obj.name, direction='O', text=msg_text ) self.assertTrue(mock_send.called) msg_arg = mock_send.call_args[0][0] self.assertEqual(msg_arg.date, sms.date)
def test_outgoing_failure_recovery(self, process_sms_delay_mock, enqueue_directly_mock): timestamp = datetime(2016, 1, 1, 12, 0) with patch_datetime_api(timestamp): send_sms(self.domain, None, '+999123', 'test outgoing') self.assertEqual(enqueue_directly_mock.call_count, 1) self.assertEqual(self.queued_sms_count, 1) self.assertEqual(self.reporting_sms_count, 0) queued_sms = self.get_queued_sms() self.assertEqual(queued_sms.datetime_to_process, timestamp) self.assertEqual(queued_sms.processed, False) self.assertEqual(queued_sms.error, False) self.assertEqual(queued_sms.num_processing_attempts, 0) with patch_failed_send() as send_mock, patch_datetime_tasks( timestamp + timedelta(seconds=1)): process_sms(queued_sms.pk) self.assertEqual(process_sms_delay_mock.call_count, 0) self.assertBillableDoesNotExist(queued_sms.couch_id) self.assertEqual(send_mock.call_count, 1) self.assertEqual(self.queued_sms_count, 1) self.assertEqual(self.reporting_sms_count, 0) timestamp += timedelta(minutes=settings.SMS_QUEUE_REPROCESS_INTERVAL) queued_sms = self.get_queued_sms() self.assertEqual(queued_sms.datetime_to_process, timestamp) self.assertEqual(queued_sms.processed, False) self.assertEqual(queued_sms.error, False) self.assertEqual(queued_sms.num_processing_attempts, 1) with patch_successful_send() as send_mock, patch_datetime_tasks( timestamp + timedelta(seconds=1)): process_sms(queued_sms.pk) self.assertEqual(send_mock.call_count, 1) self.assertEqual(self.queued_sms_count, 0) self.assertEqual(self.reporting_sms_count, 1) reporting_sms = self.get_reporting_sms() self.assertEqual(reporting_sms.processed, True) self.assertEqual(reporting_sms.error, False) self.assertEqual(reporting_sms.num_processing_attempts, 2) self.assertEqual(process_sms_delay_mock.call_count, 0) self.assertBillableExists(reporting_sms.couch_id)
def _test_outbound_backend(self, backend, msg_text): from corehq.apps.sms.tests import BackendInvocationDoc self.domain_obj.default_sms_backend_id = backend._id self.domain_obj.save() send_sms(self.domain_obj.name, None, self.test_phone_number, msg_text) sms = SMS.objects.get( domain=self.domain_obj.name, direction='O', text=msg_text ) invoke_doc_id = '%s-%s' % (backend.__class__.__name__, json_format_datetime(sms.date)) invoke_doc = BackendInvocationDoc.get(invoke_doc_id) self.assertIsNotNone(invoke_doc)
def _test_outbound_backend(self, backend, msg_text, mock_send): SQLMobileBackendMapping.set_default_domain_backend(self.domain_obj.name, backend) send_sms(self.domain_obj.name, None, self.test_phone_number, msg_text) sms = SMS.objects.get( domain=self.domain_obj.name, direction='O', text=msg_text ) self.assertTrue(mock_send.called) msg_arg = mock_send.call_args[0][0] self.assertEqual(msg_arg.date, sms.date) self.assertEqual(sms.backend_api, backend.hq_api_id) self.assertEqual(sms.backend_id, backend.couch_id)
def api_send_sms(request, domain): """ An API to send SMS. Expected post parameters: phone_number - the phone number to send to contact_id - the _id of a contact to send to (overrides phone_number) text - the text of the message backend_id - the name of the MobileBackend to use while sending """ if request.method == "POST": phone_number = request.POST.get("phone_number", None) contact_id = request.POST.get("contact_id", None) text = request.POST.get("text", None) backend_id = request.POST.get("backend_id", None) chat = request.POST.get("chat", None) if (phone_number is None and contact_id is None) or (text is None): return HttpResponseBadRequest("Not enough arguments.") vn = None if contact_id is not None: try: contact = get_contact(contact_id) assert contact is not None assert contact.domain == domain except Exception: return HttpResponseBadRequest("Contact not found.") try: vn = contact.get_verified_number() assert vn is not None phone_number = vn.phone_number except Exception: return HttpResponseBadRequest("Contact has no phone number.") try: chat_workflow = string_to_boolean(chat) except Exception: chat_workflow = False if chat_workflow: chat_user_id = request.couch_user._id else: chat_user_id = None metadata = MessageMetadata( chat_user_id=chat_user_id ) if backend_id is not None: success = send_sms_with_backend_name(domain, phone_number, text, backend_id, metadata) elif vn is not None: success = send_sms_to_verified_number(vn, text, metadata) else: success = send_sms(domain, None, phone_number, text, metadata) if success: return HttpResponse("OK") else: return HttpResponse("ERROR") else: return HttpResponseBadRequest("POST Expected.")
def send_first_message(self, domain, recipient, phone_entry_or_number, session, responses, logged_subevent, workflow): if len(responses) > 0: message = format_message_list(responses) metadata = MessageMetadata( workflow=workflow, xforms_session_couch_id=session.couch_id, ) if isinstance(phone_entry_or_number, PhoneNumber): send_sms_to_verified_number(phone_entry_or_number, message, metadata, logged_subevent=logged_subevent) else: send_sms(domain, recipient, phone_entry_or_number, message, metadata)
def test_outgoing_failure(self, process_sms_delay_mock, enqueue_directly_mock): timestamp = datetime(2016, 1, 1, 12, 0) with patch_datetime_api(timestamp): send_sms(self.domain, None, '+999123', 'test outgoing') self.assertEqual(enqueue_directly_mock.call_count, 1) self.assertEqual(self.queued_sms_count, 1) self.assertEqual(self.reporting_sms_count, 0) for i in range(settings.SMS_QUEUE_MAX_PROCESSING_ATTEMPTS): queued_sms = self.get_queued_sms() self.assertEqual(queued_sms.domain, self.domain) self.assertEqual(queued_sms.phone_number, '+999123') self.assertEqual(queued_sms.text, 'test outgoing') self.assertEqual(queued_sms.datetime_to_process, timestamp) self.assertEqual(queued_sms.processed, False) self.assertEqual(queued_sms.error, False) self.assertEqual(queued_sms.num_processing_attempts, i) with patch_failed_send() as send_mock, patch_datetime_tasks( timestamp + timedelta(seconds=1)): process_sms(queued_sms.pk) self.assertEqual(process_sms_delay_mock.call_count, 0) self.assertBillableDoesNotExist(queued_sms.couch_id) self.assertEqual(send_mock.call_count, 1) if i < (settings.SMS_QUEUE_MAX_PROCESSING_ATTEMPTS - 1): self.assertEqual(self.queued_sms_count, 1) self.assertEqual(self.reporting_sms_count, 0) timestamp += timedelta( minutes=settings.SMS_QUEUE_REPROCESS_INTERVAL) else: self.assertEqual(self.queued_sms_count, 0) self.assertEqual(self.reporting_sms_count, 1) reporting_sms = self.get_reporting_sms() self.assertEqual(reporting_sms.domain, self.domain) self.assertEqual(reporting_sms.phone_number, '+999123') self.assertEqual(reporting_sms.text, 'test outgoing') self.assertEqual(reporting_sms.processed, False) self.assertEqual(reporting_sms.error, True) self.assertEqual(reporting_sms.num_processing_attempts, settings.SMS_QUEUE_MAX_PROCESSING_ATTEMPTS) self.assertBillableDoesNotExist(reporting_sms.couch_id)
def send_verification(domain, user, phone_number, logged_event): backend = MobileBackend.auto_load(phone_number, domain) reply_phone = backend.reply_to_phone_number subevent = logged_event.create_subevent_for_single_sms( user.doc_type, user.get_id ) with localize(user.language): message = _(OUTGOING) % { 'name': user.username.split('@')[0], 'replyto': ' to %s' % util.clean_phone_number(reply_phone) if reply_phone else '', } send_sms(domain, user, phone_number, message, metadata=MessageMetadata(messaging_subevent_id=subevent.pk)) subevent.completed()
def test_outgoing_failure_recovery(self, process_sms_delay_mock, enqueue_directly_mock): timestamp = datetime(2016, 1, 1, 12, 0) with patch_datetime_api(timestamp): send_sms(self.domain, None, '+999123', 'test outgoing') self.assertEqual(enqueue_directly_mock.call_count, 1) self.assertEqual(self.queued_sms_count, 1) self.assertEqual(self.reporting_sms_count, 0) queued_sms = self.get_queued_sms() self.assertEqual(queued_sms.datetime_to_process, timestamp) self.assertEqual(queued_sms.processed, False) self.assertEqual(queued_sms.error, False) self.assertEqual(queued_sms.num_processing_attempts, 0) with patch_failed_send() as send_mock, patch_datetime_tasks(timestamp + timedelta(seconds=1)): process_sms(queued_sms.pk) self.assertEqual(process_sms_delay_mock.call_count, 0) self.assertBillableDoesNotExist(queued_sms.couch_id) self.assertEqual(send_mock.call_count, 1) self.assertEqual(self.queued_sms_count, 1) self.assertEqual(self.reporting_sms_count, 0) timestamp += timedelta(minutes=settings.SMS_QUEUE_REPROCESS_INTERVAL) queued_sms = self.get_queued_sms() self.assertEqual(queued_sms.datetime_to_process, timestamp) self.assertEqual(queued_sms.processed, False) self.assertEqual(queued_sms.error, False) self.assertEqual(queued_sms.num_processing_attempts, 1) with patch_successful_send() as send_mock, patch_datetime_tasks(timestamp + timedelta(seconds=1)): process_sms(queued_sms.pk) self.assertEqual(send_mock.call_count, 1) self.assertEqual(self.queued_sms_count, 0) self.assertEqual(self.reporting_sms_count, 1) reporting_sms = self.get_reporting_sms() self.assertEqual(reporting_sms.processed, True) self.assertEqual(reporting_sms.error, False) self.assertEqual(reporting_sms.num_processing_attempts, 2) self.assertEqual(process_sms_delay_mock.call_count, 0) self.assertBillableExists(reporting_sms.couch_id)
def fire_sms_event(reminder, handler, recipients, verified_numbers): current_event = reminder.current_event if handler.method in [METHOD_SMS, METHOD_SMS_CALLBACK]: for recipient in recipients: try: lang = recipient.get_language_code() except Exception: lang = None message = current_event.message.get(lang, current_event.message[handler.default_lang]) try: message = Message.render(message, case=reminder.case.case_properties()) except Exception: if len(recipients) == 1: raise_error(reminder, ERROR_RENDERING_MESSAGE % lang) return False else: raise_warning() # ERROR_RENDERING_MESSAGE continue verified_number = verified_numbers[recipient.get_id] if verified_number is not None: result = send_sms_to_verified_number(verified_number, message) if not result: raise_warning() # Could not send SMS elif isinstance(recipient, CouchUser): # If there is no verified number, but the recipient is a CouchUser, still try to send it try: phone_number = recipient.phone_number except Exception: phone_number = None if phone_number is None: result = False if len(recipients) == 1: raise_error(reminder, ERROR_NO_OTHER_NUMBERS) else: raise_warning() # ERROR_NO_OTHER_NUMBERS else: result = send_sms(reminder.domain, recipient.get_id, phone_number, message) if not result: raise_warning() # Could not send SMS else: if len(recipients) == 1: raise_error(reminder, ERROR_NO_VERIFIED_NUMBER) else: raise_warning() # ERROR_NO_VERIFIED_NUMBER result = False if len(recipients) == 1: return result # For multiple recipients, always move to the next event return True elif handler.method in [METHOD_TEST, METHOD_SMS_CALLBACK_TEST]: # Used for automated tests return True
def send_messages_for_config(config, actually_send=True): query_engine = QueryEngine(template_vars=config.template_variables) params = get_parsed_params(config.template) sent_messages = [] for user in config.group.get_users(): phone_number = get_preferred_phone_number_for_recipient(user) if phone_number: query_context = QueryContext(user, config.group, template_vars=config.template_variables) message_context = query_engine.get_context(params, query_context) message = config.template.format(**message_context) if actually_send: metadata = MessageMetadata(workflow=WORKFLOW_PERFORMANCE) if isinstance(phone_number, PhoneNumber): send_sms_to_verified_number(phone_number, message, metadata=metadata) else: send_sms(config.domain, user, phone_number, message, metadata=metadata) sent_messages.append(MessageResult(user, message)) return sent_messages
def __test_domain_default(self): # Test overriding with domain-level backend SQLMobileBackendMapping.set_default_domain_backend(self.domain, self.backend5) with patch( 'corehq.messaging.smsbackends.test.models.SQLTestSMSBackend.send', autospec=True ) as mock_send: self.assertTrue(send_sms(self.domain, None, '15551234567', 'Test for BACKEND5')) self.assertEqual(mock_send.call_count, 1) self.assertEqual(mock_send.call_args[0][0].pk, self.backend5.pk)
def send_sms_for_schedule_instance(schedule_instance, recipient, phone_number, message): if not message: return metadata = MessageMetadata( custom_metadata=get_sms_custom_metadata(schedule_instance), ) if schedule_instance.memoized_schedule.is_test: send_sms_with_backend_name(schedule_instance.domain, phone_number, message, 'TEST', metadata=metadata) else: send_sms(schedule_instance.domain, recipient, phone_number, message, metadata=metadata)
def __test_shared_backend(self): # Test use of backend that another domain owns but has granted access SQLMobileBackendMapping.set_default_domain_backend(self.domain, self.backend6) with patch( 'corehq.messaging.smsbackends.test.models.SQLTestSMSBackend.send', autospec=True ) as mock_send: self.assertTrue(send_sms(self.domain, None, '25800000000', 'Test for BACKEND6')) self.assertEqual(mock_send.call_count, 1) self.assertEqual(mock_send.call_args[0][0].pk, self.backend6.pk) # Test trying to use a backend that another domain owns but has not granted access SQLMobileBackendMapping.set_default_domain_backend(self.domain, self.backend7) with patch( 'corehq.messaging.smsbackends.test.models.SQLTestSMSBackend.send', autospec=True ) as mock_send: self.assertFalse(send_sms(self.domain, None, '25800000000', 'Test Unauthorized')) self.assertEqual(mock_send.call_count, 0)
def process_verification(phone_number, text, backend_id=None): v = VerifiedNumber.by_phone(phone_number, True) if not v: return if not verification_response_ok(text): return if backend_id: backend = MobileBackend.load(backend_id) else: backend = MobileBackend.auto_load(phone_number, v.domain) # i don't know how to dynamically instantiate this object, which may be any number of doc types... #owner = CommCareMobileContactMixin.get(v.owner_id) assert v.owner_doc_type == 'CommCareUser' owner = CommCareUser.get(v.owner_id) owner.save_verified_number(v.domain, phone_number, True, backend._id) api.send_sms(v.domain, owner._id, phone_number, CONFIRM)
def send_verification(domain, user, phone_number, logged_event): backend = MobileBackend.auto_load(phone_number, domain) reply_phone = backend.reply_to_phone_number subevent = logged_event.create_subevent_for_single_sms( user.doc_type, user.get_id) with localize(user.language): message = _(OUTGOING) % { 'name': user.username.split('@')[0], 'replyto': ' to %s' % util.clean_phone_number(reply_phone) if reply_phone else '', } send_sms(domain, user, phone_number, message, metadata=MessageMetadata(messaging_subevent_id=subevent.pk)) subevent.completed()
def send_first_message(self, domain, recipient, phone_entry_or_number, session, responses, logged_subevent, workflow): if len(responses) > 0: message = format_message_list(responses) metadata = MessageMetadata( workflow=workflow, xforms_session_couch_id=session.couch_id, ) if isinstance(phone_entry_or_number, PhoneNumber): send_sms_to_verified_number( phone_entry_or_number, message, metadata, logged_subevent=logged_subevent ) else: send_sms( domain, recipient, phone_entry_or_number, message, metadata )
def fire_sms_survey_event(reminder, handler, recipients, verified_numbers, logged_event): if reminder.callback_try_count > 0: # Handle timeouts if handler.submit_partial_forms and ( reminder.callback_try_count == len( reminder.current_event.callback_timeout_intervals)): # Submit partial form completions for session_id in reminder.xforms_session_ids: submit_unfinished_form(session_id, handler.include_case_side_effects) else: # Resend current question for session_id in reminder.xforms_session_ids: session = get_session_by_session_id(session_id) if session.end_time is None: vn = VerifiedNumber.view("sms/verified_number_by_owner_id", key=session.connection_id, include_docs=True).first() if vn is not None: metadata = MessageMetadata( workflow=get_workflow(handler), reminder_id=reminder._id, xforms_session_couch_id=session._id, ) resp = current_question(session_id) send_sms_to_verified_number(vn, resp.event.text_prompt, metadata) else: reminder.xforms_session_ids = [] domain_obj = Domain.get_by_name(reminder.domain, strict=True) # Get the app, module, and form try: form_unique_id = reminder.current_event.form_unique_id form = Form.get_form(form_unique_id) app = form.get_app() module = form.get_module() except Exception: logged_event.error(MessagingEvent.ERROR_CANNOT_FIND_FORM) return # Start a touchforms session for each recipient for recipient in recipients: logged_subevent = logged_event.create_subevent( handler, reminder, recipient) verified_number, unverified_number = get_recipient_phone_number( reminder, recipient, verified_numbers) no_verified_number = verified_number is None cant_use_unverified_number = ( unverified_number is None or not domain_obj.send_to_duplicated_case_numbers or form_requires_input(form)) if no_verified_number and cant_use_unverified_number: logged_subevent.error( MessagingEvent.ERROR_NO_TWO_WAY_PHONE_NUMBER) continue key = "start-sms-survey-for-contact-%s" % recipient.get_id with CriticalSection([key], timeout=60): # Get the case to submit the form against, if any if (isinstance(recipient, CommCareCase) and not handler.force_surveys_to_use_triggered_case): case_id = recipient.get_id else: case_id = reminder.case_id if form.requires_case() and not case_id: logged_subevent.error(MessagingEvent.ERROR_NO_CASE_GIVEN) continue # Close all currently open sessions SQLXFormsSession.close_all_open_sms_sessions( reminder.domain, recipient.get_id) # Start the new session try: session, responses = start_session( reminder.domain, recipient, app, module, form, case_id, case_for_case_submission=handler. force_surveys_to_use_triggered_case) except TouchformsError as e: human_readable_message = e.response_data.get( 'human_readable_message', None) logged_subevent.error( MessagingEvent.ERROR_TOUCHFORMS_ERROR, additional_error_text=human_readable_message) if touchforms_error_is_config_error(e): # Don't reraise the exception because this means there are configuration # issues with the form that need to be fixed continue else: # Reraise the exception so that the framework retries it again later raise except Exception as e: logged_subevent.error( MessagingEvent.ERROR_TOUCHFORMS_ERROR) # Reraise the exception so that the framework retries it again later raise session.survey_incentive = handler.survey_incentive session.workflow = get_workflow(handler) session.reminder_id = reminder._id session.save() reminder.xforms_session_ids.append(session.session_id) logged_subevent.xforms_session = session logged_subevent.save() # Send out first message if len(responses) > 0: message = format_message_list(responses) metadata = MessageMetadata( workflow=get_workflow(handler), reminder_id=reminder._id, xforms_session_couch_id=session._id, ) if verified_number: send_sms_to_verified_number(verified_number, message, metadata) else: send_sms(reminder.domain, recipient, unverified_number, message, metadata) logged_subevent.completed()
def send_to_recipients(request, domain): recipients = request.POST.get('recipients') message = request.POST.get('message') if not recipients: messages.error(request, _("You didn't specify any recipients")) elif not message: messages.error(request, _("You can't send an empty message")) else: recipients = [x.strip() for x in recipients.split(',') if x.strip()] phone_numbers = [] # formats: GroupName (group), "Username", +15555555555 group_names = [] usernames = [] phone_numbers = [] unknown_usernames = [] GROUP = "[group]" send_to_all_checked = False for recipient in recipients: if recipient == "[send to all]": send_to_all_checked = True phone_users = CouchUser.view("users/phone_users_by_domain", startkey=[domain], endkey=[domain, {}], include_docs=True ) for user in phone_users: usernames.append(user.username) group_names = [] break elif (not send_to_all_checked) and recipient.endswith(GROUP): name = recipient[:-len(GROUP)].strip() group_names.append(name) elif re.match(r'^\+\d+', recipient): # here we expect it to have a plus sign def wrap_user_by_type(u): return getattr(user_models, u['doc']['doc_type']).wrap(u['doc']) phone_users = CouchUser.view("users/by_default_phone", # search both with and w/o the plus keys=[recipient, recipient[1:]], include_docs=True, wrapper=wrap_user_by_type).all() phone_users = filter(lambda u: u.is_member_of(domain), phone_users) if len(phone_users) > 0: phone_numbers.append((phone_users[0], recipient)) else: phone_numbers.append((None, recipient)) elif (not send_to_all_checked) and re.match(r'[\w\.]+', recipient): usernames.append(recipient) else: unknown_usernames.append(recipient) login_ids = dict([(r['key'], r['id']) for r in get_db().view("users/by_username", keys=usernames).all()]) for username in usernames: if username not in login_ids: unknown_usernames.append(username) login_ids = login_ids.values() users = [] empty_groups = [] if len(group_names) > 0: users.extend(CouchUser.view('users/by_group', keys=[[domain, gn] for gn in group_names], include_docs=True).all()) if len(users) == 0: empty_groups = group_names users.extend(CouchUser.view('_all_docs', keys=login_ids, include_docs=True).all()) users = [user for user in users if user.is_active and not user.is_deleted()] phone_numbers.extend([(user, user.phone_number) for user in users]) failed_numbers = [] no_numbers = [] sent = [] for user, number in phone_numbers: if not number: no_numbers.append(user.raw_username) elif send_sms(domain, user.user_id if user else "", number, message): sent.append("%s" % (user.raw_username if user else number)) else: failed_numbers.append("%s (%s)" % ( number, user.raw_username if user else "<no username>" )) if empty_groups or failed_numbers or unknown_usernames or no_numbers: if empty_groups: messages.error(request, _("The following groups don't exist: ") + (', '.join(empty_groups))) if no_numbers: messages.error(request, _("The following users don't have phone numbers: ") + (', '.join(no_numbers))) if failed_numbers: messages.error(request, _("Couldn't send to the following number(s): ") + (', '.join(failed_numbers))) if unknown_usernames: messages.error(request, _("Couldn't find the following user(s): ") + (', '.join(unknown_usernames))) if sent: messages.success(request, _("Successfully sent: ") + (', '.join(sent))) else: messages.info(request, _("No messages were sent.")) else: messages.success(request, _("Message sent")) return HttpResponseRedirect( request.META.get('HTTP_REFERER') or reverse(compose_message, args=[domain]) )
def fire_sms_survey_event(reminder, handler, recipients, verified_numbers): if reminder.callback_try_count > 0: # Handle timeouts if handler.submit_partial_forms and (reminder.callback_try_count == len(reminder.current_event.callback_timeout_intervals)): # Submit partial form completions for session_id in reminder.xforms_session_ids: submit_unfinished_form(session_id, handler.include_case_side_effects) else: # Resend current question for session_id in reminder.xforms_session_ids: session = XFormsSession.view("smsforms/sessions_by_touchforms_id", startkey=[session_id], endkey=[session_id, {}], include_docs=True).one() if session.end_time is None: vn = VerifiedNumber.view("sms/verified_number_by_owner_id", key=session.connection_id, include_docs=True).first() if vn is not None: metadata = MessageMetadata( workflow=get_workflow(handler), reminder_id=reminder._id, xforms_session_couch_id=session._id, ) resp = current_question(session_id) send_sms_to_verified_number(vn, resp.event.text_prompt, metadata) return True else: reminder.xforms_session_ids = [] # Get the app, module, and form try: form_unique_id = reminder.current_event.form_unique_id form = Form.get_form(form_unique_id) app = form.get_app() module = form.get_module() except Exception as e: raise_error(reminder, ERROR_FORM) return False # Start a touchforms session for each recipient for recipient in recipients: verified_number, unverified_number = get_recipient_phone_number( reminder, recipient, verified_numbers) domain_obj = Domain.get_by_name(reminder.domain, strict=True) no_verified_number = verified_number is None cant_use_unverified_number = (unverified_number is None or not domain_obj.send_to_duplicated_case_numbers or form_requires_input(form)) if no_verified_number and cant_use_unverified_number: if len(recipients) == 1: raise_error(reminder, ERROR_NO_VERIFIED_NUMBER) return False else: continue # Close all currently open sessions XFormsSession.close_all_open_sms_sessions(reminder.domain, recipient.get_id) # Start the new session if isinstance(recipient, CommCareCase) and not handler.force_surveys_to_use_triggered_case: case_id = recipient.get_id else: case_id = reminder.case_id session, responses = start_session(reminder.domain, recipient, app, module, form, case_id, case_for_case_submission=handler.force_surveys_to_use_triggered_case) session.survey_incentive = handler.survey_incentive session.workflow = get_workflow(handler) session.reminder_id = reminder._id session.save() reminder.xforms_session_ids.append(session.session_id) # Send out first message if len(responses) > 0: message = format_message_list(responses) metadata = MessageMetadata( workflow=get_workflow(handler), reminder_id=reminder._id, xforms_session_couch_id=session._id, ) if verified_number: result = send_sms_to_verified_number(verified_number, message, metadata) else: result = send_sms(reminder.domain, recipient, unverified_number, message, metadata) if len(recipients) == 1: return result return True
def fire_sms_event(reminder, handler, recipients, verified_numbers, workflow=None): metadata = MessageMetadata( workflow=workflow or get_workflow(handler), reminder_id=reminder._id, ) current_event = reminder.current_event if handler.method in [METHOD_SMS, METHOD_SMS_CALLBACK]: template_params = {} case = reminder.case if case is not None: template_params["case"] = case.case_properties() for recipient in recipients: try: lang = recipient.get_language_code() except Exception: lang = None if handler.custom_content_handler is not None: if handler.custom_content_handler in settings.ALLOWED_CUSTOM_CONTENT_HANDLERS: try: content_handler = to_function( settings.ALLOWED_CUSTOM_CONTENT_HANDLERS[ handler.custom_content_handler]) except Exception: raise_error(reminder, ERROR_FINDING_CUSTOM_CONTENT_HANDLER) return False message = content_handler(reminder, handler, recipient) # If the content handler returns None or empty string, # don't send anything if not message: return True else: raise_error(reminder, ERROR_INVALID_CUSTOM_CONTENT_HANDLER) return False else: message = current_event.message.get( lang, current_event.message[handler.default_lang]) try: message = Message.render(message, **template_params) except Exception: if len(recipients) == 1: raise_error(reminder, ERROR_RENDERING_MESSAGE % lang) return False else: raise_warning() # ERROR_RENDERING_MESSAGE continue verified_number, unverified_number = get_recipient_phone_number( reminder, recipient, verified_numbers) domain_obj = Domain.get_by_name(reminder.domain, strict=True) if verified_number is not None: result = send_sms_to_verified_number(verified_number, message, metadata) elif isinstance(recipient, CouchUser) and unverified_number: result = send_sms(reminder.domain, recipient, unverified_number, message, metadata) elif (isinstance(recipient, CommCareCase) and unverified_number and domain_obj.send_to_duplicated_case_numbers): result = send_sms(reminder.domain, recipient, unverified_number, message, metadata) else: if len(recipients) == 1: raise_error(reminder, ERROR_NO_VERIFIED_NUMBER) result = False if len(recipients) == 1: return result # For multiple recipients, always move to the next event return True
def fire_sms_event(reminder, handler, recipients, verified_numbers, workflow=None): message_tags = { "workflow" : workflow or get_workflow(handler), "reminder_id" : reminder._id, } current_event = reminder.current_event if handler.method in [METHOD_SMS, METHOD_SMS_CALLBACK]: template_params = {} case = reminder.case if case is not None: template_params["case"] = case.case_properties() for recipient in recipients: try: lang = recipient.get_language_code() except Exception: lang = None if handler.custom_content_handler is not None: if handler.custom_content_handler in settings.ALLOWED_CUSTOM_CONTENT_HANDLERS: try: content_handler = to_function(settings.ALLOWED_CUSTOM_CONTENT_HANDLERS[handler.custom_content_handler]) except Exception: raise_error(reminder, ERROR_FINDING_CUSTOM_CONTENT_HANDLER) return False message = content_handler(reminder, handler, recipient) # If the content handler returns None or empty string, # don't send anything if not message: return True else: raise_error(reminder, ERROR_INVALID_CUSTOM_CONTENT_HANDLER) return False else: message = current_event.message.get(lang, current_event.message[handler.default_lang]) try: message = Message.render(message, **template_params) except Exception: if len(recipients) == 1: raise_error(reminder, ERROR_RENDERING_MESSAGE % lang) return False else: raise_warning() # ERROR_RENDERING_MESSAGE continue verified_number = verified_numbers[recipient.get_id] if verified_number is not None: result = send_sms_to_verified_number(verified_number, message, **message_tags) if not result: raise_warning() # Could not send SMS elif isinstance(recipient, CouchUser): # If there is no verified number, but the recipient is a CouchUser, still try to send it try: phone_number = recipient.phone_number except Exception: phone_number = None if phone_number is None: result = False if len(recipients) == 1: raise_error(reminder, ERROR_NO_OTHER_NUMBERS) else: raise_warning() # ERROR_NO_OTHER_NUMBERS else: result = send_sms(reminder.domain, recipient, phone_number, message, **message_tags) if not result: raise_warning() # Could not send SMS else: if len(recipients) == 1: raise_error(reminder, ERROR_NO_VERIFIED_NUMBER) else: raise_warning() # ERROR_NO_VERIFIED_NUMBER result = False if len(recipients) == 1: return result # For multiple recipients, always move to the next event return True elif handler.method in [METHOD_TEST, METHOD_SMS_CALLBACK_TEST]: # Used for automated tests return True
def fire_sms_event(reminder, handler, recipients, verified_numbers, workflow=None): metadata = MessageMetadata( workflow=workflow or get_workflow(handler), reminder_id=reminder._id, ) current_event = reminder.current_event template_params = {} case = reminder.case if case is not None: template_params["case"] = case.case_properties() for recipient in recipients: try: lang = recipient.get_language_code() except Exception: lang = None if handler.custom_content_handler is not None: if handler.custom_content_handler in settings.ALLOWED_CUSTOM_CONTENT_HANDLERS: try: content_handler = to_function(settings.ALLOWED_CUSTOM_CONTENT_HANDLERS[handler.custom_content_handler]) except Exception: raise_error(reminder, ERROR_FINDING_CUSTOM_CONTENT_HANDLER) return False message = content_handler(reminder, handler, recipient) # If the content handler returns None or empty string, # don't send anything if not message: return True else: raise_error(reminder, ERROR_INVALID_CUSTOM_CONTENT_HANDLER) return False else: message = current_event.message.get(lang, current_event.message[handler.default_lang]) try: message = Message.render(message, **template_params) except Exception: if len(recipients) == 1: raise_error(reminder, ERROR_RENDERING_MESSAGE % lang) return False else: raise_warning() # ERROR_RENDERING_MESSAGE continue verified_number, unverified_number = get_recipient_phone_number( reminder, recipient, verified_numbers) domain_obj = Domain.get_by_name(reminder.domain, strict=True) if verified_number is not None: result = send_sms_to_verified_number(verified_number, message, metadata) elif isinstance(recipient, CouchUser) and unverified_number: result = send_sms(reminder.domain, recipient, unverified_number, message, metadata) elif (isinstance(recipient, CommCareCase) and unverified_number and domain_obj.send_to_duplicated_case_numbers): result = send_sms(reminder.domain, recipient, unverified_number, message, metadata) else: if len(recipients) == 1: raise_error(reminder, ERROR_NO_VERIFIED_NUMBER) result = False if len(recipients) == 1: return result # For multiple recipients, always move to the next event return True
def fire_sms_survey_event(reminder, handler, recipients, verified_numbers): if reminder.callback_try_count > 0: # Handle timeouts if handler.submit_partial_forms and ( reminder.callback_try_count == len( reminder.current_event.callback_timeout_intervals)): # Submit partial form completions for session_id in reminder.xforms_session_ids: submit_unfinished_form(session_id, handler.include_case_side_effects) else: # Resend current question for session_id in reminder.xforms_session_ids: session = XFormsSession.view( "smsforms/sessions_by_touchforms_id", startkey=[session_id], endkey=[session_id, {}], include_docs=True).one() if session.end_time is None: vn = VerifiedNumber.view("sms/verified_number_by_owner_id", key=session.connection_id, include_docs=True).first() if vn is not None: metadata = MessageMetadata( workflow=get_workflow(handler), reminder_id=reminder._id, xforms_session_couch_id=session._id, ) resp = current_question(session_id) send_sms_to_verified_number(vn, resp.event.text_prompt, metadata) return True else: reminder.xforms_session_ids = [] # Get the app, module, and form try: form_unique_id = reminder.current_event.form_unique_id form = Form.get_form(form_unique_id) app = form.get_app() module = form.get_module() except Exception as e: raise_error(reminder, ERROR_FORM) return False # Start a touchforms session for each recipient for recipient in recipients: verified_number, unverified_number = get_recipient_phone_number( reminder, recipient, verified_numbers) domain_obj = Domain.get_by_name(reminder.domain, strict=True) no_verified_number = verified_number is None cant_use_unverified_number = ( unverified_number is None or not domain_obj.send_to_duplicated_case_numbers or form_requires_input(form)) if no_verified_number and cant_use_unverified_number: if len(recipients) == 1: raise_error(reminder, ERROR_NO_VERIFIED_NUMBER) return False else: continue # Close all currently open sessions XFormsSession.close_all_open_sms_sessions(reminder.domain, recipient.get_id) # Start the new session if isinstance( recipient, CommCareCase ) and not handler.force_surveys_to_use_triggered_case: case_id = recipient.get_id else: case_id = reminder.case_id session, responses = start_session( reminder.domain, recipient, app, module, form, case_id, case_for_case_submission=handler. force_surveys_to_use_triggered_case) session.survey_incentive = handler.survey_incentive session.workflow = get_workflow(handler) session.reminder_id = reminder._id session.save() reminder.xforms_session_ids.append(session.session_id) # Send out first message if len(responses) > 0: message = format_message_list(responses) metadata = MessageMetadata( workflow=get_workflow(handler), reminder_id=reminder._id, xforms_session_couch_id=session._id, ) if verified_number: result = send_sms_to_verified_number( verified_number, message, metadata) else: result = send_sms(reminder.domain, recipient, unverified_number, message, metadata) if len(recipients) == 1: return result return True
def test_outgoing_failure_indefinite_retry(self, process_sms_delay_mock, enqueue_directly_mock): timestamp = datetime(2016, 1, 1, 12, 0) update_toggle_cache(RETRY_SMS_INDEFINITELY.slug, self.domain, True, NAMESPACE_DOMAIN) with patch_datetime_api(timestamp): send_sms(self.domain, None, '+999123', 'test outgoing') self.assertEqual(enqueue_directly_mock.call_count, 1) self.assertEqual(self.queued_sms_count, 1) self.assertEqual(self.reporting_sms_count, 0) for i in range(settings.SMS_QUEUE_MAX_PROCESSING_ATTEMPTS): queued_sms = self.get_queued_sms() self.assertEqual(queued_sms.domain, self.domain) self.assertEqual(queued_sms.phone_number, '+999123') self.assertEqual(queued_sms.text, 'test outgoing') self.assertEqual(queued_sms.datetime_to_process, timestamp) self.assertEqual(queued_sms.processed, False) self.assertEqual(queued_sms.error, False) self.assertEqual(queued_sms.num_processing_attempts, i) with patch_failed_send() as send_mock, patch_datetime_tasks( timestamp + timedelta(seconds=1)): process_sms(queued_sms.pk) self.assertEqual(process_sms_delay_mock.call_count, 0) self.assertBillableDoesNotExist(queued_sms.couch_id) self.assertEqual(send_mock.call_count, 1) self.assertEqual(self.queued_sms_count, 1) self.assertEqual(self.reporting_sms_count, 0) timestamp += timedelta( minutes=settings.SMS_QUEUE_REPROCESS_INTERVAL) timestamp += (timedelta( minutes=settings.SMS_QUEUE_REPROCESS_INDEFINITELY_INTERVAL) - timedelta(minutes=settings.SMS_QUEUE_REPROCESS_INTERVAL)) queued_sms = self.get_queued_sms() self.assertEqual(queued_sms.domain, self.domain) self.assertEqual(queued_sms.phone_number, '+999123') self.assertEqual(queued_sms.text, 'test outgoing') self.assertEqual(queued_sms.datetime_to_process, timestamp) self.assertEqual(queued_sms.processed, False) self.assertEqual(queued_sms.error, False) self.assertEqual(queued_sms.num_processing_attempts, settings.SMS_QUEUE_MAX_PROCESSING_ATTEMPTS) with patch_successful_send() as send_mock, patch_datetime_tasks( timestamp + timedelta(seconds=1)): process_sms(queued_sms.pk) self.assertEqual(send_mock.call_count, 1) self.assertEqual(self.queued_sms_count, 0) self.assertEqual(self.reporting_sms_count, 1) reporting_sms = self.get_reporting_sms() self.assertEqual(reporting_sms.processed, True) self.assertEqual(reporting_sms.error, False) self.assertEqual(reporting_sms.num_processing_attempts, settings.SMS_QUEUE_MAX_PROCESSING_ATTEMPTS + 1) self.assertEqual(process_sms_delay_mock.call_count, 0) self.assertBillableExists(reporting_sms.couch_id)
def fire(self, reminder): """ Sends the message associated with the given CaseReminder's current event. reminder The CaseReminder which to fire. return True on success, False on failure """ # Get the proper recipient recipient = reminder.recipient # Retrieve the VerifiedNumber entry for the recipient try: verified_number = recipient.get_verified_number() except Exception: verified_number = None # Get the language of the recipient try: lang = recipient.get_language_code() except Exception: lang = None if reminder.method == "survey": # Close all currently open sessions sessions = XFormsSession.view("smsforms/open_sessions_by_connection", key=[reminder.domain, recipient.get_id], include_docs=True).all() for session in sessions: session.end(False) session.save() # Start the new session try: form_unique_id = reminder.current_event.form_unique_id form = Form.get_form(form_unique_id) app = form.get_app() module = form.get_module() except Exception as e: print e print "ERROR: Could not load survey form for handler " + reminder.handler_id + ", event " + str(reminder.current_event_sequence_num) return False session, responses = start_session(reminder.domain, recipient, app, module, form, reminder.case_id) # Send out first message if len(responses) > 0: message = format_message_list(responses) if verified_number is not None: return send_sms_to_verified_number(verified_number, message) else: return True else: # If it is a callback reminder and the callback has been received, skip sending the next timeout message if (reminder.method == "callback" or reminder.method == "callback_test") and len(reminder.current_event.callback_timeout_intervals) > 0 and (reminder.callback_try_count > 0): if CallLog.inbound_call_exists(recipient.doc_type, recipient._id, reminder.last_fired): reminder.callback_received = True return True elif len(reminder.current_event.callback_timeout_intervals) == reminder.callback_try_count: # On the last callback timeout, instead of sending the SMS again, log the missed callback event = EventLog( domain = reminder.domain, date = self.get_now(), event_type = MISSED_EXPECTED_CALLBACK ) if verified_number is not None: event.couch_recipient_doc_type = verified_number.owner_doc_type event.couch_recipient = verified_number.owner_id event.save() return True reminder.last_fired = self.get_now() message = reminder.current_event.message.get(lang, reminder.current_event.message[self.default_lang]) message = Message.render(message, case=reminder.case.case_properties()) if reminder.method == "sms" or reminder.method == "callback": if verified_number is not None: return send_sms_to_verified_number(verified_number, message) elif self.recipient == RECIPIENT_USER: # If there is no verified number, but the recipient is a CommCareUser, still try to send it try: phone_number = reminder.user.phone_number except Exception: # If the user has no phone number, we cannot send any SMS return False return send_sms(reminder.domain, reminder.user_id, phone_number, message) else: return False elif reminder.method == "test" or reminder.method == "callback_test": print(message) return True
def fire_sms_survey_event(reminder, handler, recipients, verified_numbers, logged_event): if reminder.callback_try_count > 0: # Handle timeouts if handler.submit_partial_forms and ( reminder.callback_try_count == len(reminder.current_event.callback_timeout_intervals) ): # Submit partial form completions for session_id in reminder.xforms_session_ids: submit_unfinished_form(session_id, handler.include_case_side_effects) else: # Resend current question for session_id in reminder.xforms_session_ids: session = get_session_by_session_id(session_id) if session.end_time is None: vn = VerifiedNumber.view( "sms/verified_number_by_owner_id", key=session.connection_id, include_docs=True ).first() if vn is not None: metadata = MessageMetadata( workflow=get_workflow(handler), reminder_id=reminder._id, xforms_session_couch_id=session._id, ) resp = current_question(session_id) send_sms_to_verified_number(vn, resp.event.text_prompt, metadata) else: reminder.xforms_session_ids = [] domain_obj = Domain.get_by_name(reminder.domain, strict=True) # Get the app, module, and form try: form_unique_id = reminder.current_event.form_unique_id form = Form.get_form(form_unique_id) app = form.get_app() module = form.get_module() except Exception: logged_event.error(MessagingEvent.ERROR_CANNOT_FIND_FORM) return # Start a touchforms session for each recipient for recipient in recipients: logged_subevent = logged_event.create_subevent(handler, reminder, recipient) verified_number, unverified_number = get_recipient_phone_number(reminder, recipient, verified_numbers) no_verified_number = verified_number is None cant_use_unverified_number = ( unverified_number is None or not domain_obj.send_to_duplicated_case_numbers or form_requires_input(form) ) if no_verified_number and cant_use_unverified_number: logged_subevent.error(MessagingEvent.ERROR_NO_TWO_WAY_PHONE_NUMBER) continue key = "start-sms-survey-for-contact-%s" % recipient.get_id with CriticalSection([key], timeout=60): # Get the case to submit the form against, if any if isinstance(recipient, CommCareCase) and not handler.force_surveys_to_use_triggered_case: case_id = recipient.get_id else: case_id = reminder.case_id if form.requires_case() and not case_id: logged_subevent.error(MessagingEvent.ERROR_NO_CASE_GIVEN) continue # Close all currently open sessions SQLXFormsSession.close_all_open_sms_sessions(reminder.domain, recipient.get_id) # Start the new session try: session, responses = start_session( reminder.domain, recipient, app, module, form, case_id, case_for_case_submission=handler.force_surveys_to_use_triggered_case, ) except TouchformsError as e: human_readable_message = e.response_data.get("human_readable_message", None) logged_subevent.error( MessagingEvent.ERROR_TOUCHFORMS_ERROR, additional_error_text=human_readable_message ) if touchforms_error_is_config_error(e): # Don't reraise the exception because this means there are configuration # issues with the form that need to be fixed continue else: # Reraise the exception so that the framework retries it again later raise except Exception as e: logged_subevent.error(MessagingEvent.ERROR_TOUCHFORMS_ERROR) # Reraise the exception so that the framework retries it again later raise session.survey_incentive = handler.survey_incentive session.workflow = get_workflow(handler) session.reminder_id = reminder._id session.save() reminder.xforms_session_ids.append(session.session_id) logged_subevent.xforms_session = session logged_subevent.save() # Send out first message if len(responses) > 0: message = format_message_list(responses) metadata = MessageMetadata( workflow=get_workflow(handler), reminder_id=reminder._id, xforms_session_couch_id=session._id ) if verified_number: send_sms_to_verified_number(verified_number, message, metadata) else: send_sms(reminder.domain, recipient, unverified_number, message, metadata) logged_subevent.completed()
if len(recipients) == 1: raise_error(reminder, ERROR_RENDERING_MESSAGE % lang) return False else: raise_warning() # ERROR_RENDERING_MESSAGE continue verified_number, unverified_number = get_recipient_phone_number( reminder, recipient, verified_numbers) domain_obj = Domain.get_by_name(reminder.domain, strict=True) if verified_number is not None: result = send_sms_to_verified_number(verified_number, message, metadata) elif isinstance(recipient, CouchUser) and unverified_number: result = send_sms(reminder.domain, recipient, unverified_number, message, metadata) elif (isinstance(recipient, CommCareCase) and unverified_number and domain_obj.send_to_duplicated_case_numbers): result = send_sms(reminder.domain, recipient, unverified_number, message, metadata) else: if len(recipients) == 1: raise_error(reminder, ERROR_NO_VERIFIED_NUMBER) result = False if len(recipients) == 1: return result # For multiple recipients, always move to the next event return True
def fire_sms_event(reminder, handler, recipients, verified_numbers): current_event = reminder.current_event if reminder.method in [METHOD_SMS, METHOD_SMS_CALLBACK]: for recipient in recipients: try: lang = recipient.get_language_code() except Exception: lang = None message = current_event.message.get( lang, current_event.message[handler.default_lang]) try: message = Message.render(message, case=reminder.case.case_properties()) except Exception: if len(recipients) == 1: raise_error(reminder, ERROR_RENDERING_MESSAGE % lang) return False else: raise_warning() # ERROR_RENDERING_MESSAGE continue verified_number = verified_numbers[recipient.get_id] if verified_number is not None: result = send_sms_to_verified_number(verified_number, message) if not result: raise_warning() # Could not send SMS elif isinstance(recipient, CouchUser): # If there is no verified number, but the recipient is a CouchUser, still try to send it try: phone_number = recipient.phone_number except Exception: phone_number = None if phone_number is None: result = False if len(recipients) == 1: raise_error(reminder, ERROR_NO_OTHER_NUMBERS) else: raise_warning() # ERROR_NO_OTHER_NUMBERS else: result = send_sms(reminder.domain, recipient.get_id, phone_number, message) if not result: raise_warning() # Could not send SMS else: if len(recipients) == 1: raise_error(reminder, ERROR_NO_VERIFIED_NUMBER) else: raise_warning() # ERROR_NO_VERIFIED_NUMBER result = False if len(recipients) == 1: return result # For multiple recipients, always move to the next event return True elif reminder.method in [METHOD_TEST, METHOD_SMS_CALLBACK_TEST]: # Used for automated tests return True
def test_backend(self): # Test the backend map self.assertTrue( send_sms(self.domain, None, "15551234567", "Test for BACKEND2")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertTrue(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend2.delete_invoke_doc() self.assertFalse(self.backend2.invoke_doc_exists()) self.assertTrue( send_sms(self.domain, None, "9100000000", "Test for BACKEND3")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertTrue(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend3.delete_invoke_doc() self.assertFalse(self.backend3.invoke_doc_exists()) self.assertTrue( send_sms(self.domain, None, "26500000000", "Test for BACKEND4")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertTrue(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend4.delete_invoke_doc() self.assertFalse(self.backend4.invoke_doc_exists()) self.assertTrue( send_sms(self.domain, None, "25800000000", "Test for BACKEND1")) self.assertTrue(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend1.delete_invoke_doc() self.assertFalse(self.backend1.invoke_doc_exists()) # Test overriding with a domain-level backend self.domain_obj.default_sms_backend_id = self.backend5._id self.domain_obj.save() self.assertTrue( send_sms(self.domain, None, "15551234567", "Test for BACKEND5")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertTrue(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend5.delete_invoke_doc() self.assertFalse(self.backend5.invoke_doc_exists()) # Test use of backend that another domain owns but has granted access self.domain_obj.default_sms_backend_id = self.backend6._id self.domain_obj.save() self.assertTrue( send_sms(self.domain, None, "25800000000", "Test for BACKEND6")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertTrue(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend6.delete_invoke_doc() self.assertFalse(self.backend6.invoke_doc_exists()) # Test backend access control self.domain_obj.default_sms_backend_id = self.backend7._id self.domain_obj.save() self.assertFalse( send_sms(self.domain, None, "25800000000", "Test for BACKEND7")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) # Test sending to verified number with backend map self.domain_obj.default_sms_backend_id = None self.domain_obj.save() verified_number = self.contact.get_verified_number() self.assertTrue(verified_number is not None) self.assertTrue(verified_number.backend_id is None) self.assertEqual(verified_number.phone_number, "15551234567") self.assertTrue( send_sms_to_verified_number(verified_number, "Test for BACKEND2")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertTrue(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend2.delete_invoke_doc() self.assertFalse(self.backend2.invoke_doc_exists()) # Test sending to verified number with default domain backend self.domain_obj.default_sms_backend_id = self.backend5._id self.domain_obj.save() self.assertTrue( send_sms_to_verified_number(verified_number, "Test for BACKEND5")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertTrue(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend5.delete_invoke_doc() self.assertFalse(self.backend5.invoke_doc_exists()) # Test sending to verified number with a contact-level backend owned by the domain self.case.set_case_property("contact_backend_id", "BACKEND") self.case.save() self.contact = CommConnectCase.wrap(self.case.to_json()) verified_number = self.contact.get_verified_number() self.assertTrue(verified_number is not None) self.assertEqual(verified_number.backend_id, "BACKEND") self.assertEqual(verified_number.phone_number, "15551234567") self.assertTrue( send_sms_to_verified_number(verified_number, "Test for BACKEND")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertTrue(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend8.delete_invoke_doc() self.assertFalse(self.backend8.invoke_doc_exists()) # Test sending to verified number with a contact-level backend granted to the domain by another domain self.backend8.delete() self.assertTrue( send_sms_to_verified_number(verified_number, "Test for BACKEND")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertTrue(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend9.delete_invoke_doc() self.assertFalse(self.backend9.invoke_doc_exists()) # Test sending to verified number with a contact-level global backend self.backend9.delete() self.assertTrue( send_sms_to_verified_number(verified_number, "Test for BACKEND")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertTrue(self.backend10.invoke_doc_exists()) self.backend10.delete_invoke_doc() self.assertFalse(self.backend10.invoke_doc_exists()) # Test raising exception if contact-level backend is not found self.backend10.delete() try: self.assertTrue( send_sms_to_verified_number(verified_number, "Test for BACKEND")) except BadSMSConfigException: pass else: self.assertTrue(False) # Test send_sms_with_backend self.assertTrue( send_sms_with_backend(self.domain, "+15551234567", "Test for BACKEND3", self.backend3._id)) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertTrue(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.backend3.delete_invoke_doc() self.assertFalse(self.backend3.invoke_doc_exists()) # Test send_sms_with_backend_name self.assertTrue( send_sms_with_backend_name(self.domain, "+15551234567", "Test for BACKEND3", "BACKEND3")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertTrue(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.backend3.delete_invoke_doc() self.assertFalse(self.backend3.invoke_doc_exists())
def test_backend(self): # Test the backend map self.assertTrue(send_sms(self.domain, None, "15551234567", "Test for BACKEND2")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertTrue(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend2.delete_invoke_doc() self.assertFalse(self.backend2.invoke_doc_exists()) self.assertTrue(send_sms(self.domain, None, "9100000000", "Test for BACKEND3")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertTrue(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend3.delete_invoke_doc() self.assertFalse(self.backend3.invoke_doc_exists()) self.assertTrue(send_sms(self.domain, None, "26500000000", "Test for BACKEND4")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertTrue(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend4.delete_invoke_doc() self.assertFalse(self.backend4.invoke_doc_exists()) self.assertTrue(send_sms(self.domain, None, "25800000000", "Test for BACKEND1")) self.assertTrue(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend1.delete_invoke_doc() self.assertFalse(self.backend1.invoke_doc_exists()) # Test overriding with a domain-level backend self.domain_obj.default_sms_backend_id = self.backend5._id self.domain_obj.save() self.assertTrue(send_sms(self.domain, None, "15551234567", "Test for BACKEND5")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertTrue(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend5.delete_invoke_doc() self.assertFalse(self.backend5.invoke_doc_exists()) # Test use of backend that another domain owns but has granted access self.domain_obj.default_sms_backend_id = self.backend6._id self.domain_obj.save() self.assertTrue(send_sms(self.domain, None, "25800000000", "Test for BACKEND6")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertTrue(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend6.delete_invoke_doc() self.assertFalse(self.backend6.invoke_doc_exists()) # Test backend access control self.domain_obj.default_sms_backend_id = self.backend7._id self.domain_obj.save() self.assertFalse(send_sms(self.domain, None, "25800000000", "Test for BACKEND7")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) # Test sending to verified number with backend map self.domain_obj.default_sms_backend_id = None self.domain_obj.save() verified_number = self.contact.get_verified_number() self.assertTrue(verified_number is not None) self.assertTrue(verified_number.backend_id is None) self.assertEqual(verified_number.phone_number, "15551234567") self.assertTrue(send_sms_to_verified_number(verified_number, "Test for BACKEND2")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertTrue(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend2.delete_invoke_doc() self.assertFalse(self.backend2.invoke_doc_exists()) # Test sending to verified number with default domain backend self.domain_obj.default_sms_backend_id = self.backend5._id self.domain_obj.save() self.assertTrue(send_sms_to_verified_number(verified_number, "Test for BACKEND5")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertTrue(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertFalse(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend5.delete_invoke_doc() self.assertFalse(self.backend5.invoke_doc_exists()) # Test sending to verified number with a contact-level backend owned by the domain self.case.set_case_property("contact_backend_id", "BACKEND") self.case.save() self.contact = CommConnectCase.wrap(self.case.to_json()) verified_number = self.contact.get_verified_number() self.assertTrue(verified_number is not None) self.assertEqual(verified_number.backend_id, "BACKEND") self.assertEqual(verified_number.phone_number, "15551234567") self.assertTrue(send_sms_to_verified_number(verified_number, "Test for BACKEND")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertTrue(self.backend8.invoke_doc_exists()) self.assertFalse(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend8.delete_invoke_doc() self.assertFalse(self.backend8.invoke_doc_exists()) # Test sending to verified number with a contact-level backend granted to the domain by another domain self.backend8.delete() self.assertTrue(send_sms_to_verified_number(verified_number, "Test for BACKEND")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertTrue(self.backend9.invoke_doc_exists()) self.assertFalse(self.backend10.invoke_doc_exists()) self.backend9.delete_invoke_doc() self.assertFalse(self.backend9.invoke_doc_exists()) # Test sending to verified number with a contact-level global backend self.backend9.delete() self.assertTrue(send_sms_to_verified_number(verified_number, "Test for BACKEND")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertFalse(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.assertTrue(self.backend10.invoke_doc_exists()) self.backend10.delete_invoke_doc() self.assertFalse(self.backend10.invoke_doc_exists()) # Test raising exception if contact-level backend is not found self.backend10.delete() try: self.assertTrue(send_sms_to_verified_number(verified_number, "Test for BACKEND")) except BadSMSConfigException: pass else: self.assertTrue(False) # Test send_sms_with_backend self.assertTrue(send_sms_with_backend(self.domain, "+15551234567", "Test for BACKEND3", self.backend3._id)) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertTrue(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.backend3.delete_invoke_doc() self.assertFalse(self.backend3.invoke_doc_exists()) # Test send_sms_with_backend_name self.assertTrue(send_sms_with_backend_name(self.domain, "+15551234567", "Test for BACKEND3", "BACKEND3")) self.assertFalse(self.backend1.invoke_doc_exists()) self.assertFalse(self.backend2.invoke_doc_exists()) self.assertTrue(self.backend3.invoke_doc_exists()) self.assertFalse(self.backend4.invoke_doc_exists()) self.assertFalse(self.backend5.invoke_doc_exists()) self.assertFalse(self.backend6.invoke_doc_exists()) self.assertFalse(self.backend7.invoke_doc_exists()) self.backend3.delete_invoke_doc() self.assertFalse(self.backend3.invoke_doc_exists())
def send_to_recipients(request, domain): recipients = request.POST.get('recipients') message = request.POST.get('message') if not recipients: messages.error(request, "You didn't specify any recipients") elif not message: messages.error(request, "You can't send an empty message") else: recipients = [x.strip() for x in recipients.split(',') if x.strip()] phone_numbers = [] # formats: GroupName (group), "Username", +15555555555 group_names = [] usernames = [] phone_numbers = [] unknown_usernames = [] GROUP = "[group]" send_to_all_checked = False for recipient in recipients: if recipient == "[send to all]": send_to_all_checked = True phone_users = CouchUser.view("users/phone_users_by_domain", startkey=[domain], endkey=[domain, {}], include_docs=True) for user in phone_users: usernames.append(user.username) group_names = [] break elif (not send_to_all_checked) and recipient.endswith(GROUP): name = recipient[:-len(GROUP)].strip() group_names.append(name) elif re.match(r'^\+\d+', recipient): # here we expect it to have a plus sign def wrap_user_by_type(u): return getattr(user_models, u['doc']['doc_type']).wrap(u['doc']) phone_users = CouchUser.view( "users/by_default_phone", # search both with and w/o the plus keys=[recipient, recipient[1:]], include_docs=True, wrapper=wrap_user_by_type).all() phone_users = filter(lambda u: u.is_member_of(domain), phone_users) if len(phone_users) > 0: phone_numbers.append((phone_users[0], recipient)) else: phone_numbers.append((None, recipient)) elif (not send_to_all_checked) and re.match(r'[\w\.]+', recipient): usernames.append(recipient) else: unknown_usernames.append(recipient) login_ids = dict([ (r['key'], r['id']) for r in get_db().view("users/by_username", keys=usernames).all() ]) for username in usernames: if username not in login_ids: unknown_usernames.append(username) login_ids = login_ids.values() users = [] empty_groups = [] if len(group_names) > 0: users.extend( CouchUser.view('users/by_group', keys=[[domain, gn] for gn in group_names], include_docs=True).all()) if len(users) == 0: empty_groups = group_names users.extend( CouchUser.view('_all_docs', keys=login_ids, include_docs=True).all()) users = [ user for user in users if user.is_active and not user.is_deleted() ] phone_numbers.extend([(user, user.phone_number) for user in users]) failed_numbers = [] no_numbers = [] sent = [] for user, number in phone_numbers: if not number: no_numbers.append(user.raw_username) elif send_sms(domain, user.user_id if user else "", number, message): sent.append("%s" % (user.raw_username if user else number)) else: failed_numbers.append( "%s (%s)" % (number, user.raw_username if user else "<no username>")) if empty_groups or failed_numbers or unknown_usernames or no_numbers: if empty_groups: messages.error( request, "The following groups don't exist: %s" % (', '.join(empty_groups))) if no_numbers: messages.error( request, "The following users don't have phone numbers: %s" % (', '.join(no_numbers))) if failed_numbers: messages.error( request, "Couldn't send to the following number(s): %s" % (', '.join(failed_numbers))) if unknown_usernames: messages.error( request, "Couldn't find the following user(s): %s" % (', '.join(unknown_usernames))) if sent: messages.success(request, "Successfully sent: %s" % (', '.join(sent))) else: messages.info(request, "No messages were sent.") else: messages.success(request, "Message sent") return HttpResponseRedirect( request.META.get('HTTP_REFERER') or reverse(compose_message, args=[domain]))
def fire_sms_survey_event(reminder, handler, recipients, verified_numbers, logged_event): current_event = reminder.current_event if reminder.callback_try_count > 0: # Leaving this as an explicit reminder that all survey related actions now happen # in a different process. Eventually all of this code will be removed when we move # to the new reminders framework. pass else: reminder.xforms_session_ids = [] domain_obj = Domain.get_by_name(reminder.domain, strict=True) # Get the app, module, and form try: form_unique_id = current_event.form_unique_id form = Form.get_form(form_unique_id) app = form.get_app() module = form.get_module() except Exception: logged_event.error(MessagingEvent.ERROR_CANNOT_FIND_FORM) return # Start a touchforms session for each recipient for recipient in recipients: logged_subevent = logged_event.create_subevent( handler, reminder, recipient) verified_number, unverified_number = get_recipient_phone_number( reminder, recipient, verified_numbers) no_verified_number = verified_number is None cant_use_unverified_number = (unverified_number is None or form_requires_input(form)) if no_verified_number and cant_use_unverified_number: logged_subevent.error( MessagingEvent.ERROR_NO_TWO_WAY_PHONE_NUMBER) continue if verified_number: pb = PhoneBlacklist.get_by_phone_number_or_none( verified_number.phone_number) else: pb = PhoneBlacklist.get_by_phone_number_or_none( unverified_number) if pb and not pb.send_sms: logged_subevent.error(MessagingEvent.ERROR_PHONE_OPTED_OUT) continue with critical_section_for_smsforms_sessions(recipient.get_id): # Get the case to submit the form against, if any if (is_commcarecase(recipient) and not handler.force_surveys_to_use_triggered_case): case_id = recipient.case_id else: case_id = reminder.case_id if form.requires_case() and not case_id: logged_subevent.error(MessagingEvent.ERROR_NO_CASE_GIVEN) continue # Close all currently open sessions SQLXFormsSession.close_all_open_sms_sessions( reminder.domain, recipient.get_id) # Start the new session try: if current_event.callback_timeout_intervals: if handler.submit_partial_forms: expire_after = sum( current_event.callback_timeout_intervals) reminder_intervals = current_event.callback_timeout_intervals[: -1] else: expire_after = SQLXFormsSession.MAX_SESSION_LENGTH reminder_intervals = current_event.callback_timeout_intervals submit_partially_completed_forms = handler.submit_partial_forms include_case_updates_in_partial_submissions = handler.include_case_side_effects else: expire_after = SQLXFormsSession.MAX_SESSION_LENGTH reminder_intervals = [] submit_partially_completed_forms = False include_case_updates_in_partial_submissions = False session, responses = start_session( SQLXFormsSession.create_session_object( reminder.domain, recipient, verified_number.phone_number if verified_number else unverified_number, app, form, expire_after=expire_after, reminder_intervals=reminder_intervals, submit_partially_completed_forms= submit_partially_completed_forms, include_case_updates_in_partial_submissions= include_case_updates_in_partial_submissions), reminder.domain, recipient, app, module, form, case_id, case_for_case_submission=handler. force_surveys_to_use_triggered_case) except TouchformsError as e: human_readable_message = get_formplayer_exception( reminder.domain, e) logged_subevent.error( MessagingEvent.ERROR_TOUCHFORMS_ERROR, additional_error_text=human_readable_message) if touchforms_error_is_config_error(reminder.domain, e): # Don't reraise the exception because this means there are configuration # issues with the form that need to be fixed continue else: # Reraise the exception so that the framework retries it again later raise except Exception as e: logged_subevent.error( MessagingEvent.ERROR_TOUCHFORMS_ERROR) # Reraise the exception so that the framework retries it again later raise session.survey_incentive = handler.survey_incentive session.workflow = get_workflow(handler) session.reminder_id = reminder._id session.save() reminder.xforms_session_ids.append(session.session_id) logged_subevent.xforms_session = session logged_subevent.save() # Send out first message if len(responses) > 0: message = format_message_list(responses) metadata = MessageMetadata( workflow=get_workflow(handler), reminder_id=reminder._id, xforms_session_couch_id=session._id, ) if verified_number: send_sms_to_verified_number( verified_number, message, metadata, logged_subevent=logged_subevent) else: send_sms(reminder.domain, recipient, unverified_number, message, metadata) logged_subevent.completed()
def respond(self, message, **kwargs): if self.verified_contact: with localize(self.user.get_language_code()): send_sms_to_verified_number(self.verified_contact, unicode(message % kwargs)) else: send_sms(self.domain, None, self.msg.phone_number, unicode(message % kwargs))