Ejemplo n.º 1
0
    def post(self, request, *args, **kwargs):
        if self.async_response is not None:
            return self.async_response

        if self.message_form.is_valid():
            # TODO editing should not create a new one
            values = self.message_form.cleaned_data
            if values['send_frequency'] == 'immediately':
                if values['translate']:
                    messages = {}
                    for lang in self.project_languages:
                        key = 'message_%s' % lang
                        if key in values:
                            messages[lang] = values[key]
                    content = SMSContent(message=messages)
                else:
                    content = SMSContent(message={'*': values['non_translated_message']})
                with transaction.atomic():
                    schedule = AlertSchedule.create_simple_alert(self.domain, content)
                    broadcast = ImmediateBroadcast(
                        domain=self.domain, name=values['schedule_name'], schedule=schedule
                    )
                    broadcast.save()
                recipients = [('CommCareUser', r_id) for r_id in values['recipients']]
                refresh_alert_schedule_instances.delay(schedule, recipients)

            return HttpResponseRedirect(reverse(BroadcastListView.urlname, args=[self.domain]))
        return self.get(request, *args, **kwargs)
Ejemplo n.º 2
0
    def test_expand_location_recipients(self):
        schedule_without_descendants = TimedSchedule.create_simple_daily_schedule(
            self.domain, TimedEvent(time=time(9, 0)),
            SMSContent(message={'en': 'Hello'}))
        schedule_without_descendants.include_descendant_locations = False
        schedule_without_descendants.save()

        schedule_with_descendants = TimedSchedule.create_simple_daily_schedule(
            self.domain, TimedEvent(time=time(9, 0)),
            SMSContent(message={'en': 'Hello'}))
        schedule_with_descendants.include_descendant_locations = True
        schedule_with_descendants.save()

        instance = CaseTimedScheduleInstance(
            domain=self.domain,
            timed_schedule_id=schedule_without_descendants.schedule_id,
            recipient_type='Location',
            recipient_id=self.city_location.location_id)
        self.assertEqual(self.user_ids(instance.expand_recipients()),
                         [self.mobile_user.get_id])

        instance = CaseTimedScheduleInstance(
            domain=self.domain,
            timed_schedule_id=schedule_without_descendants.schedule_id,
            recipient_type='Location',
            recipient_id=self.state_location.location_id)
        self.assertEqual(list(instance.expand_recipients()), [])

        instance = CaseTimedScheduleInstance(
            domain=self.domain,
            timed_schedule_id=schedule_with_descendants.schedule_id,
            recipient_type='Location',
            recipient_id=self.state_location.location_id)
        self.assertEqual(self.user_ids(instance.expand_recipients()),
                         [self.mobile_user.get_id])
Ejemplo n.º 3
0
 def setUp(self):
     self._translated_rule = self._add_rule(
         SMSContent(message={
             'en': 'Diamonds and Rust',
             'es': 'Diamantes y Óxido',
         }))
     self._untranslated_rule = self._add_rule(
         SMSContent(message={
             '*': 'Joan',
         }))
     self._email_rule = self._add_rule(
         EmailContent(
             subject={'*': 'You just won something'},
             message={'*': 'This is a scam'},
         ))
    def test_run_messaging_rule(self, task_patch):
        schedule = AlertSchedule.create_simple_alert(
            self.domain,
            SMSContent(message={'en': 'Hello'})
        )

        rule = create_empty_rule(self.domain, AutomaticUpdateRule.WORKFLOW_SCHEDULING)

        rule.add_action(
            CreateScheduleInstanceActionDefinition,
            alert_schedule_id=schedule.schedule_id,
            recipients=(('Self', None),),
        )

        AutomaticUpdateRule.clear_caches(self.domain, AutomaticUpdateRule.WORKFLOW_SCHEDULING)

        with create_case(self.domain, 'person') as case1, create_case(self.domain, 'person') as case2:
            run_messaging_rule(self.domain, rule.pk)
            self.assertEqual(task_patch.call_count, 2)
            task_patch.assert_has_calls(
                [
                    call(self.domain, case1.case_id, rule.pk),
                    call(self.domain, case2.case_id, rule.pk),
                ],
                any_order=True
            )
Ejemplo n.º 5
0
    def test_web_user_recipient_with_user_data_filter(self):
        schedule = TimedSchedule.create_simple_daily_schedule(
            self.domain,
            TimedEvent(time=time(9, 0)),
            SMSContent(message={'en': 'Hello'})
        )
        schedule.user_data_filter = {'role': ['nurse']}
        schedule.save()

        instance = CaseTimedScheduleInstance(
            domain=self.domain,
            timed_schedule_id=schedule.schedule_id,
            recipient_type=ScheduleInstance.RECIPIENT_TYPE_WEB_USER,
            recipient_id=self.web_user.get_id,
        )
        self.assertEqual(list(instance.expand_recipients()), [])

        instance = CaseTimedScheduleInstance(
            domain=self.domain,
            timed_schedule_id=schedule.schedule_id,
            recipient_type=ScheduleInstance.RECIPIENT_TYPE_WEB_USER,
            recipient_id=self.web_user2.get_id,
        )
        recipients = list(instance.expand_recipients())
        self.assertEqual(len(recipients), 1)
        self.assertIsInstance(recipients[0], WebUser)
        self.assertEqual(recipients[0].get_id, self.web_user2.get_id)
