Example #1
0
            def clean_csv_file(self):
                try:
                    Contact.get_import_file_headers(ContentFile(self.cleaned_data['csv_file'].read()))
                except Exception as e:
                    raise forms.ValidationError(str(e))

                return self.cleaned_data['csv_file']
Example #2
0
    def start_call(self, call, to, from_, status_callback):
        if not settings.SEND_CALLS:
            raise ValueError("SEND_CALLS set to False, skipping call start")

        channel = call.channel
        Contact.get_or_create(channel.org, URN.from_tel(to), channel)

        # Verboice differs from Twilio in that they expect the first block of twiml up front
        payload = str(Flow.handle_call(call))

        # now we can post that to verboice
        url = "%s?%s" % (self.endpoint, urlencode(dict(channel=self.verboice_channel, address=to)))
        response = requests.post(url, data=payload, auth=self.auth).json()

        if "call_id" not in response:
            call.status = IVRCall.FAILED
            call.save()

            raise IVRException(_("Verboice connection failed."))

        # store the verboice call id in our IVRCall
        call.external_id = response["call_id"]

        # the call was successfully sent to the IVR provider
        call.status = IVRCall.WIRED
        call.save()
Example #3
0
    def test_twilio_failed_auth(self):

        def create(self, to=None, from_=None, url=None, status_callback=None):
            from twilio import TwilioRestException
            raise TwilioRestException(403, 'http://twilio.com', code=20003)
        MockTwilioClient.MockCalls.create = create

        # connect it and check our client is configured
        self.org.connect_twilio("TEST_SID", "TEST_TOKEN")
        self.org.save()

        # import an ivr flow
        self.import_file('call-me-maybe')
        flow = Flow.objects.filter(name='Call me maybe').first()

        user_settings = self.admin.get_settings()
        user_settings.tel = '+18005551212'
        user_settings.save()

        test_contact = Contact.get_test_contact(self.admin)
        Contact.set_simulation(True)
        flow.start([], [test_contact])

        log = ActionLog.objects.all().order_by('-pk').first()
        self.assertEquals(log.text, 'Call ended. Could not authenticate with your Twilio account. '
                                    'Check your token and try again.')
Example #4
0
    def send_message(
        self,
        flow,
        message,
        restart_participants=False,
        contact=None,
        initiate_flow=False,
        assert_reply=True,
        assert_handle=True,
    ):
        """
        Starts the flow, sends the message, returns the reply
        """
        if not contact:
            contact = self.contact
        try:
            if contact.is_test:
                Contact.set_simulation(True)

            incoming = self.create_msg(
                direction=INCOMING, contact=contact, contact_urn=contact.get_urn(), text=message
            )

            # start the flow
            if initiate_flow:
                flow.start(
                    groups=[], contacts=[contact], restart_participants=restart_participants, start_msg=incoming
                )
            else:
                flow.start(groups=[], contacts=[contact], restart_participants=restart_participants)
                (handled, msgs) = Flow.find_and_handle(incoming)

                Msg.mark_handled(incoming)

                if assert_handle:
                    self.assertTrue(handled, "'%s' did not handle message as expected" % flow.name)
                else:
                    self.assertFalse(handled, "'%s' handled message, was supposed to ignore" % flow.name)

            # our message should have gotten a reply
            if assert_reply:
                replies = Msg.objects.filter(response_to=incoming).order_by("pk")
                self.assertGreaterEqual(len(replies), 1)

                if len(replies) == 1:
                    self.assertEqual(contact, replies.first().contact)
                    return replies.first().text

                # if it's more than one, send back a list of replies
                return [reply.text for reply in replies]

            else:
                # assert we got no reply
                replies = Msg.objects.filter(response_to=incoming).order_by("pk")
                self.assertFalse(replies)

            return None

        finally:
            Contact.set_simulation(False)
Example #5
0
    def send_message(self, flow, message, restart_participants=False, contact=None, initiate_flow=False, assert_reply=True):
        """
        Starts the flow, sends the message, returns the reply
        """
        if not contact:
            contact = self.contact

        try:
            if contact.is_test:
                Contact.set_simulation(True)

            incoming = self.create_msg(direction=INCOMING, contact=contact, text=message)

            # start the flow
            if initiate_flow:
                flow.start(groups=[], contacts=[contact], restart_participants=restart_participants, start_msg=incoming)
            else:
                flow.start(groups=[], contacts=[contact], restart_participants=restart_participants)
                self.assertTrue(flow.find_and_handle(incoming))

            # our message should have gotten a reply
            if assert_reply:
                reply = Msg.objects.get(response_to=incoming)
                self.assertEquals(contact, reply.contact)
                return reply.text

            return None

        finally:
            Contact.set_simulation(False)
Example #6
0
    def test_rule_first_ivr_flow(self):
        # connect it and check our client is configured
        self.org.connect_twilio("TEST_SID", "TEST_TOKEN")
        self.org.save()

        # import an ivr flow
        self.import_file('rule-first-ivr')
        flow = Flow.objects.filter(name='Rule First IVR').first()

        user_settings = self.admin.get_settings()
        user_settings.tel = '+18005551212'
        user_settings.save()

        # start our flow`
        eric = self.create_contact('Eric Newcomer', number='+13603621737')
        eric.is_test = True
        eric.save()
        Contact.set_simulation(True)
        flow.start([], [eric])

        # should be using the usersettings number in test mode
        self.assertEquals('Placing test call to +1 800-555-1212', ActionLog.objects.all().first().text)

        # we should have an outbound ivr call now
        call = IVRCall.objects.filter(direction=OUTGOING).first()

        self.assertEquals(0, call.get_duration())
        self.assertIsNotNone(call)
        self.assertEquals('CallSid', call.external_id)

        # after a call is picked up, twilio will call back to our server
        post_data = dict(CallSid='CallSid', CallStatus='in-progress', CallDuration=20)
        response = self.client.post(reverse('ivr.ivrcall_handle', args=[call.pk]), post_data)
        self.assertContains(response, '<Say>Thanks for calling!</Say>')
Example #7
0
    def get_context_data(self, **kwargs):
        org = self.request.user.get_org()

        # if there isn't a search filtering the queryset, we can replace the count function with a quick cache lookup to
        # speed up paging
        if hasattr(self, 'folder') and 'search' not in self.request.REQUEST:
            org.patch_folder_queryset(self.object_list, self.folder, self.request)

        context = super(ContactListView, self).get_context_data(**kwargs)

        folders = [dict(count=org.get_folder_count(OrgFolder.contacts_all), label=_("All Contacts"), url=reverse('contacts.contact_list')),
                   dict(count=org.get_folder_count(OrgFolder.contacts_failed), label=_("Failed"), url=reverse('contacts.contact_failed')),
                   dict(count=org.get_folder_count(OrgFolder.contacts_blocked), label=_("Blocked"), url=reverse('contacts.contact_blocked'))]

        groups_qs = ContactGroup.user_groups.filter(org=org, is_active=True).select_related('org')
        groups_qs = groups_qs.extra(select={'lower_group_name': 'lower(contacts_contactgroup.name)'}).order_by('lower_group_name')
        groups = [dict(pk=g.pk, label=g.name, count=g.get_member_count(), is_dynamic=g.is_dynamic) for g in groups_qs]

        # resolve the paginated object list so we can initialize a cache of URNs and fields
        contacts = list(context['object_list'])
        Contact.bulk_cache_initialize(org, contacts, for_show_only=True)

        context['contacts'] = contacts
        context['groups'] = groups
        context['folders'] = folders
        context['has_contacts'] = contacts or org.has_contacts()
        context['send_form'] = SendMessageForm(self.request.user)
        return context
Example #8
0
        def save(self, obj):
            urns = []
            for field_key, value in self.form.cleaned_data.iteritems():
                if field_key.startswith('__urn__') and value:
                    scheme = field_key[7:]
                    urns.append((scheme, value))

            Contact.get_or_create(obj.org, self.request.user, obj.name, urns)
Example #9
0
 def send(self, message, contact=None):
     if not contact:
         contact = self.contact
     if contact.is_test:
         Contact.set_simulation(True)
     incoming = self.create_msg(direction=INCOMING, contact=contact, text=message)
     Flow.find_and_handle(incoming)
     return Msg.all_messages.filter(response_to=incoming).order_by('pk').first()
Example #10
0
    def setUp(self):

        # if we are super verbose, turn on debug for sql queries
        if self.get_verbosity() > 2:
            settings.DEBUG = True

        self.clear_cache()

        self.superuser = User.objects.create_superuser(username="******", email="*****@*****.**", password="******")

        # create different user types
        self.non_org_user = self.create_user("NonOrg")
        self.user = self.create_user("User")
        self.editor = self.create_user("Editor")
        self.admin = self.create_user("Administrator")
        self.surveyor = self.create_user("Surveyor")

        # setup admin boundaries for Rwanda
        self.country = AdminBoundary.objects.create(osm_id='171496', name='Rwanda', level=0)
        self.state1 = AdminBoundary.objects.create(osm_id='1708283', name='Kigali City', level=1, parent=self.country)
        self.state2 = AdminBoundary.objects.create(osm_id='171591', name='Eastern Province', level=1, parent=self.country)
        self.district1 = AdminBoundary.objects.create(osm_id='1711131', name='Gatsibo', level=2, parent=self.state2)
        self.district2 = AdminBoundary.objects.create(osm_id='1711163', name='KayƓnza', level=2, parent=self.state2)
        self.district3 = AdminBoundary.objects.create(osm_id='3963734', name='Nyarugenge', level=2, parent=self.state1)
        self.district4 = AdminBoundary.objects.create(osm_id='1711142', name='Rwamagana', level=2, parent=self.state2)
        self.ward1 = AdminBoundary.objects.create(osm_id='171113181', name='Kageyo', level=3, parent=self.district1)
        self.ward2 = AdminBoundary.objects.create(osm_id='171116381', name='Kabare', level=3, parent=self.district2)
        self.ward3 = AdminBoundary.objects.create(osm_id='171114281', name='Bukure', level=3, parent=self.district4)

        self.org = Org.objects.create(name="Temba", timezone="Africa/Kigali", country=self.country, brand=settings.DEFAULT_BRAND,
                                      created_by=self.user, modified_by=self.user)

        self.org.initialize(topup_size=1000)

        # add users to the org
        self.user.set_org(self.org)
        self.org.viewers.add(self.user)

        self.editor.set_org(self.org)
        self.org.editors.add(self.editor)

        self.admin.set_org(self.org)
        self.org.administrators.add(self.admin)

        self.surveyor.set_org(self.org)
        self.org.surveyors.add(self.surveyor)

        self.superuser.set_org(self.org)

        # welcome topup with 1000 credits
        self.welcome_topup = self.org.topups.all()[0]

        # a single Android channel
        self.channel = Channel.create(self.org, self.user, 'RW', 'A', name="Test Channel", address="+250785551212",
                                      device="Nexus 5X", secret="12345", gcm_id="123")

        # reset our simulation to False
        Contact.set_simulation(False)
