Ejemplo n.º 1
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()
Ejemplo n.º 2
0
    def handle(self, *args, **options):
        # This is ok, there's only like 50k of these and we're only querying ids
        vns = VerifiedNumber.view(
            'phone_numbers/verified_number_by_owner_id',
            include_docs=False
        ).all()

        # Convert to a dict of {owner id: count of total numbers}
        owners = {}
        for vn in vns:
            owner_id = vn['key']
            if owner_id in owners:
                owners[owner_id] += 1
            else:
                owners[owner_id] = 1

        # Convert to a list of owner ids that have more than one VerifiedNumber
        # (excluding pending numbers)
        mult_list = []
        for owner_id, count in owners.iteritems():
            if count > 1:
                owner_vns = VerifiedNumber.view(
                    'phone_numbers/verified_number_by_owner_id',
                    key=owner_id,
                    include_docs=True
                ).all()
                owner_vns = [vn for vn in owner_vns if vn.verified]
                if len(owner_vns) > 1:
                    mult_list.append(owner_id)

        # If the old methodology's preferred number doesn't match the
        # new one, report it here. Only fix it if options['fix'] is True
        for owner_id in mult_list:
            user = CouchUser.get_by_user_id(owner_id)
            if not user:
                print 'ERROR: User not found: %s' % owner_id
                continue
            if not self.phones_are_strings(user):
                print 'ERROR: Phone numbers should be strings: %s' % owner_id
                continue
            preferred_old_vn = get_verified_number_for_recipient_old(user)
            preferred_new_vn = get_verified_number_for_recipient(user)

            if preferred_old_vn._id != preferred_new_vn._id:
                print "Need to change %s %s from %s to %s" % (
                    user.domain,
                    owner_id,
                    preferred_new_vn.phone_number,
                    preferred_old_vn.phone_number,
                )
                if preferred_old_vn.phone_number not in user.phone_numbers:
                    print 'ERROR: Phone numbers are out of sync: %s' % owner_id
                    continue
                if options.get('fix', False):
                    print "  fixing..."
                    user.set_default_phone_number(preferred_old_vn.phone_number)
Ejemplo n.º 3
0
def edit_contact(request, domain, sample_id, case_id):
    case = CommCareCase.get(case_id)
    if case.domain != domain:
        raise Http404
    if request.method == "POST":
        form = EditContactForm(request.POST)
        if form.is_valid():
            phone_number = form.cleaned_data.get("phone_number")
            vn = VerifiedNumber.view('sms/verified_number_by_number',
                                        key=phone_number,
                                        include_docs=True,
                                    ).one()
            if vn is not None and vn.owner_id != case_id:
                form._errors["phone_number"] = form.error_class(["Phone number is already in use."])
            else:
                update_contact(domain, case_id, request.couch_user.get_id, contact_phone_number=phone_number)
                return HttpResponseRedirect(reverse("edit_sample", args=[domain, sample_id]))
    else:
        initial = {
            "phone_number" : case.get_case_property("contact_phone_number"),
        }
        form = EditContactForm(initial=initial)
    
    context = {
        "domain" : domain,
        "case" : case,
        "form" : form,
    }
    return render(request, "reminders/partial/edit_contact.html", context)
Ejemplo n.º 4
0
 def get_couch_ids(self):
     result = VerifiedNumber.view(
         'phone_numbers/verified_number_by_domain',
         include_docs=False,
         reduce=False,
     ).all()
     return [row['id'] for row in result]
 def get_couch_ids(self):
     result = VerifiedNumber.view(
         'phone_numbers/verified_number_by_domain',
         include_docs=False,
         reduce=False,
     ).all()
     return [row['id'] for row in result]
Ejemplo n.º 6
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")
 def get_soft_deleted_couch_ids(self):
     result = VerifiedNumber.view(
         'all_docs/by_doc_type',
         startkey=['VerifiedNumber-Deleted'],
         endkey=['VerifiedNumber-Deleted', {}],
         include_docs=False,
         reduce=False,
     ).all()
     return [row['id'] for row in result]
Ejemplo n.º 8
0
 def getCouchCount(self):
     result = VerifiedNumber.view(
         'phone_numbers/verified_number_by_domain',
         startkey=[self.domain],
         endkey=[self.domain, {}],
         include_docs=False,
         reduce=True
     ).all()
     return result[0]['value'] if result else 0
Ejemplo n.º 9
0
 def get_soft_deleted_couch_ids(self):
     result = VerifiedNumber.view(
         'all_docs/by_doc_type',
         startkey=['VerifiedNumber-Deleted'],
         endkey=['VerifiedNumber-Deleted', {}],
         include_docs=False,
         reduce=False,
     ).all()
     return [row['id'] for row in result]
