def create_survey_keyword(self, keyword, form_unique_id, delimiter=None, override_open_sessions=True, initiator_filter=None): sk = SurveyKeyword( domain=self.domain, keyword=keyword, description=keyword, actions=[ SurveyKeywordAction( recipient=RECIPIENT_SENDER, recipient_id=None, action=METHOD_SMS_SURVEY, message_content=None, form_unique_id=form_unique_id, use_named_args=False, named_args={}, named_args_separator=None, ) ], delimiter=delimiter, override_open_sessions=override_open_sessions, initiator_doc_type_filter=initiator_filter or [], ) sk.save() self.keywords.append(sk) return sk
def migrate(self, log_file): count = 0 ids = self.get_couch_ids() total_count = len(ids) for doc in iter_docs_with_retry(SurveyKeyword.get_db(), ids): try: couch_obj = SurveyKeyword.wrap(doc) couch_obj._migration_do_sync() except Exception as e: log_file.write('Could not sync SurveyKeyword %s: %s\n' % (doc['_id'], e)) count += 1 if (count % 1000) == 0: print 'Processed %s / %s documents' % (count, total_count)
def get_couch_ids(self): result = SurveyKeyword.view( 'reminders/survey_keywords', include_docs=False, reduce=False, ).all() return [row['id'] for row in result]
def keyword(self): if self.keyword_id is None: raise Http404() sk = SurveyKeyword.get(self.keyword_id) if sk.domain != self.domain: raise Http404() return sk
def total(self): data = ( SurveyKeyword.get_db() .view("reminders/survey_keywords", reduce=True, startkey=[self.domain], endkey=[self.domain, {}]) .first() ) return data["value"] if data else 0
def get_keyword_display(self, keyword_id, content_cache): if keyword_id in content_cache: args = content_cache[keyword_id] return self.table_cell(*args) try: keyword = SurveyKeyword.get(keyword_id) if keyword.deleted(): display = '%s %s' % (keyword.description, _('(Deleted Keyword)')) display_text = display else: urlname = (EditStructuredKeywordView.urlname if keyword.is_structured_sms() else EditNormalKeywordView.urlname) display = '<a target="_blank" href="%s">%s</a>' % ( reverse(urlname, args=[keyword.domain, keyword_id]), keyword.description, ) display_text = keyword.description args = (display_text, display) except ResourceNotFound: args = ('-', '-') content_cache[keyword_id] = args return self.table_cell(*args)
def total(self): data = SurveyKeyword.get_db().view( 'reminders/survey_keywords', reduce=True, startkey=[self.domain], endkey=[self.domain, {}], ).first() return data['value'] if data else 0
def testCouchSyncToSQL(self): self.assertEqual(self.getCouchCount(), 0) self.assertEqual(self.getSQLCount(), 0) # Test Create couch_obj = SurveyKeyword() self.setRandomCouchObjectValues(couch_obj) self.assertEqual(self.getCouchCount(), 1) self.assertEqual(self.getSQLCount(), 1) sql_obj = Keyword.objects.get(couch_id=couch_obj._id) self.compareObjects(couch_obj, sql_obj) self.assertTrue(SurveyKeyword.get_db().get_rev(couch_obj._id).startswith('1-')) # Test Update self.setRandomCouchObjectValues(couch_obj) self.assertEqual(self.getCouchCount(), 1) self.assertEqual(self.getSQLCount(), 1) sql_obj = Keyword.objects.get(couch_id=couch_obj._id) self.compareObjects(couch_obj, sql_obj) self.assertTrue(SurveyKeyword.get_db().get_rev(couch_obj._id).startswith('2-')) # Test Delete couch_id = couch_obj._id couch_obj.delete() with self.assertRaises(ResourceNotFound): SurveyKeyword.get(couch_id) self.assertEqual(self.getCouchCount(), 0) self.assertEqual(self.getSQLCount(), 0)
def get_deleted_item_data(self, item_id): try: s = SurveyKeyword.get(item_id) except ResourceNotFound: raise Http404() if s.domain != self.domain or s.doc_type != "SurveyKeyword": raise Http404() s.delete() return {"itemData": self._fmt_deleted_keyword_data(s), "template": "keyword-deleted-template"}
def get_couch_count(self): result = SurveyKeyword.view( 'reminders/survey_keywords', include_docs=False, reduce=True, ).all() if result: return result[0]['value'] return 0
def paginated_list(self): for keyword in SurveyKeyword.get_by_domain( self.domain, limit=self.limit, skip=self.skip, ): yield { 'itemData': self._fmt_keyword_data(keyword), 'template': 'keyword-row-template', }
def getCouchCount(self): result = SurveyKeyword.view( 'reminders/survey_keywords', startkey=[self.domain], endkey=[self.domain, {}], include_docs=False, reduce=True, ).all() if result: return result[0]['value'] return 0
def get_deleted_item_data(self, item_id): try: s = SurveyKeyword.get(item_id) except ResourceNotFound: raise Http404() if s.domain != self.domain or s.doc_type != "SurveyKeyword": raise Http404() s.retire() return { 'itemData': self._fmt_keyword_data(s), 'template': 'keyword-deleted-template', }
def device_data(request): if "data" not in request.POST: return HttpResponseBadRequest("Missing 'data' POST parameter.") data = request.POST.get("data") data = data.strip() data_points = data.split(",") device_id = None for data_point in data_points: key_value = data_point.partition("=") key = key_value[0].strip().upper() value = key_value[2].strip() if key == "SN": device_id = value break if device_id is None: return HttpResponseBadRequest("Missing 'SN' in data string.") # This view lookup is an implicit assert that either one device exists # with the given device_id, or no devices exist with this device_id. case = CommConnectCase.view("wisepill/device", key=[device_id], include_docs=True).one() event = WisePillDeviceEvent( domain=case.domain if case is not None else None, data=data, received_on=datetime.utcnow(), case_id=case._id if case is not None else None, processed=False, ) event.save() if case is not None: survey_keywords = SurveyKeyword.get_all(case.domain) for survey_keyword in survey_keywords: if survey_keyword.keyword.upper() == "DEVICE_EVENT": for survey_keyword_action in survey_keyword.actions: if survey_keyword_action.action == METHOD_STRUCTURED_SMS: handle_structured_sms( survey_keyword, survey_keyword_action, case, None, "DEVICE_EVENT,%s" % data, send_response=False, ) event.processed = True event.save() break return HttpResponse("")
def create_survey_keyword(self, keyword, form_unique_id, delimiter=None, override_open_sessions=True, initiator_filter=None): sk = SurveyKeyword( domain=self.domain, keyword=keyword, description=keyword, actions=[SurveyKeywordAction( recipient=RECIPIENT_SENDER, recipient_id=None, action=METHOD_SMS_SURVEY, message_content=None, form_unique_id=form_unique_id, use_named_args=False, named_args={}, named_args_separator=None, )], delimiter=delimiter, override_open_sessions=override_open_sessions, initiator_doc_type_filter=initiator_filter or [], ) sk.save() self.keywords.append(sk) return sk
def page_context(self): domain_has_reminders_or_keywords = ( CaseReminderHandler.domain_has_reminders(self.domain) or SurveyKeyword.domain_has_keywords(self.domain)) bulk_sms_verification_enabled = ( domain_has_reminders_or_keywords and domain_has_privilege(self.domain, privileges.INBOUND_SMS)) return { 'group': self.group, 'bulk_sms_verification_enabled': bulk_sms_verification_enabled, 'num_users': len(self.member_ids), 'user_form': self.user_selection_form, 'domain_uses_case_sharing': self.domain_uses_case_sharing, }
def page_context(self): domain_has_reminders_or_keywords = ( CaseReminderHandler.domain_has_reminders(self.domain) or SurveyKeyword.domain_has_keywords(self.domain) ) bulk_sms_verification_enabled = ( domain_has_reminders_or_keywords and domain_has_privilege(self.domain, privileges.INBOUND_SMS) ) return { 'group': self.group, 'bulk_sms_verification_enabled': bulk_sms_verification_enabled, 'num_users': len(self.member_ids), 'user_form': self.user_selection_form, 'domain_uses_case_sharing': self.domain_uses_case_sharing, }
def handle(self, *args, **options): keywords = SurveyKeyword.view("reminders/survey_keywords", reduce=False, include_docs=True).all() for keyword in keywords: if keyword.oct13_migration_timestamp is None: print "Processing keyword %s, %s" % (keyword.domain, keyword._id) keyword.description = "(none)" keyword.initiator_doc_type_filter = [] if keyword.form_type == FORM_TYPE_ALL_AT_ONCE: keyword.override_open_sessions = True keyword.actions = [ SurveyKeywordAction( recipient=RECIPIENT_SENDER, recipient_id=None, action=METHOD_STRUCTURED_SMS, message_content=None, form_unique_id=keyword.form_unique_id, use_named_args=keyword.use_named_args, named_args=keyword.named_args, named_args_separator=keyword.named_args_separator, ) ] else: keyword.override_open_sessions = False keyword.actions = [ SurveyKeywordAction( recipient=RECIPIENT_SENDER, recipient_id=None, action=METHOD_SMS_SURVEY, message_content=None, form_unique_id=keyword.form_unique_id, ) ] keyword.form_type = None keyword.form_unique_id = None keyword.use_named_args = None keyword.named_args = None keyword.named_args_separator = None keyword.oct13_migration_timestamp = datetime.utcnow() keyword.save()
def get_keyword_display(self, keyword_id, content_cache): if keyword_id in content_cache: return content_cache[keyword_id] try: keyword = SurveyKeyword.get(keyword_id) if keyword.deleted(): display = '%s %s' % (keyword.description, _('(Deleted Keyword)')) else: urlname = (EditStructuredKeywordView.urlname if keyword.is_structured_sms() else EditNormalKeywordView.urlname) display = '<a target="_blank" href="%s">%s</a>' % ( reverse(urlname, args=[keyword.domain, keyword_id]), keyword.description, ) except ResourceNotFound: display = '-' content_cache[keyword_id] = display return display
def global_keyword_start(v, text, msg, text_words, open_sessions): from corehq.apps.reminders.models import SurveyKeyword outbound_metadata = MessageMetadata(workflow=WORKFLOW_KEYWORD, ) if len(text_words) > 1: keyword = text_words[1] sk = SurveyKeyword.get_keyword(v.domain, keyword) if sk: if not contact_can_use_keyword(v, sk): return False process_survey_keyword_actions(v, sk, text[6:].strip(), msg) else: message = get_message(MSG_KEYWORD_NOT_FOUND, v, (keyword, )) send_sms_to_verified_number(v, message, metadata=outbound_metadata) else: message = get_message(MSG_START_KEYWORD_USAGE, v, (text_words[0], )) send_sms_to_verified_number(v, message, metadata=outbound_metadata) return True
def handle_domain_keywords(v, text, msg, text_words, sessions): any_session_open = len(sessions) > 0 for survey_keyword in SurveyKeyword.get_all(v.domain): args = split_args(text, survey_keyword) keyword = args[0].upper() if keyword == survey_keyword.keyword.upper(): if any_session_open and not survey_keyword.override_open_sessions: # We don't want to override any open sessions, so just pass and # let the form session handler handle the message return False elif not contact_can_use_keyword(v, survey_keyword): # The contact type is not allowed to invoke this keyword return False else: inbound_metadata = MessageMetadata(workflow=WORKFLOW_KEYWORD, ) add_msg_tags(msg, inbound_metadata) process_survey_keyword_actions(v, survey_keyword, text, msg) return True # No keywords matched, so pass the message onto the next handler return False
def get_keyword_display(self, keyword_id, content_cache): if keyword_id in content_cache: return content_cache[keyword_id] try: keyword = SurveyKeyword.get(keyword_id) if keyword.doc_type.endswith('-Deleted'): # We'll get rid of this once we move to postgres raise ResourceNotFound() except ResourceNotFound: display = _('(Deleted Keyword)') else: urlname = (EditStructuredKeywordView.urlname if keyword.is_structured_sms() else EditNormalKeywordView.urlname) display = '<a target="_blank" href="%s">%s</a>' % ( reverse(urlname, args=[keyword.domain, keyword_id]), keyword.description, ) content_cache[keyword_id] = display return display
def handle_domain_keywords(v, text, msg, text_words, sessions): any_session_open = len(sessions) > 0 for survey_keyword in SurveyKeyword.get_all(v.domain): args = split_args(text, survey_keyword) keyword = args[0].upper() if keyword == survey_keyword.keyword.upper(): if any_session_open and not survey_keyword.override_open_sessions: # We don't want to override any open sessions, so just pass and # let the form session handler handle the message return False elif not contact_can_use_keyword(v, survey_keyword): # The contact type is not allowed to invoke this keyword return False else: inbound_metadata = MessageMetadata( workflow=WORKFLOW_KEYWORD, ) add_msg_tags(msg, inbound_metadata) process_survey_keyword_actions(v, survey_keyword, text, msg) return True # No keywords matched, so pass the message onto the next handler return False
def global_keyword_start(v, text, msg, text_words, open_sessions): from corehq.apps.reminders.models import SurveyKeyword outbound_metadata = MessageMetadata( workflow=WORKFLOW_KEYWORD, ) if len(text_words) > 1: keyword = text_words[1] sk = SurveyKeyword.get_keyword(v.domain, keyword) if sk: if not contact_can_use_keyword(v, sk): return False process_survey_keyword_actions(v, sk, text[6:].strip(), msg) else: message = get_message(MSG_KEYWORD_NOT_FOUND, v, (keyword,)) send_sms_to_verified_number(v, message, metadata=outbound_metadata) else: message = get_message(MSG_START_KEYWORD_USAGE, v, (text_words[0],)) send_sms_to_verified_number(v, message, metadata=outbound_metadata) return True
def add_keyword(request, domain, keyword_id=None): if keyword_id is None: s = SurveyKeyword(domain = domain) else: s = SurveyKeyword.get(keyword_id) context = { "domain" : domain, "form_list" : get_form_list(domain), "errors" : [], "keyword" : s } if request.method == "GET": return render_to_response(request, "reminders/partial/add_keyword.html", context) else: keyword = request.POST.get("keyword", None) form_unique_id = request.POST.get("survey", None) if keyword is not None: keyword = keyword.strip() s.keyword = keyword s.form_unique_id = form_unique_id errors = [] if keyword is None or keyword == "": errors.append("Please enter a keyword.") if form_unique_id is None: errors.append("Please create a form first, and then add a keyword for it.") duplicate_entry = SurveyKeyword.get_keyword(domain, keyword) if duplicate_entry is not None and keyword_id != duplicate_entry._id: errors.append("Keyword already exists.") if len(errors) > 0: context["errors"] = errors return render_to_response(request, "reminders/partial/add_keyword.html", context) else: s.save() return HttpResponseRedirect(reverse("manage_surveys", args=[domain]))
def handle(self, dry_run=False, **options): def _add_field(doc): updated = False log_prefix = "{} Domain {}, form unique_id {}".format( "[DRY RUN]" if dry_run else "", doc['domain'], doc['form_unique_id']) if doc.get('form_unique_id', None) and not doc.get('app_id', None): doc['app_id'] = get_app_id_from_form_unique_id( doc['domain'], doc['form_unique_id']) if doc['app_id']: updated = True logger.info("{}: Updated {} to use app id {}".format( log_prefix, doc['_id'], doc['app_id'])) else: logger.info("{}: Could not find app".format(log_prefix)) for action in doc.get('actions', []): if action.get('form_unique_id', None) and not action.get('app_id', None): action['app_id'] = get_app_id_from_form_unique_id( doc['domain'], action['form_unique_id']) if action['app_id']: updated = True logger.info( "{}: Updated action in {} to use app id {}".format( log_prefix, doc['_id'], action['app_id'])) else: logger.info( "{}: Could not find app".format(log_prefix)) if updated and not dry_run: return DocUpdate(doc) doc_ids = get_doc_ids_by_class( SurveyKeyword) + get_deleted_doc_ids_by_class(SurveyKeyword) iter_update(SurveyKeyword.get_db(), _add_field, doc_ids)
def handle(self, *args, **options): keywords = SurveyKeyword.view("reminders/survey_keywords", reduce=False, include_docs=True).all() for keyword in keywords: if keyword.oct13_migration_timestamp is None: print "Processing keyword %s, %s" % (keyword.domain, keyword._id) keyword.description = "(none)" keyword.initiator_doc_type_filter = [] if keyword.form_type == FORM_TYPE_ALL_AT_ONCE: keyword.override_open_sessions = True keyword.actions = [SurveyKeywordAction( recipient = RECIPIENT_SENDER, recipient_id = None, action = METHOD_STRUCTURED_SMS, message_content = None, form_unique_id = keyword.form_unique_id, use_named_args = keyword.use_named_args, named_args = keyword.named_args, named_args_separator = keyword.named_args_separator, )] else: keyword.override_open_sessions = False keyword.actions = [SurveyKeywordAction( recipient = RECIPIENT_SENDER, recipient_id = None, action = METHOD_SMS_SURVEY, message_content = None, form_unique_id = keyword.form_unique_id, )] keyword.form_type = None keyword.form_unique_id = None keyword.use_named_args = None keyword.named_args = None keyword.named_args_separator = None keyword.oct13_migration_timestamp = datetime.utcnow() keyword.save()
def deleteAllObjects(self): for obj in SurveyKeyword.get_all(self.domain): obj.delete() Keyword.objects.filter(domain=self.domain).delete()
def delete_keyword(request, domain, keyword_id): s = SurveyKeyword.get(keyword_id) s.retire() return HttpResponseRedirect(reverse("manage_surveys", args=[domain]))
def keyword(self): return SurveyKeyword(domain=self.domain)
def form_session_handler(v, text): # Circular Import from corehq.apps.reminders.models import SurveyKeyword, FORM_TYPE_ONE_BY_ONE # Handle incoming sms session = XFormsSession.view( "smsforms/open_sms_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 and sk.form_type == FORM_TYPE_ONE_BY_ONE: 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, text, error_msg = validate_answer(event, text) if valid: responses = _get_responses(v.domain, v.owner_id, text) 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 + event.text_prompt) # 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 and sk.form_type == FORM_TYPE_ONE_BY_ONE: start_session_from_keyword(sk, v) # TODO should clarify what scenarios this handler actually handles. i.e., # should the error responses instead be handler by some generic error/fallback # handler return True
def delete_keyword(request, domain, keyword_id): s = SurveyKeyword.get(keyword_id) if s.domain != domain or s.doc_type != "SurveyKeyword": raise Http404 s.retire() return HttpResponseRedirect(reverse("manage_keywords", args=[domain]))
def add_keyword(request, domain, keyword_id=None): sk = None if keyword_id is not None: sk = SurveyKeyword.get(keyword_id) if sk.domain != domain: raise Http404 if request.method == "POST": form = KeywordForm(request.POST) form._cchq_domain = domain form._sk_id = sk._id if sk is not None else None if form.is_valid(): if sk is None: sk = SurveyKeyword(domain=domain) sk.keyword = form.cleaned_data.get("keyword") sk.form_type = form.cleaned_data.get("form_type") sk.form_unique_id = form.cleaned_data.get("form_unique_id") sk.delimiter = form.cleaned_data.get("delimiter") sk.use_named_args = form.cleaned_data.get("use_named_args", False) sk.named_args = form.cleaned_data.get("named_args") sk.named_args_separator = form.cleaned_data.get("named_args_separator") sk.save() return HttpResponseRedirect(reverse("manage_keywords", args=[domain])) else: initial = {} if sk is not None: initial = { "keyword" : sk.keyword, "form_unique_id" : sk.form_unique_id, "form_type" : sk.form_type, "use_custom_delimiter" : sk.delimiter is not None, "delimiter" : sk.delimiter, "use_named_args" : sk.use_named_args, "use_named_args_separator" : sk.named_args_separator is not None, "named_args" : [{"name" : k, "xpath" : v} for k, v in sk.named_args.items()], "named_args_separator" : sk.named_args_separator, } form = KeywordForm(initial=initial) context = { "domain" : domain, "form_list" : get_form_list(domain), "form" : form, "keyword" : sk, } return render(request, "reminders/partial/add_keyword.html", context)
def delete_keyword(request, domain, keyword_id): s = SurveyKeyword.get(keyword_id) s.retire() return HttpResponseRedirect(reverse("manage_keywords", args=[domain]))
def add_keyword(request, domain, keyword_id=None): sk = None if keyword_id is not None: sk = SurveyKeyword.get(keyword_id) if sk.domain != domain: raise Http404 if request.method == "POST": form = KeywordForm(request.POST) form._cchq_domain = domain form._sk_id = sk._id if sk is not None else None if form.is_valid(): if sk is None: sk = SurveyKeyword(domain=domain) sk.keyword = form.cleaned_data.get("keyword") sk.form_type = form.cleaned_data.get("form_type") sk.form_unique_id = form.cleaned_data.get("form_unique_id") sk.delimiter = form.cleaned_data.get("delimiter") sk.use_named_args = form.cleaned_data.get("use_named_args", False) sk.named_args = form.cleaned_data.get("named_args") sk.named_args_separator = form.cleaned_data.get( "named_args_separator") sk.save() return HttpResponseRedirect( reverse("manage_keywords", args=[domain])) else: initial = {} if sk is not None: initial = { "keyword": sk.keyword, "form_unique_id": sk.form_unique_id, "form_type": sk.form_type, "use_custom_delimiter": sk.delimiter is not None, "delimiter": sk.delimiter, "use_named_args": sk.use_named_args, "use_named_args_separator": sk.named_args_separator is not None, "named_args": [{ "name": k, "xpath": v } for k, v in sk.named_args.items()], "named_args_separator": sk.named_args_separator, } form = KeywordForm(initial=initial) context = { "domain": domain, "form_list": get_form_list(domain), "form": form, "keyword": sk, } return render(request, "reminders/partial/add_keyword.html", context)
def form_session_handler(v, text): # Circular Import from corehq.apps.reminders.models import SurveyKeyword, FORM_TYPE_ONE_BY_ONE # Handle incoming sms session = XFormsSession.view("smsforms/open_sms_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 and sk.form_type == FORM_TYPE_ONE_BY_ONE: 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, text, error_msg = validate_answer(event, text) if valid: responses = _get_responses(v.domain, v.owner_id, text) 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 + event.text_prompt) # 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 and sk.form_type == FORM_TYPE_ONE_BY_ONE: start_session_from_keyword(sk, v) # TODO should clarify what scenarios this handler actually handles. i.e., # should the error responses instead be handler by some generic error/fallback # handler return True
def paginated_list(self): for keyword in SurveyKeyword.get_by_domain(self.domain, limit=self.limit, skip=self.skip): yield {"itemData": self._fmt_keyword_data(keyword), "template": "keyword-row-template"}
def structured_sms_handler(verified_number, text): # Circular Import from corehq.apps.reminders.models import SurveyKeyword, FORM_TYPE_ALL_AT_ONCE text = text.strip() if text == "": return False for survey_keyword in SurveyKeyword.get_all(verified_number.domain): if survey_keyword.form_type == FORM_TYPE_ALL_AT_ONCE: if survey_keyword.delimiter is not None: args = text.split(survey_keyword.delimiter) else: args = text.split() keyword = args[0].strip().upper() if keyword != survey_keyword.keyword.upper(): continue try: error_occurred = False error_msg = "" form_complete = False # Close any open sessions close_open_sessions(verified_number.domain, verified_number.owner_id) # Start the session form = Form.get_form(survey_keyword.form_unique_id) app = form.get_app() module = form.get_module() if verified_number.owner_doc_type == "CommCareCase": case_id = verified_number.owner_id else: # TODO: Need a way to choose the case when it's a user that's playing the form case_id = None session, responses = start_session( verified_number.domain, verified_number.owner, app, module, form, case_id=case_id, yield_responses=True, ) assert len(responses) > 0, "There should be at least one response." current_question = responses[-1] form_complete = is_form_complete(current_question) if not form_complete: if survey_keyword.use_named_args: # Arguments in the sms are named xpath_answer = {} # Dictionary of {xpath : answer} for answer in args[1:]: answer = answer.strip() answer_upper = answer.upper() if survey_keyword.named_args_separator is not None: # A separator is used for naming arguments; for example, the "=" in "register name=joe age=25" answer_parts = answer.partition(survey_keyword.named_args_separator) if answer_parts[1] != survey_keyword.named_args_separator: error_occurred = True error_msg = "ERROR: Expected name and value to be joined by" + ( " '%s'" % survey_keyword.named_args_separator ) break else: arg_name = answer_parts[0].upper().strip() xpath = survey_keyword.named_args.get(arg_name, None) if xpath is not None: if xpath in xpath_answer: error_occurred = True error_msg = "ERROR: More than one answer found for" + (" '%s'" % arg_name) break xpath_answer[xpath] = answer_parts[2].strip() else: # Ignore unexpected named arguments pass else: # No separator is used for naming arguments; for example, "update a100 b34 c5" matches = 0 for k, v in survey_keyword.named_args.items(): if answer_upper.startswith(k): matches += 1 if matches > 1: error_occurred = True error_msg = "ERROR: More than one question matches" + (" '%s'" % answer) break if v in xpath_answer: error_occurred = True error_msg = "ERROR: More than one answer found for" + (" '%s'" % k) break xpath_answer[v] = answer[len(k) :].strip() if matches == 0: # Ignore unexpected named arguments pass if error_occurred: break # Go through each question in the form, answering only the questions that the sms has answers for while not form_complete and not error_occurred: if current_question.is_error: error_occurred = True error_msg = current_question.text_prompt or "ERROR: Internal server error" break xpath = current_question.event._dict["binding"] if xpath in xpath_answer: valid, answer, _error_msg = validate_answer(current_question.event, xpath_answer[xpath]) if not valid: error_occurred = True error_msg = "ERROR: " + _error_msg break responses = _get_responses( verified_number.domain, verified_number.owner_id, answer, yield_responses=True ) else: responses = _get_responses( verified_number.domain, verified_number.owner_id, "", yield_responses=True ) current_question = responses[-1] if is_form_complete(current_question): form_complete = True else: # Arguments in the sms are not named; pass each argument to each question in order for answer in args[1:]: if form_complete: # Form is complete, ignore remaining answers break if current_question.is_error: error_occurred = True error_msg = current_question.text_prompt or "ERROR: Internal server error" break valid, answer, _error_msg = validate_answer(current_question.event, answer.strip()) if not valid: error_occurred = True error_msg = "ERROR: " + _error_msg break responses = _get_responses( verified_number.domain, verified_number.owner_id, answer, yield_responses=True ) current_question = responses[-1] form_complete = is_form_complete(current_question) # If the form isn't finished yet but we're out of arguments, try to leave each remaining question blank and continue while not form_complete and not error_occurred: responses = _get_responses( verified_number.domain, verified_number.owner_id, "", yield_responses=True ) current_question = responses[-1] if current_question.is_error: error_occurred = True error_msg = current_question.text_prompt or "ERROR: Internal server error" if is_form_complete(current_question): form_complete = True except Exception: logging.exception( "Could not process structured sms for verified number %s, domain %s, keyword %s" % (verified_number._id, verified_number.domain, keyword) ) error_occurred = True error_msg = "ERROR: Internal server error" if error_occurred: send_sms_to_verified_number(verified_number, error_msg) if error_occurred or not form_complete: session = XFormsSession.get(session._id) session.end(False) session.save() return True return False
def form_session_handler(v, text): # Circular Import from corehq.apps.reminders.models import SurveyKeyword # Handle incoming sms session = XFormsSession.view("smsforms/open_sms_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 text = text.strip() upper_text = text.upper() # Validate select if event.datatype == "select": # Try to match on phrase (i.e., "Yes" or "No") choices = format_choices(event._dict["choices"]) if upper_text in choices: text = str(choices[upper_text]) valid = True else: try: answer = int(text) if answer >= 1 and answer <= len(event._dict["choices"]): valid = True except ValueError: pass # Validate multiselect elif event.datatype == "multiselect": choices = format_choices(event._dict["choices"]) max_index = len(event._dict["choices"]) proposed_answers = text.split() final_answers = {} try: if event._dict.get("required", True): assert len(proposed_answers) > 0 for answer in proposed_answers: upper_answer = answer.upper() if upper_answer in choices: final_answers[str(choices[upper_answer])] = "" else: int_answer = int(answer) assert int_answer >= 1 and int_answer <= max_index final_answers[str(int_answer)] = "" text = " ".join(final_answers.keys()) valid = True except Exception: pass # Validate int elif event.datatype == "int": try: int(text) valid = True except ValueError: pass # Validate float elif event.datatype == "float": try: float(text) valid = True except ValueError: pass # Validate longint elif event.datatype == "longint": try: long(text) valid = True except ValueError: pass # Validate date (Format: YYYYMMDD) elif event.datatype == "date": try: assert len(text) == 8 int(text) text = text[0:4] + "-" + text[4:6] + "-" + text[6:] parse(text) valid = True except Exception: pass # Validate time (Format: HHMM, 24-hour) elif event.datatype == "time": try: assert len(text) == 4 hour = int(text[0:2]) minute = int(text[2:]) assert hour >= 0 and hour <= 23 assert minute >= 0 and minute <= 59 text = "%s:%s" % (hour, minute) valid = True except Exception: pass # Other question types pass else: valid = True if valid: responses = _get_responses(v.domain, v.owner_id, text) if len(responses) > 0: response_text = format_message_list(responses) send_sms_to_verified_number(v, response_text) else: error_msg = "Invalid Response. " + event.text_prompt 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) # TODO should clarify what scenarios this handler actually handles. i.e., # should the error responses instead be handler by some generic error/fallback # handler return True
def structured_sms_handler(verified_number, text): # Circular Import from corehq.apps.reminders.models import SurveyKeyword, FORM_TYPE_ALL_AT_ONCE text = text.strip() if text == "": return False for survey_keyword in SurveyKeyword.get_all(verified_number.domain): if survey_keyword.form_type == FORM_TYPE_ALL_AT_ONCE: if survey_keyword.delimiter is not None: args = text.split(survey_keyword.delimiter) else: args = text.split() keyword = args[0].strip().upper() if keyword != survey_keyword.keyword.upper(): continue try: error_occurred = False error_msg = "" form_complete = False # Close any open sessions close_open_sessions(verified_number.domain, verified_number.owner_id) # Start the session form = Form.get_form(survey_keyword.form_unique_id) app = form.get_app() module = form.get_module() if verified_number.owner_doc_type == "CommCareCase": case_id = verified_number.owner_id else: #TODO: Need a way to choose the case when it's a user that's playing the form case_id = None session, responses = start_session(verified_number.domain, verified_number.owner, app, module, form, case_id=case_id, yield_responses=True) assert len( responses) > 0, "There should be at least one response." current_question = responses[-1] form_complete = is_form_complete(current_question) if not form_complete: if survey_keyword.use_named_args: # Arguments in the sms are named xpath_answer = {} # Dictionary of {xpath : answer} for answer in args[1:]: answer = answer.strip() answer_upper = answer.upper() if survey_keyword.named_args_separator is not None: # A separator is used for naming arguments; for example, the "=" in "register name=joe age=25" answer_parts = answer.partition( survey_keyword.named_args_separator) if answer_parts[ 1] != survey_keyword.named_args_separator: error_occurred = True error_msg = "ERROR: Expected name and value to be joined by" + ( " '%s'" % survey_keyword.named_args_separator) break else: arg_name = answer_parts[0].upper().strip() xpath = survey_keyword.named_args.get( arg_name, None) if xpath is not None: if xpath in xpath_answer: error_occurred = True error_msg = "ERROR: More than one answer found for" + ( " '%s'" % arg_name) break xpath_answer[xpath] = answer_parts[ 2].strip() else: # Ignore unexpected named arguments pass else: # No separator is used for naming arguments; for example, "update a100 b34 c5" matches = 0 for k, v in survey_keyword.named_args.items(): if answer_upper.startswith(k): matches += 1 if matches > 1: error_occurred = True error_msg = "ERROR: More than one question matches" + ( " '%s'" % answer) break if v in xpath_answer: error_occurred = True error_msg = "ERROR: More than one answer found for" + ( " '%s'" % k) break xpath_answer[v] = answer[ len(k):].strip() if matches == 0: # Ignore unexpected named arguments pass if error_occurred: break # Go through each question in the form, answering only the questions that the sms has answers for while not form_complete and not error_occurred: if current_question.is_error: error_occurred = True error_msg = current_question.text_prompt or "ERROR: Internal server error" break xpath = current_question.event._dict["binding"] if xpath in xpath_answer: valid, answer, _error_msg = validate_answer( current_question.event, xpath_answer[xpath]) if not valid: error_occurred = True error_msg = "ERROR: " + _error_msg break responses = _get_responses( verified_number.domain, verified_number.owner_id, answer, yield_responses=True) else: responses = _get_responses( verified_number.domain, verified_number.owner_id, "", yield_responses=True) current_question = responses[-1] if is_form_complete(current_question): form_complete = True else: # Arguments in the sms are not named; pass each argument to each question in order for answer in args[1:]: if form_complete: # Form is complete, ignore remaining answers break if current_question.is_error: error_occurred = True error_msg = current_question.text_prompt or "ERROR: Internal server error" break valid, answer, _error_msg = validate_answer( current_question.event, answer.strip()) if not valid: error_occurred = True error_msg = "ERROR: " + _error_msg break responses = _get_responses( verified_number.domain, verified_number.owner_id, answer, yield_responses=True) current_question = responses[-1] form_complete = is_form_complete(current_question) # If the form isn't finished yet but we're out of arguments, try to leave each remaining question blank and continue while not form_complete and not error_occurred: responses = _get_responses( verified_number.domain, verified_number.owner_id, "", yield_responses=True) current_question = responses[-1] if current_question.is_error: error_occurred = True error_msg = current_question.text_prompt or "ERROR: Internal server error" if is_form_complete(current_question): form_complete = True except Exception: logging.exception( "Could not process structured sms for verified number %s, domain %s, keyword %s" % (verified_number._id, verified_number.domain, keyword)) error_occurred = True error_msg = "ERROR: Internal server error" if error_occurred: send_sms_to_verified_number(verified_number, error_msg) if error_occurred or not form_complete: session = XFormsSession.get(session._id) session.end(False) session.save() return True return False
def manage_keywords(request, domain): context = {"domain": domain, "keywords": SurveyKeyword.get_all(domain)} return render(request, "reminders/partial/manage_keywords.html", context)
def sms_keyword_handler(v, text, msg=None): from corehq.apps.reminders.models import SurveyKeyword text = text.strip() if text == "": return False sessions = XFormsSession.get_all_open_sms_sessions(v.domain, v.owner_id) any_session_open = len(sessions) > 0 text_words = text.upper().split() if text.startswith("#"): if len(text_words) > 0 and text_words[0] == "#START": # Respond to "#START <keyword>" command if len(text_words) > 1: sk = SurveyKeyword.get_keyword(v.domain, text_words[1]) if sk is not None: if len(sk.initiator_doc_type_filter) > 0 and v.owner_doc_type not in sk.initiator_doc_type_filter: # The contact type is not allowed to invoke this keyword return False process_survey_keyword_actions(v, sk, text[6:].strip(), msg=msg) else: send_sms_to_verified_number( v, "Keyword not found: '%s'." % text_words[1], workflow=WORKFLOW_KEYWORD ) else: send_sms_to_verified_number(v, "Usage: #START <keyword>", workflow=WORKFLOW_KEYWORD) elif len(text_words) > 0 and text_words[0] == "#STOP": # Respond to "#STOP" keyword XFormsSession.close_all_open_sms_sessions(v.domain, v.owner_id) elif len(text_words) > 0 and text_words[0] == "#CURRENT": # Respond to "#CURRENT" keyword if len(sessions) == 1: resp = current_question(sessions[0].session_id) send_sms_to_verified_number( v, resp.event.text_prompt, workflow=sessions[0].workflow, reminder_id=sessions[0].reminder_id, xforms_session_couch_id=sessions[0]._id, ) else: # Response to unknown command send_sms_to_verified_number(v, "Unknown command: '%s'" % text_words[0]) if msg is not None: msg.workflow = WORKFLOW_KEYWORD msg.save() return True else: for survey_keyword in SurveyKeyword.get_all(v.domain): if survey_keyword.delimiter is not None: args = text.split(survey_keyword.delimiter) else: args = text.split() keyword = args[0].strip().upper() if keyword == survey_keyword.keyword.upper(): if any_session_open and not survey_keyword.override_open_sessions: # We don't want to override any open sessions, so just pass and let the form session handler handle the message return False elif ( len(survey_keyword.initiator_doc_type_filter) > 0 and v.owner_doc_type not in survey_keyword.initiator_doc_type_filter ): # The contact type is not allowed to invoke this keyword return False else: process_survey_keyword_actions(v, survey_keyword, text, msg=msg) if msg is not None: msg.workflow = WORKFLOW_KEYWORD msg.save() return True # No keywords matched, so pass the message onto the next handler return False
def manage_surveys(request, domain): context = { "domain" : domain, "keywords" : SurveyKeyword.get_all(domain) } return render_to_response(request, "reminders/partial/manage_surveys.html", context)