Example #11
0
    def send(self, message, contact=None):
        if not contact:
            contact = self.contact
        if contact.is_test:
            Contact.set_simulation(True)
        incoming = self.create_msg(direction=INCOMING, contact=contact, text=message)

        # evaluate the inbound message against our triggers first
        from temba.triggers.models import Trigger
        if not Trigger.find_and_handle(incoming):
            Flow.find_and_handle(incoming)
        return Msg.objects.filter(response_to=incoming).order_by('pk').first()
Example #12
0
    def setUp(self):
        self.clear_cache()

        self.superuser = User.objects.create_superuser(username="******", email="*****@*****.**", password="******")

        # some users not tied to our org
        self.non_org_user = self.create_user("NonOrg")
        self.non_org_manager = self.create_user("NonOrgManager")

        # our three user types inside our org
        self.user = self.create_user("User")
        self.root = self.create_user("Root")
        self.root.groups.add(Group.objects.get(name="Alpha"))

        self.admin = self.create_user("Administrator")

        # setup admin boundaries for Rwanda
        self.country = AdminBoundary.objects.create(osm_id='171496', name='Rwanda', level=0)
        state1 = AdminBoundary.objects.create(osm_id='1708283', name='Kigali City', level=1, parent=self.country)
        state2 = AdminBoundary.objects.create(osm_id='171591', name='Eastern Province', level=1, parent=self.country)
        AdminBoundary.objects.create(osm_id='1711131', name='Gatsibo', level=2, parent=state2)
        AdminBoundary.objects.create(osm_id='1711163', name='Kayonza', level=2, parent=state2)
        AdminBoundary.objects.create(osm_id='60485579', name='Kigali', level=2, parent=state1)
        AdminBoundary.objects.create(osm_id='1711142', name='Rwamagana', level=2, parent=state2)

        self.org = Org.objects.create(name="Temba", timezone="Africa/Kigali", country=self.country,
                                      created_by=self.user, modified_by=self.user)
        self.org.initialize()

        # add users to the org
        self.org.administrators.add(self.admin)
        self.admin.set_org(self.org)

        self.org.administrators.add(self.root)
        self.root.set_org(self.org)

        self.user.set_org(self.org)
        self.superuser.set_org(self.org)

        # welcome topup with 1000 credits
        self.welcome_topup = self.org.topups.all()[0]

        # a single Android channel
        self.channel = Channel.objects.create(org=self.org, name="Test Channel",
                                              address="+250785551212", country='RW', channel_type='A',
                                              secret="12345", gcm_id="123",
                                              created_by=self.user, modified_by=self.user)

        # reset our simulation to False
        Contact.set_simulation(False)
Example #13
0
    def setUp(self):
        self.clear_cache()

        self.superuser = User.objects.create_superuser(username="******", email="*****@*****.**", password="******")

        # create different user types
        self.non_org_user = self.create_user("NonOrg")
        self.user = self.create_user("User")
        self.editor = self.create_user("Editor")
        self.admin = self.create_user("Administrator")
        self.surveyor = self.create_user("Surveyor")

        # setup admin boundaries for Rwanda
        self.country = AdminBoundary.objects.create(osm_id='171496', name='Rwanda', level=0)
        self.state1 = AdminBoundary.objects.create(osm_id='1708283', name='Kigali City', level=1, parent=self.country)
        self.state2 = AdminBoundary.objects.create(osm_id='171591', name='Eastern Province', level=1, parent=self.country)
        self.district1 = AdminBoundary.objects.create(osm_id='1711131', name='Gatsibo', level=2, parent=self.state2)
        self.district2 = AdminBoundary.objects.create(osm_id='1711163', name='Kayonza', level=2, parent=self.state2)
        self.district3 = AdminBoundary.objects.create(osm_id='60485579', name='Kigali', level=2, parent=self.state1)
        self.district4 = AdminBoundary.objects.create(osm_id='1711142', name='Rwamagana', level=2, parent=self.state2)

        self.org = Org.objects.create(name="Temba", timezone="Africa/Kigali", country=self.country,
                                      created_by=self.user, modified_by=self.user)
        self.org.initialize()

        # add users to the org
        self.user.set_org(self.org)
        self.org.viewers.add(self.user)

        self.editor.set_org(self.org)
        self.org.editors.add(self.editor)

        self.admin.set_org(self.org)
        self.org.administrators.add(self.admin)

        self.surveyor.set_org(self.org)
        self.org.surveyors.add(self.surveyor)

        self.superuser.set_org(self.org)

        # welcome topup with 1000 credits
        self.welcome_topup = self.org.topups.all()[0]

        # a single Android channel
        self.channel = Channel.create(self.org, self.user, 'RW', 'A', name="Test Channel", address="+250785551212",
                                      secret="12345", gcm_id="123")

        # reset our simulation to False
        Contact.set_simulation(False)
Example #14
0
    def test_contact_search(self):
        contacts = self._create_contacts(10000, ["Bobby", "Jimmy", "Mary"])
        self._create_values(contacts, self.field_nick, lambda c: c.name.lower().replace(' ', '_'))

        with SegmentProfiler("Contact search with simple query", self, force_profile=True):
            qs, is_complex = Contact.search(self.org, 'bob')

        self.assertEqual(3334, qs.count())
        self.assertEqual(False, is_complex)

        with SegmentProfiler("Contact search with complex query", self, force_profile=True):
            qs, is_complex = Contact.search(self.org, 'name = bob or tel has 078 or twitter = tweep_123 or nick is bob')

        self.assertEqual(3377, qs.count())
        self.assertEqual(True, is_complex)
Example #15
0
    def restore_object(self, attrs, instance=None):
        """
        Actually start our flows for each contact
        """
        if instance:  # pragma: no cover
            raise ValidationError("Invalid operation")

        flow = attrs['flow']
        groups = attrs.get('groups', [])
        contacts = attrs.get('contacts', [])
        extra = attrs.get('extra', None)

        # include contacts created/matched via deprecated phone field
        phone_urns = attrs.get('phone', [])
        if phone_urns:
            channel = self.org.get_send_channel(TEL_SCHEME)
            for urn in phone_urns:
                # treat each URN as separate contact
                contact = Contact.get_or_create(self.user, channel.org, urns=[urn], channel=channel)
                contacts.append(contact)

        if contacts or groups:
            return flow.start(groups, contacts, restart_participants=True, extra=extra)
        else:
            return []
Example #16
0
    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
Example #17
0
    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
Example #18
0
    def start_call(self, call, to, from_, status_callback):

        channel = call.channel
        Contact.get_or_create(channel.org, channel.created_by, urns=[(TEL_SCHEME, to)])

        # Verboice differs from Twilio in that they expect the first block of twiml up front
        payload = unicode(Flow.handle_call(call, {}))

        # now we can post that to verboice
        url = "%s?%s" % (self.endpoint, urlencode(dict(channel=self.verboice_channel, address=to)))
        response = requests.post(url, data=payload, auth=self.auth).json()

        # store the verboice call id in our IVRCall
        call.external_id = response['call_id']
        call.status = IN_PROGRESS
        call.save()
Example #19
0
    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
Example #20
0
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))
Example #21
0
    def create_contact(self, name=None, number=None, twitter=None, urn=None, is_test=False, **kwargs):
        """
        Create a contact in the master test org
        """
        urns = []
        if number:
            urns.append((TEL_SCHEME, number))
        if twitter:
            urns.append((TWITTER_SCHEME, twitter))
        if urn:
            urns.append(urn)

        if not name and not urns:  # pragma: no cover
            raise ValueError("Need a name or URN to create a contact")

        kwargs['name'] = name
        kwargs['urns'] = urns
        kwargs['is_test'] = is_test

        if 'org' not in kwargs:
            kwargs['org'] = self.org
        if 'user' not in kwargs:
            kwargs['user'] = self.user

        return Contact.get_or_create(**kwargs)
Example #22
0
    def validate_urns(self, value):
        urn_contacts = []
        for urn in value:
            contact = Contact.get_or_create(self.context['org'], self.context['user'], urns=[urn])
            urn_contacts.append(contact)

        return urn_contacts
Example #23
0
    def create_contact(self, name=None, number=None, twitter=None, twitterid=None, urn=None, is_test=False, **kwargs):
        """
        Create a contact in the master test org
        """
        urns = []
        if number:
            urns.append(URN.from_tel(number))
        if twitter:
            urns.append(URN.from_twitter(twitter))
        if twitterid:
            urns.append(URN.from_twitterid(twitterid))
        if urn:
            urns.append(urn)

        if not name and not urns:  # pragma: no cover
            raise ValueError("Need a name or URN to create a contact")

        kwargs["name"] = name
        kwargs["urns"] = urns
        kwargs["is_test"] = is_test

        if "org" not in kwargs:
            kwargs["org"] = self.org
        if "user" not in kwargs:
            kwargs["user"] = self.user

        return Contact.get_or_create_by_urns(**kwargs)
