Exemplo n.º 1
0
def get_data(ids):
    """
    returns the data in the format:
    {
        '2015-03': {
            'domain1': {
                'KOOKOO': {'calls': 40, 'minutes': 45}
            },
            'domain2': {
                'KOOKOO': {'calls': 20, 'minutes': 25}
                'TELERIVET': {'calls': 5, 'minutes': 0}
            }
        }
    }
    """
    data = {}
    for doc in iter_docs(CallLog.get_db(), ids):
        call = CallLog.wrap(doc)
        month_data = get_month_data(data, call.date)
        domain_data = get_domain_data(month_data, call.domain)
        backend_api = get_backend_api(call)
        backend_data = get_backend_data(domain_data, backend_api)
        backend_data['calls'] += 1
        duration = (call.duration or 0) / 60.0
        duration = int(ceil(duration))
        backend_data['minutes'] += duration
    return data
Exemplo n.º 2
0
    def testCallSync(self):
        self.deleteAllLogs()
        self.assertEqual(self.getCallLogCount(), 0)
        self.assertEqual(self.getCallCount(), 0)

        # Test Create
        call = Call()
        self.setRandomCallValues(call)
        call.save()

        sleep(1)
        self.assertEqual(self.getCallLogCount(), 1)
        self.assertEqual(self.getCallCount(), 1)

        calllog = CallLog.get(call.couch_id)
        self.checkFieldValues(calllog, call, Call._migration_get_fields())
        self.assertTrue(CallLog.get_db().get_rev(calllog._id).startswith('2-'))

        # Test Update
        self.setRandomCallValues(call)
        call.save()

        sleep(1)
        self.assertEqual(self.getCallLogCount(), 1)
        self.assertEqual(self.getCallCount(), 1)
        callog = CallLog.get(call.couch_id)
        self.checkFieldValues(callog, call, Call._migration_get_fields())
        self.assertTrue(CallLog.get_db().get_rev(callog._id).startswith('3-'))
Exemplo n.º 3
0
    def test_401_response(self):
        start_count = CallLog.count_by_domain(self.domain)

        response = Client().post('/twilio/ivr/xxxxx', {
            'From': self.phone_number,
            'CallSid': 'xyz',
        })
        self.assertEqual(response.status_code, 401)

        end_count = CallLog.count_by_domain(self.domain)
        self.assertEqual(start_count, end_count)
Exemplo n.º 4
0
    def test_log_call(self):
        if self.__class__ == LogCallTestCase:
            # The test runner picks up this base class too, but we only
            # want to run the test on subclasses.
            return

        self.assertEqual(CallLog.count_by_domain(self.domain), 0)
        response = self.simulate_inbound_call(self.phone_number)
        self.check_response(response)
        self.assertEqual(CallLog.count_by_domain(self.domain), 1)

        call = CallLog.by_domain_asc(self.domain).all()[0]
        self.assertEqual(call.couch_recipient_doc_type, 'CommCareCase')
        self.assertEqual(call.couch_recipient, self.case.get_id)
        self.assertEqual(call.direction, INCOMING)
Exemplo n.º 5
0
def incoming(phone_number, backend_module, gateway_session_id, ivr_event, input_data=None,
        duration=None):
    """
    The main entry point for all incoming IVR requests.
    """
    call_log_entry = CallLog.get_call_by_gateway_session_id(gateway_session_id)
    logged_subevent = None
    if call_log_entry and call_log_entry.messaging_subevent_id:
        logged_subevent = MessagingSubEvent.objects.get(
            pk=call_log_entry.messaging_subevent_id)

    if call_log_entry:
        add_metadata(call_log_entry, duration)

    if call_log_entry and call_log_entry.form_unique_id is None:
        # If this request is for a call with no form,
        # then just short circuit everything and hang up
        return hang_up_response(gateway_session_id, backend_module=backend_module)

    if call_log_entry and backend_module:
        return handle_known_call_session(call_log_entry, backend_module, ivr_event,
            input_data=input_data, logged_subevent=logged_subevent)
    else:
        if not call_log_entry:
            log_call(phone_number, gateway_session_id, backend_module=backend_module)
        return hang_up_response(gateway_session_id, backend_module=backend_module)
Exemplo n.º 6
0
def ivr_finished(request):
    # Retrieve all parameters
    status = request.POST.get("status", None)
    start_time = request.POST.get("start_time", None)
    caller_id = request.POST.get("caller_id", None)
    phone_no = request.POST.get("phone_no", None)
    sid = request.POST.get("sid", "")
    duration = request.POST.get("duration", None)
    ringing_time = request.POST.get("ringing_time", None)
    status_details = request.POST.get("status_details", None)
    
    gateway_session_id = "KOOKOO-" + sid
    
    call_log_entry = CallLog.view("sms/call_by_session",
                                  startkey=[gateway_session_id, {}],
                                  endkey=[gateway_session_id],
                                  descending=True,
                                  include_docs=True,
                                  limit=1).one()
    if call_log_entry is not None:
        try:
            duration = int(duration)
        except Exception:
            duration = None
        call_log_entry.answered = (status == "answered")
        call_log_entry.duration = duration
        call_log_entry.save()
    
    return HttpResponse("")
 def get_call_couch_ids(self):
     result = CallLog.view(
         'sms/by_domain',
         include_docs=False,
         reduce=False,
     ).all()
     return [row['id'] for row in result if row['key'][1] == 'CallLog']
Exemplo n.º 8
0
def fire_ivr_survey_event(reminder, handler, recipients, verified_numbers):
    if handler.recipient == RECIPIENT_CASE:
        # If there are no recipients, just move to the next reminder event
        if len(recipients) == 0:
            return True
        
        # If last_fired is None, it means that the reminder fired for the first time on a timeout interval. So we can
        # skip the lookup for the answered call since no call went out yet.
        if reminder.last_fired is not None and reminder.callback_try_count > 0 and CallLog.answered_call_exists(recipients[0].doc_type, recipients[0].get_id, reminder.last_fired):
            reminder.skip_remaining_timeouts = True
            return True
        verified_number = verified_numbers[recipients[0].get_id]
        if verified_number is not None:
            if initiate_outbound_call(verified_number, reminder.current_event.form_unique_id, handler.submit_partial_forms, handler.include_case_side_effects, handler.max_question_retries):
                return True
            else:
                reminder = CaseReminder.get(reminder._id)
                reminder.error_retry_count += 1
                if reminder.error_retry_count > getattr(settings, "IVR_OUTBOUND_RETRIES", DEFAULT_OUTBOUND_RETRIES):
                    return True
                else:
                    reminder.next_fire += timedelta(minutes=getattr(settings, "IVR_OUTBOUND_RETRY_INTERVAL", DEFAULT_OUTBOUND_RETRY_INTERVAL))
                    reminder.save()
                    return False
        else:
            raise_error(reminder, ERROR_NO_VERIFIED_NUMBER)
            return False
    else:
        # TODO: Implement ivr survey for RECIPIENT_USER, RECIPIENT_OWNER, and RECIPIENT_SURVEY_SAMPLE
        return False
Exemplo n.º 9
0
def ivr_finished(request):
    # Retrieve all parameters
    status = request.POST.get("status", None)
    start_time = request.POST.get("start_time", None)
    caller_id = request.POST.get("caller_id", None)
    phone_no = request.POST.get("phone_no", None)
    sid = request.POST.get("sid", "")
    duration = request.POST.get("duration", None)
    ringing_time = request.POST.get("ringing_time", None)
    status_details = request.POST.get("status_details", None)
    
    gateway_session_id = "KOOKOO-" + sid

    with CriticalSection([gateway_session_id]):
        call_log_entry = CallLog.view("sms/call_by_session",
                                      startkey=[gateway_session_id, {}],
                                      endkey=[gateway_session_id],
                                      descending=True,
                                      include_docs=True,
                                      limit=1).one()
        if call_log_entry is not None:
            try:
                duration = int(duration)
            except Exception:
                duration = None
            call_log_entry.answered = (status == "answered")
            call_log_entry.duration = duration
            call_log_entry.save()
    
    return HttpResponse("")
Exemplo n.º 10
0
def ivr_finished(request):
    """
    Kookoo invokes this view after a call is finished (whether answered or not)
    with status and some statistics.
    Point Kookoo's 'callback_url' parameter here.
    """
    # Retrieve all parameters
    status = request.POST.get("status", None)
    start_time = request.POST.get("start_time", None)
    caller_id = request.POST.get("caller_id", None)
    phone_no = request.POST.get("phone_no", None)
    sid = request.POST.get("sid", "")
    duration = request.POST.get("duration", None)
    ringing_time = request.POST.get("ringing_time", None)
    status_details = request.POST.get("status_details", None)
    
    gateway_session_id = "KOOKOO-" + sid

    with CriticalSection([gateway_session_id], timeout=300):
        call_log_entry = CallLog.view("sms/call_by_session",
                                      startkey=[gateway_session_id, {}],
                                      endkey=[gateway_session_id],
                                      descending=True,
                                      include_docs=True,
                                      limit=1).one()
        if call_log_entry is not None:
            try:
                duration = int(duration)
            except Exception:
                duration = None
            call_log_entry.answered = (status == "answered")
            call_log_entry.duration = duration
            call_log_entry.save()
    
    return HttpResponse("")
Exemplo n.º 11
0
 def get_call_couch_ids(self):
     result = CallLog.view(
         'sms/by_domain',
         include_docs=False,
         reduce=False,
     ).all()
     return [row['id'] for row in result if row['key'][1] == 'CallLog']
Exemplo n.º 12
0
def fire_sms_callback_event(reminder, handler, recipients, verified_numbers):
    current_event = reminder.current_event
    if handler.recipient in [RECIPIENT_CASE, RECIPIENT_USER]:
        # If there are no recipients, just move to the next reminder event
        if len(recipients) == 0:
            return True

        # If the callback has been received, skip sending the next timeout message
        if reminder.callback_try_count > 0:
            # Lookup the expected callback event
            if reminder.event_initiation_timestamp is None:
                event = None
            else:
                event = ExpectedCallbackEventLog.view(
                    "sms/expected_callback_event",
                    key=[
                        reminder.domain,
                        json_format_datetime(
                            reminder.event_initiation_timestamp),
                        recipients[0].get_id
                    ],
                    include_docs=True,
                    limit=1).one()

            # NOTE: If last_fired is None, it means that the reminder fired for the first time on a timeout interval
            if reminder.last_fired is not None and CallLog.inbound_entry_exists(
                    recipients[0].doc_type, recipients[0].get_id,
                    reminder.last_fired):
                reminder.skip_remaining_timeouts = True
                if event is not None:
                    event.status = CALLBACK_RECEIVED
                    event.save()
                return True
            elif reminder.callback_try_count >= len(
                    current_event.callback_timeout_intervals):
                # On the last callback timeout, instead of sending the SMS again, log the missed callback
                if event is not None:
                    event.status = CALLBACK_MISSED
                    event.save()
                return True
        else:
            # It's the first time sending the sms, so create an expected callback event
            event = ExpectedCallbackEventLog(
                domain=reminder.domain,
                date=reminder.event_initiation_timestamp,
                couch_recipient_doc_type=recipients[0].doc_type,
                couch_recipient=recipients[0].get_id,
                status=CALLBACK_PENDING,
            )
            event.save()

        return fire_sms_event(reminder,
                              handler,
                              recipients,
                              verified_numbers,
                              workflow=WORKFLOW_CALLBACK)
    else:
        # TODO: Implement sms callback for RECIPIENT_OWNER and RECIPIENT_SURVEY_SAMPLE
        return False
