Exemplo n.º 1
0
    def update_destination_no_check(self, flow, node, destination, rule=None):  # pragma: no cover
        """ Update the destination without doing a cycle check """
        # look up our destination, we need this in order to set the correct destination_type
        destination_type = ACTION_SET
        action_destination = Flow.get_node(flow, destination, destination_type)
        if not action_destination:
            destination_type = RULE_SET
            ruleset_destination = Flow.get_node(flow, destination, destination_type)
            self.assertTrue(ruleset_destination, "Unable to find new destination with uuid: %s" % destination)

        actionset = ActionSet.get(flow, node)
        if actionset:
            actionset.destination = destination
            actionset.destination_type = destination_type
            actionset.save()

        ruleset = RuleSet.get(flow, node)
        if ruleset:
            rules = ruleset.get_rules()
            for r in rules:
                if r.uuid == rule:
                    r.destination = destination
                    r.destination_type = destination_type
            ruleset.set_rules(rules)
            ruleset.save()
        else:
            self.fail("Couldn't find node with uuid: %s" % node)
def advance_stuck_runs(apps, schema_editor):

    # this data migration is not forward-compatible
    from temba.flows.models import Flow, FlowStep, FlowRun, RuleSet
    from temba.msgs.models import Msg

    flows = Flow.objects.filter(flow_type="F", version_number=5)

    if flows:
        print "%d version 5 flows" % len(flows)

        for flow in flows:

            # looking for flows that start with a passive ruleset
            ruleset = RuleSet.objects.filter(uuid=flow.entry_uuid, flow=flow).first()

            if ruleset and not ruleset.is_pause():

                # now see if there are any active steps at our current flow
                steps = FlowStep.objects.filter(
                    run__is_active=True, step_uuid=ruleset.uuid, rule_value=None, left_on=None
                ).select_related("contact")

                if steps:
                    print "\nAdvancing %d steps for %s:%s" % (len(steps), flow.org.name, flow.name)
                    for idx, step in enumerate(steps):

                        if (idx + 1) % 100 == 0:
                            print "\n\n *** Step %d of %d\n\n" % (idx + 1, len(steps))

                        # force them to be handled again
                        msg = Msg(contact=step.contact, text="", id=0)
                        Flow.handle_destination(ruleset, step, step.run, msg)
Exemplo n.º 3
0
    def update_destination_no_check(self,
                                    flow,
                                    node,
                                    destination,
                                    rule=None):  # pragma: no cover
        """ Update the destination without doing a cycle check """
        # look up our destination, we need this in order to set the correct destination_type
        destination_type = FlowStep.TYPE_ACTION_SET
        action_destination = Flow.get_node(flow, destination, destination_type)
        if not action_destination:
            destination_type = FlowStep.TYPE_RULE_SET
            ruleset_destination = Flow.get_node(flow, destination,
                                                destination_type)
            self.assertTrue(
                ruleset_destination,
                "Unable to find new destination with uuid: %s" % destination)

        actionset = ActionSet.get(flow, node)
        if actionset:
            actionset.destination = destination
            actionset.destination_type = destination_type
            actionset.save()

        ruleset = RuleSet.get(flow, node)
        if ruleset:
            rules = ruleset.get_rules()
            for r in rules:
                if r.uuid == rule:
                    r.destination = destination
                    r.destination_type = destination_type
            ruleset.set_rules(rules)
            ruleset.save()
        else:
            self.fail("Couldn't find node with uuid: %s" % node)
Exemplo n.º 4
0
    def test_flow_event(self):
        self.setupChannel()

        org = self.channel.org
        org.save()

        from temba.flows.models import ActionSet, WebhookAction, Flow
        flow = self.create_flow()

        # replace our uuid of 4 with the right thing
        actionset = ActionSet.objects.get(x=4)
        actionset.set_actions_dict(
            [WebhookAction(org.get_webhook_url()).as_json()])
        actionset.save()

        with patch('requests.Session.send') as mock:
            # run a user through this flow
            flow.start([], [self.joe])

            # have joe reply with mauve, which will put him in the other category that triggers the API Action
            sms = self.create_msg(contact=self.joe,
                                  direction='I',
                                  status='H',
                                  text="Mauve")

            mock.return_value = MockResponse(200, "{}")
            Flow.find_and_handle(sms)

            # should have one event created
            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("successfully", result.message)
            self.assertEquals(200, result.status_code)

            self.assertTrue(mock.called)

            args = mock.call_args_list[0][0]
            prepared_request = args[0]
            self.assertStringContains(self.channel.org.get_webhook_url(),
                                      prepared_request.url)

            data = parse_qs(prepared_request.body)
            self.assertEquals(self.channel.pk, int(data['channel'][0]))
            self.assertEquals(actionset.uuid, data['step'][0])
            self.assertEquals(flow.pk, int(data['flow'][0]))
            self.assertEquals(self.joe.uuid, data['contact'][0])
            self.assertEquals(unicode(self.joe.get_urn('tel')), data['urn'][0])

            values = json.loads(data['values'][0])

            self.assertEquals('Other', values[0]['category']['base'])
            self.assertEquals('color', values[0]['label'])
            self.assertEquals('Mauve', values[0]['text'])
            self.assertTrue(values[0]['time'])
            self.assertTrue(data['time'])
Exemplo n.º 5
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()
Exemplo n.º 6
0
    def test_flow_event(self):
        self.setupChannel()

        org = self.channel.org
        org.save()

        from temba.flows.models import ActionSet, WebhookAction, Flow
        flow = self.create_flow()

        # replace our uuid of 4 with the right thing
        actionset = ActionSet.objects.get(x=4)
        actionset.set_actions_dict([WebhookAction(org.get_webhook_url()).as_json()])
        actionset.save()

        with patch('requests.Session.send') as mock:
            # run a user through this flow
            flow.start([], [self.joe])

            # have joe reply with mauve, which will put him in the other category that triggers the API Action
            sms = self.create_msg(contact=self.joe, direction='I', status='H', text="Mauve")

            mock.return_value = MockResponse(200, "{}")
            Flow.find_and_handle(sms)

            # should have one event created
            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("successfully", result.message)
            self.assertEquals(200, result.status_code)

            self.assertTrue(mock.called)

            args = mock.call_args_list[0][0]
            prepared_request = args[0]
            self.assertStringContains(self.channel.org.get_webhook_url(), prepared_request.url)

            data = parse_qs(prepared_request.body)
            self.assertEquals(self.channel.pk, int(data['channel'][0]))
            self.assertEquals(actionset.uuid, data['step'][0])
            self.assertEquals(flow.pk, int(data['flow'][0]))
            self.assertEquals(self.joe.uuid, data['contact'][0])
            self.assertEquals(unicode(self.joe.get_urn('tel')), data['urn'][0])

            values = json.loads(data['values'][0])

            self.assertEquals('Other', values[0]['category']['base'])
            self.assertEquals('color', values[0]['label'])
            self.assertEquals('Mauve', values[0]['text'])
            self.assertTrue(values[0]['time'])
            self.assertTrue(data['time'])