Example #24
0
    def restore_object(self, attrs, instance=None):
        """
        Actually start our flows for each contact
        """
        if instance:  # pragma: no cover
            raise ValidationError("Invalid operation")

        flow = attrs['flow']
        channel = self.org.get_send_channel(TEL_SCHEME)

        if 'urn' in attrs and attrs['urn']:
            urns = attrs.get('urn', [])
        else:
            urns = attrs.get('phone', [])

        contacts = []
        for urn in urns:
            # treat each URN as separate contact
            contact = Contact.get_or_create(self.user, channel.org, urns=[urn], channel=channel)
            contacts.append(contact)

        # also add in any contacts specified by uuid
        uuid_contacts = attrs.get('contact', [])
        contacts += uuid_contacts

        if contacts:
            runs = flow.start([], contacts, restart_participants=True, extra=attrs.get('extra', None))
        else:
            runs = []

        return runs
Example #25
0
        def get_form(self, form_class):
            form = super(ContactCRUDL.Customize, self).get_form(form_class)
            form.fields.clear()
            
            self.headers = Contact.get_import_file_headers(self.get_object().csv_file.file)
            self.column_controls = self.create_column_controls(self.headers)

            return form
Example #26
0
    def get_or_create_contact(self, urn):
        if ':' in urn:
            parsed = ContactURN.parse_urn(urn)
            urn = (parsed.scheme, parsed.path)
        else:
            urn = (TEL_SCHEME, urn)  # assume phone number

        return Contact.get_or_create(self.org, self.user, name=None, urns=[urn])
Example #27
0
    def get_contact_fields(self, obj):
        if not obj.is_active:
            return {}

        fields = {}
        for contact_field in self.context['contact_fields']:
            value = obj.get_field(contact_field.key)
            fields[contact_field.key] = Contact.serialize_field_value(contact_field, value)
        return fields
Example #28
0
    def start_call(self, call, to, from_, status_callback):

        channel = call.channel
        Contact.get_or_create(channel.org, channel.created_by, urns=[(TEL_SCHEME, to)])

        # Verboice differs from Twilio in that they expect the first block of twiml up front
        payload = unicode(Flow.handle_call(call, {}))

        # our config should have our http basic auth parameters and verboice channel
        config = channel.config_json()

        # now we can post that to verboice
        url = "%s?%s" % (self.endpoint, urlencode(dict(channel=config["channel"], address=to)))
        response = requests.post(url, data=payload, auth=(config["username"], config["password"])).json()

        # store the verboice call id in our IVRCall
        call.external_id = response["call_id"]
        call.status = IN_PROGRESS
        call.save()
Example #29
0
    def get_queryset(self, **kwargs):
        qs = super(ContactListView, self).get_queryset(**kwargs)
        org = self.request.user.get_org()

        # contact list views don't use regular field searching but use more complex contact searching
        query = self.request.REQUEST.get('search', None)
        if query:
            qs, self.request.compiled_query = Contact.search(org, query, qs)

        return qs.prefetch_related('groups')
Example #30
0
    def _create_contacts(self, count, base_names):
        """
        Creates the given number of contacts with URNs of each type, and fields value for dob and nickname
        """
        contacts = []

        for c in range(0, count):
            name = '%s %d' % (base_names[c % len(base_names)], c + 1)
            scheme, path, channel = self.urn_generators[c % len(self.urn_generators)](c)
            contacts.append(Contact.get_or_create(self.org, self.user, name, urns=[':'.join([scheme, path])]))
        return contacts
Example #31
0
    def post(self, request, *args, **kwargs):
        from twilio.util import RequestValidator
        from temba.flows.models import FlowSession

        signature = request.META.get("HTTP_X_TWILIO_SIGNATURE", "")
        url = "https://" + request.get_host() + "%s" % request.get_full_path()

        channel_uuid = kwargs.get("uuid")
        call_sid = self.get_param("CallSid")
        direction = self.get_param("Direction")
        status = self.get_param("CallStatus")
        to_number = self.get_param("To")
        to_country = self.get_param("ToCountry")
        from_number = self.get_param("From")

        # Twilio sometimes sends un-normalized numbers
        if to_number and not to_number.startswith(
                "+") and to_country:  # pragma: no cover
            to_number, valid = URN.normalize_number(to_number, to_country)

        # see if it's a twilio call being initiated
        if to_number and call_sid and direction == "inbound" and status == "ringing":

            # 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.say(
                    "Sorry, there is no channel configured to take this call. Goodbye."
                )
                response.hangup()
                return HttpResponse(str(response))

            org = channel.org

            if self.get_channel_type(
            ) == "T" and not org.is_connected_to_twilio():
                return HttpResponse("No Twilio account is connected",
                                    status=400)

            client = self.get_client(channel=channel)
            validator = RequestValidator(client.auth[1])
            signature = request.META.get("HTTP_X_TWILIO_SIGNATURE", "")

            url = "https://%s%s" % (request.get_host(),
                                    request.get_full_path())

            if validator.validate(url, request.POST, signature):
                from temba.ivr.models import IVRCall

                # find a contact for the one initiating us
                urn = URN.from_tel(from_number)
                contact, urn_obj = Contact.get_or_create(
                    channel.org, urn, channel)

                flow = Trigger.find_flow_for_inbound_call(contact)

                if flow:
                    call = IVRCall.create_incoming(channel, contact, urn_obj,
                                                   channel.created_by,
                                                   call_sid)
                    session = FlowSession.create(contact, connection=call)

                    call.update_status(request.POST.get("CallStatus", None),
                                       request.POST.get("CallDuration", None),
                                       "T")
                    call.save()

                    FlowRun.create(flow,
                                   contact,
                                   session=session,
                                   connection=call)
                    response = Flow.handle_call(call)
                    return HttpResponse(str(response))

                else:

                    # we don't have an inbound trigger to deal with this call.
                    response = channel.generate_ivr_response()

                    # say nothing and hangup, this is a little rude, but if we reject the call, then
                    # they'll get a non-working number error. We send 'busy' when our server is down
                    # so we don't want to use that here either.
                    response.say("")
                    response.hangup()

                    # if they have a missed call trigger, fire that off
                    Trigger.catch_triggers(contact, Trigger.TYPE_MISSED_CALL,
                                           channel)

                    # either way, we need to hangup now
                    return HttpResponse(str(response))

        # check for call progress events, these include post-call hangup notifications
        if request.POST.get("CallbackSource", None) == "call-progress-events":
            if call_sid:
                from temba.ivr.models import IVRCall

                call = IVRCall.objects.filter(external_id=call_sid).first()
                if call:
                    call.update_status(request.POST.get("CallStatus", None),
                                       request.POST.get("CallDuration", None),
                                       "TW")
                    call.save()
                    return HttpResponse("Call status updated")
            return HttpResponse("No call found")

        return HttpResponse("Not Handled, unknown action",
                            status=400)  # pragma: no cover
Example #32
0
    def get(self, request, *args, **kwargs):
        from temba.flows.models import FlowSession
        from temba.ivr.models import IVRCall

        action = kwargs["action"].lower()

        request_body = force_text(request.body)
        request_path = request.get_full_path()
        request_method = request.method

        request_uuid = kwargs["uuid"]

        if action == "event":
            if not request_body:
                return HttpResponse("")

            body_json = json.loads(request_body)
            status = body_json.get("status", None)
            duration = body_json.get("duration", None)
            call_uuid = body_json.get("uuid", None)
            conversation_uuid = body_json.get("conversation_uuid", None)

            if call_uuid is None:
                return HttpResponse("Missing uuid parameter, ignoring")

            call = IVRCall.objects.filter(external_id=call_uuid).first()
            if not call:
                # try looking up by the conversation uuid (inbound calls start with that)
                call = IVRCall.objects.filter(
                    external_id=conversation_uuid).first()
                if call:
                    call.external_id = call_uuid
                    call.save()
                else:
                    response = dict(message="Call not found for %s" %
                                    call_uuid)
                    return JsonResponse(response)

            channel = call.channel
            channel_type = channel.channel_type
            call.update_status(status, duration, channel_type)
            call.save()

            response = dict(description="Updated call status",
                            call=dict(status=call.get_status_display(),
                                      duration=call.duration))

            event = HttpEvent(request_method, request_path, request_body, 200,
                              json.dumps(response))
            ChannelLog.log_ivr_interaction(call, "Updated call status", event)

            if call.status == IVRCall.COMPLETED:
                # if our call is completed, hangup
                runs = FlowRun.objects.filter(connection=call)
                for run in runs:
                    if not run.is_completed():
                        run.set_completed(exit_uuid=None)

            return JsonResponse(response)

        if action == "answer":
            if not request_body:
                return HttpResponse("")

            body_json = json.loads(request_body)
            from_number = body_json.get("from", None)
            channel_number = body_json.get("to", None)
            external_id = body_json.get("conversation_uuid", None)

            if not from_number or not channel_number or not external_id:
                return HttpResponse("Missing parameters, Ignoring")

            # look up the channel
            address_q = Q(address=channel_number) | Q(address=("+" +
                                                               channel_number))
            channel = Channel.objects.filter(address_q).filter(
                is_active=True, channel_type="NX").first()

            # make sure we got one, and that it matches the key for our org
            org_uuid = None
            if channel:
                org_uuid = channel.org.config.get(NEXMO_UUID, None)

            if not channel or org_uuid != request_uuid:
                return HttpResponse("Channel not found for number: %s" %
                                    channel_number,
                                    status=404)

            urn = URN.from_tel(from_number)
            contact, urn_obj = Contact.get_or_create(channel.org, urn, channel)

            flow = Trigger.find_flow_for_inbound_call(contact)

            if flow:
                call = IVRCall.create_incoming(channel, contact, urn_obj,
                                               channel.created_by, external_id)
                session = FlowSession.create(contact, connection=call)

                FlowRun.create(flow, contact, session=session, connection=call)
                response = Flow.handle_call(call)

                event = HttpEvent(request_method, request_path, request_body,
                                  200, str(response))
                ChannelLog.log_ivr_interaction(call,
                                               "Incoming request for call",
                                               event)
                return JsonResponse(json.loads(str(response)), safe=False)
            else:
                # we don't have an inbound trigger to deal with this call.
                response = channel.generate_ivr_response()

                # say nothing and hangup, this is a little rude, but if we reject the call, then
                # they'll get a non-working number error. We send 'busy' when our server is down
                # so we don't want to use that here either.
                response.say("")
                response.hangup()

                # if they have a missed call trigger, fire that off
                Trigger.catch_triggers(contact, Trigger.TYPE_MISSED_CALL,
                                       channel)

                # either way, we need to hangup now
                return JsonResponse(json.loads(str(response)), safe=False)