Ejemplo n.º 6
0
    def test_case_group_recipient_with_user_data_filter(self):
        # The user data filter should have no effect here because all
        # the recipients are cases.

        schedule = TimedSchedule.create_simple_daily_schedule(
            self.domain,
            TimedEvent(time=time(9, 0)),
            SMSContent(message={'en': 'Hello'})
        )
        schedule.user_data_filter = {'role': ['nurse']}
        schedule.save()

        with create_case(self.domain, 'person') as case:
            case_group = CommCareCaseGroup(domain=self.domain, cases=[case.case_id])
            case_group.save()
            self.addCleanup(case_group.delete)

            instance = CaseTimedScheduleInstance(
                domain=self.domain,
                timed_schedule_id=schedule.schedule_id,
                recipient_type=ScheduleInstance.RECIPIENT_TYPE_CASE_GROUP,
                recipient_id=case_group.get_id,
            )
            recipients = list(instance.expand_recipients())
            self.assertEqual(len(recipients), 1)
            self.assertEqual(recipients[0].case_id, case.case_id)
Ejemplo n.º 7
0
    def test_sms_content(self):
        from corehq.messaging.scheduling.models import AlertSchedule, SMSContent, AlertEvent
        from corehq.messaging.scheduling.scheduling_partitioned.dbaccessors import \
            delete_alert_schedule_instances_for_schedule

        schedule = AlertSchedule.create_simple_alert(self.domain, SMSContent())

        schedule.set_custom_alert(
            [
                (AlertEvent(minutes_to_wait=5), SMSContent()),
                (AlertEvent(minutes_to_wait=15), SMSContent()),
            ]
        )

        self.addCleanup(lambda: delete_alert_schedule_instances_for_schedule(AlertScheduleInstance, schedule.schedule_id))
        self._dump_and_load(Counter({AlertSchedule: 1, AlertEvent: 2, SMSContent: 2}))
Ejemplo n.º 8
0
 def setUpClass(cls):
     super(AlertTest, cls).setUpClass()
     cls.domain = 'alert-test'
     cls.domain_obj = Domain(name=cls.domain, default_timezone='America/New_York')
     cls.domain_obj.save()
     cls.user1 = CommCareUser.create(cls.domain, 'user1', 'password')
     cls.user2 = CommCareUser.create(cls.domain, 'user2', 'password')
     cls.schedule = AlertSchedule.create_simple_alert(cls.domain, SMSContent())
Ejemplo n.º 9
0
 def setUpClass(cls):
     super(DailyScheduleTest, cls).setUpClass()
     cls.schedule = TimedSchedule.create_simple_daily_schedule(
         cls.domain,
         time(12, 0),
         SMSContent(),
         total_iterations=2,
     )
def get_content(handler, event, translated=True):
    if handler.method == METHOD_SMS:
        check_days_until(event.message)
        if translated:
            return SMSContent(message=event.message)
        else:
            return SMSContent(
                message={'*': get_single_dict_value(event.message)})
    elif handler.method == METHOD_EMAIL:
        check_days_until(event.subject)
        check_days_until(event.message)
        if translated:
            return EmailContent(subject=event.subject, message=event.message)
        else:
            return EmailContent(
                subject={'*': get_single_dict_value(event.subject)},
                message={'*': get_single_dict_value(event.message)},
            )
    elif handler.method == METHOD_SMS_SURVEY:
        if event.callback_timeout_intervals:
            if handler.submit_partial_forms:
                expire_after = sum(event.callback_timeout_intervals)
                reminder_intervals = event.callback_timeout_intervals[:-1]
            else:
                expire_after = SQLXFormsSession.MAX_SESSION_LENGTH
                reminder_intervals = 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

        return SMSSurveyContent(
            form_unique_id=event.form_unique_id,
            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,
        )
    else:
        raise ValueError("Unexpected method '%s'" % handler.method)
Ejemplo n.º 11
0
 def setUpClass(cls):
     super(StartDayOfWeekTest, cls).setUpClass()
     cls.schedule = TimedSchedule.create_simple_daily_schedule(
         cls.domain,
         time(12, 0),
         SMSContent(),
         total_iterations=2,
         start_day_of_week=TimedSchedule.MONDAY,
     )
    def test_timed_schedule_instance_creation(self, utcnow_patch):
        schedule = TimedSchedule.create_simple_daily_schedule(
            self.domain,
            TimedEvent(time=time(9, 0)),
            SMSContent(message={'en': 'Hello'})
        )

        rule = create_empty_rule(self.domain, AutomaticUpdateRule.WORKFLOW_SCHEDULING)

        rule.add_criteria(
            MatchPropertyDefinition,
            property_name='start_sending',
            property_value='Y',
            match_type=MatchPropertyDefinition.MATCH_EQUAL,
        )

        rule.add_action(
            CreateScheduleInstanceActionDefinition,
            timed_schedule_id=schedule.schedule_id,
            recipients=(('CommCareUser', self.user.get_id),)
        )

        AutomaticUpdateRule.clear_caches(self.domain, AutomaticUpdateRule.WORKFLOW_SCHEDULING)

        utcnow_patch.return_value = datetime(2017, 5, 1, 7, 0)
        with create_case(self.domain, 'person') as case:
            # Rule does not match, no instances created
            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 0)

            # Make the rule match. On the first iteration, the instance is created. On the second,
            # no new instance is created since it already exists.
            for minute in [1, 2]:
                utcnow_patch.return_value = datetime(2017, 5, 1, 7, minute)
                update_case(self.domain, case.case_id, case_properties={'start_sending': 'Y'})

                instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
                self.assertEqual(instances.count(), 1)

                self.assertEqual(instances[0].case_id, case.case_id)
                self.assertEqual(instances[0].rule_id, rule.pk)
                self.assertEqual(instances[0].timed_schedule_id, schedule.schedule_id)
                self.assertEqual(instances[0].start_date, date(2017, 5, 1))
                self.assertEqual(instances[0].domain, self.domain)
                self.assertEqual(instances[0].recipient_type, 'CommCareUser')
                self.assertEqual(instances[0].recipient_id, self.user.get_id)
                self.assertEqual(instances[0].current_event_num, 0)
                self.assertEqual(instances[0].schedule_iteration_num, 1)
                self.assertEqual(instances[0].next_event_due, datetime(2017, 5, 1, 13, 0))
                self.assertTrue(instances[0].active)

            # Make the rule not match. Instance should no longer exist.
            utcnow_patch.return_value = datetime(2017, 5, 1, 7, 3)
            update_case(self.domain, case.case_id, case_properties={'start_sending': 'N'})
            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 0)