Exemplo n.º 13
0
def fire_sms_callback_event(reminder, handler, recipients, verified_numbers,
                            logged_event):
    current_event = reminder.current_event

    for recipient in recipients:
        send_message = False
        if reminder.callback_try_count > 0:
            if reminder.event_initiation_timestamp:
                event = ExpectedCallbackEventLog.view(
                    "sms/expected_callback_event",
                    key=[
                        reminder.domain,
                        json_format_datetime(
                            reminder.event_initiation_timestamp),
                        recipient.get_id
                    ],
                    include_docs=True,
                    limit=1).one()
                if not event:
                    continue
                if event.status == CALLBACK_RECEIVED:
                    continue
                if CallLog.inbound_entry_exists(
                        recipient.doc_type, recipient.get_id,
                        reminder.event_initiation_timestamp):
                    event.status = CALLBACK_RECEIVED
                    event.save()
                    continue
            else:
                continue

            if (reminder.callback_try_count >= len(
                    current_event.callback_timeout_intervals)):
                # On the last callback timeout, instead of sending the SMS
                # again, log the missed callback
                if event:
                    event.status = CALLBACK_MISSED
                    event.save()
            else:
                send_message = True
        else:
            # It's the first time sending the sms, so create an expected
            # callback event
            send_message = True
            event = ExpectedCallbackEventLog(
                domain=reminder.domain,
                date=reminder.event_initiation_timestamp,
                couch_recipient_doc_type=recipient.doc_type,
                couch_recipient=recipient.get_id,
                status=CALLBACK_PENDING,
            )
            event.save()

        if send_message:
            fire_sms_event(reminder,
                           handler, [recipient],
                           verified_numbers,
                           logged_event,
                           workflow=WORKFLOW_CALLBACK)
Exemplo n.º 14
0
def initiate_outbound_call(recipient,
                           form_unique_id,
                           submit_partial_form,
                           include_case_side_effects,
                           max_question_retries,
                           verified_number=None,
                           unverified_number=None,
                           case_id=None,
                           case_for_case_submission=False,
                           timestamp=None):
    """
    Returns True if the call was queued successfully, or False if an error
    occurred.
    """
    call_log_entry = None
    try:
        if not verified_number and not unverified_number:
            return False
        phone_number = (verified_number.phone_number
                        if verified_number else unverified_number)
        call_log_entry = CallLog(
            couch_recipient_doc_type=recipient.doc_type,
            couch_recipient=recipient.get_id,
            phone_number="+%s" % str(phone_number),
            direction=OUTGOING,
            date=timestamp or datetime.utcnow(),
            domain=recipient.domain,
            form_unique_id=form_unique_id,
            submit_partial_form=submit_partial_form,
            include_case_side_effects=include_case_side_effects,
            max_question_retries=max_question_retries,
            current_question_retry_count=0,
            case_id=case_id,
            case_for_case_submission=case_for_case_submission,
        )
        backend = get_ivr_backend(recipient, verified_number,
                                  unverified_number)
        if not backend:
            return False
        kwargs = backend.get_cleaned_outbound_params()
        module = __import__(backend.outbound_module,
                            fromlist=["initiate_outbound_call"])
        call_log_entry.backend_api = module.API_ID
        call_log_entry.save()
        return module.initiate_outbound_call(call_log_entry, **kwargs)
    except Exception:
        if call_log_entry:
            call_log_entry.error = True
            call_log_entry.error_message = "Internal Server Error"
            call_log_entry.save()
        raise
Exemplo n.º 15
0
def ivr_in(request):
    """
    Handles tropo call requests
    """
    if request.method == "POST":
        data = json.loads(request.body)
        phone_number = data["session"]["from"]["id"]
        # TODO: Implement tropo as an ivr backend. In the meantime, just log the call.

        if phone_number:
            cleaned_number = strip_plus(phone_number)
            v = VerifiedNumber.by_extensive_search(cleaned_number)
        else:
            v = None

        # Save the call entry
        msg = CallLog(
            phone_number=cleaned_number,
            direction=INCOMING,
            date=datetime.utcnow(),
            backend_api=TropoBackend.get_api_id(),
        )
        if v is not None:
            msg.domain = v.domain
            msg.couch_recipient_doc_type = v.owner_doc_type
            msg.couch_recipient = v.owner_id
        msg.save()

        t = Tropo()
        t.reject()
        return HttpResponse(t.RenderJson())
    else:
        return HttpResponseBadRequest("Bad Request")
Exemplo n.º 16
0
 def getCallLogCount(self):
     result = CallLog.view(
         'sms/by_domain',
         startkey=[self.domain, 'CallLog'],
         endkey=[self.domain, 'CallLog', {}],
         include_docs=False,
         reduce=True,
     ).all()
     if result:
         return result[0]['value']
     return 0
Exemplo n.º 17
0
def initiate_outbound_call(verified_number, form_unique_id, submit_partial_form, include_case_side_effects, max_question_retries):
    call_log_entry = CallLog(
        couch_recipient_doc_type = verified_number.owner_doc_type,
        couch_recipient          = verified_number.owner_id,
        phone_number             = "+" + str(verified_number.phone_number),
        direction                = OUTGOING,
        date                     = datetime.utcnow(),
        domain                   = verified_number.domain,
        form_unique_id           = form_unique_id,
        submit_partial_form      = submit_partial_form,
        include_case_side_effects = include_case_side_effects,
        max_question_retries     = max_question_retries,
        current_question_retry_count = 0,
    )
    backend = verified_number.ivr_backend
    kwargs = backend.get_cleaned_outbound_params()
    module = __import__(backend.outbound_module, fromlist=["initiate_outbound_call"])
    call_log_entry.backend_api = module.API_ID
    call_log_entry.save()
    return module.initiate_outbound_call(call_log_entry, **kwargs)
Exemplo n.º 18
0
def fire_ivr_survey_event(reminder, handler, recipients, verified_numbers,
                          logged_event):
    domain_obj = Domain.get_by_name(reminder.domain, strict=True)
    for recipient in recipients:
        initiate_call = True
        if reminder.callback_try_count > 0 and reminder.event_initiation_timestamp:
            initiate_call = not CallLog.answered_call_exists(
                recipient.doc_type, recipient.get_id,
                reminder.event_initiation_timestamp,
                CaseReminderHandler.get_now())

        if initiate_call:
            if (isinstance(recipient, CommCareCase)
                    and not handler.force_surveys_to_use_triggered_case):
                case_id = recipient.get_id
            else:
                case_id = reminder.case_id
            verified_number, unverified_number = get_recipient_phone_number(
                reminder, recipient, verified_numbers)
            if verified_number:
                initiate_outbound_call.delay(
                    recipient,
                    reminder.current_event.form_unique_id,
                    handler.submit_partial_forms,
                    handler.include_case_side_effects,
                    handler.max_question_retries,
                    logged_event.pk,
                    verified_number=verified_number,
                    case_id=case_id,
                    case_for_case_submission=handler.
                    force_surveys_to_use_triggered_case,
                    timestamp=CaseReminderHandler.get_now(),
                )
            elif domain_obj.send_to_duplicated_case_numbers and unverified_number:
                initiate_outbound_call.delay(
                    recipient,
                    reminder.current_event.form_unique_id,
                    handler.submit_partial_forms,
                    handler.include_case_side_effects,
                    handler.max_question_retries,
                    logged_event.pk,
                    unverified_number=unverified_number,
                    case_id=case_id,
                    case_for_case_submission=handler.
                    force_surveys_to_use_triggered_case,
                    timestamp=CaseReminderHandler.get_now(),
                )
            else:
                # initiate_outbound_call will create the subevent automatically,
                # so since we're not initiating the call here, we have to create
                # the subevent explicitly in order to log the error.
                logged_subevent = logged_event.create_subevent(
                    handler, reminder, recipient)
                logged_subevent.error(MessagingEvent.ERROR_NO_PHONE_NUMBER)
Exemplo n.º 19
0
def initiate_outbound_call(verified_number, form_unique_id, submit_partial_form, include_case_side_effects, max_question_retries):
    call_log_entry = CallLog(
        couch_recipient_doc_type = verified_number.owner_doc_type,
        couch_recipient          = verified_number.owner_id,
        phone_number             = "+" + str(verified_number.phone_number),
        direction                = OUTGOING,
        date                     = datetime.utcnow(),
        domain                   = verified_number.domain,
        form_unique_id           = form_unique_id,
        submit_partial_form      = submit_partial_form,
        include_case_side_effects = include_case_side_effects,
        max_question_retries     = max_question_retries,
        current_question_retry_count = 0,
    )
    backend = verified_number.ivr_backend
    kwargs = backend.get_cleaned_outbound_params()
    module = __import__(backend.outbound_module, fromlist=["initiate_outbound_call"])
    call_log_entry.backend_api = module.API_ID
    call_log_entry.save()
    return module.initiate_outbound_call(call_log_entry, **kwargs)
Exemplo n.º 20
0
def get_data(ids, timezone=None):
    """
    returns the data in the format:
    {
        '2015-03': {
            'domain1': {
                'KOOKOO': {
                    'I': {'calls': 2, 'minutes': 3},
                    'O': {'calls': 40, 'minutes': 45},
                    '?': {'calls': 0, 'minutes': 0},
                 },
            },
            'domain2': {
                'KOOKOO': {
                    'I': {'calls': 1, 'minutes': 1},
                    'O': {'calls': 20, 'minutes': 25},
                    '?': {'calls': 0, 'minutes': 0},
                 },
                'TELERIVET': {
                    'I': {'calls': 10, 'minutes': 0},
                    'O': {'calls': 0, 'minutes': 0},
                    '?': {'calls': 0, 'minutes': 0},
                 },
            }
        }
    }
    """
    data = {}
    for doc in iter_docs(CallLog.get_db(), ids):
        call = CallLog.wrap(doc)
        date = get_naive_user_datetime(call.date, timezone=timezone)
        month_data = get_month_data(data, date)
        domain_data = get_domain_data(month_data, call.domain)
        backend_api = get_backend_api(call)
        backend_data = get_backend_data(domain_data, backend_api)
        direction = get_direction(call)
        backend_data[direction]['calls'] += 1
        duration = (call.duration or 0) / 60.0
        duration = int(ceil(duration))
        backend_data[direction]['minutes'] += duration
    return data
Exemplo n.º 21
0
def get_data(ids, timezone=None):
    """
    returns the data in the format:
    {
        '2015-03': {
            'domain1': {
                'KOOKOO': {
                    'I': {'calls': 2, 'minutes': 3},
                    'O': {'calls': 40, 'minutes': 45},
                    '?': {'calls': 0, 'minutes': 0},
                 },
            },
            'domain2': {
                'KOOKOO': {
                    'I': {'calls': 1, 'minutes': 1},
                    'O': {'calls': 20, 'minutes': 25},
                    '?': {'calls': 0, 'minutes': 0},
                 },
                'TELERIVET': {
                    'I': {'calls': 10, 'minutes': 0},
                    'O': {'calls': 0, 'minutes': 0},
                    '?': {'calls': 0, 'minutes': 0},
                 },
            }
        }
    }
    """
    data = {}
    for doc in iter_docs(CallLog.get_db(), ids):
        call = CallLog.wrap(doc)
        date = get_naive_user_datetime(call.date, timezone=timezone)
        month_data = get_month_data(data, date)
        domain_data = get_domain_data(month_data, call.domain)
        backend_api = get_backend_api(call)
        backend_data = get_backend_data(domain_data, backend_api)
        direction = get_direction(call)
        backend_data[direction]['calls'] += 1
        duration = (call.duration or 0) / 60.0
        duration = int(ceil(duration))
        backend_data[direction]['minutes'] += duration
    return data
