PageRenderTime 30ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/corehq/apps/app_manager/fields.py

https://github.com/dimagi/commcare-hq
Python | 684 lines | 665 code | 7 blank | 12 comment | 4 complexity | d3679fc97d53f765992e6bcb6ee6f73f MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1
  1. import collections
  2. import itertools
  3. import logging
  4. from copy import copy
  5. from django import forms
  6. from django.http import Http404
  7. from django.urls import reverse
  8. from django.utils.translation import gettext as _
  9. from memoized import memoized
  10. from couchforms.analytics import get_exports_by_form
  11. from corehq.apps.app_manager.analytics import get_exports_by_application
  12. from corehq.apps.app_manager.const import USERCASE_TYPE
  13. from corehq.apps.app_manager.dbaccessors import get_app, get_apps_in_domain
  14. from corehq.apps.registry.models import DataRegistry
  15. from corehq.apps.registry.utils import get_data_registry_dropdown_options
  16. from corehq.apps.hqwebapp import crispy as hqcrispy
  17. from corehq.apps.reports.analytics.esaccessors import (
  18. get_case_types_for_domain_es,
  19. )
  20. from corehq.apps.userreports.app_manager.data_source_meta import (
  21. DATA_SOURCE_TYPE_CASE,
  22. DATA_SOURCE_TYPE_FORM,
  23. DATA_SOURCE_TYPE_RAW,
  24. )
  25. from corehq.apps.userreports.dbaccessors import get_datasources_for_domain
  26. from corehq.toggles import AGGREGATE_UCRS
  27. from corehq.util.soft_assert import soft_assert
  28. DataSource = collections.namedtuple('DataSource', ['application', 'source_type', 'source', 'registry_slug'])
  29. RMIDataChoice = collections.namedtuple('RMIDataChoice', ['id', 'text', 'data'])
  30. AppFormRMIResponse = collections.namedtuple('AppFormRMIResponse', [
  31. 'app_types', 'apps_by_type', 'modules_by_app',
  32. 'forms_by_app_by_module', 'labels', 'placeholders'
  33. ])
  34. AppFormRMIPlaceholder = collections.namedtuple('AppFormRMIPlaceholder', [
  35. 'application', 'module', 'form'
  36. ])
  37. AppCaseRMIResponse = collections.namedtuple('AppCaseRMIResponse', [
  38. 'app_types', 'apps_by_type', 'case_types_by_app', 'placeholders'
  39. ])
  40. AppCaseRMIPlaceholder = collections.namedtuple('AppCaseRMIPlaceholder', [
  41. 'application', 'case_type'
  42. ])
  43. class ApplicationDataSourceUIHelper(object):
  44. """
  45. A helper object that can be used in forms that allows you to select a data source from an application.
  46. Data sources can be forms and cases.
  47. To use it you must do the following:
  48. - Add this helper as a member variable of your form
  49. - Call helper.boostrap() with the domain.
  50. - Add helper.get_fields() to the form fields.
  51. - Add the following knockout bindings to your template:
  52. $(function () {
  53. $("#FORM").koApplyBindings({
  54. application: ko.observable(""),
  55. sourceType: ko.observable(""),
  56. sourcesMap: {{ sources_map|JSON }},
  57. labelMap: {
  58. 'case': gettext('Case'),
  59. 'form': gettext('Form'),
  60. 'data_source': gettext('Data Source'),
  61. },
  62. });
  63. });
  64. Where FORM is a selector for your form and sources_map is the .all_sources property from this object
  65. (which gets set after bootstrap).
  66. See usages for examples.
  67. """
  68. def __init__(self, enable_raw=False, enable_registry=False, registry_permission_checker=None):
  69. self.all_sources = {}
  70. self.enable_raw = enable_raw
  71. self.enable_registry = enable_registry
  72. self.app_and_registry_sources = {}
  73. self.registry_permission_checker = registry_permission_checker
  74. source_choices = [
  75. (DATA_SOURCE_TYPE_CASE, _("Case")),
  76. (DATA_SOURCE_TYPE_FORM, _("Form"))
  77. ]
  78. if enable_raw:
  79. source_choices.append((DATA_SOURCE_TYPE_RAW, _("Data Source")))
  80. self.application_field = forms.ChoiceField(label=_('Application'), widget=forms.Select())
  81. self.source_type_field = forms.ChoiceField(label=_('Forms or Cases'),
  82. choices=source_choices,
  83. widget=forms.Select(choices=source_choices))
  84. self.source_field = forms.ChoiceField(label=_('Data Source'), widget=forms.Select())
  85. self.source_field.label = '<span data-bind="text: labelMap[sourceType()]"></span>'
  86. self.registry_slug_field = forms.ChoiceField(label=_('Data Registry'), widget=forms.HiddenInput,
  87. required=False)
  88. if enable_registry:
  89. self.registry_slug_field.widget = forms.Select()
  90. self.application_field.required = False
  91. def bootstrap(self, domain):
  92. self.all_sources = get_app_sources(domain)
  93. self.application_field.choices = sorted(
  94. [(app_id, source['name']) for app_id, source in self.all_sources.items()],
  95. key=lambda id_name_tuple: (id_name_tuple[1] or '').lower()
  96. )
  97. if self.enable_registry:
  98. self.application_field.choices += [('', '--------')]
  99. self.all_sources.update({'': {"name": '', "case": [], "form": []}})
  100. self.app_and_registry_sources = get_dropdown_options(domain, self.all_sources,
  101. self.registry_permission_checker)
  102. self.all_sources.update(get_registry_case_sources(domain))
  103. self.source_field.choices = []
  104. def _add_choices(field, choices):
  105. field.choices.extend(choices)
  106. # it's weird/annoying that you have to manually sync these
  107. field.widget.choices.extend(choices)
  108. _add_choices(
  109. self.source_field,
  110. [(ct['value'], ct['text']) for app in self.all_sources.values() for ct in app['case']]
  111. )
  112. _add_choices(
  113. self.source_field,
  114. [(ct['value'], ct['text']) for app in self.all_sources.values() for ct in app['form']]
  115. )
  116. if self.enable_raw:
  117. available_data_sources = get_datasources_for_domain(domain, include_static=True,
  118. include_aggregate=AGGREGATE_UCRS.enabled(domain))
  119. _add_choices(
  120. self.source_field,
  121. [(ds.data_source_id, ds.display_name) for ds in available_data_sources]
  122. )
  123. # also shove these into app sources for every app for now to avoid substantially
  124. # messing with this code for this widget
  125. # (this is not the best ux / should probably be cleaned up later)
  126. for app_data in self.all_sources.values():
  127. app_data['data_source'] = [{"text": ds.display_name, "value": ds.data_source_id}
  128. for ds in available_data_sources]
  129. self.registry_slug_field.choices = sort_tuple_field_choices_by_name(
  130. [(registry["slug"], registry["name"]) for registry in
  131. get_data_registry_dropdown_options(domain, permission_checker=self.registry_permission_checker)],
  132. ) + [('', '--------')]
  133. # NOTE: This corresponds to a view-model that must be initialized in your template.
  134. # See the doc string of this class for more information.
  135. self.source_type_field.widget.attrs = {'data-bind': 'value: sourceType'}
  136. if self.enable_registry:
  137. self.application_field.widget.attrs = {'data-bind': '''
  138. value: application,
  139. disable: isDataFromOneProject() != 'true',
  140. optionsText: function(item){return item.text},
  141. optionsValue: function(item){return item.value},
  142. options: dropdownMap['app'][isDataFromOneProject()]
  143. '''}
  144. self.registry_slug_field.widget.attrs = {'data-bind': '''
  145. optionsText: function(item){return item.text},
  146. optionsValue: function(item){return item.value},
  147. value: registrySlug,
  148. disable: sourceType() != 'case' || isDataFromOneProject() != 'false',
  149. options: dropdownMap['registry'][isDataFromOneProject()]
  150. '''}
  151. self.source_field.widget.attrs = {'data-bind': '''
  152. optionsText: function(item){return item.text},
  153. optionsValue: function(item){return item.value},
  154. value: sourceId,
  155. options: _.union(sourcesMap[application()][sourceType()], sourcesMap[registrySlug()][sourceType()])
  156. '''}
  157. else:
  158. self.application_field.widget.attrs = {'data-bind': 'value: application'}
  159. self.registry_slug_field.widget.attrs = {'data-bind': '''
  160. optionsText: function(item){return item.text},
  161. optionsValue: function(item){return item.value},
  162. value: registrySlug
  163. '''}
  164. self.source_field.widget.attrs = {'data-bind': '''
  165. optionsText: function(item){return item.text},
  166. optionsValue: function(item){return item.value},
  167. value: sourceId,
  168. options: sourcesMap[application()][sourceType()]
  169. '''}
  170. def get_fields(self):
  171. fields = collections.OrderedDict()
  172. fields['source_type'] = self.source_type_field
  173. fields['application'] = self.application_field
  174. fields['source'] = self.source_field
  175. fields['registry_slug'] = self.registry_slug_field
  176. return fields
  177. def get_crispy_filed_help_texts(self):
  178. return {
  179. "source_type": _(
  180. "<strong>Form</strong>: Display data from form submissions.<br/>"
  181. "<strong>Case</strong>: Display data from your cases. You must be using case management for this "
  182. "option."),
  183. "application": _("Which application should the data come from?"),
  184. "registry_slug": _("Select the data registry containing the data you wish to access in the report"),
  185. "source": _("Choose the case type or form from which to retrieve data for this report."),
  186. }
  187. def get_crispy_fields(self):
  188. help_texts = self.get_crispy_filed_help_texts()
  189. return [
  190. hqcrispy.FieldWithHelpBubble(name, help_bubble_text=help_text)
  191. for name, help_text in help_texts.items()
  192. ]
  193. def get_app_source(self, data_dict):
  194. return DataSource(data_dict['application'], data_dict['source_type'], data_dict['source'],
  195. data_dict['registry_slug'])
  196. def get_app_sources(domain):
  197. apps = get_apps_in_domain(domain, include_remote=False)
  198. return {
  199. app._id: {
  200. "name": app.name,
  201. "case": [{"text": t, "value": t} for t in app.get_case_types()],
  202. "form": [
  203. {
  204. "text": '{} / {}'.format(form.get_module().default_name(), form.default_name()),
  205. "value": form.get_unique_id()
  206. } for form in app.get_forms()
  207. ]
  208. }
  209. for app in apps
  210. }
  211. def get_registry_case_sources(domain):
  212. return {
  213. registry.slug: {
  214. "name": registry.name,
  215. "case": [{"text": t, "value": t} for t in registry.wrapped_schema.case_types],
  216. "form": []
  217. }
  218. for registry in DataRegistry.objects.visible_to_domain(domain)
  219. }
  220. def get_dropdown_options(domain, all_sources, registry_permission_checker):
  221. registry_options = get_data_registry_dropdown_options(domain, permission_checker=registry_permission_checker)
  222. registry_options += [{'slug': '', 'name': ''}]
  223. return{
  224. "app": {
  225. "true": [{"text": source['name'], "value": app_id} for app_id, source in all_sources.items()],
  226. "false": [{"text": '--------', "value": ''}],
  227. "": [{"text": '--------', "value": ''}]
  228. },
  229. "registry": {
  230. "true": [{"text": '--------', "value": ''}],
  231. "false": [{"text": r["name"], "value": r["slug"]} for r in registry_options],
  232. "": [{"text": '--------', "value": ''}]
  233. }
  234. }
  235. def sort_tuple_field_choices_by_name(tuple_lists):
  236. return sorted(tuple_lists, key=lambda id_name_tuple: (id_name_tuple[1] or '').lower())
  237. class ApplicationDataRMIHelper(object):
  238. """
  239. ApplicationDataRMIHelper is meant to generate the response for
  240. corehq.apps.export.views.get_app_data_drilldown_values
  241. """
  242. UNKNOWN_SOURCE = '_unknown'
  243. UNKNOWN_MODULE_ID = '_unknown_module'
  244. APP_TYPE_ALL = 'all'
  245. APP_TYPE_DELETED = 'deleted'
  246. APP_TYPE_REMOTE = 'remote'
  247. APP_TYPE_NONE = 'no_app'
  248. APP_TYPE_UNKNOWN = 'unknown'
  249. def __init__(self, domain, user, as_dict=True):
  250. self.domain = domain
  251. self.user = user
  252. self.as_dict = as_dict
  253. self.form_labels = AppFormRMIPlaceholder(
  254. application=_("Application"),
  255. module=_("Menu"),
  256. form=_("Form"),
  257. )
  258. self.form_placeholders = AppFormRMIPlaceholder(
  259. application=_("Select Application"),
  260. module=_("Select Menu"),
  261. form=_("Select Form"),
  262. )
  263. self.case_placeholders = AppCaseRMIPlaceholder(
  264. application=_("Select Application"),
  265. case_type=_("Select Case Type"),
  266. )
  267. if self.as_dict:
  268. self.form_labels = self.form_labels._asdict()
  269. self.form_placeholders = self.form_placeholders._asdict()
  270. self.case_placeholders = self.case_placeholders._asdict()
  271. def _get_unknown_form_possibilities(self):
  272. possibilities = collections.defaultdict(list)
  273. for app in get_exports_by_application(self.domain):
  274. # index by xmlns
  275. x = app['value']
  276. x['has_app'] = True
  277. possibilities[app['key'][2]].append(x)
  278. return possibilities
  279. def _attach_unknown_suggestions(self, unknown_forms):
  280. """If there are any unknown forms, try and find the best possible matches
  281. from deleted apps or copied apps. If no suggestion is found, say so
  282. but provide the xmlns.
  283. """
  284. if unknown_forms:
  285. possibilities = self._get_unknown_form_possibilities()
  286. class AppCache(dict):
  287. def __init__(self, domain):
  288. super(AppCache, self).__init__()
  289. self.domain = domain
  290. def __getitem__(self, item):
  291. if item not in self:
  292. try:
  293. self[item] = get_app(app_id=item, domain=self.domain)
  294. except Http404:
  295. pass
  296. return super(AppCache, self).__getitem__(item)
  297. app_cache = AppCache(self.domain)
  298. for form in unknown_forms:
  299. app = None
  300. if form['app']['id']:
  301. try:
  302. app = app_cache[form['app']['id']]
  303. form['has_app'] = True
  304. except KeyError:
  305. form['app_does_not_exist'] = True
  306. form['possibilities'] = possibilities[form['xmlns']]
  307. if form['possibilities']:
  308. form['duplicate'] = True
  309. else:
  310. if app.domain != self.domain:
  311. logging.error("submission tagged with app from wrong domain: %s" % app.get_id)
  312. else:
  313. if app.copy_of:
  314. try:
  315. app = app_cache[app.copy_of]
  316. form['app_copy'] = {'id': app.get_id, 'name': app.name}
  317. except KeyError:
  318. form['app_copy'] = {'id': app.copy_of, 'name': '?'}
  319. if app.is_deleted():
  320. form['app_deleted'] = {'id': app.get_id}
  321. try:
  322. app_forms = app.get_xmlns_map()[form['xmlns']]
  323. except AttributeError:
  324. # it's a remote app
  325. app_forms = None
  326. form['has_app'] = True
  327. if app_forms:
  328. app_form = app_forms[0]
  329. if app_form.doc_type == 'UserRegistrationForm':
  330. form['is_user_registration'] = True
  331. else:
  332. app_module = app_form.get_module()
  333. form['module'] = app_module
  334. form['form'] = app_form
  335. form['show_xmlns'] = False
  336. if not form.get('app_copy') and not form.get('app_deleted'):
  337. form['no_suggestions'] = True
  338. if app:
  339. form['app'] = {'id': app.get_id, 'name': app.name, 'langs': app.langs}
  340. else:
  341. form['possibilities'] = possibilities[form['xmlns']]
  342. if form['possibilities']:
  343. form['duplicate'] = True
  344. else:
  345. form['no_suggestions'] = True
  346. return unknown_forms
  347. @staticmethod
  348. def _sort_key_form(form):
  349. app_id = form['app']['id']
  350. if form.get('has_app', False):
  351. order = 0 if not form.get('app_deleted') else 1
  352. app_name = form['app']['name']
  353. module = form.get('module')
  354. if module:
  355. # module is sometimes wrapped json, sometimes a dict!
  356. module_id = module['id'] if 'id' in module else module.id
  357. else:
  358. module_id = -1 if form.get('is_user_registration') else 1000
  359. app_form = form.get('form')
  360. if app_form:
  361. # app_form is sometimes wrapped json, sometimes a dict!
  362. form_id = app_form['id'] if 'id' in app_form else app_form.id
  363. else:
  364. form_id = -1
  365. return (order, app_name, app_id, module_id, form_id)
  366. else:
  367. form_xmlns = form['xmlns']
  368. return (2, form_xmlns, app_id)
  369. @property
  370. @memoized
  371. def _all_forms(self):
  372. forms = []
  373. unknown_forms = []
  374. for f in get_exports_by_form(self.domain):
  375. form = f['value']
  376. if form.get('app_deleted') and not form.get('submissions'):
  377. continue
  378. if 'app' in form:
  379. form['has_app'] = True
  380. forms.append(form)
  381. else:
  382. app_id = f['key'][1] or ''
  383. form['app'] = {
  384. 'id': app_id
  385. }
  386. form['has_app'] = False
  387. form['show_xmlns'] = True
  388. unknown_forms.append(form)
  389. forms.extend(self._attach_unknown_suggestions(unknown_forms))
  390. return sorted(forms, key=self._sort_key_form)
  391. @property
  392. @memoized
  393. def _no_app_forms(self):
  394. return [f for f in self._all_forms if not f.get('has_app', False)]
  395. @property
  396. @memoized
  397. def _remote_app_forms(self):
  398. return [f for f in self._all_forms if f.get('has_app', False) and f.get('show_xmlns', False)]
  399. @property
  400. @memoized
  401. def _deleted_app_forms(self):
  402. return [f for f in self._all_forms if f.get('has_app', False) and f.get('app_deleted') and not f.get('show_xmlns', False)]
  403. @property
  404. @memoized
  405. def _available_app_forms(self):
  406. return [f for f in self._all_forms if f.get('has_app', False) and not f.get('app_deleted') and not f.get('show_xmlns', False)]
  407. @property
  408. @memoized
  409. def _unknown_forms(self):
  410. return itertools.chain(self._deleted_app_forms, self._remote_app_forms, self._no_app_forms)
  411. def _get_app_type_choices_for_forms(self, as_dict=True):
  412. choices = [(_("Applications"), self.APP_TYPE_ALL)]
  413. if self._remote_app_forms or self._deleted_app_forms:
  414. choices.append((_("Unknown"), self.APP_TYPE_UNKNOWN))
  415. choices = [RMIDataChoice(id=c[1], text=c[0], data={}) for c in choices]
  416. if as_dict:
  417. choices = [c._asdict() for c in choices]
  418. return choices
  419. def _get_app_type_choices_for_cases(self, has_unknown_case_types=False):
  420. choices = [(_("Applications"), self.APP_TYPE_ALL)]
  421. if has_unknown_case_types:
  422. choices.append((_("Unknown"), self.APP_TYPE_UNKNOWN))
  423. choices = [RMIDataChoice(id=choice[1], text=choice[0], data={}) for choice in choices]
  424. return [choice._asdict() for choice in choices]
  425. @staticmethod
  426. def _get_unique_choices(choices):
  427. final_choices = collections.defaultdict(list)
  428. for k, val_list in choices.items():
  429. new_val_ids = []
  430. final_choices[k] = []
  431. for v in val_list:
  432. if v.id not in new_val_ids:
  433. new_val_ids.append(v.id)
  434. final_choices[k].append(v)
  435. return final_choices
  436. def _get_applications_by_type(self, as_dict=True):
  437. apps_by_type = (
  438. (self.APP_TYPE_ALL, self._available_app_forms),
  439. (self.APP_TYPE_UNKNOWN, self._unknown_forms)
  440. )
  441. _app_fmt = lambda c: (c[0], [RMIDataChoice(
  442. f['app']['id'] if f.get('has_app', False) else self.UNKNOWN_SOURCE,
  443. f['app']['name'] if f.get('has_app', False) else _("Unknown Application"),
  444. f
  445. ) for f in c[1]])
  446. apps_by_type = list(map(_app_fmt, apps_by_type))
  447. apps_by_type = dict(apps_by_type)
  448. apps_by_type = self._get_unique_choices(apps_by_type)
  449. # include restore URL for deleted apps
  450. for app in apps_by_type[self.APP_TYPE_DELETED]:
  451. app.data['restoreUrl'] = reverse('view_app', args=[self.domain, app.id])
  452. if as_dict:
  453. apps_by_type = self._map_chosen_by_choice_as_dict(apps_by_type)
  454. return apps_by_type
  455. @staticmethod
  456. def _map_chosen_by_choice_as_dict(chosen_by_choice):
  457. for k, v in chosen_by_choice.items():
  458. chosen_by_choice[k] = [f._asdict() for f in v]
  459. return chosen_by_choice
  460. @staticmethod
  461. def _get_item_name(item, has_app, app_langs, default_name):
  462. item_name = None
  463. if has_app and item is not None:
  464. for app_lang in app_langs:
  465. item_name = item['name'].get(app_lang)
  466. if item_name:
  467. break
  468. # As last resort try english
  469. if not item_name:
  470. item_name = item['name'].get('en')
  471. return item_name or default_name
  472. def _get_modules_and_forms(self, as_dict=True):
  473. modules_by_app = collections.defaultdict(list)
  474. forms_by_app_by_module = {}
  475. for form in self._all_forms:
  476. has_app = form.get('has_app', False)
  477. app_langs = copy(form['app'].get('langs', []))
  478. # Move user's language to the front (if applicable)
  479. if self.user.language in app_langs:
  480. app_langs.insert(0, self.user.language)
  481. app_id = form['app']['id'] if has_app else self.UNKNOWN_SOURCE
  482. module = None
  483. module_id = self.UNKNOWN_MODULE_ID
  484. if 'module' in form:
  485. module = form['module']
  486. if has_app and module is not None:
  487. if 'id' in module:
  488. module_id = module['id']
  489. elif hasattr(module, 'id'):
  490. # module is an instance, not a dictionary. id is a
  491. # property method, not a key. (FB 285678, HI-141)
  492. module_id = module.id
  493. else:
  494. module_id = self.UNKNOWN_SOURCE
  495. module_name = self._get_item_name(
  496. module, has_app, app_langs, _("Unknown Module")
  497. )
  498. form_xmlns = form['xmlns']
  499. form_name = form_xmlns
  500. if not form.get('show_xmlns', False):
  501. form_name = self._get_item_name(
  502. form.get('form'), has_app, app_langs,
  503. "{} (potential matches)".format(form_xmlns)
  504. )
  505. module_choice = RMIDataChoice(
  506. module_id,
  507. module_name,
  508. form
  509. )
  510. form_choice = RMIDataChoice(
  511. form_xmlns,
  512. form_name,
  513. form
  514. )
  515. if as_dict:
  516. form_choice = form_choice._asdict()
  517. if app_id not in forms_by_app_by_module:
  518. forms_by_app_by_module[app_id] = collections.defaultdict(list)
  519. modules_by_app[app_id].append(module_choice)
  520. forms_by_app_by_module[app_id][module_id].append(form_choice)
  521. modules_by_app = self._get_unique_choices(modules_by_app)
  522. if as_dict:
  523. modules_by_app = self._map_chosen_by_choice_as_dict(modules_by_app)
  524. return modules_by_app, forms_by_app_by_module
  525. def get_form_rmi_response(self):
  526. """
  527. Used for creating form-based exports (XForm + app id pair).
  528. """
  529. modules_by_app, forms_by_app_by_module = self._get_modules_and_forms(self.as_dict)
  530. response = AppFormRMIResponse(
  531. app_types=self._get_app_type_choices_for_forms(self.as_dict),
  532. apps_by_type=self._get_applications_by_type(self.as_dict),
  533. modules_by_app=modules_by_app,
  534. forms_by_app_by_module=forms_by_app_by_module,
  535. labels=self.form_labels,
  536. placeholders=self.form_placeholders,
  537. )
  538. if self.as_dict:
  539. response = response._asdict()
  540. return response
  541. def _get_cases_for_apps(self, apps_by_type, as_dict=True):
  542. used_case_types = set()
  543. case_types_by_app = collections.defaultdict(list)
  544. for app_type, apps in apps_by_type.items():
  545. for app_choice in apps:
  546. if not app_choice.id == self.UNKNOWN_SOURCE:
  547. app = get_app(self.domain, app_choice.id)
  548. case_types = []
  549. if hasattr(app, 'modules'):
  550. # Add regular case types
  551. case_types = set([
  552. module.case_type
  553. for module in app.modules if module.case_type
  554. ])
  555. # Add user case if any module uses it
  556. if any([module.uses_usercase() for module in app.modules]):
  557. case_types.add(USERCASE_TYPE)
  558. used_case_types = used_case_types.union(case_types)
  559. case_types = [RMIDataChoice(
  560. id=c,
  561. text=c,
  562. data=app_choice.data
  563. ) for c in case_types]
  564. if as_dict:
  565. case_types = [c._asdict() for c in case_types]
  566. case_types_by_app[app_choice.id] = case_types
  567. all_case_types = get_case_types_for_domain_es(self.domain)
  568. unknown_case_types = all_case_types.difference(used_case_types)
  569. unknown_case_types = [RMIDataChoice(
  570. id=c,
  571. text=c,
  572. data={
  573. 'unknown': True,
  574. }
  575. ) for c in unknown_case_types]
  576. if as_dict:
  577. unknown_case_types = [c._asdict() for c in unknown_case_types]
  578. case_types_by_app[self.UNKNOWN_SOURCE] = unknown_case_types
  579. return case_types_by_app
  580. def get_case_rmi_response(self):
  581. """
  582. Used for creating case-based exports.
  583. """
  584. apps_by_type = self._get_applications_by_type(as_dict=False)
  585. case_types_by_app = self._get_cases_for_apps(apps_by_type, self.as_dict)
  586. # If there are Unknown case types, ensure that there exists an Unknown Application
  587. if case_types_by_app.get(self.UNKNOWN_SOURCE) and not apps_by_type[self.APP_TYPE_UNKNOWN]:
  588. apps_by_type[self.APP_TYPE_UNKNOWN].append(
  589. RMIDataChoice(self.UNKNOWN_SOURCE, _("Unknown Application"), {})
  590. )
  591. if self.as_dict:
  592. apps_by_type = self._map_chosen_by_choice_as_dict(apps_by_type)
  593. response = AppCaseRMIResponse(
  594. app_types=self._get_app_type_choices_for_cases(
  595. has_unknown_case_types=bool(case_types_by_app.get(self.UNKNOWN_SOURCE))
  596. ),
  597. apps_by_type=apps_by_type,
  598. case_types_by_app=case_types_by_app,
  599. placeholders=self.case_placeholders
  600. )
  601. if self.as_dict:
  602. response = response._asdict()
  603. return response
  604. def get_dual_model_rmi_response(self):
  605. response = self.get_form_rmi_response()
  606. response.update(self.get_case_rmi_response())
  607. return response