Ejemplo n.º 1
0
    def create_workbook(self):
        """Create an Excel workbook containing the all risks and measures."""
        book = Workbook()
        sheet = book.worksheets[0]
        sheet.title = translate(_("report_timeline_title", default="Timeline"),
                                context=self.request)
        survey = self.context.aq_parent

        for (column, (ntype, key, title)) in enumerate(self.columns):
            sheet.cell(row=1, column=column + 1).value = translate(
                title, context=self.request)

        row = 2
        for (module, risk, measure) in self.get_measures():
            if risk.identification in ["n/a", "yes"]:
                continue

            column = 1
            if risk.is_custom_risk:
                zodb_node = None
            else:
                zodb_node = survey.restrictedTraverse(
                    risk.zodb_path.split("/"))
            for (ntype, key, title) in self.columns:
                value = None
                if ntype == "measure":
                    value = getattr(measure, key, None)
                elif ntype == "risk":
                    value = getattr(risk, key, None)
                    if key == "priority":
                        value = self.priority_name(value)
                    elif key == "title":
                        if (getattr(zodb_node, "problem_description", None)
                                and zodb_node.problem_description.strip()):
                            value = zodb_node.problem_description
                    elif key == "number":
                        if risk.is_custom_risk:
                            num_elems = value.split(".")
                            value = ".".join(["Ω"] + num_elems[1:])
                elif ntype == "module":
                    if key == "title":
                        if risk.is_custom_risk:
                            value = utils.get_translated_custom_risks_title(
                                self.request)
                        else:
                            value = module.title
                if value is not None:
                    cell = sheet.cell(row=row, column=column)
                    if key == "number":
                        # force sting
                        cell.set_explicit_value(value)
                    else:
                        cell.value = value
                column += 1
            row += 1
        return book
Ejemplo n.º 2
0
 def get_modules(self):
     """Returns the modules for this session"""
     sql_modules = (Session.query(model.Module).filter(
         model.SurveyTreeItem.session == self.context.session, ).order_by(
             model.SurveyTreeItem.path))
     modules = []
     for sql_module in sql_modules:
         if sql_module.skip_children:
             continue
         if sql_module.zodb_path.find("custom-risks") != -1:
             module_title = get_translated_custom_risks_title(self.request)
         else:
             module_title = sql_module.title
         risks = self.get_risks_for(sql_module)
         modules.append({
             "title": module_title,
             "checked": bool(risks),
             "risks": risks,
         })
     return modules
Ejemplo n.º 3
0
    def getModules(self):
        """ Return a list of dicts of all the top-level modules and locations
            belonging to this survey.
        """
        session = Session()
        session_id = SessionManager.id
        base_url = "%s/identification" % self.request.survey.absolute_url()
        profile = extractProfile(self.request.survey, SessionManager.session)
        module_query = self.module_query(
            sessionid=session_id,
            optional_modules=len(profile) and "(%s)" % (','.join(
                ["'%s'" % k for k in profile.keys()])) or None
        )
        module_res = session.execute(module_query).fetchall()
        modules_and_profiles = {}
        for row in module_res:
            if row[0] is not None:
                if row[0].find('profile') > 0:
                    path = row[0][:3]
                    modules_and_profiles[path] = 'profile'
                else:
                    modules_and_profiles[row[0]] = ''
        module_paths = [
            p[0] for p in session.execute(module_query).fetchall() if
            p[0] is not None]
        module_paths = modules_and_profiles.keys()
        module_paths = sorted(module_paths)
        parent_node = orm.aliased(model.Module)
        titles = dict(session.query(model.Module.path, model.Module.title)
                .filter(model.Module.session_id == session_id)
                .filter(model.Module.path.in_(module_paths)))

        location_titles = dict(session.query(
                    model.Module.path,
                    parent_node.title
                ).filter(
                        model.Module.session_id == session_id).filter(
                        model.Module.path.in_(module_paths)).filter(
                        sql.and_(
                            parent_node.session_id == session_id,
                            parent_node.depth < model.Module.depth,
                            model.Module.path.like(parent_node.path + "%")
                        )
                ))
        modules = {}
        toc = {}
        title_custom_risks = utils.get_translated_custom_risks_title(self.request)

        for path in module_paths:
            number = ".".join(self.slicePath(path))
            # top-level module, always include it in the toc
            if len(path) == 3:
                title = titles[path]
                if title == 'title_other_risks':
                    title = title_custom_risks
                toc[path] = {
                    'path': path,
                    'title': title,
                    'locations': [],
                    'number': number,
                }
                # If this is a profile (aka container for locations), skip
                # adding to the list of modules
                if modules_and_profiles[path] == 'profile':
                    continue
            # sub-module (location) or location container
            else:
                if path in location_titles:
                    title = u"{0} - {1}".format(location_titles[path], titles[path])
                    toc[path[:3]]['locations'].append({
                        'path': path,
                        'title': titles[path],
                        'number': number,
                    })
                else:
                    log.warning(
                        "Status: found a path for a submodule {0} for which "
                        "there's no location title.".format(path))
                    continue

            modules[path] = {
                'path': path,
                'title': title,
                'url': '%s/%s' % (base_url, '/'.join(self.slicePath(path))),
                'todo': 0,
                'ok': 0,
                'postponed': 0,
                'risk_with_measures': 0,
                'risk_without_measures': 0,
                'number': number,
            }
        self.tocdata = toc
        return modules
