def test_case_recipient(self): with create_case(self.domain, 'person') as case: instance = CaseTimedScheduleInstance(domain=self.domain, case_id=case.case_id, recipient_type='Self') self.assertTrue(is_commcarecase(instance.recipient)) self.assertEqual(instance.recipient.case_id, case.case_id)
def get_recipient_details(self, sms): details = { 'type': None, 'name': None, 'location_id': None, } if sms.couch_recipient: if sms.couch_recipient in self.recipient_details: return self.recipient_details[sms.couch_recipient] recipient = sms.recipient if is_commcarecase(recipient): details['name'] = recipient.name details['type'] = 'case: %s' % recipient.type if recipient.type == 'commcare-user': user = CommCareUser.get_by_user_id(recipient.owner_id) if user: details['location_id'] = user.location_id else: details['location_id'] = recipient.owner_id elif isinstance(recipient, CommCareUser): details['name'] = recipient.username details['type'] = 'mobile worker' details['location_id'] = recipient.location_id self.recipient_details[sms.couch_recipient] = details return details
def test_case_owner(self): with create_test_case(self.domain, 'participant', 'test') as case: number = PhoneNumber(owner_doc_type='CommCareCase', owner_id=case.case_id) owner = number.owner self.assertTrue(is_commcarecase(owner)) self.assertEqual(owner.case_id, case.case_id)
def get_recipient_name(recipient, include_desc=True): if recipient is None: return "(no recipient)" elif isinstance(recipient, list): if len(recipient) > 0: return ",".join([get_recipient_name(r, include_desc) for r in recipient]) else: return "(no recipient)" elif isinstance(recipient, CouchUser): name = recipient.raw_username desc = "User" elif is_commcarecase(recipient): name = recipient.name desc = "Case" elif isinstance(recipient, Group): name = recipient.name desc = "Group" elif isinstance(recipient, CommCareCaseGroup): name = recipient.name desc = "Case Group" elif isinstance(recipient, SQLLocation): name = recipient.name desc = "Location" else: name = "(unknown)" desc = "" if include_desc: return "%s '%s'" % (desc, name) else: return name
def send(self, recipient, logged_event, phone_entry=None): app, module, form, requires_input = self.get_memoized_app_module_form( logged_event.domain) if any([o is None for o in (app, module, form)]): logged_event.error(MessagingEvent.ERROR_CANNOT_FIND_FORM) return logged_subevent = logged_event.create_subevent_from_contact_and_content( recipient, self, case_id=self.case.case_id if self.case else None, ) # We don't try to look up the phone number from the user case in this scenario # because this use case involves starting a survey session, which can be # very different if the contact is a user or is a case. So here if recipient # is a user we only allow them to fill out the survey as the user contact, and # not the user case contact. phone_entry_or_number = (phone_entry or self.get_two_way_entry_or_phone_number( recipient, try_usercase=False, domain_for_toggles=logged_event.domain)) if phone_entry_or_number is None: logged_subevent.error(MessagingEvent.ERROR_NO_PHONE_NUMBER) return if requires_input and not isinstance(phone_entry_or_number, PhoneNumber): logged_subevent.error(MessagingEvent.ERROR_NO_TWO_WAY_PHONE_NUMBER) return with self.get_critical_section(recipient): # Get the case to submit the form against, if any case_id = None if is_commcarecase(recipient): case_id = recipient.case_id elif self.case: case_id = self.case.case_id if form.requires_case() and not case_id: logged_subevent.error(MessagingEvent.ERROR_NO_CASE_GIVEN) return session, responses = self.start_smsforms_session( logged_event.domain, recipient, case_id, phone_entry_or_number, logged_subevent, self.get_workflow(logged_event), app, form) if session: logged_subevent.xforms_session = session logged_subevent.save() # send_first_message is a celery task # but we first call it synchronously to save resources in the 99% case # send_first_message will retry itself as a delayed celery task # if there are conflicting sessions preventing it from sending immediately send_first_message(logged_event.domain, recipient, phone_entry_or_number, session, responses, logged_subevent, self.get_workflow(logged_event))
def get_recipient_name(recipient, include_desc=True): if recipient is None: return "(no recipient)" elif isinstance(recipient, list): if len(recipient) > 0: return ",".join( [get_recipient_name(r, include_desc) for r in recipient]) else: return "(no recipient)" elif isinstance(recipient, CouchUser): name = recipient.raw_username desc = "User" elif is_commcarecase(recipient): name = recipient.name desc = "Case" elif isinstance(recipient, Group): name = recipient.name desc = "Group" elif isinstance(recipient, CommCareCaseGroup): name = recipient.name desc = "Case Group" elif isinstance(recipient, SQLLocation): name = recipient.name desc = "Location" else: name = "(unknown)" desc = "" if include_desc: return "%s '%s'" % (desc, name) else: return name
def test_get_contact_for_case(self): with create_test_case(self.domain, "contact", "test-case") as case: contact = get_contact(self.domain, case.case_id) self.assertEqual(contact.case_id, case.case_id) self.assertTrue(is_commcarecase(contact)) with self.assertRaises(ContactNotFoundException): get_contact(self.domain + "x", case.case_id)
def test_get_contact_for_case(self): with create_test_case(self.domain, 'contact', 'test-case') as case: contact = get_contact(self.domain, case.case_id) self.assertEqual(contact.case_id, case.case_id) self.assertTrue(is_commcarecase(contact)) with self.assertRaises(ContactNotFoundException): get_contact(self.domain + 'x', case.case_id)
def get_case_id(contact, case=None): if case: case_id = case.case_id elif is_commcarecase(contact): case_id = contact.case_id else: case_id = None return case_id
def start_session(session, domain, contact, app, module, form, case_id=None, yield_responses=False, case_for_case_submission=False): """ Starts a session in touchforms and saves the record in the database. Returns a tuple containing the session object and the (text-only) list of generated questions/responses based on the form. Special params: yield_responses - If True, the list of xforms responses is returned, otherwise the text prompt for each is returned session_type - XFORMS_SESSION_SMS or XFORMS_SESSION_IVR case_for_case_submission - True if this is a submission that a case is making to alter another related case. For example, if a parent case is filling out an SMS survey which will update its child case, this should be True. """ # NOTE: this call assumes that "contact" will expose three # properties: .raw_username, .get_id, and .get_language_code session_data = CaseSessionDataHelper(domain, contact, case_id, app, form).get_session_data(COMMCONNECT_DEVICE_ID) # since the API user is a superuser, force touchforms to query only # the contact's cases by specifying it as an additional filter if is_commcarecase(contact) and form.requires_case(): session_data["additional_filters"] = { "case_id": case_id, "footprint": "true" if form.uses_parent_case() else "false", } elif isinstance(contact, CouchUser): session_data["additional_filters"] = { "user_id": contact.get_id, "footprint": "true" } if app and form: session_data.update(get_cloudcare_session_data(domain, form, contact)) language = contact.get_language_code() config = XFormsConfig(form_content=form.render_xform(), language=language, session_data=session_data, auth=AUTH) session_start_info = tfsms.start_session(config) session.session_id = session_start_info.session_id session.save() responses = session_start_info.first_responses if len(responses) > 0 and responses[0].status == 'http-error': session.mark_completed(False) session.save() raise TouchformsError('Cannot connect to touchforms.') # Prevent future update conflicts by getting the session again from the db # since the session could have been updated separately in the first_responses call session = SQLXFormsSession.objects.get(pk=session.pk) if yield_responses: return (session, responses) else: return (session, _responses_to_text(responses))
def _generic_message_bank_content(fixture_name, reminder, handler, recipient): domain = reminder.domain message_bank = FixtureDataType.by_domain_tag(domain, fixture_name).first() if not message_bank: message = "Lookup Table {} not found in {}".format( fixture_name, domain) notify_dimagi_project_admins(domain, message=message) return None fields = message_bank.fields_without_attributes if any(field not in fields for field in REQUIRED_FIXTURE_FIELDS): message = "{} in {} must have {}".format( fixture_name, domain, ','.join(REQUIRED_FIXTURE_FIELDS)) notify_dimagi_project_admins(domain, message=message) return None if not is_commcarecase(recipient): recipient_id = getattr(recipient, '_id') if hasattr( recipient, '_id') else 'id_unknown' message = "recipient {} must be a case in domain {}".format( recipient_id, domain) notify_dimagi_project_admins(domain, message=message) return None try: risk_profile = recipient.dynamic_case_properties()[RISK_PROFILE_FIELD] except KeyError: message = "case {} does not include risk_profile".format( recipient.case_id) notify_dimagi_project_admins(domain, message=message) return None current_message_seq_num = str(( (reminder.schedule_iteration_num - 1) * len(handler.events)) + reminder.current_event_sequence_num + 1) custom_messages = FixtureDataItem.by_field_value(domain, message_bank, RISK_PROFILE_FIELD, risk_profile) custom_messages = [ m for m in custom_messages if m.fields_without_attributes['sequence'] == current_message_seq_num ] if len(custom_messages) != 1: if not custom_messages: message = "No message for case {}, risk {}, seq {} in domain {} in fixture {}" else: message = "Multiple messages for case {}, risk {}, seq {} in domain {} in fixture {}" message = message.format(recipient.case_id, risk_profile, current_message_seq_num, domain, fixture_name) notify_dimagi_project_admins(domain, message=message) return None return custom_messages[0].fields_without_attributes['message']
def start_session(session, domain, contact, app, form, case_id=None, yield_responses=False): """ Starts a session in touchforms and saves the record in the database. Returns a tuple containing the session object and the (text-only) list of generated questions/responses based on the form. Special params: yield_responses - If True, the list of xforms responses is returned, otherwise the text prompt for each is returned """ # NOTE: this call assumes that "contact" will expose three # properties: .raw_username, .get_id, and .get_language_code session_data = CaseSessionDataHelper( domain, contact, case_id, app, form).get_session_data(COMMCONNECT_DEVICE_ID) kwargs = {} if is_commcarecase(contact): kwargs['restore_as_case_id'] = contact.case_id else: kwargs['restore_as'] = contact.raw_username if app and form: session_data.update(get_cloudcare_session_data(domain, form, contact)) language = contact.get_language_code() config = XFormsConfig(form_content=form.render_xform().decode('utf-8'), language=language, session_data=session_data, domain=domain, **kwargs) session_start_info = tfsms.start_session(config) session.session_id = session_start_info.session_id session.save() responses = session_start_info.first_responses if len(responses) > 0 and responses[0].status == 'http-error': session.mark_completed(False) session.save() raise TouchformsError('Cannot connect to touchforms.') # Prevent future update conflicts by getting the session again from the db # since the session could have been updated separately in the first_responses call session = SQLXFormsSession.objects.get(pk=session.pk) if yield_responses: return (session, responses) else: return (session, _responses_to_text(responses))
def fire_ivr_survey_event(reminder, handler, recipients, verified_numbers, logged_event): domain_obj = Domain.get_by_name(reminder.domain, strict=True) for recipient in recipients: initiate_call = True if reminder.callback_try_count > 0 and reminder.event_initiation_timestamp: initiate_call = not Call.answered_call_exists( recipient.doc_type, recipient.get_id, reminder.event_initiation_timestamp) if initiate_call: if (is_commcarecase(recipient) and not handler.force_surveys_to_use_triggered_case): case_id = recipient.case_id else: case_id = reminder.case_id verified_number, unverified_number = get_recipient_phone_number( reminder, recipient, verified_numbers) if verified_number: initiate_outbound_call.delay( recipient, reminder.current_event.form_unique_id, handler.submit_partial_forms, handler.include_case_side_effects, handler.max_question_retries, logged_event.pk, verified_number=verified_number, case_id=case_id, case_for_case_submission=handler. force_surveys_to_use_triggered_case, timestamp=CaseReminderHandler.get_now(), ) elif domain_obj.send_to_duplicated_case_numbers and unverified_number: initiate_outbound_call.delay( recipient, reminder.current_event.form_unique_id, handler.submit_partial_forms, handler.include_case_side_effects, handler.max_question_retries, logged_event.pk, unverified_number=unverified_number, case_id=case_id, case_for_case_submission=handler. force_surveys_to_use_triggered_case, timestamp=CaseReminderHandler.get_now(), ) else: # initiate_outbound_call will create the subevent automatically, # so since we're not initiating the call here, we have to create # the subevent explicitly in order to log the error. logged_subevent = logged_event.create_subevent( handler, reminder, recipient) logged_subevent.error(MessagingEvent.ERROR_NO_PHONE_NUMBER)
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 get_one_way_number_for_recipient(recipient): if isinstance(recipient, CouchUser): return recipient.phone_number elif is_commcarecase(recipient): one_way_number = recipient.get_case_property('contact_phone_number') one_way_number = apply_leniency(one_way_number) if one_way_number: try: CommCareMobileContactMixin.validate_number_format(one_way_number) return one_way_number except InvalidFormatException: return None return None
def _get_obj_template_info(obj): if is_commcarecase(obj): return _get_case_template_info(obj) elif isinstance(obj, WebUser): return _get_web_user_template_info(obj) elif isinstance(obj, CommCareUser): return _get_mobile_user_template_info(obj) elif isinstance(obj, Group): return _get_group_template_info(obj) elif isinstance(obj, SQLLocation): return _get_location_template_info(obj) return {}
def _get_obj_template_info(obj): if is_commcarecase(obj): return _get_case_template_info(obj) elif isinstance(obj, WebUser): return _get_web_user_template_info(obj) elif isinstance(obj, CommCareUser): return _get_mobile_user_template_info(obj) elif isinstance(obj, Group): return _get_group_template_info(obj) elif isinstance(obj, Location): return _get_location_template_info(obj) return {}
def fire_ivr_survey_event(reminder, handler, recipients, verified_numbers, logged_event): domain_obj = Domain.get_by_name(reminder.domain, strict=True) for recipient in recipients: initiate_call = True if reminder.callback_try_count > 0 and reminder.event_initiation_timestamp: initiate_call = not Call.answered_call_exists( recipient.doc_type, recipient.get_id, reminder.event_initiation_timestamp ) if initiate_call: if (is_commcarecase(recipient) and not handler.force_surveys_to_use_triggered_case): case_id = recipient.case_id else: case_id = reminder.case_id verified_number, unverified_number = get_recipient_phone_number( reminder, recipient, verified_numbers) if verified_number: initiate_outbound_call.delay( recipient, reminder.current_event.form_unique_id, handler.submit_partial_forms, handler.include_case_side_effects, handler.max_question_retries, logged_event.pk, verified_number=verified_number, case_id=case_id, case_for_case_submission=handler.force_surveys_to_use_triggered_case, timestamp=CaseReminderHandler.get_now(), ) elif domain_obj.send_to_duplicated_case_numbers and unverified_number: initiate_outbound_call.delay( recipient, reminder.current_event.form_unique_id, handler.submit_partial_forms, handler.include_case_side_effects, handler.max_question_retries, logged_event.pk, unverified_number=unverified_number, case_id=case_id, case_for_case_submission=handler.force_surveys_to_use_triggered_case, timestamp=CaseReminderHandler.get_now(), ) else: # initiate_outbound_call will create the subevent automatically, # so since we're not initiating the call here, we have to create # the subevent explicitly in order to log the error. logged_subevent = logged_event.create_subevent(handler, reminder, recipient) logged_subevent.error(MessagingEvent.ERROR_NO_PHONE_NUMBER)
def _get_message_bank_content(fixture_name, domain, schedule_iteration_num, current_event_num, num_events, recipient): message_bank = FixtureDataType.by_domain_tag(domain, fixture_name).first() if not message_bank: message = "Lookup Table {} not found in {}".format(fixture_name, domain) notify_dimagi_project_admins(domain, message=message) return None fields = message_bank.fields_without_attributes if any(field not in fields for field in REQUIRED_FIXTURE_FIELDS): message = "{} in {} must have {}".format( fixture_name, domain, ','.join(REQUIRED_FIXTURE_FIELDS) ) notify_dimagi_project_admins(domain, message=message) return None if not is_commcarecase(recipient): recipient_id = getattr(recipient, '_id') if hasattr(recipient, '_id') else 'id_unknown' message = "recipient {} must be a case in domain {}".format(recipient_id, domain) notify_dimagi_project_admins(domain, message=message) return None try: risk_profile = recipient.dynamic_case_properties()[RISK_PROFILE_FIELD] except KeyError: message = "case {} does not include risk_profile".format(recipient.case_id) notify_dimagi_project_admins(domain, message=message) return None current_message_seq_num = str( ((schedule_iteration_num - 1) * num_events) + current_event_num + 1 ) custom_messages = FixtureDataItem.by_field_value( domain, message_bank, RISK_PROFILE_FIELD, risk_profile ) custom_messages = [m for m in custom_messages if m.fields_without_attributes['sequence'] == current_message_seq_num] if len(custom_messages) != 1: if not custom_messages: message = "No message for case {}, risk {}, seq {} in domain {} in fixture {}" else: message = "Multiple messages for case {}, risk {}, seq {} in domain {} in fixture {}" message = message.format(recipient.case_id, risk_profile, current_message_seq_num, domain, fixture_name) notify_dimagi_project_admins(domain, message=message) return None return custom_messages[0].fields_without_attributes['message']
def send_sms(domain, contact, phone_number, text, metadata=None, logged_subevent=None): """ Sends an outbound SMS. Returns false if it fails. """ if phone_number is None: return False if isinstance(phone_number, six.integer_types): phone_number = str(phone_number) phone_number = clean_phone_number(phone_number) msg = get_sms_class()(domain=domain, phone_number=phone_number, direction=OUTGOING, date=get_utcnow(), backend_id=None, location_id=get_location_id_by_contact( domain, contact), text=text) if contact: msg.couch_recipient = contact.get_id msg.couch_recipient_doc_type = contact.doc_type if domain and contact and is_commcarecase(contact): backend_name = contact.get_case_property('contact_backend_id') backend_name = backend_name.strip() if isinstance( backend_name, six.string_types) else '' soft_assert_type_text(backend_name) if backend_name: try: backend = SQLMobileBackend.load_by_name( SQLMobileBackend.SMS, domain, backend_name) except BadSMSConfigException as e: if logged_subevent: logged_subevent.error( MessagingEvent.ERROR_GATEWAY_NOT_FOUND, additional_error_text=six.text_type(e)) return False msg.backend_id = backend.couch_id add_msg_tags(msg, metadata) return queue_outgoing_sms(msg)
def get_content_send_lock(self, client, recipient): if is_commcarecase(recipient): doc_type = 'CommCareCase' doc_id = recipient.case_id else: doc_type = recipient.doc_type doc_id = recipient.get_id key = "send-content-for-%s-%s-%s-%s-%s" % ( self.__class__.__name__, self.schedule_instance_id.hex, self.next_event_due.strftime('%Y-%m-%d %H:%M:%S'), doc_type, doc_id, ) return client.lock(key, timeout=STALE_SCHEDULE_INSTANCE_INTERVAL * 60)
def get_unverified_number_for_recipient(recipient): if isinstance(recipient, CouchUser): try: return recipient.phone_number except Exception: # todo: catch more specific error return None elif is_commcarecase(recipient): unverified_number = recipient.get_case_property("contact_phone_number") unverified_number = apply_leniency(unverified_number) if unverified_number: try: CommCareMobileContactMixin.validate_number_format(unverified_number) return unverified_number except InvalidFormatException: return None return None
def get_unverified_number_for_recipient(recipient): if isinstance(recipient, CouchUser): try: return recipient.phone_number except Exception: # todo: catch more specific error return None elif is_commcarecase(recipient): unverified_number = recipient.get_case_property("contact_phone_number") unverified_number = apply_leniency(unverified_number) if unverified_number: try: CommCareMobileContactMixin.validate_number_format( unverified_number) return unverified_number except InvalidFormatException: return None return None
def process_survey_keyword_actions(verified_number, survey_keyword, text, msg): sender = verified_number.owner case = None args = split_args(text, survey_keyword) logged_event = MessagingEvent.create_from_keyword(survey_keyword, sender) # Log a messaging subevent for the incoming message subevent = logged_event.create_subevent_for_single_sms( msg.couch_recipient_doc_type, msg.couch_recipient, completed=True) add_msg_tags(msg, MessageMetadata(messaging_subevent_id=subevent.pk)) # Close any open sessions even if it's just an sms that we're # responding with. SQLXFormsSession.close_all_open_sms_sessions(verified_number.domain, verified_number.owner_id) if is_commcarecase(sender): case = sender args = args[1:]
def send_sms(domain, contact, phone_number, text, metadata=None, logged_subevent=None): """ Sends an outbound SMS. Returns false if it fails. """ if phone_number is None: return False if isinstance(phone_number, six.integer_types): phone_number = str(phone_number) phone_number = clean_phone_number(phone_number) msg = get_sms_class()( domain=domain, phone_number=phone_number, direction=OUTGOING, date=get_utcnow(), backend_id=None, location_id=get_location_id_by_contact(domain, contact), text = text ) if contact: msg.couch_recipient = contact.get_id msg.couch_recipient_doc_type = contact.doc_type if domain and contact and is_commcarecase(contact): backend_name = contact.get_case_property('contact_backend_id') backend_name = backend_name.strip() if isinstance(backend_name, six.string_types) else '' soft_assert_type_text(backend_name) if backend_name: try: backend = SQLMobileBackend.load_by_name(SQLMobileBackend.SMS, domain, backend_name) except BadSMSConfigException as e: if logged_subevent: logged_subevent.error(MessagingEvent.ERROR_GATEWAY_NOT_FOUND, additional_error_text=six.text_type(e)) return False msg.backend_id = backend.couch_id add_msg_tags(msg, metadata) return queue_outgoing_sms(msg)
def test_get_contact(self): with create_test_case(self.domain, 'contact', 'test-case') as case: user = CommCareUser.create(self.domain, 'test-user', '123') self.addCleanup(user.delete) contact = get_contact(self.domain, case.case_id) self.assertEqual(contact.case_id, case.case_id) self.assertTrue(is_commcarecase(contact)) contact = get_contact(self.domain, user.get_id) self.assertEqual(contact.get_id, user.get_id) self.assertTrue(isinstance(contact, CommCareUser)) with self.assertRaises(ContactNotFoundException): get_contact(self.domain, 'this-id-should-not-be-found') with self.assertRaises(ContactNotFoundException): get_contact(self.domain + 'x', case.case_id) with self.assertRaises(ContactNotFoundException): get_contact(self.domain + 'x', user.get_id)
def get_location_id(self, sms): location_id = None if sms.couch_recipient: if sms.couch_recipient in self.recipient_id_to_location_id: return self.recipient_id_to_location_id[sms.couch_recipient] recipient = sms.recipient if is_commcarecase(recipient): if recipient.type == 'commcare-user': user = CommCareUser.get_by_user_id(recipient.owner_id) if user: location_id = user.location_id else: location_id = recipient.owner_id elif isinstance(recipient, CommCareUser): location_id = recipient.location_id self.recipient_id_to_location_id[sms.couch_recipient] = location_id return location_id
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 = verified_numbers.get(session.connection_id) 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, logged_subevent=session.related_subevent) 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 (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: 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, logged_subevent=logged_subevent) else: send_sms(reminder.domain, recipient, unverified_number, message, metadata) logged_subevent.completed()
args = text.split(survey_keyword.delimiter) else: args = text.split() args = [arg.strip() for arg in args] return args def log_error(error, logged_subevent=None): if logged_subevent: logged_subevent.error(error) def get_case_id(contact, case=None): if case: case_id = case.case_id elif is_commcarecase(contact): case_id = contact.case_id else: case_id = None return case_id def get_app_module_form(form_unique_id, logged_subevent=None): """ Returns (app, module, form, error, error_code) """ try: form = Form.get_form(form_unique_id) app = form.get_app() module = form.get_module() return (app, module, form, False, None)
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()
""" from corehq.apps.reminders.models import ( CaseReminderHandler, CaseReminderEvent, ON_DATETIME, EVENT_AS_OFFSET, REMINDER_TYPE_DEFAULT, REMINDER_TYPE_KEYWORD_INITIATED, METHOD_SMS, METHOD_SMS_SURVEY, RECIPIENT_CASE, RECIPIENT_USER, RECIPIENT_SURVEY_SAMPLE, RECIPIENT_USER_GROUP, ) if is_commcarecase(contact): recipient = RECIPIENT_CASE elif isinstance(contact, CommCareCaseGroup): recipient = RECIPIENT_SURVEY_SAMPLE elif isinstance(contact, CommCareUser): recipient = RECIPIENT_USER elif isinstance(contact, Group): recipient = RECIPIENT_USER_GROUP else: raise Exception("Unsupported contact type for %s" % contact._id) reminder_type = reminder_type or REMINDER_TYPE_DEFAULT if recipient == RECIPIENT_CASE: case_id = contact.case_id elif case is not None: case_id = case.case_id
def send(self, recipient, logged_event, phone_entry=None): app, module, form, requires_input = self.get_memoized_app_module_form(logged_event.domain) if any([o is None for o in (app, module, form)]): logged_event.error(MessagingEvent.ERROR_CANNOT_FIND_FORM) return logged_subevent = logged_event.create_subevent_from_contact_and_content( recipient, self, case_id=self.case.case_id if self.case else None, ) # We don't try to look up the phone number from the user case in this scenario # because this use case involves starting a survey session, which can be # very different if the contact is a user or is a case. So here if recipient # is a user we only allow them to fill out the survey as the user contact, and # not the user case contact. phone_entry_or_number = ( phone_entry or self.get_two_way_entry_or_phone_number(recipient, try_user_case=False) ) if phone_entry_or_number is None: logged_subevent.error(MessagingEvent.ERROR_NO_PHONE_NUMBER) return if requires_input and not isinstance(phone_entry_or_number, PhoneNumber): logged_subevent.error(MessagingEvent.ERROR_NO_TWO_WAY_PHONE_NUMBER) return # The SMS framework already checks if the number has opted out before sending to # it. But for this use case we check for it here because we don't want to start # the survey session if they've opted out. if self.phone_has_opted_out(phone_entry_or_number): logged_subevent.error(MessagingEvent.ERROR_PHONE_OPTED_OUT) return with self.get_critical_section(recipient): # Get the case to submit the form against, if any case_id = None if is_commcarecase(recipient): case_id = recipient.case_id elif self.case: case_id = self.case.case_id if form.requires_case() and not case_id: logged_subevent.error(MessagingEvent.ERROR_NO_CASE_GIVEN) return session, responses = self.start_smsforms_session( logged_event.domain, recipient, case_id, phone_entry_or_number, logged_subevent, self.get_workflow(logged_event), app, module, form ) if session: logged_subevent.xforms_session = session logged_subevent.save() self.send_first_message( logged_event.domain, recipient, phone_entry_or_number, session, responses, logged_subevent, self.get_workflow(logged_event) ) logged_subevent.completed()
def start_session(session, domain, contact, app, module, form, case_id=None, yield_responses=False, case_for_case_submission=False): """ Starts a session in touchforms and saves the record in the database. Returns a tuple containing the session object and the (text-only) list of generated questions/responses based on the form. Special params: yield_responses - If True, the list of xforms responses is returned, otherwise the text prompt for each is returned session_type - XFORMS_SESSION_SMS or XFORMS_SESSION_IVR case_for_case_submission - True if this is a submission that a case is making to alter another related case. For example, if a parent case is filling out an SMS survey which will update its child case, this should be True. """ # NOTE: this call assumes that "contact" will expose three # properties: .raw_username, .get_id, and .get_language_code session_data = CaseSessionDataHelper(domain, contact, case_id, app, form).get_session_data(COMMCONNECT_DEVICE_ID) # since the API user is a superuser, force touchforms to query only # the contact's cases by specifying it as an additional filter if is_commcarecase(contact) and form.requires_case(): session_data["additional_filters"] = { "case_id": case_id, "footprint": "true" if form.uses_parent_case() else "false", } elif isinstance(contact, CouchUser): session_data["additional_filters"] = { "user_id": contact.get_id, "footprint": "true" } kwargs = {} if is_commcarecase(contact): kwargs['restore_as_case_id'] = contact.case_id else: kwargs['restore_as'] = contact.raw_username if app and form: session_data.update(get_cloudcare_session_data(domain, form, contact)) language = contact.get_language_code() config = XFormsConfig(form_content=form.render_xform(), language=language, session_data=session_data, domain=domain, **kwargs) session_start_info = tfsms.start_session(config) session.session_id = session_start_info.session_id session.save() responses = session_start_info.first_responses if len(responses) > 0 and responses[0].status == 'http-error': session.mark_completed(False) session.save() raise TouchformsError('Cannot connect to touchforms.') # Prevent future update conflicts by getting the session again from the db # since the session could have been updated separately in the first_responses call session = SQLXFormsSession.objects.get(pk=session.pk) if yield_responses: return (session, responses) else: return (session, _responses_to_text(responses))
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 = verified_numbers.get(session.connection_id) 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, logged_subevent=session.related_subevent) 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 (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: 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, logged_subevent=logged_subevent) else: send_sms(reminder.domain, recipient, unverified_number, message, metadata) logged_subevent.completed()
def send(self, recipient, logged_event, phone_entry=None): app, module, form, requires_input = self.get_memoized_app_module_form( logged_event.domain) if any([o is None for o in (app, module, form)]): logged_event.error(MessagingEvent.ERROR_CANNOT_FIND_FORM) return logged_subevent = logged_event.create_subevent_from_contact_and_content( recipient, self, case_id=self.case.case_id if self.case else None, ) # We don't try to look up the phone number from the user case in this scenario # because this use case involves starting a survey session, which can be # very different if the contact is a user or is a case. So here if recipient # is a user we only allow them to fill out the survey as the user contact, and # not the user case contact. phone_entry_or_number = (phone_entry or self.get_two_way_entry_or_phone_number( recipient, try_user_case=False)) if phone_entry_or_number is None: logged_subevent.error(MessagingEvent.ERROR_NO_PHONE_NUMBER) return if requires_input and not isinstance(phone_entry_or_number, PhoneNumber): logged_subevent.error(MessagingEvent.ERROR_NO_TWO_WAY_PHONE_NUMBER) return # The SMS framework already checks if the number has opted out before sending to # it. But for this use case we check for it here because we don't want to start # the survey session if they've opted out. if self.phone_has_opted_out(phone_entry_or_number): logged_subevent.error(MessagingEvent.ERROR_PHONE_OPTED_OUT) return with self.get_critical_section(recipient): # Get the case to submit the form against, if any case_id = None if is_commcarecase(recipient): case_id = recipient.case_id elif self.case: case_id = self.case.case_id if form.requires_case() and not case_id: logged_subevent.error(MessagingEvent.ERROR_NO_CASE_GIVEN) return session, responses = self.start_smsforms_session( logged_event.domain, recipient, case_id, phone_entry_or_number, logged_subevent, self.get_workflow(logged_event), app, module, form) if session: logged_subevent.xforms_session = session logged_subevent.save() self.send_first_message(logged_event.domain, recipient, phone_entry_or_number, session, responses, logged_subevent, self.get_workflow(logged_event)) logged_subevent.completed()
def create_immediate_reminder(contact, content_type, reminder_type=None, message=None, form_unique_id=None, case=None, logged_event=None): """ contact - the contact to send to content_type - METHOD_SMS or METHOD_SMS_SURVEY (see corehq.apps.reminders.models) reminder_type - either REMINDER_TYPE_DEFAULT, REMINDER_TYPE_ONE_TIME, or REMINDER_TYPE_KEYWORD_INITIATED message - the message to send if content_type == METHOD_SMS form_unique_id - the form_unique_id of the form to send if content_type == METHOD_SMS_SURVEY case - the case that is associated with this reminder (so that you can embed case properties into the message) logged_event - if this reminder is being created as a subevent of a MessagingEvent, this is the MessagingEvent """ from corehq.apps.reminders.models import ( CaseReminderHandler, CaseReminderEvent, ON_DATETIME, EVENT_AS_OFFSET, REMINDER_TYPE_DEFAULT, REMINDER_TYPE_KEYWORD_INITIATED, METHOD_SMS, METHOD_SMS_SURVEY, RECIPIENT_CASE, RECIPIENT_USER, RECIPIENT_SURVEY_SAMPLE, RECIPIENT_USER_GROUP, ) if is_commcarecase(contact): recipient = RECIPIENT_CASE elif isinstance(contact, CommCareCaseGroup): recipient = RECIPIENT_SURVEY_SAMPLE elif isinstance(contact, CommCareUser): recipient = RECIPIENT_USER elif isinstance(contact, Group): recipient = RECIPIENT_USER_GROUP else: raise Exception("Unsupported contact type for %s" % contact._id) reminder_type = reminder_type or REMINDER_TYPE_DEFAULT if recipient == RECIPIENT_CASE: case_id = contact.case_id elif case is not None: case_id = case.case_id else: case_id = None handler = CaseReminderHandler( domain=contact.domain, reminder_type=reminder_type, nickname="One-time Reminder", default_lang="xx", method=content_type, recipient=recipient, start_condition_type=ON_DATETIME, start_datetime=datetime.utcnow(), start_offset=0, events = [CaseReminderEvent( day_num=0, fire_time=time(0, 0), form_unique_id=form_unique_id if content_type == METHOD_SMS_SURVEY else None, message={'xx': message} if content_type == METHOD_SMS else {}, callback_timeout_intervals = [], )], schedule_length=1, event_interpretation=EVENT_AS_OFFSET, max_iteration_count=1, case_id=case_id, user_id=contact.get_id if recipient == RECIPIENT_USER else None, sample_id=contact.get_id if recipient == RECIPIENT_SURVEY_SAMPLE else None, user_group_id=contact.get_id if recipient == RECIPIENT_USER_GROUP else None, messaging_event_id=logged_event.pk if logged_event else None, ) handler.save(send_immediately=True)
def process_survey_keyword_actions(verified_number, survey_keyword, text, msg): sender = verified_number.owner case = None args = split_args(text, survey_keyword) logged_event = MessagingEvent.create_from_keyword(survey_keyword, sender) # Log a messaging subevent for the incoming message subevent = logged_event.create_subevent_for_single_sms( msg.couch_recipient_doc_type, msg.couch_recipient, completed=True ) add_msg_tags(msg, MessageMetadata(messaging_subevent_id=subevent.pk)) # Close any open sessions even if it's just an sms that we're # responding with. SQLXFormsSession.close_all_open_sms_sessions(verified_number.domain, verified_number.owner_id) if is_commcarecase(sender): case = sender args = args[1:] elif isinstance(sender, CommCareUser): if keyword_uses_form_that_requires_case(survey_keyword): if len(args) > 1: external_id = args[1] case, matches = get_case_by_external_id(verified_number.domain, external_id, sender) if matches == 0: send_keyword_response(verified_number, MSG_CASE_NOT_FOUND, logged_event) logged_event.error(MessagingEvent.ERROR_CASE_EXTERNAL_ID_NOT_FOUND) return elif matches > 1: send_keyword_response(verified_number, MSG_MULTIPLE_CASES_FOUND, logged_event) logged_event.error(MessagingEvent.ERROR_MULTIPLE_CASES_WITH_EXTERNAL_ID_FOUND) return else: send_keyword_response(verified_number, MSG_MISSING_EXTERNAL_ID, logged_event) logged_event.error(MessagingEvent.ERROR_NO_EXTERNAL_ID_GIVEN) return args = args[2:] else: args = args[1:] def cmp_fcn(a1, a2): a1_ss = (a1.action == KeywordAction.ACTION_STRUCTURED_SMS) a2_ss = (a2.action == KeywordAction.ACTION_STRUCTURED_SMS) if a1_ss and a2_ss: return 0 elif a1_ss: return -1 elif a2_ss: return 1 else: return 0 if case: subevent.case_id = case.case_id subevent.save() # Process structured sms actions first actions = sorted(survey_keyword.keywordaction_set.all(), cmp=cmp_fcn) for survey_keyword_action in actions: if survey_keyword_action.recipient == KeywordAction.RECIPIENT_SENDER: contact = sender elif survey_keyword_action.recipient == KeywordAction.RECIPIENT_OWNER: if is_commcarecase(sender): contact = get_wrapped_owner(get_owner_id(sender)) else: contact = None elif survey_keyword_action.recipient == KeywordAction.RECIPIENT_USER_GROUP: try: contact = Group.get(survey_keyword_action.recipient_id) assert contact.doc_type == "Group" assert contact.domain == verified_number.domain except Exception: contact = None else: contact = None if contact is None: continue # contact can be either a user, case, group, or location if survey_keyword_action.action in (KeywordAction.ACTION_SMS, KeywordAction.ACTION_SMS_SURVEY): if isinstance(contact, Group): recipients = list(ScheduleInstance.expand_group(contact)) elif isinstance(contact, SQLLocation): recipients = list(ScheduleInstance.expand_location_ids(contact.domain, [contact.location_id])) else: recipients = [contact] recipient_is_sender = survey_keyword_action.recipient == KeywordAction.RECIPIENT_SENDER if survey_keyword_action.action == KeywordAction.ACTION_SMS: content = SMSContent(message={'*': survey_keyword_action.message_content}) content.set_context(case=case) elif survey_keyword_action.action == KeywordAction.ACTION_SMS_SURVEY: content = SMSSurveyContent( form_unique_id=survey_keyword_action.form_unique_id, expire_after=SQLXFormsSession.MAX_SESSION_LENGTH, ) content.set_context( case=case, critical_section_already_acquired=recipient_is_sender, ) else: raise ValueError("Unexpected action %s" % survey_keyword_action.action) for recipient in recipients: phone_entry = verified_number if recipient_is_sender else None content.send(recipient, logged_event, phone_entry=phone_entry) elif survey_keyword_action.action == KeywordAction.ACTION_STRUCTURED_SMS: res = handle_structured_sms(survey_keyword, survey_keyword_action, sender, verified_number, text, send_response=True, msg=msg, case=case, text_args=args, logged_event=logged_event) if not res: # If the structured sms processing wasn't successful, don't # process any of the other actions return logged_event.completed()
def recipient_is_an_individual_contact(recipient): return ( isinstance(recipient, (CommCareUser, WebUser)) or is_commcarecase(recipient) )
def process_survey_keyword_actions(verified_number, survey_keyword, text, msg): sender = verified_number.owner case = None args = split_args(text, survey_keyword) logged_event = MessagingEvent.create_from_keyword(survey_keyword, sender) # Log a messaging subevent for the incoming message subevent = logged_event.create_subevent_for_single_sms( msg.couch_recipient_doc_type, msg.couch_recipient, completed=True ) add_msg_tags(msg, MessageMetadata(messaging_subevent_id=subevent.pk)) # Close any open sessions even if it's just an sms that we're # responding with. SQLXFormsSession.close_all_open_sms_sessions(verified_number.domain, verified_number.owner_id) if is_commcarecase(sender): case = sender args = args[1:] elif isinstance(sender, CommCareUser): if keyword_uses_form_that_requires_case(survey_keyword): if len(args) > 1: external_id = args[1] case, matches = get_case_by_external_id(verified_number.domain, external_id, sender) if matches == 0: send_keyword_response(verified_number, MSG_CASE_NOT_FOUND, logged_event) logged_event.error(MessagingEvent.ERROR_CASE_EXTERNAL_ID_NOT_FOUND) return elif matches > 1: send_keyword_response(verified_number, MSG_MULTIPLE_CASES_FOUND, logged_event) logged_event.error(MessagingEvent.ERROR_MULTIPLE_CASES_WITH_EXTERNAL_ID_FOUND) return else: send_keyword_response(verified_number, MSG_MISSING_EXTERNAL_ID, logged_event) logged_event.error(MessagingEvent.ERROR_NO_EXTERNAL_ID_GIVEN) return args = args[2:] else: args = args[1:] def cmp_fcn(a1, a2): a1_ss = (a1.action == METHOD_STRUCTURED_SMS) a2_ss = (a2.action == METHOD_STRUCTURED_SMS) if a1_ss and a2_ss: return 0 elif a1_ss: return -1 elif a2_ss: return 1 else: return 0 if case: subevent.case_id = case.case_id subevent.save() # Process structured sms actions first actions = sorted(survey_keyword.actions, cmp=cmp_fcn) for survey_keyword_action in actions: if survey_keyword_action.recipient == RECIPIENT_SENDER: contact = sender elif survey_keyword_action.recipient == RECIPIENT_OWNER: if is_commcarecase(sender): contact = get_wrapped_owner(get_owner_id(sender)) else: contact = None elif survey_keyword_action.recipient == RECIPIENT_USER_GROUP: try: contact = Group.get(survey_keyword_action.recipient_id) assert contact.doc_type == "Group" assert contact.domain == verified_number.domain except Exception: contact = None else: contact = None if contact is None: continue if survey_keyword_action.action == METHOD_SMS: create_immediate_reminder(contact, METHOD_SMS, reminder_type=REMINDER_TYPE_KEYWORD_INITIATED, message=survey_keyword_action.message_content, case=case, logged_event=logged_event) elif survey_keyword_action.action == METHOD_SMS_SURVEY: create_immediate_reminder(contact, METHOD_SMS_SURVEY, reminder_type=REMINDER_TYPE_KEYWORD_INITIATED, form_unique_id=survey_keyword_action.form_unique_id, case=case, logged_event=logged_event) elif survey_keyword_action.action == METHOD_STRUCTURED_SMS: res = handle_structured_sms(survey_keyword, survey_keyword_action, sender, verified_number, text, send_response=True, msg=msg, case=case, text_args=args, logged_event=logged_event) if not res: # If the structured sms processing wasn't successful, don't # process any of the other actions return logged_event.completed()