Exemplo n.º 1
0
    def save(self):
        started = self.validated_data['started']
        steps = self.validated_data.get('steps', [])
        completed = self.validated_data.get('completed', False)

        # look for previous run with this contact and flow
        run = (FlowRun.objects.filter(
            org=self.org,
            contact=self.contact_obj,
            flow=self.flow_obj,
            created_on=started,
            is_active=True).order_by('-modified_on').first())

        if not run or run.submitted_by != self.submitted_by_obj:
            run = FlowRun.create(self.flow_obj,
                                 self.contact_obj,
                                 created_on=started,
                                 submitted_by=self.submitted_by_obj)

        step_objs = [
            FlowStep.from_json(step, self.flow_obj, run) for step in steps
        ]

        if completed:
            final_step = step_objs[len(step_objs) - 1] if step_objs else None
            completed_on = steps[len(steps) -
                                 1]['arrived_on'] if steps else None

            run.set_completed(final_step, completed_on=completed_on)
        else:
            run.save(update_fields=('modified_on', ))

        return run
Exemplo n.º 2
0
    def save(self):
        started = self.validated_data["started"]
        steps = self.validated_data.get("steps", [])
        completed = self.validated_data.get("completed", False)

        # look for previous run with this contact and flow
        run = (FlowRun.objects.filter(
            org=self.org,
            contact=self.contact_obj,
            flow=self.flow_obj,
            created_on=started,
            is_active=True).order_by("-modified_on").first())

        if not run or run.submitted_by != self.submitted_by_obj:
            run = FlowRun.create(self.flow_obj,
                                 self.contact_obj,
                                 created_on=started,
                                 submitted_by=self.submitted_by_obj)

        run.update_from_surveyor(steps)

        if completed:
            completed_on = steps[len(steps) -
                                 1]["arrived_on"] if steps else None

            run.set_completed(exit_uuid=None, completed_on=completed_on)
        else:
            run.save(update_fields=("modified_on", ))

        return run
Exemplo n.º 3
0
    def save(self):
        started = self.validated_data["started"]
        steps = self.validated_data.get("steps", [])
        completed = self.validated_data.get("completed", False)
        revision = self.validated_data.get("revision", self.validated_data.get("version"))

        # look for previous run with this contact and flow
        run = (
            FlowRun.objects.filter(
                org=self.org, contact=self.contact_obj, flow=self.flow_obj, created_on=started, is_active=True
            )
            .order_by("-modified_on")
            .first()
        )

        if not run or run.submitted_by != self.submitted_by_obj:
            run = FlowRun.create(
                self.flow_obj, self.contact_obj, created_on=started, submitted_by=self.submitted_by_obj
            )

        run.update_from_surveyor(revision, steps)

        if completed:
            completed_on = steps[len(steps) - 1]["arrived_on"] if steps else None

            run.set_completed(exit_uuid=None, completed_on=completed_on)
        else:
            run.save(update_fields=("modified_on",))

        return run
Exemplo n.º 4
0
 def _create_runs(self, count, flow, contacts):
     """
     Creates the given number of flow runs
     """
     runs = []
     for c in range(0, count):
         contact = contacts[c % len(contacts)]
         runs.append(FlowRun.create(flow, contact, db_insert=False))
     FlowRun.objects.bulk_create(runs)
     return runs
Exemplo n.º 5
0
 def _create_runs(self, count, flow, contacts):
     """
     Creates the given number of flow runs
     """
     runs = []
     for c in range(0, count):
         contact = contacts[c % len(contacts)]
         runs.append(FlowRun.create(flow, contact, db_insert=False))
     FlowRun.objects.bulk_create(runs)
     return runs
Exemplo n.º 6
0
    def _create_runs(self, count, flow, contacts):
        """
        Creates the given number of flow runs
        """
        runs = []
        for c in range(0, count):
            contact = contacts[c % len(contacts)]
            runs.append(FlowRun.create(flow, contact.pk, db_insert=False))
        FlowRun.objects.bulk_create(runs)

        # add a step to each run
        steps = []
        for run in FlowRun.objects.all():
            steps.append(FlowStep(run=run, contact=run.contact, step_type='R', step_uuid=flow.entry_uuid, arrived_on=timezone.now()))
        FlowStep.objects.bulk_create(steps)

        return runs
