Skip to content

Commit

Permalink
Merge pull request #2072 from nyaruka/update-twilio-only-2
Browse files Browse the repository at this point in the history
♻️ Update twilio
  • Loading branch information
rowanseymour committed Aug 30, 2018
2 parents 0cdc8d4 + f693faf commit 487ae12
Show file tree
Hide file tree
Showing 22 changed files with 282 additions and 211 deletions.
5 changes: 2 additions & 3 deletions pip-freeze.txt
Expand Up @@ -54,7 +54,6 @@ future==0.16.0 # via django-hamlpy, python-telegram-bot
geojson==1.3.5
google==1.9.3
gunicorn==19.7.1
httplib2==0.10.3 # via twilio
idna==2.5 # via cryptography, requests
inflection==0.3.1 # via python-intercom
ipaddress==1.0.19 # via elasticsearch-dsl
Expand Down Expand Up @@ -94,7 +93,7 @@ pyexcel-xlsx==0.4.0
pyexcel==0.5.0 # via pyexcel-webio
pyfcm==1.3.1
pyflakes==1.5.0 # via flake8
pyjwt[crypto]==1.6.4 # via nexmo
pyjwt[crypto]==1.6.4 # via nexmo, twilio
pysocks==1.6.8 ; python_version >= "3.0"
python-dateutil==2.2
python-gcm==0.4
Expand Down Expand Up @@ -122,7 +121,7 @@ stop-words==2015.2.23.1
stripe==1.59.0
texttable==0.9.0 # via pyexcel
toml==0.9.4 # via black
twilio==5.7.0
twilio==6.16.2
twython==3.5.0
unidecode==0.4.20
urllib3==1.21.1 # via elasticsearch, requests
Expand Down
2 changes: 1 addition & 1 deletion pip-requires.txt
Expand Up @@ -28,7 +28,7 @@ python-gcm
python-telegram-bot
raven
stripe
twilio<6.0.0
twilio
twython
elasticsearch
elasticsearch_dsl
Expand Down
6 changes: 3 additions & 3 deletions temba/channels/handlers.py
Expand Up @@ -2,7 +2,7 @@
from datetime import datetime

import pytz
from twilio import twiml
from twilio.twiml.voice_response import VoiceResponse

from django.conf import settings
from django.db.models import Q
Expand Down Expand Up @@ -79,7 +79,7 @@ def get(self, request, *args, **kwargs): # pragma: no cover
return HttpResponse("ILLEGAL METHOD")

def post(self, request, *args, **kwargs):
from twilio.util import RequestValidator
from twilio.request_validator import RequestValidator
from temba.flows.models import FlowSession

signature = request.META.get("HTTP_X_TWILIO_SIGNATURE", "")
Expand All @@ -103,7 +103,7 @@ def post(self, request, *args, **kwargs):
# find a channel that knows how to answer twilio calls
channel = self.get_ringing_channel(uuid=channel_uuid)
if not channel:
response = twiml.Response()
response = VoiceResponse()
response.say("Sorry, there is no channel configured to take this call. Goodbye.")
response.hangup()
return HttpResponse(str(response))
Expand Down
5 changes: 3 additions & 2 deletions temba/channels/models.py
Expand Up @@ -14,7 +14,8 @@
from phonenumbers import NumberParseException
from pyfcm import FCMNotification
from smartmin.models import SmartModel
from twilio import TwilioRestException, twiml
from twilio.base.exceptions import TwilioRestException
from twilio.twiml.voice_response import VoiceResponse

from django.conf import settings
from django.conf.urls import url
Expand Down Expand Up @@ -817,7 +818,7 @@ def is_delegate_caller(self):
def generate_ivr_response(self):
ivr_protocol = Channel.get_type_from_code(self.channel_type).ivr_protocol
if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_TWIML:
return twiml.Response()
return VoiceResponse()
if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_NCCO:
return NCCOResponse()

Expand Down
91 changes: 50 additions & 41 deletions temba/channels/types/twilio/tests.py
@@ -1,6 +1,6 @@

from mock import patch
from twilio import TwilioRestException
from twilio.base.exceptions import TwilioRestException

from django.urls import reverse

Expand All @@ -12,7 +12,7 @@

class TwilioTypeTest(TembaTest):
@patch("temba.ivr.clients.TwilioClient", MockTwilioClient)
@patch("twilio.util.RequestValidator", MockRequestValidator)
@patch("twilio.request_validator.RequestValidator", MockRequestValidator)
def test_claim(self):
self.login(self.admin)

Expand Down Expand Up @@ -62,7 +62,7 @@ def test_claim(self):
self.assertIn("account_trial", response.context)
self.assertTrue(response.context["account_trial"])