Example #33
0
 def prepare_for_serialization(self, object_list):
     # initialize caches of all contact fields and URNs
     org = self.request.user.get_org()
     Contact.bulk_cache_initialize(org, object_list)
Example #34
0
    def setUp(self):
        self.clear_cache()

        self.superuser = User.objects.create_superuser(username="******",
                                                       email="*****@*****.**",
                                                       password="******")

        # create different user types
        self.non_org_user = self.create_user("NonOrg")
        self.user = self.create_user("User")
        self.editor = self.create_user("Editor")
        self.admin = self.create_user("Administrator")

        # setup admin boundaries for Rwanda
        self.country = AdminBoundary.objects.create(osm_id='171496',
                                                    name='Rwanda',
                                                    level=0)
        state1 = AdminBoundary.objects.create(osm_id='1708283',
                                              name='Kigali City',
                                              level=1,
                                              parent=self.country)
        state2 = AdminBoundary.objects.create(osm_id='171591',
                                              name='Eastern Province',
                                              level=1,
                                              parent=self.country)
        AdminBoundary.objects.create(osm_id='1711131',
                                     name='Gatsibo',
                                     level=2,
                                     parent=state2)
        AdminBoundary.objects.create(osm_id='1711163',
                                     name='Kayonza',
                                     level=2,
                                     parent=state2)
        AdminBoundary.objects.create(osm_id='60485579',
                                     name='Kigali',
                                     level=2,
                                     parent=state1)
        AdminBoundary.objects.create(osm_id='1711142',
                                     name='Rwamagana',
                                     level=2,
                                     parent=state2)

        self.org = Org.objects.create(name="Temba",
                                      timezone="Africa/Kigali",
                                      country=self.country,
                                      created_by=self.user,
                                      modified_by=self.user)
        self.org.initialize()

        # add users to the org
        self.user.set_org(self.org)
        self.org.viewers.add(self.user)

        self.editor.set_org(self.org)
        self.org.editors.add(self.editor)

        self.admin.set_org(self.org)
        self.org.administrators.add(self.admin)

        self.superuser.set_org(self.org)

        # welcome topup with 1000 credits
        self.welcome_topup = self.org.topups.all()[0]

        # a single Android channel
        self.channel = Channel.objects.create(org=self.org,
                                              name="Test Channel",
                                              address="+250785551212",
                                              country='RW',
                                              channel_type='A',
                                              secret="12345",
                                              gcm_id="123",
                                              created_by=self.user,
                                              modified_by=self.user)

        # reset our simulation to False
        Contact.set_simulation(False)
Example #35
0
    def validate_urn(self, value):
        if self.context['org'].is_anon:
            raise serializers.ValidationError("Referencing by URN not allowed for anonymous organizations")

        self.instance = Contact.from_urn(self.context['org'], value)
        return value
Example #36
0
    def save(self):
        """
        Update our contact
        """
        name = self.validated_data.get("name")
        fields = self.validated_data.get("fields")
        language = self.validated_data.get("language")

        # treat empty names as None
        if not name:
            name = None

        changed = []

        if self.instance:
            if self.parsed_urns is not None:
                self.instance.update_urns(self.user, self.parsed_urns)

            # update our name and language
            if name != self.instance.name:
                self.instance.name = name
                changed.append("name")
        else:
            self.instance = Contact.get_or_create_by_urns(
                self.org,
                self.user,
                name,
                urns=self.parsed_urns,
                language=language,
                force_urn_update=True)

        # Contact.get_or_create doesn't nullify language so do that here
        if "language" in self.validated_data and language is None:
            self.instance.language = language.lower() if language else None
            changed.append("language")

        # save our contact if it changed
        if changed:
            self.instance.save(update_fields=changed, handle_update=True)

        # update our fields
        if fields is not None:
            for key, value in fields.items():
                existing_by_key = ContactField.user_fields.filter(
                    org=self.org, key__iexact=key, is_active=True).first()
                if existing_by_key:
                    self.instance.set_field(self.user, existing_by_key.key,
                                            value)
                    continue
                elif self.new_fields and key in self.new_fields:
                    new_field = ContactField.get_or_create(
                        org=self.org,
                        user=self.user,
                        key=regex.sub("[^A-Za-z0-9]+", "_", key).lower(),
                        label=key)
                    self.instance.set_field(self.user, new_field.key, value)

                # TODO as above, need to get users to stop updating via label
                existing_by_label = ContactField.get_by_label(self.org, key)
                if existing_by_label:
                    self.instance.set_field(self.user, existing_by_label.key,
                                            value)

        # update our contact's groups
        if self.group_objs is not None:
            self.instance.update_static_groups(self.user, self.group_objs)

        return self.instance
Example #37
0
class USSDSession(ChannelSession):
    USSD_PULL = INCOMING = "I"
    USSD_PUSH = OUTGOING = "O"

    objects = USSDQuerySet.as_manager()

    class Meta:
        proxy = True

    @property
    def should_end(self):
        return self.status == self.ENDING

    def mark_ending(self):  # session to be ended
        if self.status != self.ENDING:
            self.status = self.ENDING
            self.save(update_fields=["status"])

    def close(self):  # session has successfully ended
        if self.status == self.ENDING:
            self.status = self.COMPLETED
        else:
            self.status = self.INTERRUPTED

        self.ended_on = timezone.now()
        self.save(update_fields=["status", "ended_on"])

    def start_async(self, flow, date, message_id):
        from temba.msgs.models import Msg, USSD

        message = Msg.objects.create(
            channel=self.channel,
            contact=self.contact,
            contact_urn=self.contact_urn,
            sent_on=date,
            connection=self,
            msg_type=USSD,
            external_id=message_id,
            created_on=timezone.now(),
            modified_on=timezone.now(),
            org=self.channel.org,
            direction=self.INCOMING,
        )
        flow.start([], [self.contact],
                   start_msg=message,
                   restart_participants=True,
                   connection=self)

    def handle_async(self, urn, content, date, message_id):
        from temba.msgs.models import Msg, USSD

        Msg.create_incoming(
            channel=self.channel,
            org=self.org,
            urn=urn,
            text=content or "",
            sent_on=date,
            connection=self,
            msg_type=USSD,
            external_id=message_id,
        )

    def handle_sync(self):  # pragma: needs cover
        # TODO: implement for InfoBip and other sync APIs
        pass

    @classmethod
    def handle_incoming(
        cls,
        channel,
        urn,
        date,
        external_id,
        contact=None,
        message_id=None,
        status=None,
        content=None,
        starcode=None,
        org=None,
        async=True,
    ):

        trigger = None
        contact_urn = None

        # handle contact with channel
        urn = URN.from_tel(urn)

        if not contact:
            contact, contact_urn = Contact.get_or_create(
                channel.org, urn, channel)
        elif urn:
            contact_urn = ContactURN.get_or_create(org,
                                                   contact,
                                                   urn,
                                                   channel=channel)

        contact.set_preferred_channel(channel)

        if contact_urn:
            contact_urn.update_affinity(channel)

        # setup session
        defaults = dict(channel=channel,
                        contact=contact,
                        contact_urn=contact_urn,
                        org=channel.org if channel else contact.org)

        if status == cls.TRIGGERED:
            trigger = Trigger.find_trigger_for_ussd_session(contact, starcode)
            if not trigger:
                return False
            defaults.update(
                dict(started_on=date, direction=cls.USSD_PULL, status=status))

        elif status == cls.INTERRUPTED:
            defaults.update(dict(ended_on=date, status=status))

        else:
            defaults.update(dict(status=cls.IN_PROGRESS))

        # check if there's an initiated PUSH connection
        connection = cls.objects.get_initiated_push(contact)

        created = False
        if not connection:
            try:
                connection = (cls.objects.select_for_update().exclude(
                    status__in=ChannelSession.DONE).get(
                        external_id=external_id))
                created = False
                for k, v in defaults.items():
                    setattr(connection, k, v() if callable(v) else v)
                connection.save()
            except cls.DoesNotExist:
                defaults["external_id"] = external_id
                connection = cls.objects.create(**defaults)
                FlowSession.create(contact, connection=connection)
                created = True
        else:
            defaults.update(dict(external_id=external_id))
            for key, value in defaults.items():
                setattr(connection, key, value)
            connection.save()
            created = None

        # start session
        if created and async and trigger:
            connection.start_async(trigger.flow, date, message_id)
