diff --git a/temba/api/v2/tests.py b/temba/api/v2/tests.py index ad4003b224e..58a0d1874b8 100644 --- a/temba/api/v2/tests.py +++ b/temba/api/v2/tests.py @@ -4013,7 +4013,9 @@ def test_messages(self, mr_mocks): ) # filter by broadcast - broadcast = self.create_broadcast(self.user, "A beautiful broadcast", contacts=[self.joe, self.frank]) + broadcast = self.create_broadcast( + self.user, {"eng": {"text": "A beautiful broadcast"}}, contacts=[self.joe, self.frank] + ) self.assertGet( endpoint_url + f"?broadcast={broadcast.id}", [self.editor], diff --git a/temba/contacts/tests.py b/temba/contacts/tests.py index 71d0cbc947e..dd24e7602ec 100644 --- a/temba/contacts/tests.py +++ b/temba/contacts/tests.py @@ -775,11 +775,11 @@ def test_scheduled(self): # create scheduled and regular broadcasts which send to both groups bcast1 = self.create_broadcast( self.admin, - "Hi again", + {"eng": {"text": "Hi again"}}, contacts=[contact1, contact2], schedule=Schedule.create(self.org, timezone.now() + timedelta(days=3), Schedule.REPEAT_DAILY), ) - self.create_broadcast(self.admin, "Bye", contacts=[contact1, contact2]) # not scheduled + self.create_broadcast(self.admin, {"eng": {"text": "Bye"}}, contacts=[contact1, contact2]) # not scheduled # create scheduled trigger which this contact is explicitly added to trigger1_flow = self.create_flow("Favorites 1") @@ -1256,8 +1256,8 @@ def test_release(self, mr_mocks): # create scheduled and regular broadcasts which send to both groups schedule = Schedule.create(self.org, timezone.now(), Schedule.REPEAT_DAILY) - bcast1 = self.create_broadcast(self.admin, "Hi", groups=[group1, group2], schedule=schedule) - bcast2 = self.create_broadcast(self.admin, "Hi", groups=[group1, group2]) + bcast1 = self.create_broadcast(self.admin, {"eng": {"text": "Hi"}}, groups=[group1, group2], schedule=schedule) + bcast2 = self.create_broadcast(self.admin, {"eng": {"text": "Hi"}}, groups=[group1, group2]) bcast2.send_async() # group still has a hard dependency so can't be released @@ -1763,8 +1763,10 @@ def test_release(self, mr_mocks): # create scheduled and regular broadcasts which send to both contacts schedule = Schedule.create(self.org, timezone.now(), Schedule.REPEAT_DAILY) - bcast1 = self.create_broadcast(self.admin, "Test", contacts=[contact, contact2], schedule=schedule) - bcast2 = self.create_broadcast(self.admin, "Test", contacts=[contact, contact2]) + bcast1 = self.create_broadcast( + self.admin, {"eng": {"text": "Test"}}, contacts=[contact, contact2], schedule=schedule + ) + bcast2 = self.create_broadcast(self.admin, {"eng": {"text": "Test"}}, contacts=[contact, contact2]) flow_nodes = msg_flow.get_definition()["nodes"] color_prompt = flow_nodes[0] @@ -2280,7 +2282,7 @@ def test_history(self): self.joe.created_on = timezone.now() - timedelta(days=1000) self.joe.save(update_fields=("created_on",)) - self.create_broadcast(self.user, "A beautiful broadcast", contacts=[self.joe]) + self.create_broadcast(self.user, {"eng": {"text": "A beautiful broadcast"}}, contacts=[self.joe]) self.create_campaign() # add a message with some attachments diff --git a/temba/msgs/tests.py b/temba/msgs/tests.py index bb3831386b4..d86a44228ff 100644 --- a/temba/msgs/tests.py +++ b/temba/msgs/tests.py @@ -1,5 +1,4 @@ import json -from collections import OrderedDict from datetime import date, datetime, timedelta, timezone as tzone from unittest.mock import patch @@ -643,7 +642,7 @@ def test_outbox(self): # create a single message broadcast that's sent but it's message is still not sent broadcast1 = self.create_broadcast( self.admin, - "How is it going?", + {"eng": {"text": "How is it going?"}}, contacts=[contact1], status=Broadcast.STATUS_SENT, msg_status=Msg.STATUS_INITIALIZING, @@ -667,7 +666,7 @@ def test_outbox(self): group = self.create_group("Testers", contacts=[contact2, contact3]) broadcast2 = self.create_broadcast( self.admin, - "kLab is awesome", + {"eng": {"text": "kLab is awesome"}}, contacts=[contact4], groups=[group], msg_status=Msg.STATUS_QUEUED, @@ -737,7 +736,7 @@ def test_failed(self, mock_msg_resend): failed_url = reverse("msgs.msg_failed") # create broadcast and fail the only message - broadcast = self.create_broadcast(self.admin, "message number 2", contacts=[contact1]) + broadcast = self.create_broadcast(self.admin, {"eng": {"text": "message number 2"}}, contacts=[contact1]) broadcast.get_messages().update(status="F") msg2 = broadcast.get_messages()[0] @@ -1788,12 +1787,12 @@ def test_delete(self): self.create_incoming_msg(self.frank, "Bonjour") # create a broadcast which is a response to an incoming message - self.create_broadcast(self.user, self.create_translations("Noted"), contacts=[self.joe]) + self.create_broadcast(self.user, {"eng": {"text": "Noted"}}, contacts=[self.joe]) # create a broadcast which is to several contacts broadcast2 = self.create_broadcast( self.user, - "Very old broadcast", + {"eng": {"text": "Very old broadcast"}}, groups=[self.joe_and_frank], contacts=[self.kevin, self.lucy], ) @@ -1849,7 +1848,7 @@ def test_model(self): broadcast1 = Broadcast.create_legacy( self.org, self.user, - {"eng": "Hello everyone", "spa": "Hola a todos", "fra": "Salut à tous"}, + {"eng": {"text": "Hello everyone"}, "spa": {"text": "Hola a todos"}, "fra": {"text": "Salut à tous"}}, base_language="eng", groups=[self.joe_and_frank], contacts=[self.kevin, self.lucy], @@ -1864,7 +1863,9 @@ def test_model(self): mock_queue_broadcast.assert_called_once_with(broadcast1) # create a broadcast that looks like it has been sent - broadcast2 = self.create_broadcast(self.admin, "Hi everyone", contacts=[self.kevin, self.lucy]) + broadcast2 = self.create_broadcast( + self.admin, {"eng": {"text": "Hi everyone"}}, contacts=[self.kevin, self.lucy] + ) self.assertEqual(2, broadcast2.msgs.count()) self.assertEqual(2, broadcast2.get_message_count()) @@ -1968,44 +1969,6 @@ def test_get_translation(self): self.assertEqual(f'', repr(broadcast)) -def get_broadcast_form_data( - org, - *, - contacts=[], - translations=None, - send_when=ScheduleForm.SEND_LATER, - start_datetime="", - repeat_period="", - repeat_days_of_week="", -): - payload = OrderedDict( - [ - ("target", {"omnibox": omnibox_serialize(org, groups=[], contacts=contacts, json_encode=True)}), - ( - "compose", - {"compose": compose_serialize(translations, json_encode=True)} if translations else None, - ), - ( - "schedule", - ( - { - "send_when": send_when, - "start_datetime": start_datetime, - "repeat_period": repeat_period, - "repeat_days_of_week": repeat_days_of_week, - } - if send_when - else None - ), - ), - ] - ) - - if send_when == ScheduleForm.SEND_NOW: - payload["schedule"] = {"send_when": send_when, "repeat_period": Schedule.REPEAT_NEVER} - return payload - - class BroadcastCRUDLTest(TembaTest, CRUDLTestMixin): def setUp(self): super().setUp() @@ -2014,6 +1977,42 @@ def setUp(self): self.frank = self.create_contact("Frank Blow", urns=["tel:+12025550195"]) self.joe_and_frank = self.create_group("Joe and Frank", [self.joe, self.frank]) + def _form_data( + self, + org, + *, + translations, + contacts=(), + optin=None, + send_when=ScheduleForm.SEND_LATER, + start_datetime="", + repeat_period="", + repeat_days_of_week="", + ): + # UI puts optin in translations + if translations: + first_lang = next(iter(translations)) + translations[first_lang]["optin"] = {"uuid": str(optin.uuid), "name": optin.name} if optin else None + + payload = { + "target": {"omnibox": omnibox_serialize(org, groups=[], contacts=contacts, json_encode=True)}, + "compose": {"compose": compose_serialize(translations, json_encode=True)} if translations else None, + "schedule": ( + { + "send_when": send_when, + "start_datetime": start_datetime, + "repeat_period": repeat_period, + "repeat_days_of_week": repeat_days_of_week, + } + if send_when + else None + ), + } + + if send_when == ScheduleForm.SEND_NOW: + payload["schedule"] = {"send_when": send_when, "repeat_period": Schedule.REPEAT_NEVER} + return payload + def test_create(self): create_url = reverse("msgs.broadcast_create") @@ -2040,7 +2039,7 @@ def test_create(self): response = self.process_wizard( "create", create_url, - get_broadcast_form_data(self.org, translations={"und": {"text": ""}}, contacts=[self.joe]), + self._form_data(self.org, translations={"und": {"text": ""}}, contacts=[self.joe]), ) self.assertFormError(response.context["form"], "compose", ["This field is required."]) @@ -2048,7 +2047,7 @@ def test_create(self): response = self.process_wizard( "create", create_url, - get_broadcast_form_data(self.org, translations=self.create_translations("." * 641), contacts=[self.joe]), + self._form_data(self.org, translations={"eng": {"text": "." * 641}}, contacts=[self.joe]), ) self.assertFormError(response.context["form"], "compose", ["Maximum allowed text is 640 characters."]) @@ -2057,15 +2056,15 @@ def test_create(self): response = self.process_wizard( "create", create_url, - get_broadcast_form_data( - self.org, translations=self.create_translations(text, attachments * 11), contacts=[self.joe] + self._form_data( + self.org, translations={"eng": {"text": text, "attachments": attachments * 11}}, contacts=[self.joe] ), ) self.assertFormError(response.context["form"], "compose", ["Maximum allowed attachments is 10 files."]) # empty recipients response = self.process_wizard( - "create", create_url, get_broadcast_form_data(self.org, translations=self.create_translations(text)) + "create", create_url, self._form_data(self.org, translations={"eng": {"text": text}}) ) self.assertFormError(response.context["form"], "omnibox", ["At least one recipient is required."]) @@ -2073,7 +2072,7 @@ def test_create(self): response = self.process_wizard( "create", create_url, - get_broadcast_form_data(self.org, translations=self.create_translations(text), contacts=[self.joe]), + self._form_data(self.org, translations={"eng": {"text": text}}, contacts=[self.joe]), ) self.assertFormError(response.context["form"], None, ["Select when you would like the broadcast to be sent"]) @@ -2081,9 +2080,9 @@ def test_create(self): response = self.process_wizard( "create", create_url, - get_broadcast_form_data( + self._form_data( self.org, - translations=self.create_translations(text), + translations={"eng": {"text": text}}, contacts=[self.joe], start_datetime="2021-06-24 12:00Z", repeat_period="O", @@ -2100,10 +2099,11 @@ def test_create(self): response = self.process_wizard( "create", create_url, - get_broadcast_form_data( + self._form_data( self.org, - translations=self.create_translations(text, optin=optin), + translations={"eng": {"text": text}}, contacts=[self.joe], + optin=optin, start_datetime="2021-06-24 12:00", repeat_period="W", repeat_days_of_week=["M", "F"], @@ -2120,9 +2120,9 @@ def test_create(self): response = self.process_wizard( "create", create_url, - get_broadcast_form_data( + self._form_data( self.org, - translations=self.create_translations(text), + translations={"eng": {"text": text}}, contacts=[self.joe], send_when=ScheduleForm.SEND_NOW, ), @@ -2134,11 +2134,11 @@ def test_create(self): def test_update(self): optin = self.create_optin("Daily Polls") - updated_text = self.create_translations("Updated broadcast", optin=optin) + updated_text = {"und": {"text": "Updated broadcast"}} broadcast = self.create_broadcast( self.admin, - "Please update this broadcast when you get a chance.", + {"und": {"text": "Please update this broadcast when you get a chance."}}, groups=[self.joe_and_frank], contacts=[self.joe], schedule=Schedule.create(self.org, timezone.now(), Schedule.REPEAT_DAILY), @@ -2152,9 +2152,10 @@ def test_update(self): response = self.process_wizard( "update", update_url, - get_broadcast_form_data( + self._form_data( self.org, translations=updated_text, + optin=optin, contacts=[self.joe], start_datetime="2021-06-24 12:00", repeat_period="W", @@ -2165,18 +2166,15 @@ def test_update(self): broadcast.refresh_from_db() - # our optin won't be present in the translation - del updated_text["und"]["optin"] - self.assertEqual(updated_text["und"], broadcast.get_translation(self.joe)) - - # but should be on the broadcast itself + # optin should be extracted from the translations form data and saved on the broadcast itself + self.assertEqual({"und": {"text": "Updated broadcast", "attachments": []}}, broadcast.translations) self.assertEqual(optin, broadcast.optin) # now lets unset the optin from the broadcast response = self.process_wizard( "update", update_url, - get_broadcast_form_data( + self._form_data( self.org, translations=updated_text, contacts=[self.joe], @@ -2189,15 +2187,13 @@ def test_update(self): broadcast.refresh_from_db() # optin should be gone now - self.assertEqual(None, broadcast.optin) + self.assertIsNone(broadcast.optin) # now update the scheduled broadcast to send now response = self.process_wizard( "update", update_url, - get_broadcast_form_data( - self.org, translations=updated_text, contacts=[self.joe], send_when=ScheduleForm.SEND_NOW - ), + self._form_data(self.org, translations=updated_text, contacts=[self.joe], send_when=ScheduleForm.SEND_NOW), ) # when a scheduled broadcast is updated to send now, it'll redirect to broadcast list @@ -2211,7 +2207,7 @@ def test_localization(self): # create a broadcast without a language broadcast = self.create_broadcast( self.admin, - "This should end up as the language und", + {"und": {"text": "This should end up as the language und"}}, groups=[self.joe_and_frank], contacts=[self.joe], schedule=Schedule.create(self.org, timezone.now(), Schedule.REPEAT_DAILY), @@ -2229,7 +2225,7 @@ def get_languages(response): response = self.process_wizard( "update", update_url, - get_broadcast_form_data(self.org, contacts=[self.joe]), + self._form_data(self.org, translations={}, contacts=[self.joe]), ) # we only have a base language and don't have values for org languages, it should be first @@ -2240,7 +2236,7 @@ def get_languages(response): response = self.process_wizard( "update", update_url, - get_broadcast_form_data( + self._form_data( self.org, translations={"und": {"text": "undefined"}, "eng": {"text": "hello"}, "esp": {"text": "hola"}}, contacts=[self.joe], @@ -2254,7 +2250,7 @@ def get_languages(response): response = self.process_wizard( "update", update_url, - get_broadcast_form_data(self.org, contacts=[self.joe]), + self._form_data(self.org, translations={}, contacts=[self.joe]), ) # We have a primary language, it should be first @@ -2272,7 +2268,7 @@ def get_languages(response): response = self.process_wizard( "update", update_url, - get_broadcast_form_data(self.org, contacts=[self.joe]), + self._form_data(self.org, translations={}, contacts=[self.joe]), ) languages = get_languages(response) self.assertEqual("esp", languages[0]["iso"]) @@ -2410,7 +2406,7 @@ def test_list(self): broadcast = self.create_broadcast( self.admin, - "Broadcast sent to one contact", + {"eng": {"text": "Broadcast sent to one contact"}}, contacts=[self.joe], ) @@ -2426,21 +2422,21 @@ def test_scheduled(self): bc1 = self.create_broadcast( self.admin, - "good morning", + {"eng": {"text": "good morning"}}, contacts=[self.joe], schedule=Schedule.create(self.org, timezone.now(), Schedule.REPEAT_DAILY), ) bc2 = self.create_broadcast( self.admin, - "good evening", + {"eng": {"text": "good evening"}}, contacts=[self.frank], schedule=Schedule.create(self.org, timezone.now(), Schedule.REPEAT_DAILY), ) - self.create_broadcast(self.admin, "not_scheduled", groups=[self.joe_and_frank]) + self.create_broadcast(self.admin, {"eng": {"text": "not_scheduled"}}, groups=[self.joe_and_frank]) bc3 = self.create_broadcast( self.admin, - "good afternoon", + {"eng": {"text": "good afternoon"}}, contacts=[self.frank], schedule=Schedule.create(self.org, timezone.now(), Schedule.REPEAT_DAILY), ) @@ -2456,7 +2452,7 @@ def test_scheduled_read(self): schedule = Schedule.create(self.org, timezone.now(), "D", repeat_days_of_week="MWF") broadcast = self.create_broadcast( self.admin, - "Daily reminder", + {"eng": {"text": "Daily reminder"}}, groups=[self.joe_and_frank], schedule=schedule, ) @@ -2507,7 +2503,7 @@ def test_scheduled_delete(self): schedule = Schedule.create(self.org, timezone.now(), "D", repeat_days_of_week="MWF") broadcast = self.create_broadcast( self.admin, - "Daily reminder", + {"eng": {"text": "Daily reminder"}}, groups=[self.joe_and_frank], schedule=schedule, ) @@ -2831,7 +2827,7 @@ def test_get_counts(self): bcast1 = self.create_broadcast( self.user, - "Broadcast 1", + {"eng": {"text": "Broadcast 1"}}, contacts=[contact1, contact2], msg_status=Msg.STATUS_INITIALIZING, ) diff --git a/temba/orgs/tests.py b/temba/orgs/tests.py index a0980dfe99f..e897bb7a3ec 100644 --- a/temba/orgs/tests.py +++ b/temba/orgs/tests.py @@ -1349,19 +1349,23 @@ def _create_message_content(self, org, user, channels, contacts, groups, add) -> add(self.create_outgoing_msg(contact=contacts[0], text="cool story", channel=channels[0])) add(self.create_outgoing_msg(contact=contacts[0], text="synced", channel=channels[1])) - add(self.create_broadcast(user, "Announcement", contacts=contacts, groups=groups, org=org)) + add(self.create_broadcast(user, {"eng": {"text": "Announcement"}}, contacts=contacts, groups=groups, org=org)) scheduled = add( self.create_broadcast( user, - "Reminder", + {"eng": {"text": "Reminder"}}, contacts=contacts, groups=groups, org=org, schedule=Schedule.create(org, timezone.now(), Schedule.REPEAT_DAILY), ) ) - add(self.create_broadcast(user, "Reminder", contacts=contacts, groups=groups, org=org, parent=scheduled)) + add( + self.create_broadcast( + user, {"eng": {"text": "Reminder"}}, contacts=contacts, groups=groups, org=org, parent=scheduled + ) + ) label1 = add(self.create_label("Spam", org=org)) label2 = add(self.create_label("Important", org=org)) diff --git a/temba/schedules/tests.py b/temba/schedules/tests.py index 0263a4fa77d..947ac0ae8eb 100644 --- a/temba/schedules/tests.py +++ b/temba/schedules/tests.py @@ -222,7 +222,7 @@ def test_update_near_day_boundary(self): # our view asserts that our schedule is connected to a broadcast self.create_broadcast( self.admin, - text, + {"eng": {"text": text}}, contacts=[self.joe], status=Broadcast.STATUS_QUEUED, schedule=sched, diff --git a/temba/tests/base.py b/temba/tests/base.py index 2ebe2661599..c16b24d6c94 100644 --- a/temba/tests/base.py +++ b/temba/tests/base.py @@ -452,23 +452,10 @@ def _create_msg( log_uuids=[l.uuid for l in logs or []], ) - def create_translations(self, text="", attachments=[], lang="und", optin=None): - translations = { - lang: { - "text": text, - "attachments": attachments, - "quick_replies": [], - } - } - - if optin: - translations[lang]["optin"] = {"uuid": str(optin.uuid), "name": optin.name} if optin else None - return translations - def create_broadcast( self, user, - translations: dict[str, list] | str, + translations: dict[str, dict], groups=(), contacts=(), urns=(), @@ -480,9 +467,6 @@ def create_broadcast( created_on=None, org=None, ): - if isinstance(translations, str): - translations = self.create_translations(translations) - bcast = Broadcast.create_legacy( org or self.org, user, diff --git a/temba/tickets/tests.py b/temba/tickets/tests.py index 44509fd8cd9..ba1cf874102 100644 --- a/temba/tickets/tests.py +++ b/temba/tickets/tests.py @@ -419,7 +419,7 @@ def assert_tickets(resp, tickets: list): # give contact1 and old style broadcast message that doesn't have created_by set self.create_incoming_msg(contact1, "I have an issue") - c1_msg1 = self.create_broadcast(self.admin, "We can help", contacts=[contact1]).msgs.first() + c1_msg1 = self.create_broadcast(self.admin, {"eng": {"text": "We can help"}}, contacts=[contact1]).msgs.first() c1_msg1.created_by = None c1_msg1.save(update_fields=("created_by",))