with patch("temba.tests.twilio.MockTwilioClient.MockPhoneNumbers.search") as mock_search:
with patch("temba.tests.twilio.MockTwilioClient.MockPhoneNumbers.list") as mock_search:
search_url = reverse("channels.channel_search_numbers")

# try making empty request
Expand Down Expand Up @@ -95,11 +95,11 @@ def test_claim(self):
response.json()["error"], "Sorry, no numbers found, please enter another pattern and try again."
)

with patch("temba.tests.twilio.MockTwilioClient.MockPhoneNumbers.list") as mock_numbers:
mock_numbers.return_value = [MockTwilioClient.MockPhoneNumber("+12062345678")]
with patch("temba.tests.twilio.MockTwilioClient.MockPhoneNumbers.stream") as mock_numbers:
mock_numbers.return_value = iter([MockTwilioClient.MockPhoneNumber("+12062345678")])

with patch("temba.tests.twilio.MockTwilioClient.MockShortCodes.list") as mock_short_codes:
mock_short_codes.return_value = []
with patch("temba.tests.twilio.MockTwilioClient.MockShortCodes.stream") as mock_short_codes:
mock_short_codes.return_value = iter([])

response = self.client.get(claim_twilio)
self.assertContains(response, "206-234-5678")
Expand All @@ -121,29 +121,33 @@ def test_claim(self):
self.assertTrue(channel_config[Channel.CONFIG_NUMBER_SID])

# voice only number
with patch("temba.tests.twilio.MockTwilioClient.MockPhoneNumbers.list") as mock_numbers:
mock_numbers.return_value = [MockTwilioClient.MockPhoneNumber("+554139087835")]
with patch("temba.tests.twilio.MockTwilioClient.MockPhoneNumbers.stream") as mock_numbers:
mock_numbers.return_value = iter([MockTwilioClient.MockPhoneNumber("+554139087835")])

with patch("temba.tests.twilio.MockTwilioClient.MockShortCodes.list") as mock_short_codes:
mock_short_codes.return_value = []
with patch("temba.tests.twilio.MockTwilioClient.MockShortCodes.stream") as mock_short_codes:
mock_short_codes.return_value = iter([])
Channel.objects.all().delete()

response = self.client.get(claim_twilio)
self.assertContains(response, "+55 41 3908-7835")
with patch("temba.tests.twilio.MockTwilioClient.MockPhoneNumbers.get") as mock_numbers_get:
mock_numbers_get.return_value = MockTwilioClient.MockPhoneNumber("+554139087835")

# claim it
response = self.client.post(claim_twilio, dict(country="BR", phone_number="554139087835"))
self.assertRedirects(response, reverse("public.public_welcome") + "?success")
response = self.client.get(claim_twilio)
self.assertContains(response, "+55 41 3908-7835")

# make sure it is actually connected
channel = Channel.objects.get(channel_type="T", org=self.org)
self.assertEqual(channel.role, Channel.ROLE_CALL + Channel.ROLE_ANSWER)
# claim it
mock_numbers.return_value = iter([MockTwilioClient.MockPhoneNumber("+554139087835")])
response = self.client.post(claim_twilio, dict(country="BR", phone_number="554139087835"))
self.assertRedirects(response, reverse("public.public_welcome") + "?success")

# make sure it is actually connected
channel = Channel.objects.get(channel_type="T", org=self.org)
self.assertEqual(channel.role, Channel.ROLE_CALL + Channel.ROLE_ANSWER)

with patch("temba.tests.twilio.MockTwilioClient.MockPhoneNumbers.list") as mock_numbers:
mock_numbers.return_value = [MockTwilioClient.MockPhoneNumber("+4545335500")]
with patch("temba.tests.twilio.MockTwilioClient.MockPhoneNumbers.stream") as mock_numbers:
mock_numbers.return_value = iter([MockTwilioClient.MockPhoneNumber("+4545335500")])

with patch("temba.tests.twilio.MockTwilioClient.MockShortCodes.list") as mock_short_codes:
mock_short_codes.return_value = []
with patch("temba.tests.twilio.MockTwilioClient.MockShortCodes.stream") as mock_short_codes:
mock_short_codes.return_value = iter([])

Channel.objects.all().delete()

Expand All @@ -158,27 +162,32 @@ def test_claim(self):
# make sure it is actually connected
Channel.objects.get(channel_type="T", org=self.org)

with patch("temba.tests.twilio.MockTwilioClient.MockPhoneNumbers.list") as mock_numbers:
mock_numbers.return_value = []
with patch("temba.tests.twilio.MockTwilioClient.MockPhoneNumbers.stream") as mock_numbers:
mock_numbers.return_value = iter([])

with patch("temba.tests.twilio.MockTwilioClient.MockShortCodes.list") as mock_short_codes:
mock_short_codes.return_value = [MockTwilioClient.MockShortCode("8080")]
Channel.objects.all().delete()
with patch("temba.tests.twilio.MockTwilioClient.MockShortCodes.stream") as mock_short_codes:
mock_short_codes.return_value = iter([MockTwilioClient.MockShortCode("8080")])