Ejemplo n.º 10
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")
Ejemplo n.º 11
0
    def deleteAllLogs(self):
        for obj in VerifiedNumber.view(
            'phone_numbers/verified_number_by_domain',
            startkey=[self.domain],
            endkey=[self.domain, {}],
            include_docs=True,
            reduce=False
        ).all():
            obj.delete()

        PhoneNumber.objects.filter(domain=self.domain).delete()
Ejemplo n.º 12
0
def fire_sms_survey_event(reminder, handler, recipients, verified_numbers):
    if handler.recipient in [RECIPIENT_CASE, RECIPIENT_SURVEY_SAMPLE]:
        if reminder.callback_try_count > 0:
            # Handle timeouts
            if handler.submit_partial_forms and (
                    reminder.callback_try_count == len(
                        reminder.current_event.callback_timeout_intervals)):
                # Submit partial form completions
                for session_id in reminder.xforms_session_ids:
                    submit_unfinished_form(session_id,
                                           handler.include_case_side_effects)
            else:
                # Resend current question
                for session_id in reminder.xforms_session_ids:
                    session = XFormsSession.view(
                        "smsforms/sessions_by_touchforms_id",
                        startkey=[session_id],
                        endkey=[session_id, {}],
                        include_docs=True).one()
                    if session.end_time is None:
                        vn = VerifiedNumber.view(
                            "sms/verified_number_by_owner_id",
                            key=session.connection_id,
                            include_docs=True).one()
                        if vn is not None:
                            resp = current_question(session_id)
                            send_sms_to_verified_number(
                                vn, resp.event.text_prompt)
            return True
        else:
            reminder.xforms_session_ids = []

            # Get the app, module, and form
            try:
                form_unique_id = reminder.current_event.form_unique_id
                form = Form.get_form(form_unique_id)
                app = form.get_app()
                module = form.get_module()
            except Exception as e:
                raise_error(reminder, ERROR_FORM)
                return False

            # Start a touchforms session for each recipient
            for recipient in recipients:
                verified_number = verified_numbers[recipient.get_id]
                if verified_number is None:
                    if len(recipients) == 1:
                        raise_error(reminder, ERROR_NO_VERIFIED_NUMBER)
                        return False
                    else:
                        raise_warning()  # ERROR_NO_VERIFIED_NUMBER
                        continue

                # Close all currently open sessions
                close_open_sessions(reminder.domain, recipient.get_id)

                # Start the new session
                session, responses = start_session(reminder.domain, recipient,
                                                   app, module, form,
                                                   recipient.get_id)
                session.survey_incentive = handler.survey_incentive
                session.save()
                reminder.xforms_session_ids.append(session.session_id)

                # Send out first message
                if len(responses) > 0:
                    message = format_message_list(responses)
                    result = send_sms_to_verified_number(
                        verified_number, message)
                    if not result:
                        raise_warning()  # Could not send SMS

                    if len(recipients) == 1:
                        return result

            return True
    else:
        # TODO: Make sure the above flow works for RECIPIENT_USER and RECIPIENT_OWNER
        return False
