예제 #1
0
 def media_resources(self):
     PREFIX = 'jr://file/'
     # you have to call remove_unused_mappings
     # before iterating through multimedia_map
     self.app.remove_unused_mappings()
     for path, m in self.app.multimedia_map.items():
         if path.startswith(PREFIX):
             path = path[len(PREFIX):]
         else:
             raise MediaResourceError(
                 '%s does not start with jr://file/commcare/' % path)
         path, name = split_path(path)
         # CommCare assumes jr://media/,
         # which is an alias to jr://file/commcare/media/
         # so we need to replace 'jr://file/' with '../../'
         # (this is a hack)
         path = '../../' + path
         multimedia_id = m.multimedia_id
         yield MediaResource(
             id=self.id_strings.media_resource(multimedia_id, name),
             path=path,
             version=1,
             local=None,
             remote=get_url_base() + reverse(
                 'hqmedia_download', args=[m.media_type, multimedia_id]) +
             name)
예제 #2
0
 def media_resources(self):
     PREFIX = 'jr://file/'
     # you have to call remove_unused_mappings
     # before iterating through multimedia_map
     self.app.remove_unused_mappings()
     for path, m in self.app.multimedia_map.items():
         if path.startswith(PREFIX):
             path = path[len(PREFIX):]
         else:
             raise MediaResourceError('%s does not start with jr://file/commcare/' % path)
         path, name = split_path(path)
         # CommCare assumes jr://media/,
         # which is an alias to jr://file/commcare/media/
         # so we need to replace 'jr://file/' with '../../'
         # (this is a hack)
         path = '../../' + path
         multimedia_id = m.multimedia_id
         yield MediaResource(
             id=self.id_strings.media_resource(multimedia_id, name),
             path=path,
             version=1,
             local=None,
             remote=get_url_base() + reverse(
                 'hqmedia_download',
                 args=[m.media_type, multimedia_id]
             ) + name
         )
예제 #3
0
def get_bad_usercase_path(module, form, property_):
    from corehq.apps.app_manager.util import split_path
    case_id_xpath = get_add_case_preloads_case_id_xpath(module, form)
    parent_path, property_ = split_path(property_)
    property_xpath = case_property(property_)
    id_xpath = get_case_parent_id_xpath(parent_path, case_id_xpath=case_id_xpath)
    return id_xpath.case().property(property_xpath)
예제 #4
0
    def media_resources(self):
        PREFIX = 'jr://file/'
        # you have to call remove_unused_mappings
        # before iterating through multimedia_map
        self.app.remove_unused_mappings()
        if self.app.multimedia_map is None:
            self.app.multimedia_map = {}
        for path, m in self.app.multimedia_map.items():
            unchanged_path = path
            if path.startswith(PREFIX):
                path = path[len(PREFIX):]
            else:
                raise MediaResourceError('%s does not start with jr://file/commcare/' % path)
            path, name = split_path(path)
            # CommCare assumes jr://media/,
            # which is an alias to jr://file/commcare/media/
            # so we need to replace 'jr://file/' with '../../'
            # (this is a hack)
            path = '../../' + path

            if not getattr(m, 'unique_id', None):
                # lazy migration for adding unique_id to map_item
                m.unique_id = HQMediaMapItem.gen_unique_id(m.multimedia_id, unchanged_path)

            yield MediaResource(
                id=self.id_strings.media_resource(m.unique_id, name),
                path=path,
                version=m.version,
                local=None,
                remote=get_url_base() + reverse(
                    'hqmedia_download',
                    args=[m.media_type, m.multimedia_id]
                ) + urllib.quote(name.encode('utf-8')) if name else name
            )
예제 #5
0
    def media_resources(self):
        PREFIX = 'jr://file/'
        # you have to call remove_unused_mappings
        # before iterating through multimedia_map
        self.app.remove_unused_mappings()
        if self.app.multimedia_map is None:
            self.app.multimedia_map = {}
        for path, m in self.app.multimedia_map.items():
            unchanged_path = path
            if path.startswith(PREFIX):
                path = path[len(PREFIX):]
            else:
                raise MediaResourceError(
                    '%s does not start with jr://file/commcare/' % path)
            path, name = split_path(path)
            # CommCare assumes jr://media/,
            # which is an alias to jr://file/commcare/media/
            # so we need to replace 'jr://file/' with '../../'
            # (this is a hack)
            path = '../../' + path

            if not getattr(m, 'unique_id', None):
                # lazy migration for adding unique_id to map_item
                m.unique_id = HQMediaMapItem.gen_unique_id(
                    m.multimedia_id, unchanged_path)

            yield MediaResource(
                id=self.id_strings.media_resource(m.unique_id, name),
                path=path,
                version=m.version,
                local=None,
                remote=get_url_base() + reverse(
                    'hqmedia_download', args=[m.media_type, m.multimedia_id]) +
                urllib.quote(name.encode('utf-8')) if name else name)