Ejemplo n.º 4
0
def getTreeData(request, context, phase="identification", filter=None):
    """Assemble data for a navigation tree

    This function returns a nested dictionary structure reflecting the
    elements for a navigation tree. The tree will all sibling questions of
    the current context, the current module and all its module siblings, its
    parents up to the root module, and all modules at the root level.

    Optionally a SQLAlchemy clause can be provided, which will be used to
    filter items shown in the tree. The current item and its parents will
    always be shown.

    Each element is reflect as a dictionary item with the following keys:

    - id: the SQL object id
    - type: the SQL object type
    - number: a human presentable numbering of the item
    - title: the object title
    - current: boolean indicating if this is the current context or its
      direct parent module
    - active: boolean indicating if this is a parent node of the current
      context
    - class: CSS classes to use for this node
    - children: a list of child nodes (in the right order)
    - url: URL for this item
    """
    query = Session.query(model.SurveyTreeItem)
    title_custom_risks = utils.get_translated_custom_risks_title(request)
    root = context
    parents = []
    while root.parent_id is not None:
        parent = query.get(root.parent_id)
        parents.append(parent)
        root = parent
    parents.reverse()

    base_url = "%s/%s/" % (request.survey.absolute_url(), phase)

    def morph(obj):
        info = {'id': obj.id,
                'number': obj.number,
                'title': obj.title,
                'active': obj.path != context.path and
                                context.path.startswith(obj.path),
                'current': (obj.path == context.path),
                'current_parent': (obj.path == context.path[:-3]),
                'path': context.path,
                'children': [],
                'type': obj.type,
                'leaf_module': False,
                'depth': obj.depth,
                'url': base_url + "/".join(obj.short_path)
                }
        cls = []
        for key in ["active", "current", "current_parent"]:
            if info[key]:
                cls.append(key)

        if obj.postponed:
            cls.append("postponed")
        else:
            if isinstance(obj, model.Risk):
                if obj.identification:
                    cls.append("answered")
                if obj.identification == "no":
                    cls.append("risk")
        info["class"] = cls and " ".join(cls) or None
        return info

    # Result is always pointing to the level *above* the current level.
    # At the end it will be the virtual tree root
    result = {'children': [],
              'leaf_module': False,
              'current': False,
              'id': None,
              'title': None}
    result["class"] = None
    children = []
    for obj in context.siblings(filter=filter):
        info = morph(obj)
        if obj.type != 'risk' and obj.zodb_path.find('custom-risks') > -1:
            info['title'] = title_custom_risks
        children.append(info)
    result["children"] = children

    if isinstance(context, model.Module):
        if not context.skip_children:
            # For modules which do not skip children, include the list of
            # children.
            me = first(lambda x: x["current"], result["children"])
            children = []
            for obj in context.children(filter=filter):
                info = morph(obj)
                # XXX: The check for SurveySession is due to Euphorie tests which don't
                # have a proper canonical ZODB survey object and don't test the
                # following OiRA-specific code.
                if obj.depth == 2 \
                        and not getattr(obj, 'is_custom_risk', False) \
                        and not isinstance(request.survey, SurveySession):
                    module = request.survey.restrictedTraverse(obj.zodb_path.split('/'))
                    if IProfileQuestion.providedBy(module) and \
                            not ICustomRisksModule.providedBy(aq_parent(module)):
                        info['type'] = u'location'
                        info['children'] = [
                            morph(sub) for sub in obj.children(filter=filter)]
                children.append(info)
            me["children"] = children
            types = set([c["type"] for c in me["children"]])
            me["leaf_module"] = "risk" in types

    elif isinstance(context, model.Risk):
        # For a risk we also want to include all siblings of its module parent
        parent = parents.pop()
        siblings = []
        for obj in parent.siblings(model.Module, filter=filter):
            info = morph(obj)
            if obj.zodb_path.find('custom-risks') > -1:
                info['title'] = title_custom_risks
            siblings.append(info)
        myparent = first(lambda x: x["active"], siblings)
        myparent["children"] = result["children"]
        myparent["leaf_module"] = True
        result["children"] = siblings

    if parents:
        # Add all parents up to the root
        while len(parents) > 1:
            parent = parents.pop()
            new = morph(parent)
            if isinstance(parent, model.Module) and parent.depth == 2:
                module = request.survey.restrictedTraverse(parent.zodb_path.split('/'))
                if IProfileQuestion.providedBy(module) and \
                        not ICustomRisksModule.providedBy(aq_parent(module)):
                    new['type'] = u'location'
            new["children"] = result["children"]
            result["children"] = [new]

        # Finally list all modules at the root level
        parent = parents.pop()
        roots = []
        for obj in parent.siblings(model.Module, filter=filter):
            info = morph(obj)
            if obj.zodb_path.find('custom-risks') > -1:
                info['title'] = title_custom_risks
            roots.append(info)

        myroot = first(lambda x: x["active"], roots)
        myroot["children"] = result["children"]
        result["children"] = roots

    return result