Exemplo n.º 7
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()
Exemplo n.º 8
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()
Exemplo n.º 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)

        # 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()
Exemplo n.º 10
0
    def test_export_import(self):
        # tweak our current channel to be twitter so we can create a channel-based trigger
        Channel.objects.filter(id=self.channel.id).update(channel_type=TWITTER)
        flow = self.create_flow()

        group = self.create_group("Trigger Group", [])

        # create a trigger on this flow for the follow actions but only on some groups
        trigger = Trigger.objects.create(org=self.org,
                                         flow=flow,
                                         trigger_type=Trigger.TYPE_FOLLOW,
                                         channel=self.channel,
                                         created_by=self.admin,
                                         modified_by=self.admin)
        trigger.groups.add(group)

        # export everything
        export = Flow.export_definitions([flow])

        # remove our trigger
        Trigger.objects.all().delete()

        # and reimport them.. trigger should be recreated
        self.org.import_app(export, self.admin)

        trigger = Trigger.objects.get()
        self.assertEqual(trigger.trigger_type, Trigger.TYPE_FOLLOW)
        self.assertEqual(trigger.flow, flow)
        self.assertEqual(trigger.channel, self.channel)
        self.assertEqual(list(trigger.groups.all()), [group])
Exemplo n.º 11
0
    def pre_save(self, request, obj):
        # if it's before, negate the offset
        if self.cleaned_data['direction'] == 'B':
            obj.offset = -obj.offset

        if self.cleaned_data['unit'] == 'H' or self.cleaned_data[
                'unit'] == 'M':  # pragma: needs cover
            obj.delivery_hour = -1

        # if its a message flow, set that accordingly
        if self.cleaned_data['event_type'] == CampaignEvent.TYPE_MESSAGE:

            message_dict = {}
            for language in self.languages:
                iso_code = language.language['iso_code']
                message_dict[iso_code] = self.cleaned_data.get(iso_code, '')

            if not obj.flow_id or not obj.flow.is_active or obj.flow.flow_type != Flow.MESSAGE:
                obj.flow = Flow.create_single_message(request.user.get_org(),
                                                      request.user,
                                                      message_dict)

            # set our single message on our flow
            obj.flow.update_single_message_flow(message_dict)
            obj.message = json.dumps(message_dict)

        # otherwise, it's an event that runs an existing flow
        else:
            obj.flow = Flow.objects.get(pk=self.cleaned_data['flow_to_start'])
Exemplo n.º 12
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)
Exemplo n.º 13
0
    def test_missed_call_trigger(self):
        self.login(self.admin)
        flow = self.create_flow()

        trigger_url = reverse("triggers.trigger_missed_call")

        response = self.client.get(trigger_url)
        self.assertEqual(response.status_code, 200)

        response = self.client.post(trigger_url, {"flow": flow.id})
        self.assertEqual(response.status_code, 200)

        trigger = Trigger.objects.order_by("id").last()

        self.assertEqual(trigger.trigger_type, Trigger.TYPE_MISSED_CALL)
        self.assertEqual(trigger.flow, flow)

        other_flow = Flow.copy(flow, self.admin)

        response = self.client.post(
            reverse("triggers.trigger_update", args=[trigger.id]),
            {"flow": other_flow.id})
        self.assertEqual(response.status_code, 302)

        trigger.refresh_from_db()
        self.assertEqual(trigger.flow, other_flow)

        # create ten missed call triggers
        for i in range(10):
            response = self.client.get(trigger_url)
            self.assertEqual(response.status_code, 200)

            self.client.post(trigger_url, {"flow": flow.id})

            self.assertEqual(Trigger.objects.all().count(), i + 2)
            self.assertEqual(
                Trigger.objects.filter(
                    is_archived=False,
                    trigger_type=Trigger.TYPE_MISSED_CALL).count(), 1)

        # even unarchiving we only have one active trigger at a time
        triggers = Trigger.objects.filter(
            trigger_type=Trigger.TYPE_MISSED_CALL, is_archived=True)
        active_trigger = Trigger.objects.get(
            trigger_type=Trigger.TYPE_MISSED_CALL, is_archived=False)

        response = self.client.post(reverse("triggers.trigger_archived"), {
            "action": "restore",
            "objects": [t.id for t in triggers]
        })

        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            Trigger.objects.filter(
                is_archived=False,
                trigger_type=Trigger.TYPE_MISSED_CALL).count(), 1)
        self.assertNotEqual(
            active_trigger,
            Trigger.objects.filter(is_archived=False,
                                   trigger_type=Trigger.TYPE_MISSED_CALL)[0])
Exemplo n.º 14
0
def migrate_flows(min_version=None):  # pragma: no cover
    to_version = min_version or get_current_export_version()

    # get all flows below the min version
    old_versions = Flow.get_versions_before(to_version)

    flows_to_migrate = Flow.objects.filter(is_active=True, version_number__in=old_versions)

    flow_ids = list(flows_to_migrate.values_list("id", flat=True))
    total = len(flow_ids)

    if not total:
        print("All flows up to date")
        return True

    print("Found %d flows to migrate to %s..." % (len(flow_ids), to_version))

    num_updated = 0
    errored = []

    for id_batch in chunk_list(flow_ids, 1000):
        for flow in Flow.objects.filter(id__in=id_batch):
            try:
                flow.ensure_current_version(min_version=to_version)
                num_updated += 1
            except Exception:
                print("Unable to migrate flow '%s' (#%d)" % (flow.name, flow.id))
                errored.append(flow)

        print(" > Flows migrated: %d of %d (%d errored)" % (num_updated, total, len(errored)))

    if errored:
        print(" > Errored flows: %s" % (", ".join([str(e.id) for e in errored])))

    return len(errored) == 0
