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
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
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
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 _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
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
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
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 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 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
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)