Ejemplo n.º 5
0
    def getModules(self):
        """Return a list of dicts of all the top-level modules and locations
        belonging to this survey.
        """
        sql_session = self.sql_session
        session_id = self.session.id
        module_paths = self.getModulePaths()
        url_schema = "%s/{0}/@@identification" % self.context.absolute_url()
        parent_node = orm.aliased(model.Module)
        titles = dict(
            sql_session.query(model.Module.path, model.Module.title).filter(
                model.Module.session_id == session_id).filter(
                    model.Module.path.in_(module_paths)))

        location_titles = dict(
            sql_session.query(model.Module.path, parent_node.title).filter(
                model.Module.session_id == session_id).filter(
                    model.Module.path.in_(module_paths)).filter(
                        sql.and_(
                            parent_node.session_id == session_id,
                            parent_node.depth < model.Module.depth,
                            model.Module.path.like(parent_node.path + "%"),
                        )))
        modules = {}
        toc = {}
        title_custom_risks = utils.get_translated_custom_risks_title(
            self.request)

        for path in module_paths:
            number = ".".join(self.slicePath(path))
            # top-level module, always include it in the toc
            if len(path) == 3:
                title = titles[path]
                if (title == "title_other_risks" or title == "Other risks"
                        or title == "Custom risks"):
                    title = title_custom_risks
                toc[path] = {
                    "path": path,
                    "title": title,
                    "locations": [],
                    "number": number,
                }
                # If this is a profile (aka container for locations), skip
                # adding to the list of modules
                if self.modules_and_profiles[path] == "profile":
                    continue
            # sub-module (location) or location container
            else:
                if path in location_titles:
                    title = "{0} - {1}".format(location_titles[path],
                                               titles[path])
                    toc[path[:3]]["locations"].append({
                        "path": path,
                        "title": titles[path],
                        "number": number
                    })
                else:
                    log.warning(
                        "Status: found a path for a submodule {0} for which "
                        "there's no location title.".format(path))
                    continue

            modules[path] = {
                "path": path,
                "title": title,
                "url": url_schema.format("/".join(self.slicePath(path))),
                "todo": 0,
                "ok": 0,
                "postponed": 0,
                "risk_with_measures": 0,
                "risk_without_measures": 0,
                "number": number,
            }
        self.tocdata = toc
        return modules
