Skip to content

Commit

Permalink
Merge pull request #5183 from nyaruka/twilio-templates-sync
Browse files Browse the repository at this point in the history
Add Twilio content template type
  • Loading branch information
rowanseymour committed May 8, 2024
2 parents 5410c1c + 9408d3f commit 4413cf2
Show file tree
Hide file tree
Showing 7 changed files with 675 additions and 14 deletions.
63 changes: 63 additions & 0 deletions temba/channels/types/twilio_whatsapp/tests.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import json
from unittest.mock import patch

from requests import RequestException
from twilio.base.exceptions import TwilioRestException

from django.urls import reverse

from temba.channels.models import Channel
from temba.request_logs.models import HTTPLog
from temba.tests import TembaTest
from temba.tests.requests import MockResponse
from temba.tests.twilio import MockRequestValidator, MockTwilioClient

from .type import TwilioWhatsappType
Expand Down Expand Up @@ -189,3 +193,62 @@ def test_update(self):

response = self.client.post(update_url, post_data)
self.assertFormError(response.context["form"], None, "Credentials don't appear to be valid.")

@patch("requests.get")
def test_fetch_templates(self, mock_get):
config = {
Channel.CONFIG_ACCOUNT_SID: "TEST_SID",
Channel.CONFIG_AUTH_TOKEN: "TEST_TOKEN",
}
channel = self.org.channels.all().first()
channel.config = config
channel.channel_type = "TWA"
channel.save()

mock_get.side_effect = [
RequestException("Network is unreachable", response=MockResponse(100, "")),
MockResponse(400, '{ "meta": { "success": false } }'),
MockResponse(
200,
json.dumps(
{
"contents": [
{"friendly_name": "call_to_action_template"},
{"friendly_name": "media_template"},
],
"meta": {"next_page_url": "https://content.twilio.com/v1/Content?PageSize=50&Page=1"},
}
),
),
MockResponse(
200,
json.dumps(
{
"contents": [
{"friendly_name": "quick_reply_template"},
],
"meta": {"next_page_url": None},
}
),
),
]

with self.assertRaises(RequestException):
channel.type.fetch_templates(channel)

self.assertEqual(1, HTTPLog.objects.filter(log_type=HTTPLog.WHATSAPP_TEMPLATES_SYNCED, is_error=True).count())

with self.assertRaises(RequestException):
channel.type.fetch_templates(channel)

self.assertEqual(2, HTTPLog.objects.filter(log_type=HTTPLog.WHATSAPP_TEMPLATES_SYNCED, is_error=True).count())

templates = channel.type.fetch_templates(channel)
self.assertEqual(
templates,
[
{"friendly_name": "call_to_action_template"},
{"friendly_name": "media_template"},
{"friendly_name": "quick_reply_template"},
],
)
35 changes: 34 additions & 1 deletion temba/channels/types/twilio_whatsapp/type.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import base64

import requests

from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from temba.channels.types.twilio.type import TwilioType
from temba.channels.types.twilio.views import UpdateForm
from temba.contacts.models import URN
from temba.request_logs.models import HTTPLog

from ...models import ChannelType, ConfigUI
from ...models import Channel, ChannelType, ConfigUI
from .views import ClaimView


Expand Down Expand Up @@ -62,3 +68,30 @@ def get_error_ref_url(self, channel, code: str) -> str:

def check_credentials(self, config: dict) -> bool:
return TwilioType().check_credentials(config)

def fetch_templates(self, channel) -> list:
url = "https://content.twilio.com/v1/Content"
credentials_base64 = base64.b64encode(
f"{channel.config[Channel.CONFIG_ACCOUNT_SID]}:{channel.config[Channel.CONFIG_AUTH_TOKEN]}".encode()
).decode()

headers = {"Authorization": f"Basic {credentials_base64}"}

twilio_templates = []

while url:
start = timezone.now()
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
HTTPLog.from_response(
HTTPLog.WHATSAPP_TEMPLATES_SYNCED, response, start, timezone.now(), channel=channel
)

twilio_templates.extend(response.json()["contents"])
url = response.json().get("meta", {}).get("next_page_url", None)
except Exception as e:
HTTPLog.from_exception(HTTPLog.WHATSAPP_TEMPLATES_SYNCED, e, start, channel=channel)
raise e

return twilio_templates
12 changes: 12 additions & 0 deletions temba/templates/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from temba.channels.models import Channel
from temba.orgs.models import Org
from temba.utils.languages import alpha2_to_alpha3


class TemplateType:
Expand All @@ -18,6 +19,17 @@ def update_local(self, channel, raw: dict): # pragma: no cover
def _extract_variables(self, text: str) -> list:
return list(sorted({m for m in self.variable_regex.findall(text)}))

def _parse_language(self, lang: str) -> str:
"""
Converts a WhatsApp language code which can be alpha2 ('en') or alpha2_country ('en_US') or alpha3 ('fil')
to our locale format ('eng' or 'eng-US').
"""
language, country = lang.split("_") if "_" in lang else [lang, None]
if len(language) == 2:
language = alpha2_to_alpha3(language)

return f"{language}-{country}" if country else language


class Template(models.Model):
"""
Expand Down
Empty file.

0 comments on commit 4413cf2

Please sign in to comment.