예제 #6
0
def get_bad_usercase_path(module, form, property_):
    from corehq.apps.app_manager.util import split_path
    case_id_xpath = get_add_case_preloads_case_id_xpath(module, form)
    parent_path, property_ = split_path(property_)
    property_xpath = case_property(property_)
    id_xpath = get_case_parent_id_xpath(parent_path, case_id_xpath=case_id_xpath)
    return id_xpath.case().property(property_xpath)
예제 #7
0
    def media_resources(self):
        PREFIX = 'jr://file/'
        # you have to call remove_unused_mappings
        # before iterating through multimedia_map
        self.app.remove_unused_mappings()
        if self.app.multimedia_map is None:
            self.app.multimedia_map = {}
        filter_multimedia = self.app.media_language_map and self.build_profile
        if filter_multimedia:
            media_list = []
            for lang in self.build_profile.langs:
                media_list += self.app.media_language_map[lang].media_refs
            requested_media = set(media_list)
        for path, m in sorted(list(self.app.multimedia_map.items()),
                              key=lambda item: item[0]):
            if filter_multimedia and m.form_media and path not in requested_media:
                continue
            unchanged_path = path
            if path.startswith(PREFIX):
                path = path[len(PREFIX):]
            else:
                raise MediaResourceError('%s does not start with %s' %
                                         (path, PREFIX))
            path, name = split_path(path)
            # CommCare assumes jr://media/,
            # which is an alias to jr://file/commcare/media/
            # so we need to replace 'jr://file/' with '../../'
            # (this is a hack)
            install_path = '../../{}'.format(path)
            local_path = './{}/{}'.format(path, name)

            if not getattr(m, 'unique_id', None):
                # lazy migration for adding unique_id to map_item
                m.unique_id = HQMediaMapItem.gen_unique_id(
                    m.multimedia_id, unchanged_path)

            descriptor = None
            if self.app.build_version and self.app.build_version >= LooseVersion(
                    '2.9'):
                type_mapping = {
                    "CommCareImage": "Image",
                    "CommCareAudio": "Audio",
                    "CommCareVideo": "Video",
                    "CommCareMultimedia": "Text"
                }
                descriptor = "{filetype} File: {name}".format(
                    filetype=type_mapping.get(m.media_type, "Media"),
                    name=name)

            yield MediaResource(
                id=id_strings.media_resource(m.unique_id, name),
                path=install_path,
                version=m.version,
                descriptor=descriptor,
                local=(local_path if self.app.enable_local_resource else None),
                remote=self.app.url_base + reverse(
                    'hqmedia_download', args=[m.media_type, m.multimedia_id]) +
                six.moves.urllib.parse.quote(name.encode('utf-8'))
                if name else name)
예제 #8
0
    def media_resources(self):
        PREFIX = 'jr://file/'
        # you have to call remove_unused_mappings
        # before iterating through multimedia_map
        self.app.remove_unused_mappings()
        if self.app.multimedia_map is None:
            self.app.multimedia_map = {}
        filter_multimedia = self.app.media_language_map and self.build_profile
        if filter_multimedia:
            media_list = []
            for lang in self.build_profile.langs:
                media_list += self.app.media_language_map[lang].media_refs
            requested_media = set(media_list)
        for path, m in self.app.multimedia_map.items():
            if filter_multimedia and m.form_media and path not in requested_media:
                continue
            unchanged_path = path
            if path.startswith(PREFIX):
                path = path[len(PREFIX):]
            else:
                raise MediaResourceError('%s does not start with %s' % (path, PREFIX))
            path, name = split_path(path)
            # CommCare assumes jr://media/,
            # which is an alias to jr://file/commcare/media/
            # so we need to replace 'jr://file/' with '../../'
            # (this is a hack)
            install_path = u'../../{}'.format(path)
            local_path = u'./{}/{}'.format(path, name)

            if not getattr(m, 'unique_id', None):
                # lazy migration for adding unique_id to map_item
                m.unique_id = HQMediaMapItem.gen_unique_id(m.multimedia_id, unchanged_path)

            descriptor = None
            if self.app.build_version >= '2.9':
                type_mapping = {"CommCareImage": "Image",
                                "CommCareAudio": "Audio",
                                "CommCareVideo": "Video",
                                "CommCareMultimedia": "Text"}
                descriptor = u"{filetype} File: {name}".format(
                    filetype=type_mapping.get(m.media_type, "Media"),
                    name=name
                )

            yield MediaResource(
                id=id_strings.media_resource(m.unique_id, name),
                path=install_path,
                version=m.version,
                descriptor=descriptor,
                local=(local_path
                       if self.app.enable_local_resource
                       else None),
                remote=self.app.url_base + reverse(
                    'hqmedia_download',
                    args=[m.media_type, m.multimedia_id]
                ) + urllib.quote(name.encode('utf-8')) if name else name
            )