Ejemplo n.º 6
0
 def slide_title(self):
     if self.is_custom and self.item_type == "module":
         return client_utils.get_translated_custom_risks_title(self.request)
     return self.context.title
Ejemplo n.º 7
0
def getTreeData(request, context, phase="identification", filter=None):
    """Assemble data for a navigation tree

    This function returns a nested dictionary structure reflecting the
    elements for a navigation tree. The tree will all sibling questions of
    the current context, the current module and all its module siblings, its
    parents up to the root module, and all modules at the root level.

    Optionally a SQLAlchemy clause can be provided, which will be used to
    filter items shown in the tree. The current item and its parents will
    always be shown.

    Each element is reflect as a dictionary item with the following keys:

    - id: the SQL object id
    - type: the SQL object type
    - number: a human presentable numbering of the item
    - title: the object title
    - current: boolean indicating if this is the current context or its
      direct parent module
    - active: boolean indicating if this is a parent node of the current
      context
    - class: CSS classes to use for this node
    - children: a list of child nodes (in the right order)
    - url: URL for this item
    """
    query = Session.query(model.SurveyTreeItem)
    title_custom_risks = utils.get_translated_custom_risks_title(request)
    root = context
    parents = []
    while root.parent_id is not None:
        parent = query.get(root.parent_id)
        parents.append(parent)
        root = parent
    parents.reverse()

    base_url = "%s/%s/" % (request.survey.absolute_url(), phase)

    def morph(obj):
        info = {
            'id':
            obj.id,
            'number':
            obj.number,
            'title':
            obj.title,
            'active':
            obj.path != context.path and context.path.startswith(obj.path),
            'current': (obj.path == context.path),
            'current_parent': (obj.path == context.path[:-3]),
            'path':
            context.path,
            'children': [],
            'type':
            obj.type,
            'leaf_module':
            False,
            'depth':
            obj.depth,
            'url':
            base_url + "/".join(obj.short_path)
        }
        cls = []
        for key in ["active", "current", "current_parent"]:
            if info[key]:
                cls.append(key)

        if obj.postponed:
            cls.append("postponed")
        else:
            if isinstance(obj, model.Risk):
                if obj.identification:
                    cls.append("answered")
                if obj.identification == "no":
                    cls.append("risk")
        info["class"] = cls and " ".join(cls) or None
        return info

    # Result is always pointing to the level *above* the current level.
    # At the end it will be the virtual tree root
    result = {
        'children': [],
        'leaf_module': False,
        'current': False,
        'id': None,
        'title': None
    }
    result["class"] = None
    children = []
    for obj in context.siblings(filter=filter):
        info = morph(obj)
        if obj.type != 'risk' and obj.zodb_path.find('custom-risks') > -1:
            info['title'] = title_custom_risks
        children.append(info)
    result["children"] = children

    if isinstance(context, model.Module):
        # If this is an optional module, check the "postponed" flag.
        # As long as the optional question has not been answered, skip
        # showing its children.
        # Only a "Yes" answer will set skip_children to False
        module = request.survey.restrictedTraverse(
            context.zodb_path.split("/"))
        if (getattr(module, 'optional', False)
                and context.postponed in (True, None)):
            context.skip_children = True
        if not context.skip_children:
            # For modules which do not skip children, include the list of
            # children.
            me = first(lambda x: x["current"], result["children"])
            children = []
            for obj in context.children(filter=filter):
                info = morph(obj)
                # XXX: The check for SurveySession is due to Euphorie tests which don't
                # have a proper canonical ZODB survey object and don't test the
                # following OiRA-specific code.
                if obj.depth == 2 \
                        and not getattr(obj, 'is_custom_risk', False) \
                        and not isinstance(request.survey, SurveySession):
                    module = request.survey.restrictedTraverse(
                        obj.zodb_path.split('/'))
                    if IProfileQuestion.providedBy(module) and \
                            not ICustomRisksModule.providedBy(aq_parent(module)):
                        info['type'] = u'location'
                        info['children'] = [
                            morph(sub) for sub in obj.children(filter=filter)
                        ]
                children.append(info)
            me["children"] = children
            types = set([c["type"] for c in me["children"]])
            me["leaf_module"] = "risk" in types

    elif isinstance(context, model.Risk):
        # For a risk we also want to include all siblings of its module parent
        parent = parents.pop()
        siblings = []
        for obj in parent.siblings(model.Module, filter=filter):
            info = morph(obj)
            if obj.zodb_path.find('custom-risks') > -1:
                info['title'] = title_custom_risks
            siblings.append(info)
        myparent = first(lambda x: x["active"], siblings)
        myparent["children"] = result["children"]
        myparent["leaf_module"] = True
        result["children"] = siblings

    if parents:
        # Add all parents up to the root
        while len(parents) > 1:
            parent = parents.pop()
            new = morph(parent)
            if isinstance(parent, model.Module) and parent.depth == 2:
                module = request.survey.restrictedTraverse(
                    parent.zodb_path.split('/'))
                if IProfileQuestion.providedBy(module) and \
                        not ICustomRisksModule.providedBy(aq_parent(module)):
                    new['type'] = u'location'
            new["children"] = result["children"]
            result["children"] = [new]

        # Finally list all modules at the root level
        parent = parents.pop()
        roots = []
        for obj in parent.siblings(model.Module, filter=filter):
            info = morph(obj)
            if obj.zodb_path.find('custom-risks') > -1:
                info['title'] = title_custom_risks
            roots.append(info)

        myroot = first(lambda x: x["active"], roots)
        myroot["children"] = result["children"]
        result["children"] = roots

    return result