Ejemplo n.º 13
0
def fire_sms_survey_event(reminder, handler, recipients, verified_numbers):
    if reminder.callback_try_count > 0:
        # Handle timeouts
        if handler.submit_partial_forms and (reminder.callback_try_count == len(reminder.current_event.callback_timeout_intervals)):
            # Submit partial form completions
            for session_id in reminder.xforms_session_ids:
                submit_unfinished_form(session_id, handler.include_case_side_effects)
        else:
            # Resend current question
            for session_id in reminder.xforms_session_ids:
                session = XFormsSession.view("smsforms/sessions_by_touchforms_id",
                                             startkey=[session_id],
                                             endkey=[session_id, {}],
                                             include_docs=True).one()
                if session.end_time is None:
                    vn = VerifiedNumber.view("sms/verified_number_by_owner_id",
                                             key=session.connection_id,
                                             include_docs=True).first()
                    if vn is not None:
                        metadata = MessageMetadata(
                            workflow=get_workflow(handler),
                            reminder_id=reminder._id,
                            xforms_session_couch_id=session._id,
                        )
                        resp = current_question(session_id)
                        send_sms_to_verified_number(vn, resp.event.text_prompt, metadata)
        return True
    else:
        reminder.xforms_session_ids = []

        # Get the app, module, and form
        try:
            form_unique_id = reminder.current_event.form_unique_id
            form = Form.get_form(form_unique_id)
            app = form.get_app()
            module = form.get_module()
        except Exception as e:
            raise_error(reminder, ERROR_FORM)
            return False

        # Start a touchforms session for each recipient
        for recipient in recipients:

            verified_number, unverified_number = get_recipient_phone_number(
                reminder, recipient, verified_numbers)

            domain_obj = Domain.get_by_name(reminder.domain, strict=True)
            no_verified_number = verified_number is None
            cant_use_unverified_number = (unverified_number is None or
                not domain_obj.send_to_duplicated_case_numbers or
                form_requires_input(form))
            if no_verified_number and cant_use_unverified_number:
                if len(recipients) == 1:
                    raise_error(reminder, ERROR_NO_VERIFIED_NUMBER)
                    return False
                else:
                    continue

            # Close all currently open sessions
            XFormsSession.close_all_open_sms_sessions(reminder.domain, recipient.get_id)

            # Start the new session
            if isinstance(recipient, CommCareCase) and not handler.force_surveys_to_use_triggered_case:
                case_id = recipient.get_id
            else:
                case_id = reminder.case_id
            session, responses = start_session(reminder.domain, recipient, app, module, form, case_id, case_for_case_submission=handler.force_surveys_to_use_triggered_case)
            session.survey_incentive = handler.survey_incentive
            session.workflow = get_workflow(handler)
            session.reminder_id = reminder._id
            session.save()
            reminder.xforms_session_ids.append(session.session_id)

            # Send out first message
            if len(responses) > 0:
                message = format_message_list(responses)
                metadata = MessageMetadata(
                    workflow=get_workflow(handler),
                    reminder_id=reminder._id,
                    xforms_session_couch_id=session._id,
                )
                if verified_number:
                    result = send_sms_to_verified_number(verified_number, message, metadata)
                else:
                    result = send_sms(reminder.domain, recipient, unverified_number,
                        message, metadata)

                if len(recipients) == 1:
                    return result

        return True
Ejemplo n.º 14
0
    context = {"domain": domain, "samples": SurveySample.get_all(domain)}
    return render(request, "reminders/partial/sample_list.html", context)