Example #38
0
    def test_ivr_options(self):

        # should be able to create an ivr flow
        self.assertTrue(self.org.supports_ivr())
        self.assertTrue(self.admin.groups.filter(name="Beta"))
        self.assertContains(self.client.get(reverse('flows.flow_create')),
                            'Phone Call')

        # no twilio config yet
        self.assertFalse(self.org.is_connected_to_twilio())
        self.assertIsNone(self.org.get_twilio_client())

        # connect it and check our client is configured
        self.org.connect_twilio("TEST_SID", "TEST_TOKEN")
        self.org.save()
        self.assertTrue(self.org.is_connected_to_twilio())
        self.assertIsNotNone(self.org.get_twilio_client())

        # import an ivr flow
        self.import_file('call-me-maybe')

        # make sure our flow is there as expected
        flow = Flow.objects.filter(name='Call me maybe').first()
        self.assertEquals('callme', flow.triggers.all().first().keyword)

        user_settings = self.admin.get_settings()
        user_settings.tel = '+18005551212'
        user_settings.save()

        # start our flow`
        eric = self.create_contact('Eric Newcomer', number='+13603621737')
        eric.is_test = True
        eric.save()
        Contact.set_simulation(True)
        flow.start([], [eric])

        # should be using the usersettings number in test mode
        self.assertEquals('Placing test call to +1 800-555-1212',
                          ActionLog.objects.all().first().text)

        # we should have an outbound ivr call now
        call = IVRCall.objects.filter(direction=OUTGOING).first()

        self.assertEquals(0, call.get_duration())
        self.assertIsNotNone(call)
        self.assertEquals('CallSid', call.external_id)

        # after a call is picked up, twilio will call back to our server
        post_data = dict(CallSid='CallSid',
                         CallStatus='in-progress',
                         CallDuration=20)
        response = self.client.post(
            reverse('ivr.ivrcall_handle', args=[call.pk]), post_data)
        self.assertContains(
            response,
            '<Say>Would you like me to call you? Press one for yes, two for no, or three for maybe.</Say>'
        )

        # updated our status and duration accordingly
        call = IVRCall.objects.get(pk=call.pk)
        self.assertEquals(20, call.duration)
        self.assertEquals(IN_PROGRESS, call.status)

        # should mention our our action log that we read a message to them
        run = FlowRun.objects.all().first()
        logs = ActionLog.objects.filter(run=run).order_by('-pk')
        self.assertEquals(2, len(logs))
        self.assertEquals(
            'Read message "Would you like me to call you? Press one for yes, two for no, or three for maybe."',
            logs.first().text)

        # press the number 4 (unexpected)
        response = self.client.post(
            reverse('ivr.ivrcall_handle', args=[call.pk]), dict(Digits=4))
        self.assertContains(response,
                            '<Say>Press one, two, or three. Thanks.</Say>')

        # now let's have them press the number 3 (for maybe)
        response = self.client.post(
            reverse('ivr.ivrcall_handle', args=[call.pk]), dict(Digits=3))
        self.assertContains(response, '<Say>This might be crazy.</Say>')

        # twilio would then disconnect the user and notify us of a completed call
        self.client.post(reverse('ivr.ivrcall_handle', args=[call.pk]),
                         dict(CallStatus='completed'))
        self.assertEquals(COMPLETED, IVRCall.objects.get(pk=call.pk).status)

        # simulation gets flipped off by middleware, and this unhandled message doesn't flip it back on
        self.assertFalse(Contact.get_simulation())

        # test other our call status mappings with twilio
        def test_status_update(call_to_update, twilio_status, temba_status):
            call_to_update.update_status(twilio_status, 0)
            call_to_update.save()
            self.assertEquals(temba_status,
                              IVRCall.objects.get(pk=call_to_update.pk).status)

        test_status_update(call, 'queued', QUEUED)
        test_status_update(call, 'ringing', RINGING)
        test_status_update(call, 'canceled', CANCELED)
        test_status_update(call, 'busy', BUSY)
        test_status_update(call, 'failed', FAILED)
        test_status_update(call, 'no-answer', NO_ANSWER)

        # explicitly hanging up an in progress call should remove it
        call.update_status('in-progress', 0)
        call.save()
        IVRCall.hangup_test_call(flow)
        self.assertIsNone(IVRCall.objects.filter(pk=call.pk).first())
Example #39
0
    def __call__(self, request):
        Contact.set_simulation(False)

        response = self.get_response(request)
        return response
Example #40
0
    def test_new_conversation_trigger(self):
        self.login(self.admin)
        flow = self.create_flow()
        flow2 = self.create_flow()

        # see if we list new conversation triggers on the trigger page
        create_trigger_url = reverse('triggers.trigger_create', args=[])
        response = self.client.get(create_trigger_url)
        self.assertNotContains(response, "conversation is started")

        # create a facebook channel
        fb_channel = Channel.add_facebook_channel(self.org, self.user, 'Temba',
                                                  1001, 'fb_token')

        # should now be able to create one
        response = self.client.get(create_trigger_url)
        self.assertContains(response, "conversation is started")

        # go create it
        with patch('requests.post') as mock_post:
            mock_post.return_value = MockResponse(200,
                                                  '{"message": "Success"}')

            response = self.client.post(reverse(
                'triggers.trigger_new_conversation', args=[]),
                                        data=dict(channel=fb_channel.id,
                                                  flow=flow.id))
            self.assertEqual(response.status_code, 200)
            self.assertEqual(mock_post.call_count, 1)

        # check that it is right
        trigger = Trigger.objects.get(
            trigger_type=Trigger.TYPE_NEW_CONVERSATION,
            is_active=True,
            is_archived=False)
        self.assertEqual(trigger.channel, fb_channel)
        self.assertEqual(trigger.flow, flow)

        # try to create another one, fails as we already have a trigger for that channel
        response = self.client.post(reverse(
            'triggers.trigger_new_conversation', args=[]),
                                    data=dict(channel=fb_channel.id,
                                              flow=flow2.id))
        self.assertEqual(response.status_code, 200)
        self.assertFormError(response, 'form', 'channel',
                             'Trigger with this Channel already exists.')

        # ok, trigger a facebook event
        data = json.loads("""{
        "object": "page",
          "entry": [
            {
              "id": "620308107999975",
              "time": 1467841778778,
              "messaging": [
                {
                  "sender":{
                    "id":"1001"
                  },
                  "recipient":{
                    "id":"%s"
                  },
                  "timestamp":1458692752478,
                  "postback":{
                    "payload":"get_started"
                  }
                }
              ]
            }
          ]
        }
        """ % fb_channel.address)

        with patch('requests.get') as mock_get:
            mock_get.return_value = MockResponse(
                200, '{"first_name": "Ben","last_name": "Haggerty"}')

            callback_url = reverse('handlers.facebook_handler',
                                   args=[fb_channel.uuid])
            response = self.client.post(callback_url,
                                        json.dumps(data),
                                        content_type="application/json")
            self.assertEqual(response.status_code, 200)

            # should have a new flow run for Ben
            contact = Contact.from_urn(self.org, 'facebook:1001')
            self.assertTrue(contact.name, "Ben Haggerty")

            run = FlowRun.objects.get(contact=contact)
            self.assertEqual(run.flow, flow)

        # archive our trigger, should unregister our callback
        with patch('requests.post') as mock_post:
            mock_post.return_value = MockResponse(200,
                                                  '{"message": "Success"}')

            Trigger.apply_action_archive(self.admin,
                                         Trigger.objects.filter(pk=trigger.pk))
            self.assertEqual(response.status_code, 200)
            self.assertEqual(mock_post.call_count, 1)

            trigger.refresh_from_db()
            self.assertTrue(trigger.is_archived)
