Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support scaled answers #715

Closed
wants to merge 50 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
039161c
Start on showing multiple answers.
mauritsvanrees Nov 24, 2023
1b6deb9
Use the use_multiple_answers bool field as condition.
mauritsvanrees Nov 24, 2023
e0e1dfa
Use repeat/answer/number as input value.
mauritsvanrees Nov 24, 2023
d562439
Support multiple_answers field client side.
mauritsvanrees Dec 1, 2023
6d1b8e3
When multiple_answers is 4 or 5, the risk is present.
mauritsvanrees Dec 1, 2023
c7aeb77
Show the chosen 'multiple answer' in the risk action plan.
mauritsvanrees Dec 1, 2023
43d5874
Multiple answers: treat 1 and 2 as presenting a risk.
mauritsvanrees Dec 1, 2023
1dff68c
Fix the risk navigation for multiple answers.
mauritsvanrees Dec 1, 2023
93132b7
Show evaluation when needed for multiple_answers.
mauritsvanrees Dec 1, 2023
84780d6
Show the 'show_notapplicable' option correctly for fields with multip…
mauritsvanrees Dec 8, 2023
ac5e026
Support specifying the value of multiple_answers.
mauritsvanrees Dec 8, 2023
1e2cf9c
Display 'multiple answers' better in content and client.
mauritsvanrees Dec 8, 2023
a42a9ea
Renamed multiple_answers to scaled_answers.
mauritsvanrees Jan 12, 2024
db3236a
RegistryValueVocabulary: filter out empty values.
mauritsvanrees Feb 2, 2024
ab521f3
Add alias in RISK_PRESENT_FILTER_TOP5_TNO_FILTER
reinhardt Feb 5, 2024
ea743f7
Allow the client skin layer to be customized by other packages
ale-rt Feb 6, 2024
63c4565
Bump the package version
ale-rt Feb 6, 2024
b43b4e4
Do not log anymore not found exceptions
ale-rt Feb 6, 2024
228a348
Weasyprint: Increase font size
reinhardt Feb 6, 2024
94ad82f
Weasyprint: increase page margin
reinhardt Feb 6, 2024
44f3bcf
Weasyprint: Decrease h3 font size
reinhardt Feb 7, 2024
11b9e92
Short report: No icon for skipped risks
reinhardt Feb 7, 2024
52cc40c
Fix problem with the identification report not being generated for ce…
thet Feb 8, 2024
1b73ed8
Merge branch 'main' into multiple-answers
mauritsvanrees Feb 9, 2024
09b868a
Merge remote-tracking branch 'origin/main' into multiple-answers
ale-rt Feb 9, 2024
63a3784
Merge branch 'main' into multiple-answers
mauritsvanrees Feb 16, 2024
422e6d3
Fix undefined name in action plan for multiple/psychosocial answers.
mauritsvanrees Feb 23, 2024
130fdf7
client navigation: add scaled answer in the risk info.
mauritsvanrees Feb 26, 2024
b1d7fcb
client navigation: refactor getTreeData function to call a browser vi…
mauritsvanrees Feb 26, 2024
6923f8a
Add notes that SCALED_ANSWER_RISK_PRESENT may not be the best way.
mauritsvanrees Feb 26, 2024
1f9f880
Fix errors reported by pre-commit.
mauritsvanrees Feb 27, 2024
190ea77
Fix navigation tests.
mauritsvanrees Feb 27, 2024
6ba279f
black.
mauritsvanrees Feb 27, 2024
d88c0c4
Merge pull request #706 from euphorie/multiple-answers-oira-navigatio…
mauritsvanrees Feb 27, 2024
94d4192
When getting a scaled answer, set the identification as well.
mauritsvanrees Mar 1, 2024
296274a
Merge pull request #708 from euphorie/multiple-answers-set-identifica…
ale-rt Mar 6, 2024
2a77c2f
Merge remote-tracking branch 'origin/main' into multiple-answers
mauritsvanrees Mar 8, 2024
b59434a
webhelpers: add navigation_tree_legend method so we can override it.
mauritsvanrees Mar 8, 2024
03b745b
webhelpers: add use_action_plan_phase.
mauritsvanrees Mar 8, 2024
24662ee
Merge pull request #713 from euphorie/multiple-answers-feedback-issue…
mauritsvanrees Mar 8, 2024
bdc1f8c
Merge branch 'main' into multiple-answers
mauritsvanrees Mar 20, 2024
454f3db
Add tests for euphorie.content.utils.parse_scaled_answers.
mauritsvanrees Mar 20, 2024
c284f90
Simplify parse_scaled_answers by using rpartition.
mauritsvanrees Mar 22, 2024
26f10c6
Make webhelpers.navigation_tree_legend a property.
mauritsvanrees Mar 22, 2024
95eb61d
Move property to attribute
ale-rt Mar 22, 2024
fca084a
Merge pull request #716 from euphorie/multiple-answers-attribute
mauritsvanrees Mar 22, 2024
8965270
Simplify
ale-rt Mar 22, 2024
ad7b72d
Update src/euphorie/content/tests/test_utils.py
ale-rt Mar 22, 2024
011103c
Merge pull request #717 from euphorie/multiple-answers-simplify
mauritsvanrees Mar 22, 2024
5ce8582
Merge remote-tracking branch 'origin/main' into multiple-answers
ale-rt Apr 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 93 additions & 14 deletions src/euphorie/client/browser/risk.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from euphorie.content.survey import get_tool_type
from euphorie.content.survey import ISurvey
from euphorie.content.utils import IToolTypesInfo
from euphorie.content.utils import parse_scaled_answers
from htmllaundry import StripMarkup
from io import BytesIO
from plone import api
Expand Down Expand Up @@ -387,6 +388,17 @@ def notes_placeholder(self):
"anything else you might want to write about this risk.",
)