예제 #9
0
 def group_updates_by_case(updates):
     """
     updates grouped by case. Example:
     input: {'name': ..., 'parent/name'}
     output: {'': {'name': ...}, 'parent': {'name': ...}}
     """
     updates_by_case = defaultdict(dict)
     for key, value in updates.items():
         path, name = split_path(key)
         updates_by_case[path][name] = value
     return updates_by_case
예제 #10
0
 def group_updates_by_case(updates):
     """
     updates grouped by case. Example:
     input: {'name': ..., 'parent/name'}
     output: {'': {'name': ...}, 'parent': {'name': ...}}
     """
     updates_by_case = defaultdict(dict)
     for key, value in updates.items():
         path, name = split_path(key)
         updates_by_case[path][name] = value
     return updates_by_case
예제 #11
0
 def make_nested_subnode(base_node, path):
     """
     path='x/y/z' will append <x><y><z/></y></x> to base_node
     """
     prev_node = base_node
     tail, head = split_path(path)
     if tail:
         for node_name in tail.split('/'):
             prev_node = prev_node.find('{x}%s' % node_name)
     node = _make_elem('{x}%s' % head)
     prev_node.append(node)
     return node
예제 #12
0
 def make_nested_subnode(base_node, path):
     """
     path='x/y/z' will append <x><y><z/></y></x> to base_node
     """
     prev_node = base_node
     tail, head = split_path(path)
     if tail:
         for node_name in tail.split('/'):
             prev_node = prev_node.find('{x}%s' % node_name)
     node = _make_elem('{x}%s' % head)
     prev_node.append(node)
     return node
예제 #13
0
    def media_resources(self):
        PREFIX = 'jr://file/'
        multimedia_map = self.app.multimedia_map_for_build(
            build_profile=self.build_profile, remove_unused=True)
        lazy_load_preference = self.app.profile.get(
            'properties', {}).get('lazy-load-video-files')
        for path, m in sorted(list(multimedia_map.items()),
                              key=lambda item: item[0]):
            unchanged_path = path
            if path.startswith(PREFIX):
                path = path[len(PREFIX):]
            else:
                raise MediaResourceError('%s does not start with %s' %
                                         (path, PREFIX))
            path, name = split_path(path)
            # CommCare assumes jr://media/,
            # which is an alias to jr://file/commcare/media/
            # so we need to replace 'jr://file/' with '../../'
            # (this is a hack)
            install_path = '../../{}'.format(path)
            local_path = './{}/{}'.format(path, name)

            load_lazily = (lazy_load_preference == 'true'
                           and m.media_type == "CommCareVideo")
            if not getattr(m, 'unique_id', None):
                # lazy migration for adding unique_id to map_item
                m.unique_id = HQMediaMapItem.gen_unique_id(
                    m.multimedia_id, unchanged_path)

            descriptor = None
            if self.app.build_version and self.app.build_version >= LooseVersion(
                    '2.9'):
                type_mapping = {
                    "CommCareImage": "Image",
                    "CommCareAudio": "Audio",
                    "CommCareVideo": "Video",
                    "CommCareMultimedia": "Text"
                }
                descriptor = "{filetype} File: {name}".format(
                    filetype=type_mapping.get(m.media_type, "Media"),
                    name=name)

            yield MediaResource(
                id=id_strings.media_resource(m.unique_id, name),
                path=install_path,
                version=m.version,
                descriptor=descriptor,
                lazy=load_lazily,
                local=(local_path if self.app.enable_local_resource else None),
                remote=self.app.url_base + reverse(
                    'hqmedia_download', args=[m.media_type, m.multimedia_id]) +
                six.moves.urllib.parse.quote(name.encode('utf-8'))
                if name else name)