Exemplo n.º 22
0
 def get_last_outbound_call(self, contact):
     # Not clear why this should be necessary, but without it the latest
     # call may not be returned
     sleep(0.25)
     call = CallLog.view("sms/by_recipient",
         startkey=[contact.doc_type, contact._id, "CallLog", "O", {}],
         endkey=[contact.doc_type, contact._id, "CallLog", "O"],
         descending=True,
         include_docs=True,
         reduce=False,
     ).first()
     return call
Exemplo n.º 23
0
def ivr_in(request):
    """
    Handles tropo call requests
    """
    if request.method == "POST":
        data = json.loads(request.body)
        phone_number = data["session"]["from"]["id"]
        # TODO: Implement tropo as an ivr backend. In the meantime, just log the call.

        if phone_number:
            cleaned_number = strip_plus(phone_number)
            v = VerifiedNumber.by_extensive_search(cleaned_number)
        else:
            v = None

        # Save the call entry
        msg = CallLog(
            phone_number=cleaned_number,
            direction=INCOMING,
            date=datetime.utcnow(),
            backend_api=SQLTropoBackend.get_api_id(),
        )
        if v is not None:
            msg.domain = v.domain
            msg.couch_recipient_doc_type = v.owner_doc_type
            msg.couch_recipient = v.owner_id
        msg.save()

        t = Tropo()
        t.reject()
        return HttpResponse(t.RenderJson())
    else:
        return HttpResponseBadRequest("Bad Request")
Exemplo n.º 24
0
def incoming(phone_number, backend_api):
    cleaned_number = phone_number
    if len(cleaned_number) > 0 and cleaned_number[0] == "+":
        cleaned_number = cleaned_number[1:]
    
    # Try to look up the verified number entry
    v = VerifiedNumber.view("sms/verified_number_by_number",
        key=cleaned_number,
        include_docs=True
    ).one()
    
    # If none was found, try to match only the last digits of numbers in the database
    if v is None:
        v = VerifiedNumber.view("sms/verified_number_by_suffix",
            key=cleaned_number,
            include_docs=True
        ).one()
    
    # Save the call entry
    msg = CallLog(
        phone_number    = cleaned_number,
        direction       = INCOMING,
        date            = datetime.utcnow(),
        backend_api     = backend_api
    )
    if v is not None:
        msg.domain = v.domain
        msg.couch_recipient_doc_type = v.owner_doc_type
        msg.couch_recipient = v.owner_id
    msg.save()
Exemplo n.º 25
0
def fire_sms_callback_event(reminder, handler, recipients, verified_numbers):
    current_event = reminder.current_event

    for recipient in recipients:
        send_message = False
        if reminder.callback_try_count > 0:
            if reminder.event_initiation_timestamp:
                event = ExpectedCallbackEventLog.view("sms/expected_callback_event",
                    key=[reminder.domain,
                         json_format_datetime(reminder.event_initiation_timestamp),
                         recipient.get_id],
                    include_docs=True,
                    limit=1).one()
                if not event:
                    continue
                if event.status == CALLBACK_RECEIVED:
                    continue
                if CallLog.inbound_entry_exists(recipient.doc_type,
                    recipient.get_id, reminder.event_initiation_timestamp):
                    event.status = CALLBACK_RECEIVED
                    event.save()
                    continue
            else:
                continue

            if (reminder.callback_try_count >=
                len(current_event.callback_timeout_intervals)):
                # On the last callback timeout, instead of sending the SMS
                # again, log the missed callback
                if event:
                    event.status = CALLBACK_MISSED
                    event.save()
            else:
                send_message = True
        else:
            # It's the first time sending the sms, so create an expected
            # callback event
            send_message = True
            event = ExpectedCallbackEventLog(
                domain=reminder.domain,
                date=reminder.event_initiation_timestamp,
                couch_recipient_doc_type=recipient.doc_type,
                couch_recipient=recipient.get_id,
                status=CALLBACK_PENDING,
            )
            event.save()

        if send_message:
            fire_sms_event(reminder, handler, [recipient], verified_numbers,
                workflow=WORKFLOW_CALLBACK)

    return True
Exemplo n.º 26
0
def get_ids(start_date=None):
    result = CallLog.view(
        'sms/call_by_session',
        include_docs=False,
    ).all()
    def include_row(row):
        if start_date:
            try:
                date = parse(row['key'][1]).replace(tzinfo=None)
            except:
                return False
            return date >= start_date
        return True
    return [row['id'] for row in result if include_row(row)]
 def handle(self, *args, **options):
     for domain in Domain.get_all():
         count = (SMSLog.count_by_domain(domain.name) +
                  CallLog.count_by_domain(domain.name))
         if count > 0:
             if not domain.send_to_duplicated_case_numbers:
                 # if not True, explicitly set to False
                 print "Setting %s to False" % domain.name
                 domain.send_to_duplicated_case_numbers = False
                 domain.save()
         else:
             print "Setting %s to True" % domain.name
             domain.send_to_duplicated_case_numbers = True
             domain.save()
Exemplo n.º 28
0
def fire_ivr_survey_event(reminder, handler, recipients, verified_numbers, logged_event):
    domain_obj = Domain.get_by_name(reminder.domain, strict=True)
    for recipient in recipients:
        initiate_call = True
        if reminder.callback_try_count > 0 and reminder.event_initiation_timestamp:
            initiate_call = not CallLog.answered_call_exists(
                recipient.doc_type, recipient.get_id,
                reminder.event_initiation_timestamp,
                CaseReminderHandler.get_now())

        if initiate_call:
            if (isinstance(recipient, CommCareCase) and
                not handler.force_surveys_to_use_triggered_case):
                case_id = recipient.get_id
            else:
                case_id = reminder.case_id
            verified_number, unverified_number = get_recipient_phone_number(
                reminder, recipient, verified_numbers)
            if verified_number:
                initiate_outbound_call.delay(
                    recipient,
                    reminder.current_event.form_unique_id,
                    handler.submit_partial_forms,
                    handler.include_case_side_effects,
                    handler.max_question_retries,
                    logged_event.pk,
                    verified_number=verified_number,
                    case_id=case_id,
                    case_for_case_submission=handler.force_surveys_to_use_triggered_case,
                    timestamp=CaseReminderHandler.get_now(),
                )
            elif domain_obj.send_to_duplicated_case_numbers and unverified_number:
                initiate_outbound_call.delay(
                    recipient,
                    reminder.current_event.form_unique_id,
                    handler.submit_partial_forms,
                    handler.include_case_side_effects,
                    handler.max_question_retries,
                    logged_event.pk,
                    unverified_number=unverified_number,
                    case_id=case_id,
                    case_for_case_submission=handler.force_surveys_to_use_triggered_case,
                    timestamp=CaseReminderHandler.get_now(),
                )
            else:
                # initiate_outbound_call will create the subevent automatically,
                # so since we're not initiating the call here, we have to create
                # the subevent explicitly in order to log the error.
                logged_subevent = logged_event.create_subevent(handler, reminder, recipient)
                logged_subevent.error(MessagingEvent.ERROR_NO_PHONE_NUMBER)
 def handle(self, *args, **options):
     for domain in Domain.get_all():
         count = (SMSLog.count_by_domain(domain.name) +
             CallLog.count_by_domain(domain.name))
         if count > 0:
             if not domain.send_to_duplicated_case_numbers:
                 # if not True, explicitly set to False
                 print "Setting %s to False" % domain.name
                 domain.send_to_duplicated_case_numbers = False
                 domain.save()
         else:
             print "Setting %s to True" % domain.name
             domain.send_to_duplicated_case_numbers = True
             domain.save()
Exemplo n.º 30
0
def fire_ivr_survey_event(reminder, handler, recipients, verified_numbers):
    domain_obj = Domain.get_by_name(reminder.domain, strict=True)
    for recipient in recipients:
        initiate_call = True
        if reminder.callback_try_count > 0 and reminder.event_initiation_timestamp:
            initiate_call = not CallLog.answered_call_exists(
                recipient.doc_type, recipient.get_id,
                reminder.event_initiation_timestamp,
                CaseReminderHandler.get_now())

        if initiate_call:
            if (isinstance(recipient, CommCareCase)
                    and not handler.force_surveys_to_use_triggered_case):
                case_id = recipient.get_id
            else:
                case_id = reminder.case_id
            verified_number, unverified_number = get_recipient_phone_number(
                reminder, recipient, verified_numbers)
            if verified_number:
                initiate_outbound_call.delay(
                    recipient,
                    reminder.current_event.form_unique_id,
                    handler.submit_partial_forms,
                    handler.include_case_side_effects,
                    handler.max_question_retries,
                    verified_number=verified_number,
                    case_id=case_id,
                    case_for_case_submission=handler.
                    force_surveys_to_use_triggered_case,
                    timestamp=CaseReminderHandler.get_now(),
                )
            elif domain_obj.send_to_duplicated_case_numbers and unverified_number:
                initiate_outbound_call.delay(
                    recipient,
                    reminder.current_event.form_unique_id,
                    handler.submit_partial_forms,
                    handler.include_case_side_effects,
                    handler.max_question_retries,
                    unverified_number=unverified_number,
                    case_id=case_id,
                    case_for_case_submission=handler.
                    force_surveys_to_use_triggered_case,
                    timestamp=CaseReminderHandler.get_now(),
                )
            else:
                #No phone number to send to
                pass

    return True
Exemplo n.º 31
0
def get_ids(start_date=None, timezone=None):
    result = CallLog.view(
        'sms/call_by_session',
        include_docs=False,
    ).all()
    def include_row(row):
        if start_date:
            date = row['key'][1]
            if date:
                date = parse(date).replace(tzinfo=None)
                date = get_naive_user_datetime(date, timezone=timezone)
                return date >= start_date
            else:
                return False
        return True
    return [row['id'] for row in result if include_row(row)]
Exemplo n.º 32
0
def fire_ivr_survey_event(reminder, handler, recipients, verified_numbers):
    domain_obj = Domain.get_by_name(reminder.domain, strict=True)
    for recipient in recipients:
        initiate_call = True
        if reminder.callback_try_count > 0 and reminder.event_initiation_timestamp:
            initiate_call = not CallLog.answered_call_exists(
                recipient.doc_type, recipient.get_id,
                reminder.event_initiation_timestamp,
                CaseReminderHandler.get_now())

        if initiate_call:
            if (isinstance(recipient, CommCareCase) and
                not handler.force_surveys_to_use_triggered_case):
                case_id = recipient.get_id
            else:
                case_id = reminder.case_id
            verified_number, unverified_number = get_recipient_phone_number(
                reminder, recipient, verified_numbers)
            if verified_number:
                initiate_outbound_call.delay(
                    recipient,
                    reminder.current_event.form_unique_id,
                    handler.submit_partial_forms,
                    handler.include_case_side_effects,
                    handler.max_question_retries,
                    verified_number=verified_number,
                    case_id=case_id,
                    case_for_case_submission=handler.force_surveys_to_use_triggered_case,
                    timestamp=CaseReminderHandler.get_now(),
                )
            elif domain_obj.send_to_duplicated_case_numbers and unverified_number:
                initiate_outbound_call.delay(
                    recipient,
                    reminder.current_event.form_unique_id,
                    handler.submit_partial_forms,
                    handler.include_case_side_effects,
                    handler.max_question_retries,
                    unverified_number=unverified_number,
                    case_id=case_id,
                    case_for_case_submission=handler.force_surveys_to_use_triggered_case,
                    timestamp=CaseReminderHandler.get_now(),
                )
            else:
                #No phone number to send to
                pass

    return True
