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

Add behavior to hide modules from trainings. #669

Merged
merged 2 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Changelog
15.1.0 (unreleased)
-------------------

- Add behavior to hide modules from trainings.
Ref: scrum-1573
[thet]
- Cleanup: Reuse similar code for survey related views.
[thet]
- Fix incorrect named entity in configure.zcml.
Expand Down
20 changes: 20 additions & 0 deletions src/euphorie/client/browser/training.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from euphorie.client import utils as client_utils
from euphorie.client.model import Risk
from euphorie.client.model import Training
from euphorie.content.behaviors.hide_from_training import IHideFromTraining
from json import dumps
from json import loads
from logging import getLogger
Expand Down Expand Up @@ -414,10 +415,29 @@ def slide_data(self):
modules = self.getModulePaths()
risks = self.getRisks(modules, skip_unanswered=self.skip_unanswered)
seen_modules = []
hidden_modules = []
data = OrderedDict()
for module, risk in risks:
module_path = module.path
if module_path in hidden_modules:
continue
if module_path not in seen_modules:
# Get the ZODB representation of the module.
zodb_module = self.webhelpers.traversed_session.restrictedTraverse(
module.zodb_path.split("/")
)
# Check if the module is allowed in trainings.
if (
getattr(
IHideFromTraining(zodb_module, None),
"hide_from_training",
False,
)
is True
):
hidden_modules.append(module_path)
continue

module_in_context = module.__of__(self.webhelpers.traversed_session)
module_in_context.REQUEST["for_download"] = self.for_download
_view = module_in_context.restrictedTraverse("training_slide")
Expand Down
148 changes: 148 additions & 0 deletions src/euphorie/client/tests/test_training__hide_from_training.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
from euphorie.client import model
from euphorie.client.interfaces import IClientSkinLayer
from euphorie.client.tests.utils import addAccount
from euphorie.client.tests.utils import addSurvey
from euphorie.testing import EuphorieIntegrationTestCase
from plone import api
from z3c.saconfig import Session
from zope.interface import alsoProvides


SURVEY_1 = """
<sector xmlns="http://xml.simplon.biz/euphorie/survey/1.0">
<title>sector</title>
<survey>
<title>survey</title>
<language>nl</language>
<module>
<title>module1</title>
<risk>
<title>risk11</title>
<problem_description>problem-desc-11</problem_description>
<description>desc-11</description>
</risk>
</module>
<module>
<title>module2</title>
<risk>
<title>risk21</title>
<problem_description>problem-desc-21</problem_description>
<description>desc-21</description>
</risk>
</module>
</survey>
</sector>
"""

SURVEY_2 = """
<sector xmlns="http://xml.simplon.biz/euphorie/survey/1.0">
<title>sector</title>
<survey>
<title>survey</title>
<language>nl</language>
<module hide_from_training="True">
<title>module1</title>
<risk>
<title>risk11</title>
<problem_description>problem-desc-11</problem_description>
<description>desc-11</description>
</risk>
</module>
<module>
<title>module2</title>
<risk>
<title>risk21</title>
<problem_description>problem-desc-21</problem_description>
<description>desc-21</description>
</risk>
</module>
</survey>
</sector>
"""


class TestTrainingHideFromTraining(EuphorieIntegrationTestCase):
"""Tests the IHideFromTraining behavior.

Also tests importing an xml structure with the hide_from_training
attribute set.
"""

def setUp(self):
super().setUp()
api.portal.set_registry_record("euphorie.use_training_module", True)

# Add the hide_from_training behavior to the module type
types = api.portal.get_tool("portal_types")
fti_module = types.get("euphorie.module")
fti_module.behaviors = tuple(
list(fti_module.behaviors) + ["euphorie.hide_from_training"]
)
from plone.dexterity.schema import SCHEMA_CACHE

SCHEMA_CACHE.invalidate("euphorie.module")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow, thanks. didn't think of the schema cache!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't even aware it existed - I did a quick step through and got lucky because I noticed it. 😄


def create_content(self, survey_xml):
session = Session()
self.account = addAccount(password="secret")
with api.env.adopt_user("admin"):
addSurvey(self.portal, survey_xml)
self.survey = self.portal.client.nl.sector.survey
alsoProvides(self.survey.REQUEST, IClientSkinLayer)

survey_session = model.SurveySession(
title="Dummy session",
zodb_path="nl/sector/survey",
account=self.account,
)
session.add(survey_session)

module1 = survey_session.addChild(
model.Module(title="module1", module_id="module1", zodb_path="1"),
)
module1.addChild(
model.Risk(title="risk11", risk_id="risk11", zodb_path="1/2"),
)
module2 = survey_session.addChild(
model.Module(title="module2", module_id="module2", zodb_path="3"),
)
module2.addChild(
model.Risk(title="risk21", risk_id="risk21", zodb_path="3/4"),
)
session.flush()

def test_training_module_visible(self):
"""Test the standard case - all modules and ristsk are visible in the
training.
"""
self.create_content(SURVEY_1)

