diff --git a/docs/actions/option.update.md b/docs/actions/option.update.md index 8a197c201..81d99e4bd 100644 --- a/docs/actions/option.update.md +++ b/docs/actions/option.update.md @@ -20,4 +20,4 @@ If the poll's state is *created* and at least one vote value is given (`Y`, `N` The request user needs: - `motion.can_manage_polls` if the poll's content object is a motion - `assignment.can_manage` if the poll's content object is an assignment -- `poll.can_manage` else +- `poll.can_manage` if the poll's content object is a topic diff --git a/docs/actions/poll.anonymize.md b/docs/actions/poll.anonymize.md index ac0fbeb14..442ffb07e 100644 --- a/docs/actions/poll.anonymize.md +++ b/docs/actions/poll.anonymize.md @@ -12,4 +12,4 @@ Only for non-analog polls in the state *finished* or *published*. Sets all `vote The request user needs: - `motion.can_manage_polls` if the poll's content object is a motion - `assignment.can_manage` if the poll's content object is an assignment -- `poll.can_manage` else +- `poll.can_manage` if the poll's content object is a topic diff --git a/docs/actions/poll.create.md b/docs/actions/poll.create.md index 543194f79..7398cad8d 100644 --- a/docs/actions/poll.create.md +++ b/docs/actions/poll.create.md @@ -68,4 +68,4 @@ The `entitled_group_ids` may not contain the meetings `anonymous_group_id`. The request user needs: - `motion.can_manage_polls` if the poll's content object is a motion - `assignment.can_manage` if the poll's content object is an assignment -- `poll.can_manage` else +- `poll.can_manage` if the poll's content object is a topic diff --git a/docs/actions/poll.delete.md b/docs/actions/poll.delete.md index f6ef35e38..cfea2d9a2 100644 --- a/docs/actions/poll.delete.md +++ b/docs/actions/poll.delete.md @@ -10,4 +10,4 @@ Deletes the given poll and all linked options with all votes, too. The request user needs: - `motion.can_manage_polls` if the poll's content object is a motion - `assignment.can_manage` if the poll's content object is an assignment -- `poll.can_manage` else +- `poll.can_manage` if the poll's content object is a topic diff --git a/docs/actions/poll.publish.md b/docs/actions/poll.publish.md index 38df11f89..a5a0c8af5 100644 --- a/docs/actions/poll.publish.md +++ b/docs/actions/poll.publish.md @@ -10,4 +10,4 @@ Sets the state to *published*. Only allowed for polls in the *finished* state. The request user needs: - `motion.can_manage_polls` if the poll's content object is a motion - `assignment.can_manage` if the poll's content object is an assignment -- `poll.can_manage` else +- `poll.can_manage` if the poll's content object is a topic diff --git a/docs/actions/poll.reset.md b/docs/actions/poll.reset.md index 33081bc0e..0d8368b0b 100644 --- a/docs/actions/poll.reset.md +++ b/docs/actions/poll.reset.md @@ -12,4 +12,4 @@ If `type != "pseudoanonymous"`, `is_pseudoanonymized` may be reset to `false` (i The request user needs: - `motion.can_manage_polls` if the poll's content object is a motion - `assignment.can_manage` if the poll's content object is an assignment -- `poll.can_manage` else +- `poll.can_manage` if the poll's content object is a topic diff --git a/docs/actions/poll.start.md b/docs/actions/poll.start.md index d50b49991..127877913 100644 --- a/docs/actions/poll.start.md +++ b/docs/actions/poll.start.md @@ -12,4 +12,4 @@ If `meeting/poll_couple_countdown` is true and the poll is an electronic poll, t The request user needs: - `motion.can_manage_polls` if the poll's content object is a motion - `assignment.can_manage` if the poll's content object is an assignment -- `poll.can_manage` else +- `poll.can_manage` if the poll's content object is a topic diff --git a/docs/actions/poll.stop.md b/docs/actions/poll.stop.md index a44d7abd4..d271a3e2b 100644 --- a/docs/actions/poll.stop.md +++ b/docs/actions/poll.stop.md @@ -16,4 +16,4 @@ Some fields have to be calculated upon stopping a poll: The request user needs: - `motion.can_manage_polls` if the poll's content object is a motion - `assignment.can_manage` if the poll's content object is an assignment -- `poll.can_manage` else +- `poll.can_manage` if the poll's content object is a topic diff --git a/docs/actions/poll.update.md b/docs/actions/poll.update.md index c880c442d..4544c4d64 100644 --- a/docs/actions/poll.update.md +++ b/docs/actions/poll.update.md @@ -43,4 +43,4 @@ The `entitled_group_ids` may not contain the meetings `anonymous_group_id`. The request user needs: - `motion.can_manage_polls` if the poll's content object is a motion - `assignment.can_manage` if the poll's content object is an assignment -- `poll.can_manage` else +- `poll.can_manage` if the poll's content object is a topic diff --git a/openslides_backend/action/actions/poll/functions.py b/openslides_backend/action/actions/poll/functions.py index ebd9666f9..d4b9bbcc2 100644 --- a/openslides_backend/action/actions/poll/functions.py +++ b/openslides_backend/action/actions/poll/functions.py @@ -1,8 +1,8 @@ from ....permissions.permission_helper import has_perm from ....permissions.permissions import Permission, Permissions from ....services.datastore.interface import DatastoreService -from ....shared.exceptions import MissingPermission -from ....shared.patterns import KEYSEPARATOR +from ....shared.exceptions import ActionException, MissingPermission +from ....shared.patterns import KEYSEPARATOR, collection_from_fqid def check_poll_or_option_perms( @@ -15,7 +15,11 @@ def check_poll_or_option_perms( perm: Permission = Permissions.Motion.CAN_MANAGE_POLLS elif content_object_id.startswith("assignment" + KEYSEPARATOR): perm = Permissions.Assignment.CAN_MANAGE - else: + elif content_object_id.startswith("topic" + KEYSEPARATOR): perm = Permissions.Poll.CAN_MANAGE + else: + raise ActionException( + f"'{collection_from_fqid(content_object_id)}' is not a valid poll collection." + ) if not has_perm(datastore, user_id, perm, meeting_id): raise MissingPermission(perm) diff --git a/openslides_backend/action/actions/poll/mixins.py b/openslides_backend/action/actions/poll/mixins.py index 557b72c20..6616952e5 100644 --- a/openslides_backend/action/actions/poll/mixins.py +++ b/openslides_backend/action/actions/poll/mixins.py @@ -5,7 +5,7 @@ from openslides_backend.shared.typing import HistoryInformation from ....services.datastore.commands import GetManyRequest -from ....shared.exceptions import VoteServiceException +from ....shared.exceptions import ActionException, VoteServiceException from ....shared.interfaces.write_request import WriteRequest from ....shared.patterns import ( collection_from_fqid, @@ -33,6 +33,8 @@ def check_permissions(self, instance: dict[str, Any]) -> None: ) content_object_id = poll.get("content_object_id", "") meeting_id = poll["meeting_id"] + if not content_object_id: + raise ActionException("No 'content_object_id' was given") check_poll_or_option_perms( content_object_id, self.datastore, self.user_id, meeting_id ) diff --git a/tests/system/action/poll/test_create.py b/tests/system/action/poll/test_create.py index eb3e7bea6..75f6d765f 100644 --- a/tests/system/action/poll/test_create.py +++ b/tests/system/action/poll/test_create.py @@ -276,6 +276,7 @@ def test_create_wrong_publish_immediately(self) -> None: response = self.request( "poll.create", { + "content_object_id": "assignment/1", "title": "test_title_ahThai4pae1pi4xoogoo", "pollmethod": "YN", "type": "pseudoanonymous", @@ -799,10 +800,7 @@ def test_create_without_content_object(self) -> None: }, ) self.assert_status_code(response, 400) - assert ( - response.json["message"] - == "Creation of poll/1: You try to set following required fields to an empty value: ['content_object_id']" - ) + assert response.json["message"] == "No 'content_object_id' was given" def test_create_no_permissions_assignment(self) -> None: self.base_permission_test( diff --git a/tests/system/action/poll/test_delete.py b/tests/system/action/poll/test_delete.py index 639d04c9f..714034d1f 100644 --- a/tests/system/action/poll/test_delete.py +++ b/tests/system/action/poll/test_delete.py @@ -33,15 +33,18 @@ def test_delete_wrong_id(self) -> None: def test_delete_correct_cascading(self) -> None: self.set_models( { + "topic/1": {"poll_ids": [111], "meeting_id": 1}, "poll/111": { "option_ids": [42], "meeting_id": 1, "projection_ids": [1], + "content_object_id": "topic/1", }, "option/42": {"poll_id": 111, "meeting_id": 1}, "meeting/1": { "is_active_in_organization_id": 1, "all_projection_ids": [1], + "topic_ids": [1], }, "projection/1": { "content_object_id": "poll/111", @@ -64,9 +67,11 @@ def test_delete_correct_cascading(self) -> None: def test_delete_cascading_poll_candidate_list(self) -> None: self.set_models( { + "topic/1": {"poll_ids": [111], "meeting_id": 1}, "poll/111": { "option_ids": [42], "meeting_id": 1, + "content_object_id": "topic/1", }, "option/42": { "poll_id": 111, @@ -77,6 +82,7 @@ def test_delete_cascading_poll_candidate_list(self) -> None: "is_active_in_organization_id": 1, "poll_candidate_list_ids": [12], "poll_candidate_ids": [13], + "topic_ids": [1], }, "poll_candidate_list/12": { "meeting_id": 1, @@ -100,12 +106,14 @@ def test_delete_cascading_poll_candidate_list(self) -> None: def test_delete_no_permissions(self) -> None: self.base_permission_test( - {"poll/111": {"meeting_id": 1}}, "poll.delete", {"id": 111} + {"poll/111": {"meeting_id": 1, "content_object_id": "topic/1"}}, + "poll.delete", + {"id": 111}, ) def test_delete_permissions(self) -> None: self.base_permission_test( - {"poll/111": {"meeting_id": 1}}, + {"poll/111": {"meeting_id": 1, "content_object_id": "topic/1"}}, "poll.delete", {"id": 111}, Permissions.Poll.CAN_MANAGE, @@ -113,7 +121,7 @@ def test_delete_permissions(self) -> None: def test_delete_permissions_locked_meeting(self) -> None: self.base_locked_out_superadmin_permission_test( - {"poll/111": {"meeting_id": 1}}, + {"poll/111": {"meeting_id": 1, "content_object_id": "topic/1"}}, "poll.delete", {"id": 111}, ) diff --git a/tests/system/action/poll/test_publish.py b/tests/system/action/poll/test_publish.py index b7d5fad5c..b2c9126c5 100644 --- a/tests/system/action/poll/test_publish.py +++ b/tests/system/action/poll/test_publish.py @@ -42,8 +42,13 @@ def test_publish_assignment(self) -> None: def test_publish_wrong_state(self) -> None: self.set_models( { - "poll/1": {"state": "created", "meeting_id": 1}, - "meeting/1": {"is_active_in_organization_id": 1}, + "topic/1": {"poll_ids": [111], "meeting_id": 1}, + "poll/1": { + "state": "created", + "meeting_id": 1, + "content_object_id": "topic/1", + }, + "meeting/1": {"is_active_in_organization_id": 1, "topic_ids": [1]}, } ) response = self.request("poll.publish", {"id": 1})