From 150b3d6b409bede0deb2c5330abe204d5d3e2103 Mon Sep 17 00:00:00 2001 From: Luisa Date: Fri, 2 Aug 2024 17:06:22 +0200 Subject: [PATCH 1/4] Implement motion_change_recommendation creation in motion forward --- global/meta | 2 +- .../actions/motion/base_create_forwarded.py | 24 +++++++++ .../action/actions/motion/create_forwarded.py | 1 + .../motion/create_forwarded_amendment.py | 1 + .../action/motion/test_create_forwarded.py | 52 +++++++++++++++++++ 5 files changed, 79 insertions(+), 1 deletion(-) diff --git a/global/meta b/global/meta index ab5274633..13234af24 160000 --- a/global/meta +++ b/global/meta @@ -1 +1 @@ -Subproject commit ab527463324866435ea932305b77dc1334c1738d +Subproject commit 13234af243473a00f17fa03455efed95c18a3ddf diff --git a/openslides_backend/action/actions/motion/base_create_forwarded.py b/openslides_backend/action/actions/motion/base_create_forwarded.py index c6b31f105..2ec1cb833 100644 --- a/openslides_backend/action/actions/motion/base_create_forwarded.py +++ b/openslides_backend/action/actions/motion/base_create_forwarded.py @@ -13,6 +13,7 @@ from ....shared.interfaces.write_request import WriteRequest from ....shared.patterns import fqid_from_collection_and_id from ...util.typing import ActionData, ActionResultElement, ActionResults +from ..motion_change_recommendation.create import MotionChangeRecommendationCreateAction from .create_base import MotionCreateBase @@ -193,9 +194,31 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: self.set_origin_ids(instance) self.set_text_hash(instance) instance["forwarded"] = round(time.time()) + with_change_recommendations = instance.pop("with_change_recommendations", False) self.datastore.apply_changed_model( fqid_from_collection_and_id("motion", instance["id"]), instance ) + if with_change_recommendations: + change_recos = self.datastore.filter( + "motion_change_recommendation", + FilterOperator("motion_id", "=", instance["origin_id"]), + [ + "rejected", + "internal", + "type", + "other_description", + "line_from", + "line_to", + "text", + ], + ) + change_reco_data = [ + {**change_reco, "motion_id": instance["id"]} + for change_reco in change_recos.values() + ] + self.execute_other_action( + MotionChangeRecommendationCreateAction, change_reco_data + ) amendment_ids = self.datastore.get( fqid_from_collection_and_id("motion", instance["origin_id"]), ["amendment_ids"], @@ -254,6 +277,7 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: "meeting_id": instance["meeting_id"], "use_original_submitter": use_original_submitter, "use_original_number": use_original_number, + "with_change_recommendations": with_change_recommendations, } ) amendment.pop("meta_position", 0) diff --git a/openslides_backend/action/actions/motion/create_forwarded.py b/openslides_backend/action/actions/motion/create_forwarded.py index 23339127f..150853731 100644 --- a/openslides_backend/action/actions/motion/create_forwarded.py +++ b/openslides_backend/action/actions/motion/create_forwarded.py @@ -23,6 +23,7 @@ class MotionCreateForwarded(BaseMotionCreateForwarded): additional_optional_fields={ "use_original_submitter": {"type": "boolean"}, "use_original_number": {"type": "boolean"}, + "with_change_recommendations": {"type": "boolean"}, "with_amendments": {"type": "boolean"}, }, ) diff --git a/openslides_backend/action/actions/motion/create_forwarded_amendment.py b/openslides_backend/action/actions/motion/create_forwarded_amendment.py index 82b84d30a..85670fbf9 100644 --- a/openslides_backend/action/actions/motion/create_forwarded_amendment.py +++ b/openslides_backend/action/actions/motion/create_forwarded_amendment.py @@ -25,6 +25,7 @@ class MotionCreateForwardedAmendment(BaseMotionCreateForwarded): additional_optional_fields={ "use_original_submitter": {"type": "boolean"}, "use_original_number": {"type": "boolean"}, + "with_change_recommendations": {"type": "boolean"}, }, ) diff --git a/tests/system/action/motion/test_create_forwarded.py b/tests/system/action/motion/test_create_forwarded.py index 18784a532..8ed2e3969 100644 --- a/tests/system/action/motion/test_create_forwarded.py +++ b/tests/system/action/motion/test_create_forwarded.py @@ -1316,3 +1316,55 @@ def test_name_generation(self) -> None: "Sue B. Mid-Edit", ]: assert name in motion["additional_submitter"] + + def test_with_change_recommendation(self) -> None: + self.set_models(self.test_model) + self.set_models( + { + "motion/12": {"change_recommendation_ids": [1]}, + "meeting/1": {"motion_change_recommendation_ids": [1]}, + "motion_change_recommendation/1": { + "line_from": 11, + "line_to": 23, + "text": "Hello world", + "motion_id": 12, + "meeting_id": 1, + "rejected": True, + "internal": True, + "type": "replacement", + "other_description": "Iamachangerecommendation", + "creation_time": 0, + }, + } + ) + response = self.request( + "motion.create_forwarded", + { + "title": "test_Xcdfgee", + "meeting_id": 2, + "origin_id": 12, + "text": "test", + "reason": "reason_jLvcgAMx", + "with_change_recommendations": True, + }, + ) + self.assert_status_code(response, 200) + created_id = response.json["results"][0][0]["id"] + self.assert_model_exists( + f"motion/{created_id}", {"change_recommendation_ids": [2]} + ) + reco = self.assert_model_exists( + "motion_change_recommendation/2", + { + "line_from": 11, + "line_to": 23, + "text": "Hello world", + "motion_id": created_id, + "meeting_id": 2, + "rejected": True, + "internal": True, + "type": "replacement", + "other_description": "Iamachangerecommendation", + }, + ) + assert reco["creation_time"] > 0 From 0590740a0ab8dbf8d8987275578e5ed874277dce Mon Sep 17 00:00:00 2001 From: Luisa Date: Mon, 5 Aug 2024 10:39:09 +0200 Subject: [PATCH 2/4] Write more tests --- .../action/motion/test_create_forwarded.py | 180 +++++++++++++++++- 1 file changed, 175 insertions(+), 5 deletions(-) diff --git a/tests/system/action/motion/test_create_forwarded.py b/tests/system/action/motion/test_create_forwarded.py index 8ed2e3969..44b571ede 100644 --- a/tests/system/action/motion/test_create_forwarded.py +++ b/tests/system/action/motion/test_create_forwarded.py @@ -1317,12 +1317,12 @@ def test_name_generation(self) -> None: ]: assert name in motion["additional_submitter"] - def test_with_change_recommendation(self) -> None: + def test_with_change_recommendations(self) -> None: self.set_models(self.test_model) self.set_models( { - "motion/12": {"change_recommendation_ids": [1]}, - "meeting/1": {"motion_change_recommendation_ids": [1]}, + "motion/12": {"change_recommendation_ids": [1, 2]}, + "meeting/1": {"motion_change_recommendation_ids": [1, 2]}, "motion_change_recommendation/1": { "line_from": 11, "line_to": 23, @@ -1335,6 +1335,15 @@ def test_with_change_recommendation(self) -> None: "other_description": "Iamachangerecommendation", "creation_time": 0, }, + "motion_change_recommendation/2": { + "line_from": 24, + "line_to": 25, + "text": "!", + "motion_id": 12, + "meeting_id": 1, + "type": "replacement", + "creation_time": 1, + }, } ) response = self.request( @@ -1351,10 +1360,10 @@ def test_with_change_recommendation(self) -> None: self.assert_status_code(response, 200) created_id = response.json["results"][0][0]["id"] self.assert_model_exists( - f"motion/{created_id}", {"change_recommendation_ids": [2]} + f"motion/{created_id}", {"change_recommendation_ids": [3, 4]} ) reco = self.assert_model_exists( - "motion_change_recommendation/2", + "motion_change_recommendation/3", { "line_from": 11, "line_to": 23, @@ -1368,3 +1377,164 @@ def test_with_change_recommendation(self) -> None: }, ) assert reco["creation_time"] > 0 + reco = self.assert_model_exists( + "motion_change_recommendation/4", + { + "line_from": 24, + "line_to": 25, + "text": "!", + "type": "replacement", + "motion_id": created_id, + "meeting_id": 2, + }, + ) + + def test_without_change_recommendations(self) -> None: + self.set_models(self.test_model) + self.set_models( + { + "motion/12": {"change_recommendation_ids": [1, 2]}, + "meeting/1": {"motion_change_recommendation_ids": [1, 2]}, + "motion_change_recommendation/1": { + "line_from": 11, + "line_to": 23, + "text": "Hello world", + "motion_id": 12, + "meeting_id": 1, + "rejected": True, + "internal": True, + "type": "replacement", + "other_description": "Iamachangerecommendation", + "creation_time": 0, + }, + "motion_change_recommendation/2": { + "line_from": 24, + "line_to": 25, + "text": "!", + "motion_id": 12, + "meeting_id": 1, + "type": "replacement", + "creation_time": 1, + }, + } + ) + response = self.request( + "motion.create_forwarded", + { + "title": "test_Xcdfgee", + "meeting_id": 2, + "origin_id": 12, + "text": "test", + "reason": "reason_jLvcgAMx", + }, + ) + self.assert_status_code(response, 200) + created_id = response.json["results"][0][0]["id"] + self.assert_model_exists( + f"motion/{created_id}", {"change_recommendation_ids": None} + ) + self.assert_model_not_exists("motion_change_recommendation/3") + + def test_with_no_change_recommendations(self) -> None: + self.set_models(self.test_model) + response = self.request( + "motion.create_forwarded", + { + "title": "test_Xcdfgee", + "meeting_id": 2, + "origin_id": 12, + "text": "test", + "reason": "reason_jLvcgAMx", + "with_change_recommendations": True, + }, + ) + self.assert_status_code(response, 200) + + def test_with_amendment_change_recommendations(self) -> None: + self.set_models(self.test_model) + self.set_models( + { + "motion/12": {"change_recommendation_ids": [1], "amendment_ids": [13]}, + "meeting/1": {"motion_change_recommendation_ids": [1, 2]}, + "motion/13": { + "number": "AMNDMNT1", + "title": "amendment1", + "meeting_id": 1, + "derived_motion_ids": [], + "all_origin_ids": [], + "all_derived_motion_ids": [], + "lead_motion_id": 12, + "state_id": 30, + "text": "bla", + "change_recommendation_ids": [2], + }, + "motion_change_recommendation/1": { + "line_from": 11, + "line_to": 23, + "text": "Hello world", + "motion_id": 12, + "meeting_id": 1, + "rejected": True, + "internal": True, + "type": "replacement", + "other_description": "Iamachangerecommendation", + "creation_time": 0, + }, + "motion_change_recommendation/2": { + "line_from": 24, + "line_to": 25, + "text": "!", + "motion_id": 13, + "meeting_id": 1, + "type": "replacement", + "creation_time": 1, + }, + } + ) + response = self.request( + "motion.create_forwarded", + { + "title": "test_Xcdfgee", + "meeting_id": 2, + "origin_id": 12, + "text": "test", + "reason": "reason_jLvcgAMx", + "with_change_recommendations": True, + "with_amendments": True, + }, + ) + self.assert_status_code(response, 200) + created_id = response.json["results"][0][0]["id"] + self.assert_model_exists( + f"motion/{created_id}", {"change_recommendation_ids": [3]} + ) + self.assert_model_exists( + f"motion/{created_id+1}", + {"change_recommendation_ids": [4], "lead_motion_id": created_id}, + ) + reco = self.assert_model_exists( + "motion_change_recommendation/3", + { + "line_from": 11, + "line_to": 23, + "text": "Hello world", + "motion_id": created_id, + "meeting_id": 2, + "rejected": True, + "internal": True, + "type": "replacement", + "other_description": "Iamachangerecommendation", + }, + ) + assert reco["creation_time"] > 0 + reco = self.assert_model_exists( + "motion_change_recommendation/4", + { + "line_from": 24, + "line_to": 25, + "text": "!", + "type": "replacement", + "motion_id": created_id + 1, + "meeting_id": 2, + }, + ) From f23ab7fa35d0b4933d4b7f540e9b95ea81b8e780 Mon Sep 17 00:00:00 2001 From: Luisa Date: Mon, 5 Aug 2024 12:28:02 +0200 Subject: [PATCH 3/4] Change wiki --- docs/actions/motion.create_forwarded.md | 3 +++ docs/actions/motion.create_forwarded_amendment.md | 1 + 2 files changed, 4 insertions(+) diff --git a/docs/actions/motion.create_forwarded.md b/docs/actions/motion.create_forwarded.md index 5dba5949d..0b54003c7 100644 --- a/docs/actions/motion.create_forwarded.md +++ b/docs/actions/motion.create_forwarded.md @@ -12,6 +12,7 @@ use_original_submitter: boolean; use_original_number: boolean; with_amendments: boolean; + with_change_recommendations: boolean; } ``` @@ -35,6 +36,8 @@ The three boolean flags for extra rules will be applied to the amendments as wel If the forwarded amendments have amendments themselves, those will also be treated the same way +If `with_change_recommendations` is set to True, all change recommendations of the motion will be copied to the target meeting and connected to the newly forwarded lead motion. They will not have any reference to the original recommendation afterwards. + ### Forwarding tree fields * `all_origin_ids` of the newly created motion must be set to `all_origin_ids` of the origin motion plus the given `origin_id`. It is important that the id is appended at the end of the list, since the order of this field represents the order of the tree in case a motion of the tree is deleted. diff --git a/docs/actions/motion.create_forwarded_amendment.md b/docs/actions/motion.create_forwarded_amendment.md index 53338ec57..2261f41dd 100644 --- a/docs/actions/motion.create_forwarded_amendment.md +++ b/docs/actions/motion.create_forwarded_amendment.md @@ -13,6 +13,7 @@ amendment_paragraphs: JSON use_original_submitter: boolean; use_original_number: boolean; + with_change_recommendations: boolean; } ``` From 42a6aa0b177ba289c1e644952d150d05c75f5a6e Mon Sep 17 00:00:00 2001 From: Luisa Date: Thu, 8 Aug 2024 15:23:57 +0200 Subject: [PATCH 4/4] Make changes --- tests/system/action/motion/test_create_forwarded.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/system/action/motion/test_create_forwarded.py b/tests/system/action/motion/test_create_forwarded.py index 44b571ede..3a6e05abd 100644 --- a/tests/system/action/motion/test_create_forwarded.py +++ b/tests/system/action/motion/test_create_forwarded.py @@ -1377,7 +1377,7 @@ def test_with_change_recommendations(self) -> None: }, ) assert reco["creation_time"] > 0 - reco = self.assert_model_exists( + self.assert_model_exists( "motion_change_recommendation/4", { "line_from": 24, @@ -1527,7 +1527,7 @@ def test_with_amendment_change_recommendations(self) -> None: }, ) assert reco["creation_time"] > 0 - reco = self.assert_model_exists( + self.assert_model_exists( "motion_change_recommendation/4", { "line_from": 24,