Example #1
0
    def end_flow_activity(self, org):
        self._log(" > Ending flow %s for in org %s\n" % (org.cache['activity']['flow'].name, org.name))

        org.cache['activity'] = None

        runs = FlowRun.objects.filter(org=org, is_active=True)
        FlowRun.bulk_exit(runs, FlowRun.EXIT_TYPE_EXPIRED)
def apply_manual():
    from temba.flows.models import Flow, FlowRun

    flows = Flow.objects.filter(is_archived=True)
    for flow in flows:
        runs = FlowRun.objects.filter(is_active=True, exit_type=None, flow_id=flow.id)
        FlowRun.bulk_exit(runs, FlowRun.EXIT_TYPE_INTERRUPTED)
Example #3
0
    def end_flow_activity(self, org):
        self._log(" > Ending flow %s for in org %s\n" % (org.cache["activity"]["flow"].name, org.name))

        org.cache["activity"] = None

        runs = FlowRun.objects.filter(org=org, is_active=True)
        FlowRun.bulk_exit(runs, FlowRun.EXIT_TYPE_EXPIRED)
Example #4
0
def remove_expired_flows_from_active(apps, schema_editor):
    r = get_redis_connection()
    for key in r.keys('*:step_active_set:*'):
        # make sure our flow run activity is removed
        FlowRun.do_expire_runs(
            FlowRun.objects.filter(pk__in=r.smembers(key),
                                   is_active=False,
                                   contact__is_test=False))
Example #5
0
def remove_expired_flows_from_active(apps, schema_editor):
    r = get_redis_connection()
    for key in r.keys('*:step_active_set:*'):
        # make sure our flow run activity is removed
        runs = FlowRun.objects.filter(pk__in=r.smembers(key),
                                      is_active=False,
                                      contact__is_test=False)
        FlowRun.bulk_exit(runs, FlowRun.EXIT_TYPE_EXPIRED)
Example #6
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
Example #7
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
Example #8
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
Example #9
0
    def validate_extra(self, value):
        # request is parsed by DRF.JSONParser, and if extra is a valid json it gets deserialized as dict
        # in any other case we need to raise a ValidationError
        if not isinstance(value, dict):
            raise serializers.ValidationError("Must be a valid JSON value")

        if not value:  # pragma: needs cover
            return None
        else:
            return FlowRun.normalize_fields(value)[0]
Example #10
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
Example #11
0
    def validate_extra(self, value):
        # request is parsed by DRF.JSONParser, and if extra is a valid json it gets deserialized as dict
        # in any other case we need to raise a ValidationError
        if not isinstance(value, dict):
            raise serializers.ValidationError("Must be a valid JSON value")

        if not value:  # pragma: needs cover
            return None
        else:
            return FlowRun.normalize_fields(value)[0]
Example #12
0
def remove_never_expirations(apps, schema_editor):
    from temba.flows.models import Flow, FlowRun

    # for every flow that has an expiration set to 0
    for flow in Flow.objects.filter(expires_after_minutes=0):
        # set the expiration to 30 days
        flow.expires_after_minutes = 60 * 24 * 30
        flow.save(update_fields=["expires_after_minutes"])

        # recalculate expiration on any active runs (possibly expiring them)
        for run in FlowRun.objects.filter(flow=flow, is_active=True):
            run.flow = flow
            if run.path:
                last_arrived_on = iso8601.parse_date(run.path[-1]["arrived_on"])
                run.update_expiration(last_arrived_on)

    # check and expire any flows that are now expired
    runs = FlowRun.objects.filter(is_active=True, expires_on__lte=timezone.now()).order_by("expires_on")
    FlowRun.bulk_exit(runs, FlowRun.EXIT_TYPE_EXPIRED)
Example #13
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
def exit_active_flowruns(Contact, log=False):
    from temba.flows.models import FlowRun

    exit_runs = []

    # find all contacts that have more than one active run
    active_contact_ids = Contact.objects.filter(runs__is_active=True).order_by('id')\
        .annotate(run_count=Count('id')).filter(run_count__gt=1).values_list('id', flat=True)

    if log:
        print "%d contacts to evaluate runs for" % len(active_contact_ids)

    for idx, contact_id in enumerate(active_contact_ids):
        active_runs = FlowRun.objects.filter(
            contact_id=contact_id, is_active=True).order_by('-modified_on')

        # more than one? we may need to expire some
        if len(active_runs) > 1:
            last = active_runs[0]
            contact_exit_runs = [r.id for r in active_runs[1:]]
            ancestor = last.parent
            while ancestor:
                exit_runs.remove(ancestor.id)
                ancestor = ancestor.parent

            exit_runs += contact_exit_runs

        if (idx % 100) == 0:
            if log:
                print "  - %d / %d contacts evaluated. %d runs to exit" % (
                    idx, len(active_contact_ids), len(exit_runs))

    # ok, now exit those runs
    exited = 0
    for batch in chunk_list(exit_runs, 1000):
        runs = FlowRun.objects.filter(id__in=batch)
        FlowRun.bulk_exit(runs, FlowRun.EXIT_TYPE_INTERRUPTED, timezone.now())

        exited += len(batch)
        if log:
            print " * %d / %d runs exited." % (exited, len(exit_runs))