Exemplo n.º 15
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()
Exemplo n.º 16
0
def migrate_flows(min_version=None):  # pragma: no cover
    to_version = min_version or Flow.FINAL_LEGACY_VERSION

    # get all flows below the min version
    old_versions = Flow.get_versions_before(to_version)

    flows_to_migrate = Flow.objects.filter(is_active=True, version_number__in=old_versions)

    flow_ids = list(flows_to_migrate.values_list("id", flat=True))
    total = len(flow_ids)

    if not total:
        print("All flows up to date")
        return True

    print(f"Found {len(flow_ids)} flows to migrate to {to_version}...")

    num_updated = 0
    num_errored = 0

    for id_batch in chunk_list(flow_ids, 1000):
        for flow in Flow.objects.filter(id__in=id_batch):
            try:
                flow.ensure_current_version(min_version=to_version)
                num_updated += 1
            except Exception:
                print(f"Unable to migrate flow '{flow.name}' ({str(flow.uuid)}):")
                print(traceback.format_exc())
                num_errored += 1

        print(f" > Flows migrated: {num_updated} of {total} ({num_errored} errored)")

    return num_errored == 0
Exemplo n.º 17
0
 def get_flow(self, filename, substitutions=None, flow_type=Flow.FLOW):
     flow = Flow.create(self.org,
                        self.admin,
                        name=filename,
                        flow_type=flow_type)
     self.update_flow(flow, filename, substitutions)
     return flow
Exemplo n.º 18
0
        def form_valid(self, form):
            keyword = form.cleaned_data["keyword"]
            join_group = form.cleaned_data["action_join_group"]
            start_flow = form.cleaned_data["flow"]
            send_msg = form.cleaned_data["response"]

            org = self.request.user.get_org()
            group_flow = Flow.create_join_group(org, self.request.user,
                                                join_group, send_msg,
                                                start_flow)

            Trigger.objects.create(
                created_by=self.request.user,
                modified_by=self.request.user,
                org=self.request.user.get_org(),
                keyword=keyword,
                trigger_type=Trigger.TYPE_KEYWORD,
                flow=group_flow,
            )

            analytics.track(self.request.user.username,
                            "temba.trigger_created", dict(type="register"))

            response = self.render_to_response(
                self.get_context_data(form=form))
            response["REDIRECT"] = self.get_success_url()
            return response
Exemplo n.º 19
0
    def pre_save(self, request, obj):
        org = self.user.get_org()

        # if it's before, negate the offset
        if self.cleaned_data["direction"] == "B":
            obj.offset = -obj.offset

        if self.cleaned_data["unit"] == "H" or self.cleaned_data["unit"] == "M":  # pragma: needs cover
            obj.delivery_hour = -1

        # if its a message flow, set that accordingly
        if self.cleaned_data["event_type"] == CampaignEvent.TYPE_MESSAGE:

            if self.instance.id:
                base_language = self.instance.flow.base_language
            else:
                base_language = org.primary_language.iso_code if org.primary_language else "base"

            translations = {}
            for language in self.languages:
                iso_code = language.language["iso_code"]
                translations[iso_code] = self.cleaned_data.get(iso_code, "")

            if not obj.flow_id or not obj.flow.is_active or obj.flow.flow_type != Flow.MESSAGE:
                obj.flow = Flow.create_single_message(org, request.user, translations, base_language=base_language)
            else:
                # set our single message on our flow
                obj.flow.update_single_message_flow(translations, base_language)

            obj.message = translations
            obj.full_clean()

        # otherwise, it's an event that runs an existing flow
        else:
            obj.flow = Flow.objects.get(org=org, id=self.cleaned_data["flow_to_start"])
Exemplo n.º 20
0
        def form_valid(self, form):
            keyword = form.cleaned_data["keyword"]
            join_group = form.cleaned_data["action_join_group"]
            start_flow = form.cleaned_data["flow"]
            send_msg = form.cleaned_data["response"]
            groups = form.cleaned_data["groups"]
            exclude_groups = form.cleaned_data["exclude_groups"]

            org = self.request.user.get_org()
            register_flow = Flow.create_join_group(org, self.request.user,
                                                   join_group, send_msg,
                                                   start_flow)

            Trigger.create(
                org,
                self.request.user,
                Trigger.TYPE_KEYWORD,
                register_flow,
                groups=groups,
                exclude_groups=exclude_groups,
                keyword=keyword,
            )

            response = self.render_to_response(
                self.get_context_data(form=form))
            response["REDIRECT"] = self.get_success_url()
            return response
Exemplo n.º 21
0
    def test_get_sorted_events(self):
        # create a campaign
        campaign = Campaign.create(self.org, self.user, "Planting Reminders", self.farmers)

        flow = self.create_flow()

        event1 = CampaignEvent.create_flow_event(self.org, self.admin, campaign, self.planting_date,
                                                 offset=1, unit='W', flow=flow, delivery_hour='13')
        event2 = CampaignEvent.create_flow_event(self.org, self.admin, campaign, self.planting_date,
                                                 offset=1, unit='W', flow=flow, delivery_hour='9')
        event3 = CampaignEvent.create_flow_event(self.org, self.admin, campaign, self.planting_date,
                                                 offset=2, unit='W', flow=flow, delivery_hour='1')

        self.assertEqual(campaign.get_sorted_events(), [event2, event1, event3])

        flow_json = self.get_flow_json('call_me_maybe')['definition']
        flow = Flow.create_instance(dict(name='Call Me Maybe', org=self.org, flow_type=Flow.MESSAGE,
                                         created_by=self.admin, modified_by=self.admin,
                                         saved_by=self.admin, version_number=3))

        FlowRevision.create_instance(dict(flow=flow, definition=flow_json,
                                          spec_version=3, revision=1,
                                          created_by=self.admin, modified_by=self.admin))

        event4 = CampaignEvent.create_flow_event(self.org, self.admin, campaign, self.planting_date,
                                                 offset=2, unit='W', flow=flow, delivery_hour='5')

        self.assertEqual(flow.version_number, 3)
        self.assertEqual(campaign.get_sorted_events(), [event2, event1, event3, event4])
        flow.refresh_from_db()
        self.assertNotEqual(flow.version_number, 3)
        self.assertEqual(flow.version_number, get_current_export_version())
