def restore_object(self, attrs, instance=None): """ Create a new broadcast to send out """ if instance: # pragma: no cover raise ValidationError("Invalid operation") user = self.user org = self.org if 'urn' in attrs and attrs['urn']: urns = attrs.get('urn', []) else: urns = attrs.get('phone', []) channel = attrs['channel'] contacts = list() for urn in urns: # treat each urn as a separate contact contacts.append(Contact.get_or_create(user, channel.org, urns=[urn], channel=channel)) # add any contacts specified by uuids uuid_contacts = attrs.get('contact', []) for contact in uuid_contacts: contacts.append(contact) # create the broadcast broadcast = Broadcast.create(org, user, attrs['text'], recipients=contacts) # send it broadcast.send() return broadcast
def save(self): """ Create a new broadcast to send out """ if "urn" in self.validated_data and self.validated_data["urn"]: urns = self.validated_data.get("urn") else: urns = self.validated_data.get("phone", []) channel = self.validated_data.get("channel") contacts = list() for urn in urns: # treat each urn as a separate contact contact, urn_obj = Contact.get_or_create(channel.org, urn, user=self.user) contacts.append(contact) # add any contacts specified by uuids uuid_contacts = self.validated_data.get("contact", []) for contact in uuid_contacts: contacts.append(contact) # create the broadcast broadcast = Broadcast.create( self.org, self.user, self.validated_data["text"], contacts=contacts, channel=channel ) # send it broadcast.send(expressions_context={}) return broadcast
def send_spam(user_id, contact_id): # pragma: no cover """ Processses a single incoming message through our queue. """ from django.contrib.auth.models import User from temba.contacts.models import Contact, TEL_SCHEME from temba.msgs.models import Broadcast contact = Contact.all().get(pk=contact_id) user = User.objects.get(pk=user_id) channel = contact.org.get_send_channel(TEL_SCHEME) if not channel: # pragma: no cover print("Sorry, no channel to be all spammy with") return long_text = ( "Test Message #%d. The path of the righteous man is beset on all sides by the iniquities of the " "selfish and the tyranny of evil men. Blessed is your face." ) # only trigger sync on the last one for idx in range(10): broadcast = Broadcast.create(contact.org, user, long_text % (idx + 1), contacts=[contact]) broadcast.send(trigger_send=(idx == 149))
def save(self): """ Create a new broadcast to send out """ contact_urns = [] for urn in self.validated_data.get("urns", []): # create contacts for URNs if necessary __, contact_urn = Contact.get_or_create(self.context["org"], urn, user=self.context["user"]) contact_urns.append(contact_urn) text, base_language = self.validated_data["text"] # create the broadcast broadcast = Broadcast.create( self.context["org"], self.context["user"], text=text, base_language=base_language, groups=self.validated_data.get("groups", []), contacts=self.validated_data.get("contacts", []), urns=contact_urns, channel=self.validated_data.get("channel"), ) # send in task on_transaction_commit(lambda: send_broadcast_task.delay(broadcast.id)) return broadcast
def test_calculating_next_fire(self): self.org.timezone = 'US/Eastern' self.org.save() tz = self.org.get_tzinfo() eleven_pm_est = datetime(2013, 1, 3, hour=23, minute=0, second=0, microsecond=0).replace(tzinfo=tz) # Test date is 10am on a Thursday, Jan 3rd schedule = self.create_schedule('D', start_date=eleven_pm_est) schedule.save() Broadcast.create(self.org, self.admin, 'Message', [], schedule=schedule) schedule = Schedule.objects.get(pk=schedule.pk) # when is the next fire once our first one passes sched_date = datetime(2013, 1, 3, hour=23, minute=30, second=0, microsecond=0).replace(tzinfo=tz) schedule.update_schedule(sched_date) self.assertEquals('2013-01-04 23:00:00-05:00', unicode(schedule.next_fire))
def test_update_near_day_boundary(self): self.org.timezone = 'US/Eastern' self.org.save() tz = pytz.timezone(self.org.timezone) sched = self.create_schedule('D') Broadcast.create(self.org, self.admin, 'Message', [], schedule=sched) sched = Schedule.objects.get(pk=sched.pk) update_url = reverse('schedules.schedule_update', args=[sched.pk]) self.login(self.admin) # way off into the future start_date = datetime(2050, 1, 3, 23, 0, 0, 0) start_date = tz.localize(start_date) start_date = pytz.utc.normalize(start_date.astimezone(pytz.utc)) post_data = dict() post_data['repeat_period'] = 'D' post_data['start'] = 'later' post_data['start_datetime_value'] = "%d" % time.mktime(start_date.timetuple()) self.client.post(update_url, post_data) sched = Schedule.objects.get(pk=sched.pk) # 11pm in NY should be 4am UTC the next day self.assertEquals('2050-01-04 04:00:00+00:00', unicode(sched.next_fire)) # a time in the past start_date = datetime(2010, 1, 3, 23, 45, 0, 0) start_date = tz.localize(start_date) start_date = pytz.utc.normalize(start_date.astimezone(pytz.utc)) post_data = dict() post_data['repeat_period'] = 'D' post_data['start'] = 'later' post_data['start_datetime_value'] = "%d" % time.mktime(start_date.timetuple()) self.client.post(update_url, post_data) sched = Schedule.objects.get(pk=sched.pk) # next fire should fall at the right hour and minute self.assertIn('04:45:00+00:00', unicode(sched.next_fire))
def test_update_near_day_boundary(self): self.org.timezone = pytz.timezone("US/Eastern") self.org.save() tz = self.org.timezone sched = self.create_schedule("D") Broadcast.create(self.org, self.admin, "Message", schedule=sched, contacts=[self.joe]) sched = Schedule.objects.get(pk=sched.pk) update_url = reverse("schedules.schedule_update", args=[sched.pk]) self.login(self.admin) # way off into the future start_date = datetime(2050, 1, 3, 23, 0, 0, 0) start_date = tz.localize(start_date) start_date = pytz.utc.normalize(start_date.astimezone(pytz.utc)) post_data = dict() post_data["repeat_period"] = "D" post_data["start"] = "later" post_data["start_datetime_value"] = "%d" % time.mktime(start_date.timetuple()) self.client.post(update_url, post_data) sched = Schedule.objects.get(pk=sched.pk) # 11pm in NY should be 4am UTC the next day self.assertEqual("2050-01-04 04:00:00+00:00", str(sched.next_fire)) # a time in the past start_date = datetime(2010, 1, 3, 23, 45, 0, 0) start_date = tz.localize(start_date) start_date = pytz.utc.normalize(start_date.astimezone(pytz.utc)) post_data = dict() post_data["repeat_period"] = "D" post_data["start"] = "later" post_data["start_datetime_value"] = "%d" % time.mktime(start_date.timetuple()) self.client.post(update_url, post_data) sched = Schedule.objects.get(pk=sched.pk) # next fire should fall at the right hour and minute self.assertIn("04:45:00+00:00", str(sched.next_fire))
def test_calculating_next_fire(self): self.org.timezone = pytz.timezone("US/Eastern") self.org.save() tz = self.org.timezone eleven_fifteen_est = tz.localize(datetime(2013, 1, 3, hour=23, minute=15, second=0, microsecond=0)) # Test date is 10:15am on a Thursday, Jan 3rd schedule = self.create_schedule("D", start_date=eleven_fifteen_est) schedule.save() Broadcast.create(self.org, self.admin, "Message", schedule=schedule, contacts=[self.joe]) schedule = Schedule.objects.get(pk=schedule.pk) # when is the next fire once our first one passes sched_date = tz.localize(datetime(2013, 1, 3, hour=23, minute=30, second=0, microsecond=0)) schedule.update_schedule(sched_date) self.assertEqual("2013-01-04 23:15:00-05:00", str(schedule.next_fire))
def test_broadcasts(self): url = reverse('api.v2.broadcasts') self.assertEndpointAccess(url) reporters = self.create_group("Reporters", [self.joe, self.frank]) bcast1 = Broadcast.create(self.org, self.admin, "Hello 1", [self.frank.get_urn('twitter')]) bcast2 = Broadcast.create(self.org, self.admin, "Hello 2", [self.joe]) bcast3 = Broadcast.create(self.org, self.admin, "Hello 3", [self.frank], status='S') bcast4 = Broadcast.create( self.org, self.admin, "Hello 4", [self.frank.get_urn('twitter'), self.joe, reporters], status='F') Broadcast.create(self.org2, self.admin2, "Different org...", [self.hans]) # no filtering with self.assertNumQueries(NUM_BASE_REQUEST_QUERIES + 5): response = self.fetchJSON(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.json['next'], None) self.assertResultsById(response, [bcast4, bcast3, bcast2, bcast1]) self.assertEqual( response.json['results'][0], { 'id': bcast4.pk, 'urns': ["twitter:franky"], 'contacts': [{ 'uuid': self.joe.uuid, 'name': self.joe.name }], 'groups': [{ 'uuid': reporters.uuid, 'name': reporters.name }], 'text': "Hello 4", 'created_on': format_datetime(bcast4.created_on), 'status': "failed" }) # filter by id response = self.fetchJSON(url, 'id=%d' % bcast3.pk) self.assertResultsById(response, [bcast3]) # filter by after response = self.fetchJSON( url, 'after=%s' % format_datetime(bcast3.created_on)) self.assertResultsById(response, [bcast4, bcast3]) # filter by before response = self.fetchJSON( url, 'before=%s' % format_datetime(bcast2.created_on)) self.assertResultsById(response, [bcast2, bcast1])
def test_queue_broadcast(self): jim = self.create_contact("Jim", phone="+12065551212") bobs = self.create_group( "Bobs", [self.create_contact("Bob", phone="+12065551313")]) ticketer = Ticketer.create(self.org, self.admin, "mailgun", "Support Tickets", {}) ticket = self.create_ticket(ticketer, jim, "Help!") bcast = Broadcast.create( self.org, self.admin, { "eng": "Welcome to mailroom!", "spa": "¡Bienvenidx a mailroom!" }, groups=[bobs], contacts=[jim], urns=["tel:+12065556666"], base_language="eng", ticket=ticket, ) bcast.send_async() self.assert_org_queued(self.org, "batch") self.assert_queued_batch_task( self.org, { "type": "send_broadcast", "org_id": self.org.id, "task": { "translations": { "eng": { "text": "Welcome to mailroom!" }, "spa": { "text": "\u00a1Bienvenidx a mailroom!" }, }, "template_state": "legacy", "base_language": "eng", "urns": ["tel:+12065556666"], "contact_ids": [jim.id], "group_ids": [bobs.id], "broadcast_id": bcast.id, "org_id": self.org.id, "ticket_id": ticket.id, }, "queued_on": matchers.ISODate(), }, )
def test_queue_broadcast(self): jim = self.create_contact("Jim", "+12065551212") bobs = self.create_group("Bobs", [self.create_contact("Bob", "+12065551313")]) bcast = Broadcast.create( self.org, self.admin, { "eng": "Welcome to mailroom!", "spa": "¡Bienvenidx a mailroom!" }, groups=[bobs], contacts=[jim], urns=[jim.urns.get()], base_language="eng", ) with override_settings(TESTING=False): bcast.send() self.assert_org_queued(self.org, "batch") self.assert_queued_batch_task( self.org, { "type": "send_broadcast", "org_id": self.org.id, "task": { "translations": { "eng": { "text": "Welcome to mailroom!" }, "spa": { "text": "\u00a1Bienvenidx a mailroom!" }, }, "template_state": "legacy", "base_language": "eng", "urns": ["tel:+12065551212"], "contact_ids": [jim.id], "group_ids": [bobs.id], "broadcast_id": bcast.id, "org_id": self.org.id, }, "queued_on": matchers.ISODate(), }, )
def create_broadcast( self, user, text, contacts=(), groups=(), response_to=None, msg_status=Msg.STATUS_SENT, parent=None, schedule=None, ): bcast = Broadcast.create( self.org, user, text, contacts=contacts, groups=groups, status=Msg.STATUS_SENT, parent=parent, schedule=schedule, ) contacts = set(bcast.contacts.all()) for group in bcast.groups.all(): contacts.update(group.contacts.all()) if not schedule: for contact in contacts: self._create_msg( contact, text, Msg.DIRECTION_OUT, channel=None, msg_type=Msg.TYPE_INBOX, attachments=(), status=msg_status, created_on=timezone.now(), sent_on=timezone.now(), response_to=response_to, broadcast=bcast, ) return bcast
def save(self): """ Create a new broadcast to send out """ from temba.msgs.tasks import send_broadcast_task recipients = self.validated_data.get('contacts', []) + self.validated_data.get('groups', []) for urn in self.validated_data.get('urns', []): # create contacts for URNs if necessary contact = Contact.get_or_create(self.context['org'], self.context['user'], urns=[urn]) contact_urn = contact.urn_objects[urn] recipients.append(contact_urn) # create the broadcast broadcast = Broadcast.create(self.context['org'], self.context['user'], self.validated_data['text'], recipients=recipients, channel=self.validated_data.get('channel')) # send in task send_broadcast_task.delay(broadcast.id) return broadcast
def restore_object(self, attrs, instance=None): """ Create a new broadcast to send out """ if instance: # pragma: no cover raise ValidationError("Invalid operation") user = self.user org = self.org if 'urn' in attrs and attrs['urn']: urns = attrs.get('urn', []) else: urns = attrs.get('phone', []) channel = attrs['channel'] contacts = list() for urn in urns: # treat each urn as a separate contact contacts.append( Contact.get_or_create(user, channel.org, urns=[urn], channel=channel)) # add any contacts specified by uuids uuid_contacts = attrs.get('contact', []) for contact in uuid_contacts: contacts.append(contact) # create the broadcast broadcast = Broadcast.create(org, user, attrs['text'], recipients=contacts) # send it broadcast.send() return broadcast
def send_spam(user_id, contact_id): # pragma: no cover """ Processses a single incoming message through our queue. """ from django.contrib.auth.models import User from temba.contacts.models import Contact, TEL_SCHEME from temba.msgs.models import Broadcast contact = Contact.all().get(pk=contact_id) user = User.objects.get(pk=user_id) channel = contact.org.get_send_channel(TEL_SCHEME) if not channel: # pragma: no cover print("Sorry, no channel to be all spammy with") return long_text = "Test Message #%d. The path of the righteous man is beset on all sides by the iniquities of the " "selfish and the tyranny of evil men. Blessed is your face." # only trigger sync on the last one for idx in range(10): broadcast = Broadcast.create(contact.org, user, long_text % (idx + 1), contacts=[contact]) broadcast.send(trigger_send=(idx == 149))
def setUpBeforeMigration(self, apps): contact1 = self.create_contact("Bob", twitter="bob") favorites = self.get_flow("favorites") # create schedule attached to a trigger self.trigger = Trigger.create(self.org, self.admin, Trigger.TYPE_SCHEDULE, flow=favorites, schedule=create_schedule( self.admin, "D")) # create schedule attached to a broadcast self.broadcast = Broadcast.create(self.org, self.admin, "hi there", contacts=[contact1], schedule=create_schedule( self.admin, "W")) # create orphan schedule create_schedule(self.admin, "M")
def test_broadcasts(self): url = reverse('api.v2.broadcasts') self.assertEndpointAccess(url) reporters = self.create_group("Reporters", [self.joe, self.frank]) bcast1 = Broadcast.create(self.org, self.admin, "Hello 1", [self.frank.get_urn('twitter')]) bcast2 = Broadcast.create(self.org, self.admin, "Hello 2", [self.joe]) bcast3 = Broadcast.create(self.org, self.admin, "Hello 3", [self.frank], status='S') bcast4 = Broadcast.create(self.org, self.admin, "Hello 4", [self.frank.get_urn('twitter'), self.joe, reporters], status='F') Broadcast.create(self.org2, self.admin2, "Different org...", [self.hans]) # no filtering with self.assertNumQueries(NUM_BASE_REQUEST_QUERIES + 5): response = self.fetchJSON(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.json['next'], None) self.assertResultsById(response, [bcast4, bcast3, bcast2, bcast1]) self.assertEqual(response.json['results'][0], { 'id': bcast4.pk, 'urns': ["twitter:franky"], 'contacts': [{'uuid': self.joe.uuid, 'name': self.joe.name}], 'groups': [{'uuid': reporters.uuid, 'name': reporters.name}], 'text': "Hello 4", 'created_on': format_datetime(bcast4.created_on) }) # filter by id response = self.fetchJSON(url, 'id=%d' % bcast3.pk) self.assertResultsById(response, [bcast3]) # filter by after response = self.fetchJSON(url, 'after=%s' % format_datetime(bcast3.created_on)) self.assertResultsById(response, [bcast4, bcast3]) # filter by before response = self.fetchJSON(url, 'before=%s' % format_datetime(bcast2.created_on)) self.assertResultsById(response, [bcast2, bcast1]) with AnonymousOrg(self.org): # URNs shouldn't be included response = self.fetchJSON(url, 'id=%d' % bcast1.pk) self.assertEqual(response.json['results'][0]['urns'], None)
def create_broadcast(self, user, text, contacts=(), groups=(), response_to=None, msg_status=SENT, parent=None, schedule=None): bcast = Broadcast.create(self.org, user, text, contacts=contacts, groups=groups, status=SENT, parent=parent, schedule=schedule) contacts = set(bcast.contacts.all()) for group in bcast.groups.all(): contacts.update(group.contacts.all()) for contact in contacts: self._create_msg( contact, text, OUTGOING, channel=None, msg_type=INBOX, attachments=(), status=msg_status, created_on=timezone.now(), sent_on=timezone.now(), response_to=response_to, broadcast=bcast, ) return bcast
def save(self): """ Create a new broadcast to send out """ recipients = self.validated_data.get('contacts', []) + self.validated_data.get('groups', []) for urn in self.validated_data.get('urns', []): # create contacts for URNs if necessary contact = Contact.get_or_create(self.context['org'], self.context['user'], urns=[urn]) contact_urn = contact.urn_objects[urn] recipients.append(contact_urn) text, base_language = self.validated_data['text'] # create the broadcast broadcast = Broadcast.create(self.context['org'], self.context['user'], text=text, base_language=base_language, recipients=recipients, channel=self.validated_data.get('channel')) # send in task on_transaction_commit(lambda: send_broadcast_task.delay(broadcast.id)) return broadcast
def restore_object(self, attrs, instance=None): """ Create a new broadcast to send out """ from temba.msgs.tasks import send_broadcast_task if instance: # pragma: no cover raise ValidationError("Invalid operation") recipients = attrs.get('contacts') + attrs.get('groups') for urn in attrs.get('urns'): # create contacts for URNs if necessary contact = Contact.get_or_create(self.org, self.user, urns=[urn]) contact_urn = contact.urn_objects[urn] recipients.append(contact_urn) # create the broadcast broadcast = Broadcast.create(self.org, self.user, attrs['text'], recipients=recipients, channel=attrs['channel']) # send in task send_broadcast_task.delay(broadcast.id) return broadcast
def _create_broadcast(self, text, recipients): """ Creates the a single broadcast to the given recipients (which can groups, contacts, URNs) """ return Broadcast.create(self.org, self.user, text, recipients)
def test_messages(self): url = reverse('api.v2.messages') # make sure user rights are correct self.assertEndpointAccess(url, "folder=inbox") # make sure you have to pass in something to filter by response = self.fetchJSON(url) self.assertResponseError(response, None, "You must specify one of the contact, folder, label, broadcast, id parameters") # create some messages joe_msg1 = self.create_msg(direction='I', msg_type='F', text="Howdy", contact=self.joe) frank_msg1 = self.create_msg(direction='I', msg_type='I', text="Bonjour", contact=self.frank, channel=self.twitter) joe_msg2 = self.create_msg(direction='O', msg_type='I', text="How are you?", contact=self.joe, status='Q') frank_msg2 = self.create_msg(direction='O', msg_type='I', text="Ça va?", contact=self.frank, status='D') joe_msg3 = self.create_msg(direction='I', msg_type='F', text="Good", contact=self.joe) frank_msg3 = self.create_msg(direction='I', msg_type='I', text="Bien", contact=self.frank, channel=self.twitter, visibility='A') # add a surveyor message (no URN etc) joe_msg4 = self.create_msg(direction='O', msg_type='F', text="Surveys!", contact=self.joe, contact_urn=None, status='S', channel=None, sent_on=timezone.now()) # add a deleted message deleted_msg = self.create_msg(direction='I', msg_type='I', text="!@$!%", contact=self.frank, visibility='D') # add a test contact message self.create_msg(direction='I', msg_type='F', text="Hello", contact=self.test_contact) # add message in other org self.create_msg(direction='I', msg_type='I', text="Guten tag!", contact=self.hans, org=self.org2) # label some of the messages, this will change our modified on as well for our `incoming` view label = Label.get_or_create(self.org, self.admin, "Spam") # we do this in two calls so that we can predict ordering later label.toggle_label([frank_msg3], add=True) label.toggle_label([frank_msg1], add=True) label.toggle_label([joe_msg3], add=True) frank_msg1.refresh_from_db(fields=('modified_on',)) joe_msg3.refresh_from_db(fields=('modified_on',)) # filter by inbox with self.assertNumQueries(NUM_BASE_REQUEST_QUERIES + 7): response = self.fetchJSON(url, 'folder=INBOX') self.assertEqual(response.status_code, 200) self.assertEqual(response.json['next'], None) self.assertResultsById(response, [frank_msg1]) self.assertMsgEqual(response.json['results'][0], frank_msg1, msg_type='inbox', msg_status='queued', msg_visibility='visible') # filter by incoming, should get deleted messages too with self.assertNumQueries(NUM_BASE_REQUEST_QUERIES + 7): response = self.fetchJSON(url, 'folder=incoming') self.assertEqual(response.status_code, 200) self.assertEqual(response.json['next'], None) self.assertResultsById(response, [joe_msg3, frank_msg1, frank_msg3, deleted_msg, joe_msg1]) self.assertMsgEqual(response.json['results'][0], joe_msg3, msg_type='flow', msg_status='queued', msg_visibility='visible') # filter by folder (flow) response = self.fetchJSON(url, 'folder=flows') self.assertResultsById(response, [joe_msg3, joe_msg1]) # filter by folder (archived) response = self.fetchJSON(url, 'folder=archived') self.assertResultsById(response, [frank_msg3]) # filter by folder (outbox) response = self.fetchJSON(url, 'folder=outbox') self.assertResultsById(response, [joe_msg2]) # filter by folder (sent) response = self.fetchJSON(url, 'folder=sent') self.assertResultsById(response, [joe_msg4, frank_msg2]) # filter by invalid view response = self.fetchJSON(url, 'folder=invalid') self.assertResultsById(response, []) # filter by id response = self.fetchJSON(url, 'id=%d' % joe_msg3.pk) self.assertResultsById(response, [joe_msg3]) # filter by contact response = self.fetchJSON(url, 'contact=%s' % self.joe.uuid) self.assertResultsById(response, [joe_msg4, joe_msg3, joe_msg2, joe_msg1]) # filter by invalid contact response = self.fetchJSON(url, 'contact=invalid') self.assertResultsById(response, []) # filter by label name response = self.fetchJSON(url, 'label=Spam') self.assertResultsById(response, [joe_msg3, frank_msg1]) # filter by label UUID response = self.fetchJSON(url, 'label=%s' % label.uuid) self.assertResultsById(response, [joe_msg3, frank_msg1]) # filter by invalid label response = self.fetchJSON(url, 'label=invalid') self.assertResultsById(response, []) # filter by before (inclusive) response = self.fetchJSON(url, 'folder=incoming&before=%s' % format_datetime(frank_msg1.modified_on)) self.assertResultsById(response, [frank_msg1, frank_msg3, deleted_msg, joe_msg1]) # filter by after (inclusive) response = self.fetchJSON(url, 'folder=incoming&after=%s' % format_datetime(frank_msg1.modified_on)) self.assertResultsById(response, [joe_msg3, frank_msg1]) # filter by broadcast broadcast = Broadcast.create(self.org, self.user, "A beautiful broadcast", [self.joe, self.frank]) broadcast.send() response = self.fetchJSON(url, 'broadcast=%s' % broadcast.pk) expected = {m.pk for m in broadcast.msgs.all()} results = {m['id'] for m in response.json['results']} self.assertEqual(expected, results) # can't filter by more than one of contact, folder, label or broadcast together for query in ('contact=%s&label=Spam' % self.joe.uuid, 'label=Spam&folder=inbox', 'broadcast=12345&folder=inbox', 'broadcast=12345&label=Spam'): response = self.fetchJSON(url, query) self.assertResponseError(response, None, "You may only specify one of the contact, folder, label, broadcast parameters")
def test_update(self): # create a scheduled broadcast schedule = Schedule.create_blank_schedule(self.org, self.admin) Broadcast.create( self.org, self.admin, {"eng": "Hi"}, contacts=[self.create_contact("Jim", phone="1234")], base_language="eng", schedule=schedule, ) update_url = reverse("schedules.schedule_update", args=[schedule.id]) self.assertUpdateFetch( update_url, allow_viewers=False, allow_editors=True, form_fields=["start_datetime", "repeat_period", "repeat_days_of_week"], ) def datepicker_fmt(d: datetime): return datetime_to_str(d, "%Y-%m-%d %H:%M", self.org.timezone) today = timezone.now().replace(second=0, microsecond=0) yesterday = today - timedelta(days=1) tomorrow = today + timedelta(days=1) # update to start in past with no repeat self.assertUpdateSubmit(update_url, {"start_datetime": datepicker_fmt(yesterday), "repeat_period": "O"}) schedule.refresh_from_db() self.assertEqual("O", schedule.repeat_period) self.assertIsNone(schedule.next_fire) # update to start in future with no repeat self.assertUpdateSubmit(update_url, {"start_datetime": datepicker_fmt(tomorrow), "repeat_period": "O"}) schedule.refresh_from_db() self.assertEqual("O", schedule.repeat_period) self.assertEqual(tomorrow, schedule.next_fire) # update to start now with daily repeat self.assertUpdateSubmit(update_url, {"start_datetime": datepicker_fmt(today), "repeat_period": "D"}) schedule.refresh_from_db() self.assertEqual("D", schedule.repeat_period) self.assertEqual(tomorrow, schedule.next_fire) # try to submit weekly without specifying days of week self.assertUpdateSubmit( update_url, {"start_datetime": datepicker_fmt(today), "repeat_period": "W"}, form_errors={"__all__": "Must specify at least one day of the week."}, object_unchanged=schedule, ) # try to submit weekly with an invalid day of the week (UI doesn't actually allow this) self.assertUpdateSubmit( update_url, {"start_datetime": datepicker_fmt(today), "repeat_period": "W", "repeat_days_of_week": ["X"]}, form_errors={"repeat_days_of_week": "Select a valid choice. X is not one of the available choices."}, object_unchanged=schedule, ) # update with valid days of the week self.assertUpdateSubmit( update_url, {"start_datetime": datepicker_fmt(today), "repeat_period": "W", "repeat_days_of_week": ["M", "F"]}, ) schedule.refresh_from_db() self.assertEqual("W", schedule.repeat_period) self.assertEqual("MF", schedule.repeat_days_of_week) # update to repeat monthly self.assertUpdateSubmit( update_url, {"start_datetime": datepicker_fmt(today), "repeat_period": "M"}, ) schedule.refresh_from_db() self.assertEqual("M", schedule.repeat_period) self.assertIsNone(schedule.repeat_days_of_week) # update with empty start date to signify unscheduling self.assertUpdateSubmit(update_url, {"start_datetime": "", "repeat_period": "O"}) schedule.refresh_from_db() self.assertEqual("O", schedule.repeat_period) self.assertIsNone(schedule.next_fire)