def _build_remote_request_datums(self): details_helper = DetailsHelper(self.app) if self.module.case_details.short.custom_xml: short_detail_id = 'case_short' long_detail_id = 'case_long' else: short_detail_id = 'search_short' long_detail_id = 'search_long' nodeset = CaseTypeXpath( self.module.case_type).case(instance_name=RESULTS_INSTANCE) if self.module.search_config.search_filter and toggles.USH_CASE_CLAIM_UPDATES.enabled( self.app.domain): nodeset = f"{nodeset}[{interpolate_xpath(self.module.search_config.search_filter)}]" return [ SessionDatum( id=self.module.search_config.case_session_var, nodeset=nodeset, value='./@case_id', detail_select=details_helper.get_detail_id_safe( self.module, short_detail_id), detail_confirm=details_helper.get_detail_id_safe( self.module, long_detail_id), ) ]
def _build_remote_request_datums(self): details_helper = DetailsHelper(self.app) return [SessionDatum( id='case_id', nodeset=(CaseTypeXpath(self.module.case_type) .case(instance_name=RESULTS_INSTANCE)), value='./@case_id', detail_select=details_helper.get_detail_id_safe(self.module, 'case_short'), detail_confirm=details_helper.get_detail_id_safe(self.module, 'case_long'), )]
def _build_remote_request_datums(self): details_helper = DetailsHelper(self.app) if self.module.case_details.short.custom_xml: short_detail_id = 'case_short' else: short_detail_id = 'search_short' nodeset = CaseTypeXpath(self.module.case_type).case(instance_name=RESULTS_INSTANCE) if self.module.search_config.search_filter: nodeset = f"{nodeset}[{interpolate_xpath(self.module.search_config.search_filter)}]" return [SessionDatum( id='case_id', nodeset=nodeset, value='./@case_id', detail_select=details_helper.get_detail_id_safe(self.module, short_detail_id), detail_confirm=details_helper.get_detail_id_safe(self.module, 'case_long'), )]
def __init__(self, app, modules=None): from corehq.apps.app_manager.suite_xml.sections.details import DetailsHelper self.app = app self.modules = modules or list(app.get_modules()) self.details_helper = DetailsHelper(self.app, self.modules)
class EntriesHelper(object): def __init__(self, app, modules=None): from corehq.apps.app_manager.suite_xml.sections.details import DetailsHelper self.app = app self.modules = modules or list(app.get_modules()) self.details_helper = DetailsHelper(self.app, self.modules) def get_datums_meta_for_form_generic(self, form, module=None): if not module: module = form.get_module() if form.form_type == 'module_form': datums_meta = self.get_case_datums_basic_module(module, form) elif form.form_type == 'advanced_form': datums_meta, _ = self.get_datum_meta_assertions_advanced(module, form) datums_meta.extend(EntriesHelper.get_new_case_id_datums_meta(form)) else: raise SuiteValidationError("Unexpected form type '{}' with a case list form: {}".format( form.form_type, form.unique_id )) return datums_meta @staticmethod def get_filter_xpath(module, delegation=False): filter = module.case_details.short.filter if filter: xpath = '[%s]' % interpolate_xpath(filter) else: xpath = '' if delegation: xpath += "[index/parent/@case_type = '%s']" % module.case_type xpath += "[start_date = '' or double(date(start_date)) <= double(now())]" return xpath @staticmethod def get_nodeset_xpath(case_type, filter_xpath=''): return "instance('casedb')/casedb/case[@case_type='{case_type}'][@status='open']{filter_xpath}".format( case_type=case_type, filter_xpath=filter_xpath, ) @staticmethod def get_parent_filter(relationship, parent_id): return "[index/{relationship}=instance('commcaresession')/session/data/{parent_id}]".format( relationship=relationship, parent_id=parent_id, ) @staticmethod def get_userdata_autoselect(key, session_id, mode): base_xpath = session_var('data', path='user') xpath = session_var(key, path='user/data') protected_xpath = XPath.if_( XPath.and_(base_xpath.count().eq(1), xpath.count().eq(1)), xpath, XPath.empty_string(), ) datum = SessionDatum(id=session_id, function=protected_xpath) assertions = [ EntriesHelper.get_assertion( XPath.and_(base_xpath.count().eq(1), xpath.count().eq(1)), 'case_autoload.{0}.property_missing'.format(mode), [key], ), EntriesHelper.get_assertion( CaseIDXPath(xpath).case().count().eq(1), 'case_autoload.{0}.case_missing'.format(mode), ) ] return datum, assertions def entry_for_module(self, module): # avoid circular dependency from corehq.apps.app_manager.models import Module, AdvancedModule results = [] for form in module.get_suite_forms(): e = Entry() e.form = form.xmlns # Ideally all of this version check should happen in Command/Display class if self.app.enable_localized_menu_media: e.command = LocalizedCommand( id=id_strings.form_command(form, module), menu_locale_id=id_strings.form_locale(form), media_image=bool(len(form.all_image_paths())), media_audio=bool(len(form.all_audio_paths())), image_locale_id=id_strings.form_icon_locale(form), audio_locale_id=id_strings.form_audio_locale(form), ) else: e.command = Command( id=id_strings.form_command(form, module), locale_id=id_strings.form_locale(form), media_image=form.default_media_image, media_audio=form.default_media_audio, ) config_entry = { 'module_form': self.configure_entry_module_form, 'advanced_form': self.configure_entry_advanced_form, 'careplan_form': self.configure_entry_careplan_form, }[form.form_type] config_entry(module, e, form) if ( self.app.commtrack_enabled and session_var('supply_point_id') in getattr(form, 'source', "") ): from corehq.apps.app_manager.models import AUTO_SELECT_LOCATION datum, assertions = EntriesHelper.get_userdata_autoselect( 'commtrack-supply-point', 'supply_point_id', AUTO_SELECT_LOCATION, ) e.datums.append(datum) e.assertions.extend(assertions) results.append(e) if hasattr(module, 'case_list') and module.case_list.show: e = Entry() if self.app.enable_localized_menu_media: e.command = LocalizedCommand( id=id_strings.case_list_command(module), menu_locale_id=id_strings.case_list_locale(module), media_image=bool(len(module.case_list.all_image_paths())), media_audio=bool(len(module.case_list.all_audio_paths())), image_locale_id=id_strings.case_list_icon_locale(module), audio_locale_id=id_strings.case_list_audio_locale(module), ) else: e.command = Command( id=id_strings.case_list_command(module), locale_id=id_strings.case_list_locale(module), media_image=module.case_list.default_media_image, media_audio=module.case_list.default_media_audio, ) if isinstance(module, Module): for datum_meta in self.get_case_datums_basic_module(module): e.datums.append(datum_meta.datum) elif isinstance(module, AdvancedModule): e.datums.append(SessionDatum( id='case_id_case_%s' % module.case_type, nodeset=(EntriesHelper.get_nodeset_xpath(module.case_type)), value="./@case_id", detail_select=self.details_helper.get_detail_id_safe(module, 'case_short'), detail_confirm=self.details_helper.get_detail_id_safe(module, 'case_long'), detail_persistent=self.get_detail_persistent_attr(module, module, "case_short"), detail_inline=self.get_detail_inline_attr(module, module, "case_short"), autoselect=module.auto_select_case, )) if self.app.commtrack_enabled: e.datums.append(SessionDatum( id='product_id', nodeset=ProductInstanceXpath().instance(), value="./@id", detail_select=self.details_helper.get_detail_id_safe(module, 'product_short') )) results.append(e) for entry in module.get_custom_entries(): results.append(entry) return results @staticmethod def get_assertion(test, locale_id, locale_arguments=None): assertion = Assertion(test=test) text = Text(locale_id=locale_id) if locale_arguments: locale = text.locale for arg in locale_arguments: locale.arguments.append(LocaleArgument(value=arg)) assertion.text.append(text) return assertion @staticmethod def add_case_sharing_assertion(entry): assertion = EntriesHelper.get_assertion("count(instance('groups')/groups/group) = 1", 'case_sharing.exactly_one_group') entry.assertions.append(assertion) @staticmethod def get_auto_select_assertions(case_id_xpath, mode, locale_arguments=None): case_count = CaseIDXPath(case_id_xpath).case().count() return [ EntriesHelper.get_assertion( "{0} = 1".format(case_id_xpath.count()), 'case_autoload.{0}.property_missing'.format(mode), locale_arguments ), EntriesHelper.get_assertion( "{0} = 1".format(case_count), 'case_autoload.{0}.case_missing'.format(mode), ) ] @staticmethod def get_extra_case_id_datums(form): datums = [] actions = form.active_actions() if form.form_type == 'module_form' and actions_use_usercase(actions): case = UserCaseXPath().case() datums.append(FormDatumMeta( datum=SessionDatum(id=USERCASE_ID, function=('%s/@case_id' % case)), case_type=USERCASE_TYPE, requires_selection=False, action=None # Unused (and could be actions['usercase_update'] or actions['usercase_preload']) )) return datums
def get_module_contributions(self, module): if module_offers_search(module): domain = self.app.domain details_helper = DetailsHelper(self.app) remote_request = RemoteRequest( post=RemoteRequestPost( url=absolute_reverse('claim_case', args=[domain]), relevant=module.search_config.relevant, data=[ QueryData( key='case_id', ref=QuerySessionXPath('case_id').instance(), # e.g. instance('querysession')/session/data/case_id ), ]), command=Command( id=id_strings.search_command(module), display=Display(text=Text( locale_id=id_strings.case_search_locale(module)), ), ), instances=[ Instance(id=SESSION_INSTANCE, src='jr://instance/session'), Instance(id='casedb', src='jr://instance/casedb'), ], session=RemoteRequestSession( queries=[ RemoteRequestQuery( url=absolute_reverse('remote_search', args=[domain]), storage_instance=RESULTS_INSTANCE, data=[ QueryData(key='case_type', ref="'{}'".format(module.case_type)), ], prompts=[ QueryPrompt( key=p.name, display=Display( text=Text(locale_id=id_strings. search_property_locale( module, p.name)), ), ) for p in module.search_config.properties ]) ], data=[ SessionDatum( id='case_id', nodeset=(CaseTypeXpath(module.case_type).case( instance_name=RESULTS_INSTANCE).select( u'@status', u'open', quote=True)), value='./@case_id', detail_select=details_helper.get_detail_id_safe( module, 'case_short'), detail_confirm=details_helper.get_detail_id_safe( module, 'case_long'), ) ], ), stack=Stack(), ) frame = CreateFrame() # Open first form in module frame.add_command(XPath.string(id_strings.menu_id(module))) frame.add_datum( StackDatum(id='case_id', value=QuerySessionXPath('case_id').instance())) remote_request.stack.add_frame(frame) return [remote_request] return []
class EntriesHelper(object): def __init__(self, app, modules=None): from corehq.apps.app_manager.suite_xml.sections.details import DetailsHelper self.app = app self.modules = modules or list(app.get_modules()) self.details_helper = DetailsHelper(self.app, self.modules) def get_datums_meta_for_form_generic(self, form, module=None): if not module: module = form.get_module() if form.form_type == "module_form": datums_meta = self.get_case_datums_basic_module(module, form) elif form.form_type == "advanced_form": datums_meta, _ = self.get_datum_meta_assertions_advanced(module, form) datums_meta.extend(EntriesHelper.get_new_case_id_datums_meta(form)) else: raise SuiteError( "Unexpected form type '{}' with a case list form: {}".format(form.form_type, form.unique_id) ) return datums_meta @staticmethod def get_filter_xpath(module, delegation=False): filter = module.case_details.short.filter if filter: xpath = "[%s]" % filter else: xpath = "" if delegation: xpath += "[index/parent/@case_type = '%s']" % module.case_type xpath += "[start_date = '' or double(date(start_date)) <= double(now())]" return xpath @staticmethod def get_nodeset_xpath(case_type, filter_xpath=""): return "instance('casedb')/casedb/case[@case_type='{case_type}'][@status='open']{filter_xpath}".format( case_type=case_type, filter_xpath=filter_xpath ) @staticmethod def get_parent_filter(relationship, parent_id): return "[index/{relationship}=instance('commcaresession')/session/data/{parent_id}]".format( relationship=relationship, parent_id=parent_id ) @staticmethod def get_userdata_autoselect(key, session_id, mode): base_xpath = session_var("data", path="user") xpath = session_var(key, path="user/data") protected_xpath = XPath.if_( XPath.and_(base_xpath.count().eq(1), xpath.count().eq(1)), xpath, XPath.empty_string() ) datum = SessionDatum(id=session_id, function=protected_xpath) assertions = [ EntriesHelper.get_assertion( XPath.and_(base_xpath.count().eq(1), xpath.count().eq(1)), "case_autoload.{0}.property_missing".format(mode), [key], ), EntriesHelper.get_assertion( CaseIDXPath(xpath).case().count().eq(1), "case_autoload.{0}.case_missing".format(mode) ), ] return datum, assertions def entry_for_module(self, module): # avoid circular dependency from corehq.apps.app_manager.models import Module, AdvancedModule results = [] for form in module.get_suite_forms(): e = Entry() e.form = form.xmlns # Ideally all of this version check should happen in Command/Display class if self.app.enable_localized_menu_media: e.command = LocalizedCommand( id=id_strings.form_command(form, module), menu_locale_id=id_strings.form_locale(form), media_image=bool(len(form.all_image_paths())), media_audio=bool(len(form.all_audio_paths())), image_locale_id=id_strings.form_icon_locale(form), audio_locale_id=id_strings.form_audio_locale(form), ) else: e.command = Command( id=id_strings.form_command(form, module), locale_id=id_strings.form_locale(form), media_image=form.default_media_image, media_audio=form.default_media_audio, ) config_entry = { "module_form": self.configure_entry_module_form, "advanced_form": self.configure_entry_advanced_form, "careplan_form": self.configure_entry_careplan_form, }[form.form_type] config_entry(module, e, form) if self.app.commtrack_enabled and session_var("supply_point_id") in getattr(form, "source", ""): from corehq.apps.app_manager.models import AUTO_SELECT_LOCATION datum, assertions = EntriesHelper.get_userdata_autoselect( "commtrack-supply-point", "supply_point_id", AUTO_SELECT_LOCATION ) e.datums.append(datum) e.assertions.extend(assertions) results.append(e) if hasattr(module, "case_list") and module.case_list.show: e = Entry() if self.app.enable_localized_menu_media: e.command = LocalizedCommand( id=id_strings.case_list_command(module), menu_locale_id=id_strings.case_list_locale(module), media_image=bool(len(module.case_list.all_image_paths())), media_audio=bool(len(module.case_list.all_audio_paths())), image_locale_id=id_strings.case_list_icon_locale(module), audio_locale_id=id_strings.case_list_audio_locale(module), ) else: e.command = Command( id=id_strings.case_list_command(module), locale_id=id_strings.case_list_locale(module), media_image=module.case_list.default_media_image, media_audio=module.case_list.default_media_audio, ) if isinstance(module, Module): for datum_meta in self.get_datum_meta_module(module, use_filter=False): e.datums.append(datum_meta.datum) elif isinstance(module, AdvancedModule): detail_persistent, detail_inline = self.get_case_tile_datum_attrs(module, module) e.datums.append( SessionDatum( id="case_id_case_%s" % module.case_type, nodeset=(EntriesHelper.get_nodeset_xpath(module.case_type)), value="./@case_id", detail_select=self.details_helper.get_detail_id_safe(module, "case_short"), detail_confirm=self.details_helper.get_detail_id_safe(module, "case_long"), detail_persistent=detail_persistent, detail_inline=detail_inline, ) ) if self.app.commtrack_enabled: e.datums.append( SessionDatum( id="product_id", nodeset=ProductInstanceXpath().instance(), value="./@id", detail_select=self.details_helper.get_detail_id_safe(module, "product_short"), ) ) results.append(e) for entry in module.get_custom_entries(): results.append(entry) return results @staticmethod def get_assertion(test, locale_id, locale_arguments=None): assertion = Assertion(test=test) text = Text(locale_id=locale_id) if locale_arguments: locale = text.locale for arg in locale_arguments: locale.arguments.append(LocaleArgument(value=arg)) assertion.text.append(text) return assertion @staticmethod def add_case_sharing_assertion(entry): assertion = EntriesHelper.get_assertion( "count(instance('groups')/groups/group) = 1", "case_sharing.exactly_one_group" ) entry.assertions.append(assertion) @staticmethod def get_auto_select_assertions(case_id_xpath, mode, locale_arguments=None): case_count = CaseIDXPath(case_id_xpath).case().count() return [ EntriesHelper.get_assertion( "{0} = 1".format(case_id_xpath.count()), "case_autoload.{0}.property_missing".format(mode), locale_arguments, ), EntriesHelper.get_assertion("{0} = 1".format(case_count), "case_autoload.{0}.case_missing".format(mode)), ] @staticmethod def get_extra_case_id_datums(form): datums = [] actions = form.active_actions() if form.form_type == "module_form" and actions_use_usercase(actions): case = UserCaseXPath().case() datums.append( FormDatumMeta( datum=SessionDatum(id=USERCASE_ID, function=("%s/@case_id" % case)), case_type=USERCASE_TYPE, requires_selection=False, action=None, # Unused (and could be actions['usercase_update'] or actions['usercase_preload']) ) ) return datums @staticmethod def any_usercase_datums(datums): return any(d.case_type == USERCASE_TYPE for d in datums) @staticmethod def get_new_case_id_datums_meta(form): if not form: return [] datums = [] if form.form_type == "module_form": actions = form.active_actions() if "open_case" in actions: datums.append( FormDatumMeta( datum=SessionDatum(id=form.session_var_for_action("open_case"), function="uuid()"), case_type=form.get_module().case_type, requires_selection=False, action=actions["open_case"], ) ) if "subcases" in actions: for subcase in actions["subcases"]: # don't put this in the loop to be consistent with the form's indexing # see XForm.create_casexml_2 if not subcase.repeat_context: datums.append( FormDatumMeta( datum=SessionDatum(id=form.session_var_for_action(subcase), function="uuid()"), case_type=subcase.case_type, requires_selection=False, action=subcase, ) ) elif form.form_type == "advanced_form": for action in form.actions.get_open_actions(): if not action.repeat_context: datums.append( FormDatumMeta( datum=SessionDatum(id=action.case_session_var, function="uuid()"), case_type=action.case_type, requires_selection=False, action=action, ) ) return datums def get_case_datums_basic_module(self, module, form): datums = [] if not form or form.requires_case(): datums.extend(self.get_datum_meta_module(module, use_filter=True)) datums.extend(EntriesHelper.get_new_case_id_datums_meta(form)) datums.extend(EntriesHelper.get_extra_case_id_datums(form)) self.add_parent_datums(datums, module) return datums def configure_entry_module_form(self, module, e, form=None, use_filter=True, **kwargs): def case_sharing_requires_assertion(form): actions = form.active_actions() if "open_case" in actions and autoset_owner_id_for_open_case(actions): return True if "subcases" in actions: for subcase in actions["subcases"]: if autoset_owner_id_for_subcase(subcase): return True return False datums = self.get_case_datums_basic_module(module, form) for datum in datums: e.datums.append(datum.datum) if form and self.app.case_sharing and case_sharing_requires_assertion(form): EntriesHelper.add_case_sharing_assertion(e) def get_datum_meta_module(self, module, use_filter=False): datums = [] datum_module = module.source_module if module.module_type == "shadow" else module datums_meta = get_select_chain_meta(self.app, datum_module) for i, datum in enumerate(datums_meta): # get the session var for the previous datum if there is one parent_id = datums_meta[i - 1]["session_var"] if i >= 1 else "" if parent_id: parent_filter = EntriesHelper.get_parent_filter(datum["module"].parent_select.relationship, parent_id) else: parent_filter = "" detail_module = module if module.module_type == "shadow" else datum["module"] detail_persistent, detail_inline = self.get_case_tile_datum_attrs(datum["module"], detail_module) fixture_select_filter = "" if datum["module"].fixture_select.active: datums.append( FormDatumMeta( datum=SessionDatum( id=id_strings.fixture_session_var(datum["module"]), nodeset=ItemListFixtureXpath(datum["module"].fixture_select.fixture_type).instance(), value=datum["module"].fixture_select.variable_column, detail_select=id_strings.fixture_detail(detail_module), ), case_type=None, requires_selection=True, action="fixture_select", ) ) filter_xpath_template = datum["module"].fixture_select.xpath fixture_value = session_var(id_strings.fixture_session_var(datum["module"])) fixture_select_filter = "[{}]".format(filter_xpath_template.replace("$fixture_value", fixture_value)) filter_xpath = EntriesHelper.get_filter_xpath(module) if use_filter else "" datums.append( FormDatumMeta( datum=SessionDatum( id=datum["session_var"], nodeset=( EntriesHelper.get_nodeset_xpath(datum["case_type"], filter_xpath=filter_xpath) + parent_filter + fixture_select_filter ), value="./@case_id", detail_select=self.details_helper.get_detail_id_safe(detail_module, "case_short"), detail_confirm=( self.details_helper.get_detail_id_safe(detail_module, "case_long") if datum["index"] == 0 and not detail_inline else None ), detail_persistent=detail_persistent, detail_inline=detail_inline, ), case_type=datum["case_type"], requires_selection=True, action="update_case", ) ) return datums @staticmethod def get_auto_select_datums_and_assertions(action, auto_select, form): from corehq.apps.app_manager.models import ( AUTO_SELECT_USER, AUTO_SELECT_CASE, AUTO_SELECT_FIXTURE, AUTO_SELECT_RAW, AUTO_SELECT_USERCASE, ) if auto_select.mode == AUTO_SELECT_USER: return EntriesHelper.get_userdata_autoselect( auto_select.value_key, action.case_session_var, auto_select.mode ) elif auto_select.mode == AUTO_SELECT_CASE: try: ref = form.actions.actions_meta_by_tag[auto_select.value_source]["action"] sess_var = ref.case_session_var except KeyError: raise ValueError("Case tag not found: %s" % auto_select.value_source) xpath = CaseIDXPath(session_var(sess_var)).case().index_id(auto_select.value_key) assertions = EntriesHelper.get_auto_select_assertions(xpath, auto_select.mode, [auto_select.value_key]) return SessionDatum(id=action.case_session_var, function=xpath), assertions elif auto_select.mode == AUTO_SELECT_FIXTURE: xpath_base = ItemListFixtureXpath(auto_select.value_source).instance() xpath = xpath_base.slash(auto_select.value_key) fixture_assertion = EntriesHelper.get_assertion( "{0} = 1".format(xpath_base.count()), "case_autoload.{0}.exactly_one_fixture".format(auto_select.mode), [auto_select.value_source], ) assertions = EntriesHelper.get_auto_select_assertions(xpath, auto_select.mode, [auto_select.value_key]) return SessionDatum(id=action.case_session_var, function=xpath), [fixture_assertion] + assertions elif auto_select.mode == AUTO_SELECT_RAW: case_id_xpath = auto_select.value_key case_count = CaseIDXPath(case_id_xpath).case().count() return ( SessionDatum(id=action.case_session_var, function=case_id_xpath), [ EntriesHelper.get_assertion( "{0} = 1".format(case_count), "case_autoload.{0}.case_missing".format(auto_select.mode) ) ], ) elif auto_select.mode == AUTO_SELECT_USERCASE: case = UserCaseXPath().case() return ( SessionDatum(id=action.case_session_var, function=case.slash("@case_id")), [ EntriesHelper.get_assertion( "{0} = 1".format(case.count()), "case_autoload.{0}.case_missing".format(auto_select.mode) ) ], ) def configure_entry_advanced_form(self, module, e, form, **kwargs): def case_sharing_requires_assertion(form): actions = form.actions.open_cases for action in actions: if "owner_id" in action.case_properties: return True return False datums, assertions = self.get_datum_meta_assertions_advanced(module, form) datums.extend(EntriesHelper.get_new_case_id_datums_meta(form)) for datum_meta in datums: e.datums.append(datum_meta.datum) # assertions come after session e.assertions.extend(assertions) if self.app.case_sharing and case_sharing_requires_assertion(form): EntriesHelper.add_case_sharing_assertion(e) def get_datum_meta_assertions_advanced(self, module, form): def get_target_module(case_type, module_id, with_product_details=False): if module_id: if module_id == module.unique_id: return module from corehq.apps.app_manager.models import ModuleNotFoundException try: target = module.get_app().get_module_by_unique_id(module_id) if target.case_type != case_type: raise ParentModuleReferenceError("Module with ID %s has incorrect case type" % module_id) if with_product_details and not hasattr(target, "product_details"): raise ParentModuleReferenceError( "Module with ID %s has no product details configuration" % module_id ) return target except ModuleNotFoundException as ex: raise ParentModuleReferenceError(ex.message) else: if case_type == module.case_type: return module target_modules = [ mod for mod in module.get_app().modules if mod.case_type == case_type and (not with_product_details or hasattr(mod, "product_details")) ] try: return target_modules[0] except IndexError: raise ParentModuleReferenceError( "Module with case type %s in app %s not found" % (case_type, self.app) ) def get_manual_datum(action_, parent_filter_=""): target_module_ = get_target_module(action_.case_type, action_.details_module) referenced_by = form.actions.actions_meta_by_parent_tag.get(action_.case_tag) filter_xpath = EntriesHelper.get_filter_xpath(target_module_) detail_persistent, detail_inline = self.get_case_tile_datum_attrs(target_module_, target_module_) return SessionDatum( id=action_.case_session_var, nodeset=( EntriesHelper.get_nodeset_xpath(action_.case_type, filter_xpath=filter_xpath) + parent_filter_ ), value="./@case_id", detail_select=self.details_helper.get_detail_id_safe(target_module_, "case_short"), detail_confirm=( self.details_helper.get_detail_id_safe(target_module_, "case_long") if not referenced_by or referenced_by["type"] != "load" else None ), detail_persistent=detail_persistent, detail_inline=detail_inline, ) datums = [] assertions = [] for action in form.actions.get_load_update_actions(): auto_select = action.auto_select if auto_select and auto_select.mode: datum, assertions = EntriesHelper.get_auto_select_datums_and_assertions(action, auto_select, form) datums.append(FormDatumMeta(datum=datum, case_type=None, requires_selection=False, action=action)) else: if action.case_index.tag: parent_action = form.actions.actions_meta_by_tag[action.case_index.tag]["action"] parent_filter = EntriesHelper.get_parent_filter( action.case_index.reference_id, parent_action.case_session_var ) else: parent_filter = "" datums.append( FormDatumMeta( datum=get_manual_datum(action, parent_filter), case_type=action.case_type, requires_selection=True, action=action, ) ) if module.get_app().commtrack_enabled: try: last_action = list(form.actions.get_load_update_actions())[-1] if last_action.show_product_stock: nodeset = ProductInstanceXpath().instance() if last_action.product_program: nodeset = nodeset.select("program_id", last_action.product_program) target_module = get_target_module(last_action.case_type, last_action.details_module, True) datums.append( FormDatumMeta( datum=SessionDatum( id="product_id", nodeset=nodeset, value="./@id", detail_select=self.details_helper.get_detail_id_safe(target_module, "product_short"), ), case_type=None, requires_selection=True, action=None, ) ) except IndexError: pass self.add_parent_datums(datums, module) return datums, assertions def add_parent_datums(self, datums, module): def update_refs(datum_meta, changed_ids_): """ Update references in the nodeset of the given datum, if necessary e.g. "instance('casedb')/casedb/case[@case_type='guppy'] [@status='open'] [index/parent=instance('commcaresession')/session/data/parent_id]" is updated to "instance('casedb')/casedb/case[@case_type='guppy'] [@status='open'] [index/parent=instance('commcaresession')/session/data/case_id]" ^^^^^^^ because the case referred to by "parent_id" in the child module has the ID "case_id" in the parent module. """ def _apply_change_to_datum_attr(datum, attr, change): xpath = getattr(datum, attr, None) if xpath: old = session_var(change["old_id"]) new = session_var(change["new_id"]) setattr(datum, attr, xpath.replace(old, new)) datum = datum_meta.datum action = datum_meta.action if action: if hasattr(action, "case_indices"): # This is an advanced module for case_index in action.case_indices: if case_index.tag in changed_ids_: # update any reference to previously changed datums for change in changed_ids_[case_index.tag]: _apply_change_to_datum_attr(datum, "nodeset", change) _apply_change_to_datum_attr(datum, "function", change) else: if "basic" in changed_ids_: for change in changed_ids_["basic"]: _apply_change_to_datum_attr(datum, "nodeset", change) _apply_change_to_datum_attr(datum, "function", change) def rename_other_id(this_datum_meta_, parent_datum_meta_, datum_ids_): """ If the ID of parent datum matches the ID of another datum in this form, rename the ID of the other datum in this form e.g. if parent datum ID == "case_id" and there is a datum in this form with the ID of "case_id" too, then rename the ID of the datum in this form to "case_id_<case_type>" (where <case_type> is the case type of the datum in this form). """ changed_id = {} parent_datum = parent_datum_meta_.datum action = this_datum_meta_.action if action: if parent_datum.id in datum_ids_: datum = datum_ids_[parent_datum.id] new_id = "_".join((datum.datum.id, datum.case_type)) # Only advanced module actions have a case_tag attribute. case_tag = getattr(action, "case_tag", "basic") changed_id = {case_tag: {"old_id": datum.datum.id, "new_id": new_id}} datum.datum.id = new_id return changed_id def get_changed_id(this_datum_meta_, parent_datum_meta_): """ Maps IDs in the child module to IDs in the parent module e.g. The case with the ID "parent_id" in the child module has the ID "case_id" in the parent module. """ changed_id = {} action = this_datum_meta_.action if action: case_tag = getattr(action, "case_tag", "basic") changed_id = {case_tag: {"old_id": this_datum_meta_.datum.id, "new_id": parent_datum_meta_.datum.id}} return changed_id def get_datums(module_): """ Return the datums of the first form in the given module """ datums_ = [] if module_: try: # assume that all forms in the module have the same case management form = module_.get_form(0) except FormNotFoundException: pass else: datums_.extend(self.get_datums_meta_for_form_generic(form)) return datums_ def append_update(dict_, new_dict): for key in new_dict: dict_[key].append(new_dict[key]) parent_datums = get_datums(module.root_module) if parent_datums: # we need to try and match the datums to the root module so that # the navigation on the phone works correctly # 1. Add in any datums that don't require user selection e.g. new case IDs # 2. Match the datum ID for datums that appear in the same position and # will be loading the same case type # see advanced_app_features#child-modules in docs datum_ids = {d.datum.id: d for d in datums} index = 0 changed_ids_by_case_tag = defaultdict(list) for this_datum_meta, parent_datum_meta in list(izip_longest(datums, parent_datums)): if not this_datum_meta: continue update_refs(this_datum_meta, changed_ids_by_case_tag) if not parent_datum_meta: continue if this_datum_meta.datum.id != parent_datum_meta.datum.id: if not parent_datum_meta.requires_selection: # Add parent datums of opened subcases and automatically-selected cases datums.insert(index, parent_datum_meta) elif this_datum_meta.case_type == parent_datum_meta.case_type: append_update( changed_ids_by_case_tag, rename_other_id(this_datum_meta, parent_datum_meta, datum_ids) ) append_update(changed_ids_by_case_tag, get_changed_id(this_datum_meta, parent_datum_meta)) this_datum_meta.datum.id = parent_datum_meta.datum.id index += 1 def configure_entry_careplan_form(self, module, e, form=None, **kwargs): parent_module = self.app.get_module_by_unique_id(module.parent_select.module_id) e.datums.append( SessionDatum( id="case_id", nodeset=EntriesHelper.get_nodeset_xpath(parent_module.case_type), value="./@case_id", detail_select=self.details_helper.get_detail_id_safe(parent_module, "case_short"), detail_confirm=self.details_helper.get_detail_id_safe(parent_module, "case_long"), ) ) def session_datum(datum_id, case_type, parent_ref, parent_val): nodeset = ( CaseTypeXpath(case_type) .case() .select("index/%s" % parent_ref, session_var(parent_val), quote=False) .select("@status", "open") ) return SessionDatum( id=datum_id, nodeset=nodeset, value="./@case_id", detail_select=self.details_helper.get_detail_id_safe(module, "%s_short" % case_type), detail_confirm=self.details_helper.get_detail_id_safe(module, "%s_long" % case_type), ) e.stack = Stack() frame = CreateFrame() e.stack.add_frame(frame) if form.case_type == CAREPLAN_GOAL: if form.mode == "create": new_goal_id_var = "case_id_goal_new" e.datums.append(SessionDatum(id=new_goal_id_var, function="uuid()")) elif form.mode == "update": new_goal_id_var = "case_id_goal" e.datums.append(session_datum(new_goal_id_var, CAREPLAN_GOAL, "parent", "case_id")) if not module.display_separately: open_goal = CaseIDXPath(session_var(new_goal_id_var)).case().select("@status", "open") frame.if_clause = "{count} = 1".format(count=open_goal.count()) frame.add_command(XPath.string(id_strings.menu_id(parent_module))) frame.add_datum(StackDatum(id="case_id", value=session_var("case_id"))) frame.add_command(XPath.string(id_strings.menu_id(module))) frame.add_datum(StackDatum(id="case_id_goal", value=session_var(new_goal_id_var))) else: frame.add_command(XPath.string(id_strings.menu_id(module))) frame.add_datum(StackDatum(id="case_id", value=session_var("case_id"))) elif form.case_type == CAREPLAN_TASK: if not module.display_separately: frame.add_command(XPath.string(id_strings.menu_id(parent_module))) frame.add_datum(StackDatum(id="case_id", value=session_var("case_id"))) frame.add_command(XPath.string(id_strings.menu_id(module))) frame.add_datum(StackDatum(id="case_id_goal", value=session_var("case_id_goal"))) if form.mode == "update": count = ( CaseTypeXpath(CAREPLAN_TASK) .case() .select("index/goal", session_var("case_id_goal"), quote=False) .select("@status", "open") .count() ) frame.if_clause = "{count} >= 1".format(count=count) frame.add_command( XPath.string(id_strings.form_command(module.get_form_by_type(CAREPLAN_TASK, "update"))) ) else: frame.add_command(XPath.string(id_strings.menu_id(module))) frame.add_datum(StackDatum(id="case_id", value=session_var("case_id"))) if form.mode == "create": e.datums.append(session_datum("case_id_goal", CAREPLAN_GOAL, "parent", "case_id")) elif form.mode == "update": e.datums.append(session_datum("case_id_goal", CAREPLAN_GOAL, "parent", "case_id")) e.datums.append(session_datum("case_id_task", CAREPLAN_TASK, "goal", "case_id_goal")) def get_case_tile_datum_attrs(self, module, detail_module): detail_persistent = None detail_inline = None for detail_type, detail, enabled in module.get_details(): if detail.persist_tile_on_forms and (detail.use_case_tiles or detail.custom_xml) and enabled: detail_persistent = id_strings.detail(detail_module, detail_type) if detail.pull_down_tile: detail_inline = self.details_helper.get_detail_id_safe(detail_module, "case_long") break return detail_persistent, detail_inline
def _details_helper(self): return DetailsHelper(self.app)
class EntriesHelper(object): def __init__(self, app, modules=None): from corehq.apps.app_manager.suite_xml.sections.details import DetailsHelper self.app = app self.modules = modules or list(app.get_modules()) self.details_helper = DetailsHelper(self.app, self.modules) def get_datums_meta_for_form_generic(self, form, module=None): if not module: module = form.get_module() if form.form_type == 'module_form': datums_meta = self.get_case_datums_basic_module(module, form) elif form.form_type == 'advanced_form' or form.form_type == "shadow_form": datums_meta, _ = self.get_datum_meta_assertions_advanced(module, form) datums_meta.extend(EntriesHelper.get_new_case_id_datums_meta(form)) else: raise SuiteValidationError("Unexpected form type '{}' with a case list form: {}".format( form.form_type, form.unique_id )) return datums_meta @staticmethod def get_filter_xpath(module, delegation=False): filter = module.case_details.short.filter if filter: xpath = '[%s]' % interpolate_xpath(filter) else: xpath = '' if delegation: xpath += "[index/parent/@case_type = '%s']" % module.case_type xpath += "[start_date = '' or double(date(start_date)) <= double(now())]" return xpath @staticmethod def get_nodeset_xpath(case_type, filter_xpath=''): return "instance('casedb')/casedb/case[@case_type='{case_type}'][@status='open']{filter_xpath}".format( case_type=case_type, filter_xpath=filter_xpath, ) @staticmethod def get_parent_filter(relationship, parent_id): return "[index/{relationship}=instance('commcaresession')/session/data/{parent_id}]".format( relationship=relationship, parent_id=parent_id, ) @staticmethod def get_userdata_autoselect(key, session_id, mode): base_xpath = session_var('data', path='user') xpath = session_var(key, path='user/data') protected_xpath = XPath.if_( XPath.and_(base_xpath.count().eq(1), xpath.count().eq(1)), xpath, XPath.empty_string(), ) datum = SessionDatum(id=session_id, function=protected_xpath) assertions = [ EntriesHelper.get_assertion( XPath.and_(base_xpath.count().eq(1), xpath.count().eq(1)), 'case_autoload.{0}.property_missing'.format(mode), [key], ), EntriesHelper.get_assertion( CaseIDXPath(xpath).case().count().eq(1), 'case_autoload.{0}.case_missing'.format(mode), ) ] return datum, assertions def entry_for_module(self, module): # avoid circular dependency from corehq.apps.app_manager.models import Module, AdvancedModule results = [] for form in module.get_suite_forms(): e = Entry() e.form = form.xmlns # Ideally all of this version check should happen in Command/Display class if self.app.enable_localized_menu_media: form_custom_icon = form.custom_icon e.command = LocalizedCommand( id=id_strings.form_command(form, module), menu_locale_id=id_strings.form_locale(form), media_image=bool(len(form.all_image_paths())), media_audio=bool(len(form.all_audio_paths())), image_locale_id=id_strings.form_icon_locale(form), audio_locale_id=id_strings.form_audio_locale(form), custom_icon_locale_id=(id_strings.form_custom_icon_locale(form, form_custom_icon.form) if form_custom_icon and not form_custom_icon.xpath else None), custom_icon_form=(form_custom_icon.form if form_custom_icon else None), custom_icon_xpath=(form_custom_icon.xpath if form_custom_icon and form_custom_icon.xpath else None), ) else: e.command = Command( id=id_strings.form_command(form, module), locale_id=id_strings.form_locale(form), media_image=form.default_media_image, media_audio=form.default_media_audio, ) config_entry = { 'module_form': self.configure_entry_module_form, 'advanced_form': self.configure_entry_advanced_form, 'shadow_form': self.configure_entry_advanced_form, }[form.form_type] config_entry(module, e, form) if form.uses_usercase(): EntriesHelper.add_usercase_id_assertion(e) EntriesHelper.add_custom_assertions(e, form) if ( self.app.commtrack_enabled and session_var('supply_point_id') in getattr(form, 'source', "") ): from corehq.apps.app_manager.models import AUTO_SELECT_LOCATION datum, assertions = EntriesHelper.get_userdata_autoselect( 'commtrack-supply-point', 'supply_point_id', AUTO_SELECT_LOCATION, ) e.datums.append(datum) e.assertions.extend(assertions) results.append(e) if hasattr(module, 'case_list') and module.case_list.show: e = Entry() if self.app.enable_localized_menu_media: e.command = LocalizedCommand( id=id_strings.case_list_command(module), menu_locale_id=id_strings.case_list_locale(module), media_image=bool(len(module.case_list.all_image_paths())), media_audio=bool(len(module.case_list.all_audio_paths())), image_locale_id=id_strings.case_list_icon_locale(module), audio_locale_id=id_strings.case_list_audio_locale(module), ) else: e.command = Command( id=id_strings.case_list_command(module), locale_id=id_strings.case_list_locale(module), media_image=module.case_list.default_media_image, media_audio=module.case_list.default_media_audio, ) if isinstance(module, Module): for datum_meta in self.get_case_datums_basic_module(module): e.datums.append(datum_meta.datum) elif isinstance(module, AdvancedModule): detail_inline = self.get_detail_inline_attr(module, module, "case_short") detail_confirm = None if not detail_inline: detail_confirm = self.details_helper.get_detail_id_safe(module, 'case_long') e.datums.append(SessionDatum( id='case_id_case_%s' % module.case_type, nodeset=(EntriesHelper.get_nodeset_xpath(module.case_type)), value="./@case_id", detail_select=self.details_helper.get_detail_id_safe(module, 'case_short'), detail_confirm=detail_confirm, detail_persistent=self.get_detail_persistent_attr(module, module, "case_short"), detail_inline=detail_inline, autoselect=module.auto_select_case, )) if self.app.commtrack_enabled: e.datums.append(SessionDatum( id='product_id', nodeset=ProductInstanceXpath().instance(), value="./@id", detail_select=self.details_helper.get_detail_id_safe(module, 'product_short') )) results.append(e) for entry in module.get_custom_entries(): results.append(entry) return results @staticmethod def get_assertion(test, locale_id, locale_arguments=None): assertion = Assertion(test=test) text = Text(locale_id=locale_id) if locale_arguments: locale = text.locale for arg in locale_arguments: locale.arguments.append(LocaleArgument(value=arg)) assertion.text.append(text) return assertion @staticmethod def add_case_sharing_assertion(entry): assertion = EntriesHelper.get_assertion("count(instance('groups')/groups/group) = 1", 'case_sharing.exactly_one_group') entry.assertions.append(assertion) @staticmethod def get_auto_select_assertions(case_id_xpath, mode, locale_arguments=None): case_count = CaseIDXPath(case_id_xpath).case().count() return [ EntriesHelper.get_assertion( "{0} = 1".format(case_id_xpath.count()), 'case_autoload.{0}.property_missing'.format(mode), locale_arguments ), EntriesHelper.get_assertion( "{0} = 1".format(case_count), 'case_autoload.{0}.case_missing'.format(mode), ) ] @staticmethod def add_custom_assertions(entry, form): for id, assertion in enumerate(form.custom_assertions): locale_id = id_strings.custom_assertion_locale(form.get_module(), form, id) entry.assertions.append(EntriesHelper.get_assertion(assertion.test, locale_id)) @staticmethod def add_usercase_id_assertion(entry): assertion = EntriesHelper.get_assertion("count(instance('casedb')/casedb/case[@case_type='commcare-user']" "[hq_user_id=instance('commcaresession')/session/context/userid])" " = 1", "case_autoload.usercase.case_missing") entry.assertions.append(assertion) @staticmethod def get_extra_case_id_datums(form): datums = [] actions = form.active_actions() if form.form_type == 'module_form' and actions_use_usercase(actions): case = UserCaseXPath().case() datums.append(FormDatumMeta( datum=SessionDatum(id=USERCASE_ID, function=('%s/@case_id' % case)), case_type=USERCASE_TYPE, requires_selection=False, action=None # Unused (and could be actions['usercase_update'] or actions['usercase_preload']) )) return datums @staticmethod def any_usercase_datums(datums): return any(d.case_type == USERCASE_TYPE for d in datums) @staticmethod def get_new_case_id_datums_meta(form): if not form: return [] datums = [] if form.form_type == 'module_form': actions = form.active_actions() if 'open_case' in actions: datums.append(FormDatumMeta( datum=SessionDatum(id=form.session_var_for_action('open_case'), function='uuid()'), case_type=form.get_module().case_type, requires_selection=False, action=actions['open_case'] )) if 'subcases' in actions: for subcase in actions['subcases']: # don't put this in the loop to be consistent with the form's indexing # see XForm._create_casexml_2 if not subcase.repeat_context: datums.append(FormDatumMeta( datum=SessionDatum( id=form.session_var_for_action(subcase), function='uuid()' ), case_type=subcase.case_type, requires_selection=False, action=subcase )) elif form.form_type == 'advanced_form' or form.form_type == "shadow_form": for action in form.actions.get_open_actions(): if not action.repeat_context: datums.append(FormDatumMeta( datum=SessionDatum(id=action.case_session_var, function='uuid()'), case_type=action.case_type, requires_selection=False, action=action )) return datums def get_case_datums_basic_module(self, module, form=None): datums = [] if not form or form.requires_case(): datums.extend(self.get_datum_meta_module(module, use_filter=True)) if form: datums.extend(EntriesHelper.get_new_case_id_datums_meta(form)) datums.extend(EntriesHelper.get_extra_case_id_datums(form)) self.add_parent_datums(datums, module) return datums def configure_entry_module_form(self, module, e, form=None, use_filter=True, **kwargs): def case_sharing_requires_assertion(form): actions = form.active_actions() if 'open_case' in actions and autoset_owner_id_for_open_case(actions): return True if 'subcases' in actions: for subcase in actions['subcases']: if autoset_owner_id_for_subcase(subcase): return True return False datums = self.get_case_datums_basic_module(module, form) for datum in datums: e.datums.append(datum.datum) if form and self.app.case_sharing and case_sharing_requires_assertion(form): EntriesHelper.add_case_sharing_assertion(e) def get_datum_meta_module(self, module, use_filter=False): datums = [] datum_module = module.source_module if module.module_type == 'shadow' else module datums_meta = get_select_chain_meta(self.app, datum_module) for i, datum in enumerate(datums_meta): # get the session var for the previous datum if there is one parent_id = datums_meta[i - 1]['session_var'] if i >= 1 else '' if parent_id: parent_filter = EntriesHelper.get_parent_filter( datum['module'].parent_select.relationship, parent_id ) else: parent_filter = '' # Figure out which module will supply the details (select, confirm, etc.) # for this datum. Normally this is the datum's own module. detail_module = datum['module'] # Shadow modules are different because datums_meta is generated based on the source module, # but the various details should be supplied based on the shadow's own configuration. if module.module_type == 'shadow': if datum['module'].unique_id == module.source_module_id: # We're looking at the datum that corresponds to the original module, # so use that module for details detail_module = module else: # Check for case list parent child selection. If both shadow and source use parent case # selection, datums_meta will contain a datum for the parent case, based on the SOURCE's # parent select, and when we see that datum, we need to use the SHADOW's parent select # to supply the details. shadow_active = hasattr(module, 'parent_select') and module.parent_select.active source_active = hasattr(datum_module, 'parent_select') and datum_module.parent_select.active if shadow_active and source_active: if datum['module'].unique_id == datum_module.parent_select.module_id: detail_module = self.app.get_module_by_unique_id(module.parent_select.module_id) detail_persistent = self.get_detail_persistent_attr(datum['module'], detail_module, "case_short") detail_inline = self.get_detail_inline_attr(datum['module'], detail_module, "case_short") fixture_select_filter = '' if datum['module'].fixture_select.active: datums.append(FormDatumMeta( datum=SessionDatum( id=id_strings.fixture_session_var(datum['module']), nodeset=ItemListFixtureXpath(datum['module'].fixture_select.fixture_type).instance(), value=datum['module'].fixture_select.variable_column, detail_select=id_strings.fixture_detail(detail_module) ), case_type=None, requires_selection=True, action='fixture_select' )) filter_xpath_template = datum['module'].fixture_select.xpath fixture_value = session_var(id_strings.fixture_session_var(datum['module'])) fixture_select_filter = "[{}]".format( filter_xpath_template.replace('$fixture_value', fixture_value) ) filter_xpath = EntriesHelper.get_filter_xpath(detail_module) if use_filter else '' datums.append(FormDatumMeta( datum=SessionDatum( id=datum['session_var'], nodeset=(EntriesHelper.get_nodeset_xpath(datum['case_type'], filter_xpath=filter_xpath) + parent_filter + fixture_select_filter), value="./@case_id", detail_select=self.details_helper.get_detail_id_safe(detail_module, 'case_short'), detail_confirm=( self.details_helper.get_detail_id_safe(detail_module, 'case_long') if datum['index'] == 0 and not detail_inline else None ), detail_persistent=detail_persistent, detail_inline=detail_inline, autoselect=datum['module'].auto_select_case, ), case_type=datum['case_type'], requires_selection=True, action='update_case' )) return datums @staticmethod def get_auto_select_datums_and_assertions(action, auto_select, form): from corehq.apps.app_manager.models import AUTO_SELECT_USER, AUTO_SELECT_CASE, \ AUTO_SELECT_FIXTURE, AUTO_SELECT_RAW, AUTO_SELECT_USERCASE if auto_select.mode == AUTO_SELECT_USER: return EntriesHelper.get_userdata_autoselect( auto_select.value_key, action.case_session_var, auto_select.mode, ) elif auto_select.mode == AUTO_SELECT_CASE: try: ref = form.actions.actions_meta_by_tag[auto_select.value_source]['action'] sess_var = ref.case_session_var except KeyError: raise ValueError("Case tag not found: %s" % auto_select.value_source) xpath = CaseIDXPath(session_var(sess_var)).case().index_id(auto_select.value_key) assertions = EntriesHelper.get_auto_select_assertions(xpath, auto_select.mode, [auto_select.value_key]) return SessionDatum( id=action.case_session_var, function=xpath ), assertions elif auto_select.mode == AUTO_SELECT_FIXTURE: xpath_base = ItemListFixtureXpath(auto_select.value_source).instance() xpath = xpath_base.slash(auto_select.value_key) fixture_assertion = EntriesHelper.get_assertion( "{0} = 1".format(xpath_base.count()), 'case_autoload.{0}.exactly_one_fixture'.format(auto_select.mode), [auto_select.value_source] ) assertions = EntriesHelper.get_auto_select_assertions(xpath, auto_select.mode, [auto_select.value_key]) return SessionDatum( id=action.case_session_var, function=xpath ), [fixture_assertion] + assertions elif auto_select.mode == AUTO_SELECT_RAW: case_id_xpath = auto_select.value_key case_count = CaseIDXPath(case_id_xpath).case().count() return SessionDatum( id=action.case_session_var, function=case_id_xpath ), [ EntriesHelper.get_assertion( "{0} = 1".format(case_count), 'case_autoload.{0}.case_missing'.format(auto_select.mode) ) ] elif auto_select.mode == AUTO_SELECT_USERCASE: case = UserCaseXPath().case() return SessionDatum( id=action.case_session_var, function=case.slash('@case_id') ), [ EntriesHelper.get_assertion( "{0} = 1".format(case.count()), 'case_autoload.{0}.case_missing'.format(auto_select.mode) ) ] def get_load_case_from_fixture_datums(self, action, target_module, form): datums = [] load_case_from_fixture = action.load_case_from_fixture if (load_case_from_fixture.arbitrary_datum_id and load_case_from_fixture.arbitrary_datum_function): datums.append(FormDatumMeta( SessionDatum( id=load_case_from_fixture.arbitrary_datum_id, function=load_case_from_fixture.arbitrary_datum_function, ), case_type=action.case_type, requires_selection=True, action=action, )) datums.append(FormDatumMeta( datum=SessionDatum( id=load_case_from_fixture.fixture_tag, nodeset=load_case_from_fixture.fixture_nodeset, value=load_case_from_fixture.fixture_variable, detail_select=self.details_helper.get_detail_id_safe(target_module, 'case_short'), detail_confirm=self.details_helper.get_detail_id_safe(target_module, 'case_long'), ), case_type=action.case_type, requires_selection=True, action=action, )) if action.case_tag: if action.case_index.tag: parent_action = form.actions.actions_meta_by_tag[action.case_index.tag]['action'] parent_filter = EntriesHelper.get_parent_filter( action.case_index.reference_id, parent_action.case_session_var ) else: parent_filter = '' session_var_for_fixture = session_var(load_case_from_fixture.fixture_tag) filter_for_casedb = '[{0}={1}]'.format(load_case_from_fixture.case_property, session_var_for_fixture) nodeset = EntriesHelper.get_nodeset_xpath(action.case_type, filter_xpath=filter_for_casedb) nodeset += parent_filter datums.append(FormDatumMeta( datum=SessionDatum( id=action.case_tag, nodeset=nodeset, value="./@case_id", autoselect=load_case_from_fixture.auto_select, ), case_type=action.case_type, requires_selection=False, action=action, )) return datums def configure_entry_advanced_form(self, module, e, form, **kwargs): def case_sharing_requires_assertion(form): actions = form.actions.open_cases for action in actions: if autoset_owner_id_for_advanced_action(action): return True return False datums, assertions = self.get_datum_meta_assertions_advanced(module, form) datums.extend(EntriesHelper.get_new_case_id_datums_meta(form)) for datum_meta in datums: e.datums.append(datum_meta.datum) # assertions come after session e.assertions.extend(assertions) if self.app.case_sharing and case_sharing_requires_assertion(form): EntriesHelper.add_case_sharing_assertion(e) def get_datum_meta_assertions_advanced(self, module, form): def get_target_module(case_type, module_id, with_product_details=False): if module_id: if module_id == module.unique_id: return module from corehq.apps.app_manager.models import ModuleNotFoundException try: target = module.get_app().get_module_by_unique_id(module_id, error=_("Could not find target module used by form '{}'").format(form.default_name())) if target.case_type != case_type: raise ParentModuleReferenceError( _( "Form '%(form_name)s' in module '%(module_name)s' " "references a module with an incorrect case type: " "module '%(target_name)s' expected '%(expected_case_type)s', " "found '%(target_case_type)s'" ) % { 'form_name': form.default_name(), 'module_name': module.default_name(), 'target_name': target.default_name(), 'expected_case_type': case_type, 'target_case_type': target.case_type, } ) if with_product_details and not hasattr(target, 'product_details'): raise ParentModuleReferenceError( "Module with ID %s has no product details configuration" % module_id ) return target except ModuleNotFoundException as ex: raise ParentModuleReferenceError(ex.message) else: if case_type == module.case_type: return module target_modules = [ mod for mod in module.get_app().modules if mod.case_type == case_type and (not with_product_details or hasattr(mod, 'product_details')) ] try: return target_modules[0] except IndexError: raise ParentModuleReferenceError( "Module with case type %s in app %s not found" % (case_type, self.app) ) def get_manual_datum(action_, parent_filter_=''): target_module_ = get_target_module(action_.case_type, action_.details_module) referenced_by = form.actions.actions_meta_by_parent_tag.get(action_.case_tag) filter_xpath = EntriesHelper.get_filter_xpath(target_module_) detail_inline = self.get_detail_inline_attr(target_module_, target_module_, "case_short") return SessionDatum( id=action_.case_session_var, nodeset=(EntriesHelper.get_nodeset_xpath(action_.case_type, filter_xpath=filter_xpath) + parent_filter_), value="./@case_id", detail_select=self.details_helper.get_detail_id_safe(target_module_, 'case_short'), detail_confirm=( self.details_helper.get_detail_id_safe(target_module_, 'case_long') if (not referenced_by or referenced_by['type'] != 'load') and not detail_inline else None ), detail_persistent=self.get_detail_persistent_attr(target_module_, target_module_, "case_short"), detail_inline=detail_inline, autoselect=target_module_.auto_select_case, ) datums = [] assertions = [] for action in form.actions.get_load_update_actions(): auto_select = action.auto_select load_case_from_fixture = action.load_case_from_fixture if auto_select and auto_select.mode: datum, assertions = EntriesHelper.get_auto_select_datums_and_assertions(action, auto_select, form) datums.append(FormDatumMeta( datum=datum, case_type=None, requires_selection=False, action=action )) elif load_case_from_fixture: target_module = get_target_module(action.case_type, action.details_module) datums.extend(self.get_load_case_from_fixture_datums(action, target_module, form)) else: if action.case_index.tag: parent_action = form.actions.actions_meta_by_tag[action.case_index.tag]['action'] parent_filter = EntriesHelper.get_parent_filter( action.case_index.reference_id, parent_action.case_session_var ) else: parent_filter = '' datums.append(FormDatumMeta( datum=get_manual_datum(action, parent_filter), case_type=action.case_type, requires_selection=True, action=action )) if module.get_app().commtrack_enabled: try: last_action = list(form.actions.get_load_update_actions())[-1] if last_action.show_product_stock: nodeset = ProductInstanceXpath().instance() if last_action.product_program: nodeset = nodeset.select('program_id', last_action.product_program) target_module = get_target_module(last_action.case_type, last_action.details_module, True) datums.append(FormDatumMeta( datum=SessionDatum( id='product_id', nodeset=nodeset, value="./@id", detail_select=self.details_helper.get_detail_id_safe(target_module, 'product_short'), detail_persistent=self.get_detail_persistent_attr( target_module, target_module, "product_short" ), detail_inline=self.get_detail_inline_attr( target_module, target_module, "product_short" ), ), case_type=None, requires_selection=True, action=None )) except IndexError: pass self.add_parent_datums(datums, module) return datums, assertions def add_parent_datums(self, datums, module): def update_refs(datum_meta, changed_ids_): """ Update references in the nodeset of the given datum, if necessary e.g. "instance('casedb')/casedb/case[@case_type='guppy'] [@status='open'] [index/parent=instance('commcaresession')/session/data/parent_id]" is updated to "instance('casedb')/casedb/case[@case_type='guppy'] [@status='open'] [index/parent=instance('commcaresession')/session/data/case_id]" ^^^^^^^ because the case referred to by "parent_id" in the child module has the ID "case_id" in the parent module. """ def _apply_change_to_datum_attr(datum, attr, change): xpath = getattr(datum, attr, None) if xpath: old = session_var(change['old_id']) new = session_var(change['new_id']) setattr(datum, attr, xpath.replace(old, new)) datum = datum_meta.datum action = datum_meta.action if action: if hasattr(action, 'case_indices'): # This is an advanced module for case_index in action.case_indices: if case_index.tag in changed_ids_: # update any reference to previously changed datums for change in changed_ids_[case_index.tag]: _apply_change_to_datum_attr(datum, 'nodeset', change) _apply_change_to_datum_attr(datum, 'function', change) else: if 'basic' in changed_ids_: for change in changed_ids_['basic']: _apply_change_to_datum_attr(datum, 'nodeset', change) _apply_change_to_datum_attr(datum, 'function', change) def rename_other_id(this_datum_meta_, parent_datum_meta_, datum_ids_): """ If the ID of parent datum matches the ID of another datum in this form, rename the ID of the other datum in this form e.g. if parent datum ID == "case_id" and there is a datum in this form with the ID of "case_id" too, then rename the ID of the datum in this form to "case_id_<case_type>" (where <case_type> is the case type of the datum in this form). """ changed_id = {} parent_datum = parent_datum_meta_.datum action = this_datum_meta_.action if action: if parent_datum.id in datum_ids_: datum = datum_ids_[parent_datum.id] new_id = '_'.join((datum.datum.id, datum.case_type)) # Only advanced module actions have a case_tag attribute. case_tag = getattr(action, 'case_tag', 'basic') changed_id = { case_tag: { 'old_id': datum.datum.id, 'new_id': new_id, } } datum.datum.id = new_id return changed_id def get_changed_id(this_datum_meta_, parent_datum_meta_): """ Maps IDs in the child module to IDs in the parent module e.g. The case with the ID "parent_id" in the child module has the ID "case_id" in the parent module. """ changed_id = {} action = this_datum_meta_.action if action: case_tag = getattr(action, 'case_tag', 'basic') changed_id = { case_tag: { "old_id": this_datum_meta_.datum.id, "new_id": parent_datum_meta_.datum.id } } return changed_id def get_datums(module_): """ Return the datums of the first form in the given module """ datums_ = [] if module_: try: # assume that all forms in the module have the same case management form = module_.get_form(0) except FormNotFoundException: pass else: datums_.extend(self.get_datums_meta_for_form_generic(form)) return datums_ def append_update(dict_, new_dict): for key in new_dict: dict_[key].append(new_dict[key]) parent_datums = get_datums(module.root_module) if parent_datums: # we need to try and match the datums to the root module so that # the navigation on the phone works correctly # 1. Add in any datums that don't require user selection e.g. new case IDs # 2. Match the datum ID for datums that appear in the same position and # will be loading the same case type # see advanced_app_features#child-modules in docs datum_ids = {d.datum.id: d for d in datums} index = 0 changed_ids_by_case_tag = defaultdict(list) for this_datum_meta, parent_datum_meta in list(zip_longest(datums, parent_datums)): if this_datum_meta: update_refs(this_datum_meta, changed_ids_by_case_tag) if not parent_datum_meta: continue if not this_datum_meta or this_datum_meta.datum.id != parent_datum_meta.datum.id: if not parent_datum_meta.requires_selection: # Add parent datums of opened subcases and automatically-selected cases datums.insert(index, parent_datum_meta._replace(from_parent=True)) elif this_datum_meta and this_datum_meta.case_type == parent_datum_meta.case_type: append_update(changed_ids_by_case_tag, rename_other_id(this_datum_meta, parent_datum_meta, datum_ids)) append_update(changed_ids_by_case_tag, get_changed_id(this_datum_meta, parent_datum_meta)) this_datum_meta.datum.id = parent_datum_meta.datum.id index += 1 @staticmethod def _get_module_for_persistent_context(detail_module, module_unique_id): module_for_persistent_context = detail_module.get_app().get_module_by_unique_id(module_unique_id) if (module_for_persistent_context and (module_for_persistent_context.case_details.short.use_case_tiles or module_for_persistent_context.case_details.short.custom_xml )): return module_for_persistent_context def get_detail_persistent_attr(self, module, detail_module, detail_type="case_short"): detail, detail_enabled = self._get_detail_from_module(module, detail_type) if detail_enabled: # if configured to use persisted case tile context from another module which has case tiles # configured then get id_string for that module if detail.persistent_case_tile_from_module: module_for_persistent_context = self._get_module_for_persistent_context( module, detail.persistent_case_tile_from_module ) if module_for_persistent_context: return id_strings.detail(module_for_persistent_context, detail_type) if self._has_persistent_tile(detail): return id_strings.detail(detail_module, detail_type) if detail.persist_case_context and detail_type == "case_short": # persistent_case_context will not work on product lists. return id_strings.persistent_case_context_detail(detail_module) return None def _get_detail_inline_attr_from_module(self, module, module_unique_id): module_for_persistent_context = self._get_module_for_persistent_context(module, module_unique_id) if module_for_persistent_context: return self.details_helper.get_detail_id_safe(module_for_persistent_context, "case_long") def get_detail_inline_attr(self, module, detail_module, detail_type="case_short"): assert detail_type in ["case_short", "product_short"] detail, detail_enabled = self._get_detail_from_module(module, detail_type) if detail_enabled and detail.pull_down_tile: if detail_type == "case_short" and detail.persistent_case_tile_from_module: inline_attr = self._get_detail_inline_attr_from_module( module, detail.persistent_case_tile_from_module) if inline_attr: return inline_attr if self._has_persistent_tile(detail): list_type = "case_long" if detail_type == "case_short" else "product_long" return self.details_helper.get_detail_id_safe(detail_module, list_type) return None def _get_detail_from_module(self, module, detail_type): """ Return the Detail object of the given type from the given module """ details = {d[0]: d for d in module.get_details()} _, detail, detail_enabled = details[detail_type] return detail, detail_enabled def _has_persistent_tile(self, detail): """ Return True if the given Detail is configured to persist a case tile on forms """ return detail.persist_tile_on_forms and (detail.use_case_tiles or detail.custom_xml)
def get_module_contributions(self, module): if module_offers_search(module): domain = self.app.domain details_helper = DetailsHelper(self.app) sync_request = SyncRequest( post=SyncRequestPost( url=absolute_reverse('claim_case', args=[domain]), # Check whether the case to be claimed already exists in casedb: # count( # instance('casedb')/casedb/case[@case_id=instance('querysession')/session/data/case_id] # ) = 0 relevant=XPath.count( CaseIDXPath(QuerySessionXPath('case_id').instance()).case() ) + ' = 0', data=[ QueryData( key='case_id', ref=QuerySessionXPath('case_id').instance(), # e.g. instance('querysession')/session/data/case_id ), ] ), command=Command( id=id_strings.search_command(module), display=Display( text=Text(locale_id=id_strings.case_search_locale(module)), ), ), instances=[Instance( id=SESSION_INSTANCE, src='jr://instance/session' )], session=SyncRequestSession( queries=[ SyncRequestQuery( url=absolute_reverse('sync_search', args=[domain]), storage_instance=RESULTS_INSTANCE, data=[ QueryData( key='case_type', ref="'{}'".format(module.case_type) ), ], prompts=[ QueryPrompt( key=p.name, display=Display( text=Text(locale_id=id_strings.search_property_locale(module, p.name)), ), ) for p in module.search_config.properties ] ) ], data=[SessionDatum( id='case_id', nodeset=(CaseTypeXpath(module.case_type) .case(instance_name=RESULTS_INSTANCE) .select(u'@status', u'open', quote=True)), value='./@case_id', detail_select=details_helper.get_detail_id_safe(module, 'case_short'), detail_confirm=details_helper.get_detail_id_safe(module, 'case_long'), )], ), stack=Stack(), ) frame = PushFrame() # Open first form in module frame.add_command(XPath.string(id_strings.menu_id(module))) frame.add_datum(StackDatum(id=CALCULATED_DATA, value=XPath.string(MARK_AS_CLAIMED))) sync_request.stack.add_frame(frame) return [sync_request] return []