예제 #14
0
    def media_resources(self):
        PREFIX = 'jr://file/'
        for path, m in sorted(list(self.app.multimedia_map_for_build(build_profile=self.build_profile).items()),
                              key=lambda item: item[0]):
            unchanged_path = path
            if path.startswith(PREFIX):
                path = path[len(PREFIX):]
            else:
                raise MediaResourceError('%s does not start with %s' % (path, PREFIX))
            path, name = split_path(path)
            # CommCare assumes jr://media/,
            # which is an alias to jr://file/commcare/media/
            # so we need to replace 'jr://file/' with '../../'
            # (this is a hack)
            install_path = '../../{}'.format(path)
            local_path = './{}/{}'.format(path, name)

            if not getattr(m, 'unique_id', None):
                # lazy migration for adding unique_id to map_item
                m.unique_id = HQMediaMapItem.gen_unique_id(m.multimedia_id, unchanged_path)

            descriptor = None
            if self.app.build_version and self.app.build_version >= LooseVersion('2.9'):
                type_mapping = {"CommCareImage": "Image",
                                "CommCareAudio": "Audio",
                                "CommCareVideo": "Video",
                                "CommCareMultimedia": "Text"}
                descriptor = "{filetype} File: {name}".format(
                    filetype=type_mapping.get(m.media_type, "Media"),
                    name=name
                )

            yield MediaResource(
                id=id_strings.media_resource(m.unique_id, name),
                path=install_path,
                version=m.version,
                descriptor=descriptor,
                local=(local_path
                       if self.app.enable_local_resource
                       else None),
                remote=self.app.url_base + reverse(
                    'hqmedia_download',
                    args=[m.media_type, m.multimedia_id]
                ) + six.moves.urllib.parse.quote(name.encode('utf-8')) if name else name
            )
예제 #15
0
    def check_case_properties(self, all_names=None, subcase_names=None, case_tag=None):
        all_names = all_names or []
        subcase_names = subcase_names or []
        errors = []

        reserved_words = load_case_reserved_words()
        for key in all_names:
            try:
                validate_property(key)
            except ValueError:
                errors.append({'type': 'update_case word illegal', 'word': key, 'case_tag': case_tag})
            _, key = split_path(key)
            if key in reserved_words:
                errors.append({'type': 'update_case uses reserved word', 'word': key, 'case_tag': case_tag})

        # no parent properties for subcase
        for key in subcase_names:
            if not re.match(r'^[a-zA-Z][\w_-]*$', key):
                errors.append({'type': 'update_case word illegal', 'word': key, 'case_tag': case_tag})

        return errors
예제 #16
0
    def check_case_properties(self, all_names=None, subcase_names=None, case_tag=None):
        all_names = all_names or []
        subcase_names = subcase_names or []
        errors = []

        reserved_words = load_case_reserved_words()
        for key in all_names:
            try:
                validate_property(key)
            except ValueError:
                errors.append({'type': 'update_case word illegal', 'word': key, 'case_tag': case_tag})
            _, key = split_path(key)
            if key in reserved_words:
                errors.append({'type': 'update_case uses reserved word', 'word': key, 'case_tag': case_tag})

        # no parent properties for subcase
        for key in subcase_names:
            if not re.match(r'^[a-zA-Z][\w_-]*$', key):
                errors.append({'type': 'update_case word illegal', 'word': key, 'case_tag': case_tag})

        return errors