Exemplo n.º 7
0
    def _create_runs(self, count, flow, contacts):
        """
        Creates the given number of flow runs
        """
        runs = []
        for c in range(0, count):
            contact = contacts[c % len(contacts)]
            runs.append(FlowRun.create(flow, contact.pk, db_insert=False))
        FlowRun.objects.bulk_create(runs)

        # add a step to each run
        steps = []
        for run in FlowRun.objects.all():
            steps.append(
                FlowStep(run=run,
                         contact=run.contact,
                         step_type='R',
                         step_uuid=flow.entry_uuid,
                         arrived_on=timezone.now()))
        FlowStep.objects.bulk_create(steps)

        return runs
Exemplo n.º 8
0
    def post(self, request, *args, **kwargs):
        from twilio.request_validator 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 = VoiceResponse()
                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
Exemplo n.º 9
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)
                channel_type = channel.channel_type
                call.update_status("answered", None, channel_type)

                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)
Exemplo n.º 10
0
    def test_runs_offset(self):
        url = reverse('api.v2.runs')

        self.assertEndpointAccess(url)

        flow1 = self.create_flow(uuid_start=0)

        for i in range(600):
            FlowRun.create(flow1, self.joe.pk)

        with patch.object(timezone, 'now', return_value=datetime(2015, 9, 15, 0, 0, 0, 0, pytz.UTC)):
            now = timezone.now()
            for r in FlowRun.objects.all():
                r.modified_on = now
                r.save()

        with self.settings(CURSOR_PAGINATION_OFFSET_CUTOFF=10):
            response = self.fetchJSON(url)
            self.assertEqual(len(response.json['results']), 250)
            self.assertTrue(response.json['next'])

            query = response.json['next'].split('?')[1]
            response = self.fetchJSON(url, query=query)

            self.assertEqual(len(response.json['results']), 250)
            self.assertTrue(response.json['next'])

            query = response.json['next'].split('?')[1]
            response = self.fetchJSON(url, query=query)

            self.assertEqual(len(response.json['results']), 250)
            self.assertTrue(response.json['next'])

            query = response.json['next'].split('?')[1]
            response = self.fetchJSON(url, query=query)

            self.assertEqual(len(response.json['results']), 250)
            self.assertTrue(response.json['next'])

        with self.settings(CURSOR_PAGINATION_OFFSET_CUTOFF=400):
            url = reverse('api.v2.runs')
            response = self.fetchJSON(url)
            self.assertEqual(len(response.json['results']), 250)
            self.assertTrue(response.json['next'])

            query = response.json['next'].split('?')[1]
            response = self.fetchJSON(url, query=query)

            self.assertEqual(len(response.json['results']), 250)
            self.assertTrue(response.json['next'])

            query = response.json['next'].split('?')[1]
            response = self.fetchJSON(url, query=query)

            self.assertEqual(len(response.json['results']), 200)
            self.assertIsNone(response.json['next'])

        with self.settings(CURSOR_PAGINATION_OFFSET_CUTOFF=5000):
            url = reverse('api.v2.runs')
            response = self.fetchJSON(url)
            self.assertEqual(len(response.json['results']), 250)
            self.assertTrue(response.json['next'])

            query = response.json['next'].split('?')[1]
            response = self.fetchJSON(url, query=query)

            self.assertEqual(len(response.json['results']), 250)
            self.assertTrue(response.json['next'])

            query = response.json['next'].split('?')[1]
            response = self.fetchJSON(url, query=query)

            self.assertEqual(len(response.json['results']), 100)
            self.assertIsNone(response.json['next'])
Exemplo n.º 11
0
    def post(self, request, *args, **kwargs):
        from twilio.request_validator 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 = VoiceResponse()
                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
Exemplo n.º 12
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)
                channel_type = channel.channel_type
                call.update_status("answered", None, channel_type)

                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)