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()
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)
def assertMatch(self, match, phone_search, suffix_search, owner_id_search): lookedup = VerifiedNumber.by_phone(phone_search) self.assertEqual(match._id, lookedup._id) self.assertEqual(match._rev, lookedup._rev) lookedup = VerifiedNumber.by_suffix(suffix_search) self.assertEqual(match._id, lookedup._id) self.assertEqual(match._rev, lookedup._rev) [lookedup] = VerifiedNumber.by_owner_id(owner_id_search) self.assertEqual(match._id, lookedup._id) self.assertEqual(match._rev, lookedup._rev)
def handle(self, *args, **options): if len(args) == 0: raise CommandError( 'Usage: python manage.py set_backend_ids domain [backend_id] [--test]' ) domain = args[0] if len(args) > 1: backend_id = args[1] else: backend_id = None test_only = options['test'] for vn in VerifiedNumber.by_domain(domain): if (not vn.backend_id) and (not backend_id): pass elif vn.backend_id == backend_id: pass elif test_only: print '%s %s, number %s has backend %s instead of %s' % \ (vn.owner_doc_type, vn.owner_id, vn.phone_number, 'None' if vn.backend_id is None else "'%s'" % vn.backend_id, backend_id) else: if vn.owner_doc_type == "CommCareCase": print 'Cannot update backend_id for %s because it is a case' % vn.owner_id else: print 'Updating backend_id from %s to %s for %s %s, number %s' % \ (vn.backend_id, backend_id, vn.owner_doc_type, vn.owner_id, vn.phone_number) vn.backend_id = backend_id vn.save()
def initiate_sms_verification_workflow(contact, phone_number): # For now this is only applicable to mobile workers assert isinstance(contact, CommCareUser) logged_event = MessagingEvent.get_current_verification_event( contact.domain, contact.get_id, phone_number) with CriticalSection(['verifying-phone-number-%s' % phone_number]): vn = VerifiedNumber.by_phone(phone_number, include_pending=True) if vn: if vn.owner_id != contact._id: return VERIFICATION__ALREADY_IN_USE if vn.verified: return VERIFICATION__ALREADY_VERIFIED else: result = VERIFICATION__RESENT_PENDING else: contact.save_verified_number(contact.domain, phone_number, False) result = VERIFICATION__WORKFLOW_STARTED # Always create a new event when the workflow starts if logged_event: logged_event.status = MessagingEvent.STATUS_NOT_COMPLETED logged_event.save() logged_event = MessagingEvent.create_verification_event( contact.domain, contact) if not logged_event: logged_event = MessagingEvent.create_verification_event( contact.domain, contact) send_verification(contact.domain, contact, phone_number, logged_event) return result
def validate_row(row, domain, data_cols): """pre-validate the information in a particular import row: valid location, reporting user, and data formats """ # identify location loc_code = row.get('outlet_code') or row.get('site_code') row['loc'] = get_supply_point(domain, loc_code)['case'] if row['loc'] is None: set_error(row, 'ERROR location code is invalid') return # identify user phone = row.get('phone') owner = None if phone: vn = VerifiedNumber.by_phone(phone) if not vn: set_error(row, 'ERROR phone number is not verified with any user') return owner = vn.owner row['phone'] = strip_plus(phone) username = row.get('reporter') if username: user = CouchUser.get_by_username('%s@%s.commcarehq.org' % (username, domain)) if not user: set_error(row, 'ERROR reporter user does not exist') return if owner: if user and user._id != owner._id: set_error(row, 'ERROR phone number does not belong to user') return user = owner row['user'] = user # validate other fields try: row['timestamp'] = datetime.strptime(row['date'], '%Y-%m-%d') # TODO: allow time? except ValueError: set_error(row, 'ERROR invalid date format') return for k in data_cols: val = row[k] if val: try: int(val) except ValueError: set_error( row, 'ERROR invalid data value "%s" in column "%s"' % (val, k)) return if all(not row[k] for k in data_cols): set_error(row, 'ERROR stock report is empty') return
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")
def handle(self, *args, **options): if len(args) == 0: raise CommandError('Usage: python manage.py set_backend_ids domain [backend_id] [--test]') domain = args[0] if len(args) > 1: backend_id = args[1] else: backend_id = None test_only = options['test'] for vn in VerifiedNumber.by_domain(domain): if (not vn.backend_id) and (not backend_id): pass elif vn.backend_id == backend_id: pass elif test_only: print '%s %s, number %s has backend %s instead of %s' % \ (vn.owner_doc_type, vn.owner_id, vn.phone_number, 'None' if vn.backend_id is None else "'%s'" % vn.backend_id, backend_id) else: if vn.owner_doc_type == "CommCareCase": print 'Cannot update backend_id for %s because it is a case' % vn.owner_id else: print 'Updating backend_id from %s to %s for %s %s, number %s' % \ (vn.backend_id, backend_id, vn.owner_doc_type, vn.owner_id, vn.phone_number) vn.backend_id = backend_id vn.save()
def process_verification(phone_number, msg, backend_id=None): v = VerifiedNumber.by_phone(phone_number, True) if not v: return if not verification_response_ok(msg.text): return msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.save() if not domain_has_privilege(msg.domain, privileges.INBOUND_SMS): return if backend_id: backend = MobileBackend.load(backend_id) else: backend = MobileBackend.auto_load(phone_number, v.domain) # i don't know how to dynamically instantiate this object, which may be any number of doc types... #owner = CommCareMobileContactMixin.get(v.owner_id) assert v.owner_doc_type == 'CommCareUser' owner = CommCareUser.get(v.owner_id) v = owner.save_verified_number(v.domain, phone_number, True, backend.name) with localize(owner.language): send_sms_to_verified_number(v, _(CONFIRM))
def post(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) reminder = request.POST.get('reminder') phone_number = context.get('phone_number') if reminder and phone_number: phone_number = clean_phone_number(phone_number) v = VerifiedNumber.by_phone(phone_number, include_pending=True) if v and v.verified: user = v.owner if reminder == 'first_soh': first_soh_process_user(user, test=True) elif reminder == 'second_soh': now = datetime.datetime.utcnow() date = now - datetime.timedelta(days=5) second_soh_process_user(user, date, test=True) elif reminder == 'third_soh': third_soh_process_users_and_facilities([user], [user.location.sql_location], test=True) elif reminder == 'stockout': stockout_process_user(user, test=True) elif reminder == 'rrirv': rrirv_process_user(user, test=True) elif reminder == 'visit_website': visit_website_process_user(user, test=True) messages.success(request, "Reminder was sent successfully") return self.get(request, *args, **kwargs)
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")
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 process_verification(phone_number, msg, backend_id=None): v = VerifiedNumber.by_phone(phone_number, True) if not v: return if not verification_response_ok(msg.text): return msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.save() if backend_id: backend = MobileBackend.load(backend_id) else: backend = MobileBackend.auto_load(phone_number, v.domain) # i don't know how to dynamically instantiate this object, which may be any number of doc types... #owner = CommCareMobileContactMixin.get(v.owner_id) assert v.owner_doc_type == 'CommCareUser' owner = CommCareUser.get(v.owner_id) v = owner.save_verified_number(v.domain, phone_number, True, backend.name) with localize(owner.language): send_sms_to_verified_number(v, _(CONFIRM))
def chat_contacts(request, domain): domain_obj = Domain.get_by_name(domain, strict=True) verified_numbers = VerifiedNumber.by_domain(domain) contacts = [] for vn in verified_numbers: owner = vn.owner if owner is not None and owner.doc_type in ('CommCareCase','CommCareUser'): if owner.doc_type == "CommCareUser": url = reverse(EditCommCareUserView.urlname, args=[domain, owner._id]) name = owner.raw_username else: url = reverse("case_details", args=[domain, owner._id]) if domain_obj.custom_case_username: name = owner.get_case_property(domain_obj.custom_case_username) or _("(unknown)") else: name = owner.name contacts.append({ "id" : owner._id, "doc_type" : owner.doc_type, "url" : url, "name" : name, }) context = { "domain" : domain, "contacts" : contacts, } return render(request, "sms/chat_contacts.html", context)
def process_incoming(msg, delay=True): v = VerifiedNumber.by_phone(msg.phone_number, include_pending=True) if v is not None and v.verified: msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.domain = v.domain msg.save() if msg.domain_scope: # only process messages for phones known to be associated with this domain if v is None or v.domain != msg.domain_scope: raise DomainScopeValidationError( 'Attempted to simulate incoming sms from phone number not ' \ 'verified with this domain' ) create_billable_for_sms(msg, msg.backend_api, delay=delay) if v is not None and v.verified: for h in settings.SMS_HANDLERS: try: handler = to_function(h) except: logging.exception('error loading sms handler: %s' % h) continue try: was_handled = handler(v, msg.text, msg=msg) except Exception, e: logging.exception('unhandled error in sms handler %s for message [%s]: %s' % (h, msg._id, e)) was_handled = False if was_handled: break
def tearDownClass(cls): cls.user1.delete() cls.national_user.delete() cls.regional_user.delete() for vn in VerifiedNumber.by_domain(TEST_DOMAIN): vn.delete() super(TestAlerts, cls).tearDownClass()
def initiate_sms_verification_workflow(contact, phone_number): # For now this is only applicable to mobile workers assert isinstance(contact, CommCareUser) logged_event = MessagingEvent.get_current_verification_event( contact.domain, contact.get_id, phone_number) with CriticalSection(['verifying-phone-number-%s' % phone_number]): vn = VerifiedNumber.by_phone(phone_number, include_pending=True) if vn: if vn.owner_id != contact._id: return VERIFICATION__ALREADY_IN_USE if vn.verified: return VERIFICATION__ALREADY_VERIFIED else: result = VERIFICATION__RESENT_PENDING else: contact.save_verified_number(contact.domain, phone_number, False) result = VERIFICATION__WORKFLOW_STARTED # Always create a new event when the workflow starts if logged_event: logged_event.status = MessagingEvent.STATUS_NOT_COMPLETED logged_event.save() logged_event = MessagingEvent.create_verification_event(contact.domain, contact) if not logged_event: logged_event = MessagingEvent.create_verification_event(contact.domain, contact) send_verification(contact.domain, contact, phone_number, logged_event) return result
def process_incoming(msg, delay=True): v = VerifiedNumber.by_phone(msg.phone_number, include_pending=True) if v is not None and v.verified: msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.domain = v.domain msg.save() if msg.domain_scope: # only process messages for phones known to be associated with this domain if v is None or v.domain != msg.domain_scope: raise DomainScopeValidationError( 'Attempted to simulate incoming sms from phone number not ' \ 'verified with this domain' ) if v is not None and v.verified: for h in settings.SMS_HANDLERS: try: handler = to_function(h) except: notify_exception(None, message=('error loading sms handler: %s' % h)) continue try: was_handled = handler(v, msg.text, msg=msg) except Exception, e: log_sms_exception(msg) was_handled = False if was_handled: break
def get_context_data(self, **kwargs): context = super(BaseRemindersTester, self).get_context_data(**kwargs) context['phone_number'] = kwargs.get('phone_number') verified_number = VerifiedNumber.by_phone(context['phone_number']) context['phone_user'] = CommCareUser.get( verified_number.owner_id) if verified_number else None return context
def validate_sms_users(self): for sms_user in iterate_over_api_objects(self.endpoint.get_smsusers): description = "" user = CommCareUser.get_by_username(self.get_username(sms_user)[0]) if not user: description = "Not exists" EWSMigrationProblem.objects.create( domain=self.domain, external_id=sms_user.id, object_type='smsuser', description=description ) continue phone_numbers = { apply_leniency(connection.phone_number) for connection in sms_user.phone_numbers } if phone_numbers - set(user.phone_numbers): description += "Invalid phone numbers, " phone_to_backend = { connection.phone_number: connection.backend for connection in sms_user.phone_numbers } default_phone_number = [ connection.phone_number for connection in sms_user.phone_numbers if connection.default ] default_phone_number = default_phone_number[0] if default_phone_number else None if default_phone_number and (apply_leniency(default_phone_number) != user.default_phone_number): description += "Invalid default phone number, " for phone_number in user.phone_numbers: vn = VerifiedNumber.by_phone(phone_number) if not vn or vn.owner_id != user.get_id: description += "Phone number not verified, " else: backend = phone_to_backend.get(phone_number) if backend == 'message_tester' and vn.backend_id != 'MOBILE_BACKEND_TEST' \ or (backend != 'message_tester' and vn.backend_id): description += "Invalid backend, " if description: migration_problem, _ = EWSMigrationProblem.objects.get_or_create( domain=self.domain, object_id=user.get_id, object_type='smsuser' ) migration_problem.external_id = sms_user.id migration_problem.description = description.rstrip(' ,') migration_problem.save() else: EWSMigrationProblem.objects.filter( domain=self.domain, external_id=sms_user.id, object_type='smsuser' ).delete()
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)
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 tearDownClass(cls): cls.user1.delete() cls.national_user.delete() cls.regional_user.delete() cls.backend.delete() cls.sms_backend_mapping.delete() for vn in VerifiedNumber.by_domain(TEST_DOMAIN): vn.delete()
def delete_phone_numbers_for_owners(owner_ids): for ids in chunked(owner_ids, 50): results = VerifiedNumber.get_db().view( 'sms/verified_number_by_owner_id', keys=ids, include_docs=True ) soft_delete_docs([row['doc'] for row in results], VerifiedNumber)
def tearDownClass(cls): cls.sms_backend_mapping.delete() cls.sms_backend.delete() for user in WebUser.by_domain(TEST_DOMAIN): user.delete() for vn in VerifiedNumber.by_domain(TEST_DOMAIN): vn.delete()
def incoming(phone_number, text, backend_api, timestamp=None, domain_scope=None, delay=True): """ entry point for incoming sms phone_number - originating phone number text - message content backend_api - backend ID of receiving sms backend timestamp - message received timestamp; defaults to now (UTC) domain_scope - if present, only messages from phone numbers that can be definitively linked to this domain will be processed; others will be dropped (useful to provide security when simulating incoming sms) """ phone_number = clean_phone_number(phone_number) v = VerifiedNumber.by_phone(phone_number, include_pending=True) if domain_scope: # only process messages for phones known to be associated with this domain if v is None or v.domain != domain_scope: raise RuntimeError("attempted to simulate incoming sms from phone number not verified with this domain") # Log message in message log msg = SMSLog( phone_number=phone_number, direction=INCOMING, date=timestamp or datetime.utcnow(), text=text, backend_api=backend_api, ) if v is not None and v.verified: msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.domain = v.domain msg.save() create_billable_for_sms(msg, backend_api, delay=delay) if v is not None and v.verified: for h in settings.SMS_HANDLERS: try: handler = to_function(h) except: logging.exception("error loading sms handler: %s" % h) continue try: was_handled = handler(v, text) except: logging.exception("unhandled error in sms handler %s for message [%s]" % (h, text)) was_handled = False if was_handled: break else: if not process_sms_registration(msg): import verify verify.process_verification(phone_number, text) return msg
def handle(self): words = self.args if len(words) < 2 or len(words) > 3: self.respond(REGISTER_HELP) return name = words[0] code = words[1] params = { "msd_code": code } if not self.user: domains = [config.domain for config in ILSGatewayConfig.get_all_configs()] for domain in domains: loc = self._get_facility_location(domain, code) if not loc: continue splited_name = name.split(' ', 1) first_name = splited_name[0] last_name = splited_name[1] if len(splited_name) > 1 else "" clean_name = name.replace(' ', '.') username = "******" % (clean_name, domain) password = User.objects.make_random_password() user = CommCareUser.create(domain=domain, username=username, password=password, commit=False) user.first_name = first_name user.last_name = last_name if len(words) == 3: user.user_data = { 'role': words[2] } try: user.set_default_phone_number(self.msg.phone_number.replace('', '')) user.save_verified_number(domain, self.msg.phone_number.replace('', ''), True, self.msg.backend_api) except PhoneNumberInUseException as e: v = VerifiedNumber.by_phone(self.msg.phone_number, include_pending=True) v.delete() user.save_verified_number(domain, self.msg.phone_number.replace('', ''), True, self.msg.backend_api) except CommCareUser.Inconsistent: continue user.language = Languages.DEFAULT params.update({ 'sdp_name': loc.name, 'contact_name': name }) dm = user.get_domain_membership(domain) dm.location_id = loc._id user.save() add_location(user, loc._id) self.respond(REGISTRATION_CONFIRM, **params)
def tearDown(self): for location in Location.by_domain(self.TEST_DOMAIN): location.delete() for user in WebUser.by_domain(self.TEST_DOMAIN): user.delete() for vn in VerifiedNumber.by_domain(self.TEST_DOMAIN): vn.delete()
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]
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
def delete_models(self, delete_interval): print 'Deleting VerifiedNumbers...' count = iter_bulk_delete_with_doc_type_verification( VerifiedNumber.get_db(), self.get_couch_ids(), 'VerifiedNumber', wait_time=delete_interval, max_fetch_attempts=5) print 'Deleted %s documents' % count print 'Deleting Soft-Deleted VerifiedNumbers...' count = iter_bulk_delete_with_doc_type_verification( VerifiedNumber.get_db(), self.get_soft_deleted_couch_ids(), 'VerifiedNumber-Deleted', wait_time=delete_interval, max_fetch_attempts=5) print 'Deleted %s documents' % count
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")
def validate_row(row, domain, data_cols): """pre-validate the information in a particular import row: valid location, reporting user, and data formats """ # identify location loc_code = row.get('outlet_code') or row.get('site_code') row['loc'] = get_supply_point(domain, loc_code)['case'] if row['loc'] is None: set_error(row, 'ERROR location code is invalid') return # identify user phone = row.get('phone') owner = None if phone: vn = VerifiedNumber.by_phone(phone) if not vn: set_error(row, 'ERROR phone number is not verified with any user') return owner = vn.owner row['phone'] = strip_plus(phone) username = row.get('reporter') if username: user = CouchUser.get_by_username('%s@%s.commcarehq.org' % (username, domain)) if not user: set_error(row, 'ERROR reporter user does not exist') return if owner: if user and user._id != owner._id: set_error(row, 'ERROR phone number does not belong to user') return user = owner row['user'] = user # validate other fields try: row['timestamp'] = datetime.strptime(row['date'], '%Y-%m-%d') # TODO: allow time? except ValueError: set_error(row, 'ERROR invalid date format') return for k in data_cols: val = row[k] if val: try: int(val) except ValueError: set_error(row, 'ERROR invalid data value "%s" in column "%s"' % (val, k)) return if all(not row[k] for k in data_cols): set_error(row, 'ERROR stock report is empty') return
def _sync_case_phone_number(contact_case): phone_info = contact_case.get_phone_info() lock_keys = ['sync-case-phone-number-for-%s' % contact_case._id] if phone_info.phone_number: lock_keys.append('verifying-phone-number-%s' % phone_info.phone_number) with CriticalSection(lock_keys): phone_number = contact_case.get_verified_number() if ( phone_number and phone_number.contact_last_modified and phone_number.contact_last_modified >= contact_case.server_modified_on ): return if phone_info.requires_entry: try: contact_case.verify_unique_number(phone_info.phone_number) except (InvalidFormatException, PhoneNumberInUseException): if phone_number: phone_number.delete() return if not phone_number: phone_number = VerifiedNumber( domain=contact_case.domain, owner_doc_type=contact_case.doc_type, owner_id=contact_case._id, ) elif _phone_number_is_same(phone_number, phone_info): return phone_number.phone_number = phone_info.phone_number phone_number.backend_id = phone_info.sms_backend_id phone_number.ivr_backend_id = phone_info.ivr_backend_id phone_number.verified = True phone_number.contact_last_modified = contact_case.server_modified_on phone_number.save() else: if phone_number: phone_number.delete()
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()
def delete_models(self, delete_interval): print('Deleting VerifiedNumbers...') count = iter_bulk_delete_with_doc_type_verification( VerifiedNumber.get_db(), self.get_couch_ids(), 'VerifiedNumber', wait_time=delete_interval, max_fetch_attempts=5 ) print('Deleted %s documents' % count) print('Deleting Soft-Deleted VerifiedNumbers...') count = iter_bulk_delete_with_doc_type_verification( VerifiedNumber.get_db(), self.get_soft_deleted_couch_ids(), 'VerifiedNumber-Deleted', wait_time=delete_interval, max_fetch_attempts=5 ) print('Deleted %s documents' % count)
def process_incoming(msg, delay=True): v = VerifiedNumber.by_phone(msg.phone_number, include_pending=True) if v is not None and v.verified: msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.domain = v.domain contact = v.owner if isinstance(contact, CommCareUser) and hasattr(contact, 'location_id'): msg.location_id = contact.location_id msg.save() if msg.domain_scope: # only process messages for phones known to be associated with this domain if v is None or v.domain != msg.domain_scope: raise DomainScopeValidationError( 'Attempted to simulate incoming sms from phone number not ' \ 'verified with this domain' ) can_receive_sms = PhoneNumber.can_receive_sms(msg.phone_number) opt_in_keywords, opt_out_keywords = get_opt_keywords(msg) if is_opt_message(msg.text, opt_out_keywords) and can_receive_sms: if PhoneNumber.opt_out_sms(msg.phone_number): metadata = MessageMetadata(ignore_opt_out=True) text = get_message(MSG_OPTED_OUT, v, context=(opt_in_keywords[0],)) if v: send_sms_to_verified_number(v, text, metadata=metadata) else: send_sms(msg.domain, None, msg.phone_number, text, metadata=metadata) elif is_opt_message(msg.text, opt_in_keywords) and not can_receive_sms: if PhoneNumber.opt_in_sms(msg.phone_number): text = get_message(MSG_OPTED_IN, v, context=(opt_out_keywords[0],)) if v: send_sms_to_verified_number(v, text) else: send_sms(msg.domain, None, msg.phone_number, text) elif v is not None and v.verified: if domain_has_privilege(msg.domain, privileges.INBOUND_SMS): for h in settings.SMS_HANDLERS: try: handler = to_function(h) except: notify_exception(None, message=('error loading sms handler: %s' % h)) continue try: was_handled = handler(v, msg.text, msg=msg) except Exception, e: log_sms_exception(msg) was_handled = False if was_handled: break
def run_script(self, script): commands = self.parse_script(script) for command in commands: phone_number = command['phone_number'] v = VerifiedNumber.by_phone(phone_number) if command['direction'] == '>': incoming(phone_number, command['text'], v.backend_id, domain_scope=v.domain) else: msg = self.get_last_outbound_sms(v.owner_doc_type, v.owner_id) self.assertEqual(msg.text, unicode(command['text'])) self.assertEqual(strip_plus(msg.phone_number), strip_plus(phone_number)) msg.delete()
def setUp(self): self.endpoint = MockEndpoint('http://test-api.com/', 'dummy', 'dummy') self.api_object = EWSApi(TEST_DOMAIN, self.endpoint) self.datapath = os.path.join(os.path.dirname(__file__), 'data') initial_bootstrap(TEST_DOMAIN) self.api_object.prepare_commtrack_config() self.api_object.create_or_edit_roles() for user in CommCareUser.by_domain(TEST_DOMAIN): user.delete() for verified_number in VerifiedNumber.by_domain(TEST_DOMAIN): verified_number.delete()
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()
def process_verification(phone_number, msg, backend_id=None): v = VerifiedNumber.by_phone(phone_number, True) if not v: return logged_event = MessagingEvent.get_current_verification_event( v.domain, v.owner_id, phone_number) if not logged_event: logged_event = MessagingEvent.create_verification_event( v.domain, v.owner) subevent = logged_event.create_subevent_for_single_sms( v.owner_doc_type, v.owner_id) subevent.completed() msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.messaging_subevent_id = subevent.pk msg.save() if (not domain_has_privilege(msg.domain, privileges.INBOUND_SMS) or not verification_response_ok(msg.text)): return if backend_id: backend = MobileBackend.load(backend_id) else: backend = MobileBackend.auto_load(phone_number, v.domain) assert v.owner_doc_type == 'CommCareUser' owner = CommCareUser.get(v.owner_id) v = owner.save_verified_number(v.domain, phone_number, True, backend.name) logged_event.completed() subevent = logged_event.create_subevent_for_single_sms( v.owner_doc_type, v.owner_id) with localize(owner.language): send_sms_to_verified_number( v, _(CONFIRM), metadata=MessageMetadata(messaging_subevent_id=subevent.pk)) subevent.completed()
def post(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) reminder = request.POST.get('reminder') phone_number = context.get('phone_number') if reminder and phone_number: phone_number = clean_phone_number(phone_number) v = VerifiedNumber.by_phone(phone_number, include_pending=True) if v and v.verified: user = v.owner if not user: return self.get(request, *args, **kwargs) reminder_function = self.reminders.get(reminder) reminder_function(self.domain, datetime.utcnow(), test_list=[user]) messages.success(request, "Reminder was sent successfully") return self.get(request, *args, **kwargs)
def post(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) reminder = request.POST.get('reminder') phone_number = context.get('phone_number') if reminder and phone_number: phone_number = clean_phone_number(phone_number) v = VerifiedNumber.by_phone(phone_number, include_pending=True) if v and v.verified: user = v.owner if not user: return self.get(request, *args, **kwargs) reminder_function = self.reminders.get(reminder) if reminder_function: if reminder == 'third_soh': reminder_function([user], [user.sql_location], test=True) else: reminder_function(user, test=True) messages.success(request, "Reminder was sent successfully") return self.get(request, *args, **kwargs)
def incoming(phone_number, text, backend_api, timestamp=None, domain_scope=None, delay=True): """ entry point for incoming sms phone_number - originating phone number text - message content backend_api - backend ID of receiving sms backend timestamp - message received timestamp; defaults to now (UTC) domain_scope - if present, only messages from phone numbers that can be definitively linked to this domain will be processed; others will be dropped (useful to provide security when simulating incoming sms) """ phone_number = clean_phone_number(phone_number) v = VerifiedNumber.by_phone(phone_number, include_pending=True) if domain_scope: # only process messages for phones known to be associated with this domain if v is None or v.domain != domain_scope: raise RuntimeError( 'attempted to simulate incoming sms from phone number not verified with this domain' ) # Log message in message log msg = SMSLog(phone_number=phone_number, direction=INCOMING, date=timestamp or datetime.utcnow(), text=text, backend_api=backend_api) if v is not None and v.verified: msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.domain = v.domain msg.save() create_billable_for_sms(msg, backend_api, delay=delay) if v is not None and v.verified: for h in settings.SMS_HANDLERS: try: handler = to_function(h) except: logging.exception('error loading sms handler: %s' % h) continue try: was_handled = handler(v, text) except: logging.exception( 'unhandled error in sms handler %s for message [%s]' % (h, text)) was_handled = False if was_handled: break else: if not process_sms_registration(msg): import verify verify.process_verification(phone_number, text) return msg
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"),
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)
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
def _reassign_number(self, user, phone_number): v = VerifiedNumber.by_phone(phone_number, include_pending=True) if v.domain in self._get_logistics_domains(): v.delete() user.save_verified_number(self.domain, phone_number, True)
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("")
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()
def handle(self): text = ' '.join(self.msg.text.split()[1:]) is_district = False sp = "" msd_code = "" if text.find(self.DISTRICT_REG_DELIMITER) != -1: phrases = [x.strip() for x in text.split(":")] if len(phrases) != 2: self.respond(REGISTER_HELP) return name = phrases[0] sp = phrases[1] role = Roles.DISTRICT_PHARMACIST message = REGISTRATION_CONFIRM_DISTRICT params = {} is_district = True else: names = [] msd_codes = [] location_regex = '^({prefs})\d+'.format(prefs='|'.join( p.lower() for p in DISTRICT_PREFIXES)) for the_string in self.args: if re.match(location_regex, the_string.strip().lower()): msd_codes.append(the_string.strip().lower()) else: names.append(the_string) name = " ".join(names) if len(msd_codes) != 1: self.respond(REGISTER_HELP) return else: [msd_code] = msd_codes role = Roles.IN_CHARGE message = REGISTRATION_CONFIRM params = {"msd_code": msd_code} if not self.user: domains = [ config.domain for config in ILSGatewayConfig.get_all_configs() ] for domain in domains: if is_district: loc = self._get_district_location(domain, sp) else: loc = self._get_facility_location(domain, msd_code) if not loc: continue splited_name = name.split(' ', 1) first_name = splited_name[0] last_name = splited_name[1] if len(splited_name) > 1 else "" clean_name = name.replace(' ', '.') username = "******" % (clean_name, domain) password = User.objects.make_random_password() user = CommCareUser.create(domain=domain, username=username, password=password, commit=False) user.first_name = first_name user.last_name = last_name try: user.set_default_phone_number( self.msg.phone_number.replace('+', '')) user.save_verified_number( domain, self.msg.phone_number.replace('+', ''), True, self.msg.backend_api) except PhoneNumberInUseException as e: v = VerifiedNumber.by_phone(self.msg.phone_number, include_pending=True) v.delete() user.save_verified_number( domain, self.msg.phone_number.replace('+', ''), True, self.msg.backend_api) except CommCareUser.Inconsistent: continue user.language = Languages.DEFAULT params.update({'sdp_name': loc.name, 'contact_name': name}) user.user_data = {'role': role} dm = user.get_domain_membership(domain) dm.location_id = loc._id user.save() add_location(user, loc._id) if params: self.respond(message, **params)
def process_incoming(msg, delay=True): v = VerifiedNumber.by_phone(msg.phone_number, include_pending=True) if v is not None and v.verified: msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.domain = v.domain contact = v.owner if isinstance(contact, CommCareUser) and hasattr( contact, 'location_id'): msg.location_id = contact.location_id msg.save() if msg.domain_scope: # only process messages for phones known to be associated with this domain if v is None or v.domain != msg.domain_scope: raise DomainScopeValidationError( 'Attempted to simulate incoming sms from phone number not ' \ 'verified with this domain' ) can_receive_sms = PhoneNumber.can_receive_sms(msg.phone_number) opt_in_keywords, opt_out_keywords = get_opt_keywords(msg) if is_opt_message(msg.text, opt_out_keywords) and can_receive_sms: if PhoneNumber.opt_out_sms(msg.phone_number): metadata = MessageMetadata(ignore_opt_out=True) text = get_message(MSG_OPTED_OUT, v, context=(opt_in_keywords[0], )) if v: send_sms_to_verified_number(v, text, metadata=metadata) else: send_sms(msg.domain, None, msg.phone_number, text, metadata=metadata) elif is_opt_message(msg.text, opt_in_keywords) and not can_receive_sms: if PhoneNumber.opt_in_sms(msg.phone_number): text = get_message(MSG_OPTED_IN, v, context=(opt_out_keywords[0], )) if v: send_sms_to_verified_number(v, text) else: send_sms(msg.domain, None, msg.phone_number, text) elif v is not None and v.verified: if domain_has_privilege(msg.domain, privileges.INBOUND_SMS): for h in settings.SMS_HANDLERS: try: handler = to_function(h) except: notify_exception(None, message=('error loading sms handler: %s' % h)) continue try: was_handled = handler(v, msg.text, msg=msg) except Exception, e: log_sms_exception(msg) was_handled = False if was_handled: break
def sync_ilsgateway_smsuser(domain, ilsgateway_smsuser): domain_part = "%s.commcarehq.org" % domain username_part = "%s%d" % (ilsgateway_smsuser.name.strip().replace(' ', '.').lower(), ilsgateway_smsuser.id) username = "******" % (username_part[:(128 - len(domain_part))], domain_part) #sanity check assert len(username) <= 128 user = CouchUser.get_by_username(username) splitted_value = ilsgateway_smsuser.name.split(' ', 1) first_name = last_name = '' if splitted_value: first_name = splitted_value[0][:30] last_name = splitted_value[1][:30] if len(splitted_value) > 1 else '' user_dict = { 'first_name': first_name, 'last_name': last_name, 'is_active': bool(ilsgateway_smsuser.is_active), 'email': ilsgateway_smsuser.email, 'user_data': {} } if ilsgateway_smsuser.role: user_dict['user_data']['role'] = ilsgateway_smsuser.role if ilsgateway_smsuser.phone_numbers: user_dict['phone_numbers'] = [ilsgateway_smsuser.phone_numbers[0].replace('+', '')] user_dict['user_data']['backend'] = ilsgateway_smsuser.backend sp = SupplyPointCase.view('hqcase/by_domain_external_id', key=[domain, str(ilsgateway_smsuser.supply_point)], reduce=False, include_docs=True, limit=1).first() location_id = sp.location_id if sp else None if user is None and username_part: try: password = User.objects.make_random_password() user = CommCareUser.create(domain=domain, username=username, password=password, email=ilsgateway_smsuser.email, commit=False) user.first_name = first_name user.last_name = last_name user.is_active = bool(ilsgateway_smsuser.is_active) user.user_data = user_dict["user_data"] if "phone_numbers" in user_dict: user.set_default_phone_number(user_dict["phone_numbers"][0]) try: user.save_verified_number(domain, user_dict["phone_numbers"][0], True, ilsgateway_smsuser.backend) except PhoneNumberInUseException as e: v = VerifiedNumber.by_phone(user_dict["phone_numbers"][0], include_pending=True) v.delete() user.save_verified_number(domain, user_dict["phone_numbers"][0], True, ilsgateway_smsuser.backend) dm = user.get_domain_membership(domain) dm.location_id = location_id user.save() add_location(user, location_id) except Exception as e: logging.error(e) else: dm = user.get_domain_membership(domain) current_location_id = dm.location_id if dm else None save = False if current_location_id != location_id: dm.location_id = location_id add_location(user, location_id) save = True if apply_updates(user, user_dict) or save: user.save() return user
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