Ejemplo n.º 13
0
 def test_expand_group_recipients(self):
     schedule = TimedSchedule.create_simple_daily_schedule(
         self.domain, TimedEvent(time=time(9, 0)),
         SMSContent(message={'en': 'Hello'}))
     instance = CaseTimedScheduleInstance(
         domain=self.domain,
         timed_schedule_id=schedule.schedule_id,
         recipient_type='Group',
         recipient_id=self.group.get_id)
     self.assertEqual(self.user_ids(instance.expand_recipients()),
                      [self.mobile_user.get_id])
Ejemplo n.º 14
0
    def setUp(self):
        super(DeactivateScheduleTest, self).setUp()
        self.domain_1 = 'deactivate-schedules-1'
        self.domain_obj_1 = Domain(name=self.domain_1)
        self.domain_obj_1.save()
        self.domain_2 = 'deactivate-schedules-2'
        self.domain_obj_2 = Domain(name=self.domain_2)
        self.domain_obj_2.save()

        self.domain_1_sms_schedules = [
            self.create_scheduled_broadcast(self.domain_1, SMSContent()),
            self.create_immediate_broadcast(self.domain_1, SMSContent()),
            self.create_conditional_alert(self.domain_1, SMSContent()),
        ]

        self.domain_1_survey_schedules = [
            self.create_scheduled_broadcast(self.domain_1, self.create_survey_content()),
            self.create_immediate_broadcast(self.domain_1, self.create_survey_content()),
            self.create_conditional_alert(self.domain_1, self.create_survey_content()),
        ]

        self.domain_2_sms_schedules = [
            self.create_scheduled_broadcast(self.domain_2, SMSContent()),
            self.create_immediate_broadcast(self.domain_2, SMSContent()),
            self.create_conditional_alert(self.domain_2, SMSContent()),
        ]

        self.domain_2_survey_schedules = [
            self.create_scheduled_broadcast(self.domain_2, self.create_survey_content()),
            self.create_immediate_broadcast(self.domain_2, self.create_survey_content()),
            self.create_conditional_alert(self.domain_2, self.create_survey_content()),
        ]
Ejemplo n.º 15
0
 def setUpClass(cls):
     super(DailyScheduleTest, cls).setUpClass()
     cls.domain = 'scheduling-test'
     cls.domain_obj = Domain(name=cls.domain, default_timezone='America/New_York')
     cls.domain_obj.save()
     cls.user1 = CommCareUser.create(cls.domain, 'user1', 'password')
     cls.user2 = CommCareUser.create(cls.domain, 'user2', 'password')
     cls.schedule = TimedSchedule.create_simple_daily_schedule(
         cls.domain,
         time(12, 0),
         SMSContent(),
         total_iterations=2,
     )
Ejemplo n.º 16
0
    def test_mobile_worker_recipients_with_user_data_filter(self):
        schedule = TimedSchedule.create_simple_daily_schedule(
            self.domain, TimedEvent(time=time(9, 0)),
            SMSContent(message={'en': 'Hello'}))
        schedule.user_data_filter = {'role': ['nurse']}
        schedule.save()

        instance = CaseTimedScheduleInstance(
            domain=self.domain,
            timed_schedule_id=schedule.schedule_id,
            recipient_type='Group',
            recipient_id=self.group2.get_id)
        self.assertEqual(self.user_ids(instance.expand_recipients()),
                         [self.mobile_user4.get_id, self.mobile_user5.get_id])
Ejemplo n.º 17
0
    def test_expand_location_recipients_with_location_type_filter(self):
        schedule = TimedSchedule.create_simple_daily_schedule(
            self.domain, TimedEvent(time=time(9, 0)),
            SMSContent(message={'en': 'Hello'}))
        schedule.include_descendant_locations = True
        schedule.location_type_filter = [self.city_location.location_type_id]
        schedule.save()

        instance = CaseTimedScheduleInstance(
            domain=self.domain,
            timed_schedule_id=schedule.schedule_id,
            recipient_type='Location',
            recipient_id=self.country_location.location_id)
        self.assertItemsEqual(self.user_ids(instance.expand_recipients()),
                              [self.mobile_user.get_id])
