diff --git a/openslides_backend/action/actions/meeting/clone.py b/openslides_backend/action/actions/meeting/clone.py index 89fe4934d4..f04cb1eb53 100644 --- a/openslides_backend/action/actions/meeting/clone.py +++ b/openslides_backend/action/actions/meeting/clone.py @@ -268,6 +268,7 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: raise ActionException( "Cannot create a non-template meeting without administrators" ) + self.transform_json_fields(instance) return instance def _create_or_get_meeting_user(self, meeting_id: int, user_id: int) -> int: diff --git a/openslides_backend/action/actions/meeting/import_.py b/openslides_backend/action/actions/meeting/import_.py index bdefeb6d66..e30b040dbe 100644 --- a/openslides_backend/action/actions/meeting/import_.py +++ b/openslides_backend/action/actions/meeting/import_.py @@ -4,6 +4,8 @@ from datetime import datetime from typing import Any +from psycopg.types.json import Jsonb + from openslides_backend.action.actions.meeting.mixins import MeetingPermissionMixin from openslides_backend.migrations.migration_helper import MigrationHelper from openslides_backend.models.base import model_registry @@ -13,9 +15,11 @@ BaseRelationField, GenericRelationField, GenericRelationListField, + JSONField, OnDelete, RelationField, RelationListField, + TimestampField, ) from openslides_backend.models.models import Meeting from openslides_backend.permissions.management_levels import OrganizationManagementLevel @@ -161,9 +165,42 @@ def preprocess_data(self, instance: dict[str, Any]) -> dict[str, Any]: self.remove_not_allowed_fields(instance) self.set_committee_and_orga_relation(instance) self.check_data_migration_index(instance) + self.transform_timestamps(instance) self.unset_committee_and_orga_relation(instance) return instance + def transform_timestamps(self, instance: dict[str, Any]) -> dict[str, Any]: + for collection, collection_data in instance["meeting"].items(): + if model := model_registry.get(collection): + fields = list(model().get_fields()) + timestamp_field_names = [ + field.own_field_name + for field in fields + if isinstance(field, TimestampField) + ] + if timestamp_field_names: + for mod in collection_data.values(): + for field in timestamp_field_names: + if (iso := mod.get(field)) and isinstance(iso, str): + mod[field] = datetime.fromisoformat(iso) + return instance + + def transform_json_fields(self, instance: dict[str, Any]) -> dict[str, Any]: + for collection, collection_data in instance["meeting"].items(): + if model := model_registry.get(collection): + fields = list(model().get_fields()) + json_field_names = [ + field.own_field_name + for field in fields + if isinstance(field, JSONField) + ] + if json_field_names: + for mod in collection_data.values(): + for field in json_field_names: + if field in mod: + mod[field] = Jsonb(mod[field]) + return instance + def check_one_meeting(self, instance: dict[str, Any]) -> None: if len(instance["meeting"]["meeting"]) != 1: raise ActionException("Need exactly one meeting in meeting collection.") @@ -276,6 +313,7 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: raise ActionException(str(ce)) self.allowed_collections = checker.allowed_collections + self.transform_json_fields(instance) self.check_limit_of_meetings() self.update_meeting_and_users(instance) @@ -752,7 +790,7 @@ def check_data_migration_index(self, instance: dict[str, Any]) -> None: """ Check for valid migration index. """ - start_migration_index = instance["meeting"].pop("_migration_index") + start_migration_index = instance["meeting"].get("_migration_index") backend_migration_index = MigrationHelper.get_backend_migration_index() if backend_migration_index < start_migration_index: raise ActionException( diff --git a/openslides_backend/models/fields.py b/openslides_backend/models/fields.py index bebc49dca0..eed3ed0307 100644 --- a/openslides_backend/models/fields.py +++ b/openslides_backend/models/fields.py @@ -1,5 +1,5 @@ from datetime import datetime -from decimal import Decimal +from decimal import Decimal, InvalidOperation from enum import StrEnum from typing import Any, cast @@ -253,7 +253,12 @@ def validate(self, value: Any, payload: dict[str, Any] = {}) -> Any: if value is not None or self.required: if (min_ := self.constraints.get("minimum")) is not None: if isinstance(value, str): - value = Decimal(value) + try: + value = Decimal(value) + except InvalidOperation: + raise ActionException( + f"{self.own_field_name}: value '{value}' couldn't be converted to decimal." + ) elif not isinstance(value, Decimal | None): raise NotImplementedError( f"Unexpected type: {type(value)} (value: {value}) for field {self.get_own_field_name()}" diff --git a/openslides_backend/presenter/export_meeting.py b/openslides_backend/presenter/export_meeting.py index a6df59c5b2..176e1e7324 100644 --- a/openslides_backend/presenter/export_meeting.py +++ b/openslides_backend/presenter/export_meeting.py @@ -40,7 +40,9 @@ def get_result(self) -> Any: msg = "You are not allowed to perform presenter export_meeting." msg += f" Missing permission: {OrganizationManagementLevel.SUPERADMIN}" raise PermissionDenied(msg) - export_data = export_meeting(self.datastore, self.data["meeting_id"]) + export_data = export_meeting( + self.datastore, self.data["meeting_id"], datetime_decimal_to_string=True + ) if id_ := next( ( id_ diff --git a/openslides_backend/shared/export_helper.py b/openslides_backend/shared/export_helper.py index ca644791b0..5d8b95db10 100644 --- a/openslides_backend/shared/export_helper.py +++ b/openslides_backend/shared/export_helper.py @@ -1,4 +1,6 @@ +import datetime from collections.abc import Iterable +from decimal import Decimal from typing import Any from openslides_backend.migrations.migration_helper import MigrationHelper @@ -34,6 +36,7 @@ def export_meeting( meeting_id: int, internal_target: bool = False, update_mediafiles: bool = False, + datetime_decimal_to_string: bool = False, ) -> dict[str, Any]: export: dict[str, Any] = {} @@ -212,6 +215,14 @@ def export_meeting( export[collection] = dict( sorted(instances.items(), key=lambda item: int(item[0])) ) + if datetime_decimal_to_string and isinstance(instances, dict): + for data in instances.values(): + for field, value in data.items(): + if isinstance(value, datetime.datetime): + data[field] = value.isoformat() + if isinstance(value, Decimal): + data[field] = str(value) + return export diff --git a/tests/system/action/meeting/test_clone.py b/tests/system/action/meeting/test_clone.py index a53e4df526..907ac551f4 100644 --- a/tests/system/action/meeting/test_clone.py +++ b/tests/system/action/meeting/test_clone.py @@ -1372,11 +1372,11 @@ def test_meeting_name_exact_fit(self) -> None: self.assert_model_exists("meeting/2", {"name": long_name + " - Copy"}) def test_meeting_name_too_long(self) -> None: - self.meeting_data["name"] = "A" * 100 + self.meeting_data["name"] = "A" * 200 self.set_test_data_with_admin() response = self.request("meeting.clone", {"meeting_id": 1}) self.assert_status_code(response, 200) - self.assert_model_exists("meeting/2", {"name": "A" * 90 + "... - Copy"}) + self.assert_model_exists("meeting/2", {"name": "A" * 190 + "... - Copy"}) def test_permissions_explicit_source_committee_permission(self) -> None: self.set_test_data() @@ -1841,6 +1841,21 @@ def test_clone_amendment_paragraphs(self) -> None: response.json["message"], ) + def test_clone_amendment_paragraphs_regular(self) -> None: + self.set_test_data() + self.set_user_groups(1, [1]) + self.create_motion( + 1, 1, motion_data={"amendment_paragraphs": Jsonb({"1": "
test
"})} + ) + response = self.request( + "meeting.clone", + { + "meeting_id": 1, + "admin_ids": [1], + }, + ) + self.assert_status_code(response, 200) + def test_permissions_oml_locked_meeting(self) -> None: self.create_meeting( meeting_data={"locked_from_inside": True, "template_for_organization_id": 1} @@ -2134,3 +2149,15 @@ def test_clone_with_structured_published_orga_files(self) -> None: for fqid, model in models.items(): self.assert_model_exists(fqid, model) self.media.duplicate_mediafile.assert_not_called() + + def test_clone_require_duplicate_from_allowed(self) -> None: + self.set_test_data_with_admin() + self.set_models( + { + "meeting/1": {"template_for_organization_id": 1, "name": "m1"}, + } + ) + self.set_committee_management_level([60]) + self.set_organization_management_level(None) + response = self.request("meeting.clone", {"meeting_id": 1}) + self.assert_status_code(response, 200) diff --git a/tests/system/action/meeting/test_import.py b/tests/system/action/meeting/test_import.py index e0c75cebbd..98eae058fe 100644 --- a/tests/system/action/meeting/test_import.py +++ b/tests/system/action/meeting/test_import.py @@ -3,11 +3,11 @@ from copy import deepcopy from typing import Any -import pytest from psycopg.types.json import Jsonb from openslides_backend.action.action_worker import ActionWorkerState from openslides_backend.http.views.presenter_view import PresenterView +from openslides_backend.migrations.migration_helper import MigrationHelper from openslides_backend.models.models import Meeting from openslides_backend.shared.util import ONE_ORGANIZATION_FQID, get_initial_data_file from tests.system.action.base import BaseActionTestCase @@ -20,10 +20,10 @@ from tests.util import Client -@pytest.mark.skip(reason="Requires initial migration. TODO: unskip once it is added.") class MeetingImport(BaseActionTestCase): def setUp(self) -> None: super().setUp() + self.mig_index = MigrationHelper.get_backend_migration_index() self.create_meeting(1, {"external_id": "ext_id"}) self.create_motion(1, 1, motion_data={"number_value": 31}) self.set_models( @@ -39,6 +39,7 @@ def create_request_data( data: dict[str, Any] = { "committee_id": 60, "meeting": { + "_migration_index": self.mig_index, "meeting": { "1": { "id": 1, @@ -58,8 +59,8 @@ def create_request_data( "template_for_organization_id": None, "enable_anonymous": False, "location": "", - "start_time": 10, - "end_time": 10, + "start_time": "1989-11-09T19:00:00+01:00", + "end_time": "1990-10-03T00:00:00+01:00", "welcome_title": "Welcome to OpenSlides", "welcome_text": "[Space for your welcome text.]", "conference_show": False, @@ -390,8 +391,8 @@ def get_motion_data(self, obj_id: int, data: dict[str, Any] = {}) -> dict[str, A "state_extension": "regeer
", "recommendation_extension": None, "sort_weight": 10000, - "created": 1584512346, - "last_modified": 1584512346, + "created": "1990-07-06T00:00:00+01:00", + "last_modified": "1990-07-22T12:00:00+01:00", "start_line_number": 1, **data, } @@ -410,7 +411,7 @@ def get_mediafile_data( "filename": "A.txt", "mimetype": "text/plain", "pdf_information": {}, - "create_timestamp": 1584513771, + "create_timestamp": "1990-07-22T12:00:00+01:00", "parent_id": None, "child_ids": [], "meeting_mediafile_ids": [obj_id], @@ -449,6 +450,7 @@ def test_no_meeting_collection(self) -> None: { "committee_id": 1, "meeting": { + "_migration_index": self.mig_index, "meeting": {}, }, }, @@ -465,6 +467,7 @@ def test_too_many_meeting_collections(self) -> None: { "committee_id": 1, "meeting": { + "_migration_index": self.mig_index, "meeting": {"1": {"id": 1}, "2": {"id": 2}}, }, }, @@ -561,18 +564,18 @@ def test_replace_ids_and_write_to_datastore(self) -> None: { "name": "Test", "description": "blablabla", - "committee_id": 1, + "committee_id": 60, "enable_anonymous": False, "is_active_in_organization_id": 1, }, ) - assert start <= meeting_2.get("imported_at", 0) <= end + assert start <= round(meeting_2.get("imported_at", 0).timestamp()) <= end user_2 = self.assert_model_exists( "user/2", { "username": "test", "meeting_ids": [2], - "committee_ids": [1], + "committee_ids": [60], "meeting_user_ids": [1], }, ) @@ -584,17 +587,17 @@ def test_replace_ids_and_write_to_datastore(self) -> None: "user_id": 2, "structure_level_ids": [1], "personal_note_ids": [1], - "motion_submitter_ids": [], - "group_ids": [2], + "motion_submitter_ids": None, + "group_ids": [4], }, ) self.assert_model_exists( - "meeting_user/2", {"meeting_id": 2, "user_id": 1, "group_ids": [2]} + "meeting_user/2", {"meeting_id": 2, "user_id": 1, "group_ids": [4]} ) self.assert_model_exists( - "projector/2", {"meeting_id": 2, "sequential_number": 2} + "projector/2", {"meeting_id": 2, "sequential_number": 1} ) - self.assert_model_exists("group/2", {"meeting_user_ids": [1, 2]}) + self.assert_model_exists("group/4", {"meeting_user_ids": [1, 2]}) self.assert_model_exists( "personal_note/1", {"content_object_id": "motion/2", "meeting_user_id": 1, "meeting_id": 2}, @@ -606,7 +609,7 @@ def test_replace_ids_and_write_to_datastore(self) -> None: "structure_level/1", {"meeting_user_ids": [1], "name": "meeting freak"} ) self.assert_model_exists( - "committee/1", {"user_ids": [2, 1], "meeting_ids": [1, 2]} + "committee/60", {"user_ids": [1, 2], "meeting_ids": [1, 2]} ) self.assert_model_exists(ONE_ORGANIZATION_FQID, {"active_meeting_ids": [1, 2]}) @@ -699,6 +702,7 @@ def test_check_usernames_1(self) -> None: del request_data["meeting"]["meeting_user"]["11"] request_data["meeting"]["meeting"]["1"]["admin_group_id"] = 1111 request_data["meeting"]["meeting"]["1"]["meeting_user_ids"] = [111] + request_data["meeting"]["meeting"]["1"]["user_ids"] = [11] request_data["meeting"]["meeting"]["1"]["group_ids"] = [2, 1111] req_user = request_data["meeting"]["user"]["11"] self.set_models( @@ -718,13 +722,13 @@ def test_check_usernames_1(self) -> None: imported_meeting = self.assert_model_exists( "meeting/2", { - "group_ids": [2, 3], - "committee_id": 1, + "group_ids": [4, 5], + "committee_id": 60, "projector_ids": [2], - "admin_group_id": 3, - "default_group_id": 2, - "motion_state_ids": [1], - "motion_workflow_ids": [1], + "admin_group_id": 5, + "default_group_id": 4, + "motion_state_ids": [2], + "motion_workflow_ids": [2], "is_active_in_organization_id": 1, }, ) @@ -747,7 +751,7 @@ def test_check_usernames_1(self) -> None: { "meeting_id": 2, "user_id": 1, - "group_ids": [3], + "group_ids": [5], "comment": "imported user111 for external meeting1", }, ) @@ -757,20 +761,20 @@ def test_check_usernames_1(self) -> None: "group/1", { "meeting_id": 1, - "name": "group1_m1", + "name": "group1", }, ) self.assert_model_exists( - "group/2", + "group/4", { "name": "imported default group2", - "meeting_user_ids": [], + "meeting_user_ids": None, "meeting_id": 2, "default_group_for_meeting_id": 2, }, ) self.assert_model_exists( - "group/3", + "group/5", { "name": "group1111", "meeting_user_ids": [1], @@ -801,6 +805,7 @@ def test_check_usernames_2(self) -> None: "last_name": "admin1", }, ) + request_data["meeting"]["meeting"]["1"]["user_ids"] = [1, 2] response = self.request("meeting.import", request_data) self.assert_status_code(response, 200) @@ -820,13 +825,13 @@ def test_check_usernames_2(self) -> None: "user/3", {"username": "admin11", "last_name": "admin1"} ) self.assert_model_exists( - "meeting_user/1", {"meeting_id": 2, "user_id": 2, "group_ids": [2]} + "meeting_user/1", {"meeting_id": 2, "user_id": 2, "group_ids": [4]} ) self.assert_model_exists( - "meeting_user/2", {"meeting_id": 2, "user_id": 1, "group_ids": [2]} + "meeting_user/2", {"meeting_id": 2, "user_id": 1, "group_ids": [4]} ) self.assert_model_exists( - "group/2", {"meeting_user_ids": [1, 2], "meeting_id": 2} + "group/4", {"meeting_user_ids": [1, 2], "meeting_id": 2} ) def test_check_usernames_new_and_twice(self) -> None: @@ -963,13 +968,13 @@ def test_double_import(self) -> None: ) self.assert_model_exists( "meeting_user/1", - {"user_id": 2, "meeting_id": 2, "group_ids": [2], "personal_note_ids": [1]}, + {"user_id": 2, "meeting_id": 2, "group_ids": [4], "personal_note_ids": [1]}, ) self.assert_model_exists( - "meeting_user/2", {"user_id": 1, "meeting_id": 2, "group_ids": [2]} + "meeting_user/2", {"user_id": 1, "meeting_id": 2, "group_ids": [4]} ) self.assert_model_exists( - "group/2", + "group/4", { "meeting_user_ids": [1, 2], "meeting_id": 2, @@ -977,9 +982,9 @@ def test_double_import(self) -> None: }, ) self.assert_model_exists( - "group/3", + "group/5", { - "meeting_user_ids": [], + "meeting_user_ids": None, "meeting_id": 2, "default_group_for_meeting_id": 2, }, @@ -997,35 +1002,37 @@ def test_double_import(self) -> None: "username": "test", "meeting_user_ids": [1, 3], "meeting_ids": [2, 3], - "committee_ids": [1], + "committee_ids": [60], }, ) self.assert_model_exists( "meeting_user/3", - {"user_id": 2, "meeting_id": 3, "group_ids": [4], "personal_note_ids": [2]}, + {"user_id": 2, "meeting_id": 3, "group_ids": [6], "personal_note_ids": [2]}, ) self.assert_model_exists( - "meeting_user/4", {"user_id": 1, "meeting_id": 3, "group_ids": [4]} + "meeting_user/4", {"user_id": 1, "meeting_id": 3, "group_ids": [6]} ) meeting_3 = self.assert_model_exists( "meeting/3", { "name": "Test", "description": "blablabla", - "committee_id": 1, + "committee_id": 60, "enable_anonymous": False, - "user_ids": [2, 1], - "group_ids": [4, 5], + "user_ids": [1, 2], + "group_ids": [6, 7], "meeting_user_ids": [3, 4], }, ) - assert start <= meeting_3.get("imported_at", 0) <= start + 300 + assert ( + start <= round(meeting_3.get("imported_at", 0).timestamp()) <= start + 300 + ) self.assert_model_exists( - "projector/3", {"meeting_id": 3, "sequential_number": 3} + "projector/3", {"meeting_id": 3, "sequential_number": 1} ) self.assert_model_exists( - "group/4", + "group/6", { "meeting_user_ids": [3, 4], "meeting_id": 3, @@ -1033,10 +1040,10 @@ def test_double_import(self) -> None: }, ) self.assert_model_exists( - "group/5", + "group/7", { "name": "imported default group2", - "meeting_user_ids": [], + "meeting_user_ids": None, "meeting_id": 3, "default_group_for_meeting_id": 3, }, @@ -1048,7 +1055,7 @@ def test_double_import(self) -> None: "tag/2", {"tagged_ids": ["motion/3"], "name": "testag", "meeting_id": 3} ) self.assert_model_exists( - "committee/1", {"user_ids": [2, 1], "meeting_ids": [1, 2, 3]} + "committee/60", {"user_ids": [1, 2], "meeting_ids": [1, 2, 3]} ) def test_no_permission(self) -> None: @@ -1160,11 +1167,9 @@ def test_meeting_user_ids(self) -> None: # User/1 is in user_ids, because calling user is added response = self.request("meeting.import", self.create_request_data({})) self.assert_status_code(response, 200) - # XXX meeting2 = self.assert_model_exists("meeting/2") - # XXX self.assertCountEqual(meeting2["user_ids"], [1, 2]) - # self.assert_model_exists("user/2", {"username": "test", "meeting_ids": [2]}) - organization = self.assert_model_exists("organization/1") - self.assertCountEqual(organization.get("user_ids", []), [1, 2]) + self.assert_model_exists("meeting/2", {"user_ids": [1, 2]}) + self.assert_model_exists("user/2", {"username": "test", "meeting_ids": [2]}) + self.assert_model_exists("organization/1", {"user_ids": [1, 2]}) def test_motion_recommendation_extension(self) -> None: # Special field @@ -1222,7 +1227,7 @@ def test_motion_recommendation_extension(self) -> None: "state_extension_reference_ids": ["motion/2"], "recommendation_extension": "bla[motion/2]bla", "recommendation_extension_reference_ids": ["motion/2"], - "sequential_number": 3, + "sequential_number": 2, }, ) @@ -1393,23 +1398,31 @@ def test_request_user_in_admin_group(self) -> None: "user/1", {"meeting_user_ids": [2], "username": "admin"} ) self.assert_model_exists( - "meeting_user/2", {"group_ids": [2], "meeting_id": 2, "user_id": 1} + "meeting_user/2", {"group_ids": [4], "meeting_id": 2, "user_id": 1} ) self.assert_model_exists( "user/2", {"meeting_user_ids": [1], "username": "test"} ) self.assert_model_exists( - "meeting_user/1", {"group_ids": [2], "meeting_id": 2, "user_id": 2} + "meeting_user/1", {"group_ids": [4], "meeting_id": 2, "user_id": 2} ) - self.assert_model_exists("meeting/2", {"user_ids": [2, 1]}) + self.assert_model_exists("meeting/2", {"user_ids": [1, 2]}) self.assert_model_exists( - "group/2", + "group/4", { "meeting_user_ids": [1, 2], "meeting_id": 2, "name": "imported admin group1", }, ) + self.assert_model_exists( + "group/5", + { + "name": "imported default group2", + "meeting_user_ids": None, + "default_group_for_meeting_id": 2, + }, + ) def test_motion_all_derived_motion_ids(self) -> None: """ @@ -1454,7 +1467,7 @@ def test_motion_all_derived_motion_ids(self) -> None: "derived_motion_ids": None, "all_origin_ids": None, "all_derived_motion_ids": None, - "sequential_number": 2, + "sequential_number": 1, }, ) @@ -1509,11 +1522,11 @@ def test_motion_all_origin_ids(self) -> None: response = self.request("meeting.import", request_data) self.assert_status_code(response, 200) motion = self.assert_model_exists( - "motion/2", {"meeting_id": 2, "sequential_number": 2} + "motion/2", {"meeting_id": 2, "sequential_number": 1} ) assert motion.get("all_origin_ids") is None motion = self.assert_model_exists( - "motion/3", {"meeting_id": 2, "sequential_number": 3} + "motion/3", {"meeting_id": 2, "sequential_number": 2} ) assert motion.get("all_derived_motion_ids") is None @@ -1555,7 +1568,7 @@ def test_foreign_motion_all_origin_ids(self) -> None: "meeting_id": 2, "all_origin_ids": None, "origin_meeting_id": None, - "sequential_number": 2, + "sequential_number": 1, }, ) @@ -1721,10 +1734,10 @@ def test_check_hit_limit_of_users(self) -> None: def test_merge_users_check_committee_and_meeting(self) -> None: self.set_models( { - "committee/1": { + "committee/60": { "user_ids": [1, 14], }, - "committee/2": { + "committee/61": { "name": "Committee for imported meeting", }, "meeting/1": { @@ -1771,6 +1784,7 @@ def test_merge_users_check_committee_and_meeting(self) -> None: "username": "username_to_merge", "email": "test@example.de", "meeting_user_ids": [12], + "meeting_ids": [1], "organization_id": 1, }, "13": { @@ -1778,6 +1792,7 @@ def test_merge_users_check_committee_and_meeting(self) -> None: "username": "username_import13", "email": "test_new@example.de", "meeting_user_ids": [13], + "meeting_ids": [1], "organization_id": 1, }, }, @@ -1802,7 +1817,7 @@ def test_merge_users_check_committee_and_meeting(self) -> None: request_data["meeting"]["meeting"]["1"]["meeting_user_ids"] = [11, 12, 13] request_data["meeting"]["meeting"]["1"]["user_ids"] = [1, 12, 13] request_data["meeting"]["user"]["1"]["username"] = "username_import1" - request_data["committee_id"] = 2 + request_data["committee_id"] = 61 response = self.request("meeting.import", request_data) self.assert_status_code(response, 200) assert response.json["results"][0][0]["number_of_imported_users"] == 3 @@ -1812,7 +1827,7 @@ def test_merge_users_check_committee_and_meeting(self) -> None: { "username": "admin", "meeting_ids": [1, 2], - "committee_ids": [1, 2], + "committee_ids": [60, 61], "meeting_user_ids": [1, 18], }, ) @@ -1821,7 +1836,7 @@ def test_merge_users_check_committee_and_meeting(self) -> None: { "username": "username_to_merge", "meeting_ids": [1, 2], - "committee_ids": [1, 2], + "committee_ids": [60, 61], "meeting_user_ids": [14, 16], }, ) @@ -1830,7 +1845,7 @@ def test_merge_users_check_committee_and_meeting(self) -> None: { "username": "username_import1", "meeting_ids": [2], - "committee_ids": [2], + "committee_ids": [61], "meeting_user_ids": [15], }, ) @@ -1839,21 +1854,27 @@ def test_merge_users_check_committee_and_meeting(self) -> None: { "username": "username_import13", "meeting_ids": [2], - "committee_ids": [2], + "committee_ids": [61], "meeting_user_ids": [17], }, ) - committee1 = self.assert_model_exists("committee/1", {"meeting_ids": [1]}) - assert sorted(committee1.get("user_ids", [])) == [1, 14] - meeting1 = self.assert_model_exists("meeting/1", {"committee_id": 1}) - assert sorted(meeting1.get("user_ids", [])) == [1, 14] - assert sorted(meeting1.get("meeting_user_ids", [])) == [1, 14] - self.assert_model_exists("committee/2", {"meeting_ids": [2]}) - self.assert_model_exists("meeting/2", {"committee_id": 2}) - organization = self.assert_model_exists( - "organization/1", {"committee_ids": [1, 2], "active_meeting_ids": [1, 2]} + self.assert_model_exists( + "committee/60", {"meeting_ids": [1], "user_ids": [1, 14]} + ) + self.assert_model_exists( + "meeting/1", + {"committee_id": 60, "user_ids": [1, 14], "meeting_user_ids": [1, 14]}, + ) + self.assert_model_exists("committee/61", {"meeting_ids": [2]}) + self.assert_model_exists("meeting/2", {"committee_id": 61}) + self.assert_model_exists( + "organization/1", + { + "committee_ids": [60, 61], + "active_meeting_ids": [1, 2], + "user_ids": [1, 14, 15, 16], + }, ) - assert sorted(organization.get("user_ids", [])) == [1, 14, 15, 16] def test_merge_users_check_user_meeting_ids(self) -> None: self.set_models( @@ -1905,14 +1926,15 @@ def test_merge_users_check_user_meeting_ids(self) -> None: ) request_data["meeting"]["group"]["1"]["meeting_user_ids"] = [11, 12] request_data["meeting"]["meeting"]["1"]["meeting_user_ids"] = [11, 12] + request_data["meeting"]["meeting"]["1"]["user_ids"] = [1, 12] response = self.request("meeting.import", request_data) self.assert_status_code(response, 200) assert response.json["results"][0][0]["number_of_imported_users"] == 2 assert response.json["results"][0][0]["number_of_merged_users"] == 1 self.assert_model_exists( - "committee/1", {"meeting_ids": [1, 2], "user_ids": [15, 14, 1]} + "committee/60", {"meeting_ids": [1, 2], "user_ids": [1, 14, 15]} ) - meeting2 = self.assert_model_exists("meeting/2", {"committee_id": 1}) + meeting2 = self.assert_model_exists("meeting/2", {"committee_id": 60}) assert sorted(meeting2.get("user_ids", [])) == [1, 14, 15] self.assert_model_exists("meeting/1", {"user_ids": [14]}) self.assert_model_exists("user/1", {"username": "admin", "meeting_ids": [2]}) @@ -2049,8 +2071,18 @@ def test_merge_meeting_users_fields(self) -> None: "personal_note_ids": [1], "motion_submitter_ids": [], "vote_delegated_to_id": 1, + "group_ids": [1], + }, + "user/1": { + "meeting_user_ids": [1], }, - "group/1": {"meeting_user_ids": [14]}, + "meeting_user/1": { + "meeting_id": 1, + "user_id": 1, + "vote_delegations_from_ids": [14], + "group_ids": [1], + }, + "group/1": {"meeting_user_ids": [1, 14]}, "personal_note/1": { "meeting_id": 1, "content_object_id": None, @@ -2060,7 +2092,7 @@ def test_merge_meeting_users_fields(self) -> None: }, "meeting/1": { "personal_note_ids": [1], - "meeting_user_ids": [14], + "meeting_user_ids": [1, 14], }, } ) @@ -2253,7 +2285,7 @@ def test_import_amendment_paragraphs(self) -> None: "1": "<it>test</it>", "2": "broken", }, - "sequential_number": 2, + "sequential_number": 1, }, ) @@ -2263,7 +2295,7 @@ def test_import_with_wrong_decimal(self) -> None: response = self.request("meeting.import", data) self.assert_status_code(response, 400) assert ( - "user/1/default_vote_weight: Type error: Type is not