Example #15
0
    def save(self):
        user = self.context['user']
        contacts = self.validated_data['contacts']
        action = self.validated_data['action']
        group = self.validated_data.get('group')

        if action == self.ADD:
            group.update_contacts(user, contacts, add=True)
        elif action == self.REMOVE:
            group.update_contacts(user, contacts, add=False)
        elif action == self.INTERRUPT:
            FlowRun.exit_all_for_contacts(contacts, FlowRun.EXIT_TYPE_INTERRUPTED)
        elif action == self.ARCHIVE:
            Msg.archive_all_for_contacts(contacts)
        else:
            for contact in contacts:
                if action == self.BLOCK:
                    contact.block(user)
                elif action == self.UNBLOCK:
                    contact.unblock(user)
                elif action == self.DELETE:
                    contact.release(user)
Example #16
0
    def save(self):
        user = self.context['user']
        contacts = self.validated_data['contacts']
        action = self.validated_data['action']
        group = self.validated_data.get('group')

        if action == self.ADD:
            group.update_contacts(user, contacts, add=True)
        elif action == self.REMOVE:
            group.update_contacts(user, contacts, add=False)
        elif action == self.INTERRUPT:
            FlowRun.exit_all_for_contacts(contacts, FlowRun.EXIT_TYPE_INTERRUPTED)
        elif action == self.ARCHIVE:
            Msg.archive_all_for_contacts(contacts)
        else:
            for contact in contacts:
                if action == self.BLOCK:
                    contact.block(user)
                elif action == self.UNBLOCK:
                    contact.unblock(user)
                elif action == self.DELETE:
                    contact.release(user)
Example #17
0
def exit_active_flowruns(Contact, log=False):
    from temba.flows.models import FlowRun

    exit_runs = []

    # find all contacts that have more than one active run
    active_contact_ids = Contact.objects.filter(runs__is_active=True).order_by('id')\
        .annotate(run_count=Count('id')).filter(run_count__gt=1).values_list('id', flat=True)

    if log:
        print "%d contacts to evaluate runs for" % len(active_contact_ids)

    for idx, contact_id in enumerate(active_contact_ids):
        active_runs = FlowRun.objects.filter(contact_id=contact_id, is_active=True).order_by('-modified_on')

        # more than one? we may need to expire some
        if len(active_runs) > 1:
            last = active_runs[0]
            contact_exit_runs = [r.id for r in active_runs[1:]]
            ancestor = last.parent
            while ancestor:
                exit_runs.remove(ancestor.id)
                ancestor = ancestor.parent

            exit_runs += contact_exit_runs

        if (idx % 100) == 0:
            if log:
                print "  - %d / %d contacts evaluated. %d runs to exit" % (idx, len(active_contact_ids), len(exit_runs))

    # ok, now exit those runs
    exited = 0
    for batch in chunk_list(exit_runs, 1000):
        runs = FlowRun.objects.filter(id__in=batch)
        FlowRun.bulk_exit(runs, FlowRun.EXIT_TYPE_INTERRUPTED, timezone.now())

        exited += len(batch)
        if log:
            print " * %d / %d runs exited." % (exited, len(exit_runs))
Example #18
0
def enable_flow_server(org):
    """
    Enables the flow server for an org. This switches all flows to be flow-server-enabled and switched all handling
    to take place through Mailroom going forward. Note that people currently in flows will be interrupted and there's
    no going back after doing this.
    """
    from temba.flows.models import FlowRun

    # update all channels (we do this first as this may throw and we don't want to do the rest unless it succeeds)
    for channel in org.channels.filter(is_active=True):
        channel_type = channel.get_type()
        channel_type.enable_flow_server(channel)

    # interrupt all active runs
    FlowRun.bulk_exit(org.runs.filter(is_active=True),
                      FlowRun.EXIT_TYPE_INTERRUPTED)

    # flip all flows
    org.flows.filter(is_active=True).update(flow_server_enabled=True)

    # finally flip our org
    org.flow_server_enabled = True
    org.save(update_fields=["flow_server_enabled", "modified_on"])
Example #19
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
Example #20
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
Example #21
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)
def remove_expired_flows_from_active(apps, schema_editor):
    r = get_redis_connection()
    for key in r.keys('*:step_active_set:*'):
        # make sure our flow run activity is removed
        runs = FlowRun.objects.filter(pk__in=r.smembers(key), is_active=False, contact__is_test=False)
        FlowRun.bulk_exit(runs, FlowRun.EXIT_TYPE_EXPIRED)
Example #23
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
Example #24
0
 def validate_extra(self, value):
     if not value:  # pragma: needs cover
         return None
     else:
         return FlowRun.normalize_fields(value)[0]
Example #25
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)
Example #26
0
 def validate_extra(self, value):
     if not value:
         return None
     else:
         return FlowRun.normalize_fields(value)[0]
Example #27
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
Example #28
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'])
def remove_expired_flows_from_active(apps, schema_editor):
    r = get_redis_connection()
    for key in r.keys('*:step_active_set:*'):
        # make sure our flow run activity is removed
        FlowRun.do_expire_runs(FlowRun.objects.filter(pk__in=r.smembers(key), is_active=False, contact__is_test=False))