Ejemplo n.º 8
0
    def getModules(self):
        """ Return a list of dicts of all the top-level modules and locations
            belonging to this survey.
        """
        sql_session = self.sql_session
        session_id = self.session.id
        module_paths = self.getModulePaths()
        base_url = "%s/identification" % self.request.survey.absolute_url()
        parent_node = orm.aliased(model.Module)
        titles = dict(
            sql_session.query(
                model.Module.path, model.Module.title
            ).filter(
                model.Module.session_id == session_id
            ).filter(
                model.Module.path.in_(module_paths)
            )
        )

        location_titles = dict(
            sql_session.query(
                model.Module.path,
                parent_node.title
            ).filter(
                model.Module.session_id == session_id
            ).filter(
                model.Module.path.in_(module_paths)
            ).filter(
                sql.and_(
                    parent_node.session_id == session_id,
                    parent_node.depth < model.Module.depth,
                    model.Module.path.like(parent_node.path + "%")
                )
            )
        )
        modules = {}
        toc = {}
        title_custom_risks = utils.get_translated_custom_risks_title(self.request)

        for path in module_paths:
            number = ".".join(self.slicePath(path))
            # top-level module, always include it in the toc
            if len(path) == 3:
                title = titles[path]
                if title == 'title_other_risks':
                    title = title_custom_risks
                toc[path] = {
                    'path': path,
                    'title': title,
                    'locations': [],
                    'number': number,
                }
                # If this is a profile (aka container for locations), skip
                # adding to the list of modules
                if self.modules_and_profiles[path] == 'profile':
                    continue
            # sub-module (location) or location container
            else:
                if path in location_titles:
                    title = u"{0} - {1}".format(location_titles[path], titles[path])
                    toc[path[:3]]['locations'].append({
                        'path': path,
                        'title': titles[path],
                        'number': number,
                    })
                else:
                    log.warning(
                        "Status: found a path for a submodule {0} for which "
                        "there's no location title.".format(path))
                    continue

            modules[path] = {
                'path': path,
                'title': title,
                'url': '%s/%s' % (base_url, '/'.join(self.slicePath(path))),
                'todo': 0,
                'ok': 0,
                'postponed': 0,
                'risk_with_measures': 0,
                'risk_without_measures': 0,
                'number': number,
            }
        self.tocdata = toc
        return modules
