def buildProfile(self, input, survey, session): idmap = {} for profilequestion in survey.values(): if not IProfileQuestion.providedBy(profilequestion): continue idmap[profilequestion.external_id] = profilequestion if not idmap: return ({}, {}) profile = {} keuzemap = {} # Map `keuze' to profile index for facet in input.profiel.facet: question = idmap.get(facet.attrib["vraag-id"]) if question is None: continue if question.type == "optional": profile[question.id] = True keuzemap[facet.keuze.attrib["antwoord"]] = 0 elif question.type == "repeat": profile[question.id] = [] for (i, keuze) in enumerate(facet.keuze): antwoord = attr_unicode(keuze, "antwoord") profile[question.id].append(antwoord) keuzemap[keuze.attrib["antwoord"]] = i return (profile, keuzemap)
def update(self): if redirectOnSurveyUpdate(self.request): return if self.request.environ["REQUEST_METHOD"] == "POST": return self._update() context = aq_inner(self.context) module = self.request.survey.restrictedTraverse( self.context.zodb_path.split("/")) self.module = module if ((IProfileQuestion.providedBy(module) and context.depth == 2) or (ICustomRisksModule.providedBy(module) and self.phase == 'actionplan')): next = FindNextQuestion(context, filter=self.question_filter) if next is None: if self.phase == 'identification': url = "%s/actionplan" % self.request.survey.absolute_url() elif self.phase == 'evaluation': url = "%s/actionplan" % self.request.survey.absolute_url() elif self.phase == 'actionplan': url = "%s/report" % self.request.survey.absolute_url() else: url = QuestionURL(self.request.survey, next, phase=self.phase) return self.request.response.redirect(url) else: return self._update()
def exportSurvey(self, parent, survey): """ :returns: An XML node with the details of an :obj:`euphorie.content.survey`. """ node = etree.SubElement(parent, "survey") if getattr(survey, "external_id", None): node.attrib["external-id"] = survey.external_id etree.SubElement(node, "title").text = aq_parent(survey).title if StripMarkup(survey.introduction): etree.SubElement(node, "introduction").text = StripUnwanted( survey.introduction) if survey.classification_code: etree.SubElement(node, "classification-code").text = \ survey.classification_code etree.SubElement(node, "language").text = survey.language etree.SubElement(node, "evaluation-algorithm").text = \ aq_parent(survey).evaluation_algorithm etree.SubElement(node, "evaluation-optional").text = \ "true" if survey.evaluation_optional else "false" for child in survey.values(): if IProfileQuestion.providedBy(child): self.exportProfileQuestion(node, child) if IModule.providedBy(child): self.exportModule(node, child) return node
def BuildSurveyTree(survey, profile, dbsession, old_session=None): """(Re)build the survey SQL tree. The existing tree for the session is deleted before a new tree is created. :param survey: survey to build tree for :type survey: :py:class:`euphorie.content.survey.Survey` :param profile: desired profile to be used for the tree :type profile: dictionary :param dbsession: session to build tree in. Defaults to currently active session. :type dbsession: :py:class:`euphorie.client.model.SurveySession` """ dbsession.reset() for child in survey.values(): if ICustomRisksModule.providedBy(child): AddToTree(dbsession, child) # Now copy over the custom risks risks = get_custom_risks(old_session) if risks: # find the module that holds the custom risks modules = (Session.query(model.Module).filter( sql.and_( model.Module.session_id == dbsession.id, model.Module.module_id == child.id, )).all()) # there should only ever be 1 result if modules: for risk in risks: modules[0].addChild(risk) elif IProfileQuestion.providedBy(child): # Safeguard against double adding of profile questions existing = [ getattr(item, "module_id") for item in dbsession.children() ] if child.id in existing: continue p = profile.get(child.id) if not p: continue if isinstance(p, list): profile_question = AddToTree( dbsession, child, title=child.title, profile_index=-1, skip_children=True, ) for (index, title) in enumerate(p): AddToTree(profile_question, child, title=title, profile_index=index) # If we get a bool, it will be True, because of `if not p` above # Simply add the profile to the tree, don't care about locations elif isinstance(p, bool): AddToTree(dbsession, child, title=child.title) else: AddToTree(dbsession, child)
def update(self): if redirectOnSurveyUpdate(self.request): return if self.request.environ["REQUEST_METHOD"] == "POST": return self._update() context = aq_inner(self.context) module = self.request.survey.restrictedTraverse( self.context.zodb_path.split("/")) self.module = module if ( (IProfileQuestion.providedBy(module) and context.depth == 2) or (ICustomRisksModule.providedBy(module) and self.phase == 'actionplan') ): next = FindNextQuestion(context, filter=self.question_filter) if next is None: if self.phase == 'identification': url = "%s/actionplan" % self.request.survey.absolute_url() elif self.phase == 'evaluation': url = "%s/actionplan" % self.request.survey.absolute_url() elif self.phase == 'actionplan': url = "%s/report" % self.request.survey.absolute_url() else: url = QuestionURL(self.request.survey, next, phase=self.phase) return self.request.response.redirect(url) else: return self._update()
def ImportProfileQuestion(self, node, survey): profile = upload.SurveyImporter.ImportProfileQuestion(self, node, survey) if IProfileQuestion.providedBy(profile): for field in PQ_FIELDS: setattr(profile, field, upload.el_unicode(node, field.replace('_', '-'))) return profile
def getDesiredProfile(self): """Get the requested profile from the request. The profile is returned as a dictionary. The id of the profile questions are used as keys. For optional profile questions the value is a boolean. For repetable profile questions the value is a list of titles as provided by the user. This format is compatible with :py:func:`extractProfile`. :rtype: dictionary with profile answers """ profile = {} for (id, answer) in self.request.form.items(): question = self.context.get(id) if not IProfileQuestion.providedBy(question): continue if not self.request.form.get("pq{0}.present".format(id), '') == 'yes': continue if isinstance(answer, list): profile[id] = filter(None, (a.strip() for a in answer)) if not self.request.form.get("pq{0}.multiple".format(id), '') == 'yes': profile[id] = profile[id][:1] else: profile[id] = answer return profile
def ImportProfileQuestion(self, node, survey): profile = upload.SurveyImporter.ImportProfileQuestion( self, node, survey) if IProfileQuestion.providedBy(profile): for field in PQ_FIELDS: setattr(profile, field, upload.el_unicode(node, field.replace('_', '-'))) return profile
def _morph(self, child): state = getMultiAdapter((child, self.request), name="plone_context_state") return dict(id=child.id, title=child.title, url=state.view_url(), is_profile_question=IProfileQuestion.providedBy(child))
def _morph(self, child): state = getMultiAdapter( (child, self.request), name="plone_context_state") return dict(id=child.id, title=child.title, url=state.view_url(), is_profile_question=IProfileQuestion.providedBy(child))
def BuildSurveyTree(survey, profile={}, dbsession=None, old_session=None): """(Re)build the survey SQL tree. The existing tree for the session is deleted before a new tree is created. :param survey: survey to build tree for :type survey: :py:class:`euphorie.content.survey.Survey` :param profile: desired profile to be used for the tree :type profile: dictionary :param dbsession: session to build tree in. Defaults to currently active session. :type dbsession: :py:class:`euphorie.client.model.SurveySession` """ if dbsession is None: dbsession = SessionManager.session dbsession.reset() for child in survey.values(): if ICustomRisksModule.providedBy(child): AddToTree(dbsession, child) # Now copy over the custom risks risks = get_custom_risks(old_session) if risks: # find the module that holds the custom risks modules = Session.query(model.Module).filter(sql.and_( model.Module.session_id==dbsession.id, model.Module.module_id==child.id)).all() # there should only ever be 1 result if modules: for risk in risks: modules[0].addChild(risk) elif IProfileQuestion.providedBy(child): # Safeguard against double adding of profile questions existing = [ getattr(item, 'module_id') for item in dbsession.children()] if child.id in existing: continue p = profile.get(child.id) if not p: continue if isinstance(p, list): profile_question = AddToTree( dbsession, child, title=child.title, profile_index=-1, skip_children=True) for (index, title) in enumerate(p): AddToTree( profile_question, child, title=title, profile_index=index) # If we get a bool, it will be True, because of `if not p` above # Simply add the profile to the tree, don't care about locations elif isinstance(p, bool): AddToTree( dbsession, child, title=child.title) else: AddToTree(dbsession, child)
def __call__(self): # Render the page only if the user has edit rights, # otherwise redirect to the start page of the session. if not self.webhelpers.can_edit_session: return self.request.response.redirect( self.context.aq_parent.absolute_url() + "/@@start" ) if self.webhelpers.redirectOnSurveyUpdate(): return context = aq_inner(self.context) utils.setLanguage(self.request, self.survey, self.survey.language) module = self.webhelpers.traversed_session.restrictedTraverse( context.zodb_path.split("/") ) if self.request.environ["REQUEST_METHOD"] == "POST": return self.save_and_continue(module) if IProfileQuestion.providedBy(module) and context.depth == 2: if self.next_question is None: url = self.next_phase_url else: url = self.next_question_url return self.request.response.redirect(url) self.title = module.title self.module = module number_files = 0 for i in range(1, 5): number_files += getattr(self.module, "file{0}".format(i), None) and 1 or 0 self.has_files = number_files > 0 self.next_is_actionplan = not self.next_question if ICustomRisksModule.providedBy(module): template = ViewPageTemplateFile( "templates/module_identification_custom.pt" ).__get__(self, "") else: template = self.template return template()
def update(self): if redirectOnSurveyUpdate(self.request): return context = aq_inner(self.context) module = self.request.survey.restrictedTraverse( context.zodb_path.split("/")) if self.request.environ["REQUEST_METHOD"] == "POST": self.save_and_continue(module) else: if IProfileQuestion.providedBy(module) and context.depth == 2: next = FindNextQuestion(context, filter=self.question_filter) if next is None: url = "%s/actionplan" % self.request.survey.absolute_url() else: url = QuestionURL(self.request.survey, next, phase=self.phase) return self.request.response.redirect(url) elif ICustomRisksModule.providedBy(module) \ and not self.context.skip_children \ and len(self.get_custom_risks()): url = "%s/customization/%d" % ( self.request.survey.absolute_url(), int(self.context.path)) return self.request.response.redirect(url) self.tree = getTreeData(self.request, context, filter=model.NO_CUSTOM_RISKS_FILTER) self.title = module.title self.module = module number_files = 0 for i in range(1, 5): number_files += getattr(self.module, 'file{0}'.format(i), None) and 1 or 0 self.has_files = number_files > 0 self.next_is_actionplan = not FindNextQuestion( context, filter=self.question_filter) super(IdentificationView, self).update()
def update(self): if redirectOnSurveyUpdate(self.request): return context = aq_inner(self.context) module = self.request.survey.restrictedTraverse( context.zodb_path.split("/")) if self.request.environ["REQUEST_METHOD"] == "POST": self.save_and_continue(module) else: if IProfileQuestion.providedBy(module) and context.depth == 2: next = FindNextQuestion(context, filter=self.question_filter) if next is None: url = "%s/actionplan" % self.request.survey.absolute_url() else: url = QuestionURL(self.request.survey, next, phase=self.phase) return self.request.response.redirect(url) elif ICustomRisksModule.providedBy(module) \ and not self.context.skip_children \ and len(self.get_custom_risks()): url = "%s/customization/%d" % ( self.request.survey.absolute_url(), int(self.context.path)) return self.request.response.redirect(url) self.tree = getTreeData( self.request, context, filter=model.NO_CUSTOM_RISKS_FILTER) self.title = module.title self.module = module number_files = 0 for i in range(1, 5): number_files += getattr( self.module, 'file{0}'.format(i), None) and 1 or 0 self.has_files = number_files > 0 self.next_is_actionplan = not FindNextQuestion( context, filter=self.question_filter) super(IdentificationView, self).update()
def getDesiredProfile(self): """Get the requested profile from the request. The profile is returned as a dictionary. The id of the profile questions are used as keys. For optional profile questions the value is a boolean. For repetable profile questions the value is a list of titles as provided by the user. This format is compatible with :py:func:`extractProfile`. :rtype: dictionary with profile answers """ profile = {} for (id, answer) in self.request.form.items(): match = self.id_patt.match(id) if match: id = match.group(1) question = self.context.get(id) if not IProfileQuestion.providedBy(question): continue if getattr(question, 'use_location_question', True): # Ignore questions found via the id pattern if they profile # is repeatable if match: continue if not self.request.form.get("pq{0}.present".format(id), '') == 'yes': continue if isinstance(answer, list): profile[id] = filter(None, (a.strip() for a in answer)) if not self.request.form.get("pq{0}.multiple".format(id), '') == 'yes': profile[id] = profile[id][:1] else: profile[id] = answer else: profile[id] = answer in (True, 'yes') return profile
def getDesiredProfile(self): """Get the requested profile from the request. The profile is returned as a dictionary. The id of the profile questions are used as keys. For optional profile questions the value is a boolean. For repetable profile questions the value is a list of titles as provided by the user. This format is compatible with :py:func:`extractProfile`. :rtype: dictionary with profile answers """ profile = {} for (id, answer) in self.request.form.items(): match = self.id_patt.match(id) if match: id = match.group(1) question = self.context.get(id) if not IProfileQuestion.providedBy(question): continue if getattr(question, 'use_location_question', True): # Ignore questions found via the id pattern if they profile # is repeatable if match: continue if not self.request.form.get( "pq{0}.present".format(id), '') == 'yes': continue if isinstance(answer, list): profile[id] = filter(None, (a.strip() for a in answer)) if not self.request.form.get("pq{0}.multiple".format(id), '') == 'yes': profile[id] = profile[id][:1] else: profile[id] = answer else: profile[id] = answer in (True, 'yes') return profile
def __call__(self): # Render the page only if the user has edit rights, # otherwise redirect to the start page of the session. if not self.webhelpers.can_edit_session: return self.request.response.redirect( self.context.aq_parent.aq_parent.absolute_url() + "/@@start" ) if self.webhelpers.redirectOnSurveyUpdate(): return context = aq_inner(self.context) utils.setLanguage(self.request, self.survey, self.survey.language) if (IProfileQuestion.providedBy(self.module) and context.depth == 2) or ( ICustomRisksModule.providedBy(self.module) and self.phase == "actionplan" ): next_question = FindNextQuestion( context, self.context.session, filter=self.question_filter ) if next_question is None: if self.phase == ("identification", "evaluation"): url = ( self.webhelpers.traversed_session.absolute_url() + "/@@actionplan" ) elif self.phase == "actionplan": url = self.webhelpers.traversed_session.absolute_url() + "/@@report" else: url = "{session_url}/{path}/@@actionplan".format( session_url=self.webhelpers.traversed_session.absolute_url(), path="/".join(next_question.short_path), ) return self.request.response.redirect(url) self.title = self.context.title previous = FindPreviousQuestion( self.context, self.context.session, filter=self.question_filter ) if previous is None: self.previous_url = "%s/@@%s" % ( self.context.aq_parent.absolute_url(), self.phase, ) else: self.previous_url = "{session_url}/{path}/@@{phase}".format( session_url=self.webhelpers.traversed_session.absolute_url(), path="/".join(previous.short_path), phase=self.phase, ) next_question = FindNextQuestion( self.context, self.context.session, filter=self.question_filter ) if next_question is None: self.next_url = ( self.webhelpers.traversed_session.absolute_url() + "/@@report" ) else: self.next_url = "{session_url}/{path}/@@{phase}".format( session_url=self.webhelpers.traversed_session.absolute_url(), path="/".join(next_question.short_path), phase=self.phase, ) return self.index()
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
def update(self): super(MeasuresOverview, self).update() lang = getattr(self.request, "LANGUAGE", "en") if "-" in lang: lang = lang.split("-")[0] if self.session is not None and self.session.title != ( callable(getattr(self.context, "Title", None)) and self.context.Title() or "" ): self.session_title = self.session.title else: self.session_title = ( callable(getattr(self.context, "Title", None)) and self.context.Title() or "" ) today = date.today() this_month = date(today.year, today.month, 1) self.label_page = translate( _(u"label_page", default=u"Page"), target_language=lang ) self.label_page_of = translate( _(u"label_page_of", default=u"of"), target_language=lang ) def get_next_month(this_month): month = this_month.month + 1 year = this_month.year if month == 13: month = 1 year = year + 1 return date(year, month, 1) next_month = get_next_month(this_month) month_after_next = get_next_month(next_month) self.months = [] self.months.append(today.strftime("%b")) self.months.append(next_month.strftime("%b")) self.months.append(month_after_next.strftime("%b")) self.monthstrings = [ translate( PloneLocalesFactory( "month_{0}_abbr".format(month.lower()), default=month, ), target_language=lang, ) for month in self.months ] query = ( Session.query(model.Module, model.Risk, model.ActionPlan) .select_from(model.Module) .filter( sql.and_( model.Module.session == self.session, model.Module.profile_index > -1, ) ) .filter(sql.not_(model.SKIPPED_PARENTS)) .filter( sql.or_( model.MODULE_WITH_RISK_OR_TOP5_FILTER, model.RISK_PRESENT_OR_TOP5_FILTER, ) ) .join(model.Risk, model.Risk.parent_id == model.Module.id) .join(model.ActionPlan, model.ActionPlan.risk_id == model.Risk.id) .order_by( sql.case( value=model.Risk.priority, whens={"high": 0, "medium": 1}, else_=2 ), model.Risk.path, ) ) measures = [ t for t in query.all() if ( ( ( t[-1].planning_start is not None and t[-1].planning_start.strftime("%b") in self.months ) or ( t[-1].planning_end is not None and t[-1].planning_end.strftime("%b") in self.months ) or ( t[-1].planning_start is not None and ( t[-1].planning_end is None or t[-1].planning_end >= month_after_next ) and t[-1].planning_start <= this_month ) ) and t[1].identification not in ("n/a", "yes") and ( t[-1].responsible is not None or t[-1].requirements is not None or t[-1].budget is not None or t[-1].action is not None ) ) ] modulesdict = defaultdict(lambda: defaultdict(list)) for module, risk, action in measures: if "custom-risks" not in risk.zodb_path: risk_obj = self.survey.restrictedTraverse(risk.zodb_path.split("/")) title = risk_obj and risk_obj.problem_description or risk.title else: title = risk.title classes = [] start_month = action.planning_start and date( action.planning_start.year, action.planning_start.month, 1 ) end_month = action.planning_end and date( action.planning_end.year, action.planning_end.month, 1 ) for m in [this_month, next_month, month_after_next]: cls = None if start_month: if start_month == m: cls = "start" if end_month: if end_month == m: if end_month == (start_month is not None and start_month): cls = "start-end" else: cls = "end" elif start_month < m and end_month > m: cls = "ongoing" elif start_month < m: cls = "ongoing" elif end_month: if end_month == m: cls = "end" elif end_month > m: cls = "ongoing" classes.append(cls) modulesdict[module][risk.priority].append( { "title": title, "description": action.action, "months": [ ( action.planning_start and action.planning_start.month == m.month ) or ( action.planning_end and action.planning_end.month == m.month ) for m in [today, next_month, month_after_next] ], "classes": classes, } ) main_modules = {} for module, risks in sorted(modulesdict.items(), key=lambda m: m[0].zodb_path): module_obj = self.survey.restrictedTraverse(module.zodb_path.split("/")) if ( IProfileQuestion.providedBy(module_obj) or ICustomRisksModule.providedBy(module_obj) or module.depth >= 3 ): path = module.path[:6] else: path = module.path[:3] if path in main_modules: for prio in risks.keys(): if prio in main_modules[path]["risks"]: main_modules[path]["risks"][prio].extend(risks[prio]) else: main_modules[path]["risks"][prio] = risks[prio] else: title = module.title number = module.number main_modules[path] = {"name": title, "number": number, "risks": risks} self.modules = [] for key in sorted(main_modules.keys()): self.modules.append(main_modules[key])
def update(self): self.verify_view_permission() super(MeasuresOverview, self).update() lang = getattr(self.request, "LANGUAGE", "en") if "-" in lang: lang = lang.split("-")[0] now = datetime.now() next_month = datetime(now.year, (now.month + 1) % 12 or 12, 1) month_after_next = datetime(now.year, (now.month + 2) % 12 or 12, 1) self.months = [] self.months.append(now.strftime("%b")) self.months.append(next_month.strftime("%b")) self.months.append(month_after_next.strftime("%b")) self.monthstrings = [ translate( PloneLocalesMessageFactory("month_{0}_abbr".format( month.lower()), default=month), target_language=lang, ) for month in self.months ] query = (Session.query( Module, Risk, ActionPlan).select_from(Module).filter( sql.and_( Module.session == self.session, Module.profile_index > -1)).filter(sql.not_(SKIPPED_PARENTS)).filter( sql.or_( MODULE_WITH_RISK_OR_TOP5_FILTER, RISK_PRESENT_OR_TOP5_FILTER)).join( Risk, Risk.parent_id == Module.id).join( ActionPlan, ActionPlan.risk_id == Risk.id).order_by( sql.case(value=Risk.priority, whens={ "high": 0, "medium": 1 }, else_=2), Risk.path, )) measures = [ t for t in query.all() if ((t[-1].planning_end is not None and t[-1].planning_end.strftime("%b") in self.months) and (t[-1].planning_start is not None or t[-1].responsible is not None or t[-1].prevention_plan is not None or t[-1].requirements is not None or t[-1].budget is not None or t[-1].action_plan is not None)) ] modulesdict = defaultdict(lambda: defaultdict(list)) for module, risk, action in measures: if "custom-risks" not in risk.zodb_path: risk_obj = self.context.restrictedTraverse( risk.zodb_path.split("/")) title = risk_obj and risk_obj.problem_description or risk.title else: title = risk.title modulesdict[module][risk.priority or "low"].append({ "title": title, "description": action.action_plan, "months": [ action.planning_end and action.planning_end.month == m.month for m in [now, next_month, month_after_next] ], }) main_modules = {} for module, risks in sorted(modulesdict.items(), key=lambda m: m[0].zodb_path): module_obj = self.context.restrictedTraverse( module.zodb_path.split("/")) if (IProfileQuestion.providedBy(module_obj) or ICustomRisksModule.providedBy(module_obj) or module.depth >= 3): path = module.path[:6] else: path = module.path[:3] if path in main_modules: for prio in risks.keys(): if prio in main_modules[path]["risks"]: main_modules[path]["risks"][prio].extend(risks[prio]) else: main_modules[path]["risks"][prio] = risks[prio] else: title = module.title number = module.number if "custom-risks" in module.zodb_path: num_elems = number.split(".") number = ".".join(["Ω"] + num_elems[1:]) title = api.portal.translate(_(title)) main_modules[path] = { "name": title, "number": number, "risks": risks } self.modules = [] for key in sorted(main_modules.keys()): self.modules.append(main_modules[key])
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
def exportSurvey(self, parent, survey): """Export a survey given a parent and the survey itself :returns: An XML node with the details of an :obj:`euphorie.content.survey`. """ node = etree.SubElement(parent, "survey") if getattr(survey, "external_id", None): node.attrib["external-id"] = survey.external_id etree.SubElement(node, "title").text = aq_parent(survey).title if self.include_intro_text and StripMarkup(survey.introduction): node = self._add_string_or_html(node, survey.introduction, "introduction") if survey.classification_code: etree.SubElement( node, "classification-code").text = survey.classification_code etree.SubElement(node, "language").text = survey.language if self.is_etranslate_compatible: etree.SubElement(node, "tool_type", attrib={"value": get_tool_type(survey)}) etree.SubElement( node, "measures_text_handling", attrib={ "value": getattr(survey, "measures_text_handling", "full") }, ) etree.SubElement( node, "integrated_action_plan", attrib={ "value": "true" if getattr(survey, "integrated_action_plan", False) else "false" }, ) etree.SubElement( node, "evaluation-algorithm", attrib={"value": aq_parent(survey).evaluation_algorithm}, ) etree.SubElement( node, "evaluation-optional", attrib={ "value": "true" if survey.evaluation_optional else "false" }, ) else: etree.SubElement(node, "tool_type").text = get_tool_type(survey) etree.SubElement(node, "measures_text_handling").text = getattr( survey, "measures_text_handling", "full") etree.SubElement( node, "integrated_action_plan").text = ("true" if getattr( survey, "integrated_action_plan", False) else "false") etree.SubElement(node, "evaluation-algorithm").text = aq_parent( survey).evaluation_algorithm etree.SubElement(node, "evaluation-optional").text = ( "true" if survey.evaluation_optional else "false") if IToolCategory.providedBy(survey): tool_category = IToolCategory(survey).tool_category or [] etree.SubElement(node, "tool-category").text = ", ".join( [x.replace(",", COMMA_REPLACEMENT) for x in tool_category]) if getattr(survey, "external_site_logo", None): self.exportImage(node, survey.external_site_logo, tagname="external_site_logo") if getattr(survey, "image", None): self.exportImage(node, survey.image) for child in survey.values(): if IProfileQuestion.providedBy(child): self.exportProfileQuestion(node, child) if IModule.providedBy(child): self.exportModule(node, child) return node
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