self.org.timezone = "America/New_York"
self.org.save()
with patch("temba.tests.twilio.MockTwilioClient.MockShortCodes.get") as mock_short_codes_get:
mock_short_codes_get.return_value = MockTwilioClient.MockShortCode("8080")

response = self.client.get(claim_twilio)
self.assertContains(response, "8080")
self.assertContains(response, 'class="country">US') # we look up the country from the timezone
Channel.objects.all().delete()

# claim it
response = self.client.post(claim_twilio, dict(country="US", phone_number="8080"))
self.assertRedirects(response, reverse("public.public_welcome") + "?success")
self.assertEqual(mock_numbers.call_args_list[0][1], {"page_size": 1000})
self.org.timezone = "America/New_York"
self.org.save()

# make sure it is actually connected
Channel.objects.get(channel_type="T", org=self.org)
response = self.client.get(claim_twilio)
self.assertContains(response, "8080")
self.assertContains(response, 'class="country">US') # we look up the country from the timezone

# claim it
mock_short_codes.return_value = iter([MockTwilioClient.MockShortCode("8080")])
response = self.client.post(claim_twilio, dict(country="US", phone_number="8080"))
self.assertRedirects(response, reverse("public.public_welcome") + "?success")
self.assertEqual(mock_numbers.call_args_list[0][1], {"page_size": 1000})

# make sure it is actually connected
Channel.objects.get(channel_type="T", org=self.org)

twilio_channel = self.org.channels.all().first()
# make channel support both sms and voice to check we clear both applications
Expand All @@ -187,7 +196,7 @@ def test_claim(self):
self.assertEqual("T", twilio_channel.channel_type)

with self.settings(IS_PROD=True):
with patch("temba.tests.twilio.MockTwilioClient.MockPhoneNumbers.update") as mock_numbers:
with patch("temba.tests.twilio.MockTwilioClient.MockPhoneNumber.update") as mock_numbers:
# our twilio channel removal should fail on bad auth
mock_numbers.side_effect = TwilioRestException(
401, "http://twilio", msg="Authentication Failure", code=20003
Expand All @@ -209,7 +218,7 @@ def test_claim(self):
)

@patch("temba.ivr.clients.TwilioClient", MockTwilioClient)
@patch("twilio.util.RequestValidator", MockRequestValidator)
@patch("twilio.request_validator.RequestValidator", MockRequestValidator)
def test_deactivate(self):

# make our channel of the twilio ilk
Expand Down
13 changes: 7 additions & 6 deletions temba/channels/types/twilio/type.py
@@ -1,5 +1,5 @@

from twilio import TwilioRestException
from twilio.base.exceptions import TwilioRestException

from django.utils.translation import ugettext_lazy as _

Expand Down Expand Up @@ -52,16 +52,17 @@ def deactivate(self, channel):
try:
try:
number_sid = channel.bod or channel.config.get("number_sid")
client.phone_numbers.update(number_sid, **number_update_args)
client.api.incoming_phone_numbers.get(number_sid).update(**number_update_args)
except Exception:
if client:
matching = client.phone_numbers.list(phone_number=channel.address)
if matching:
client.phone_numbers.update(matching[0].sid, **number_update_args)
matching = client.api.incoming_phone_numbers.stream(phone_number=channel.address)
first_match = next(matching, None)
if first_match:
client.api.incoming_phone_numbers.get(first_match.sid).update(**number_update_args)

if "application_sid" in config:
try:
client.applications.delete(sid=config["application_sid"])
client.api.applications.get(sid=config["application_sid"]).delete()
except TwilioRestException: # pragma: no cover
pass

Expand Down
31 changes: 16 additions & 15 deletions temba/channels/types/twilio/views.py
Expand Up @@ -4,7 +4,7 @@
import phonenumbers
from phonenumbers.phonenumberutil import region_code_for_number
from smartmin.views import SmartFormView
from twilio import TwilioRestException
from twilio.base.exceptions import TwilioRestException

from django import forms
from django.conf import settings
Expand Down Expand Up @@ -54,7 +54,7 @@ def pre_process(self, *args, **kwargs):
self.client = org.get_twilio_client()
if not self.client:
return HttpResponseRedirect(reverse("orgs.org_twilio_connect"))
self.account = self.client.accounts.get(org.config[ACCOUNT_SID])
self.account = self.client.api.account.fetch()
except TwilioRestException:
return HttpResponseRedirect(reverse("orgs.org_twilio_connect"))