Ejemplo n.º 9
0
    def getModules(self):
        """ Return a list of dicts of all the top-level modules and locations
            belonging to this survey.
        """
        sql_session = self.sql_session
        session_id = self.session.id
        module_paths = self.getModulePaths()
        base_url = "%s/identification" % self.request.survey.absolute_url()
        parent_node = orm.aliased(model.Module)
        titles = dict(
            sql_session.query(model.Module.path, model.Module.title).filter(
                model.Module.session_id == session_id).filter(
                    model.Module.path.in_(module_paths)))

        location_titles = dict(
            sql_session.query(model.Module.path, parent_node.title).filter(
                model.Module.session_id == session_id).filter(
                    model.Module.path.in_(module_paths)).filter(
                        sql.and_(
                            parent_node.session_id == session_id,
                            parent_node.depth < model.Module.depth,
                            model.Module.path.like(parent_node.path + "%"))))
        modules = {}
        toc = {}
        title_custom_risks = utils.get_translated_custom_risks_title(
            self.request)

        for path in module_paths:
            number = ".".join(self.slicePath(path))
            # top-level module, always include it in the toc
            if len(path) == 3:
                title = titles[path]
                if title == 'title_other_risks':
                    title = title_custom_risks
                toc[path] = {
                    'path': path,
                    'title': title,
                    'locations': [],
                    'number': number,
                }
                # If this is a profile (aka container for locations), skip
                # adding to the list of modules
                if self.modules_and_profiles[path] == 'profile':
                    continue
            # sub-module (location) or location container
            else:
                if path in location_titles:
                    title = u"{0} - {1}".format(location_titles[path],
                                                titles[path])
                    toc[path[:3]]['locations'].append({
                        'path': path,
                        'title': titles[path],
                        'number': number,
                    })
                else:
                    log.warning(
                        "Status: found a path for a submodule {0} for which "
                        "there's no location title.".format(path))
                    continue

            modules[path] = {
                'path': path,
                'title': title,
                'url': '%s/%s' % (base_url, '/'.join(self.slicePath(path))),
                'todo': 0,
                'ok': 0,
                'postponed': 0,
                'risk_with_measures': 0,
                'risk_without_measures': 0,
                'number': number,
            }
        self.tocdata = toc
        return modules