예제 #17
0
    def create_casexml_2(self, form):
        from corehq.apps.app_manager.util import split_path

        actions = form.active_actions()

        if form.requires == 'none' and 'open_case' not in actions and 'update_case' in actions:
            raise CaseError("To update a case you must either open a case or require a case to begin with")

        def make_case_elem(tag, attr=None):
            return _make_elem('{cx2}%s' % tag, attr)

        def make_case_block(path=''):
            case_block = ET.Element('{cx2}case'.format(**namespaces), {
                'case_id': '',
                'date_modified': '',
                'user_id': '',
                }, nsmap={
                None: namespaces['cx2'][1:-1]
            })

            self.add_bind(
                nodeset="%scase/@date_modified" % path,
                type="dateTime",
                calculate=self.resolve_path("meta/timeEnd")
            )
            self.add_bind(
                nodeset="%scase/@user_id" % path,
                calculate=self.resolve_path("meta/userID"),
            )
            return case_block

        def relevance(action):
            if action.condition.type == 'always':
                return 'true()'
            elif action.condition.type == 'if':
                return "%s = '%s'" % (self.resolve_path(action.condition.question), action.condition.answer)
            else:
                return 'false()'

        def add_create_block(case_block, action, case_name, case_type, path='',
                             delay_case_id=False):
            create_block = make_case_elem('create')
            case_block.append(create_block)
            case_type_node = make_case_elem('case_type')
            case_type_node.text = case_type
            create_block.extend([
                make_case_elem('case_name'),
                make_case_elem('owner_id'),
                case_type_node,
            ])
            self.add_bind(
                nodeset='%scase' % path,
                relevant=relevance(action),
            )
            if not delay_case_id:
                self.add_setvalue(
                    ref='%scase/@case_id' % path,
                    value='uuid()',
                )
            else:
                self.add_bind(
                    nodeset='%scase/@case_id' % path,
                    calculate='uuid()',
                    # relevant='count(%scase) > 0' % path,
                )
            self.add_bind(
                nodeset="%scase/create/case_name" % path,
                calculate=self.resolve_path(case_name),
            )

            if form.get_app().case_sharing:
                self.add_instance('groups', src='jr://fixture/user-groups')
                self.add_setvalue(
                    ref="%scase/create/owner_id" % path,
                    value="instance('groups')/groups/group/@id"
                )
            else:
                self.add_bind(
                    nodeset="%scase/create/owner_id" % path,
                    calculate=self.resolve_path("meta/userID"),
                )

            if not case_name:
                raise CaseError("Please set 'Name according to question'. "
                                "This will give each case a 'name' attribute")
            self.add_bind(
                nodeset=case_name,
                required="true()",
            )

        def add_update_block(case_block, updates, path=''):
            update_block = make_case_elem('update')
            case_block.append(update_block)
            update_mapping = {}

            if updates:
                for key, value in updates.items():
                    if key == 'name':
                        key = 'case_name'
                    update_mapping[key] = value

            for key in sorted(update_mapping.keys()):
                update_block.append(make_case_elem(key))

            for key, q_path in sorted(update_mapping.items()):
                self.add_bind(
                    nodeset="%scase/update/%s" % (path, key),
                    calculate=self.resolve_path(q_path),
                    relevant=("count(%s) > 0" % self.resolve_path(q_path))
                )

        def add_close_block(case_block, action=None, path=''):
            case_block.append(make_case_elem('close'))
            self.add_bind(
                nodeset="%scase/close" % path,
                relevant=relevance(action) if action else 'true()',
            )

        def get_case_parent_xpath(parent_path):
            xpath = SESSION_CASE_ID.case()
            if parent_path:
                for parent_name in parent_path.split('/'):
                    xpath = xpath.index_id(parent_name).case()
            return xpath

        delegation_case_block = None
        if not actions or (form.requires == 'none' and 'open_case' not in actions):
            case_block = None
        else:
            extra_updates = {}
            needs_casedb_instance = False

            case_block = make_case_block()
            if form.requires != 'none':
                def make_delegation_stub_case_block():
                    path = 'cc_delegation_stub/'
                    DELEGATION_ID = 'delegation_id'
                    outer_block = _make_elem('{x}cc_delegation_stub', {DELEGATION_ID: ''})
                    delegation_case_block = make_case_block(path)
                    add_close_block(delegation_case_block)
                    session_delegation_id = "instance('commcaresession')/session/data/%s" % DELEGATION_ID
                    path_to_delegation_id = self.resolve_path("%s@%s" % (path, DELEGATION_ID))
                    self.add_setvalue(
                        ref="%s@%s" % (path, DELEGATION_ID),
                        value="if(count({d}) = 1, {d}, '')".format(d=session_delegation_id),
                    )
                    self.add_bind(
                        nodeset="%scase" % path,
                        relevant="%s != ''" % path_to_delegation_id,
                    )
                    self.add_bind(
                        nodeset="%scase/@case_id" % path,
                        calculate=path_to_delegation_id
                    )
                    outer_block.append(delegation_case_block)
                    return outer_block


                if form.get_module().task_list.show:
                    delegation_case_block = make_delegation_stub_case_block()

            if 'open_case' in actions:
                open_case_action = actions['open_case']
                add_create_block(case_block, open_case_action, case_name=open_case_action.name_path, case_type=form.get_case_type())
                if 'external_id' in actions['open_case'] and actions['open_case'].external_id:
                    extra_updates['external_id'] = actions['open_case'].external_id
            else:
                self.add_bind(
                    nodeset="case/@case_id",
                    calculate=SESSION_CASE_ID,
                )

            if 'update_case' in actions or extra_updates:
                def group_updates_by_case(updates):
                    """
                    updates grouped by case. Example:
                    input: {'name': ..., 'parent/name'}
                    output: {'': {'name': ...}, 'parent': {'name': ...}}
                    """
                    updates_by_case = defaultdict(dict)
                    for key, value in updates.items():
                        path, name = split_path(key)
                        updates_by_case[path][name] = value
                    return updates_by_case
                updates_by_case = group_updates_by_case(
                    getattr(actions.get('update_case'), 'update', {})
                )
                updates_by_case[''].update(extra_updates)
                if '' in updates_by_case:
                    # 90% use-case
                    basic_updates = updates_by_case.pop('')
                    add_update_block(case_block, basic_updates)
                if updates_by_case:
                    def make_nested_subnode(base_node, path):
                        """
                        path='x/y/z' will append <x><y><z/></y></x> to base_node
                        """
                        prev_node = base_node
                        tail, head = split_path(path)
                        if tail:
                            for node_name in tail.split('/'):
                                prev_node = prev_node.find('{x}%s' % node_name)
                        node = _make_elem('{x}%s' % head)
                        prev_node.append(node)
                        return node

                    def make_parent_case_block(node_path, parent_path):
                        case_block = make_case_block(node_path)
                        xpath = get_case_parent_xpath(parent_path)
                        self.add_bind(
                            nodeset='%scase/@case_id' % node_path,
                            calculate=xpath.property('@case_id'),
                        )
                        return case_block

                    base_node = _make_elem('{x}parents')
                    self.data_node.append(base_node)
                    for parent_path, updates in sorted(updates_by_case.items()):
                        node = make_nested_subnode(base_node, parent_path)
                        node_path = 'parents/%s/' % parent_path
                        parent_case_block = make_parent_case_block(node_path,
                                                                   parent_path)
                        add_update_block(parent_case_block, updates, node_path)
                        node.append(parent_case_block)

            if 'close_case' in actions:
                add_close_block(case_block, actions['close_case'])

            if 'case_preload' in actions:
                needs_casedb_instance = True
                for nodeset, property in actions['case_preload'].preload.items():
                    parent_path, property = split_path(property)
                    property_xpath = {
                        'name': 'case_name',
                        'owner_id': '@owner_id'
                    }.get(property, property)

                    xpath = get_case_parent_xpath(parent_path)
                    self.add_setvalue(
                        ref=nodeset,
                        value=xpath.property(property_xpath),
                    )
            if needs_casedb_instance:
                self.add_instance('casedb', src='jr://instance/casedb')

        if 'subcases' in actions:
            for i, subcase in enumerate(actions['subcases']):
                if subcase.repeat_context:
                    name = subcase.repeat_context
                    _xpath = '/{x}'.join(subcase.repeat_context.split('/'))[1:]
                    subcase_node = self.instance_node.find(_xpath)
                else:
                    name = 'subcase_%s' % i
                    subcase_node = _make_elem('{x}%s' % name)
                    self.data_node.append(subcase_node)

                path = '%s/' % name
                subcase_block = make_case_block(path)
                subcase_node.insert(0, subcase_block)
                add_create_block(
                    subcase_block,
                    subcase,
                    case_name=subcase.case_name,
                    case_type=subcase.case_type,
                    path=path,
                    delay_case_id=bool(subcase.repeat_context),
                )

                add_update_block(subcase_block, subcase.case_properties, path=path)

                if case_block is not None and subcase.case_type != form.get_case_type():
                    index_node = make_case_elem('index')
                    parent_index = make_case_elem('parent', {'case_type': form.get_case_type()})
                    self.add_bind(
                        nodeset='%s/case/index/parent' % name,
                        calculate=self.resolve_path("case/@case_id"),
                    )
                    index_node.append(parent_index)
                    subcase_block.append(index_node)

        # always needs session instance for meta
        self.add_instance('commcaresession', src='jr://instance/session')

        case = self.case_node
        case_parent = self.data_node

        if case_block is not None:
            if case.exists():
                raise XFormError("You cannot use the Case Management UI if you already have a case block in your form.")
            else:
                case_parent.append(case_block)
                if delegation_case_block is not None:
                    case_parent.append(delegation_case_block)

        if not case_parent.exists():
            raise XFormError("Couldn't get the case XML from one of your forms. "
                             "A common reason for this is if you don't have the "
                             "xforms namespace defined in your form. Please verify "
                             'that the xmlns="http://www.w3.org/2002/xforms" '
                             "attribute exists in your form.")