Exemplo n.º 33
0
def initiate_outbound_call(recipient, form_unique_id, submit_partial_form,
    include_case_side_effects, max_question_retries, verified_number=None,
    unverified_number=None, case_id=None, case_for_case_submission=False,
    timestamp=None):
    """
    Returns True if the call was queued successfully, or False if an error
    occurred.
    """
    call_log_entry = None
    try:
        if not verified_number and not unverified_number:
            return False
        phone_number = (verified_number.phone_number if verified_number
            else unverified_number)
        call_log_entry = CallLog(
            couch_recipient_doc_type=recipient.doc_type,
            couch_recipient=recipient.get_id,
            phone_number="+%s" % str(phone_number),
            direction=OUTGOING,
            date=timestamp or datetime.utcnow(),
            domain=recipient.domain,
            form_unique_id=form_unique_id,
            submit_partial_form=submit_partial_form,
            include_case_side_effects=include_case_side_effects,
            max_question_retries=max_question_retries,
            current_question_retry_count=0,
            case_id=case_id,
            case_for_case_submission=case_for_case_submission,
        )
        backend = get_ivr_backend(recipient, verified_number, unverified_number)
        if not backend:
            return False
        kwargs = backend.get_cleaned_outbound_params()
        module = __import__(backend.outbound_module,
            fromlist=["initiate_outbound_call"])
        call_log_entry.backend_api = module.API_ID
        call_log_entry.save()
        return module.initiate_outbound_call(call_log_entry, **kwargs)
    except Exception:
        if call_log_entry:
            call_log_entry.error = True
            call_log_entry.error_message = "Internal Server Error"
            call_log_entry.save()
        raise
Exemplo n.º 34
0
def fire_sms_callback_event(reminder, handler, recipients, verified_numbers):
    current_event = reminder.current_event
    if handler.recipient in [RECIPIENT_CASE, RECIPIENT_USER]:
        # If there are no recipients, just move to the next reminder event
        if len(recipients) == 0:
            return True
        
        # If the callback has been received, skip sending the next timeout message
        if reminder.callback_try_count > 0:
            # Lookup the expected callback event
            if reminder.event_initiation_timestamp is None:
                event = None
            else:
                event = ExpectedCallbackEventLog.view("sms/expected_callback_event",
                                                      key=[reminder.domain, json_format_datetime(reminder.event_initiation_timestamp), recipients[0].get_id],
                                                      include_docs=True,
                                                      limit=1).one()
            
            # NOTE: If last_fired is None, it means that the reminder fired for the first time on a timeout interval
            if reminder.last_fired is not None and CallLog.inbound_entry_exists(recipients[0].doc_type, recipients[0].get_id, reminder.last_fired):
                reminder.skip_remaining_timeouts = True
                if event is not None:
                    event.status = CALLBACK_RECEIVED
                    event.save()
                return True
            elif reminder.callback_try_count >= len(current_event.callback_timeout_intervals):
                # On the last callback timeout, instead of sending the SMS again, log the missed callback
                if event is not None:
                    event.status = CALLBACK_MISSED
                    event.save()
                return True
        else:
            # It's the first time sending the sms, so create an expected callback event
            event = ExpectedCallbackEventLog(
                domain                   = reminder.domain,
                date                     = reminder.event_initiation_timestamp,
                couch_recipient_doc_type = recipients[0].doc_type,
                couch_recipient          = recipients[0].get_id,
                status                   = CALLBACK_PENDING,
            )
            event.save()
        
        return fire_sms_event(reminder, handler, recipients, verified_numbers, workflow=WORKFLOW_CALLBACK)
    else:
        # TODO: Implement sms callback for RECIPIENT_OWNER and RECIPIENT_SURVEY_SAMPLE
        return False
Exemplo n.º 35
0
def log_call(phone_number, gateway_session_id, backend_module=None):
    cleaned_number = strip_plus(phone_number)
    v = VerifiedNumber.by_extensive_search(cleaned_number)

    call = CallLog(
        phone_number=cleaned_number,
        direction=INCOMING,
        date=datetime.utcnow(),
        backend_api=backend_module.API_ID if backend_module else None,
        gateway_session_id=gateway_session_id,
    )
    if v:
        call.domain = v.domain
        call.couch_recipient_doc_type = v.owner_doc_type
        call.couch_recipient = v.owner_id
    call.save()
Exemplo n.º 36
0
def get_ids(start_date=None, timezone=None):
    result = CallLog.view(
        'sms/call_by_session',
        include_docs=False,
    ).all()

    def include_row(row):
        if start_date:
            date = row['key'][1]
            if date:
                date = parse(date).replace(tzinfo=None)
                date = get_naive_user_datetime(date, timezone=timezone)
                return date >= start_date
            else:
                return False
        return True

    return [row['id'] for row in result if include_row(row)]
Exemplo n.º 37
0
def ivr_in(request):
    """
    Handles tropo call requests
    """
    if request.method == "POST":
        data = json.loads(request.raw_post_data)
        phone_number = data["session"]["from"]["id"]
        ####

        # TODO: Implement tropo as an ivr backend. In the meantime, just log the call.

        cleaned_number = phone_number
        if cleaned_number is not None and len(
                cleaned_number) > 0 and cleaned_number[0] == "+":
            cleaned_number = cleaned_number[1:]

        # Try to look up the verified number entry
        v = VerifiedNumber.view("sms/verified_number_by_number",
                                key=cleaned_number,
                                include_docs=True).one()

        # If none was found, try to match only the last digits of numbers in the database
        if v is None:
            v = VerifiedNumber.view("sms/verified_number_by_suffix",
                                    key=cleaned_number,
                                    include_docs=True).one()

        # Save the call entry
        msg = CallLog(phone_number=cleaned_number,
                      direction=INCOMING,
                      date=datetime.utcnow(),
                      backend_api=TROPO_BACKEND_API_ID)
        if v is not None:
            msg.domain = v.domain
            msg.couch_recipient_doc_type = v.owner_doc_type
            msg.couch_recipient = v.owner_id
        msg.save()

        ####
        t = Tropo()
        t.reject()
        return HttpResponse(t.RenderJson())
    else:
        return HttpResponseBadRequest("Bad Request")
Exemplo n.º 38
0
    def deleteAllLogs(self):
        for smslog in SMSLog.view(
            'sms/by_domain',
            startkey=[self.domain, 'SMSLog'],
            endkey=[self.domain, 'SMSLog', {}],
            include_docs=True,
            reduce=False,
        ).all():
            smslog.delete()

        for callog in CallLog.view(
            'sms/by_domain',
            startkey=[self.domain, 'CallLog'],
            endkey=[self.domain, 'CallLog', {}],
            include_docs=True,
            reduce=False,
        ).all():
            callog.delete()

        for obj in LastReadMessage.view(
            'sms/last_read_message',
            startkey=['by_anyone', self.domain],
            endkey=['by_anyone', self.domain, {}],
            include_docs=True,
        ).all():
            obj.delete()

        for obj in ExpectedCallbackEventLog.view(
            'sms/expected_callback_event',
            startkey=[self.domain],
            endkey=[self.domain, {}],
            include_docs=True,
        ).all():
            obj.delete()

        SMS.objects.filter(domain=self.domain).delete()
        Call.objects.filter(domain=self.domain).delete()
        MessagingSubEvent.objects.filter(parent__domain=self.domain).delete()
        MessagingEvent.objects.filter(domain=self.domain).delete()
        SQLLastReadMessage.objects.filter(domain=self.domain).delete()
        ExpectedCallback.objects.filter(domain=self.domain).delete()
Exemplo n.º 39
0
def log_call(phone_number, gateway_session_id, backend_module=None):
    cleaned_number = strip_plus(phone_number)
    v = VerifiedNumber.by_extensive_search(cleaned_number)

    call = CallLog(
        phone_number=cleaned_number,
        direction=INCOMING,
        date=datetime.utcnow(),
        backend_api=backend_module.API_ID if backend_module else None,
        gateway_session_id=gateway_session_id,
    )
    if v:
        call.domain = v.domain
        call.couch_recipient_doc_type = v.owner_doc_type
        call.couch_recipient = v.owner_id
    call.save()
Exemplo n.º 40
0
def fire_ivr_survey_event(reminder, handler, recipients, verified_numbers):
    if handler.recipient == RECIPIENT_CASE:
        # If there are no recipients, just move to the next reminder event
        if len(recipients) == 0:
            return True

        # If last_fired is None, it means that the reminder fired for the first time on a timeout interval. So we can
        # skip the lookup for the answered call since no call went out yet.
        if reminder.last_fired is not None and reminder.callback_try_count > 0 and CallLog.answered_call_exists(
                recipients[0].doc_type, recipients[0].get_id,
                reminder.last_fired):
            reminder.skip_remaining_timeouts = True
            return True
        verified_number = verified_numbers[recipients[0].get_id]
        if verified_number is not None:
            if initiate_outbound_call(verified_number,
                                      reminder.current_event.form_unique_id,
                                      handler.submit_partial_forms,
                                      handler.include_case_side_effects,
                                      handler.max_question_retries):
                return True
            else:
                reminder = CaseReminder.get(reminder._id)
                reminder.error_retry_count += 1
                if reminder.error_retry_count > getattr(
                        settings, "IVR_OUTBOUND_RETRIES",
                        DEFAULT_OUTBOUND_RETRIES):
                    return True
                else:
                    reminder.next_fire += timedelta(minutes=getattr(
                        settings, "IVR_OUTBOUND_RETRY_INTERVAL",
                        DEFAULT_OUTBOUND_RETRY_INTERVAL))
                    reminder.save()
                    return False
        else:
            raise_error(reminder, ERROR_NO_VERIFIED_NUMBER)
            return False
    else:
        # TODO: Implement ivr survey for RECIPIENT_USER, RECIPIENT_OWNER, and RECIPIENT_SURVEY_SAMPLE
        return False
Exemplo n.º 41
0
def ivr_in(request):
    """
    Handles tropo call requests
    """
    if request.method == "POST":
        data = json.loads(request.raw_post_data)
        phone_number = data["session"]["from"]["id"]
        ####
        
        # TODO: Implement tropo as an ivr backend. In the meantime, just log the call.
        
        cleaned_number = phone_number
        if cleaned_number is not None and len(cleaned_number) > 0 and cleaned_number[0] == "+":
            cleaned_number = cleaned_number[1:]
        
        # Try to look up the verified number entry
        v = VerifiedNumber.view("sms/verified_number_by_number",
            key=cleaned_number,
            include_docs=True
        ).one()
        
        # If none was found, try to match only the last digits of numbers in the database
        if v is None:
            v = VerifiedNumber.view("sms/verified_number_by_suffix",
                key=cleaned_number,
                include_docs=True
            ).one()
        
        # Save the call entry
        msg = CallLog(
            phone_number = cleaned_number,
            direction = INCOMING,
            date = datetime.utcnow(),
            backend_api = TropoBackend.get_api_id(),
        )
        if v is not None:
            msg.domain = v.domain
            msg.couch_recipient_doc_type = v.owner_doc_type
            msg.couch_recipient = v.owner_id
        msg.save()
        
        ####
        t = Tropo()
        t.reject()
        return HttpResponse(t.RenderJson())
    else:
        return HttpResponseBadRequest("Bad Request")
