diff --git a/backend/news/6.feature b/backend/news/6.feature new file mode 100644 index 0000000..d2f138e --- /dev/null +++ b/backend/news/6.feature @@ -0,0 +1 @@ +Add optional presenter roles behavior to let sessions inherit local roles from their connected presenters @datakurre diff --git a/backend/src/collective/techevent/behaviors/configure.zcml b/backend/src/collective/techevent/behaviors/configure.zcml index f79871e..cc43749 100644 --- a/backend/src/collective/techevent/behaviors/configure.zcml +++ b/backend/src/collective/techevent/behaviors/configure.zcml @@ -26,4 +26,16 @@ provides=".session.IEventSession" /> + + + + diff --git a/backend/src/collective/techevent/behaviors/presenter.py b/backend/src/collective/techevent/behaviors/presenter.py new file mode 100644 index 0000000..ffe69d5 --- /dev/null +++ b/backend/src/collective/techevent/behaviors/presenter.py @@ -0,0 +1,57 @@ +from borg.localrole.interfaces import ILocalRoleProvider +from plone import api +from z3c.relationfield import RelationValue +from zope.component import adapter +from zope.interface import implementer +from zope.interface import Interface + + +class IPresenterLocalRoles(Interface): + """Marker interface for the local roles of a presenter.""" + + +@implementer(ILocalRoleProvider) +@adapter(IPresenterLocalRoles) +class PresenterRoleProvider: + def __init__(self, context): + self.context = context + + def getRoles(self, user_id): + """ + Return the roles assigned to the given user ID in the context of presenters for the current event session. + """ + mtool = api.portal.get_tool("portal_membership") + user = mtool.getMemberById(user_id) + if not user: + return [] + roles = set() + presenters = getattr(self.context, "presenters", []) + for presenter_relation in presenters: + if ( + isinstance(presenter_relation, RelationValue) + and presenter_relation.to_object + ): + presenter = presenter_relation.to_object + roles.update(user.getRolesInContext(presenter)) + return list(roles) + + def getAllRoles(self): + """ + Retrieve all roles assigned to users in the context of presenters for the current event session. + + This method merges roles from all presenters for each user. It does not consider additional role adapters. + """ + roles = {} + presenters = getattr(self.context, "presenters", []) + for presenter_relation in presenters: + if ( + isinstance(presenter_relation, RelationValue) + and presenter_relation.to_object + ): + presenter = presenter_relation.to_object + local_roles = presenter.get_local_roles() + for user_id, user_roles in local_roles: + if user_id not in roles: + roles[user_id] = set() + roles[user_id].update(user_roles) + return [(user_id, tuple(user_roles)) for user_id, user_roles in roles.items()] diff --git a/backend/tests/content_types/schedule/conftest.py b/backend/tests/content_types/schedule/conftest.py index 3a2fdd5..d45bc93 100644 --- a/backend/tests/content_types/schedule/conftest.py +++ b/backend/tests/content_types/schedule/conftest.py @@ -26,3 +26,18 @@ def func(portal_type: str): return brains return func + + +@pytest.fixture +def enable_presenter_roles(): + def func(portal: DexterityContent, portal_type: str): + with api.env.adopt_roles(["Manager"]): + fti = portal.portal_types.get(portal_type) + if fti: + behaviors = list(fti.behaviors) + if "collective.techevent.presenter_roles" not in behaviors: + behaviors.append("collective.techevent.presenter_roles") + fti.behaviors = tuple(behaviors) + return fti + + return func diff --git a/backend/tests/content_types/schedule/test_ct_talk.py b/backend/tests/content_types/schedule/test_ct_talk.py index e92adf5..b77d3fb 100644 --- a/backend/tests/content_types/schedule/test_ct_talk.py +++ b/backend/tests/content_types/schedule/test_ct_talk.py @@ -1,4 +1,8 @@ from collective.techevent.content.schedule.talk import Talk +from plone.app.testing import TEST_USER_ID +from z3c.relationfield import RelationValue +from zope.component import getUtility +from zope.intid.interfaces import IIntIds import pytest @@ -8,6 +12,16 @@ def portal_type() -> str: return "Talk" +@pytest.fixture +def test_user(portal): + return portal.acl_users.getUserById(TEST_USER_ID) + + +@pytest.fixture +def intids(): + return getUtility(IIntIds) + + class TestContentType: @pytest.fixture(autouse=True) def _setup(self, container): @@ -65,3 +79,70 @@ def test_create_version_on_save( version = last_version(content_instance) assert version.comment is None assert version.version_id == 1 + + +@pytest.fixture +def setup_talk_and_presenter( + portal, + container, + intids, + content_factory, + payloads, + portal_type, +): + def _setup(enable_roles=False, enable_presenter_roles=None): + if enable_roles and enable_presenter_roles: + enable_presenter_roles(portal, portal_type) + + talk = content_factory(container, payloads[portal_type][0]) + talk.__ac_local_roles__ = {} + talk.__ac_local_roles_block__ = True + + presenter = content_factory(portal, payloads["Presenter"][0]) + presenter.__ac_local_roles_block__ = True + + talk.presenters = [RelationValue(intids.getId(presenter))] + + return talk, presenter + + return _setup + + +class TestPresenterRolesDisabled: + @pytest.fixture(autouse=True) + def _setup(self, setup_talk_and_presenter): + self.talk, self.presenter = setup_talk_and_presenter() + + def test_talk_without_presenter_roles( + self, + portal, + test_user, + ): + talk_roles = portal.acl_users._getAllLocalRoles(self.talk) + presenter_roles = portal.acl_users._getAllLocalRoles(self.presenter) + + assert talk_roles == {} + assert presenter_roles != {} + assert talk_roles != presenter_roles + assert "Owner" not in test_user.getRolesInContext(self.talk) + + +class TestPresenterRolesEnabled: + @pytest.fixture(autouse=True) + def _setup(self, setup_talk_and_presenter, enable_presenter_roles): + self.talk, self.presenter = setup_talk_and_presenter( + enable_roles=True, enable_presenter_roles=enable_presenter_roles + ) + + def test_talk_with_presenter_roles( + self, + portal, + test_user, + ): + talk_roles = portal.acl_users._getAllLocalRoles(self.talk) + presenter_roles = portal.acl_users._getAllLocalRoles(self.presenter) + + assert talk_roles != {} + assert presenter_roles != {} + assert talk_roles == presenter_roles + assert "Owner" in test_user.getRolesInContext(self.talk) diff --git a/backend/tests/services/conftest.py b/backend/tests/services/conftest.py index 7bdb32c..da31d65 100644 --- a/backend/tests/services/conftest.py +++ b/backend/tests/services/conftest.py @@ -15,7 +15,7 @@ def portal(functional): portal = functional["portal"] setSite(portal) tool: SetupTool = api.portal.get_tool("portal_setup") - with api.env.adopt_roles(["Manager", "Member"]): + with api.env.adopt_roles(["Manager"]): tool.runAllImportStepsFromProfile(f"{PACKAGE_NAME}:demo") transaction.commit() return portal