def details(self): r = [] from corehq.apps.app_manager.detail_screen import get_column_generator if not self.app.use_custom_suite: for module in self.modules: for detail in module.get_details(): detail_columns = detail.get_columns() if detail_columns and detail.type in ('case_short', 'case_long'): d = Detail( id=self.id_strings.detail(module, detail), title=Text(locale_id=self.id_strings.detail_title_locale(module, detail)) ) if detail.type == 'case_short': detail_fields = [c.field for c in detail_columns] sort_fields = [e.field for e in module.detail_sort_elements] sort_only_fields = [field for field in sort_fields if field not in detail_fields] from corehq.apps.app_manager.models import DetailColumn for sort_field in sort_only_fields: # set up a fake detailcolumn so we can # add this field but not actually # save it dc = DetailColumn( model='case', field=sort_field, format='invisible', ) detail.append_column(dc) # need to add a default here so that it doesn't # get persisted if self.app.enable_multi_sort and \ len(module.detail_sort_elements) == 0: from corehq.apps.app_manager.models import SortElement try: se = SortElement() se.field = detail.columns[0].field se.type = 'string' se.direction = 'ascending' module.detail_sort_elements.append(se) except Exception: pass for column in detail.get_columns(): fields = get_column_generator(self.app, module, detail, column).fields d.fields.extend(fields) try: if not self.app.enable_multi_sort: d.fields[0].sort = 'default' except IndexError: pass else: # only yield the Detail if it has Fields r.append(d) return r
def get_default_sort_elements(detail): from corehq.apps.app_manager.models import SortElement if not detail.columns: return [] def get_sort_params(column): if column.field_type == FIELD_TYPE_LEDGER: return dict(type='int', direction='descending') else: return dict(type='string', direction='ascending') col_0 = detail.get_column(0) sort_elements = [SortElement( field=col_0.field, **get_sort_params(col_0) )] for column in detail.columns[1:]: if column.field_type == FIELD_TYPE_LEDGER: sort_elements.append(SortElement( field=column.field, **get_sort_params(column) )) return sort_elements
def test_sort_calculation(self): app = Application.wrap(self.get_json('suite-advanced')) detail = app.modules[0].case_details.short detail.sort_elements.append( SortElement( field=detail.columns[0].field, type='string', direction='descending', blanks='first', sort_calculation='random()' ) ) sort_node = """ <partial> <sort direction="descending" blanks="first" order="1" type="string"> <text> <xpath function="random()"/> </text> </sort> </partial> """ self.assertXmlPartialEqual( sort_node, app.create_suite(), "./detail[@id='m0_case_short']/field/sort" )
def test_sort_cache_suite(self): app = Application.wrap(self.get_json('suite-advanced')) detail = app.modules[0].case_details.short detail.sort_elements.append( SortElement( field=detail.columns[0].field, type='index', direction='descending', )) self.assertXmlPartialEqual(self.get_xml('sort-cache'), app.create_suite(), "./detail[@id='m0_case_short']")
def get_nodeset_sort_elements(detail): from corehq.apps.app_manager.models import SortElement sort_elements = [] tab_spans = detail.get_tab_spans() for tab in detail.get_tabs(): if tab.nodeset: tab_span = tab_spans[tab.id] for column in detail.columns[tab_span[0]:tab_span[1]]: if column.invisible: sort_elements.append( SortElement(field=column.field, type='string', direction='ascending')) return sort_elements
def test_short_detail_xml_sort_only(self): short = self.case_details.short short.display = 'short' short.sort_elements.append( SortElement( field='gps', type='distance', direction='descending', ) ) suite = self.factory.app.create_suite() template_xpath = './detail[@id="m0_case_short"]/field' self.assertXmlHasXpath(suite, template_xpath) self.assertXmlPartialEqual( """ <partial> <field> <header> <text> <locale id="m0.case_short.case_name_1.header"/> </text> </header> <template> <text> <xpath function="case_name"/> </text> </template> </field> <field> <header width="0"> <text/> </header> <template width="0"> <text> <xpath function="gps"/> </text> </template> <sort direction="descending" order="1" type="double"> <text> <xpath function="if(gps = '', 2147483647, round(distance(gps, here())))"/> </text> </sort> </field> </partial> """, suite, template_xpath )
def get_detail_column_infos(detail, include_sort): """ This is not intented to be a widely used format just a packaging of column info into a form most convenient for rendering """ from corehq.apps.app_manager.models import SortElement DetailColumnInfo = namedtuple('DetailColumnInfo', 'column sort_element order') if not include_sort: return [ DetailColumnInfo(column, None, None) for column in detail.get_columns() ] if detail.sort_elements: sort_elements = detail.sort_elements elif detail.columns: sort_elements = [ SortElement( field=detail.get_column(0).field, type='string', direction='ascending', ) ] else: sort_elements = [] # order is 1-indexed sort_elements = dict( (s.field, (s, i + 1)) for i, s in enumerate(sort_elements)) columns = [] for column in detail.get_columns(): sort_element, order = sort_elements.pop(column.field, (None, None)) columns.append(DetailColumnInfo(column, sort_element, order)) # sort elements is now populated with only what's not in any column # add invisible columns for these sort_only = sorted(sort_elements.items(), key=lambda (field, (sort_element, order)): order) for field, (sort_element, order) in sort_only: column = create_temp_sort_column(field, len(columns)) columns.append(DetailColumnInfo(column, sort_element, order)) return columns
def test_sort_cache_search(self): app = Application.wrap(self.get_json('suite-advanced')) app.modules[0].search_config = CaseSearch( properties=[CaseSearchProperty(name='name', label={'en': 'Name'})], ) detail = app.modules[0].case_details.short detail.sort_elements.append( SortElement( field=detail.columns[0].field, type='index', direction='descending', blanks='first', ) ) self.assertXmlPartialEqual( self.get_xml('sort-cache-search'), app.create_suite(), "./detail[@id='m0_search_short']" )
def test_short_detail_xml_with_sort(self): short = self.case_details.short short.display = 'short' short_column = short.get_column(0) short.sort_elements.append( SortElement( field=short_column.field, type='distance', direction='descending', )) suite = self.factory.app.create_suite() template_xpath = './detail[@id="m0_case_short"]/field' self.assertXmlHasXpath(suite, template_xpath) self.assertXmlPartialEqual( """ <partial> <field> <header> <text> <locale id="m0.case_short.case_name_1.header"/> </text> </header> <template> <text> <xpath function="case_name"/> </text> </template> <sort direction="descending" order="1" type="double"> <text> <xpath function="round(distance(case_name, here()))"/> </text> </sort> </field> </partial> """, suite, template_xpath)
def edit_module_detail_screens(request, domain, app_id, module_id): """ Overwrite module case details. Only overwrites components that have been provided in the request. Components are short, long, filter, parent_select, fixture_select and sort_elements. """ params = json_request(request.POST) detail_type = params.get('type') short = params.get('short', None) long = params.get('long', None) tabs = params.get('tabs', None) filter = params.get('filter', ()) custom_xml = params.get('custom_xml', None) parent_select = params.get('parent_select', None) fixture_select = params.get('fixture_select', None) sort_elements = params.get('sort_elements', None) persist_case_context = params.get('persistCaseContext', None) use_case_tiles = params.get('useCaseTiles', None) persist_tile_on_forms = params.get("persistTileOnForms", None) pull_down_tile = params.get("enableTilePullDown", None) case_list_lookup = params.get("case_list_lookup", None) app = get_app(domain, app_id) module = app.get_module(module_id) if detail_type == 'case': detail = module.case_details elif detail_type == CAREPLAN_GOAL: detail = module.goal_details elif detail_type == CAREPLAN_TASK: detail = module.task_details else: try: detail = getattr(module, '{0}_details'.format(detail_type)) except AttributeError: return HttpResponseBadRequest("Unknown detail type '%s'" % detail_type) if short is not None: detail.short.columns = map(DetailColumn.wrap, short) if persist_case_context is not None: detail.short.persist_case_context = persist_case_context if use_case_tiles is not None: detail.short.use_case_tiles = use_case_tiles if persist_tile_on_forms is not None: detail.short.persist_tile_on_forms = persist_tile_on_forms if pull_down_tile is not None: detail.short.pull_down_tile = pull_down_tile if case_list_lookup is not None: _save_case_list_lookup_params(detail.short, case_list_lookup) if long is not None: detail.long.columns = map(DetailColumn.wrap, long) if tabs is not None: detail.long.tabs = map(DetailTab.wrap, tabs) if filter != (): # Note that we use the empty tuple as the sentinel because a filter # value of None represents clearing the filter. detail.short.filter = filter if custom_xml is not None: detail.short.custom_xml = custom_xml if sort_elements is not None: detail.short.sort_elements = [] for sort_element in sort_elements: item = SortElement() item.field = sort_element['field'] item.type = sort_element['type'] item.direction = sort_element['direction'] detail.short.sort_elements.append(item) if parent_select is not None: module.parent_select = ParentSelect.wrap(parent_select) if fixture_select is not None: module.fixture_select = FixtureSelect.wrap(fixture_select) resp = {} app.save(resp) return json_response(resp)
def edit_module_detail_screens(request, domain, app_id, module_id): """ Overwrite module case details. Only overwrites components that have been provided in the request. Components are short, long, filter, parent_select, fixture_select and sort_elements. """ params = json_request(request.POST) detail_type = params.get('type') short = params.get('short', None) long = params.get('long', None) tabs = params.get('tabs', None) filter = params.get('filter', ()) custom_xml = params.get('custom_xml', None) parent_select = params.get('parent_select', None) fixture_select = params.get('fixture_select', None) sort_elements = params.get('sort_elements', None) persist_case_context = params.get('persistCaseContext', None) persistent_case_context_xml = params.get('persistentCaseContextXML', None) use_case_tiles = params.get('useCaseTiles', None) persist_tile_on_forms = params.get("persistTileOnForms", None) pull_down_tile = params.get("enableTilePullDown", None) case_list_lookup = params.get("case_list_lookup", None) search_properties = params.get("search_properties") custom_variables = { 'short': params.get("short_custom_variables", None), 'long': params.get("long_custom_variables", None) } app = get_app(domain, app_id) module = app.get_module(module_id) if detail_type == 'case': detail = module.case_details elif detail_type == CAREPLAN_GOAL: detail = module.goal_details elif detail_type == CAREPLAN_TASK: detail = module.task_details else: try: detail = getattr(module, '{0}_details'.format(detail_type)) except AttributeError: return HttpResponseBadRequest("Unknown detail type '%s'" % detail_type) lang = request.COOKIES.get('lang', app.langs[0]) if short is not None: detail.short.columns = map(DetailColumn.from_json, short) if persist_case_context is not None: detail.short.persist_case_context = persist_case_context detail.short.persistent_case_context_xml = persistent_case_context_xml if use_case_tiles is not None: detail.short.use_case_tiles = use_case_tiles if persist_tile_on_forms is not None: detail.short.persist_tile_on_forms = persist_tile_on_forms if pull_down_tile is not None: detail.short.pull_down_tile = pull_down_tile if case_list_lookup is not None: _save_case_list_lookup_params(detail.short, case_list_lookup, lang) if long is not None: detail.long.columns = map(DetailColumn.from_json, long) if tabs is not None: detail.long.tabs = map(DetailTab.wrap, tabs) if filter != (): # Note that we use the empty tuple as the sentinel because a filter # value of None represents clearing the filter. detail.short.filter = filter if custom_xml is not None: detail.short.custom_xml = custom_xml if custom_variables['short'] is not None: try: etree.fromstring("<variables>{}</variables>".format( custom_variables['short'])) except etree.XMLSyntaxError as error: return HttpResponseBadRequest( "There was an issue with your custom variables: {}".format( error.message)) detail.short.custom_variables = custom_variables['short'] if custom_variables['long'] is not None: try: etree.fromstring("<variables>{}</variables>".format( custom_variables['long'])) except etree.XMLSyntaxError as error: return HttpResponseBadRequest( "There was an issue with your custom variables: {}".format( error.message)) detail.long.custom_variables = custom_variables['long'] if sort_elements is not None: detail.short.sort_elements = [] for sort_element in sort_elements: item = SortElement() item.field = sort_element['field'] item.type = sort_element['type'] item.direction = sort_element['direction'] item.display[lang] = sort_element['display'] if toggles.SORT_CALCULATION_IN_CASE_LIST.enabled(domain): item.sort_calculation = sort_element['sort_calculation'] else: item.sort_calculation = "" detail.short.sort_elements.append(item) if parent_select is not None: module.parent_select = ParentSelect.wrap(parent_select) if module_case_hierarchy_has_circular_reference(module): return HttpResponseBadRequest( _("The case hierarchy contains a circular reference.")) if fixture_select is not None: module.fixture_select = FixtureSelect.wrap(fixture_select) if search_properties is not None: if search_properties.get('properties') is not None: module.search_config = CaseSearch( properties=[ CaseSearchProperty.wrap(p) for p in _update_search_properties( module, search_properties.get('properties'), lang) ], relevant=(search_properties.get('relevant') if search_properties.get('relevant') is not None else CLAIM_DEFAULT_RELEVANT_CONDITION), include_closed=bool(search_properties.get('include_closed')), default_properties=[ DefaultCaseSearchProperty.wrap(p) for p in search_properties.get('default_properties') ]) resp = {} app.save(resp) return json_response(resp)
def edit_module_detail_screens(request, domain, app_id, module_unique_id): """ Overwrite module case details. Only overwrites components that have been provided in the request. Components are short, long, filter, parent_select, fixture_select and sort_elements. """ params = json_request(request.POST) detail_type = params.get('type') short = params.get('short', None) long_ = params.get('long', None) tabs = params.get('tabs', None) filter = params.get('filter', ()) custom_xml = params.get('custom_xml', None) parent_select = params.get('parent_select', None) fixture_select = params.get('fixture_select', None) sort_elements = params.get('sort_elements', None) persist_case_context = params.get('persistCaseContext', None) persistent_case_context_xml = params.get('persistentCaseContextXML', None) use_case_tiles = params.get('useCaseTiles', None) persist_tile_on_forms = params.get("persistTileOnForms", None) persistent_case_tile_from_module = params.get("persistentCaseTileFromModule", None) pull_down_tile = params.get("enableTilePullDown", None) sort_nodeset_columns = params.get("sortNodesetColumns", None) print_template = params.get('printTemplate', None) case_list_lookup = params.get("case_list_lookup", None) search_properties = params.get("search_properties") custom_variables = { 'short': params.get("short_custom_variables", None), 'long': params.get("long_custom_variables", None) } app = get_app(domain, app_id) try: module = app.get_module_by_unique_id(module_unique_id) except ModuleNotFoundException: # temporary fallback module = app.get_module(module_unique_id) if detail_type == 'case': detail = module.case_details else: try: detail = getattr(module, '{0}_details'.format(detail_type)) except AttributeError: return HttpResponseBadRequest("Unknown detail type '%s'" % detail_type) lang = request.COOKIES.get('lang', app.langs[0]) if short is not None: detail.short.columns = list(map(DetailColumn.from_json, short)) if persist_case_context is not None: detail.short.persist_case_context = persist_case_context detail.short.persistent_case_context_xml = persistent_case_context_xml if use_case_tiles is not None: detail.short.use_case_tiles = use_case_tiles if persist_tile_on_forms is not None: detail.short.persist_tile_on_forms = persist_tile_on_forms if persistent_case_tile_from_module is not None: detail.short.persistent_case_tile_from_module = persistent_case_tile_from_module if pull_down_tile is not None: detail.short.pull_down_tile = pull_down_tile if case_list_lookup is not None: _save_case_list_lookup_params(detail.short, case_list_lookup, lang) if long_ is not None: detail.long.columns = list(map(DetailColumn.from_json, long_)) if tabs is not None: detail.long.tabs = list(map(DetailTab.wrap, tabs)) if print_template is not None: detail.long.print_template = print_template if filter != (): # Note that we use the empty tuple as the sentinel because a filter # value of None represents clearing the filter. detail.short.filter = filter if custom_xml is not None: detail.short.custom_xml = custom_xml if custom_variables['short'] is not None: try: etree.fromstring("<variables>{}</variables>".format(custom_variables['short'])) except etree.XMLSyntaxError as error: return HttpResponseBadRequest( "There was an issue with your custom variables: {}".format(error.message) ) detail.short.custom_variables = custom_variables['short'] if custom_variables['long'] is not None: try: etree.fromstring("<variables>{}</variables>".format(custom_variables['long'])) except etree.XMLSyntaxError as error: return HttpResponseBadRequest( "There was an issue with your custom variables: {}".format(error.message) ) detail.long.custom_variables = custom_variables['long'] if sort_nodeset_columns is not None: detail.long.sort_nodeset_columns = sort_nodeset_columns if sort_elements is not None: # Attempt to map new elements to old so we don't lose translations # Imperfect because the same field may be used multiple times, or user may change field old_elements_by_field = {e['field']: e for e in detail.short.sort_elements} detail.short.sort_elements = [] for sort_element in sort_elements: item = SortElement() item.field = sort_element['field'] item.type = sort_element['type'] item.direction = sort_element['direction'] item.blanks = sort_element['blanks'] if item.field in old_elements_by_field: item.display = old_elements_by_field[item.field].display item.display[lang] = sort_element['display'] if toggles.SORT_CALCULATION_IN_CASE_LIST.enabled(domain): item.sort_calculation = sort_element['sort_calculation'] else: item.sort_calculation = "" detail.short.sort_elements.append(item) if parent_select is not None: module.parent_select = ParentSelect.wrap(parent_select) if module_case_hierarchy_has_circular_reference(module): return HttpResponseBadRequest(_("The case hierarchy contains a circular reference.")) if fixture_select is not None: module.fixture_select = FixtureSelect.wrap(fixture_select) if search_properties is not None: if ( search_properties.get('properties') is not None or search_properties.get('default_properties') is not None ): module.search_config = CaseSearch( properties=[ CaseSearchProperty.wrap(p) for p in _update_search_properties( module, search_properties.get('properties'), lang ) ], relevant=( search_properties.get('relevant') if search_properties.get('relevant') is not None else CLAIM_DEFAULT_RELEVANT_CONDITION ), include_closed=bool(search_properties.get('include_closed')), search_button_display_condition=search_properties.get('search_button_display_condition', ""), blacklisted_owner_ids_expression=search_properties.get('blacklisted_owner_ids_expression', ""), default_properties=[ DefaultCaseSearchProperty.wrap(p) for p in search_properties.get('default_properties') ] ) resp = {} app.save(resp) return json_response(resp)
def edit_module_detail_screens(request, domain, app_id, module_id): """ Overwrite module case details. Only overwrites components that have been provided in the request. Components are short, long, filter, parent_select, fixture_select and sort_elements. """ params = json_request(request.POST) detail_type = params.get('type') short = params.get('short', None) long = params.get('long', None) tabs = params.get('tabs', None) filter = params.get('filter', ()) custom_xml = params.get('custom_xml', None) parent_select = params.get('parent_select', None) fixture_select = params.get('fixture_select', None) sort_elements = params.get('sort_elements', None) persist_case_context = params.get('persistCaseContext', None) persistent_case_context_xml = params.get('persistentCaseContextXML', None) use_case_tiles = params.get('useCaseTiles', None) persist_tile_on_forms = params.get("persistTileOnForms", None) pull_down_tile = params.get("enableTilePullDown", None) case_list_lookup = params.get("case_list_lookup", None) search_properties = params.get("search_properties") app = get_app(domain, app_id) module = app.get_module(module_id) if detail_type == 'case': detail = module.case_details elif detail_type == CAREPLAN_GOAL: detail = module.goal_details elif detail_type == CAREPLAN_TASK: detail = module.task_details else: try: detail = getattr(module, '{0}_details'.format(detail_type)) except AttributeError: return HttpResponseBadRequest("Unknown detail type '%s'" % detail_type) lang = request.COOKIES.get('lang', app.langs[0]) if short is not None: detail.short.columns = map(DetailColumn.from_json, short) if persist_case_context is not None: detail.short.persist_case_context = persist_case_context detail.short.persistent_case_context_xml = persistent_case_context_xml if use_case_tiles is not None: detail.short.use_case_tiles = use_case_tiles if persist_tile_on_forms is not None: detail.short.persist_tile_on_forms = persist_tile_on_forms if pull_down_tile is not None: detail.short.pull_down_tile = pull_down_tile if case_list_lookup is not None: _save_case_list_lookup_params(detail.short, case_list_lookup, lang) if long is not None: detail.long.columns = map(DetailColumn.from_json, long) if tabs is not None: detail.long.tabs = map(DetailTab.wrap, tabs) if filter != (): # Note that we use the empty tuple as the sentinel because a filter # value of None represents clearing the filter. detail.short.filter = filter if custom_xml is not None: detail.short.custom_xml = custom_xml if sort_elements is not None: detail.short.sort_elements = [] for sort_element in sort_elements: item = SortElement() item.field = sort_element['field'] item.type = sort_element['type'] item.direction = sort_element['direction'] item.display[lang] = sort_element['display'] detail.short.sort_elements.append(item) if parent_select is not None: module.parent_select = ParentSelect.wrap(parent_select) if fixture_select is not None: module.fixture_select = FixtureSelect.wrap(fixture_select) if search_properties is not None: if search_properties.get('properties') is not None: module.search_config = CaseSearch( properties=[ CaseSearchProperty.wrap(p) for p in _update_search_properties( module, search_properties.get('properties'), lang) ], relevant=(search_properties.get('relevant') if search_properties.get('relevant') is not None else CLAIM_DEFAULT_RELEVANT_CONDITION)) resp = {} app.save(resp) return json_response(resp)
def edit_module_detail_screens(request, domain, app_id, module_id): """ Overwrite module case details. Only overwrites components that have been provided in the request. Components are short, long, filter, parent_select, fixture_select and sort_elements. """ params = json_request(request.POST) detail_type = params.get('type') short = params.get('short', None) long = params.get('long', None) tabs = params.get('tabs', None) filter = params.get('filter', ()) custom_xml = params.get('custom_xml', None) parent_select = params.get('parent_select', None) fixture_select = params.get('fixture_select', None) sort_elements = params.get('sort_elements', None) persist_case_context = params.get('persistCaseContext', None) persistent_case_context_xml = params.get('persistentCaseContextXML', None) use_case_tiles = params.get('useCaseTiles', None) persist_tile_on_forms = params.get("persistTileOnForms", None) pull_down_tile = params.get("enableTilePullDown", None) case_list_lookup = params.get("case_list_lookup", None) search_properties = params.get("search_properties") custom_variables = { 'short': params.get("short_custom_variables", None), 'long': params.get("long_custom_variables", None) } app = get_app(domain, app_id) module = app.get_module(module_id) if detail_type == 'case': detail = module.case_details elif detail_type == CAREPLAN_GOAL: detail = module.goal_details elif detail_type == CAREPLAN_TASK: detail = module.task_details else: try: detail = getattr(module, '{0}_details'.format(detail_type)) except AttributeError: return HttpResponseBadRequest("Unknown detail type '%s'" % detail_type) lang = request.COOKIES.get('lang', app.langs[0]) if short is not None: detail.short.columns = map(DetailColumn.from_json, short) if persist_case_context is not None: detail.short.persist_case_context = persist_case_context detail.short.persistent_case_context_xml = persistent_case_context_xml if use_case_tiles is not None: detail.short.use_case_tiles = use_case_tiles if persist_tile_on_forms is not None: detail.short.persist_tile_on_forms = persist_tile_on_forms if pull_down_tile is not None: detail.short.pull_down_tile = pull_down_tile if case_list_lookup is not None: _save_case_list_lookup_params(detail.short, case_list_lookup, lang) if long is not None: detail.long.columns = map(DetailColumn.from_json, long) if tabs is not None: detail.long.tabs = map(DetailTab.wrap, tabs) if filter != (): # Note that we use the empty tuple as the sentinel because a filter # value of None represents clearing the filter. detail.short.filter = filter if custom_xml is not None: detail.short.custom_xml = custom_xml if custom_variables['short'] is not None: try: etree.fromstring("<variables>{}</variables>".format(custom_variables['short'])) except etree.XMLSyntaxError as error: return HttpResponseBadRequest( "There was an issue with your custom variables: {}".format(error.message) ) detail.short.custom_variables = custom_variables['short'] if custom_variables['long'] is not None: try: etree.fromstring("<variables>{}</variables>".format(custom_variables['long'])) except etree.XMLSyntaxError as error: return HttpResponseBadRequest( "There was an issue with your custom variables: {}".format(error.message) ) detail.long.custom_variables = custom_variables['long'] if sort_elements is not None: detail.short.sort_elements = [] for sort_element in sort_elements: item = SortElement() item.field = sort_element['field'] item.type = sort_element['type'] item.direction = sort_element['direction'] item.display[lang] = sort_element['display'] detail.short.sort_elements.append(item) if parent_select is not None: module.parent_select = ParentSelect.wrap(parent_select) if module_case_hierarchy_has_circular_reference(module): return HttpResponseBadRequest(_("The case hierarchy contains a circular reference.")) if fixture_select is not None: module.fixture_select = FixtureSelect.wrap(fixture_select) if search_properties is not None: if search_properties.get('properties') is not None: module.search_config = CaseSearch( properties=[ CaseSearchProperty.wrap(p) for p in _update_search_properties( module, search_properties.get('properties'), lang ) ], relevant=( search_properties.get('relevant') if search_properties.get('relevant') is not None else CLAIM_DEFAULT_RELEVANT_CONDITION ), include_closed=bool(search_properties.get('include_closed')), default_properties=[ DefaultCaseSearchProperty.wrap(p) for p in search_properties.get('default_properties') ] ) resp = {} app.save(resp) return json_response(resp)