@property
@memoize
def scaled_answers(self):
"""Get values and answers if the scaled_answers field is used.

This returns a list of dictionaries.
"""
if not getattr(self.risk, "use_scaled_answer", False):
return []
return parse_scaled_answers(self.risk.scaled_answers)


class IdentificationView(RiskBase):
"""A view for displaying a question in the identification phase."""
Expand All @@ -409,6 +421,7 @@ class IdentificationView(RiskBase):
always_present_answer = "no"

monitored_properties = {
"scaled_answer": None,
"identification": None,
"postponed": None,
"frequency": None,
Expand Down Expand Up @@ -474,7 +487,11 @@ def skip_evaluation(self):
@memoize
def evaluation_condition(self):
"""In what circumstances will the Evaluation panel be shown, provided
that evaluation is not skipped in general?"""
that evaluation is not skipped in general?

If you are using scaled answers instead of yes/no, you likely want to
override this.
"""
condition = "condition: answer=no"
if self.italy_special and not self.skip_evaluation:
condition = "condition: answer=no or answer=yes"
Expand Down Expand Up @@ -598,24 +615,61 @@ def set_parameter_values(self):
and self.my_tool_type in self.tti.types_existing_measures
)

def get_identification_from_scaled_answer(self, scaled_answer):
"""Determine the yes/no identification based on the scaled answer.

A simplistic implementation could be:

return "no" if scaled_answer in ("1", "2") else "yes"

You likely want to override this if you actually use scaled answers,
so by default we return nothing, making the identification empty.
"""
pass

def set_answer_data(self, reply):
"""Set answer data from the reply.

For years the only answer possibilities were yes, no, n/a or postponed.
Now we may have an extra field scaled_answer, for answers in the
range of (usually) 1-5.
We might want to merge these two possibilities, but for now they are
separate.
Currently, when scaled_answer is in the reply, we also get
'postponed' as answer. We can ignore this: scaled_answer is filled
in, so the answer is not postponed.
We make sure that either 'identification' is set (yes/no) or
'scaled_answer' is set (1-5), and the other None.
Or both None in the case the answer is really postponed.
"""
answer = reply.get("answer", None)
# If answer is not present in the request, do not attempt to set
# any answer-related data, since the request might have come
# from a sub-form.
if answer:
self.context.comment = self.webhelpers.get_safe_html(reply.get("comment"))
if not answer:
return
self.context.comment = self.webhelpers.get_safe_html(reply.get("comment"))
scaled_answer = reply.get("scaled_answer", None)
if scaled_answer:
# We have an answer on the scale of 1-5 (or similar).
self.context.scaled_answer = scaled_answer
self.context.postponed = False
self.context.identification = self.get_identification_from_scaled_answer(
scaled_answer
)
else:
self.context.scaled_answer = None
self.context.postponed = answer == "postponed"
if self.context.postponed:
self.context.identification = None
else:
self.context.identification = answer
if getattr(self.risk, "type", "") in ("top5", "policy"):
self.context.priority = "high"
elif getattr(self.risk, "evaluation_method", "") == "calculated":
self.calculatePriority(self.risk, reply)
elif self.risk is None or self.risk.evaluation_method == "direct":
self.context.priority = reply.get("priority")
return
self.context.identification = answer
if getattr(self.risk, "type", "") in ("top5", "policy"):
self.context.priority = "high"
elif getattr(self.risk, "evaluation_method", "") == "calculated":
self.calculatePriority(self.risk, reply)
elif self.risk is None or self.risk.evaluation_method == "direct":
self.context.priority = reply.get("priority")

def set_measure_data(self, reply, session):
changed = False
Expand Down Expand Up @@ -950,7 +1004,14 @@ def proceed_to_next(self, reply):
target = self.next_question
if target is None:
# We ran out of questions, proceed to the action plan
url = self.webhelpers.traversed_session.absolute_url() + "/@@actionplan"
if self.webhelpers.use_action_plan_phase:
next_view_name = "@@actionplan"
elif self.webhelpers.use_consultancy_phase:
next_view_name = "@@consultancy"
else:
next_view_name = "@@report"
base_url = self.webhelpers.traversed_session.absolute_url()
url = f"{base_url}/{next_view_name}"
return self.request.response.redirect(url)

elif _next == "add_custom_risk":
Expand Down Expand Up @@ -1184,8 +1245,11 @@ def risk_present(self):

@property
def risk_postponed(self):
return self.context.identification is None and (
(self.italy_special and self.context.postponed) or True
return (
self.context.identification is None
and self.context.scaled_answer is None
# We had this as well, but this is always true:
# and ((self.italy_special and self.context.postponed) or True)
)

@property
Expand All @@ -1197,6 +1261,21 @@ def use_problem_description(self):
text = self.risk.problem_description or ""
return bool(text.strip())

@property
def scaled_answer_chosen(self):
if not self.risk.use_scaled_answer:
return ""
if self.context.scaled_answer is None:
return ""
answer = self.context.scaled_answer
# answer is a string like '1'.
# Use it to find the textual representation of the answer.
for info in self.scaled_answers:
# Note: currently both info value and answer are strings ('1', '2', etc).
if info["value"] == answer:
return info
return answer

def _extractViewData(self):
"""Extract the data from the current context and build a data structure
that is usable by the view."""
Expand Down
8 changes: 8 additions & 0 deletions src/euphorie/client/browser/templates/risk_actionplan.pt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@
<h2 tal:condition="not:use_problem_description"
tal:content="risk/title"
>The fridges are checked daily.</h2>
<p tal:condition="risk/use_scaled_answer|nothing">
<tal:chosen define="
answer view/scaled_answer_chosen;
">
<label i18n:translate="">Answer:</label>
${answer/text} (${answer/value})
</tal:chosen>
</p>
</tal:block>
<tal:priority tal:define="
show_statement python:True;
Expand Down
81 changes: 51 additions & 30 deletions src/euphorie/client/browser/templates/webhelpers.pt
Original file line number Diff line number Diff line change
Expand Up @@ -149,18 +149,9 @@
>
<metal:call use-macro="webhelpers/macros/survey_tree_inner" />
<ul class="legend">
<li class="unvisited"
i18n:translate=""
>Unvisited</li>
<li class="postponed"
i18n:translate=""
>Postponed</li>
<li class="answered"
i18n:translate=""
>Risk not present</li>
<li class="answered risk"
i18n:translate=""
>Risk present</li>
<li class="${legend/class}"
tal:repeat="legend webhelpers/navigation_tree_legend"
>${legend/title}</li>
</ul>
</div>
</metal:survey>
Expand Down Expand Up @@ -278,7 +269,7 @@
</div>
<!-- END Existing measures -->

<!-- Yes / No -->
<!-- Yes / No / Multiple -->
<fieldset class="pat-checklist radio pat-rich">
<tal:existing condition="python:view.show_existing_measures and not always_present">
<p tal:condition="view/intro_questions"
Expand All @@ -294,23 +285,53 @@
type="hidden"
value="postponed"
/>
<label class="yes"><input checked="${python:'checked' if context.identification=='yes' else None}"
name="answer"
type="radio"
value="yes"
/><tal:answer replace="structure view/answer_yes" /></label>
<label class="no"><input checked="${python:'checked' if context.identification=='no' else None}"
name="answer"
type="radio"
value="no"
/><tal:answer replace="structure view/answer_no" /></label>
<label class="not-applicable"
tal:condition="risk/show_notapplicable|nothing"
><input checked="${python:'checked' if context.identification=='n/a' else None}"
name="answer"
type="radio"
value="n/a"
/><tal:answer replace="structure view/answer_na" /></label>

<!-- scaled answers -->
<tal:scaled_answers condition="risk/use_scaled_answer|nothing">
<label tal:repeat="answer view/scaled_answers">
<input checked="${python:'checked' if context.scaled_answer==value else None}"
name="scaled_answer"
type="radio"
value="${value}"
tal:define="
value answer/value;
"
/>
${answer/text}
</label>
<label class="not-applicable"
tal:condition="risk/show_notapplicable|nothing"
><input checked="${python:'checked' if context.scaled_answer=='n/a' else None}"
name="scaled_answer"
type="radio"
value="n/a"
/><tal:answer replace="structure view/answer_na" /></label>

</tal:scaled_answers>

<!-- Yes/No -->
<tal:yes_no condition="not:risk/use_scaled_answer|nothing">

<label class="yes"><input checked="${python:'checked' if context.identification=='yes' else None}"
name="answer"
type="radio"
value="yes"
/><tal:answer replace="structure view/answer_yes" /></label>
<label class="no"><input checked="${python:'checked' if context.identification=='no' else None}"
name="answer"
type="radio"
value="no"
/><tal:answer replace="structure view/answer_no" /></label>
<label class="not-applicable"
tal:condition="risk/show_notapplicable|nothing"
><input checked="${python:'checked' if context.identification=='n/a' else None}"
name="answer"
type="radio"
value="n/a"
/><tal:answer replace="structure view/answer_na" /></label>

</tal:yes_no>

</tal:answers>
<tal:always_present condition="always_present"><input name="answer"
type="hidden"
Expand Down
14 changes: 13 additions & 1 deletion src/euphorie/client/browser/webhelpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ class WebHelpers(BrowserView):
survey_session_model = SurveySession
dashboard_tabs = ["surveys", "assessments", "organisation"]

navigation_tree_legend = [
{"class": "unvisited", "title": _("Unvisited")},
{"class": "postponed", "title": _("Postponed")},
{"class": "answered", "title": _("Risk not present")},
{"class": "answered risk", "title": _("Risk present")},
]

def to_decimal(self, value):
"""Transform value in to a decimal."""
return Decimal(value)
Expand Down Expand Up @@ -800,6 +807,11 @@ def integrated_action_plan(self):
return False
return getattr(self._survey, "integrated_action_plan", False)

@property
@memoize
def use_action_plan_phase(self):
return not self.integrated_action_plan

@property
@memoize
def in_session(self):
Expand Down Expand Up @@ -1338,7 +1350,7 @@ def survey_tree_data(self):
}
)

if not integrated_action_plan:
if self.use_action_plan_phase:
# The tree for the action section uses the same structure as the
# identification tree, with the only differences that only risks
# and their parent modules are shown and that the entire tree is
Expand Down
8 changes: 8 additions & 0 deletions src/euphorie/client/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@
layer=".interfaces.IClientSkinLayer"
/>

<browser:page
name="oira_navigation_tree"
for="*"
class=".navigation.TreeDataCreator"
permission="zope2.View"
layer=".interfaces.IClientSkinLayer"
/>

<genericsetup:registerProfile
name="default"
title="Euphorie online client"
Expand Down
6 changes: 5 additions & 1 deletion src/euphorie/client/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1451,6 +1451,7 @@ class Risk(SurveyTreeItem):
image_data = schema.Column(types.LargeBinary())
image_data_scaled = schema.Column(types.LargeBinary())
image_filename = schema.Column(types.UnicodeText())
scaled_answer = schema.Column(types.UnicodeText())

@memoize
def measures_of_type(self, plan_type):
Expand Down Expand Up @@ -1855,7 +1856,10 @@ def _RISK_PRESENT_OR_TOP5_FILTER_factory():
sql.select([Risk_.sql_risk_id]).where(
sql.and_(
Risk_.sql_risk_id == SurveyTreeItem.id,
sql.or_(Risk_.identification == "no", Risk_.risk_type == "top5"),
sql.or_(
Risk_.identification == "no",
Risk_.risk_type == "top5",
),
)
)
),
Expand Down
Loading
Loading