Exemplo n.º 22
0
    def pre_save(self, request, obj):
        org = self.user.get_org()

        # if it's before, negate the offset
        if self.cleaned_data["direction"] == "B":
            obj.offset = -obj.offset

        if self.cleaned_data["unit"] == "H" or self.cleaned_data["unit"] == "M":  # pragma: needs cover
            obj.delivery_hour = -1

        # if its a message flow, set that accordingly
        if self.cleaned_data["event_type"] == CampaignEvent.TYPE_MESSAGE:

            if self.instance.id:
                base_language = self.instance.flow.base_language
            else:
                base_language = org.primary_language.iso_code if org.primary_language else "base"

            translations = {}
            for language in self.languages:
                iso_code = language.language["iso_code"]
                translations[iso_code] = self.cleaned_data.get(iso_code, "")

            if not obj.flow_id or not obj.flow.is_active or not obj.flow.is_system:
                obj.flow = Flow.create_single_message(org, request.user, translations, base_language=base_language)
            else:
                # set our single message on our flow
                obj.flow.update_single_message_flow(translations, base_language)

            obj.message = translations
            obj.full_clean()

        # otherwise, it's an event that runs an existing flow
        else:
            obj.flow = Flow.objects.get(org=org, id=self.cleaned_data["flow_to_start"])
Exemplo n.º 23
0
def migrate_to_version_11_5(json_flow, flow=None):
    """
    Replaces @flow.foo and @flow.foo.value with @extra.webhook where foo is a webhook or resthook ruleset
    """
    # figure out which rulesets are webhook or resthook calls
    rule_sets = json_flow.get("rule_sets", [])
    webhook_rulesets = set()
    non_webhook_rulesets = set()
    for r in rule_sets:
        slug = Flow.label_to_slug(r["label"])
        if not slug:  # pragma: no cover
            continue
        if r["ruleset_type"] in (RuleSet.TYPE_WEBHOOK, RuleSet.TYPE_RESTHOOK):
            webhook_rulesets.add(slug)
        else:
            non_webhook_rulesets.add(slug)

    # ignore any slugs of webhook rulesets which are also used by non-webhook rulesets
    slugs = webhook_rulesets.difference(non_webhook_rulesets)
    if not slugs:
        return json_flow

    # make a regex that matches a context reference to these (see https://regex101.com/r/65b2ZT/3)
    replace_pattern = r"flow\.(" + "|".join(slugs) + r")(\.value)?(?!\.\w)"
    replace_regex = regex.compile(replace_pattern,
                                  flags=regex.UNICODE | regex.IGNORECASE
                                  | regex.MULTILINE)
    replace_with = r"extra.\1"

    replace_templates(json_flow, lambda t: replace_regex.sub(replace_with, t))

    return json_flow
Exemplo n.º 24
0
    def __init__(self, user, *args, **kwargs):
        flows = Flow.get_triggerable_flows(user.get_org())
        super().__init__(user, flows, *args, **kwargs)

        self.fields["channel"].queryset = Channel.objects.filter(
            is_active=True, org=self.user.get_org(), schemes__overlap=list(ContactURN.SCHEMES_SUPPORTING_REFERRALS)
        )
Exemplo n.º 25
0
    def test_export_import(self):
        # tweak our current channel to be twitter so we can create a channel-based trigger
        Channel.objects.filter(id=self.channel.id).update(channel_type=TWITTER)
        flow = self.create_flow()

        group = self.create_group("Trigger Group", [])

        # create a trigger on this flow for the follow actions but only on some groups
        trigger = Trigger.objects.create(org=self.org, flow=flow, trigger_type=Trigger.TYPE_FOLLOW, channel=self.channel,
                                         created_by=self.admin, modified_by=self.admin)
        trigger.groups.add(group)

        # export everything
        export = Flow.export_definitions([flow])

        # remove our trigger
        Trigger.objects.all().delete()

        # and reimport them.. trigger should be recreated
        self.org.import_app(export, self.admin)

        trigger = Trigger.objects.get()
        self.assertEqual(trigger.trigger_type, Trigger.TYPE_FOLLOW)
        self.assertEqual(trigger.flow, flow)
        self.assertEqual(trigger.channel, self.channel)
        self.assertEqual(list(trigger.groups.all()), [group])
Exemplo n.º 26
0
        def form_valid(self, form):
            keyword = form.cleaned_data['keyword']
            join_group = form.cleaned_data['action_join_group']
            start_flow = form.cleaned_data['flow']
            send_msg = form.cleaned_data['response']

            org = self.request.user.get_org()
            group_flow = Flow.create_join_group(org, self.request.user,
                                                join_group, send_msg,
                                                start_flow)

            Trigger.objects.create(created_by=self.request.user,
                                   modified_by=self.request.user,
                                   org=self.request.user.get_org(),
                                   keyword=keyword,
                                   trigger_type=Trigger.TYPE_KEYWORD,
                                   flow=group_flow)

            analytics.track(self.request.user.username,
                            'temba.trigger_created_register',
                            dict(name=join_group.name))

            response = self.render_to_response(
                self.get_context_data(form=form))
            response['REDIRECT'] = self.get_success_url()
            return response
Exemplo n.º 27
0
    def __init__(self, user, *args, **kwargs):
        flows = Flow.get_triggerable_flows(user.get_org())
        super().__init__(user, flows, *args, **kwargs)

        self.fields["channel"].queryset = Channel.objects.filter(
            is_active=True, org=self.user.get_org(), schemes__overlap=list(ContactURN.SCHEMES_SUPPORTING_REFERRALS)
        )
