def test_update_form_references_form_link(self): app = Application.new_app('domain', 'Foo') app.modules.append(Module(forms=[Form()])) app.modules.append(Module(forms=[Form(), Form()])) app.build_spec = BuildSpec.from_string('2.7.0/latest') app.get_module(0).get_form(0).source = get_simple_form( xmlns='xmlns-0.0') app.get_module(1).get_form(0).source = get_simple_form(xmlns='xmlns-1') original_form_id1 = app.get_module(1).get_form(0).unique_id original_form_id2 = app.get_module(1).get_form(1).unique_id app.get_module(0).get_form(0).form_links = [ FormLink(xpath="", form_id=original_form_id1), FormLink(xpath="", form_id=original_form_id2), ] copy = Application.from_source(app.export_json(dump_json=False), 'domain') new_form_id1 = copy.get_module(1).get_form(0).unique_id new_form_id2 = copy.get_module(1).get_form(1).unique_id self.assertNotEqual(original_form_id1, new_form_id1) self.assertNotEqual(original_form_id2, new_form_id2) self.assertEqual(new_form_id1, copy.get_module(0).get_form(0).form_links[0].form_id) self.assertEqual(new_form_id2, copy.get_module(0).get_form(0).form_links[1].form_id)
def dehydrate_module(self, app, module, langs): """ Convert a Module object to a JValue representation with just the good parts. NOTE: This is not a tastypie "magic"-name method to dehydrate the "module" field; there is no such field. """ dehydrated = {} dehydrated['case_type'] = module.case_type dehydrated['case_properties'] = app_manager_util.get_case_properties(app, [module.case_type], defaults=['name'])[module.case_type] dehydrated['forms'] = [] for form in module.forms: form = Form.get_form(form.unique_id) form_jvalue = { 'xmlns': form.xmlns, 'name': form.name, 'questions': form.get_questions(langs), } dehydrated['forms'].append(form_jvalue) return dehydrated
def keyword_uses_form_that_requires_case(survey_keyword): for action in survey_keyword.actions: if action.action in [METHOD_SMS_SURVEY, METHOD_STRUCTURED_SMS]: form = Form.get_form(action.form_unique_id) if form.requires_case(): return True return False
def get_form_name(form_unique_id): try: form = Form.get_form(form_unique_id) except ResourceNotFound: return _("[unknown]") return form.full_path_name
def start_session_from_keyword(survey_keyword, verified_number): try: form_unique_id = survey_keyword.form_unique_id form = Form.get_form(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) if len(responses) > 0: message = format_message_list(responses) send_sms_to_verified_number(verified_number, message) except Exception: logging.exception( "Exception while starting survey for keyword %s, domain %s" % (survey_keyword.keyword, verified_number.domain))
def keyword_uses_form_that_requires_case(survey_keyword): for action in survey_keyword.keywordaction_set.all(): if action.action in [KeywordAction.ACTION_SMS_SURVEY, KeywordAction.ACTION_STRUCTURED_SMS]: form = Form.get_form(action.form_unique_id) if form.requires_case(): return True return False
def create_data_source_from_app(request, domain): if request.method == 'POST': form = ConfigurableDataSourceFromAppForm(domain, request.POST) if form.is_valid(): # save config app_source = form.app_source_helper.get_app_source(form.cleaned_data) app = Application.get(app_source.application) if app_source.source_type == 'case': data_source = get_case_data_source(app, app_source.source) data_source.save() messages.success(request, _(u"Data source created for '{}'".format(app_source.source))) else: assert app_source.source_type == 'form' xform = Form.get_form(app_source.source) data_source = get_form_data_source(app, xform) data_source.save() messages.success(request, _(u"Data source created for '{}'".format(xform.default_name()))) return HttpResponseRedirect(reverse('edit_configurable_data_source', args=[domain, data_source._id])) else: form = ConfigurableDataSourceFromAppForm(domain) context = _shared_context(domain) context['sources_map'] = form.app_source_helper.all_sources context['form'] = form return render(request, 'userreports/data_source_from_app.html', context)
def dehydrate_module(self, app, module, langs): """ Convert a Module object to a JValue representation with just the good parts. NOTE: This is not a tastypie "magic"-name method to dehydrate the "module" field; there is no such field. """ dehydrated = {} dehydrated['case_type'] = module.case_type dehydrated['case_properties'] = app_manager_util.get_case_properties( app, [module.case_type], defaults=['name'])[module.case_type] dehydrated['forms'] = [] for form in module.forms: form = Form.get_form(form.unique_id) form_jvalue = { 'xmlns': form.xmlns, 'name': form.name, 'questions': form.get_questions(langs), } dehydrated['forms'].append(form_jvalue) return dehydrated
def dehydrate_module(self, app, module, langs): """ Convert a Module object to a JValue representation with just the good parts. NOTE: This is not a tastypie "magic"-name method to dehydrate the "module" field; there is no such field. """ try: dehydrated = {} dehydrated["case_type"] = module.case_type dehydrated["case_properties"] = app_manager_util.get_case_properties( app, [module.case_type], defaults=["name"] )[module.case_type] dehydrated["unique_id"] = module.unique_id dehydrated["forms"] = [] for form in module.forms: form_unique_id = form.unique_id form = Form.get_form(form_unique_id) form_jvalue = { "xmlns": form.xmlns, "name": form.name, "questions": form.get_questions(langs, include_translations=True), "unique_id": form_unique_id, } dehydrated["forms"].append(form_jvalue) return dehydrated except Exception as e: return {"error": unicode(e)}
def get_form_data_schema(request, domain, form_unique_id): """Get data schema One of `app_id` or `form_unique_id` is required. `app_id` is ignored if `form_unique_id` is provided. :returns: A list of data source schema definitions. A data source schema definition is a dictionary. For details on the content of the dictionary, see https://github.com/dimagi/Vellum/blob/master/src/datasources.js """ data = [] try: form, app = Form.get_form(form_unique_id, and_app=True) except ResourceConflict: raise Http404() if app.domain != domain: raise Http404() try: data.append(get_session_schema(form)) if form.requires_case() or is_usercase_in_use(domain): data.append(get_casedb_schema(form)) except Exception: logger.exception("schema error") return HttpResponseBadRequest("schema error, see log for details") data.extend( sorted(item_lists_by_domain(domain), key=lambda x: x['name'].lower()) ) kw = {} if "pretty" in request.GET: kw["indent"] = 2 return HttpResponse(json.dumps(data, **kw))
def create_data_source_from_app(request, domain): if request.method == 'POST': form = ConfigurableDataSourceFromAppForm(domain, request.POST) if form.is_valid(): # save config app_source = form.app_source_helper.get_app_source( form.cleaned_data) app = Application.get(app_source.application) if app_source.source_type == 'case': data_source = get_case_data_source(app, app_source.source) data_source.save() messages.success( request, _(u"Data source created for '{}'".format( app_source.source))) else: assert app_source.source_type == 'form' xform = Form.get_form(app_source.source) data_source = get_form_data_source(app, xform) data_source.save() messages.success( request, _(u"Data source created for '{}'".format( xform.default_name()))) return HttpResponseRedirect( reverse('edit_configurable_data_source', args=[domain, data_source._id])) else: form = ConfigurableDataSourceFromAppForm(domain) context = _shared_context(domain) context['sources_map'] = form.app_source_helper.all_sources context['form'] = form return render(request, 'userreports/data_source_from_app.html', context)
def post(self, request, *args, **kwargs): if self.form.is_valid(): app_source = self.form.app_source_helper.get_app_source( self.form.cleaned_data) app = Application.get(app_source.application) if app_source.source_type == 'case': data_source = get_case_data_source(app, app_source.source) data_source.save() messages.success( request, _(u"Data source created for '{}'".format( app_source.source))) else: assert app_source.source_type == 'form' xform = Form.get_form(app_source.source) data_source = get_form_data_source(app, xform) data_source.save() messages.success( request, _(u"Data source created for '{}'".format( xform.default_name()))) return HttpResponseRedirect( reverse(EditDataSourceView.urlname, args=[self.domain, data_source._id])) return self.get(request, *args, **kwargs)
def get_form(form_unique_id, include_app_module=False): form = Form.get_form(form_unique_id) if include_app_module: app = form.get_app() module = form.get_module() return (app, module, form) else: return form
def form_casexml(request, domain, form_unique_id): try: form, app = Form.get_form(form_unique_id, and_app=True) except ResourceNotFound: raise Http404() if domain != app.domain: raise Http404() return HttpResponse(form.create_casexml())
def test_update_form_references_case_list_form(self): app = Application.new_app('domain', 'Foo') app.modules.append(Module(forms=[Form()])) app.modules.append(Module(forms=[Form()])) app.build_spec = BuildSpec.from_string('2.7.0/latest') app.get_module(0).get_form(0).source = get_simple_form( xmlns='xmlns-0.0') app.get_module(1).get_form(0).source = get_simple_form(xmlns='xmlns-1') original_form_id = app.get_module(1).get_form(0).unique_id app.get_module(0).case_list_form.form_id = original_form_id copy = Application.from_source(app.export_json(dump_json=False), 'domain') new_form_id = copy.get_module(1).get_form(0).unique_id self.assertNotEqual(original_form_id, new_form_id) self.assertEqual(new_form_id, copy.get_module(0).case_list_form.form_id)
def _resolve_from_template(self, template, query_context): # todo: support other types and options assert template.type == 'form' startdate, enddate = get_daterange_start_end_dates(template.time_range) xmlns = Form.get_form(template.source_id).xmlns return FormES().user_id(query_context.user._id).xmlns([xmlns]).submitted( gte=startdate, lte=enddate, ).size(0).count()
def _resolve_from_template(self, template, query_context): # todo: support other types and options assert template.type == 'form' startdate, enddate = get_daterange_start_end_dates(template.time_range) xmlns = Form.get_form(template.source_id).xmlns return FormES().user_id(query_context.user._id).xmlns( [xmlns]).submitted( gte=startdate, lte=enddate, ).size(0).count()
def clean_form_unique_id(self): value = self.cleaned_data.get("form_unique_id") if value is None: raise ValidationError(_("Please create a form first, and then add a keyword for it.")) try: form = CCHQForm.get_form(value) app = form.get_app() assert app.domain == self._cchq_domain except Exception: raise ValidationError(_("Invalid form chosen.")) return value
def _resolve_from_template(self, template, query_context): # todo: support other types and options assert template.type == 'form' startdate, enddate = get_daterange_start_end_dates(template.time_range) xmlns = Form.get_form(template.source_id).xmlns return FormData.objects.filter( user_id=query_context.user._id, xmlns=xmlns, received_on__gte=startdate, received_on__lt=enddate, ).count()
def search_form_by_id_response(self): """ Returns a dict of {"id": [form unique id], "text": [full form path]} """ form_unique_id = self.search_term try: form = Form.get_form(form_unique_id) assert form.get_app().domain == self.domain return {"text": form.full_path_name, "id": form_unique_id} except: return {}
def validate_form_unique_id(form_unique_id, domain): error_msg = _('Invalid form chosen.') try: form = CCHQForm.get_form(form_unique_id) app = form.get_app() except Exception: raise ValidationError(error_msg) if app.domain != domain: raise ValidationError(error_msg) return form_unique_id
def get_memoized_app_module_form(self, domain): try: form = Form.get_form(self.form_unique_id) app = form.get_app() module = form.get_module() except (ResourceNotFound, XFormIdNotUnique): return None, None, None, None if app.domain != domain: return None, None, None, None return app, module, form, form_requires_input(form)
def get_app_module_form(call_log_entry, logged_subevent): """ Returns (app, module, form, error) """ try: form = Form.get_form(call_log_entry.form_unique_id) app = form.get_app() module = form.get_module() return (app, module, form, False) except: log_error(MessagingEvent.ERROR_CANNOT_FIND_FORM, call_log_entry, logged_subevent) return (None, None, None, True)
def clean(self): cleaned_data = super(ConfigurableFormDataSourceFromAppForm, self).clean() app = Application.get(cleaned_data['app_id']) form = Form.get_form(cleaned_data['form_id']) if form.get_app()._id != app._id: raise ValidationError(_('Form name {} not found in application {}').format( form.default_name(), app.name )) self.app = app self.form = form return cleaned_data
def get_app_module_form(form_unique_id, logged_subevent=None): """ Returns (app, module, form, error, error_code) """ try: form = Form.get_form(form_unique_id) app = form.get_app() module = form.get_module() return (app, module, form, False, None) except: log_error(MessagingEvent.ERROR_CANNOT_FIND_FORM, logged_subevent) return (None, None, None, True, MSG_FORM_NOT_FOUND)
def get_memoized_app_module_form(self, domain): try: form = Form.get_form(self.form_unique_id) app = form.get_app() module = form.get_module() except (ResourceNotFound, XFormIdNotUnique): return None, None, None if app.domain != domain: return None, None, None return app, module, form
def clean_form_unique_id(self): value = self.cleaned_data.get("form_unique_id") if value is None: raise ValidationError( _("Please create a form first, and then add a keyword for it.") ) try: form = CCHQForm.get_form(value) app = form.get_app() assert app.domain == self._cchq_domain except Exception: raise ValidationError(_("Invalid form chosen.")) return value
def search_form_by_id_response(self): """ Returns a dict of {"id": [form unique id], "text": [full form path]} """ form_unique_id = self.search_term try: form = Form.get_form(form_unique_id) assert form.get_app().domain == self.domain return { 'text': form.full_path_name, 'id': form_unique_id, } except: return {}
def get_form_name(form_unique_id): form = Form.get_form(form_unique_id) app = form.get_app() module = form.get_module() lang = app.langs[0] try: module_name = module.name[lang] except Exception: module_name = module.name.items()[0][1] try: form_name = form.name[lang] except Exception: form_name = form.name.items()[0][1] return app.name + "/" + module_name + "/" + form_name
def setUpClass(cls): super(ExportsFormsAnalyticsTest, cls).setUpClass() from casexml.apps.case.tests.util import delete_all_xforms from corehq.apps.app_manager.models import Application, Module, Form delete_all_xforms() with trap_extra_setup(ConnectionError, msg="cannot connect to elasicsearch"): cls.es = get_es_new() initialize_index_and_mapping(cls.es, XFORM_INDEX_INFO) cls.domain = 'exports_forms_analytics_domain' cls.app_id_1 = 'a' + uuid.uuid4().hex cls.app_id_2 = 'b' + uuid.uuid4().hex cls.xmlns_1 = 'my://crazy.xmlns/' cls.xmlns_2 = 'my://crazy.xmlns/app' cls.apps = [ Application(_id=cls.app_id_2, domain=cls.domain, modules=[Module(forms=[Form(xmlns=cls.xmlns_2)])]) ] for app in cls.apps: app.save() cls.forms = [ create_form_for_test(domain=cls.domain, app_id=cls.app_id_1, xmlns=cls.xmlns_1, save=False), create_form_for_test(domain=cls.domain, app_id=cls.app_id_1, xmlns=cls.xmlns_1, save=False), create_form_for_test(domain=cls.domain, app_id=cls.app_id_2, xmlns=cls.xmlns_2, save=False), ] cls.error_forms = [ create_form_for_test(domain=cls.domain, state=XFormInstance.ERROR, save=False) ] cls.all_forms = cls.forms + cls.error_forms for form in cls.all_forms: elastic_form = transform_xform_for_elasticsearch(form.to_json()) send_to_elasticsearch('forms', elastic_form) cls.es.indices.refresh(XFORM_INDEX_INFO.alias)
def __init__(self, domain, app, source_type, source_id): assert (source_type in ['case', 'form']) self.domain = domain self.app = app self.source_type = source_type # source_id is a case type of form id self.source_id = source_id if self.source_type == 'form': self.source_form = Form.get_form(self.source_id) self.source_xform = XForm(self.source_form.source) if self.source_type == 'case': prop_map = get_case_properties( self.app, [self.source_id], defaults=DEFAULT_CASE_PROPERTY_DATATYPES.keys() ) self.case_properties = sorted(set(prop_map[self.source_id]) | {'closed'})
def rename_language(request, domain, form_unique_id): old_code = request.POST.get('oldCode') new_code = request.POST.get('newCode') try: form, app = Form.get_form(form_unique_id, and_app=True) except ResourceConflict: raise Http404() if app.domain != domain: raise Http404() try: form.rename_xform_language(old_code, new_code) app.save() return HttpResponse(json.dumps({"status": "ok"})) except XFormException as e: response = HttpResponse(json.dumps({'status': 'error', 'message': unicode(e)}), status=409) return response
def rename_language(request, domain, form_unique_id): old_code = request.POST.get("oldCode") new_code = request.POST.get("newCode") try: form, app = Form.get_form(form_unique_id, and_app=True) except ResourceConflict: raise Http404() if app.domain != domain: raise Http404() try: form.rename_xform_language(old_code, new_code) app.save() return HttpResponse(json.dumps({"status": "ok"})) except XFormException as e: response = HttpResponse(json.dumps({"status": "error", "message": unicode(e)}), status=409) return response
def test_repeat_subcases_schema_generation(self): module = Module(case_type='child', _parent=Application()) form = Form().with_id(0, module) form.actions.subcases = [ OpenSubCaseAction( repeat_context='/data/repeat', case_properties={ 'weight': ConditionalCaseUpdate( question_path='/data/repeat/group/weight'), }, subcase_index=0, _nest=True).with_id(0, None), OpenSubCaseAction( repeat_context='/data/repeat', case_properties={ 'height': ConditionalCaseUpdate(question_path='/data/repeat/height'), }, subcase_index=1, _nest=True).with_id(1, None), OpenSubCaseAction( repeat_context='/data/repeat/nested_repeat', case_properties={ 'age': ConditionalCaseUpdate( question_path='/data/repeat/nested_repeat/age'), }, subcase_index=2, _nest=False).with_id(2, None), ] schema = FormExportDataSchema._add_export_items_for_cases( ExportGroupSchema(path=MAIN_TABLE), [form], ['/data/repeat', '/data/nested_repeat'], )[0] self.assertEqual(len(schema.group_schemas), len(form.actions.subcases)) for group_schema, action in zip(schema.group_schemas, form.actions.subcases): base_path = 'form.{}'.format(action.repeat_context[6:].replace( '/', '.')) if action._nest: base_path += '.{}'.format(action.form_element_name) self._check_subcase_repeat_group_schema( group_schema, list(action.case_properties), base_path)
def get_form_name(form_unique_id): try: form = Form.get_form(form_unique_id) except ResourceNotFound: return _("[unknown]") app = form.get_app() module = form.get_module() lang = app.langs[0] try: module_name = module.name[lang] except Exception: module_name = module.name.items()[0][1] try: form_name = form.name[lang] except Exception: form_name = form.name.items()[0][1] return app.name + "/" + module_name + "/" + form_name
def __init__(self, domain, app, source_type, source_id): assert (source_type in ['case', 'form']) self.domain = domain self.app = app self.source_type = source_type # source_id is a case type of form id self.source_id = source_id if self.source_type == 'form': self.source_form = Form.get_form(self.source_id) self.source_xform = XForm(self.source_form.source) if self.source_type == 'case': prop_map = get_case_properties( self.app, [self.source_id], defaults=DEFAULT_CASE_PROPERTY_DATATYPES.keys()) self.case_properties = sorted( set(prop_map[self.source_id]) | {'closed'})
def __init__(self, domain, app, source_type, source_id): assert (source_type in ['case', 'form']) self.domain = domain self.app = app self.source_type = source_type # source_id is a case type of form id self.source_id = source_id if self.source_type == 'form': self.source_form = Form.get_form(self.source_id) self.source_xform = XForm(self.source_form.source) if self.source_type == 'case': property_builder = ParentCasePropertyBuilder( self.app, DEFAULT_CASE_PROPERTY_DATATYPES.keys() ) self.case_properties = list( property_builder.get_properties(self.source_id) | {'closed'} )
def post(self, request, *args, **kwargs): if self.form.is_valid(): app_source = self.form.app_source_helper.get_app_source(self.form.cleaned_data) app = Application.get(app_source.application) if app_source.source_type == 'case': data_source = get_case_data_source(app, app_source.source) data_source.save() messages.success(request, _(u"Data source created for '{}'".format(app_source.source))) else: assert app_source.source_type == 'form' xform = Form.get_form(app_source.source) data_source = get_form_data_source(app, xform) data_source.save() messages.success(request, _(u"Data source created for '{}'".format(xform.default_name()))) return HttpResponseRedirect(reverse( EditDataSourceView.urlname, args=[self.domain, data_source._id] )) return self.get(request, *args, **kwargs)
def container_fieldset(self): source_name = '' if self.source_type == 'case': source_name = self.report_source_id if self.source_type == 'form': source_name = Form.get_form(self.report_source_id).default_name() return crispy.Fieldset( '', crispy.Fieldset( _legend( _("Rows"), _('This report will show one row for each {name} {source}').format( name=source_name, source=self.source_type ) ) ), self.column_fieldset, self.filter_fieldset )
def xform_display(request, domain, form_unique_id): try: form, app = Form.get_form(form_unique_id, and_app=True) except ResourceNotFound: raise Http404() if domain != app.domain: raise Http404() langs = [request.GET.get('lang')] + app.langs questions = form.get_questions(langs, include_triggers=True, include_groups=True) if request.GET.get('format') == 'html': questions = [FormQuestionResponse(q) for q in questions] return render(request, "app_manager/xform_display.html", {'questions': questions_in_hierarchy(questions)}) else: return json_response(questions)
def xform_display(request, domain, form_unique_id): try: form, app = Form.get_form(form_unique_id, and_app=True) except ResourceNotFound: raise Http404() if domain != app.domain: raise Http404() langs = [request.GET.get('lang')] + app.langs questions = form.get_questions(langs, include_triggers=True, include_groups=True) if request.GET.get('format') == 'html': questions = [FormQuestionResponse(q) for q in questions] return render(request, 'app_manager/v1/xform_display.html', { 'questions': questions_in_hierarchy(questions) }) else: return json_response(questions)
def setUpClass(cls): from casexml.apps.case.tests.util import delete_all_xforms from corehq.apps.app_manager.models import Application, Module, Form delete_all_xforms() with trap_extra_setup(ConnectionError, msg="cannot connect to elasicsearch"): cls.es = get_es_new() initialize_index_and_mapping(cls.es, XFORM_INDEX_INFO) cls.domain = 'exports_forms_analytics_domain' cls.app_id_1 = 'a' + uuid.uuid4().hex cls.app_id_2 = 'b' + uuid.uuid4().hex cls.xmlns_1 = 'my://crazy.xmlns/' cls.xmlns_2 = 'my://crazy.xmlns/app' cls.apps = [ Application(_id=cls.app_id_2, domain=cls.domain, modules=[Module(forms=[Form(xmlns=cls.xmlns_2)])]) ] for app in cls.apps: app.save() cls.forms = [ XFormInstance(domain=cls.domain, app_id=cls.app_id_1, xmlns=cls.xmlns_1), XFormInstance(domain=cls.domain, app_id=cls.app_id_1, xmlns=cls.xmlns_1), XFormInstance(domain=cls.domain, app_id=cls.app_id_2, xmlns=cls.xmlns_2), ] cls.error_forms = [XFormError(domain=cls.domain)] cls.all_forms = cls.forms + cls.error_forms for form in cls.all_forms: form.save() send_to_elasticsearch('forms', form.to_json()) cls.es.indices.refresh(XFORM_INDEX_INFO.index) update_analytics_indexes()
def get_form_data_schema(request, domain, form_unique_id): """Get data schema One of `app_id` or `form_unique_id` is required. `app_id` is ignored if `form_unique_id` is provided. :returns: A list of data source schema definitions. A data source schema definition is a dictionary. For details on the content of the dictionary, see https://github.com/dimagi/Vellum/blob/master/src/datasources.js """ data = [] try: form, app = Form.get_form(form_unique_id, and_app=True) except ResourceConflict: raise Http404() if app.domain != domain: raise Http404() try: data.append(get_session_schema(form)) if form.requires_case() or is_usercase_in_use(domain): data.append(get_casedb_schema(form)) except AppManagerException as e: notify_exception(request, message=str(e)) return HttpResponseBadRequest( str(e) or _("There is an error in the case management of your application. " "Please fix the error to see case properties in this tree") ) except Exception as e: notify_exception(request, message=six.text_type(e)) return HttpResponseBadRequest("schema error, see log for details") data.extend( sorted(item_lists_by_domain(domain), key=lambda x: x['name'].lower()) ) kw = {} if "pretty" in request.GET: kw["indent"] = 2 return HttpResponse(json.dumps(data, **kw))
def start_session_from_keyword(survey_keyword, verified_number): try: form_unique_id = survey_keyword.form_unique_id form = Form.get_form(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) if len(responses) > 0: message = format_message_list(responses) send_sms_to_verified_number(verified_number, message) except Exception: logging.exception("Exception while starting survey for keyword %s, domain %s" % (survey_keyword.keyword, verified_number.domain))
def setUp(self): self.form = Form()
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 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 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 __init__(self, domain, data_source_type, data_source_id): super(FormDataSourceMeta, self).__init__(domain, data_source_type, data_source_id) self.source_form = Form.get_form(self.data_source_id) self.source_xform = XForm(self.source_form.source)
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