예제 #18
0
    def create_casexml_2(self, form):
        from corehq.apps.app_manager.util import split_path

        actions = form.active_actions()

        if form.requires == 'none' and 'open_case' not in actions and 'update_case' in actions:
            raise CaseError(
                "To update a case you must either open a case or require a case to begin with"
            )

        def make_case_elem(tag, attr=None):
            return _make_elem('{cx2}%s' % tag, attr)

        def make_case_block(path=''):
            case_block = ET.Element('{cx2}case'.format(**namespaces), {
                'case_id': '',
                'date_modified': '',
                'user_id': '',
            },
                                    nsmap={None: namespaces['cx2'][1:-1]})

            self.add_bind(nodeset="%scase/@date_modified" % path,
                          type="dateTime",
                          calculate=self.resolve_path("meta/timeEnd"))
            self.add_bind(
                nodeset="%scase/@user_id" % path,
                calculate=self.resolve_path("meta/userID"),
            )
            return case_block

        def relevance(action):
            if action.condition.type == 'always':
                return 'true()'
            elif action.condition.type == 'if':
                return "%s = '%s'" % (self.resolve_path(
                    action.condition.question), action.condition.answer)
            else:
                return 'false()'

        def add_create_block(case_block,
                             action,
                             case_name,
                             case_type,
                             path=''):
            create_block = make_case_elem('create')
            case_block.append(create_block)
            case_type_node = make_case_elem('case_type')
            case_type_node.text = case_type
            create_block.extend([
                make_case_elem('case_name'),
                make_case_elem('owner_id'),
                case_type_node,
            ])
            self.add_bind(
                nodeset='%scase' % path,
                relevant=relevance(action),
            )
            self.add_setvalue(
                ref="%scase/@case_id" % path,
                value="uuid()",
            )
            self.add_bind(
                nodeset="%scase/create/case_name" % path,
                calculate=self.resolve_path(case_name),
            )

            if form.get_app().case_sharing:
                self.add_instance('groups', src='jr://fixture/user-groups')
                self.add_setvalue(ref="%scase/create/owner_id" % path,
                                  value="instance('groups')/groups/group/@id")
            else:
                self.add_bind(
                    nodeset="%scase/create/owner_id" % path,
                    calculate=self.resolve_path("meta/userID"),
                )

            if not case_name:
                raise CaseError("Please set 'Name according to question'. "
                                "This will give each case a 'name' attribute")
            self.add_bind(
                nodeset=case_name,
                required="true()",
            )

        def add_update_block(case_block, updates, path=''):
            update_block = make_case_elem('update')
            case_block.append(update_block)
            update_mapping = {}

            if updates:
                for key, value in updates.items():
                    if key == 'name':
                        key = 'case_name'
                    update_mapping[key] = value

            for key in sorted(update_mapping.keys()):
                update_block.append(make_case_elem(key))

            for key, q_path in sorted(update_mapping.items()):
                self.add_bind(nodeset="%scase/update/%s" % (path, key),
                              calculate=self.resolve_path(q_path),
                              relevant=("count(%s) > 0" %
                                        self.resolve_path(q_path)))

        def add_close_block(case_block, action=None, path=''):
            case_block.append(make_case_elem('close'))
            self.add_bind(
                nodeset="%scase/close" % path,
                relevant=relevance(action) if action else 'true()',
            )

        def get_case_parent_xpath(parent_path):
            xpath = SESSION_CASE_ID.case()
            if parent_path:
                for parent_name in parent_path.split('/'):
                    xpath = xpath.index_id(parent_name).case()
            return xpath

        delegation_case_block = None
        if not actions or (form.requires == 'none'
                           and 'open_case' not in actions):
            case_block = None
        else:
            extra_updates = {}
            needs_casedb_instance = False

            case_block = make_case_block()
            if form.requires != 'none':

                def make_delegation_stub_case_block():
                    path = 'cc_delegation_stub/'
                    DELEGATION_ID = 'delegation_id'
                    outer_block = _make_elem('{x}cc_delegation_stub',
                                             {DELEGATION_ID: ''})
                    delegation_case_block = make_case_block(path)
                    add_close_block(delegation_case_block)
                    session_delegation_id = "instance('commcaresession')/session/data/%s" % DELEGATION_ID
                    path_to_delegation_id = self.resolve_path(
                        "%s@%s" % (path, DELEGATION_ID))
                    self.add_setvalue(
                        ref="%s@%s" % (path, DELEGATION_ID),
                        value="if(count({d}) = 1, {d}, '')".format(
                            d=session_delegation_id),
                    )
                    self.add_bind(
                        nodeset="%scase" % path,
                        relevant="%s != ''" % path_to_delegation_id,
                    )
                    self.add_bind(nodeset="%scase/@case_id" % path,
                                  calculate=path_to_delegation_id)
                    outer_block.append(delegation_case_block)
                    return outer_block

                if form.get_module().task_list.show:
                    delegation_case_block = make_delegation_stub_case_block()

            if 'open_case' in actions:
                open_case_action = actions['open_case']
                add_create_block(case_block,
                                 open_case_action,
                                 case_name=open_case_action.name_path,
                                 case_type=form.get_case_type())
                if 'external_id' in actions['open_case'] and actions[
                        'open_case'].external_id:
                    extra_updates['external_id'] = actions[
                        'open_case'].external_id
            else:
                self.add_bind(
                    nodeset="case/@case_id",
                    calculate=SESSION_CASE_ID,
                )

            if 'update_case' in actions or extra_updates:

                def group_updates_by_case(updates):
                    """
                    updates grouped by case. Example:
                    input: {'name': ..., 'parent/name'}
                    output: {'': {'name': ...}, 'parent': {'name': ...}}
                    """
                    updates_by_case = defaultdict(dict)
                    for key, value in updates.items():
                        path, name = split_path(key)
                        updates_by_case[path][name] = value
                    return updates_by_case

                updates_by_case = group_updates_by_case(
                    getattr(actions.get('update_case'), 'update', {}))
                updates_by_case[''].update(extra_updates)
                if '' in updates_by_case:
                    # 90% use-case
                    basic_updates = updates_by_case.pop('')
                    add_update_block(case_block, basic_updates)
                if updates_by_case:

                    def make_nested_subnode(base_node, path):
                        """
                        path='x/y/z' will append <x><y><z/></y></x> to base_node
                        """
                        prev_node = base_node
                        tail, head = split_path(path)
                        if tail:
                            for node_name in tail.split('/'):
                                prev_node = prev_node.find('{x}%s' % node_name)
                        node = _make_elem('{x}%s' % head)
                        prev_node.append(node)
                        return node

                    def make_parent_case_block(node_path, parent_path):
                        case_block = make_case_block(node_path)
                        xpath = get_case_parent_xpath(parent_path)
                        self.add_bind(
                            nodeset='%scase/@case_id' % node_path,
                            calculate=xpath.property('@case_id'),
                        )
                        return case_block

                    base_node = _make_elem('{x}parents')
                    self.data_node.append(base_node)
                    for parent_path, updates in sorted(
                            updates_by_case.items()):
                        node = make_nested_subnode(base_node, parent_path)
                        node_path = 'parents/%s/' % parent_path
                        parent_case_block = make_parent_case_block(
                            node_path, parent_path)
                        add_update_block(parent_case_block, updates, node_path)
                        node.append(parent_case_block)

            if 'close_case' in actions:
                add_close_block(case_block, actions['close_case'])

            if 'case_preload' in actions:
                needs_casedb_instance = True
                for nodeset, property in actions['case_preload'].preload.items(
                ):
                    parent_path, property = split_path(property)
                    property_xpath = {
                        'name': 'case_name',
                        'owner_id': '@owner_id'
                    }.get(property, property)

                    xpath = get_case_parent_xpath(parent_path)
                    self.add_setvalue(
                        ref=nodeset,
                        value=xpath.property(property_xpath),
                    )
            if needs_casedb_instance:
                self.add_instance('casedb', src='jr://instance/casedb')

        if 'subcases' in actions:
            for i, subcase in enumerate(actions['subcases']):
                name = 'subcase_%s' % i
                path = '%s/' % name
                subcase_node = _make_elem('{x}%s' % name)
                subcase_block = make_case_block(path)

                add_create_block(subcase_block,
                                 subcase,
                                 case_name=subcase.case_name,
                                 case_type=subcase.case_type,
                                 path=path)

                add_update_block(subcase_block,
                                 subcase.case_properties,
                                 path=path)

                if case_block is not None and subcase.case_type != form.get_case_type(
                ):
                    index_node = make_case_elem('index')
                    parent_index = make_case_elem(
                        'parent', {'case_type': form.get_case_type()})
                    self.add_bind(
                        nodeset='%s/case/index/parent' % name,
                        calculate=self.resolve_path("case/@case_id"),
                    )
                    index_node.append(parent_index)
                    subcase_block.append(index_node)

                subcase_node.append(subcase_block)
                self.data_node.append(subcase_node)

        # always needs session instance for meta
        self.add_instance('commcaresession', src='jr://instance/session')

        case = self.case_node
        case_parent = self.data_node

        if case_block is not None:
            if case.exists():
                raise XFormError(
                    "You cannot use the Case Management UI if you already have a case block in your form."
                )
            else:
                case_parent.append(case_block)
                if delegation_case_block is not None:
                    case_parent.append(delegation_case_block)