Exemplo n.º 42
0
    def delete_models(self, delete_interval):
        print('Deleting SMSLogs...')
        count = iter_bulk_delete_with_doc_type_verification(SMSLog.get_db(), self.get_sms_couch_ids(), 'SMSLog',
            wait_time=delete_interval, max_fetch_attempts=5)
        print('Deleted %s documents' % count)

        print('Deleting CallLogs...')
        count = iter_bulk_delete_with_doc_type_verification(CallLog.get_db(), self.get_call_couch_ids(), 'CallLog',
            wait_time=delete_interval, max_fetch_attempts=5)
        print('Deleted %s documents' % count)

        print('Deleting ExpectedCallbackEventLogs...')
        count = iter_bulk_delete_with_doc_type_verification(ExpectedCallbackEventLog.get_db(),
            self.get_callback_couch_ids(), 'ExpectedCallbackEventLog', wait_time=delete_interval,
            max_fetch_attempts=5)
        print('Deleted %s documents' % count)

        print('Deleting LastReadMessages...')
        count = iter_bulk_delete_with_doc_type_verification(LastReadMessage.get_db(),
            self.get_lastreadmessage_couch_ids(), 'LastReadMessage', wait_time=delete_interval,
            max_fetch_attempts=5)
        print('Deleted %s documents' % count)
    def delete_models(self, delete_interval):
        print 'Deleting SMSLogs...'
        count = iter_bulk_delete_with_doc_type_verification(SMSLog.get_db(), self.get_sms_couch_ids(), 'SMSLog',
            wait_time=delete_interval, max_fetch_attempts=5)
        print 'Deleted %s documents' % count

        print 'Deleting CallLogs...'
        count = iter_bulk_delete_with_doc_type_verification(CallLog.get_db(), self.get_call_couch_ids(), 'CallLog',
            wait_time=delete_interval, max_fetch_attempts=5)
        print 'Deleted %s documents' % count

        print 'Deleting ExpectedCallbackEventLogs...'
        count = iter_bulk_delete_with_doc_type_verification(ExpectedCallbackEventLog.get_db(),
            self.get_callback_couch_ids(), 'ExpectedCallbackEventLog', wait_time=delete_interval,
            max_fetch_attempts=5)
        print 'Deleted %s documents' % count

        print 'Deleting LastReadMessages...'
        count = iter_bulk_delete_with_doc_type_verification(LastReadMessage.get_db(),
            self.get_lastreadmessage_couch_ids(), 'LastReadMessage', wait_time=delete_interval,
            max_fetch_attempts=5)
        print 'Deleted %s documents' % count
Exemplo n.º 44
0
def incoming(phone_number,
             backend_module,
             gateway_session_id,
             ivr_event,
             input_data=None,
             duration=None):
    """
    The main entry point for all incoming IVR requests.
    """
    call_log_entry = CallLog.get_call_by_gateway_session_id(gateway_session_id)
    logged_subevent = None
    if call_log_entry and call_log_entry.messaging_subevent_id:
        logged_subevent = MessagingSubEvent.objects.get(
            pk=call_log_entry.messaging_subevent_id)

    if call_log_entry:
        add_metadata(call_log_entry, duration)

    if call_log_entry and call_log_entry.form_unique_id is None:
        # If this request is for a call with no form,
        # then just short circuit everything and hang up
        return hang_up_response(gateway_session_id,
                                backend_module=backend_module)

    if call_log_entry and backend_module:
        return handle_known_call_session(call_log_entry,
                                         backend_module,
                                         ivr_event,
                                         input_data=input_data,
                                         logged_subevent=logged_subevent)
    else:
        if not call_log_entry:
            log_call(phone_number,
                     gateway_session_id,
                     backend_module=backend_module)
        return hang_up_response(gateway_session_id,
                                backend_module=backend_module)
Exemplo n.º 45
0
    def rows(self):
        startdate = json_format_datetime(self.datespan.startdate_utc)
        enddate = json_format_datetime(self.datespan.enddate_utc)
        data = CallLog.by_domain_date(self.domain, startdate, enddate)
        result = []

        # Store the results of lookups for faster loading
        username_map = {}
        form_map = {}

        direction_map = {
            INCOMING: _("Incoming"),
            OUTGOING: _("Outgoing"),
        }

        # Retrieve message log options
        message_log_options = getattr(settings, "MESSAGE_LOG_OPTIONS", {})
        abbreviated_phone_number_domains = message_log_options.get(
            "abbreviated_phone_number_domains", [])
        abbreviate_phone_number = (self.domain
                                   in abbreviated_phone_number_domains)

        for call in data:
            recipient_id = call.couch_recipient
            if recipient_id in [None, ""]:
                username = "******"
            elif recipient_id in username_map:
                username = username_map.get(recipient_id)
            else:
                username = "******"
                try:
                    if call.couch_recipient_doc_type == "CommCareCase":
                        username = CommCareCase.get(recipient_id).name
                    else:
                        username = CouchUser.get_by_user_id(
                            recipient_id).username
                except Exception:
                    pass

                username_map[recipient_id] = username

            form_unique_id = call.form_unique_id
            if form_unique_id in [None, ""]:
                form_name = "-"
            elif form_unique_id in form_map:
                form_name = form_map.get(form_unique_id)
            else:
                form_name = get_form_name(form_unique_id)
                form_map[form_unique_id] = form_name

            phone_number = call.phone_number
            if abbreviate_phone_number and phone_number is not None:
                phone_number = phone_number[0:7] if phone_number[
                    0:1] == "+" else phone_number[0:6]

            timestamp = tz_utils.adjust_datetime_to_timezone(
                call.date, pytz.utc.zone, self.timezone.zone)

            if call.direction == INCOMING:
                answered = "-"
            else:
                answered = _("Yes") if call.answered else _("No")

            if call.xforms_session_id is None:
                submission_id = None
            else:
                session = XFormsSession.latest_by_session_id(
                    call.xforms_session_id)
                submission_id = session.submission_id

            row = [
                self._fmt_timestamp(timestamp),
                self._fmt(username),
                self._fmt(phone_number),
                self._fmt(direction_map.get(call.direction, "-")),
                self._fmt(form_name),
                self._fmt("-") if submission_id is None else
                self._fmt_submission_link(submission_id),
                self._fmt(answered),
                self._fmt(call.duration),
                self._fmt(_("Yes") if call.error else _("No")),
                self._fmt(call.error_message),
            ]

            if self.request.couch_user.is_previewer():
                row.append(self._fmt(call.gateway_session_id))

            result.append(row)

        return result
Exemplo n.º 46
0
def initiate_outbound_call(recipient,
                           form_unique_id,
                           submit_partial_form,
                           include_case_side_effects,
                           max_question_retries,
                           messaging_event_id,
                           verified_number=None,
                           unverified_number=None,
                           case_id=None,
                           case_for_case_submission=False,
                           timestamp=None):
    """
    Returns False if an error occurred and the call should be retried.
    Returns True if the call should not be retried (either because it was
    queued successfully or because an unrecoverable error occurred).
    """
    call_log_entry = None
    logged_event = MessagingEvent.objects.get(pk=messaging_event_id)
    logged_subevent = logged_event.create_ivr_subevent(recipient,
                                                       form_unique_id,
                                                       case_id=case_id)

    if not verified_number and not unverified_number:
        log_error(MessagingEvent.ERROR_NO_PHONE_NUMBER,
                  logged_subevent=logged_subevent)
        return True

    backend = get_ivr_backend(recipient, verified_number, unverified_number)
    if not backend:
        log_error(MessagingEvent.ERROR_NO_SUITABLE_GATEWAY,
                  logged_subevent=logged_subevent)
        return True

    phone_number = (verified_number.phone_number
                    if verified_number else unverified_number)

    call_log_entry = CallLog(
        couch_recipient_doc_type=recipient.doc_type,
        couch_recipient=recipient.get_id,
        phone_number='+%s' % str(phone_number),
        direction=OUTGOING,
        date=timestamp or datetime.utcnow(),
        domain=recipient.domain,
        form_unique_id=form_unique_id,
        submit_partial_form=submit_partial_form,
        include_case_side_effects=include_case_side_effects,
        max_question_retries=max_question_retries,
        current_question_retry_count=0,
        case_id=case_id,
        case_for_case_submission=case_for_case_submission,
        messaging_subevent_id=logged_subevent.pk,
    )

    ivr_data, error = get_first_ivr_response_data(recipient, call_log_entry,
                                                  logged_subevent)
    if error:
        return True
    if ivr_data:
        logged_subevent.xforms_session = ivr_data.session
        logged_subevent.save()

    try:
        kwargs = backend.get_cleaned_outbound_params()
        module = __import__(backend.outbound_module,
                            fromlist=['initiate_outbound_call'])

        call_log_entry.backend_api = module.API_ID
        call_log_entry.save()

        result = module.initiate_outbound_call(call_log_entry,
                                               logged_subevent,
                                               ivr_data=ivr_data,
                                               **kwargs)
        logged_subevent.completed()
        return result
    except GatewayConnectionError:
        log_error(MessagingEvent.ERROR_GATEWAY_ERROR, call_log_entry,
                  logged_subevent)
        raise
    except Exception:
        log_error(MessagingEvent.ERROR_INTERNAL_SERVER_ERROR, call_log_entry,
                  logged_subevent)
        raise
Exemplo n.º 47
0
    def rows(self):
        startdate = json_format_datetime(self.datespan.startdate_utc)
        enddate = json_format_datetime(self.datespan.enddate_utc)
        data = CallLog.by_domain_date(self.domain, startdate, enddate)
        result = []
        
        # Store the results of lookups for faster loading
        contact_cache = {}
        form_map = {}
        xforms_sessions = {}
        
        direction_map = {
            INCOMING: _("Incoming"),
            OUTGOING: _("Outgoing"),
        }
        
        # Retrieve message log options
        message_log_options = getattr(settings, "MESSAGE_LOG_OPTIONS", {})
        abbreviated_phone_number_domains = message_log_options.get("abbreviated_phone_number_domains", [])
        abbreviate_phone_number = (self.domain in abbreviated_phone_number_domains)
        
        for call in data:
            doc_info = self.get_recipient_info(call.couch_recipient_doc_type,
                call.couch_recipient, contact_cache)

            form_unique_id = call.form_unique_id
            if form_unique_id in [None, ""]:
                form_name = "-"
            elif form_unique_id in form_map:
                form_name = form_map.get(form_unique_id)
            else:
                form_name = get_form_name(form_unique_id)
                form_map[form_unique_id] = form_name
            
            phone_number = call.phone_number
            if abbreviate_phone_number and phone_number is not None:
                phone_number = phone_number[0:7] if phone_number[0:1] == "+" else phone_number[0:6]
            
            timestamp = ServerTime(call.date).user_time(self.timezone).done()
            
            if call.direction == INCOMING:
                answered = "-"
            else:
                answered = _("Yes") if call.answered else _("No")
            
            if call.xforms_session_id:
                xforms_sessions[call.xforms_session_id] = None
            
            row = [
                call.xforms_session_id,
                self._fmt_timestamp(timestamp),
                self._fmt_contact_link(call.couch_recipient, doc_info),
                self._fmt(phone_number),
                self._fmt(direction_map.get(call.direction,"-")),
                self._fmt(form_name),
                self._fmt("-"),
                self._fmt(answered),
                self._fmt(call.duration),
                self._fmt(_("Yes") if call.error else _("No")),
                self._fmt(cgi.escape(call.error_message) if call.error_message else None),
            ]
            
            if self.request.couch_user.is_previewer():
                row.append(self._fmt(call.gateway_session_id))
            
            result.append(row)

        all_session_ids = xforms_sessions.keys()
        session_submission_map = dict(
            SQLXFormsSession.objects.filter(session_id__in=all_session_ids).values_list(
                'session_id', 'submission_id'
            )
        )
        xforms_sessions.update(session_submission_map)

        # Add into the final result the link to the submission based on the
        # outcome of the above lookups.
        final_result = []
        for row in result:
            final_row = row[1:]
            session_id = row[0]
            if session_id:
                submission_id = xforms_sessions[session_id]
                if submission_id:
                    final_row[5] = self._fmt_submission_link(submission_id)
            final_result.append(final_row)

        return final_result