Example #41
0
    def test_event_deliveries(self):
        sms = self.create_msg(contact=self.joe, direction="I", status="H", text="I'm gonna pop some tags")

        with patch("requests.Session.send") as mock:
            now = timezone.now()
            mock.return_value = MockResponse(200, "Hello World")

            # trigger an event, shouldnn't fire as we don't have a webhook
            WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now)
            self.assertFalse(WebHookEvent.objects.all())

        self.setupChannel()

        with patch("requests.Session.send") as mock:
            # clear out which events we listen for, we still shouldnt be notified though we have a webhook
            self.channel.org.webhook_events = 0
            self.channel.org.save()

            now = timezone.now()
            mock.return_value = MockResponse(200, "Hello World")

            # trigger an event, shouldnn't fire as we don't have a webhook
            WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now)
            self.assertFalse(WebHookEvent.objects.all())

        self.setupChannel()

        with patch("requests.Session.send") as mock:
            # remove all the org users
            self.org.administrators.clear()
            self.org.editors.clear()
            self.org.viewers.clear()

            mock.return_value = MockResponse(200, "Hello World")

            # trigger an event
            WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now)
            event = WebHookEvent.objects.get()

            self.assertEqual("F", event.status)
            self.assertEqual(0, event.try_count)
            self.assertFalse(event.next_attempt)

            result = WebHookResult.objects.get()
            self.assertIn("No active user", result.message)
            self.assertEqual(0, result.status_code)

            self.assertFalse(mock.called)

            # what if they send weird json back?
            self.release(WebHookEvent.objects.all())

        # add ad manager back in
        self.org.administrators.add(self.admin)
        self.admin.set_org(self.org)

        with patch("requests.Session.send") as mock:
            mock.return_value = MockResponse(200, "Hello World")

            # trigger an event
            WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now)
            event = WebHookEvent.objects.get()

            self.assertEqual("C", event.status)
            self.assertEqual(1, event.try_count)
            self.assertFalse(event.next_attempt)

            result = WebHookResult.objects.get()
            self.assertIn("Event delivered successfully", result.message)
            self.assertIn("not JSON", result.message)
            self.assertEqual(200, result.status_code)

            self.assertTrue(mock.called)

            self.release(WebHookEvent.objects.all())

        with patch("requests.Session.send") as mock:
            mock.side_effect = [MockResponse(500, "I am error")]

            # trigger an event
            WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now)
            event = WebHookEvent.objects.all().first()

            self.assertEqual("E", event.status)
            self.assertEqual(1, event.try_count)
            self.assertTrue(event.next_attempt)

            mock.return_value = MockResponse(200, "Hello World")
            # simulate missing channel
            event.channel = None
            event.save()

            # no exception should raised
            event.deliver()

            self.assertTrue(mock.called)
            self.assertEqual(mock.call_count, 2)

            self.release(WebHookEvent.objects.all())

        with patch("requests.Session.send") as mock:
            # valid json, but not our format
            bad_json = '{ "thrift_shops": ["Goodwill", "Value Village"] }'
            mock.return_value = MockResponse(200, bad_json)

            WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now)
            event = WebHookEvent.objects.get()

            self.assertEqual("C", event.status)
            self.assertEqual(1, event.try_count)
            self.assertFalse(event.next_attempt)

            self.assertTrue(mock.called)

            result = WebHookResult.objects.get()
            self.assertIn("Event delivered successfully", result.message)
            self.assertIn("ignoring", result.message)
            self.assertEqual(200, result.status_code)
            self.assertEqual(bad_json, result.body)

            self.release(WebHookEvent.objects.all())

        with patch("requests.Session.send") as mock:
            mock.return_value = MockResponse(200, '{ "phone": "+250788123123", "text": "I am success" }')

            WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now)
            event = WebHookEvent.objects.get()

            self.assertEqual("C", event.status)
            self.assertEqual(1, event.try_count)
            self.assertFalse(event.next_attempt)

            result = WebHookResult.objects.get()
            self.assertEqual(200, result.status_code)

            self.assertTrue(mock.called)

            broadcast = Broadcast.objects.get()
            contact, urn_obj = Contact.get_or_create(self.org, "tel:+250788123123", self.channel, user=self.admin)
            self.assertTrue(broadcast.text, {"base": "I am success"})
            self.assertTrue(contact, broadcast.contacts.all())

            self.assertTrue(mock.called)
            args = mock.call_args_list[0][0]
            prepared_request = args[0]
            self.assertEqual(self.org.get_webhook_url(), prepared_request.url)

            data = parse_qs(prepared_request.body)
            self.assertEqual(self.joe.get_urn(TEL_SCHEME).path, data["phone"][0])
            self.assertEqual(str(self.joe.get_urn(TEL_SCHEME)), data["urn"][0])
            self.assertEqual(self.joe.uuid, data["contact"][0])
            self.assertEqual(self.joe.name, data["contact_name"][0])
            self.assertEqual(sms.pk, int(data["sms"][0]))
            self.assertEqual(self.channel.pk, int(data["channel"][0]))
            self.assertEqual(WebHookEvent.TYPE_SMS_RECEIVED, data["event"][0])
            self.assertEqual("I'm gonna pop some tags", data["text"][0])
            self.assertIn("time", data)

            self.release(WebHookEvent.objects.all())

        with patch("requests.Session.send") as mock:
            mock.return_value = MockResponse(500, "I am error")

            next_attempt_earliest = timezone.now() + timedelta(minutes=4)
            next_attempt_latest = timezone.now() + timedelta(minutes=6)

            WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now)
            event = WebHookEvent.objects.get()

            self.assertEqual("E", event.status)
            self.assertEqual(1, event.try_count)
            self.assertTrue(event.next_attempt)
            self.assertTrue(next_attempt_earliest < event.next_attempt and next_attempt_latest > event.next_attempt)

            result = WebHookResult.objects.get()
            self.assertIn("Error", result.message)
            self.assertEqual(500, result.status_code)
            self.assertEqual("I am error", result.body)

            # make sure things become failures after three retries
            event.try_count = 2
            event.deliver()
            event.save()

            self.assertTrue(mock.called)

            self.assertEqual("F", event.status)
            self.assertEqual(3, event.try_count)
            self.assertFalse(event.next_attempt)

            result = WebHookResult.objects.get()
            self.assertIn("Error", result.message)
            self.assertEqual(500, result.status_code)
            self.assertEqual("I am error", result.body)
            self.assertEqual("http://fake.com/webhook.php", result.url)
            self.assertTrue(result.data.find("pop+some+tags") > 0)

            # check out our api log
            response = self.client.get(reverse("api.log"))
            self.assertRedirect(response, reverse("users.user_login"))

            response = self.client.get(reverse("api.log_read", args=[event.pk]))
            self.assertRedirect(response, reverse("users.user_login"))

            self.release(WebHookEvent.objects.all())

        # add a webhook header to the org
        self.channel.org.webhook = {
            "url": "http://fake.com/webhook.php",
            "headers": {"X-My-Header": "foobar", "Authorization": "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="},
            "method": "POST",
        }
        self.channel.org.save()

        # check that our webhook settings have saved
        self.assertEqual("http://fake.com/webhook.php", self.channel.org.get_webhook_url())
        self.assertDictEqual(
            {"X-My-Header": "foobar", "Authorization": "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="},
            self.channel.org.get_webhook_headers(),
        )

        with patch("requests.Session.send") as mock:
            mock.return_value = MockResponse(200, "Boom")
            WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now)
            event = WebHookEvent.objects.get()

            result = WebHookResult.objects.get()
            # both headers should be in the json-encoded url string
            self.assertIn("X-My-Header: foobar", result.request)
            self.assertIn("Authorization: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", result.request)
Example #42
0
    def test_event_deliveries(self):
        sms = self.create_msg(contact=self.joe,
                              direction='I',
                              status='H',
                              text="I'm gonna pop some tags")

        with patch('requests.Session.send') as mock:
            now = timezone.now()
            mock.return_value = MockResponse(200, "Hello World")

            # trigger an event, shouldnn't fire as we don't have a webhook
            WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now)
            self.assertFalse(WebHookEvent.objects.all())

        self.setupChannel()

        with patch('requests.Session.send') as mock:
            # clear out which events we listen for, we still shouldnt be notified though we have a webhook
            self.channel.org.webhook_events = 0
            self.channel.org.save()

            now = timezone.now()
            mock.return_value = MockResponse(200, "Hello World")

            # trigger an event, shouldnn't fire as we don't have a webhook
            WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now)
            self.assertFalse(WebHookEvent.objects.all())

        self.setupChannel()

        with patch('requests.Session.send') as mock:
            # remove all the org users
            self.org.administrators.clear()
            self.org.editors.clear()
            self.org.viewers.clear()

            mock.return_value = MockResponse(200, "Hello World")

            # trigger an event
            WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now)
            event = WebHookEvent.objects.get()

            self.assertEquals('F', event.status)
            self.assertEquals(0, event.try_count)
            self.assertFalse(event.next_attempt)

            result = WebHookResult.objects.get()
            self.assertStringContains("No active user", result.message)
            self.assertEquals(0, result.status_code)

            self.assertFalse(mock.called)

            # what if they send weird json back?
            WebHookEvent.objects.all().delete()
            WebHookResult.objects.all().delete()

        # add ad manager back in
        self.org.administrators.add(self.admin)
        self.admin.set_org(self.org)

        with patch('requests.Session.send') as mock:
            mock.return_value = MockResponse(200, "Hello World")

            # trigger an event
            WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now)
            event = WebHookEvent.objects.get()

            self.assertEquals('C', event.status)
            self.assertEquals(1, event.try_count)
            self.assertFalse(event.next_attempt)

            result = WebHookResult.objects.get()
            self.assertStringContains("Event delivered successfully",
                                      result.message)
            self.assertStringContains("not JSON", result.message)
            self.assertEquals(200, result.status_code)

            self.assertTrue(mock.called)

            WebHookEvent.objects.all().delete()
            WebHookResult.objects.all().delete()

        with patch('requests.Session.send') as mock:
            # valid json, but not our format
            bad_json = '{ "thrift_shops": ["Goodwill", "Value Village"] }'
            mock.return_value = MockResponse(200, bad_json)

            WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now)
            event = WebHookEvent.objects.get()

            self.assertEquals('C', event.status)
            self.assertEquals(1, event.try_count)
            self.assertFalse(event.next_attempt)

            self.assertTrue(mock.called)

            result = WebHookResult.objects.get()
            self.assertStringContains("Event delivered successfully",
                                      result.message)
            self.assertStringContains("ignoring", result.message)
            self.assertEquals(200, result.status_code)
            self.assertEquals(bad_json, result.body)

            WebHookEvent.objects.all().delete()
            WebHookResult.objects.all().delete()

        with patch('requests.Session.send') as mock:
            mock.return_value = MockResponse(
                200, '{ "phone": "+250788123123", "text": "I am success" }')

            WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now)
            event = WebHookEvent.objects.get()

            self.assertEquals('C', event.status)
            self.assertEquals(1, event.try_count)
            self.assertFalse(event.next_attempt)

            result = WebHookResult.objects.get()
            self.assertEquals(200, result.status_code)

            self.assertTrue(mock.called)

            broadcast = Broadcast.objects.get()
            contact = Contact.get_or_create(self.org,
                                            self.admin,
                                            name=None,
                                            urns=["tel:+250788123123"],
                                            channel=self.channel)
            self.assertTrue("I am success", broadcast.text)
            self.assertTrue(contact, broadcast.contacts.all())

            self.assertTrue(mock.called)
            args = mock.call_args_list[0][0]
            prepared_request = args[0]
            self.assertEquals(self.org.get_webhook_url(), prepared_request.url)

            data = parse_qs(prepared_request.body)
            self.assertEquals(
                self.joe.get_urn(TEL_SCHEME).path, data['phone'][0])
            self.assertEquals(unicode(self.joe.get_urn(TEL_SCHEME)),
                              data['urn'][0])
            self.assertEquals(self.joe.uuid, data['contact'][0])
            self.assertEquals(sms.pk, int(data['sms'][0]))
            self.assertEquals(self.channel.pk, int(data['channel'][0]))
            self.assertEquals(SMS_RECEIVED, data['event'][0])
            self.assertEquals("I'm gonna pop some tags", data['text'][0])
            self.assertTrue('time' in data)

            WebHookEvent.objects.all().delete()
            WebHookResult.objects.all().delete()

        with patch('requests.Session.send') as mock:
            mock.return_value = MockResponse(500, "I am error")

            next_attempt_earliest = timezone.now() + timedelta(minutes=4)
            next_attempt_latest = timezone.now() + timedelta(minutes=6)

            WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now)
            event = WebHookEvent.objects.get()

            self.assertEquals('E', event.status)
            self.assertEquals(1, event.try_count)
            self.assertTrue(event.next_attempt)
            self.assertTrue(next_attempt_earliest < event.next_attempt
                            and next_attempt_latest > event.next_attempt)

            result = WebHookResult.objects.get()
            self.assertStringContains("Error", result.message)
            self.assertEquals(500, result.status_code)
            self.assertEquals("I am error", result.body)

            # make sure things become failures after three retries
            event.try_count = 2
            event.deliver()
            event.save()

            self.assertTrue(mock.called)

            self.assertEquals('F', event.status)
            self.assertEquals(3, event.try_count)
            self.assertFalse(event.next_attempt)

            result = WebHookResult.objects.get()
            self.assertStringContains("Error", result.message)
            self.assertEquals(500, result.status_code)
            self.assertEquals("I am error", result.body)
            self.assertEquals("http://fake.com/webhook.php", result.url)
            self.assertTrue(result.data.find("pop+some+tags") > 0)

            # check out our api log
            response = self.client.get(reverse('api.log'))
            self.assertRedirect(response, reverse('users.user_login'))

            response = self.client.get(reverse('api.log_read',
                                               args=[event.pk]))
            self.assertRedirect(response, reverse('users.user_login'))

            WebHookEvent.objects.all().delete()
            WebHookResult.objects.all().delete()

        # add a webhook header to the org
        self.channel.org.webhook = u'{"url": "http://fake.com/webhook.php", "headers": {"X-My-Header": "foobar", "Authorization": "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="}, "method": "POST"}'
        self.channel.org.save()

        # check that our webhook settings have saved
        self.assertEquals('http://fake.com/webhook.php',
                          self.channel.org.get_webhook_url())
        self.assertDictEqual(
            {
                'X-My-Header':
                'foobar',
                'Authorization':
                'Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
            }, self.channel.org.get_webhook_headers())

        with patch('requests.Session.send') as mock:
            mock.return_value = MockResponse(200, "Boom")
            WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now)
            event = WebHookEvent.objects.get()

            result = WebHookResult.objects.get()
            # both headers should be in the json-encoded url string
            self.assertStringContains('X-My-Header: foobar', result.request)
            self.assertStringContains(
                'Authorization: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==',
                result.request)