Ejemplo n.º 18
0
def create_beneficiary_indicator_1(domain):
    with transaction.atomic():
        schedule = TimedSchedule.create_simple_daily_schedule(
            domain,
            TimedEvent(time=time(9, 0)),
            SMSContent(
                message={
                    u'en':
                    u'{case.host.name} is severely malnourished. Please consult the Anganwadi for advice in the next visit',
                    u'hin':
                    u'{case.host.name} \u0917\u093e\u0902\u092d\u0940\u0930 \u0930\u0942\u092a \u0938\u0947 \u0915\u0941\u092a\u094b\u0937\u093f\u0924 \u0939\u0948\u0902 | \u0915\u0943\u092a\u093e \u0905\u0917\u0932\u0947 \u0917\u094d\u0930\u0939 \u092d\u094d\u0930\u092e\u0923 \u092e\u0947\u0902 \u0906\u0901\u0917\u0928\u0935\u093e\u095c\u0940 \u0938\u0947 \u092a\u0930\u093e\u092e\u0930\u094d\u0936 \u0915\u0930 \u0938\u0932\u093e\u0939 \u092a\u094d\u0930\u093e\u092a\u094d\u0924  \u0915\u0930\u0947 |',
                    u'tel':
                    u'{case.host.name} \u0c24\u0c40\u0c35\u0c4d\u0c30\u0c2e\u0c48\u0c28 \u0c15\u0c41\u0c2a\u0c4b\u0c37\u0c23\u0c32\u0c4b \u0c09\u0c28\u0c4d\u0c28\u0c3e\u0c30\u0c41. \u0c08 \u0c38\u0c3e\u0c30\u0c3f \u0c05\u0c02\u0c17\u0c28\u0c4d \u0c35\u0c3e\u0c21\u0c40 \u0c38\u0c46\u0c02\u0c1f\u0c30\u0c41\u0c15\u0c41 \u0c35\u0c46\u0c33\u0c4d\u0c33\u0c3f\u0c28\u0c2a\u0c4d\u0c2a\u0c41\u0c21\u0c41 \u0c24\u0c17\u0c41 \u0c38\u0c32\u0c39\u0c3e \u0c15\u0c4a\u0c30\u0c15\u0c41 \u0c15\u0c3e\u0c30\u0c4d\u0c2f\u0c15\u0c30\u0c4d\u0c24\u0c28\u0c41 \u0c38\u0c02\u0c2a\u0c4d\u0c30\u0c26\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f.',
                }),
            total_iterations=1)
        schedule.default_language_code = 'hin'
        schedule.custom_metadata = {'icds_indicator': 'beneficiary_1'}
        schedule.save()

        rule = AutomaticUpdateRule.objects.create(
            domain=domain,
            name="Beneficiary #1: Z-Score Grading Indicator",
            case_type='child_health',
            active=True,
            deleted=False,
            filter_on_server_modified=False,
            server_modified_boundary=None,
            migrated=True,
            workflow=AutomaticUpdateRule.WORKFLOW_SCHEDULING,
        )
        rule.add_criteria(
            MatchPropertyDefinition,
            property_name='zscore_grading_wfa',
            property_value='red',
            match_type=MatchPropertyDefinition.MATCH_EQUAL,
        )
        rule.add_action(
            CreateScheduleInstanceActionDefinition,
            timed_schedule_id=schedule.schedule_id,
            recipients=(('CustomRecipient',
                         'ICDS_MOTHER_PERSON_CASE_FROM_CHILD_HEALTH_CASE'), ),
            reset_case_property_name='last_date_gmp',
        )
 def _create_models(self, event_date):
     event = MessagingEvent.objects.create(
         domain=self.domain,
         date=event_date,
         source=MessagingEvent.SOURCE_OTHER,
         source_id='other',
         content_type=MessagingEvent.CONTENT_SMS,
         app_id=None,
         form_unique_id=None,
         form_name=None,
         status=MessagingEvent.STATUS_IN_PROGRESS,
         recipient_type=MessagingEvent.RECIPIENT_UNKNOWN,
         recipient_id=None)
     for _ in range(10):
         subevent = event.create_subevent_from_contact_and_content(
             self.mobile_user, SMSContent(message={'en': 'Hello'}), None)
         SMS.objects.create(domain=self.domain,
                            phone_number="12345",
                            direction=OUTGOING,
                            date=datetime.utcnow(),
                            backend_id=None,
                            location_id=None,
                            text="test",
                            messaging_subevent=subevent)
 def setUp(self):
     self._rules = {
         self.EMAIL_RULE:
         self._add_daily_rule(
             EmailContent(
                 subject={'*': 'You just won something'},
                 message={'*': 'This is a scam'},
             )),
         self.LOCKED_RULE:
         self._add_immediate_rule(
             SMSContent(message={
                 '*': 'Fool That I Am',
             })),
         self.UNTRANSLATED_IMMEDIATE_RULE:
         self._add_immediate_rule(SMSContent(message={
             '*': 'Joni',
         })),
         self.UNTRANSLATED_DAILY_RULE:
         self._add_daily_rule(SMSContent(message={
             '*': 'Joan',
         })),
         self.IMMEDIATE_RULE:
         self._add_immediate_rule(
             SMSContent(message={
                 '*': 'Car on a Hill',
             })),
         self.DAILY_RULE:
         self._add_daily_rule(
             SMSContent(message={
                 'en': 'Diamonds and Rust',
                 'es': 'Diamantes y Óxido',
             })),
         self.WEEKLY_RULE:
         self._add_weekly_rule(
             SMSContent(message={
                 'en': 'It\'s Too Late',
                 'es': 'Es Demasiado Tarde',
             })),
         self.MONTHLY_RULE:
         self._add_monthly_rule(
             SMSContent(message={
                 'en': 'Both Sides Now',
                 'es': 'Ahora Ambos Lados',
             })),
         self.CUSTOM_DAILY_RULE:
         self._add_custom_daily_rule([
             SMSContent(message={'*': 'Just Like This Train'}),
             SMSContent(message={'*': 'Free Man in Paris'}),
         ]),
         self.CUSTOM_IMMEDIATE_RULE:
         self._add_custom_immediate_rule([
             SMSContent(message={
                 'en': 'Paper Bag',
                 'es': 'Bolsa de Papel',
             }),
             SMSContent(message={
                 'en': 'A Mistake',
                 'es': 'Un Error',
             }),
         ]),
         self.CUSTOM_RULE_BOTH_SHEETS:
         self._add_custom_immediate_rule([
             SMSContent(message={
                 'en': "I'm Lucky",
                 'es': "Tengo Suerte",
             }),
             SMSContent(message={
                 '*': 'Down to Zero',
             }),
             SMSContent(message={
                 'en': 'Me Myself I',
                 'es': 'Yo Mí Mismo Yo',
             }),
             SMSContent(message={
                 '*': 'Rosie',
             }),
         ])
     }
     locked_rule = self._get_rule(self.LOCKED_RULE)
     locked_rule.locked_for_editing = True
     locked_rule.save()
    def handle(self, domain, filename, **options):
        domain_obj = Domain.get_by_name(domain)
        if domain_obj is None:
            raise CommandError("Project space '%s' not found" % domain)

        if not project_is_on_new_reminders(domain_obj):
            raise CommandError(
                "Project space '%s' does not have new reminders enabled" %
                domain)

        json_rules = []
        with open_for_json_read(filename) as f:
            for line in f:
                json_rules.append(json.loads(line))

        print("Importing %s rules..." % len(json_rules))

        rules = []
        with transaction.atomic():
            for entry in json_rules:
                json_rule = SimpleSchedulingRule(entry['rule'])

                schedule_type = entry['schedule']['schedule_type']
                if schedule_type == SIMPLE_SMS_DAILY_SCHEDULE_WITH_TIME:
                    json_schedule = SimpleSMSDailyScheduleWithTime(
                        entry['schedule'])
                    schedule = TimedSchedule.create_simple_daily_schedule(
                        domain,
                        TimedEvent(time=json_schedule.time),
                        SMSContent(message=json_schedule.message),
                        total_iterations=json_schedule.total_iterations,
                        start_offset=json_schedule.start_offset,
                        start_day_of_week=json_schedule.start_day_of_week,
                        extra_options=json_schedule.extra_options.to_json(),
                        repeat_every=json_schedule.repeat_every,
                    )
                elif schedule_type == SIMPLE_SMS_ALERT_SCHEDULE:
                    json_schedule = SimpleSMSAlertSchedule(entry['schedule'])
                    schedule = AlertSchedule.create_simple_alert(
                        domain,
                        SMSContent(message=json_schedule.message),
                        extra_options=json_schedule.extra_options.to_json(),
                    )
                else:
                    raise CommandError("Unexpected schedule_type: %s" %
                                       schedule_type)

                rule = AutomaticUpdateRule.objects.create(
                    domain=domain,
                    name=json_rule.name,
                    case_type=json_rule.case_type,
                    active=True,
                    filter_on_server_modified=False,
                    workflow=AutomaticUpdateRule.WORKFLOW_SCHEDULING,
                )

                for criterion in json_rule.criteria:
                    rule.add_criteria(
                        MatchPropertyDefinition,
                        property_name=criterion.property_name,
                        property_value=criterion.property_value,
                        match_type=criterion.match_type,
                    )

                rule.add_action(
                    CreateScheduleInstanceActionDefinition,
                    alert_schedule_id=schedule.schedule_id if isinstance(
                        schedule, AlertSchedule) else None,
                    timed_schedule_id=schedule.schedule_id if isinstance(
                        schedule, TimedSchedule) else None,
                    recipients=json_rule.recipients,
                    reset_case_property_name=json_rule.
                    reset_case_property_name,
                    start_date_case_property=json_rule.
                    start_date_case_property,
                    specific_start_date=json_rule.specific_start_date,
                    scheduler_module_info=json_rule.scheduler_module_info.
                    to_json(),
                )

                rules.append(rule)

        print("Import complete. Starting instance refresh tasks...")

        for rule in rules:
            initiate_messaging_rule_run(rule.domain, rule.pk)

        print("Done.")