Exemplo n.º 28
0
    def start_call(self, call, to, from_, status_callback):
        if not settings.SEND_CALLS:
            raise IVRException("SEND_CALLS set to False, skipping call start")

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

        # Verboice differs from Twilio in that they expect the first block of twiml up front
        payload = six.text_type(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:
            raise IVRException(_('Verboice connection failed.'))

        # store the verboice call id in our IVRCall
        call.external_id = response['call_id']
        call.status = IVRCall.IN_PROGRESS
        call.save()
Exemplo n.º 29
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()
Exemplo n.º 30
0
    def create_flow(self):
        start = int(time.time()*1000) % 1000000

        definition = dict(action_sets=[dict(uuid=uuid(start + 1), x=1, y=1, destination=uuid(start + 5),
                                            actions=[dict(type='reply', msg='What is your favorite color?')]),
                                       dict(uuid=uuid(start + 2), x=2, y=2, destination=None,
                                            actions=[dict(type='reply', msg='I love orange too!')]),
                                       dict(uuid=uuid(start + 3), x=3, y=3, destination=None,
                                            actions=[dict(type='reply', msg='Blue is sad. :(')]),
                                       dict(uuid=uuid(start + 4), x=4, y=4, destination=None,
                                            actions=[dict(type='reply', msg='That is a funny color.')])
                                       ],
                          rule_sets=[dict(uuid=uuid(start + 5), x=5, y=5,
                                          label='color',
                                          response_type='C',
                                          rules=[
                                              dict(uuid=uuid(start + 12), destination=uuid(start + 2), test=dict(type='contains', test='orange'), category="Orange"),
                                              dict(uuid=uuid(start + 13), destination=uuid(start + 3), test=dict(type='contains', test='blue'), category="Blue"),
                                              dict(uuid=uuid(start + 14), destination=uuid(start + 4), test=dict(type='true'), category="Other"),
                                              dict(uuid=uuid(start + 15), test=dict(type='true'), category="Nothing")]) # test case with no destination
                                    ],
                          entry=uuid(start + 1))

        flow = Flow.create(self.org, self.admin, "Color Flow")
        flow.update(definition)
        return flow
Exemplo n.º 31
0
    def create_message_event(
        cls, org, user, campaign, relative_to, offset, unit, message, delivery_hour=-1, base_language=None
    ):
        if campaign.org != org:
            raise ValueError("Org mismatch")

        if relative_to.value_type != Value.TYPE_DATETIME:
            raise ValueError(
                f"Contact fields for CampaignEvents must have a datetime type, got {relative_to.value_type}."
            )

        if isinstance(message, str):
            base_language = org.primary_language.iso_code if org.primary_language else "base"
            message = {base_language: message}

        flow = Flow.create_single_message(org, user, message, base_language)

        return cls.objects.create(
            campaign=campaign,
            relative_to=relative_to,
            offset=offset,
            unit=unit,
            event_type=cls.TYPE_MESSAGE,
            message=message,
            flow=flow,
            delivery_hour=delivery_hour,
            created_by=user,
            modified_by=user,
        )
Exemplo n.º 32
0
def migrate_to_version_11_4(json_flow, flow=None):
    """
    Replaces @flow.foo.text with @step.value for non-waiting rulesets, to bring old world functionality inline with the
    new engine, where @run.results.foo.input is always the router operand.
    """
    # figure out which rulesets aren't waits
    rule_sets = json_flow.get("rule_sets", [])
    non_waiting = {
        Flow.label_to_slug(r["label"])
        for r in rule_sets if r["ruleset_type"] not in RuleSet.TYPE_WAIT
    }

    # make a regex that matches a context reference to the .text on any result from these
    replace_pattern = r"flow\.(" + "|".join(non_waiting) + ")\.text"
    replace_regex = regex.compile(replace_pattern,
                                  flags=regex.UNICODE | regex.IGNORECASE
                                  | regex.MULTILINE)
    replace_with = "step.value"

    # for every action in this flow, replace such references with @step.text
    for actionset in json_flow.get("action_sets", []):
        for action in actionset.get("actions", []):
            if action["type"] in ["reply", "send", "say", "email"]:
                msg = action["msg"]
                if isinstance(msg, str):
                    action["msg"] = replace_regex.sub(replace_with, msg)
                else:
                    for lang, text in msg.items():
                        msg[lang] = replace_regex.sub(replace_with, text)

    return json_flow
Exemplo n.º 33
0
    def create_message_event(cls,
                             org,
                             user,
                             campaign,
                             relative_to,
                             offset,
                             unit,
                             message,
                             delivery_hour=-1):
        if campaign.org != org:  # pragma: no cover
            raise ValueError("Org mismatch")

        flow = Flow.create_single_message(org, user, message)

        if isinstance(message, dict):
            message = json.dumps(message)

        return cls.objects.create(campaign=campaign,
                                  relative_to=relative_to,
                                  offset=offset,
                                  unit=unit,
                                  event_type=cls.TYPE_MESSAGE,
                                  message=message,
                                  flow=flow,
                                  delivery_hour=delivery_hour,
                                  created_by=user,
                                  modified_by=user)
Exemplo n.º 34
0
    def create_message_event(cls,
                             org,
                             user,
                             campaign,
                             relative_to,
                             offset,
                             unit,
                             message,
                             delivery_hour=-1,
                             base_language=None):
        if campaign.org != org:  # pragma: no cover
            raise ValueError("Org mismatch")

        if isinstance(message, six.string_types):
            base_language = org.primary_language.iso_code if org.primary_language else 'base'
            message = {base_language: message}

        flow = Flow.create_single_message(org, user, message, base_language)

        return cls.objects.create(campaign=campaign,
                                  relative_to=relative_to,
                                  offset=offset,
                                  unit=unit,
                                  event_type=cls.TYPE_MESSAGE,
                                  message=message,
                                  flow=flow,
                                  delivery_hour=delivery_hour,
                                  created_by=user,
                                  modified_by=user)
Exemplo n.º 35
0
    def save(self):
        """
        Create or update our campaign event
        """
        campaign = self.validated_data.get('campaign')
        offset = self.validated_data.get('offset')
        unit = self.validated_data.get('unit')
        delivery_hour = self.validated_data.get('delivery_hour')
        relative_to = self.validated_data.get('relative_to')
        message = self.validated_data.get('message')
        flow = self.validated_data.get('flow')

        if self.instance:
            # we are being set to a flow
            if flow:
                self.instance.flow = flow
                self.instance.event_type = CampaignEvent.TYPE_FLOW
                self.instance.message = None

            # we are being set to a message
            else:
                translations, base_language = message
                self.instance.message = translations

                # if we aren't currently a message event, we need to create our hidden message flow
                if self.instance.event_type != CampaignEvent.TYPE_MESSAGE:
                    self.instance.flow = Flow.create_single_message(
                        self.context['org'], self.context['user'],
                        translations, base_language)
                    self.instance.event_type = CampaignEvent.TYPE_MESSAGE

                # otherwise, we can just update that flow
                else:
                    # set our single message on our flow
                    self.instance.flow.update_single_message_flow(
                        translations, base_language)

            # update our other attributes
            self.instance.offset = offset
            self.instance.unit = unit
            self.instance.delivery_hour = delivery_hour
            self.instance.relative_to = relative_to
            self.instance.save()
            self.instance.update_flow_name()

        else:
            if flow:
                self.instance = CampaignEvent.create_flow_event(
                    self.context['org'], self.context['user'], campaign,
                    relative_to, offset, unit, flow, delivery_hour)
            else:
                translations, base_language = message
                self.instance = CampaignEvent.create_message_event(
                    self.context['org'], self.context['user'], campaign,
                    relative_to, offset, unit, translations, delivery_hour,
                    base_language)
            self.instance.update_flow_name()

        return self.instance
Exemplo n.º 36
0
    def __init__(self, user, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.user = user
        self.fields["omnibox"].set_user(user)

        flows = Flow.get_triggerable_flows(user.get_org())

        self.fields["flow"].queryset = flows
Exemplo n.º 37
0
    def __init__(self, user, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.user = user
        self.fields["omnibox"].set_user(user)

        flows = Flow.get_triggerable_flows(user.get_org())

        self.fields["flow"].queryset = flows
Exemplo n.º 38
0
    def create_flow(self):
        start = int(time.time() * 1000) % 1000000

        definition = dict(
            action_sets=[
                dict(uuid=uuid(start + 1),
                     x=1,
                     y=1,
                     destination=uuid(start + 5),
                     actions=[
                         dict(type='reply', msg='What is your favorite color?')
                     ]),
                dict(uuid=uuid(start + 2),
                     x=2,
                     y=2,
                     destination=None,
                     actions=[dict(type='reply', msg='I love orange too!')]),
                dict(uuid=uuid(start + 3),
                     x=3,
                     y=3,
                     destination=None,
                     actions=[dict(type='reply', msg='Blue is sad. :(')]),
                dict(
                    uuid=uuid(start + 4),
                    x=4,
                    y=4,
                    destination=None,
                    actions=[dict(type='reply', msg='That is a funny color.')])
            ],
            rule_sets=[
                dict(uuid=uuid(start + 5),
                     x=5,
                     y=5,
                     label='color',
                     response_type='C',
                     rules=[
                         dict(uuid=uuid(start + 12),
                              destination=uuid(start + 2),
                              test=dict(type='contains', test='orange'),
                              category="Orange"),
                         dict(uuid=uuid(start + 13),
                              destination=uuid(start + 3),
                              test=dict(type='contains', test='blue'),
                              category="Blue"),
                         dict(uuid=uuid(start + 14),
                              destination=uuid(start + 4),
                              test=dict(type='true'),
                              category="Other"),
                         dict(uuid=uuid(start + 15),
                              test=dict(type='true'),
                              category="Nothing")
                     ])  # test case with no destination
            ],
            entry=uuid(start + 1))

        flow = Flow.create(self.org, self.admin, "Color Flow")
        flow.update(definition)
        return flow
Exemplo n.º 39
0
    def post(self, request, *args, **kwargs):
        call = IVRCall.objects.filter(pk=kwargs['pk']).first()

        if not call:
            return HttpResponse("Not found", status=404)

        client = call.channel.get_ivr_client()
        if request.REQUEST.get('hangup', 0):
            if not request.user.is_anonymous():
                user_org = request.user.get_org()
                if user_org and user_org.pk == call.org.pk:
                    client.calls.hangup(call.external_id)
                    return HttpResponse(json.dumps(dict(status='Canceled')),
                                        content_type="application/json")
                else:
                    return HttpResponse("Not found", status=404)

        if client.validate(request):
            status = request.POST.get('CallStatus', None)
            duration = request.POST.get('CallDuration', None)
            call.update_status(status, duration)

            # update any calls we have spawned with the same
            for child in call.child_calls.all():
                child.update_status(status, duration)
                child.save()

            call.save()

            # figure out if this is a callback due to an empty gather
            is_empty = '1' == request.GET.get('empty', '0')
            user_response = request.POST.copy()

            # if the user pressed pound, then record no digits as the input
            if is_empty:
                user_response['Digits'] = ''

            hangup = 'hangup' == user_response.get('Digits', None)

            if call.status in [IN_PROGRESS, RINGING] or hangup:
                if call.is_flow():
                    response = Flow.handle_call(call,
                                                user_response,
                                                hangup=hangup)
                    return HttpResponse(unicode(response))
            else:
                if call.status == COMPLETED:
                    # if our call is completed, hangup
                    run = FlowRun.objects.filter(call=call).first()
                    if run:
                        run.set_completed()
                return build_json_response(dict(message="Updated call status"))

        else:  # pragma: no cover
            # raise an exception that things weren't properly signed
            raise ValidationError("Invalid request signature")

        return build_json_response(dict(message="Unhandled"))
Exemplo n.º 40
0
    def __init__(self, user, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.user = user
        org = user.get_org()
        flows = Flow.get_triggerable_flows(org)

        self.fields["start_datetime"].help_text = _("%s Time Zone" %
                                                    org.timezone)
        self.fields["flow"].queryset = flows
Exemplo n.º 41
0
    def create_message_event(cls, org, user, campaign, relative_to, offset, unit, message, delivery_hour=-1):
        if campaign.org != org:  # pragma: no cover
            raise ValueError("Org mismatch")

        flow = Flow.create_single_message(org, user, message)

        return cls.objects.create(campaign=campaign, relative_to=relative_to, offset=offset, unit=unit,
                                  event_type=MESSAGE_EVENT, message=message, flow=flow, delivery_hour=delivery_hour,
                                  created_by=user, modified_by=user)
Exemplo n.º 42
0
    def create_flow(self,
                    name="Test Flow",
                    *,
                    flow_type=Flow.TYPE_MESSAGE,
                    nodes=None,
                    is_system=False,
                    org=None):
        org = org or self.org
        flow = Flow.create(org,
                           self.admin,
                           name,
                           flow_type=flow_type,
                           is_system=is_system)
        if not nodes:
            nodes = [{
                "uuid":
                "f3d5ccd0-fee0-4955-bcb7-21613f049eae",
                "actions": [{
                    "uuid": "f661e3f0-5148-4397-92ef-925629ad444d",
                    "type": "send_msg",
                    "text": "Hey everybody!"
                }],
                "exits": [{
                    "uuid": "72a3f1da-bde1-4549-a986-d35809807be8"
                }],
            }]
        definition = {
            "uuid": str(uuid4()),
            "name": name,
            "type": Flow.GOFLOW_TYPES[flow_type],
            "revision": 1,
            "spec_version": "13.1.0",
            "expire_after_minutes": Flow.DEFAULT_EXPIRES_AFTER,
            "language": "eng",
            "nodes": nodes,
        }

        flow.version_number = definition["spec_version"]
        flow.save()

        json_flow = Flow.migrate_definition(definition, flow)
        flow.save_revision(self.admin, json_flow)

        return flow
Exemplo n.º 43
0
    def post(self, request, *args, **kwargs):
        call = IVRCall.objects.filter(pk=kwargs['pk']).first()

        if not call:
            return HttpResponse("Not found", status=404)

        client = call.channel.get_ivr_client()
        if request.REQUEST.get('hangup', 0):
            if not request.user.is_anonymous():
                user_org = request.user.get_org()
                if user_org and user_org.pk == call.org.pk:
                    client.calls.hangup(call.external_id)
                    return HttpResponse(json.dumps(dict(status='Canceled')), content_type="application/json")
                else:
                    return HttpResponse("Not found", status=404)

        if client.validate(request):
            status = request.POST.get('CallStatus', None)
            duration = request.POST.get('CallDuration', None)
            call.update_status(status, duration)

            # update any calls we have spawned with the same
            for child in call.child_calls.all():
                child.update_status(status, duration)
                child.save()

            call.save()

            # figure out if this is a callback due to an empty gather
            is_empty = '1' == request.GET.get('empty', '0')
            user_response = request.POST.copy()

            # if the user pressed pound, then record no digits as the input
            if is_empty:
                user_response['Digits'] = ''

            hangup = 'hangup' == user_response.get('Digits', None)

            if call.status == IN_PROGRESS or hangup:
                if call.is_flow():
                    response = Flow.handle_call(call, user_response, hangup=hangup)
                    return HttpResponse(unicode(response))
            else:

                if call.status == COMPLETED:
                    # if our call is completed, hangup
                    run = FlowRun.objects.filter(call=call).first()
                    if run:
                        run.set_completed()
                return build_json_response(dict(message="Updated call status"))

        else:  # pragma: no cover
            # raise an exception that things weren't properly signed
            raise ValidationError("Invalid request signature")

        return build_json_response(dict(message="Unhandled"))
Exemplo n.º 44
0
    def __init__(self, user, *args, **kwargs):
        flows = Flow.get_triggerable_flows(user.get_org())

        super().__init__(user, flows, *args, **kwargs)

        self.fields["flow"].required = False
        group_field = self.fields["action_join_group"]
        group_field.queryset = ContactGroup.user_groups.filter(
            org=self.user.get_org(), is_active=True).order_by("name")
        group_field.user = user
Exemplo n.º 45
0
    def create_flow(self, uuid_start=None, **kwargs):
        if 'org' not in kwargs:
            kwargs['org'] = self.org
        if 'user' not in kwargs:
            kwargs['user'] = self.user
        if 'name' not in kwargs:
            kwargs['name'] = "Color Flow"

        flow = Flow.create(**kwargs)
        flow.update(self.create_flow_definition(uuid_start))
        return Flow.objects.get(pk=flow.pk)
Exemplo n.º 46
0
    def create_flow(self, uuid_start=None, **kwargs):
        if 'org' not in kwargs:
            kwargs['org'] = self.org
        if 'user' not in kwargs:
            kwargs['user'] = self.user
        if 'name' not in kwargs:
            kwargs['name'] = "Color Flow"

        flow = Flow.create(**kwargs)
        flow.update(self.create_flow_definition(uuid_start))
        return Flow.objects.get(pk=flow.pk)
Exemplo n.º 47
0
    def __init__(self, user, *args, **kwargs):
        flows = Flow.get_triggerable_flows(user.get_org())

        super().__init__(user, flows, *args, **kwargs)

        self.fields["flow"].required = False
        group_field = self.fields["action_join_group"]
        group_field.queryset = ContactGroup.user_groups.filter(org=self.user.get_org(), is_active=True).order_by(
            "name"
        )
        group_field.user = user
Exemplo n.º 48
0
    def save(self):
        """
        Create or update our campaign event
        """
        campaign = self.validated_data.get('campaign')
        offset = self.validated_data.get('offset')
        unit = self.validated_data.get('unit')
        delivery_hour = self.validated_data.get('delivery_hour')
        relative_to = self.validated_data.get('relative_to')
        message = self.validated_data.get('message')
        flow = self.validated_data.get('flow')

        if self.instance:
            # we are being set to a flow
            if flow:
                self.instance.flow = flow
                self.instance.event_type = CampaignEvent.TYPE_FLOW
                self.instance.message = None

            # we are being set to a message
            else:
                self.instance.message = message

                # if we aren't currently a message event, we need to create our hidden message flow
                if self.instance.event_type != CampaignEvent.TYPE_MESSAGE:
                    self.instance.flow = Flow.create_single_message(self.context['org'], self.context['user'], message)
                    self.instance.event_type = CampaignEvent.TYPE_MESSAGE

                # otherwise, we can just update that flow
                else:
                    # set our single message on our flow
                    self.instance.flow.update_single_message_flow(message=message)

            # update our other attributes
            self.instance.offset = offset
            self.instance.unit = unit
            self.instance.delivery_hour = delivery_hour
            self.instance.relative_to = relative_to
            self.instance.save()
            self.instance.update_flow_name()

        else:
            if flow:
                self.instance = CampaignEvent.create_flow_event(self.context['org'], self.context['user'], campaign,
                                                                relative_to, offset, unit, flow, delivery_hour)
            else:
                self.instance = CampaignEvent.create_message_event(self.context['org'], self.context['user'], campaign,
                                                                   relative_to, offset, unit, message, delivery_hour)
            self.instance.update_flow_name()

        return self.instance
Exemplo n.º 49
0
    def test_icon(self):
        from temba.campaigns.models import Campaign
        from temba.triggers.models import Trigger
        from temba.flows.models import Flow
        from temba.utils.templatetags.temba import icon

        campaign = Campaign.create(self.org, self.admin, 'Test Campaign', self.create_group('Test group', []))
        flow = Flow.create(self.org, self.admin, 'Test Flow')
        trigger = Trigger.objects.create(org=self.org, keyword='trigger', flow=flow, created_by=self.admin, modified_by=self.admin)

        self.assertEquals('icon-instant', icon(campaign))
        self.assertEquals('icon-feed', icon(trigger))
        self.assertEquals('icon-tree', icon(flow))
        self.assertEquals("", icon(None))
Exemplo n.º 50
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()
Exemplo n.º 51
0
        def form_valid(self, form):
            keyword = form.cleaned_data['keyword']
            join_group = form.cleaned_data['action_join_group']
            start_flow = form.cleaned_data['flow']
            send_msg = form.cleaned_data['response']

            group_flow = Flow.create_join_group_flow(self.request.user, join_group, send_msg, start_flow)

            Trigger.objects.create(created_by=self.request.user, modified_by=self.request.user,
                                   org=self.request.user.get_org(), keyword=keyword,
                                   trigger_type=KEYWORD_TRIGGER,
                                   flow=group_flow)

            analytics.track(self.request.user.username, 'temba.trigger_created_register', dict(name=join_group.name))

            response = self.render_to_response(self.get_context_data(form=form))
            response['REDIRECT'] = self.get_success_url()
            return response
Exemplo n.º 52
0
    def post(self, request, *args, **kwargs):
        from twilio.util import RequestValidator

        call = IVRCall.objects.filter(pk=kwargs['pk']).first()

        if not call:
            return HttpResponse("Not found", status=404)

        client = call.channel.get_ivr_client()
        if request.REQUEST.get('hangup', 0):
            if not request.user.is_anonymous():
                user_org = request.user.get_org()
                if user_org and user_org.pk == call.org.pk:
                    client.calls.hangup(call.external_id)
                    return HttpResponse(json.dumps(dict(status='Canceled')), content_type="application/json")
                else:
                    return HttpResponse("Not found", status=404)

        if client.validate(request):
            call.update_status(request.POST.get('CallStatus', None),
                               request.POST.get('CallDuration', None))
            call.save()

            hangup = 'hangup' == request.POST.get('Digits', None)

            if call.status == IN_PROGRESS or hangup:
                if call.is_flow():
                    response = Flow.handle_call(call, request.POST, hangup=hangup)
                    return HttpResponse(unicode(response))
            else:

                if call.status == COMPLETED:
                    # if our call is completed, hangup
                    run = FlowRun.objects.filter(call=call).first()
                    if run:
                        run.set_completed()
                        run.expire()
                return build_json_response(dict(message="Updated call status"))

        else:  # pragma: no cover
            # raise an exception that things weren't properly signed
            raise ValidationError("Invalid request signature")

        return build_json_response(dict(message="Unhandled"))
Exemplo n.º 53
0
    def start_call(self, call, to, from_, status_callback):

        channel = call.channel
        contact = Contact.get_or_create(channel.created_by, channel.org, 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()
Exemplo n.º 54
0
    def post(self, request, *args, **kwargs):
        from twilio.util import RequestValidator

        call = IVRCall.objects.filter(pk=kwargs['pk']).first()

        if not call:
            return HttpResponse("Not found", status=404)

        client = call.channel.get_ivr_client()
        if request.REQUEST.get('hangup', 0):
            if not request.user.is_anonymous():
                user_org = request.user.get_org()
                if user_org and user_org.pk == call.org.pk:
                    client.calls.hangup(call.external_id)
                    return HttpResponse(json.dumps(dict(status='Canceled')), content_type="application/json")
                else:
                    return HttpResponse("Not found", status=404)

        validator = RequestValidator(client.auth[1])
        signature = request.META.get('HTTP_X_TWILIO_SIGNATURE', '')

        base_url = settings.TEMBA_HOST
        url = "https://%s%s" % (base_url, request.get_full_path())

        # make sure this is coming from twilio
        if validator.validate(url, request.POST, signature):
            call.update_status(request.POST.get('CallStatus', None),
                               request.POST.get('CallDuration', None))
            call.save()

            if call.status == IN_PROGRESS:
                if call.is_flow():
                    response = Flow.handle_call(call, request.POST)
                    return HttpResponse(unicode(response))
            else:
                return build_json_response(dict(message="Updated call status"))

        else:  # pragma: no cover
            # raise an exception that things weren't properly signed
            raise ValidationError("Invalid request signature")

        return build_json_response(dict(message="Unhandled"))
Exemplo n.º 55
0
    def pre_save(self, request, obj):
        # if it's before, negate the offset
        if self.cleaned_data['direction'] == 'B':
            obj.offset = -obj.offset

        if self.cleaned_data['unit'] == 'H' or self.cleaned_data['unit'] == 'M':
            obj.delivery_hour = -1

        # if its a message flow, set that accordingly
        if self.cleaned_data['event_type'] == 'M':

            if not obj.flow_id or not obj.flow.is_active or obj.flow.flow_type != Flow.MESSAGE:
                obj.flow = Flow.create_single_message(request.user.get_org(), request.user,
                                                      self.cleaned_data['message'])

            # set our single message on our flow
            obj.flow.update_single_message_flow(message=self.cleaned_data['message'])
            obj.message = self.cleaned_data['message']

        # otherwise, it's an event that runs an existing flow
        else:
            obj.flow = Flow.objects.get(pk=self.cleaned_data['flow_to_start'])
Exemplo n.º 56
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>')

        # 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))
Exemplo n.º 57
0
    def create_flow(self, definition=None, **kwargs):
        if "org" not in kwargs:
            kwargs["org"] = self.org
        if "user" not in kwargs:
            kwargs["user"] = self.user
        if "name" not in kwargs:
            kwargs["name"] = "Color Flow"

        flow = Flow.create(**kwargs)
        if not definition:
            # if definition isn't provided, generate simple single message flow
            node_uuid = str(uuid4())
            definition = {
                "version": 10,
                "flow_type": "F",
                "base_language": "eng",
                "entry": node_uuid,
                "action_sets": [
                    {
                        "uuid": node_uuid,
                        "x": 0,
                        "y": 0,
                        "actions": [
                            {"msg": {"eng": "Hey everybody!"}, "media": {}, "send_all": False, "type": "reply"}
                        ],
                        "destination": None,
                    }
                ],
                "rule_sets": [],
            }

        flow.version_number = definition["version"]
        flow.save()

        json_flow = FlowRevision.migrate_definition(definition, flow)
        flow.update(json_flow)

        return flow
Exemplo n.º 58
0
        def form_valid(self, form):
            keyword = form.cleaned_data["keyword"]
            join_group = form.cleaned_data["action_join_group"]
            start_flow = form.cleaned_data["flow"]
            send_msg = form.cleaned_data["response"]

            org = self.request.user.get_org()
            group_flow = Flow.create_join_group(org, self.request.user, join_group, send_msg, start_flow)

            Trigger.objects.create(
                created_by=self.request.user,
                modified_by=self.request.user,
                org=self.request.user.get_org(),
                keyword=keyword,
                trigger_type=Trigger.TYPE_KEYWORD,
                flow=group_flow,
            )

            analytics.track(self.request.user.username, "temba.trigger_created_register", dict(name=join_group.name))

            response = self.render_to_response(self.get_context_data(form=form))
            response["REDIRECT"] = self.get_success_url()
            return response
Exemplo n.º 59
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.admin)
        self.org.save()

        # import an ivr flow
        flow = self.get_flow("rule_first_ivr")

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

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

        # 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>")

        # 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=test_contact, text="message during phone call")
        self.assertFalse(Flow.find_and_handle(msg))