Example #43
0
    def test_deleting_reimport_contact_groups(self):
        campaign = Campaign.create(self.org, self.admin, "Planting Reminders", self.farmers)

        # create a reminder for our first planting event
        planting_reminder = CampaignEvent.create_flow_event(
            self.org, self.admin, campaign, relative_to=self.planting_date, offset=3, unit="D", flow=self.reminder_flow
        )

        self.assertEqual(0, EventFire.objects.all().count())
        self.farmer1.set_field(self.user, "planting_date", "10-05-2020 12:30:10")
        self.farmer2.set_field(self.user, "planting_date", "15-05-2020 12:30:10")

        # now we have event fires accordingly
        self.assertEqual(2, EventFire.objects.all().count())

        # farmer one fire
        scheduled = EventFire.objects.get(contact=self.farmer1, event=planting_reminder).scheduled
        self.assertEqual("13-5-2020", "%s-%s-%s" % (scheduled.day, scheduled.month, scheduled.year))

        # farmer two fire
        scheduled = EventFire.objects.get(contact=self.farmer2, event=planting_reminder).scheduled
        self.assertEqual("18-5-2020", "%s-%s-%s" % (scheduled.day, scheduled.month, scheduled.year))

        # delete our farmers group
        self.farmers.release()

        # this should have removed all the event fires for that group
        self.assertEqual(0, EventFire.objects.filter(event=planting_reminder).count())

        # and our group is no longer active
        self.assertFalse(campaign.group.is_active)

        # now import the group again
        filename = "farmers.csv"
        extra_fields = [dict(key="planting_date", header="planting_date", label="Planting Date", type="D")]
        import_params = dict(
            org_id=self.org.id, timezone=str(self.org.timezone), extra_fields=extra_fields, original_filename=filename
        )

        task = ImportTask.objects.create(
            created_by=self.admin,
            modified_by=self.admin,
            csv_file="test_imports/" + filename,
            model_class="Contact",
            import_params=json.dumps(import_params),
            import_log="",
            task_id="A",
        )
        Contact.import_csv(task, log=None)

        # check that we have new planting dates
        self.farmer1 = Contact.objects.get(pk=self.farmer1.pk)
        self.farmer2 = Contact.objects.get(pk=self.farmer2.pk)

        planting = self.farmer1.get_field_value(self.planting_date)
        self.assertEqual("10-8-2020", "%s-%s-%s" % (planting.day, planting.month, planting.year))

        planting = self.farmer2.get_field_value(self.planting_date)
        self.assertEqual("15-8-2020", "%s-%s-%s" % (planting.day, planting.month, planting.year))

        # now update the campaign
        self.farmers = ContactGroup.user_groups.get(name="Farmers")
        self.login(self.admin)
        post_data = dict(name="Planting Reminders", group=self.farmers.pk)
        self.client.post(reverse("campaigns.campaign_update", args=[campaign.pk]), post_data)

        # should have two fresh new fires
        self.assertEqual(2, EventFire.objects.all().count())

        # check their new planting dates
        scheduled = EventFire.objects.get(contact=self.farmer1, event=planting_reminder).scheduled
        self.assertEqual("13-8-2020", "%s-%s-%s" % (scheduled.day, scheduled.month, scheduled.year))

        # farmer two fire
        scheduled = EventFire.objects.get(contact=self.farmer2, event=planting_reminder).scheduled
        self.assertEqual("18-8-2020", "%s-%s-%s" % (scheduled.day, scheduled.month, scheduled.year))

        # give our non farmer a planting date
        self.nonfarmer.set_field(self.user, "planting_date", "20-05-2020 12:30:10")

        # now update to the non-farmer group
        self.nonfarmers = self.create_group("Not Farmers", [self.nonfarmer])
        post_data = dict(name="Planting Reminders", group=self.nonfarmers.pk)
        self.client.post(reverse("campaigns.campaign_update", args=[campaign.pk]), post_data)

        # only one fire for the non-farmer the previous two should be deleted by the group change
        self.assertEqual(1, EventFire.objects.all().count())
        self.assertEqual(1, EventFire.objects.filter(contact=self.nonfarmer).count())
Example #44
0
    def send_message(
        self,
        flow,
        message,
        restart_participants=False,
        contact=None,
        initiate_flow=False,
        assert_reply=True,
        assert_handle=True,
    ):
        """
        Starts the flow, sends the message, returns the reply
        """
        if not contact:
            contact = self.contact
        try:
            if contact.is_test:
                Contact.set_simulation(True)

            incoming = self.create_msg(direction=INCOMING,
                                       contact=contact,
                                       contact_urn=contact.get_urn(),
                                       text=message)

            # start the flow
            if initiate_flow:
                flow.start(groups=[],
                           contacts=[contact],
                           restart_participants=restart_participants,
                           start_msg=incoming)
            else:
                flow.start(groups=[],
                           contacts=[contact],
                           restart_participants=restart_participants)
                (handled, msgs) = Flow.find_and_handle(incoming)

                Msg.mark_handled(incoming)

                if assert_handle:
                    self.assertTrue(
                        handled,
                        "'%s' did not handle message as expected" % flow.name)
                else:
                    self.assertFalse(
                        handled,
                        "'%s' handled message, was supposed to ignore" %
                        flow.name)

            # our message should have gotten a reply
            if assert_reply:
                replies = Msg.objects.filter(
                    response_to=incoming).order_by("pk")
                self.assertGreaterEqual(len(replies), 1)

                if len(replies) == 1:
                    self.assertEqual(contact, replies.first().contact)
                    return replies.first().text

                # if it's more than one, send back a list of replies
                return [reply.text for reply in replies]

            else:
                # assert we got no reply
                replies = Msg.objects.filter(
                    response_to=incoming).order_by("pk")
                self.assertFalse(replies)

            return None

        finally:
            Contact.set_simulation(False)