Exemplo n.º 48
0
    def rows(self):
        startdate = json_format_datetime(self.datespan.startdate_utc)
        enddate = json_format_datetime(self.datespan.enddate_utc)
        data = CallLog.by_domain_date(self.domain, startdate, enddate)
        result = []

        # Store the results of lookups for faster loading
        contact_cache = {}
        form_map = {}
        xforms_sessions = {}

        direction_map = {
            INCOMING: _("Incoming"),
            OUTGOING: _("Outgoing"),
        }

        # Retrieve message log options
        message_log_options = getattr(settings, "MESSAGE_LOG_OPTIONS", {})
        abbreviated_phone_number_domains = message_log_options.get(
            "abbreviated_phone_number_domains", [])
        abbreviate_phone_number = (self.domain
                                   in abbreviated_phone_number_domains)

        for call in data:
            doc_info = self.get_recipient_info(call, contact_cache)

            form_unique_id = call.form_unique_id
            if form_unique_id in [None, ""]:
                form_name = "-"
            elif form_unique_id in form_map:
                form_name = form_map.get(form_unique_id)
            else:
                form_name = get_form_name(form_unique_id)
                form_map[form_unique_id] = form_name

            phone_number = call.phone_number
            if abbreviate_phone_number and phone_number is not None:
                phone_number = phone_number[0:7] if phone_number[
                    0:1] == "+" else phone_number[0:6]

            timestamp = tz_utils.adjust_datetime_to_timezone(
                call.date, pytz.utc.zone, self.timezone.zone)

            if call.direction == INCOMING:
                answered = "-"
            else:
                answered = _("Yes") if call.answered else _("No")

            if call.xforms_session_id:
                xforms_sessions[call.xforms_session_id] = None

            row = [
                call.xforms_session_id,
                self._fmt_timestamp(timestamp),
                self._fmt_contact_link(call, doc_info),
                self._fmt(phone_number),
                self._fmt(direction_map.get(call.direction, "-")),
                self._fmt(form_name),
                self._fmt("-"),
                self._fmt(answered),
                self._fmt(call.duration),
                self._fmt(_("Yes") if call.error else _("No")),
                self._fmt(call.error_message),
            ]

            if self.request.couch_user.is_previewer():
                row.append(self._fmt(call.gateway_session_id))

            result.append(row)

        # Look up the XFormsSession documents 500 at a time.
        # Had to do this because looking up one document at a time slows things
        # down a lot.
        all_session_ids = xforms_sessions.keys()
        limit = 500
        range_max = int(ceil(len(all_session_ids) * 1.0 / limit))
        for i in range(range_max):
            lower_bound = i * limit
            upper_bound = (i + 1) * limit
            sessions = XFormsSession.view(
                "smsforms/sessions_by_touchforms_id",
                keys=all_session_ids[lower_bound:upper_bound],
                include_docs=True).all()
            for session in sessions:
                xforms_sessions[session.session_id] = session.submission_id

        # Add into the final result the link to the submission based on the
        # outcome of the above lookups.
        final_result = []
        for row in result:
            final_row = row[1:]
            session_id = row[0]
            if session_id:
                submission_id = xforms_sessions[session_id]
                if submission_id:
                    final_row[5] = self._fmt_submission_link(submission_id)
            final_result.append(final_row)

        return final_result
Exemplo n.º 49
0
 def rows(self):
     startdate = json_format_datetime(self.datespan.startdate_utc)
     enddate = json_format_datetime(self.datespan.enddate_utc)
     data = CallLog.by_domain_date(self.domain, startdate, enddate)
     result = []
     
     # Store the results of lookups for faster loading
     username_map = {} 
     form_map = {}
     
     direction_map = {
         INCOMING: _("Incoming"),
         OUTGOING: _("Outgoing"),
     }
     
     # Retrieve message log options
     message_log_options = getattr(settings, "MESSAGE_LOG_OPTIONS", {})
     abbreviated_phone_number_domains = message_log_options.get("abbreviated_phone_number_domains", [])
     abbreviate_phone_number = (self.domain in abbreviated_phone_number_domains)
     
     for call in data:
         recipient_id = call.couch_recipient
         if recipient_id in [None, ""]:
             username = "******"
         elif recipient_id in username_map:
             username = username_map.get(recipient_id)
         else:
             username = "******"
             try:
                 if call.couch_recipient_doc_type == "CommCareCase":
                     username = CommCareCase.get(recipient_id).name
                 else:
                     username = CouchUser.get_by_user_id(recipient_id).username
             except Exception:
                 pass
            
             username_map[recipient_id] = username
         
         form_unique_id = call.form_unique_id
         if form_unique_id in [None, ""]:
             form_name = "-"
         elif form_unique_id in form_map:
             form_name = form_map.get(form_unique_id)
         else:
             form_name = get_form_name(form_unique_id)
             form_map[form_unique_id] = form_name
         
         phone_number = call.phone_number
         if abbreviate_phone_number and phone_number is not None:
             phone_number = phone_number[0:7] if phone_number[0:1] == "+" else phone_number[0:6]
         
         timestamp = tz_utils.adjust_datetime_to_timezone(call.date, pytz.utc.zone, self.timezone.zone)
         
         if call.direction == INCOMING:
             answered = "-"
         else:
             answered = _("Yes") if call.answered else _("No")
         
         if call.xforms_session_id is None:
             submission_id = None
         else:
             session = XFormsSession.latest_by_session_id(call.xforms_session_id)
             submission_id = session.submission_id
         
         row = [
             self._fmt_timestamp(timestamp),
             self._fmt(username),
             self._fmt(phone_number),
             self._fmt(direction_map.get(call.direction,"-")),
             self._fmt(form_name),
             self._fmt("-") if submission_id is None else self._fmt_submission_link(submission_id),
             self._fmt(answered),
             self._fmt(call.duration),
             self._fmt(_("Yes") if call.error else _("No")),
             self._fmt(call.error_message),
         ]
         
         if self.request.couch_user.is_previewer():
             row.append(self._fmt(call.gateway_session_id))
         
         result.append(row)
     
     return result
Exemplo n.º 50
0
    def test_ok(self):
        self.assertEqual(self.handler.get_reminder(self.case), None)

        # Spawn CaseReminder
        CaseReminderHandler.now = datetime(year=2011, month=12, day=31, hour=23, minute=0)
        self.case.set_case_property('start_sending', 'ok')
        self.case.save()
        reminder = self.handler.get_reminder(self.case)
        self.assertNotEqual(reminder, None)
        self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=1, hour=7, minute=0))
        self.assertEqual(reminder.start_date, date(year=2012, month=1, day=1))
        self.assertEqual(reminder.schedule_iteration_num, 1)
        self.assertEqual(reminder.current_event_sequence_num, 0)
        self.assertEqual(reminder.last_fired, None)
        
        ######################
        # Day1, 10:00 reminder
        CaseReminderHandler.now = datetime(year=2012, month=1, day=1, hour=7, minute=0)
        CaseReminderHandler.fire_reminders()
        reminder = self.handler.get_reminder(self.case)
        self.assertNotEqual(reminder, None)
        self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=1, hour=8, minute=0))
        self.assertEqual(reminder.schedule_iteration_num, 1)
        self.assertEqual(reminder.current_event_sequence_num, 1)
        self.assertEqual(reminder.last_fired, CaseReminderHandler.now)
        
        # Day1, 11:00 reminder
        CaseReminderHandler.now = datetime(year=2012, month=1, day=1, hour=8, minute=1)
        CaseReminderHandler.fire_reminders()
        reminder = self.handler.get_reminder(self.case)
        self.assertNotEqual(reminder, None)
        self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=1, hour=8, minute=15))
        self.assertEqual(reminder.schedule_iteration_num, 1)
        self.assertEqual(reminder.current_event_sequence_num, 1)
        self.assertEqual(reminder.last_fired, CaseReminderHandler.now)
        
        event = ExpectedCallbackEventLog.view("sms/expected_callback_event",
                                              key=["test", json_format_datetime(datetime(year=2012, month=1, day=1, hour=8, minute=1)), self.user_id],
                                              include_docs=True).one()
        self.assertNotEqual(event, None)
        self.assertEqual(event.status, CALLBACK_PENDING)
        
        # Create a callback
        c = CallLog(
            couch_recipient_doc_type    = "CommCareUser",
            couch_recipient             = self.user_id,
            phone_number                = "14445551234",
            direction                   = "I",
            date                        = datetime(year=2012, month=1, day=1, hour=8, minute=5)
        )
        c.save()

        # Day1, 11:15 timeout (should move on to next event)
        CaseReminderHandler.now = datetime(year=2012, month=1, day=1, hour=8, minute=15)
        CaseReminderHandler.fire_reminders()
        reminder = self.handler.get_reminder(self.case)
        self.assertNotEqual(reminder, None)
        self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=1, hour=8, minute=45))
        self.assertEqual(reminder.schedule_iteration_num, 1)
        self.assertEqual(reminder.current_event_sequence_num, 1)
        self.assertEqual(reminder.last_fired, CaseReminderHandler.now)

        # Day1, 11:45 timeout (should move on to next event)
        CaseReminderHandler.now = datetime(year=2012, month=1, day=1, hour=8, minute=45)
        CaseReminderHandler.fire_reminders()
        reminder = self.handler.get_reminder(self.case)
        self.assertNotEqual(reminder, None)
        self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=2, hour=7, minute=0))
        self.assertEqual(reminder.schedule_iteration_num, 2)
        self.assertEqual(reminder.current_event_sequence_num, 0)
        self.assertEqual(reminder.last_fired, CaseReminderHandler.now)

        event = ExpectedCallbackEventLog.view("sms/expected_callback_event",
                                              key=["test", json_format_datetime(datetime(year=2012, month=1, day=1, hour=8, minute=1)), self.user_id],
                                              include_docs=True).one()
        self.assertNotEqual(event, None)
        self.assertEqual(event.status, CALLBACK_RECEIVED)

        ######################
        # Day2, 10:00 reminder
        CaseReminderHandler.now = datetime(year=2012, month=1, day=2, hour=7, minute=0)
        CaseReminderHandler.fire_reminders()
        reminder = self.handler.get_reminder(self.case)
        self.assertNotEqual(reminder, None)
        self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=2, hour=8, minute=0))
        self.assertEqual(reminder.schedule_iteration_num, 2)
        self.assertEqual(reminder.current_event_sequence_num, 1)
        self.assertEqual(reminder.last_fired, CaseReminderHandler.now)
        
        # Day2, 11:00 reminder
        CaseReminderHandler.now = datetime(year=2012, month=1, day=2, hour=8, minute=1)
        CaseReminderHandler.fire_reminders()
        reminder = self.handler.get_reminder(self.case)
        self.assertNotEqual(reminder, None)
        self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=2, hour=8, minute=15))
        self.assertEqual(reminder.schedule_iteration_num, 2)
        self.assertEqual(reminder.current_event_sequence_num, 1)
        self.assertEqual(reminder.last_fired, CaseReminderHandler.now)
        
        event = ExpectedCallbackEventLog.view("sms/expected_callback_event",
                                              key=["test", json_format_datetime(datetime(year=2012, month=1, day=2, hour=8, minute=1)), self.user_id],
                                              include_docs=True).one()
        self.assertNotEqual(event, None)
        self.assertEqual(event.status, CALLBACK_PENDING)
        
        # Day2, 11:15 timeout (should move on to next timeout)
        CaseReminderHandler.now = datetime(year=2012, month=1, day=2, hour=8, minute=15)
        CaseReminderHandler.fire_reminders()
        reminder = self.handler.get_reminder(self.case)
        self.assertNotEqual(reminder, None)
        self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=2, hour=8, minute=45))
        self.assertEqual(reminder.schedule_iteration_num, 2)
        self.assertEqual(reminder.current_event_sequence_num, 1)
        self.assertEqual(reminder.last_fired, CaseReminderHandler.now)
        
        event = ExpectedCallbackEventLog.view("sms/expected_callback_event",
                                              key=["test", json_format_datetime(datetime(year=2012, month=1, day=2, hour=8, minute=1)), self.user_id],
                                              include_docs=True).one()
        self.assertNotEqual(event, None)
        self.assertEqual(event.status, CALLBACK_PENDING)
        
        # Day2, 11:45 timeout (should move on to next day)
        CaseReminderHandler.now = datetime(year=2012, month=1, day=2, hour=8, minute=45)
        CaseReminderHandler.fire_reminders()
        reminder = self.handler.get_reminder(self.case)
        self.assertNotEqual(reminder, None)
        self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=3, hour=7, minute=0))
        self.assertEqual(reminder.schedule_iteration_num, 3)
        self.assertEqual(reminder.current_event_sequence_num, 0)
        self.assertEqual(reminder.last_fired, CaseReminderHandler.now)
        
        event = ExpectedCallbackEventLog.view("sms/expected_callback_event",
                                              key=["test", json_format_datetime(datetime(year=2012, month=1, day=2, hour=8, minute=1)), self.user_id],
                                              include_docs=True).one()
        self.assertNotEqual(event, None)
        self.assertEqual(event.status, CALLBACK_MISSED)
        
        ######################
        # Day3, 10:00 reminder
        CaseReminderHandler.now = datetime(year=2012, month=1, day=3, hour=7, minute=0)
        CaseReminderHandler.fire_reminders()
        reminder = self.handler.get_reminder(self.case)
        self.assertNotEqual(reminder, None)
        self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=3, hour=8, minute=0))
        self.assertEqual(reminder.schedule_iteration_num, 3)
        self.assertEqual(reminder.current_event_sequence_num, 1)
        self.assertEqual(reminder.last_fired, CaseReminderHandler.now)
        
        # Day3, 11:00 reminder
        CaseReminderHandler.now = datetime(year=2012, month=1, day=3, hour=8, minute=1)
        CaseReminderHandler.fire_reminders()
        reminder = self.handler.get_reminder(self.case)
        self.assertNotEqual(reminder, None)
        self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=3, hour=8, minute=15))
        self.assertEqual(reminder.schedule_iteration_num, 3)
        self.assertEqual(reminder.current_event_sequence_num, 1)
        self.assertEqual(reminder.last_fired, CaseReminderHandler.now)
        
        event = ExpectedCallbackEventLog.view("sms/expected_callback_event",
                                              key=["test", json_format_datetime(datetime(year=2012, month=1, day=3, hour=8, minute=1)), self.user_id],
                                              include_docs=True).one()
        self.assertNotEqual(event, None)
        self.assertEqual(event.status, CALLBACK_PENDING)
        
        # Day3, 11:15 timeout (should move on to next timeout)
        CaseReminderHandler.now = datetime(year=2012, month=1, day=3, hour=8, minute=15)
        CaseReminderHandler.fire_reminders()
        reminder = self.handler.get_reminder(self.case)
        self.assertNotEqual(reminder, None)
        self.assertEqual(reminder.next_fire, datetime(year=2012, month=1, day=3, hour=8, minute=45))
        self.assertEqual(reminder.schedule_iteration_num, 3)
        self.assertEqual(reminder.current_event_sequence_num, 1)
        self.assertEqual(reminder.last_fired, CaseReminderHandler.now)
        
        event = ExpectedCallbackEventLog.view("sms/expected_callback_event",
                                              key=["test", json_format_datetime(datetime(year=2012, month=1, day=3, hour=8, minute=1)), self.user_id],
                                              include_docs=True).one()
        self.assertNotEqual(event, None)
        self.assertEqual(event.status, CALLBACK_PENDING)
        
        # Create a callback (with phone_number missing country code)
        c = CallLog(
            couch_recipient_doc_type    = "CommCareUser",
            couch_recipient             = self.user_id,
            phone_number                = "4445551234",
            direction                   = "I",
            date                        = datetime(year=2012, month=1, day=3, hour=8, minute=22)
        )
        c.save()
        
        # Day3, 11:45 timeout (should deactivate the reminder)
        CaseReminderHandler.now = datetime(year=2012, month=1, day=3, hour=8, minute=45)
        CaseReminderHandler.fire_reminders()
        reminder = self.handler.get_reminder(self.case)
        self.assertNotEqual(reminder, None)
        self.assertEqual(reminder.schedule_iteration_num, 4)
        self.assertEqual(reminder.current_event_sequence_num, 0)
        self.assertEqual(reminder.last_fired, CaseReminderHandler.now)
        self.assertEqual(reminder.active, False)
        
        event = ExpectedCallbackEventLog.view("sms/expected_callback_event",
                                              key=["test", json_format_datetime(datetime(year=2012, month=1, day=3, hour=8, minute=1)), self.user_id],
                                              include_docs=True).one()
        self.assertNotEqual(event, None)
        self.assertEqual(event.status, CALLBACK_RECEIVED)
