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