Example #45
0
    def test_ivr_flow(self):
        # should be able to create an ivr flow
        self.assertTrue(self.org.supports_ivr())
        self.assertTrue(self.admin.groups.filter(name="Beta"))
        self.assertContains(self.client.get(reverse('flows.flow_create')),
                            'Phone Call')

        # no twilio config yet
        self.assertFalse(self.org.is_connected_to_twilio())
        self.assertIsNone(self.org.get_twilio_client())

        # connect it and check our client is configured
        self.org.connect_twilio("TEST_SID", "TEST_TOKEN")
        self.org.save()
        self.assertTrue(self.org.is_connected_to_twilio())
        self.assertIsNotNone(self.org.get_twilio_client())

        # import an ivr flow
        self.import_file('call-me-maybe')

        # make sure our flow is there as expected
        flow = Flow.objects.filter(name='Call me maybe').first()
        self.assertEquals(
            'callme',
            flow.triggers.filter(trigger_type='K').first().keyword)

        user_settings = self.admin.get_settings()
        user_settings.tel = '+18005551212'
        user_settings.save()

        # start our flow as a test contact
        test_contact = Contact.get_test_contact(self.admin)
        Contact.set_simulation(True)
        flow.start([], [test_contact])
        call = IVRCall.objects.filter(direction=OUTGOING).first()

        # should be using the usersettings number in test mode
        self.assertEquals('Placing test call to +1 800-555-1212',
                          ActionLog.objects.all().first().text)

        # explicitly hanging up on a test call should remove it
        call.update_status('in-progress', 0)
        call.save()
        IVRCall.hangup_test_call(flow)
        self.assertIsNone(IVRCall.objects.filter(pk=call.pk).first())

        ActionLog.objects.all().delete()
        IVRCall.objects.all().delete()

        # now pretend we are a normal caller
        eric = self.create_contact('Eric Newcomer', number='+13603621737')
        Contact.set_simulation(False)
        flow.start([], [eric], restart_participants=True)

        # we should have an outbound ivr call now
        call = IVRCall.objects.filter(direction=OUTGOING).first()

        self.assertEquals(0, call.get_duration())
        self.assertIsNotNone(call)
        self.assertEquals('CallSid', call.external_id)

        # after a call is picked up, twilio will call back to our server
        post_data = dict(CallSid='CallSid',
                         CallStatus='in-progress',
                         CallDuration=20)
        response = self.client.post(
            reverse('ivr.ivrcall_handle', args=[call.pk]), post_data)

        self.assertContains(
            response,
            '<Say>Would you like me to call you? Press one for yes, two for no, or three for maybe.</Say>'
        )
        self.assertEquals(1, Msg.all_messages.filter(msg_type=IVR).count())
        self.assertEquals(1, self.org.get_credits_used())

        # make sure a message from the person on the call goes to the
        # inbox since our flow doesn't handle text messages
        msg = self.create_msg(direction='I',
                              contact=eric,
                              text="message during phone call")
        self.assertFalse(Flow.find_and_handle(msg))

        # updated our status and duration accordingly
        call = IVRCall.objects.get(pk=call.pk)
        self.assertEquals(20, call.duration)
        self.assertEquals(IN_PROGRESS, call.status)

        # press the number 4 (unexpected)
        response = self.client.post(
            reverse('ivr.ivrcall_handle', args=[call.pk]), dict(Digits=4))
        self.assertContains(response,
                            '<Say>Press one, two, or three. Thanks.</Say>')
        self.assertEquals(4, self.org.get_credits_used())

        # two more messages, one inbound and it's response
        self.assertEquals(3, Msg.all_messages.filter(msg_type=IVR).count())

        # now let's have them press the number 3 (for maybe)
        response = self.client.post(
            reverse('ivr.ivrcall_handle', args=[call.pk]), dict(Digits=3))
        self.assertContains(response, '<Say>This might be crazy.</Say>')
        messages = Msg.all_messages.filter(msg_type=IVR).order_by('pk')
        self.assertEquals(5, messages.count())
        self.assertEquals(6, self.org.get_credits_used())

        for msg in messages:
            self.assertEquals(1,
                              msg.steps.all().count(),
                              msg="Message '%s' not attached to step" %
                              msg.text)

        # twilio would then disconnect the user and notify us of a completed call
        self.client.post(reverse('ivr.ivrcall_handle', args=[call.pk]),
                         dict(CallStatus='completed'))
        call = IVRCall.objects.get(pk=call.pk)
        self.assertEquals(COMPLETED, call.status)
        self.assertFalse(FlowRun.objects.filter(call=call).first().is_active)

        # simulation gets flipped off by middleware, and this unhandled message doesn't flip it back on
        self.assertFalse(Contact.get_simulation())

        # also shouldn't have any ActionLogs for non-test users
        self.assertEquals(0, ActionLog.objects.all().count())
        self.assertEquals(1, flow.get_completed_runs())

        # should still have no active runs
        self.assertEquals(0, FlowRun.objects.filter(is_active=True).count())

        # and we've exited the flow
        step = FlowStep.objects.all().order_by('-pk').first()
        self.assertTrue(step.left_on)

        # test other our call status mappings with twilio
        def test_status_update(call_to_update, twilio_status, temba_status):
            call_to_update.update_status(twilio_status, 0)
            call_to_update.save()
            self.assertEquals(temba_status,
                              IVRCall.objects.get(pk=call_to_update.pk).status)

        test_status_update(call, 'queued', QUEUED)
        test_status_update(call, 'ringing', RINGING)
        test_status_update(call, 'canceled', CANCELED)
        test_status_update(call, 'busy', BUSY)
        test_status_update(call, 'failed', FAILED)
        test_status_update(call, 'no-answer', NO_ANSWER)

        FlowStep.objects.all().delete()
        IVRCall.objects.all().delete()

        # try sending callme trigger
        from temba.msgs.models import INCOMING
        msg = self.create_msg(direction=INCOMING, contact=eric, text="callme")

        # make sure if we are started with a message we still create a normal voice run
        flow.start([], [eric], restart_participants=True, start_msg=msg)

        # we should have an outbound ivr call now, and no steps yet
        call = IVRCall.objects.filter(direction=OUTGOING).first()
        self.assertIsNotNone(call)
        self.assertEquals(0, FlowStep.objects.all().count())

        # after a call is picked up, twilio will call back to our server
        post_data = dict(CallSid='CallSid',
                         CallStatus='in-progress',
                         CallDuration=20)
        self.client.post(reverse('ivr.ivrcall_handle', args=[call.pk]),
                         post_data)

        # should have two flow steps (the outgoing messages, and the step to handle the response)
        steps = FlowStep.objects.all().order_by('pk')

        # the first step has exactly one message which is an outgoing IVR message
        self.assertEquals(1, steps.first().messages.all().count())
        self.assertEquals(
            1,
            steps.first().messages.filter(direction=OUTGOING,
                                          msg_type=IVR).count())

        # the next step shouldn't have any messages yet since they haven't pressed anything
        self.assertEquals(0, steps[1].messages.all().count())

        # try updating our status to completed for a test contact
        Contact.set_simulation(True)
        flow.start([], [test_contact])
        call = IVRCall.objects.filter(
            direction=OUTGOING).order_by('-pk').first()
        call.update_status('completed', 30)
        call.save()
        call.refresh_from_db()

        self.assertEqual(ActionLog.objects.all().order_by('-pk').first().text,
                         'Call ended.')
        self.assertEqual(call.duration, 30)

        # now look at implied duration
        call.update_status('in-progress', None)
        call.save()
        call.refresh_from_db()
        self.assertIsNotNone(call.get_duration())
Example #46
0
def omnibox_mixed_search(org, query, types):
    """
    Performs a mixed group, contact and URN search, returning the first N matches of each type.
    """
    query_terms = query.split(" ") if query else None
    search_types = types or (SEARCH_ALL_GROUPS, SEARCH_CONTACTS, SEARCH_URNS)
    per_type_limit = 25
    results = []

    if SEARCH_ALL_GROUPS in search_types or SEARCH_STATIC_GROUPS in search_types:
        groups = ContactGroup.get_user_groups(org, ready_only=True)

        # exclude dynamic groups if not searching all groups
        if SEARCH_ALL_GROUPS not in search_types:
            groups = groups.filter(query=None)

        if query:
            groups = term_search(groups, ("name__icontains", ), query_terms)

        results += list(groups.order_by(Upper("name"))[:per_type_limit])

    if SEARCH_CONTACTS in search_types:
        try:
            # query elastic search for contact ids, then fetch contacts from db
            search_results = search_contacts(org,
                                             query,
                                             group=org.active_contacts_group,
                                             sort="name")
            contacts = IDSliceQuerySet(
                Contact,
                search_results.contact_ids,
                offset=0,
                total=len(search_results.contact_ids),
                only=("id", "uuid", "name", "org_id"),
            ).prefetch_related("org")

            results += list(contacts[:per_type_limit])
            Contact.bulk_urn_cache_initialize(contacts=results)

        except SearchException:
            pass

    if SEARCH_URNS in search_types:
        if not org.is_anon and query and len(query) >= 3:
            try:
                # build an OR'ed query of all sendable schemes
                sendable_schemes = org.get_schemes(Channel.ROLE_SEND)
                scheme_query = " OR ".join(f"{s} ~ {json.dumps(query)}"
                                           for s in sendable_schemes)
                search_results = search_contacts(
                    org,
                    scheme_query,
                    group=org.active_contacts_group,
                    sort="name")
                urns = ContactURN.objects.filter(
                    contact_id__in=search_results.contact_ids,
                    scheme__in=sendable_schemes)
                results += list(
                    urns.prefetch_related("contact").order_by(
                        Upper("path"))[:per_type_limit])
            except SearchException:
                pass

    return results
Example #47
0
    def setUp(self):
        self.maxDiff = 4096
        self.mock_server = mock_server

        # if we are super verbose, turn on debug for sql queries
        if self.get_verbosity() > 2:
            settings.DEBUG = True

        # make sure we start off without any service users
        Group.objects.get(name="Service Users").user_set.clear()

        self.clear_cache()

        self.create_anonymous_user()

        self.superuser = User.objects.create_superuser(username="******",
                                                       email="*****@*****.**",
                                                       password="******")

        # create different user types
        self.non_org_user = self.create_user("NonOrg")
        self.user = self.create_user("User")
        self.editor = self.create_user("Editor")
        self.admin = self.create_user("Administrator")
        self.surveyor = self.create_user("Surveyor")
        self.customer_support = self.create_user("support",
                                                 ("Customer Support", ))

        # setup admin boundaries for Rwanda
        self.country = AdminBoundary.create(osm_id="171496",
                                            name="Rwanda",
                                            level=0)
        self.state1 = AdminBoundary.create(osm_id="1708283",
                                           name="Kigali City",
                                           level=1,
                                           parent=self.country)
        self.state2 = AdminBoundary.create(osm_id="171591",
                                           name="Eastern Province",
                                           level=1,
                                           parent=self.country)
        self.district1 = AdminBoundary.create(osm_id="R1711131",
                                              name="Gatsibo",
                                              level=2,
                                              parent=self.state2)
        self.district2 = AdminBoundary.create(osm_id="1711163",
                                              name="KayƓnza",
                                              level=2,
                                              parent=self.state2)
        self.district3 = AdminBoundary.create(osm_id="3963734",
                                              name="Nyarugenge",
                                              level=2,
                                              parent=self.state1)
        self.district4 = AdminBoundary.create(osm_id="1711142",
                                              name="Rwamagana",
                                              level=2,
                                              parent=self.state2)
        self.ward1 = AdminBoundary.create(osm_id="171113181",
                                          name="Kageyo",
                                          level=3,
                                          parent=self.district1)
        self.ward2 = AdminBoundary.create(osm_id="171116381",
                                          name="Kabare",
                                          level=3,
                                          parent=self.district2)
        self.ward3 = AdminBoundary.create(osm_id="VMN.49.1_1",
                                          name="Bukure",
                                          level=3,
                                          parent=self.district4)

        self.country.update_path()

        self.org = Org.objects.create(
            name="Temba",
            timezone=pytz.timezone("Africa/Kigali"),
            country=self.country,
            brand=settings.DEFAULT_BRAND,
            created_by=self.user,
            modified_by=self.user,
        )

        self.org.initialize(topup_size=1000)

        # add users to the org
        self.user.set_org(self.org)
        self.org.viewers.add(self.user)

        self.editor.set_org(self.org)
        self.org.editors.add(self.editor)

        self.admin.set_org(self.org)
        self.org.administrators.add(self.admin)

        self.surveyor.set_org(self.org)
        self.org.surveyors.add(self.surveyor)

        self.superuser.set_org(self.org)

        # welcome topup with 1000 credits
        self.welcome_topup = self.org.topups.all()[0]

        # a single Android channel
        self.channel = Channel.create(
            self.org,
            self.user,
            "RW",
            "A",
            name="Test Channel",
            address="+250785551212",
            device="Nexus 5X",
            secret="12345",
            config={Channel.CONFIG_FCM_ID: "123"},
        )

        # don't cache anon user between tests
        from temba import utils

        utils._anon_user = None
        clear_flow_users()

        # reset our simulation to False
        Contact.set_simulation(False)