Exemplo n.º 51
0
    def testOutbound(self):
        # Send an outbound call using self.reminder1 to self.case
        # and answer it
        CaseReminderHandler.now = datetime(2014, 6, 23, 10, 0)
        self.case = CommCareCase.get(
            register_sms_contact(
                self.domain,
                'participant',
                'case1',
                self.user1._id,
                '91000',
                owner_id=self.groups[0]._id,
            ))
        CaseReminderHandler.now = datetime(2014, 6, 23, 12, 0)
        CaseReminderHandler.fire_reminders()
        reminder = self.reminder1.get_reminder(self.case)
        self.assertEquals(reminder.next_fire, datetime(2014, 6, 23, 12, 30))

        call = self.get_last_outbound_call(self.case)
        self.assertTrue(call.use_precached_first_response)

        kookoo_session_id = call.gateway_session_id[7:]
        resp = self.kookoo_in({
            "cid": "0000",
            "sid": kookoo_session_id,
            "event": "NewCall",
        })
        self.assertEqual(
            resp, '<response sid="%s"><collectdtmf l="1" o="3000">'
            '<playtext>How do you feel today? Press 1 for good, 2 for bad.'
            '</playtext></collectdtmf></response>' % kookoo_session_id)

        resp = self.kookoo_in({
            "cid": "0000",
            "sid": kookoo_session_id,
            "event": "GotDTMF",
            "data": "1",
        })
        self.assertEqual(
            resp, '<response sid="%s"><collectdtmf l="1" o="3000">'
            '<playtext>Did you remember to take your meds today? Press 1 for yes, 2 for no.'
            '</playtext></collectdtmf></response>' % kookoo_session_id)

        resp = self.kookoo_in({
            "cid": "0000",
            "sid": kookoo_session_id,
            "event": "GotDTMF",
            "data": "2",
        })
        self.assertEqual(
            resp,
            '<response sid="%s"><hangup/></response>' % kookoo_session_id)

        self.kookoo_finished({
            "sid": kookoo_session_id,
            "status": "answered",
            "duration": "20",
        })

        call = CallLog.get(call._id)
        self.assertTrue(call.answered)
        self.assertEqual(call.duration, 20)

        form = self.get_last_form_submission()
        self.assertFormQuestionEquals(form, "how_feel", "1")
        self.assertFormQuestionEquals(form, "take_meds", "2")
        case = CommCareCase.get(self.case._id)
        self.assertCasePropertyEquals(case, "how_feel", "1")
        self.assertCasePropertyEquals(case, "take_meds", "2")

        CaseReminderHandler.now = datetime(2014, 6, 23, 12, 30)
        CaseReminderHandler.fire_reminders()

        reminder = self.reminder1.get_reminder(self.case)
        self.assertEquals(reminder.next_fire, datetime(2014, 6, 23, 13, 0))

        last_call = self.get_last_outbound_call(self.case)
        self.assertEqual(call._id, last_call._id)

        # Move on to the second event which now uses an all-label form and
        # should not precache the first ivr response
        CaseReminderHandler.now = datetime(2014, 6, 23, 13, 0)
        CaseReminderHandler.fire_reminders()

        reminder = self.reminder1.get_reminder(self.case)
        self.assertEquals(reminder.next_fire, datetime(2014, 6, 23, 13, 30))

        call = self.get_last_outbound_call(self.case)
        self.assertFalse(call.use_precached_first_response)

        kookoo_session_id = call.gateway_session_id[7:]
        resp = self.kookoo_in({
            "cid": "0000",
            "sid": kookoo_session_id,
            "event": "NewCall",
        })
        self.assertEqual(
            resp, '<response sid="%s">'
            '<playtext>This is just a reminder to take your meds.'
            '</playtext><hangup/></response>' % kookoo_session_id)

        self.kookoo_finished({
            "sid": kookoo_session_id,
            "status": "answered",
            "duration": "5",
        })

        call = CallLog.get(call._id)
        self.assertTrue(call.answered)
        self.assertEqual(call.duration, 5)

        form = self.get_last_form_submission()
        self.assertFormQuestionEquals(form, "label", "ok")

        CaseReminderHandler.now = datetime(2014, 6, 23, 13, 30)
        CaseReminderHandler.fire_reminders()

        reminder = self.reminder1.get_reminder(self.case)
        self.assertEquals(reminder.next_fire, datetime(2014, 6, 24, 12, 0))

        last_call = self.get_last_outbound_call(self.case)
        self.assertEqual(call._id, last_call._id)

        # Now test sending outbound calls to a group of users (the owners
        # of the case)

        # Allow sending to unverified numbers
        self.domain_obj = Domain.get(self.domain_obj._id)
        self.domain_obj.send_to_duplicated_case_numbers = True
        self.domain_obj.save()

        CaseReminderHandler.now = datetime(2014, 6, 24, 10, 0)
        self.case = CommCareCase.get(
            register_sms_contact(
                self.domain,
                'participant',
                'case2',
                self.user1._id,
                '91003',
                owner_id=self.groups[0]._id,
            ))
        reminder = self.reminder2.get_reminder(self.case)
        self.assertEquals(reminder.next_fire, datetime(2014, 6, 24, 12, 0))

        CaseReminderHandler.now = datetime(2014, 6, 24, 12, 0)
        CaseReminderHandler.fire_reminders()
        reminder = self.reminder2.get_reminder(self.case)
        self.assertEquals(reminder.next_fire, datetime(2014, 6, 24, 12, 30))

        call1 = self.get_last_outbound_call(self.user1)
        self.assertTrue(call1.use_precached_first_response)
        self.assertFalse(call1.answered)

        call2 = self.get_last_outbound_call(self.user2)
        self.assertTrue(call2.use_precached_first_response)
        self.assertFalse(call2.answered)

        old_call1 = call1
        old_call2 = call2

        CaseReminderHandler.now = datetime(2014, 6, 24, 12, 30)
        CaseReminderHandler.fire_reminders()
        reminder = self.reminder2.get_reminder(self.case)
        self.assertEquals(reminder.next_fire, datetime(2014, 6, 24, 13, 0))

        call1 = self.get_last_outbound_call(self.user1)
        self.assertTrue(call1.use_precached_first_response)
        self.assertNotEqual(call1._id, old_call1._id)

        call2 = self.get_last_outbound_call(self.user2)
        self.assertTrue(call2.use_precached_first_response)
        self.assertFalse(call2.answered)
        self.assertNotEqual(call2._id, old_call2._id)

        kookoo_session_id = call1.gateway_session_id[7:]
        resp = self.kookoo_in({
            "cid": "0001",
            "sid": kookoo_session_id,
            "event": "NewCall",
        })
        self.assertEqual(
            resp, '<response sid="%s"><collectdtmf l="1" o="3000">'
            '<playtext>How do you feel today? Press 1 for good, 2 for bad.'
            '</playtext></collectdtmf></response>' % kookoo_session_id)

        resp = self.kookoo_in({
            "cid": "0001",
            "sid": kookoo_session_id,
            "event": "GotDTMF",
            "data": "2",
        })
        self.assertEqual(
            resp, '<response sid="%s"><collectdtmf l="1" o="3000">'
            '<playtext>Did you remember to take your meds today? Press 1 for yes, 2 for no.'
            '</playtext></collectdtmf></response>' % kookoo_session_id)

        resp = self.kookoo_in({
            "cid": "0001",
            "sid": kookoo_session_id,
            "event": "GotDTMF",
            "data": "1",
        })
        self.assertEqual(
            resp,
            '<response sid="%s"><hangup/></response>' % kookoo_session_id)

        self.kookoo_finished({
            "sid": kookoo_session_id,
            "status": "answered",
            "duration": "20",
        })
        call1 = CallLog.get(call1._id)
        self.assertTrue(call1.answered)
        self.assertEqual(call1.duration, 20)

        form = self.get_last_form_submission()
        self.assertFormQuestionEquals(form, "how_feel", "2")
        self.assertFormQuestionEquals(form, "take_meds", "1")
        self.assertEqual(form.form["meta"]["userID"], self.user1._id)
        case = CommCareCase.get(self.case._id)
        self.assertCasePropertyEquals(case, "how_feel", "2")
        self.assertCasePropertyEquals(case, "take_meds", "1")
        self.assertEqual(case.user_id, self.user1._id)

        old_call1 = call1
        old_call2 = call2

        CaseReminderHandler.now = datetime(2014, 6, 24, 13, 0)
        CaseReminderHandler.fire_reminders()
        reminder = self.reminder2.get_reminder(self.case)
        self.assertEquals(reminder.next_fire, datetime(2014, 6, 25, 12, 0))

        call1 = self.get_last_outbound_call(self.user1)
        # No new call for user1 since it was already answered
        self.assertEqual(call1._id, old_call1._id)

        call2 = self.get_last_outbound_call(self.user2)
        self.assertTrue(call2.use_precached_first_response)
        self.assertNotEqual(call2._id, old_call2._id)

        kookoo_session_id = call2.gateway_session_id[7:]
        resp = self.kookoo_in({
            "cid": "0002",
            "sid": kookoo_session_id,
            "event": "NewCall",
        })
        self.assertEqual(
            resp, '<response sid="%s"><collectdtmf l="1" o="3000">'
            '<playtext>How do you feel today? Press 1 for good, 2 for bad.'
            '</playtext></collectdtmf></response>' % kookoo_session_id)

        resp = self.kookoo_in({
            "cid": "0002",
            "sid": kookoo_session_id,
            "event": "GotDTMF",
            "data": "1",
        })
        self.assertEqual(
            resp, '<response sid="%s"><collectdtmf l="1" o="3000">'
            '<playtext>Did you remember to take your meds today? Press 1 for yes, 2 for no.'
            '</playtext></collectdtmf></response>' % kookoo_session_id)

        resp = self.kookoo_in({
            "cid": "0002",
            "sid": kookoo_session_id,
            "event": "GotDTMF",
            "data": "2",
        })
        self.assertEqual(
            resp,
            '<response sid="%s"><hangup/></response>' % kookoo_session_id)

        self.kookoo_finished({
            "sid": kookoo_session_id,
            "status": "answered",
            "duration": "20",
        })
        call2 = CallLog.get(call2._id)
        self.assertTrue(call2.answered)
        self.assertEqual(call2.duration, 20)

        form = self.get_last_form_submission()
        self.assertFormQuestionEquals(form, "how_feel", "1")
        self.assertFormQuestionEquals(form, "take_meds", "2")
        self.assertEqual(form.form["meta"]["userID"], self.user2._id)
        case = CommCareCase.get(self.case._id)
        self.assertCasePropertyEquals(case, "how_feel", "1")
        self.assertCasePropertyEquals(case, "take_meds", "2")
        self.assertEqual(case.user_id, self.user2._id)
