From cca28a990a75b3aa01486d7aae720892b5e00135 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sat, 19 Jan 2019 21:36:30 +0100 Subject: [PATCH] New projector IV * Motion Slide --- openslides/motions/models.py | 8 +- openslides/motions/projector.py | 103 ++++++++++++++-- openslides/utils/projector.py | 12 ++ tests/integration/helpers.py | 26 ++-- tests/unit/motions/test_projector.py | 173 +++++++++++++++++++++++++++ 5 files changed, 304 insertions(+), 18 deletions(-) create mode 100644 tests/unit/motions/test_projector.py diff --git a/openslides/motions/models.py b/openslides/motions/models.py index 60e9a15dc..98686c911 100644 --- a/openslides/motions/models.py +++ b/openslides/motions/models.py @@ -507,9 +507,11 @@ class Motion(RESTModelMixin, models.Model): """ if self.recommendation is not None: self.set_state(self.recommendation) - if (self.recommendation_extension is not None - and self.state.show_state_extension_field - and self.recommendation.show_recommendation_extension_field): + if ( + self.recommendation_extension is not None + and self.state.show_state_extension_field + and self.recommendation.show_recommendation_extension_field + ): self.state_extension = self.recommendation_extension """ diff --git a/openslides/motions/projector.py b/openslides/motions/projector.py index 568db85a1..ab280a1bd 100644 --- a/openslides/motions/projector.py +++ b/openslides/motions/projector.py @@ -1,6 +1,6 @@ from typing import Any, Dict -from ..utils.projector import register_projector_element +from ..utils.projector import AllData, get_config, get_user, register_projector_element # Important: All functions have to be prune. This means, that thay can only @@ -9,18 +9,105 @@ from ..utils.projector import register_projector_element # to be fast! -def motion( - element: Dict[str, Any], all_data: Dict[str, Dict[int, Dict[str, Any]]] +def get_state( + all_data: AllData, motion: Dict[str, Any], state_id: int ) -> Dict[str, Any]: + """ + Returns a state element from one motion. + + Returns an error if the state_id does not exist for the workflow in the motion. + """ + states = all_data["motions/workflow"][motion["workflow_id"]]["states"] + for state in states: + if state["id"] == state_id: + return state + return {"error": f"motion {motion['id']} can not have to id {state_id}"} + + +def motion_slide(element: Dict[str, Any], all_data: AllData) -> Dict[str, Any]: """ Motion slide. + + The returned dict can contain the following fields: + * identifier + * title + * text + * amendment_paragraphs + * is_child + * show_meta_box + * reason + * modified_final_version + * state + * state_extension + * recommendation + * recommendation_extension + * submitter + * poll """ - return {"error": "TODO", "some_key": "another_value"} + mode = element.get("mode") + motion_id = element.get("id") + + if motion_id is None: + return {"error": "id is required for motion slide"} + + try: + motion = all_data["motions/motion"][motion_id] + except KeyError: + return {"error": f"motion with id {motion_id} does not exist"} + + show_meta_box = not get_config(all_data, "motions_disable_sidebox_on_projector") + + return_value = { + "identifier": motion["identifier"], + "title": motion["title"], + "text": motion["text"], + "amendment_paragraphs": motion["amendment_paragraphs"], + "is_child": bool(motion["parent_id"]), + "show_meta_box": show_meta_box, + } + + if not get_config(all_data, "motions_disable_reason_on_projector"): + return_value["reason"] = motion["reason"] + if mode == "final": + return_value["modified_final_version"] = motion["modified_final_version"] + + if show_meta_box: + state = get_state(all_data, motion, motion["state_id"]) + if state.get("error"): + return state + + return_value["state"] = state["name"] + return_value["state_extension"] = motion["state_extension"] + + if ( + not get_config(all_data, "motions_disable_recommendation_on_projector") + and motion["recommendation_id"] + ): + return_value["recommendation"] = all_data[ + "motions/motion-change-recommendation" + ][motion["recommendation_id"]]["text"] + return_value["recommendation_extension"] = motion[ + "recommendation_extension" + ] + + return_value["submitter"] = [ + get_user(all_data, submitter["user_id"]) + for submitter in motion["submitters"] + ] + + for poll in motion["polls"][::-1]: + if poll["has_votes"]: + return_value["poll"] = { + "yes": poll["yes"], + "no": poll["no"], + "abstain": poll["abstain"], + } + break + + return return_value -def motion_block( - element: Dict[str, Any], all_data: Dict[str, Dict[int, Dict[str, Any]]] -) -> Dict[str, Any]: +def motion_block(element: Dict[str, Any], all_data: AllData) -> Dict[str, Any]: """ Motion slide. """ @@ -28,5 +115,5 @@ def motion_block( def register_projector_elements() -> None: - register_projector_element("motions/motion", motion) + register_projector_element("motions/motion", motion_slide) register_projector_element("motions/motion-block", motion_block) diff --git a/openslides/utils/projector.py b/openslides/utils/projector.py index a0b4ac246..6fa2458a5 100644 --- a/openslides/utils/projector.py +++ b/openslides/utils/projector.py @@ -92,3 +92,15 @@ def get_config(all_data: AllData, key: str) -> Any: return all_data[config.get_collection_string()][config.get_key_to_id()[key]][ "value" ] + + +def get_user(all_data: AllData, user_id: int) -> Dict[str, Any]: + """ + Returns the value of a user to show his name. + """ + user = all_data["users/user"][user_id] + return { + "title": user["title"], + "first_name": user["first_name"], + "last_name": user["last_name"], + } diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 8642bf0c3..9661c16a3 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -6,7 +6,7 @@ from django.test.utils import CaptureQueriesContext from openslides.core.config import config from openslides.core.models import Projector from openslides.users.models import User -from openslides.utils.projector import get_config, register_projector_element +from openslides.utils.projector import AllData, get_config, register_projector_element class TConfig: @@ -90,18 +90,14 @@ class TProjector: return elements -def slide1( - config: Dict[str, Any], all_data: Dict[str, Dict[int, Dict[str, Any]]] -) -> Dict[str, Any]: +def slide1(element: Dict[str, Any], all_data: AllData) -> Dict[str, Any]: """ Slide that shows the general_event_name. """ return {"name": "slide1", "event_name": get_config(all_data, "general_event_name")} -def slide2( - config: Dict[str, Any], all_data: Dict[str, Dict[int, Dict[str, Any]]] -) -> Dict[str, Any]: +def slide2(element: Dict[str, Any], all_data: AllData) -> Dict[str, Any]: return {"name": "slide2"} @@ -121,3 +117,19 @@ def count_queries(func, *args, **kwargs) -> int: print(f"{len(context)} queries executed\nCaptured queries were:\n{queries}") return len(context) + + +def all_data_config() -> AllData: + return { + TConfig().get_collection_string(): { + element["id"]: element for element in TConfig().get_elements() + } + } + + +def all_data_users() -> AllData: + return { + TUser().get_collection_string(): { + element["id"]: element for element in TUser().get_elements() + } + } diff --git a/tests/unit/motions/test_projector.py b/tests/unit/motions/test_projector.py new file mode 100644 index 000000000..b84def61e --- /dev/null +++ b/tests/unit/motions/test_projector.py @@ -0,0 +1,173 @@ +from typing import Any, Dict + +import pytest + +from openslides.motions import projector + +from ...integration.helpers import all_data_config, all_data_users + + +@pytest.fixture +def all_data(): + return_value = all_data_config() + return_value.update(all_data_users()) + return_value["motions/motion"] = { + 1: { + "id": 1, + "identifier": "4", + "title": "12345", + "text": "motion text", + "amendment_paragraphs": None, + "modified_final_version": "", + "reason": "", + "parent_id": None, + "category_id": None, + "comments": [], + "motion_block_id": None, + "origin": "", + "submitters": [{"id": 4, "user_id": 1, "motion_id": 1, "weight": 1}], + "supporters_id": [], + "state_id": 1, + "state_extension": None, + "state_required_permission_to_see": "", + "statute_paragraph_id": None, + "workflow_id": 1, + "recommendation_id": None, + "recommendation_extension": None, + "tags_id": [], + "attachments_id": [], + "polls": [ + { + "id": 1, + "motion_id": 4, + "yes": "10.000000", + "no": "-1.000000", + "abstain": "20.000000", + "votesvalid": "11.000000", + "votesinvalid": "2.000000", + "votescast": "30.000000", + "has_votes": True, + } + ], + "agenda_item_id": 4, + "log_messages": [ + { + "message_list": "['Vote updated']", + "person_id": 1, + "time": "2019-01-19T22:15:53.291123+01:00", + "message": "Jan. 19, 2019, 10:15 p.m. Vote updated by Administrator", + }, + { + "message_list": "['Vote created']", + "person_id": 1, + "time": "2019-01-19T22:15:37.446262+01:00", + "message": "Jan. 19, 2019, 10:15 p.m. Vote created by Administrator", + }, + { + "message_list": "['Motion created']", + "person_id": 1, + "time": "2019-01-19T18:37:34.833749+01:00", + "message": "Jan. 19, 2019, 6:37 p.m. Motion created by Administrator", + }, + ], + "sort_parent_id": None, + "weight": 10000, + "created": "2019-01-19T18:37:34.741336+01:00", + "last_modified": "2019-01-19T18:37:34.741368+01:00", + } + } + return_value["motions/workflow"] = { + 1: { + "id": 1, + "name": "Simple Workflow", + "states": [ + { + "id": 1, + "name": "submitted", + "recommendation_label": None, + "css_class": "primary", + "required_permission_to_see": "", + "allow_support": True, + "allow_create_poll": True, + "allow_submitter_edit": True, + "dont_set_identifier": False, + "show_state_extension_field": False, + "merge_amendment_into_final": 0, + "show_recommendation_extension_field": False, + "next_states_id": [2, 3, 4], + "workflow_id": 1, + }, + { + "id": 2, + "name": "accepted", + "recommendation_label": "Acceptance", + "css_class": "success", + "required_permission_to_see": "", + "allow_support": False, + "allow_create_poll": False, + "allow_submitter_edit": False, + "dont_set_identifier": False, + "show_state_extension_field": False, + "merge_amendment_into_final": 1, + "show_recommendation_extension_field": False, + "next_states_id": [], + "workflow_id": 1, + }, + { + "id": 3, + "name": "rejected", + "recommendation_label": "Rejection", + "css_class": "danger", + "required_permission_to_see": "", + "allow_support": False, + "allow_create_poll": False, + "allow_submitter_edit": False, + "dont_set_identifier": False, + "show_state_extension_field": False, + "merge_amendment_into_final": -1, + "show_recommendation_extension_field": False, + "next_states_id": [], + "workflow_id": 1, + }, + { + "id": 4, + "name": "not decided", + "recommendation_label": "No decision", + "css_class": "default", + "required_permission_to_see": "", + "allow_support": False, + "allow_create_poll": False, + "allow_submitter_edit": False, + "dont_set_identifier": False, + "show_state_extension_field": False, + "merge_amendment_into_final": -1, + "show_recommendation_extension_field": False, + "next_states_id": [], + "workflow_id": 1, + }, + ], + "first_state_id": 1, + } + } + return_value["motions/motion-change-recommendation"] = {} + return return_value + + +def test_motion_slide(all_data): + element: Dict[str, Any] = {"id": 1} + + data = projector.motion_slide(element, all_data) + + assert data == { + "identifier": "4", + "title": "12345", + "text": "motion text", + "amendment_paragraphs": None, + "is_child": False, + "show_meta_box": True, + "reason": "", + "state": "submitted", + "state_extension": None, + "submitter": [{"first_name": "", "last_name": "Administrator", "title": ""}], + "poll": {"yes": "10.000000", "no": "-1.000000", "abstain": "20.000000"}, + }