@reminders_permission
def edit_contact(request, domain, sample_id, case_id):
    case = CommCareCase.get(case_id)
    if case.domain != domain:
        raise Http404
    if request.method == "POST":
        form = EditContactForm(request.POST)
        if form.is_valid():
            phone_number = form.cleaned_data.get("phone_number")
            vn = VerifiedNumber.view(
                'sms/verified_number_by_number',
                key=phone_number,
                include_docs=True,
            ).one()
            if vn is not None and vn.owner_id != case_id:
                form._errors["phone_number"] = form.error_class(
                    ["Phone number is already in use."])
            else:
                update_contact(domain,
                               case_id,
                               request.couch_user.get_id,
                               contact_phone_number=phone_number)
                return HttpResponseRedirect(
                    reverse("edit_sample", args=[domain, sample_id]))
    else:
        initial = {
            "phone_number": case.get_case_property("contact_phone_number"),
Ejemplo n.º 15
0
def add_sample(request, domain, sample_id=None):
    sample = None
    if sample_id is not None:
        sample = SurveySample.get(sample_id)

    if request.method == "POST":
        form = SurveySampleForm(request.POST, request.FILES)
        if form.is_valid():
            name = form.cleaned_data.get("name")
            sample_contacts = form.cleaned_data.get("sample_contacts")
            time_zone = form.cleaned_data.get("time_zone")
            use_contact_upload_file = form.cleaned_data.get(
                "use_contact_upload_file")
            contact_upload_file = form.cleaned_data.get("contact_upload_file")

            if sample is None:
                sample = SurveySample(domain=domain,
                                      name=name,
                                      time_zone=time_zone.zone)
            else:
                sample.name = name
                sample.time_zone = time_zone.zone

            errors = []

            phone_numbers = []
            if use_contact_upload_file == "Y":
                for contact in contact_upload_file:
                    phone_numbers.append(contact["phone_number"])
            else:
                for contact in sample_contacts:
                    phone_numbers.append(contact["phone_number"])

            existing_number_entries = VerifiedNumber.view(
                'sms/verified_number_by_number',
                keys=phone_numbers,
                include_docs=True).all()

            for entry in existing_number_entries:
                if entry.domain != domain or entry.owner_doc_type != "CommCareCase":
                    errors.append("Cannot use phone number %s" %
                                  entry.phone_number)

            if len(errors) > 0:
                if use_contact_upload_file == "Y":
                    form._errors["contact_upload_file"] = form.error_class(
                        errors)
                else:
                    form._errors["sample_contacts"] = form.error_class(errors)
            else:
                existing_numbers = [
                    v.phone_number for v in existing_number_entries
                ]
                nonexisting_numbers = list(
                    set(phone_numbers).difference(existing_numbers))

                id_range = DomainCounter.increment(domain, "survey_contact_id",
                                                   len(nonexisting_numbers))
                ids = iter(range(id_range[0], id_range[1] + 1))
                for phone_number in nonexisting_numbers:
                    register_sms_contact(
                        domain,
                        "participant",
                        str(ids.next()),
                        request.couch_user.get_id,
                        phone_number,
                        contact_phone_number_is_verified="1",
                        contact_backend_id="MOBILE_BACKEND_TROPO_US",
                        language_code="en",
                        time_zone=time_zone.zone)

                newly_registered_entries = VerifiedNumber.view(
                    'sms/verified_number_by_number',
                    keys=nonexisting_numbers,
                    include_docs=True).all()

                sample.contacts = [
                    v.owner_id for v in existing_number_entries
                ] + [v.owner_id for v in newly_registered_entries]

                sample.save()

                # Update delegation tasks for surveys using this sample
                surveys = Survey.view("reminders/sample_to_survey",
                                      key=[domain, sample._id, "CATI"],
                                      include_docs=True).all()
                for survey in surveys:
                    survey.update_delegation_tasks(request.couch_user.get_id)
                    survey.save()

                return HttpResponseRedirect(
                    reverse("sample_list", args=[domain]))
    else:
        initial = {}
        if sample is not None:
            initial["name"] = sample.name
            initial["time_zone"] = sample.time_zone
            contact_info = []
            for case_id in sample.contacts:
                case = CommCareCase.get(case_id)
                contact_info.append({
                    "id": case.name,
                    "phone_number": case.contact_phone_number,
                    "case_id": case_id
                })
            initial["sample_contacts"] = contact_info
        form = SurveySampleForm(initial=initial)
Ejemplo n.º 16
0
def fire_sms_survey_event(reminder, handler, recipients, verified_numbers):
    if handler.recipient in [RECIPIENT_CASE, RECIPIENT_SURVEY_SAMPLE]:
        if reminder.callback_try_count > 0:
            # Handle timeouts
            if handler.submit_partial_forms and (reminder.callback_try_count == len(reminder.current_event.callback_timeout_intervals)):
                # Submit partial form completions
                for session_id in reminder.xforms_session_ids:
                    submit_unfinished_form(session_id, handler.include_case_side_effects)
            else:
                # Resend current question
                for session_id in reminder.xforms_session_ids:
                    session = XFormsSession.view("smsforms/sessions_by_touchforms_id",
                                                    startkey=[session_id],
                                                    endkey=[session_id, {}],
                                                    include_docs=True).one()
                    if session.end_time is None:
                        vn = VerifiedNumber.view("sms/verified_number_by_owner_id",
                                                  key=session.connection_id,
                                                  include_docs=True).one()
                        if vn is not None:
                            resp = current_question(session_id)
                            send_sms_to_verified_number(vn, resp.event.text_prompt)
            return True
        else:
            reminder.xforms_session_ids = []
            
            # Get the app, module, and form
            try:
                form_unique_id = reminder.current_event.form_unique_id
                form = Form.get_form(form_unique_id)
                app = form.get_app()
                module = form.get_module()
            except Exception as e:
                raise_error(reminder, ERROR_FORM)
                return False
            
            # Start a touchforms session for each recipient
            for recipient in recipients:
                verified_number = verified_numbers[recipient.get_id]
                if verified_number is None:
                    if len(recipients) == 1:
                        raise_error(reminder, ERROR_NO_VERIFIED_NUMBER)
                        return False
                    else:
                        raise_warning() # ERROR_NO_VERIFIED_NUMBER
                        continue
                
                # Close all currently open sessions
                close_open_sessions(reminder.domain, recipient.get_id)
                
                # Start the new session
                session, responses = start_session(reminder.domain, recipient, app, module, form, recipient.get_id)
                session.survey_incentive = handler.survey_incentive
                session.save()
                reminder.xforms_session_ids.append(session.session_id)
                
                # Send out first message
                if len(responses) > 0:
                    message = format_message_list(responses)
                    result = send_sms_to_verified_number(verified_number, message)
                    if not result:
                        raise_warning() # Could not send SMS
                    
                    if len(recipients) == 1:
                        return result
                
            return True
    else:
        # TODO: Make sure the above flow works for RECIPIENT_USER and RECIPIENT_OWNER
       return False
Ejemplo n.º 17
0
def fire_sms_survey_event(reminder, handler, recipients, verified_numbers):
    if reminder.callback_try_count > 0:
        # Handle timeouts
        if handler.submit_partial_forms and (
                reminder.callback_try_count == len(
                    reminder.current_event.callback_timeout_intervals)):
            # Submit partial form completions
            for session_id in reminder.xforms_session_ids:
                submit_unfinished_form(session_id,
                                       handler.include_case_side_effects)
        else:
            # Resend current question
            for session_id in reminder.xforms_session_ids:
                session = XFormsSession.view(
                    "smsforms/sessions_by_touchforms_id",
                    startkey=[session_id],
                    endkey=[session_id, {}],
                    include_docs=True).one()
                if session.end_time is None:
                    vn = VerifiedNumber.view("sms/verified_number_by_owner_id",
                                             key=session.connection_id,
                                             include_docs=True).first()
                    if vn is not None:
                        metadata = MessageMetadata(
                            workflow=get_workflow(handler),
                            reminder_id=reminder._id,
                            xforms_session_couch_id=session._id,
                        )
                        resp = current_question(session_id)
                        send_sms_to_verified_number(vn, resp.event.text_prompt,
                                                    metadata)
        return True
    else:
        reminder.xforms_session_ids = []

        # Get the app, module, and form
        try:
            form_unique_id = reminder.current_event.form_unique_id
            form = Form.get_form(form_unique_id)
            app = form.get_app()
            module = form.get_module()
        except Exception as e:
            raise_error(reminder, ERROR_FORM)
            return False

        # Start a touchforms session for each recipient
        for recipient in recipients:

            verified_number, unverified_number = get_recipient_phone_number(
                reminder, recipient, verified_numbers)

            domain_obj = Domain.get_by_name(reminder.domain, strict=True)
            no_verified_number = verified_number is None
            cant_use_unverified_number = (
                unverified_number is None
                or not domain_obj.send_to_duplicated_case_numbers
                or form_requires_input(form))
            if no_verified_number and cant_use_unverified_number:
                if len(recipients) == 1:
                    raise_error(reminder, ERROR_NO_VERIFIED_NUMBER)
                    return False
                else:
                    continue

            # Close all currently open sessions
            XFormsSession.close_all_open_sms_sessions(reminder.domain,
                                                      recipient.get_id)

            # Start the new session
            if isinstance(
                    recipient, CommCareCase
            ) and not handler.force_surveys_to_use_triggered_case:
                case_id = recipient.get_id
            else:
                case_id = reminder.case_id
            session, responses = start_session(
                reminder.domain,
                recipient,
                app,
                module,
                form,
                case_id,
                case_for_case_submission=handler.
                force_surveys_to_use_triggered_case)
            session.survey_incentive = handler.survey_incentive
            session.workflow = get_workflow(handler)
            session.reminder_id = reminder._id
            session.save()
            reminder.xforms_session_ids.append(session.session_id)

            # Send out first message
            if len(responses) > 0:
                message = format_message_list(responses)
                metadata = MessageMetadata(
                    workflow=get_workflow(handler),
                    reminder_id=reminder._id,
                    xforms_session_couch_id=session._id,
                )
                if verified_number:
                    result = send_sms_to_verified_number(
                        verified_number, message, metadata)
                else:
                    result = send_sms(reminder.domain, recipient,
                                      unverified_number, message, metadata)

                if len(recipients) == 1:
                    return result

        return True
Ejemplo n.º 18
0
def fire_sms_survey_event(reminder, handler, recipients, verified_numbers,
                          logged_event):
    if reminder.callback_try_count > 0:
        # Handle timeouts
        if handler.submit_partial_forms and (
                reminder.callback_try_count == len(
                    reminder.current_event.callback_timeout_intervals)):
            # Submit partial form completions
            for session_id in reminder.xforms_session_ids:
                submit_unfinished_form(session_id,
                                       handler.include_case_side_effects)
        else:
            # Resend current question
            for session_id in reminder.xforms_session_ids:
                session = get_session_by_session_id(session_id)
                if session.end_time is None:
                    vn = VerifiedNumber.view("sms/verified_number_by_owner_id",
                                             key=session.connection_id,
                                             include_docs=True).first()
                    if vn is not None:
                        metadata = MessageMetadata(
                            workflow=get_workflow(handler),
                            reminder_id=reminder._id,
                            xforms_session_couch_id=session._id,
                        )
                        resp = current_question(session_id)
                        send_sms_to_verified_number(vn, resp.event.text_prompt,
                                                    metadata)
    else:
        reminder.xforms_session_ids = []
        domain_obj = Domain.get_by_name(reminder.domain, strict=True)

        # Get the app, module, and form
        try:
            form_unique_id = reminder.current_event.form_unique_id
            form = Form.get_form(form_unique_id)
            app = form.get_app()
            module = form.get_module()
        except Exception:
            logged_event.error(MessagingEvent.ERROR_CANNOT_FIND_FORM)
            return

        # Start a touchforms session for each recipient
        for recipient in recipients:
            logged_subevent = logged_event.create_subevent(
                handler, reminder, recipient)

            verified_number, unverified_number = get_recipient_phone_number(
                reminder, recipient, verified_numbers)

            no_verified_number = verified_number is None
            cant_use_unverified_number = (
                unverified_number is None
                or not domain_obj.send_to_duplicated_case_numbers
                or form_requires_input(form))
            if no_verified_number and cant_use_unverified_number:
                logged_subevent.error(
                    MessagingEvent.ERROR_NO_TWO_WAY_PHONE_NUMBER)
                continue

            key = "start-sms-survey-for-contact-%s" % recipient.get_id
            with CriticalSection([key], timeout=60):
                # Get the case to submit the form against, if any
                if (isinstance(recipient, CommCareCase)
                        and not handler.force_surveys_to_use_triggered_case):
                    case_id = recipient.get_id
                else:
                    case_id = reminder.case_id

                if form.requires_case() and not case_id:
                    logged_subevent.error(MessagingEvent.ERROR_NO_CASE_GIVEN)
                    continue

                # Close all currently open sessions
                SQLXFormsSession.close_all_open_sms_sessions(
                    reminder.domain, recipient.get_id)

                # Start the new session
                try:
                    session, responses = start_session(
                        reminder.domain,
                        recipient,
                        app,
                        module,
                        form,
                        case_id,
                        case_for_case_submission=handler.
                        force_surveys_to_use_triggered_case)
                except TouchformsError as e:
                    human_readable_message = e.response_data.get(
                        'human_readable_message', None)

                    logged_subevent.error(
                        MessagingEvent.ERROR_TOUCHFORMS_ERROR,
                        additional_error_text=human_readable_message)

                    if touchforms_error_is_config_error(e):
                        # Don't reraise the exception because this means there are configuration
                        # issues with the form that need to be fixed
                        continue
                    else:
                        # Reraise the exception so that the framework retries it again later
                        raise
                except Exception as e:
                    logged_subevent.error(
                        MessagingEvent.ERROR_TOUCHFORMS_ERROR)
                    # Reraise the exception so that the framework retries it again later
                    raise
                session.survey_incentive = handler.survey_incentive
                session.workflow = get_workflow(handler)
                session.reminder_id = reminder._id
                session.save()

            reminder.xforms_session_ids.append(session.session_id)
            logged_subevent.xforms_session = session
            logged_subevent.save()

            # Send out first message
            if len(responses) > 0:
                message = format_message_list(responses)
                metadata = MessageMetadata(
                    workflow=get_workflow(handler),
                    reminder_id=reminder._id,
                    xforms_session_couch_id=session._id,
                )
                if verified_number:
                    send_sms_to_verified_number(verified_number, message,
                                                metadata)
                else:
                    send_sms(reminder.domain, recipient, unverified_number,
                             message, metadata)

            logged_subevent.completed()
Ejemplo n.º 19
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("")
Ejemplo n.º 20
0
def fire_sms_survey_event(reminder, handler, recipients, verified_numbers, logged_event):
    if reminder.callback_try_count > 0:
        # Handle timeouts
        if handler.submit_partial_forms and (
            reminder.callback_try_count == len(reminder.current_event.callback_timeout_intervals)
        ):
            # Submit partial form completions
            for session_id in reminder.xforms_session_ids:
                submit_unfinished_form(session_id, handler.include_case_side_effects)
        else:
            # Resend current question
            for session_id in reminder.xforms_session_ids:
                session = get_session_by_session_id(session_id)
                if session.end_time is None:
                    vn = VerifiedNumber.view(
                        "sms/verified_number_by_owner_id", key=session.connection_id, include_docs=True
                    ).first()
                    if vn is not None:
                        metadata = MessageMetadata(
                            workflow=get_workflow(handler),
                            reminder_id=reminder._id,
                            xforms_session_couch_id=session._id,
                        )
                        resp = current_question(session_id)
                        send_sms_to_verified_number(vn, resp.event.text_prompt, metadata)
    else:
        reminder.xforms_session_ids = []
        domain_obj = Domain.get_by_name(reminder.domain, strict=True)

        # Get the app, module, and form
        try:
            form_unique_id = reminder.current_event.form_unique_id
            form = Form.get_form(form_unique_id)
            app = form.get_app()
            module = form.get_module()
        except Exception:
            logged_event.error(MessagingEvent.ERROR_CANNOT_FIND_FORM)
            return

        # Start a touchforms session for each recipient
        for recipient in recipients:
            logged_subevent = logged_event.create_subevent(handler, reminder, recipient)

            verified_number, unverified_number = get_recipient_phone_number(reminder, recipient, verified_numbers)

            no_verified_number = verified_number is None
            cant_use_unverified_number = (
                unverified_number is None or not domain_obj.send_to_duplicated_case_numbers or form_requires_input(form)
            )
            if no_verified_number and cant_use_unverified_number:
                logged_subevent.error(MessagingEvent.ERROR_NO_TWO_WAY_PHONE_NUMBER)
                continue

            key = "start-sms-survey-for-contact-%s" % recipient.get_id
            with CriticalSection([key], timeout=60):
                # Get the case to submit the form against, if any
                if isinstance(recipient, CommCareCase) and not handler.force_surveys_to_use_triggered_case:
                    case_id = recipient.get_id
                else:
                    case_id = reminder.case_id

                if form.requires_case() and not case_id:
                    logged_subevent.error(MessagingEvent.ERROR_NO_CASE_GIVEN)
                    continue

                # Close all currently open sessions
                SQLXFormsSession.close_all_open_sms_sessions(reminder.domain, recipient.get_id)

                # Start the new session
                try:
                    session, responses = start_session(
                        reminder.domain,
                        recipient,
                        app,
                        module,
                        form,
                        case_id,
                        case_for_case_submission=handler.force_surveys_to_use_triggered_case,
                    )
                except TouchformsError as e:
                    human_readable_message = e.response_data.get("human_readable_message", None)

                    logged_subevent.error(
                        MessagingEvent.ERROR_TOUCHFORMS_ERROR, additional_error_text=human_readable_message
                    )

                    if touchforms_error_is_config_error(e):
                        # Don't reraise the exception because this means there are configuration
                        # issues with the form that need to be fixed
                        continue
                    else:
                        # Reraise the exception so that the framework retries it again later
                        raise
                except Exception as e:
                    logged_subevent.error(MessagingEvent.ERROR_TOUCHFORMS_ERROR)
                    # Reraise the exception so that the framework retries it again later
                    raise
                session.survey_incentive = handler.survey_incentive
                session.workflow = get_workflow(handler)
                session.reminder_id = reminder._id
                session.save()

            reminder.xforms_session_ids.append(session.session_id)
            logged_subevent.xforms_session = session
            logged_subevent.save()

            # Send out first message
            if len(responses) > 0:
                message = format_message_list(responses)
                metadata = MessageMetadata(
                    workflow=get_workflow(handler), reminder_id=reminder._id, xforms_session_couch_id=session._id
                )
                if verified_number:
                    send_sms_to_verified_number(verified_number, message, metadata)
                else:
                    send_sms(reminder.domain, recipient, unverified_number, message, metadata)

            logged_subevent.completed()
Ejemplo n.º 21
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 = get_session_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("")
Ejemplo n.º 22
0
def add_sample(request, domain, sample_id=None):
    sample = None
    if sample_id is not None:
        sample = SurveySample.get(sample_id)
    
    if request.method == "POST":
        form = SurveySampleForm(request.POST, request.FILES)
        if form.is_valid():
            name            = form.cleaned_data.get("name")
            sample_contacts = form.cleaned_data.get("sample_contacts")
            time_zone       = form.cleaned_data.get("time_zone")
            use_contact_upload_file = form.cleaned_data.get("use_contact_upload_file")
            contact_upload_file = form.cleaned_data.get("contact_upload_file")
            
            if sample is None:
                sample = SurveySample (
                    domain = domain,
                    name = name,
                    time_zone = time_zone.zone
                )
            else:
                sample.name = name
                sample.time_zone = time_zone.zone
            
            errors = []
            
            phone_numbers = []
            if use_contact_upload_file == "Y":
                for contact in contact_upload_file:
                    phone_numbers.append(contact["phone_number"])
            else:
                for contact in sample_contacts:
                    phone_numbers.append(contact["phone_number"])
            
            existing_number_entries = VerifiedNumber.view('sms/verified_number_by_number',
                                            keys=phone_numbers,
                                            include_docs=True
                                       ).all()
            
            for entry in existing_number_entries:
                if entry.domain != domain or entry.owner_doc_type != "CommCareCase":
                    errors.append("Cannot use phone number %s" % entry.phone_number)
            
            if len(errors) > 0:
                if use_contact_upload_file == "Y":
                    form._errors["contact_upload_file"] = form.error_class(errors)
                else:
                    form._errors["sample_contacts"] = form.error_class(errors)
            else:
                existing_numbers = [v.phone_number for v in existing_number_entries]
                nonexisting_numbers = list(set(phone_numbers).difference(existing_numbers))
                
                id_range = DomainCounter.increment(domain, "survey_contact_id", len(nonexisting_numbers))
                ids = iter(range(id_range[0], id_range[1] + 1))
                for phone_number in nonexisting_numbers:
                    register_sms_contact(domain, "participant", str(ids.next()), request.couch_user.get_id, phone_number, contact_phone_number_is_verified="1", contact_backend_id="MOBILE_BACKEND_TROPO_US", language_code="en", time_zone=time_zone.zone)
                
                newly_registered_entries = VerifiedNumber.view('sms/verified_number_by_number',
                                                keys=nonexisting_numbers,
                                                include_docs=True
                                           ).all()
                
                sample.contacts = [v.owner_id for v in existing_number_entries] + [v.owner_id for v in newly_registered_entries]
                
                sample.save()
                
                # Update delegation tasks for surveys using this sample
                surveys = Survey.view("reminders/sample_to_survey", key=[domain, sample._id, "CATI"], include_docs=True).all()
                for survey in surveys:
                    survey.update_delegation_tasks(request.couch_user.get_id)
                    survey.save()
                
                return HttpResponseRedirect(reverse("sample_list", args=[domain]))
    else:
        initial = {}
        if sample is not None:
            initial["name"] = sample.name
            initial["time_zone"] = sample.time_zone
            contact_info = []
            for case_id in sample.contacts:
                case = CommCareCase.get(case_id)
                contact_info.append({"id":case.name, "phone_number":case.contact_phone_number, "case_id" : case_id})
            initial["sample_contacts"] = contact_info
        form = SurveySampleForm(initial=initial)
    
    context = {
        "domain" : domain,
        "form" : form,
        "sample_id" : sample_id
    }
    return render(request, "reminders/partial/add_sample.html", context)
Ejemplo n.º 23
0
def incoming(phone_number, text, backend_api):
    phone_without_plus = str(phone_number)
    if phone_without_plus[0] == "+":
        phone_without_plus = phone_without_plus[1:]
    phone_with_plus = "+" + phone_without_plus
    
    # Circular Import
    from corehq.apps.reminders.models import SurveyKeyword
    
    v = VerifiedNumber.view("sms/verified_number_by_number",
        key=phone_without_plus,
        include_docs=True
    ).one()
    
    # Log message in message log
    msg = SMSLog(
        phone_number    = phone_with_plus,
        direction       = INCOMING,
        date            = datetime.utcnow(),
        text            = text,
        backend_api     = backend_api
    )
    if v is not None:
        msg.couch_recipient_doc_type    = v.owner_doc_type
        msg.couch_recipient             = v.owner_id
        msg.domain                      = v.domain
    msg.save()
    
    # Handle incoming sms
    if v is not None:
        session = XFormsSession.view("smsforms/open_sessions_by_connection",
                                     key=[v.domain, v.owner_id],
                                     include_docs=True).one()
        
        text_words = text.upper().split()
        
        # Respond to "#START <keyword>" command
        if len(text_words) > 0 and text_words[0] == "#START":
            if len(text_words) > 1:
                sk = SurveyKeyword.get_keyword(v.domain, text_words[1])
                if sk is not None:
                    if session is not None:
                        session.end(False)
                        session.save()
                    start_session_from_keyword(sk, v)
                else:
                    send_sms_to_verified_number(v, "Survey '" + text_words[1] + "' not found.")
            else:
                send_sms_to_verified_number(v, "Usage: #START <keyword>")
        
        # Respond to "#STOP" keyword
        elif len(text_words) > 0 and text_words[0] == "#STOP":
            if session is not None:
                session.end(False)
                session.save()
        
        # Respond to "#CURRENT" keyword
        elif len(text_words) > 0 and text_words[0] == "#CURRENT":
            if session is not None:
                resp = current_question(session.session_id)
                send_sms_to_verified_number(v, resp.event.text_prompt)
        
        # Respond to unknown command
        elif len(text_words) > 0 and text_words[0][0] == "#":
            send_sms_to_verified_number(v, "Unknown command '" + text_words[0] + "'")
        
        # If there's an open session, treat the inbound text as the answer to the next question
        elif session is not None:
            resp = current_question(session.session_id)
            event = resp.event
            valid = False
            error_msg = None
            
            # Validate select questions
            if event.datatype == "select":
                try:
                    answer = int(text.strip())
                    if answer >= 1 and answer <= len(event._dict["choices"]):
                        valid = True
                except Exception:
                    pass
                if not valid:
                    error_msg = "Invalid Response. " + event.text_prompt
            
            # For now, anything else passes
            else:
                valid = True
            
            if valid:
                responses = get_responses(msg)
                if len(responses) > 0:
                    response_text = format_message_list(responses)
                    send_sms_to_verified_number(v, response_text)
            else:
                send_sms_to_verified_number(v, error_msg)
        
        # Try to match the text against a keyword to start a survey
        elif len(text_words) > 0:
            sk = SurveyKeyword.get_keyword(v.domain, text_words[0])
            if sk is not None:
                start_session_from_keyword(sk, v)
    else:
        #TODO: Registration via SMS
        pass