Exemplo n.º 52
0
    def rows(self):
        startdate = json_format_datetime(self.datespan.startdate_utc)
        enddate = json_format_datetime(self.datespan.enddate_utc)
        data = CallLog.by_domain_date(self.domain, startdate, enddate)
        result = []
        
        # Store the results of lookups for faster loading
        contact_cache = {}
        form_map = {}
        xforms_sessions = {}
        
        direction_map = {
            INCOMING: _("Incoming"),
            OUTGOING: _("Outgoing"),
        }
        
        # Retrieve message log options
        message_log_options = getattr(settings, "MESSAGE_LOG_OPTIONS", {})
        abbreviated_phone_number_domains = message_log_options.get("abbreviated_phone_number_domains", [])
        abbreviate_phone_number = (self.domain in abbreviated_phone_number_domains)
        
        for call in data:
            doc_info = self.get_recipient_info(call, contact_cache)

            form_unique_id = call.form_unique_id
            if form_unique_id in [None, ""]:
                form_name = "-"
            elif form_unique_id in form_map:
                form_name = form_map.get(form_unique_id)
            else:
                form_name = get_form_name(form_unique_id)
                form_map[form_unique_id] = form_name
            
            phone_number = call.phone_number
            if abbreviate_phone_number and phone_number is not None:
                phone_number = phone_number[0:7] if phone_number[0:1] == "+" else phone_number[0:6]
            
            timestamp = tz_utils.adjust_datetime_to_timezone(call.date, pytz.utc.zone, self.timezone.zone)
            
            if call.direction == INCOMING:
                answered = "-"
            else:
                answered = _("Yes") if call.answered else _("No")
            
            if call.xforms_session_id:
                xforms_sessions[call.xforms_session_id] = None
            
            row = [
                call.xforms_session_id,
                self._fmt_timestamp(timestamp),
                self._fmt_contact_link(call, doc_info),
                self._fmt(phone_number),
                self._fmt(direction_map.get(call.direction,"-")),
                self._fmt(form_name),
                self._fmt("-"),
                self._fmt(answered),
                self._fmt(call.duration),
                self._fmt(_("Yes") if call.error else _("No")),
                self._fmt(call.error_message),
            ]
            
            if self.request.couch_user.is_previewer():
                row.append(self._fmt(call.gateway_session_id))
            
            result.append(row)

        # Look up the XFormsSession documents 500 at a time.
        # Had to do this because looking up one document at a time slows things
        # down a lot.
        all_session_ids = xforms_sessions.keys()
        limit = 500
        range_max = int(ceil(len(all_session_ids) * 1.0 / limit))
        for i in range(range_max):
            lower_bound = i * limit
            upper_bound = (i + 1) * limit
            sessions = XFormsSession.view("smsforms/sessions_by_touchforms_id",
                keys=all_session_ids[lower_bound:upper_bound],
                include_docs=True).all()
            for session in sessions:
                xforms_sessions[session.session_id] = session.submission_id

        # Add into the final result the link to the submission based on the
        # outcome of the above lookups.
        final_result = []
        for row in result:
            final_row = row[1:]
            session_id = row[0]
            if session_id:
                submission_id = xforms_sessions[session_id]
                if submission_id:
                    final_row[5] = self._fmt_submission_link(submission_id)
            final_result.append(final_row)

        return final_result
Exemplo n.º 53
0
def incoming(phone_number,
             backend_module,
             gateway_session_id,
             ivr_event,
             input_data=None):
    # Look up the call if one already exists
    call_log_entry = CallLog.view("sms/call_by_session",
                                  startkey=[gateway_session_id, {}],
                                  endkey=[gateway_session_id],
                                  descending=True,
                                  include_docs=True,
                                  limit=1).one()

    answer_is_valid = False  # This will be set to True if IVR validation passes
    error_occurred = False  # This will be set to False if touchforms validation passes (i.e., no form constraints fail)

    if call_log_entry is not None and backend_module:
        if ivr_event == IVR_EVENT_NEW_CALL and call_log_entry.use_precached_first_response:
            return HttpResponse(call_log_entry.first_response)

        form = Form.get_form(call_log_entry.form_unique_id)
        app = form.get_app()
        module = form.get_module()
        recipient = call_log_entry.recipient

        if ivr_event == IVR_EVENT_NEW_CALL:
            case_id = call_log_entry.case_id
            case_for_case_submission = call_log_entry.case_for_case_submission
            session, responses = start_session(
                recipient.domain,
                recipient,
                app,
                module,
                form,
                case_id,
                yield_responses=True,
                session_type=XFORMS_SESSION_IVR,
                case_for_case_submission=case_for_case_submission)
            call_log_entry.xforms_session_id = session.session_id
        elif ivr_event == IVR_EVENT_INPUT:
            if call_log_entry.xforms_session_id is not None:
                current_q = current_question(call_log_entry.xforms_session_id)
                if validate_answer(input_data, current_q):
                    answer_is_valid = True
                    responses = _get_responses(
                        recipient.domain,
                        recipient._id,
                        input_data,
                        yield_responses=True,
                        session_id=call_log_entry.xforms_session_id)
                else:
                    call_log_entry.current_question_retry_count += 1
                    responses = [current_q]
            else:
                responses = []
        else:
            responses = []

        ivr_responses = []
        hang_up = False
        for response in responses:
            if response.is_error:
                error_occurred = True
                call_log_entry.current_question_retry_count += 1
                if response.text_prompt is None:
                    ivr_responses = []
                    break
                else:
                    ivr_responses.append(
                        format_ivr_response(response.text_prompt, app))
            elif response.event.type == "question":
                ivr_responses.append(
                    format_ivr_response(response.event.caption, app))
            elif response.event.type == "form-complete":
                hang_up = True

        if answer_is_valid and not error_occurred:
            call_log_entry.current_question_retry_count = 0

        if call_log_entry.max_question_retries is not None and call_log_entry.current_question_retry_count > call_log_entry.max_question_retries:
            # Force hang-up
            ivr_responses = []

        if len(ivr_responses) == 0:
            hang_up = True

        input_length = None

        if hang_up:
            if call_log_entry.xforms_session_id is not None:
                # Process disconnect
                session = XFormsSession.latest_by_session_id(
                    call_log_entry.xforms_session_id)
                if session.end_time is None:
                    if call_log_entry.submit_partial_form:
                        submit_unfinished_form(
                            session.session_id,
                            call_log_entry.include_case_side_effects)
                    else:
                        session.end(completed=False)
                        session.save()
        else:
            # Set input_length to let the ivr gateway know how many digits we need to collect.
            # Have to get the current question again, since the last XFormsResponse in responses
            # may not have an event if it was a response to a constraint error.
            if error_occurred:
                current_q = current_question(call_log_entry.xforms_session_id)
            else:
                current_q = responses[-1]

            input_length = get_input_length(current_q)

        call_log_entry.save()
        return HttpResponse(
            backend_module.get_http_response_string(
                gateway_session_id,
                ivr_responses,
                collect_input=(not hang_up),
                hang_up=hang_up,
                input_length=input_length))

    # If not processed, just log the call

    if call_log_entry:
        # No need to log, already exists
        return HttpResponse("")

    cleaned_number = phone_number
    if cleaned_number is not None and len(
            cleaned_number) > 0 and cleaned_number[0] == "+":
        cleaned_number = cleaned_number[1:]

    # Try to look up the verified number entry
    v = VerifiedNumber.view("sms/verified_number_by_number",
                            key=cleaned_number,
                            include_docs=True).one()

    # If none was found, try to match only the last digits of numbers in the database
    if v is None:
        v = VerifiedNumber.view("sms/verified_number_by_suffix",
                                key=cleaned_number,
                                include_docs=True).one()

    # Save the call entry
    msg = CallLog(
        phone_number=cleaned_number,
        direction=INCOMING,
        date=datetime.utcnow(),
        backend_api=backend_module.API_ID if backend_module else None,
        gateway_session_id=gateway_session_id,
    )
    if v is not None:
        msg.domain = v.domain
        msg.couch_recipient_doc_type = v.owner_doc_type
        msg.couch_recipient = v.owner_id
    msg.save()

    return HttpResponse("")