traversed_session = self.survey.restrictedTraverse("++session++1")
with api.env.adopt_user(user=self.account):
with self._get_view(
"training", traversed_session, self.request.clone()
) as view:
self.survey.enable_web_training = True
data = list(view.slide_data.values())

self.assertEqual(len(data), 4)
self.assertEqual(data[0]["item"].title, "module1")
self.assertEqual(data[1]["item"].title, "risk11")
self.assertEqual(data[2]["item"].title, "module2")
self.assertEqual(data[3]["item"].title, "risk21")

def test_training_module_hidden(self):
"""Test module1 hidden from training."""
self.create_content(SURVEY_2)

traversed_session = self.survey.restrictedTraverse("++session++1")
with api.env.adopt_user(user=self.account):
with self._get_view(
"training", traversed_session, self.request.clone()
) as view:
self.survey.enable_web_training = True
data = list(view.slide_data.values())

self.assertEqual(len(data), 2)
self.assertEqual(data[0]["item"].title, "module2")
self.assertEqual(data[1]["item"].title, "risk21")
6 changes: 6 additions & 0 deletions src/euphorie/content/behaviors/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
i18n_domain="untranslated"
>

<plone:behavior
name="euphorie.hide_from_training"
title="Hide from training"
provides=".hide_from_training.IHideFromTraining"
/>

<plone:behavior
title="Tool Category"
description="Add a tool category"
Expand Down
21 changes: 21 additions & 0 deletions src/euphorie/content/behaviors/hide_from_training.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from .. import MessageFactory as _
from plone.autoform.directives import order_after
from plone.autoform.interfaces import IFormFieldProvider
from plone.supermodel import model
from zope import schema
from zope.interface import provider


@provider(IFormFieldProvider)
class IHideFromTraining(model.Schema):
hide_from_training = schema.Bool(
title=_("label_hide_from_training", default="Hide from training"),
description=_(
"help_hide_from_training",
default="Do not include this module in the training, for example "
"if the module does not have any relevance for workers.",
),
required=False,
default=False,
)
order_after(hide_from_training="question")
9 changes: 6 additions & 3 deletions src/euphorie/content/browser/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,9 +413,12 @@ def exportProfileQuestion(self, parent, profile):
def exportModule(self, parent, module):
""":returns: An XML node with the details of an
:obj:`euphorie.content.module`."""
node = etree.SubElement(
parent, "module", optional="true" if module.optional else "false"
)

module_kwargs = {"optional": "true" if module.optional else "false"}
if getattr(module, "hide_from_training", False):
module_kwargs["hide_from_training"] = "true"
node = etree.SubElement(parent, "module", **module_kwargs)

if getattr(module, "external_id", None):
node.attrib["external-id"] = module.external_id
etree.SubElement(node, "title").text = module.title
Expand Down
7 changes: 7 additions & 0 deletions src/euphorie/content/browser/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from ..user import validLoginValue
from Acquisition import aq_inner
from euphorie.content import MessageFactory as _
from euphorie.content.behaviors.hide_from_training import IHideFromTraining
from euphorie.content.behaviors.toolcategory import IToolCategory
from euphorie.content.utils import IToolTypesInfo
from io import BytesIO
Expand Down Expand Up @@ -356,6 +357,12 @@ def ImportModule(self, node, survey):
(image, caption) = self.ImportImage(image)
module.image = image
module.caption = caption

if node.get("hide_from_training"):
behavior = IHideFromTraining(module, None)
if behavior:
behavior.hide_from_training = True

return module

def ImportProfileQuestion(self, node, survey):
Expand Down
32 changes: 32 additions & 0 deletions src/euphorie/content/tests/test_functional_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,35 @@ def testEditTitleForSubModule(self):
browser.open("%s/@@edit" % submodule.absolute_url())
self.assertTrue("Edit Module" not in browser.contents)
self.assertTrue("Edit Submodule" in browser.contents)

def testInactiveHideFromTrainingBehavior(self):
"""When `euphorie.hide_from_training` is not activated, the
`hide_from_training` field should not be visible.
"""
self.createStructure()

browser = self.get_browser(logged_in=True)
browser.open("%s/@@edit" % self.module.absolute_url())

self.assertTrue(
'name="form.widgets.IHideFromTraining.hide_from_training:list"'
not in browser.contents
)

def testActiveHideFromTrainingBehavior(self):
"""When `euphorie.hide_from_training` is activated, the
`hide_from_training` field should be visible.
"""
self.createStructure()

portal_types = api.portal.get_tool("portal_types")
fti = portal_types["euphorie.module"]
fti.behaviors = tuple(list(fti.behaviors) + ["euphorie.hide_from_training"])

browser = self.get_browser(logged_in=True)
browser.open("%s/@@edit" % self.module.absolute_url())

self.assertTrue(
'name="form.widgets.IHideFromTraining.hide_from_training:list"'
in browser.contents
)
Loading