Ejemplo n.º 22
0
        # 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:
    def test_alert_schedule_reset(self, utcnow_patch):
        schedule = AlertSchedule.create_simple_alert(
            self.domain,
            SMSContent(message={'en': 'Hello'})
        )

        rule = create_empty_rule(self.domain, AutomaticUpdateRule.WORKFLOW_SCHEDULING)

        rule.add_criteria(
            MatchPropertyDefinition,
            property_name='start_sending',
            property_value='Y',
            match_type=MatchPropertyDefinition.MATCH_EQUAL,
        )

        rule.add_action(
            CreateScheduleInstanceActionDefinition,
            alert_schedule_id=schedule.schedule_id,
            recipients=(('CommCareUser', self.user.get_id),),
            reset_case_property_name='reset_property',
        )

        AutomaticUpdateRule.clear_caches(self.domain, AutomaticUpdateRule.WORKFLOW_SCHEDULING)

        utcnow_patch.return_value = datetime(2017, 5, 1, 7, 0)
        with create_case(self.domain, 'person') as case:
            # Rule does not match, no instances created
            instances = get_case_alert_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 0)

            # Make the rule match. On the first iteration, the instance is created. On the second,
            # nothing is changed.
            for minute in (1, 2):
                utcnow_patch.return_value = datetime(2017, 5, 1, 7, minute)
                update_case(self.domain, case.case_id,
                    case_properties={'start_sending': 'Y', 'reset_property': 'a'})

                instances = get_case_alert_schedule_instances_for_schedule(case.case_id, schedule)
                self.assertEqual(instances.count(), 1)

                self.assertEqual(instances[0].case_id, case.case_id)
                self.assertEqual(instances[0].rule_id, rule.pk)
                self.assertEqual(instances[0].alert_schedule_id, schedule.schedule_id)
                self.assertEqual(instances[0].domain, self.domain)
                self.assertEqual(instances[0].recipient_type, 'CommCareUser')
                self.assertEqual(instances[0].recipient_id, self.user.get_id)
                self.assertEqual(instances[0].current_event_num, 0)
                self.assertEqual(instances[0].schedule_iteration_num, 1)
                self.assertEqual(instances[0].next_event_due, datetime(2017, 5, 1, 7, 1))
                self.assertEqual(instances[0].last_reset_case_property_value, 'a')
                self.assertTrue(instances[0].active)

            # Update the reset property, and the instance is reset.
            utcnow_patch.return_value = datetime(2017, 6, 1, 7, 0)
            update_case(self.domain, case.case_id, case_properties={'reset_property': 'b'})

            instances = get_case_alert_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 1)

            self.assertEqual(instances[0].case_id, case.case_id)
            self.assertEqual(instances[0].rule_id, rule.pk)
            self.assertEqual(instances[0].alert_schedule_id, schedule.schedule_id)
            self.assertEqual(instances[0].domain, self.domain)
            self.assertEqual(instances[0].recipient_type, 'CommCareUser')
            self.assertEqual(instances[0].recipient_id, self.user.get_id)
            self.assertEqual(instances[0].current_event_num, 0)
            self.assertEqual(instances[0].schedule_iteration_num, 1)
            self.assertEqual(instances[0].next_event_due, datetime(2017, 6, 1, 7, 0))
            self.assertEqual(instances[0].last_reset_case_property_value, 'b')
            self.assertTrue(instances[0].active)
    def test_timed_schedule_case_property_timed_event(self, utcnow_patch):
        schedule = TimedSchedule.create_simple_daily_schedule(
            self.domain,
            CasePropertyTimedEvent(case_property_name='reminder_time'),
            SMSContent(message={'en': 'Hello'})
        )

        rule = create_empty_rule(self.domain, AutomaticUpdateRule.WORKFLOW_SCHEDULING)

        rule.add_criteria(
            MatchPropertyDefinition,
            property_name='start_sending',
            property_value='Y',
            match_type=MatchPropertyDefinition.MATCH_EQUAL,
        )

        rule.add_action(
            CreateScheduleInstanceActionDefinition,
            timed_schedule_id=schedule.schedule_id,
            recipients=(('CommCareUser', self.user.get_id),),
        )

        AutomaticUpdateRule.clear_caches(self.domain, AutomaticUpdateRule.WORKFLOW_SCHEDULING)

        utcnow_patch.return_value = datetime(2017, 5, 1, 7, 0)
        with create_case(self.domain, 'person') as case:
            # Rule does not match, no instances created
            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 0)

            # Make the rule match, but don't give a preferred time. Default scheduling time is used.
            update_case(self.domain, case.case_id, case_properties={'start_sending': 'Y'})
            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 1)

            self.assertEqual(instances[0].case_id, case.case_id)
            self.assertEqual(instances[0].rule_id, rule.pk)
            self.assertEqual(instances[0].timed_schedule_id, schedule.schedule_id)
            self.assertEqual(instances[0].start_date, date(2017, 5, 1))
            self.assertEqual(instances[0].domain, self.domain)
            self.assertEqual(instances[0].recipient_type, 'CommCareUser')
            self.assertEqual(instances[0].recipient_id, self.user.get_id)
            self.assertEqual(instances[0].current_event_num, 0)
            self.assertEqual(instances[0].schedule_iteration_num, 1)
            self.assertEqual(instances[0].next_event_due, datetime(2017, 5, 1, 16, 0))
            self.assertTrue(instances[0].active)

            # Update the preferred time, and the schedule should recalculate
            update_case(self.domain, case.case_id, case_properties={'reminder_time': '09:00'})
            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 1)
            self.assertEqual(instances[0].case_id, case.case_id)
            self.assertEqual(instances[0].rule_id, rule.pk)
            self.assertEqual(instances[0].timed_schedule_id, schedule.schedule_id)
            self.assertEqual(instances[0].start_date, date(2017, 5, 1))
            self.assertEqual(instances[0].domain, self.domain)
            self.assertEqual(instances[0].recipient_type, 'CommCareUser')
            self.assertEqual(instances[0].recipient_id, self.user.get_id)
            self.assertEqual(instances[0].current_event_num, 0)
            self.assertEqual(instances[0].schedule_iteration_num, 1)
            self.assertEqual(instances[0].next_event_due, datetime(2017, 5, 1, 13, 0))
            self.assertTrue(instances[0].active)

            # Update the preferred time to a bad value and the default time is used again.
            update_case(self.domain, case.case_id, case_properties={'reminder_time': 'x'})
            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 1)
            self.assertEqual(instances[0].case_id, case.case_id)
            self.assertEqual(instances[0].rule_id, rule.pk)
            self.assertEqual(instances[0].timed_schedule_id, schedule.schedule_id)
            self.assertEqual(instances[0].start_date, date(2017, 5, 1))
            self.assertEqual(instances[0].domain, self.domain)
            self.assertEqual(instances[0].recipient_type, 'CommCareUser')
            self.assertEqual(instances[0].recipient_id, self.user.get_id)
            self.assertEqual(instances[0].current_event_num, 0)
            self.assertEqual(instances[0].schedule_iteration_num, 1)
            self.assertEqual(instances[0].next_event_due, datetime(2017, 5, 1, 16, 0))
            self.assertTrue(instances[0].active)
    def test_visit_scheduler_integration(self, utcnow_patch, module_and_form_patch):
        schedule = TimedSchedule.create_simple_daily_schedule(
            self.domain,
            TimedEvent(time=time(9, 0)),
            SMSContent(message={'en': 'Hello'}),
            total_iterations=1,
        )

        rule = create_empty_rule(self.domain, AutomaticUpdateRule.WORKFLOW_SCHEDULING)

        _, definition = rule.add_action(
            CreateScheduleInstanceActionDefinition,
            timed_schedule_id=schedule.schedule_id,
            recipients=(('CommCareUser', self.user.get_id),)
        )

        module, form = get_visit_scheduler_module_and_form_for_test()

        definition.set_scheduler_module_info(CreateScheduleInstanceActionDefinition.SchedulerModuleInfo(
            enabled=True,
            app_id='n/a for test',
            form_unique_id=form.unique_id,
            visit_number=1,
            window_position=VISIT_WINDOW_START,
        ))

        definition.save()

        AutomaticUpdateRule.clear_caches(self.domain, AutomaticUpdateRule.WORKFLOW_SCHEDULING)

        utcnow_patch.return_value = datetime(2017, 8, 1, 7, 0)
        module_and_form_patch.return_value = module, form

        with create_case(self.domain, 'person') as case:
            # Schedule phase does not match, nothing is scheduled
            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 0)

            update_case(self.domain, case.case_id,
                case_properties={'add': '2017-08-01', 'current_schedule_phase': '2'})

            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 1)

            self.assertEqual(instances[0].case_id, case.case_id)
            self.assertEqual(instances[0].rule_id, rule.pk)
            self.assertEqual(instances[0].timed_schedule_id, schedule.schedule_id)
            self.assertEqual(instances[0].start_date, date(2017, 8, 6))
            self.assertEqual(instances[0].domain, self.domain)
            self.assertEqual(instances[0].recipient_type, 'CommCareUser')
            self.assertEqual(instances[0].recipient_id, self.user.get_id)
            self.assertEqual(instances[0].current_event_num, 0)
            self.assertEqual(instances[0].schedule_iteration_num, 1)
            self.assertEqual(instances[0].next_event_due, datetime(2017, 8, 6, 13, 0))
            self.assertTrue(instances[0].active)

            # If the anchor date gets updated (due to correction, for example), the schedule recalculates
            update_case(self.domain, case.case_id, case_properties={'add': '2017-08-10'})
            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 1)

            self.assertEqual(instances[0].case_id, case.case_id)
            self.assertEqual(instances[0].rule_id, rule.pk)
            self.assertEqual(instances[0].timed_schedule_id, schedule.schedule_id)
            self.assertEqual(instances[0].start_date, date(2017, 8, 15))
            self.assertEqual(instances[0].domain, self.domain)
            self.assertEqual(instances[0].recipient_type, 'CommCareUser')
            self.assertEqual(instances[0].recipient_id, self.user.get_id)
            self.assertEqual(instances[0].current_event_num, 0)
            self.assertEqual(instances[0].schedule_iteration_num, 1)
            self.assertEqual(instances[0].next_event_due, datetime(2017, 8, 15, 13, 0))
            self.assertTrue(instances[0].active)

            # If the anchor date is in the past, the schedule instance is deactivated
            update_case(self.domain, case.case_id, case_properties={'add': '2017-07-01'})
            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 1)

            self.assertEqual(instances[0].case_id, case.case_id)
            self.assertEqual(instances[0].rule_id, rule.pk)
            self.assertEqual(instances[0].timed_schedule_id, schedule.schedule_id)
            self.assertEqual(instances[0].start_date, date(2017, 7, 6))
            self.assertEqual(instances[0].domain, self.domain)
            self.assertEqual(instances[0].recipient_type, 'CommCareUser')
            self.assertEqual(instances[0].recipient_id, self.user.get_id)
            self.assertEqual(instances[0].current_event_num, 0)
            self.assertEqual(instances[0].schedule_iteration_num, 2)
            self.assertEqual(instances[0].next_event_due, datetime(2017, 7, 7, 13, 0))
            self.assertFalse(instances[0].active)

            # If the anchor date is reset, the schedule instance is reactivated
            update_case(self.domain, case.case_id, case_properties={'add': '2017-08-01'})
            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 1)

            self.assertEqual(instances[0].case_id, case.case_id)
            self.assertEqual(instances[0].rule_id, rule.pk)
            self.assertEqual(instances[0].timed_schedule_id, schedule.schedule_id)
            self.assertEqual(instances[0].start_date, date(2017, 8, 6))
            self.assertEqual(instances[0].domain, self.domain)
            self.assertEqual(instances[0].recipient_type, 'CommCareUser')
            self.assertEqual(instances[0].recipient_id, self.user.get_id)
            self.assertEqual(instances[0].current_event_num, 0)
            self.assertEqual(instances[0].schedule_iteration_num, 1)
            self.assertEqual(instances[0].next_event_due, datetime(2017, 8, 6, 13, 0))
            self.assertTrue(instances[0].active)

            # Making an arbitrary update doesn't cause any recalculating to happen
            with patch('corehq.messaging.scheduling.scheduling_partitioned.models.AbstractTimedScheduleInstance.recalculate_schedule') as recalculate_patch:
                update_case(self.domain, case.case_id, case_properties={'new_property': 'new value'})
                self.assertEqual(recalculate_patch.call_count, 0)

            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 1)

            self.assertEqual(instances[0].case_id, case.case_id)
            self.assertEqual(instances[0].rule_id, rule.pk)
            self.assertEqual(instances[0].timed_schedule_id, schedule.schedule_id)
            self.assertEqual(instances[0].start_date, date(2017, 8, 6))
            self.assertEqual(instances[0].domain, self.domain)
            self.assertEqual(instances[0].recipient_type, 'CommCareUser')
            self.assertEqual(instances[0].recipient_id, self.user.get_id)
            self.assertEqual(instances[0].current_event_num, 0)
            self.assertEqual(instances[0].schedule_iteration_num, 1)
            self.assertEqual(instances[0].next_event_due, datetime(2017, 8, 6, 13, 0))
            self.assertTrue(instances[0].active)

            # Terminate the schedule, no more schedule instances should be scheduled
            update_case(self.domain, case.case_id, case_properties={'current_schedule_phase': '-1'})
            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 0)
    def test_start_offset(self, utcnow_patch):
        schedule = TimedSchedule.create_simple_daily_schedule(
            self.domain,
            TimedEvent(time=time(9, 0)),
            SMSContent(message={'en': 'Hello'}),
            start_offset=2,
        )

        rule = create_empty_rule(self.domain, AutomaticUpdateRule.WORKFLOW_SCHEDULING)

        rule.add_action(
            CreateScheduleInstanceActionDefinition,
            timed_schedule_id=schedule.schedule_id,
            recipients=(('CommCareUser', self.user.get_id),),
        )

        AutomaticUpdateRule.clear_caches(self.domain, AutomaticUpdateRule.WORKFLOW_SCHEDULING)
        utcnow_patch.return_value = datetime(2017, 8, 1, 15, 0)

        with create_case(self.domain, 'person') as case:
            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 1)

            self.assertEqual(instances[0].case_id, case.case_id)
            self.assertEqual(instances[0].rule_id, rule.pk)
            self.assertEqual(instances[0].timed_schedule_id, schedule.schedule_id)
            self.assertEqual(instances[0].start_date, date(2017, 8, 1))
            self.assertEqual(instances[0].domain, self.domain)
            self.assertEqual(instances[0].recipient_type, 'CommCareUser')
            self.assertEqual(instances[0].recipient_id, self.user.get_id)
            self.assertEqual(instances[0].current_event_num, 0)
            self.assertEqual(instances[0].schedule_iteration_num, 1)
            self.assertEqual(instances[0].next_event_due, datetime(2017, 8, 3, 13, 0))
            self.assertEqual(instances[0].schedule_revision, schedule.get_schedule_revision())
            self.assertTrue(instances[0].active)

            # Change the schedule's start offset and force a case update to reprocess the schedule instance.
            # The start date should not change, but the schedule instance should respond to the new start offset
            # by calculating a new next_event_due timestamp.
            schedule.start_offset = 5
            schedule.save()
            schedule = TimedSchedule.objects.get(schedule_id=schedule.schedule_id)
            utcnow_patch.return_value = datetime(2017, 8, 4, 7, 0)
            update_case(self.domain, case.case_id, case_properties={'new_property': 'new value'})

            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 1)

            self.assertEqual(instances[0].case_id, case.case_id)
            self.assertEqual(instances[0].rule_id, rule.pk)
            self.assertEqual(instances[0].timed_schedule_id, schedule.schedule_id)
            self.assertEqual(instances[0].start_date, date(2017, 8, 1))
            self.assertEqual(instances[0].domain, self.domain)
            self.assertEqual(instances[0].recipient_type, 'CommCareUser')
            self.assertEqual(instances[0].recipient_id, self.user.get_id)
            self.assertEqual(instances[0].current_event_num, 0)
            self.assertEqual(instances[0].schedule_iteration_num, 1)
            self.assertEqual(instances[0].next_event_due, datetime(2017, 8, 6, 13, 0))
            self.assertEqual(instances[0].schedule_revision, schedule.get_schedule_revision())
            self.assertTrue(instances[0].active)

            # Making another arbitrary update doesn't cause any recalculating to happen
            with patch('corehq.messaging.scheduling.scheduling_partitioned.models.AbstractTimedScheduleInstance.recalculate_schedule') as recalculate_patch:
                update_case(self.domain, case.case_id, case_properties={'new_property': 'new value 2'})
                self.assertEqual(recalculate_patch.call_count, 0)

            instances = get_case_timed_schedule_instances_for_schedule(case.case_id, schedule)
            self.assertEqual(instances.count(), 1)

            self.assertEqual(instances[0].case_id, case.case_id)
            self.assertEqual(instances[0].rule_id, rule.pk)
            self.assertEqual(instances[0].timed_schedule_id, schedule.schedule_id)
            self.assertEqual(instances[0].start_date, date(2017, 8, 1))
            self.assertEqual(instances[0].domain, self.domain)
            self.assertEqual(instances[0].recipient_type, 'CommCareUser')
            self.assertEqual(instances[0].recipient_id, self.user.get_id)
            self.assertEqual(instances[0].current_event_num, 0)
            self.assertEqual(instances[0].schedule_iteration_num, 1)
            self.assertEqual(instances[0].next_event_due, datetime(2017, 8, 6, 13, 0))
            self.assertEqual(instances[0].schedule_revision, schedule.get_schedule_revision())
            self.assertTrue(instances[0].active)
Ejemplo n.º 27
0
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()