Ejemplo n.º 10
0
def getTreeData(
    request,
    context,
    element=None,
    phase="identification",
    filter=None,
    survey=None,
    no_current=False,
):
    """Assemble data for a navigation tree

    This function returns a nested dictionary structure reflecting the
    elements for a navigation tree. The tree will all sibling questions of
    the current context, the current module and all its module siblings, its
    parents up to the root module, and all modules at the root level.

    Optionally a SQLAlchemy clause can be provided, which will be used to
    filter items shown in the tree. The current item and its parents will
    always be shown.

    Each element is reflect as a dictionary item with the following keys:

    - id: the SQL object id
    - type: the SQL object type
    - number: a human presentable numbering of the item
    - title: the object title
    - current: boolean indicating if this is the current context or its
      direct parent module
    - active: boolean indicating if this is a parent node of the current
      context
    - class: CSS classes to use for this node
    - children: a list of child nodes (in the right order)
    - url: URL for this item
    """
    if not survey:
        # Standard, real-world case
        webhelpers = api.content.get_view("webhelpers", context, request)
        survey = webhelpers._survey
        traversed_session = webhelpers.traversed_session
    else:
        # XXX Fixme
        # Only in tests...
        # In some tests in test_navigation, the view "webhelpers" cannot be found
        # for the given context. That's why we pass in the survey item directly.
        traversed_session = survey

    # This is the tree element that we start from.
    # It can be the same as the context that gets passed in, if it has an Acquisition
    # chain. On views that are called outside of the context of a module or risk,
    # e.g. the initial @@identification view, the tree-element that we find is not
    # in an acqusition context, so that we cannot use it for fetching the traversed
    # session via webhelpers.
    if not element:
        element = context

    query = Session.query(model.SurveyTreeItem)
    title_custom_risks = utils.get_translated_custom_risks_title(request)
    root = element
    parents = []
    while root.parent_id is not None:
        parent = query.get(root.parent_id)
        parents.append(parent)
        root = parent
    parents.reverse()

    def morph(obj):
        number = obj.number
        # The custom risks don't have a real number, but an Omega instead
        if obj.zodb_path.find("custom-risks") > -1:
            num_elems = number.split(".")
            number = ".".join(["Ω"] + num_elems[1:])
        info = {
            "id":
            obj.id,
            "number":
            number,
            "title":
            obj.title,
            "active": (obj.path != element.path
                       and element.path.startswith(obj.path)),
            "current": (obj.path == element.path),
            "current_parent": (obj.path == element.path[:-3]),
            "path":
            element.path,
            "children": [],
            "type":
            obj.type,
            "leaf_module":
            False,
            "depth":
            obj.depth,
            "url":
            "{session_url}/{obj_path}/@@{phase}".format(
                session_url=traversed_session.absolute_url(),
                obj_path="/".join(obj.short_path),
                phase=phase,
            ),
            "css_id":
            "",
        }
        cls = []
        for key in ["active", "current", "current_parent"]:
            if info[key]:
                if key == "current" and no_current:
                    continue
                cls.append(key)

        if obj.postponed:
            cls.append("postponed")
        else:
            if isinstance(obj, model.Risk):
                if obj.identification:
                    cls.append("answered")
                if obj.identification == "no":
                    cls.append("risk")
        info["class"] = cls and " ".join(cls) or None
        return info

    # Result is always pointing to the level *above* the current level.
    # At the end it will be the virtual tree root
    result = {
        "children": [],
        "leaf_module": False,
        "current": False,
        "id": None,
        "title": None,
    }
    result["class"] = None
    children = []
    for obj in element.siblings(filter=filter):
        info = morph(obj)
        if obj.type != "risk" and obj.zodb_path.find("custom-risks") > -1:
            info["title"] = title_custom_risks
            info["css_id"] = "other-risks"
        children.append(info)
    result["children"] = children

    if isinstance(element, model.Module):
        # If this is an optional module, check the "postponed" flag.
        # As long as the optional question has not been answered, skip
        # showing its children.
        # Only a "Yes" answer will set skip_children to False
        module = survey.restrictedTraverse(element.zodb_path.split("/"))
        # In the custom risks module, we never skip children
        # Due to historical reasons, some custom modules might be set to
        # postponed. Here, we ignore that setting.
        if ICustomRisksModule.providedBy(module):
            element.skip_children = False
        elif getattr(module, "optional",
                     False) and element.postponed in (True, None):
            element.skip_children = True
        if not element.skip_children:
            # For modules which do not skip children, include the list of
            # children.
            me = first(lambda x: x["current"], result["children"])
            children = []
            for obj in element.children(filter=filter):
                info = morph(obj)
                # XXX: The check for SurveySession is due to Euphorie tests which don't
                # have a proper canonical ZODB survey object and don't test the
                # following OiRA-specific code.
                if (obj.depth == 2
                        and not getattr(obj, "is_custom_risk", False)
                        and not isinstance(survey, SurveySession)):
                    module = survey.restrictedTraverse(
                        obj.zodb_path.split("/"))
                    if IProfileQuestion.providedBy(
                            module) and not ICustomRisksModule.providedBy(
                                aq_parent(module)):
                        info["type"] = "location"
                        info["children"] = [
                            morph(sub) for sub in obj.children(filter=filter)
                        ]
                children.append(info)
            me["children"] = children
            types = set([c["type"] for c in me["children"]])
            me["leaf_module"] = "risk" in types

    elif isinstance(element, model.Risk):
        # For a risk we also want to include all siblings of its module parent
        parent = parents.pop()
        siblings = []
        for obj in parent.siblings(model.Module, filter=filter):
            info = morph(obj)
            if obj.zodb_path.find("custom-risks") > -1:
                info["title"] = title_custom_risks
                info["css_id"] = "other-risks"
            siblings.append(info)
        myparent = first(lambda x: x["active"], siblings)
        myparent["children"] = result["children"]
        myparent["leaf_module"] = True
        result["children"] = siblings

    if parents:
        # Add all parents up to the root
        while len(parents) > 1:
            parent = parents.pop()
            new = morph(parent)
            if isinstance(parent, model.Module) and parent.depth == 2:
                module = survey.restrictedTraverse(parent.zodb_path.split("/"))
                if IProfileQuestion.providedBy(
                        module) and not ICustomRisksModule.providedBy(
                            aq_parent(module)):
                    new["type"] = "location"
            new["children"] = result["children"]
            result["children"] = [new]

        # Finally list all modules at the root level
        parent = parents.pop()
        roots = []
        for obj in parent.siblings(model.Module, filter=filter):
            info = morph(obj)
            if obj.zodb_path.find("custom-risks") > -1:
                info["title"] = title_custom_risks
            roots.append(info)

        myroot = first(lambda x: x["active"], roots)
        myroot["children"] = result["children"]
        result["children"] = roots

    return result