Expand All @@ -78,8 +78,8 @@ def get_context_data(self, **kwargs):
def get_existing_numbers(self, org):
client = org.get_twilio_client()
if client:
twilio_account_numbers = client.phone_numbers.list(page_size=1000)
twilio_short_codes = client.sms.short_codes.list(page_size=1000)
twilio_account_numbers = client.api.incoming_phone_numbers.stream(page_size=1000)
twilio_short_codes = client.api.short_codes.stream(page_size=1000)

numbers = []
for number in twilio_account_numbers:
Expand Down Expand Up @@ -107,7 +107,7 @@ def claim_number(self, user, phone_number, country, role):
org = user.get_org()

client = org.get_twilio_client()
twilio_phones = client.phone_numbers.list(phone_number=phone_number)
twilio_phones = client.api.incoming_phone_numbers.stream(phone_number=phone_number)
channel_uuid = uuid4()

# create new TwiML app
Expand All @@ -118,7 +118,7 @@ def claim_number(self, user, phone_number, country, role):
)
new_voice_url = "https://" + callback_domain + reverse("handlers.twilio_handler", args=["voice", channel_uuid])

new_app = client.applications.create(
new_app = client.api.applications.create(
friendly_name="%s/%s" % (callback_domain.lower(), channel_uuid),
sms_url=new_receive_url,
sms_method="POST",
Expand All @@ -131,13 +131,13 @@ def claim_number(self, user, phone_number, country, role):

is_short_code = len(phone_number) <= 6
if is_short_code:
short_codes = client.sms.short_codes.list(short_code=phone_number)
short_codes = client.api.short_codes.stream(short_code=phone_number)
short_code = next(short_codes, None)

if short_codes:
short_code = short_codes[0]
if short_code:
number_sid = short_code.sid
app_url = "https://" + callback_domain + "%s" % reverse("courier.t", args=[channel_uuid, "receive"])
client.sms.short_codes.update(number_sid, sms_url=app_url, sms_method="POST")
client.api.short_codes.get(number_sid).update(sms_url=app_url, sms_method="POST")

role = Channel.ROLE_SEND + Channel.ROLE_RECEIVE
phone = phone_number
Expand All @@ -150,14 +150,15 @@ def claim_number(self, user, phone_number, country, role):
)
)
else:
if twilio_phones:
twilio_phone = twilio_phones[0]
client.phone_numbers.update(
twilio_phone.sid, voice_application_sid=new_app.sid, sms_application_sid=new_app.sid
twilio_phone = next(twilio_phones, None)
if twilio_phone:

client.api.incoming_phone_numbers.get(twilio_phone.sid).update(
voice_application_sid=new_app.sid, sms_application_sid=new_app.sid
)

else: # pragma: needs cover
twilio_phone = client.phone_numbers.purchase(
twilio_phone = client.api.incoming_phone_numbers.create(
phone_number=phone_number, voice_application_sid=new_app.sid, sms_application_sid=new_app.sid
)

Expand Down
4 changes: 2 additions & 2 deletions temba/channels/types/twilio_messaging_service/tests.py
@@ -1,6 +1,6 @@

from mock import patch
from twilio import TwilioRestException
from twilio.base.exceptions import TwilioRestException

from django.urls import reverse

Expand All @@ -12,7 +12,7 @@

class TwilioMessagingServiceTypeTest(TembaTest):
@patch("temba.ivr.clients.TwilioClient", MockTwilioClient)
@patch("twilio.util.RequestValidator", MockRequestValidator)
@patch("twilio.request_validator.RequestValidator", MockRequestValidator)
def test_claim(self):

self.login(self.admin)
Expand Down
4 changes: 2 additions & 2 deletions temba/channels/types/twilio_messaging_service/views.py
@@ -1,6 +1,6 @@

from smartmin.views import SmartFormView
from twilio import TwilioRestException
from twilio.base.exceptions import TwilioRestException

from django import forms
from django.http import HttpResponseRedirect
Expand Down Expand Up @@ -34,7 +34,7 @@ def pre_process(self, *args, **kwargs):
self.client = org.get_twilio_client()
if not self.client:
return HttpResponseRedirect(reverse("orgs.org_twilio_connect"))
self.account = self.client.accounts.get(org.config[ACCOUNT_SID])
self.account = self.client.api.account.fetch()
except TwilioRestException:
return HttpResponseRedirect(reverse("orgs.org_twilio_connect"))

Expand Down
2 changes: 1 addition & 1 deletion temba/channels/types/twiml_api/tests.py
Expand Up @@ -9,7 +9,7 @@

class TwimlAPITypeTest(TembaTest):
@patch("temba.ivr.clients.TwilioClient", MockTwilioClient)
@patch("twilio.util.RequestValidator", MockRequestValidator)
@patch("twilio.request_validator.RequestValidator", MockRequestValidator)
def test_claim(self):
self.login(self.admin)

Expand Down

0 comments on commit 487ae12

Please sign in to comment.