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)
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])
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 )
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)
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)
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}))
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())
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)
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)
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])
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()), ]
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, )
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])
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])
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.")
# 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)
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()