diff --git a/Dockerfile b/Dockerfile index 6ac8fa2fda..d6f74b8873 100644 --- a/Dockerfile +++ b/Dockerfile @@ -74,6 +74,7 @@ COPY dev/cleanup.sh . # Copy files which are mounted to make the full stack work COPY scripts scripts COPY cli cli +COPY data data COPY meta meta COPY Makefile . @@ -100,11 +101,13 @@ RUN adduser --system --no-create-home appuser COPY scripts scripts COPY entrypoint.sh ./ COPY openslides_backend openslides_backend +COPY data data COPY meta meta RUN chown appuser ./scripts/ && \ chown appuser ./entrypoint.sh && \ chown appuser ./openslides_backend && \ + chown appuser ./data && \ chown appuser ./meta && \ chown appuser ./command.sh diff --git a/data/example-data.json b/data/example-data.json new file mode 100644 index 0000000000..6379615bf5 --- /dev/null +++ b/data/example-data.json @@ -0,0 +1,2606 @@ +{ + "_migration_index": 68, + "gender":{ + "1":{ + "id": 1, + "name": "male", + "organization_id": 1, + "user_ids":[1] + }, + "2":{ + "id": 2, + "name": "female", + "organization_id": 1, + "user_ids":[2] + }, + "3":{ + "id": 3, + "name": "diverse", + "organization_id": 1, + "user_ids":[3] + }, + "4":{ + "id": 4, + "name": "non-binary", + "organization_id": 1, + "user_ids":[] + } + }, + "organization": { + "1": { + "id": 1, + "name": "Test Organization", + "legal_notice": "OpenSlides is a free web based presentation and assembly system for visualizing and controlling agenda, motions and elections of an assembly.", + "login_text": "Good Morning!", + "default_language": "en", + "gender_ids": [1, 2 ,3 ,4], + "enable_electronic_voting": true, + "enable_chat": true, + "reset_password_verbose_errors": true, + "committee_ids": [ + 1 + ], + "active_meeting_ids": [ + 1 + ], + "limit_of_meetings": 0, + "limit_of_users": 0, + "organization_tag_ids": [ + 1 + ], + "theme_id": 1, + "mediafile_ids": [], + "theme_ids": [ + 1, + 2, + 3 + ], + "users_email_sender": "OpenSlides", + "users_email_subject": "OpenSlides access data", + "users_email_body": "Dear {name},\n\nthis is your personal OpenSlides login:\n\n{url}\nUsername: {username}\nPassword: {password}\n\n\nThis email was generated automatically.", + "url": "https://example.com", + "user_ids": [1, 2, 3], + "require_duplicate_from": false, + "saml_enabled": false, + "saml_login_button_text": "SAML Login", + "saml_attr_mapping": { + "saml_id": "username", + "title": "title", + "first_name": "firstName", + "last_name": "lastName", + "email": "email", + "gender": "gender", + "pronoun": "pronoun", + "is_active": "is_active", + "is_physical_person": "is_person", + "member_number": "member_number" + } + } + }, + "user": { + "1": { + "id": 1, + "username": "admin", + "last_name": "Administrator", + "is_active": true, + "is_physical_person": true, + "password": "316af7b2ddc20ead599c38541fbe87e9a9e4e960d4017d6e59de188b41b2758flD5BVZAZ8jLy4nYW9iomHcnkXWkfk3PgBjeiTSxjGG7+fBjMBxsaS1vIiAMxYh+K38l0gDW4wcP+i8tgoc4UBg==", + "default_password": "admin", + "can_change_own_password": true, + "gender_id": 1, + "default_vote_weight": "1.000000", + "organization_management_level": "superadmin", + "is_present_in_meeting_ids": [ + 1 + ], + "committee_ids": [ + 1 + ], + "committee_management_ids": [1], + "poll_voted_ids": [5], + "option_ids": [5, 7], + "vote_ids": [9], + "delegated_vote_ids": [9], + "meeting_user_ids": [1], + "meeting_ids": [ + 1 + ], + "organization_id": 1 + }, + "2": { + "id": 2, + "username": "a", + "first_name": "a", + "is_active": true, + "is_physical_person": true, + "password": "316af7b2ddc20ead599c38541fbe87e9a9e4e960d4017d6e59de188b41b2758fDB3tv5HcCtPRREt7bPGqerTf1AbmoKXt/fVFkLY4znDRh2Yy0m3ZjXD0nHI8oa6KrGlHH/cvysfvf8i2fWIzmw==", + "default_password": "a", + "can_change_own_password": true, + "gender_id": 2, + "default_vote_weight": "1.000000", + "committee_ids": [ + 1 + ], + "option_ids": [9, 12], + "meeting_user_ids": [2], + "meeting_ids": [ + 1 + ], + "organization_id": 1 + }, + "3": { + "id": 3, + "username": "b", + "first_name": "b", + "is_active": true, + "is_physical_person": true, + "password": "316af7b2ddc20ead599c38541fbe87e9a9e4e960d4017d6e59de188b41b2758fIxDxvpkn6dDLRxT9DxJhZ/f04AL2oK2beICRFobSw53CI93U+dfN+w+NaL7BvrcR4JWuMj9NkH4dVjnnI0YTkg==", + "default_password": "jKwSLGCk", + "can_change_own_password": true, + "gender_id": 3, + "default_vote_weight": "1.000000", + "option_ids": [8, 11], + "meeting_user_ids": [3], + "meeting_ids": [ + 1 + ], + "organization_id": 1, + "committee_ids": [ + 1 + ] + } + }, + "meeting_user": { + "1": { + "id": 1, + "user_id": 1, + "meeting_id": 1, + "comment": "Test comment", + "number": "12345-67890", + "about_me": "What I want to say about me.", + "vote_weight": "1.000000", + "personal_note_ids": [1], + "speaker_ids": [1, 5, 6, 12], + "motion_submitter_ids": [1, 2, 3, 4], + "assignment_candidate_ids": [1], + "structure_level_ids": [1], + "group_ids": [2] + }, + "2": { + "id": 2, + "user_id": 2, + "meeting_id": 1, + "comment": "Test comment a", + "number": "12345-67891", + "about_me": "What I want to say about me with a", + "vote_weight": "1.000000", + "speaker_ids": [2, 3, 7, 10, 11, 13], + "assignment_candidate_ids": [3, 5], + "structure_level_ids": [2], + "group_ids": [5] + }, + "3": { + "id": 3, + "user_id": 3, + "meeting_id": 1, + "comment": "Test comment b as an external user", + "number": "12345-67892", + "about_me": "What I want to say about me. B", + "vote_weight": "1.000000", + "speaker_ids": [4, 8, 9], + "supported_motion_ids": [3], + "assignment_candidate_ids": [2, 4], + "structure_level_ids": [3], + "group_ids": [5] + } + }, + "theme": { + "1": { + "id": 1, + "name": "OpenSlides Blue", + "accent_500": "#2196f3", + "primary_500": "#317796", + "warn_500": "#f06400", + "organization_id": 1, + "theme_for_organization_id": 1 + }, + "2": { + "id": 2, + "name": "OpenSlides Red", + "accent_500": "#03a9f4", + "primary_500": "#c31c23", + "warn_500": "#11c2a2", + "organization_id": 1 + }, + "3": { + "id": 3, + "name": "OpenSlides Green", + "accent_500": "#55c3b6", + "primary_500": "#46962c", + "warn_500": "#e359ce", + "organization_id": 1 + } + }, + "organization_tag": { + "1": { + "id": 1, + "name": "Orga Tag 1", + "color": "#317796", + "tagged_ids": [ + "committee/1", + "meeting/1" + ], + "organization_id": 1 + } + }, + "committee": { + "1": { + "id": 1, + "name": "Default committee", + "description": "Add description here", + "meeting_ids": [ + 1 + ], + "default_meeting_id": 1, + "user_ids": [ + 1, + 2, + 3 + ], + "manager_ids": [1], + "organization_tag_ids": [ + 1 + ], + "organization_id": 1 + } + }, + "meeting": { + "1": { + "id": 1, + "is_active_in_organization_id": 1, + "language": "en", + "name": "OpenSlides Demo", + "description": "Presentation and assembly system", + "welcome_title": "Welcome to OpenSlides", + "welcome_text": "[Space for your welcome text.]", + "conference_open_microphone": false, + "conference_open_video": false, + "conference_auto_connect": false, + "conference_auto_connect_next_speakers": 0, + "conference_enable_helpdesk": false, + "conference_los_restriction": true, + "conference_show": false, + "applause_enable": false, + "applause_type": "applause-type-bar", + "applause_min_amount": 1, + "applause_max_amount": 0, + "applause_show_level": false, + "applause_timeout": 5, + "enable_anonymous": false, + "projector_countdown_default_time": 60, + "projector_countdown_warning_time": 0, + "export_csv_encoding": "utf-8", + "export_csv_separator": ";", + "export_pdf_pagenumber_alignment": "center", + "export_pdf_fontsize": 10, + "export_pdf_line_height": 1.25, + "export_pdf_page_margin_left": 20, + "export_pdf_page_margin_top": 25, + "export_pdf_page_margin_right": 20, + "export_pdf_page_margin_bottom": 20, + "export_pdf_pagesize": "A4", + "agenda_enable_numbering": true, + "agenda_numeral_system": "arabic", + "agenda_item_creation": "default_no", + "agenda_new_items_default_visibility": "internal", + "agenda_show_internal_items_on_projector": false, + "agenda_show_subtitles": false, + "agenda_number_prefix": "TOP", + "agenda_show_topic_navigation_on_detail_view": false, + "list_of_speakers_amount_last_on_projector": 0, + "list_of_speakers_amount_next_on_projector": -1, + "list_of_speakers_couple_countdown": true, + "list_of_speakers_show_amount_of_speakers_on_slide": true, + "list_of_speakers_show_first_contribution": false, + "list_of_speakers_hide_contribution_count": false, + "list_of_speakers_allow_multiple_speakers": false, + "list_of_speakers_enable_point_of_order_speakers": true, + "list_of_speakers_can_create_point_of_order_for_others": false, + "list_of_speakers_enable_point_of_order_categories": false, + "list_of_speakers_closing_disables_point_of_order": false, + "list_of_speakers_enable_pro_contra_speech": false, + "list_of_speakers_can_set_contribution_self": false, + "list_of_speakers_speaker_note_for_everyone": true, + "list_of_speakers_initially_closed": false, + "list_of_speakers_present_users_only": false, + "motions_default_workflow_id": 1, + "motions_default_amendment_workflow_id": 1, + "motions_preamble": "The assembly may decide:", + "motions_default_line_numbering": "outside", + "motions_line_length": 85, + "motions_origin_motion_toggle_default": false, + "motions_enable_origin_motion_display": true, + "motions_enable_text_on_projector": true, + "motions_enable_reason_on_projector": false, + "motions_enable_sidebox_on_projector": false, + "motions_enable_recommendation_on_projector": true, + "motions_hide_metadata_background": false, + "motions_show_referring_motions": true, + "motions_show_sequential_number": true, + "motions_reason_required": false, + "motions_recommendations_by": "ABK", + "motions_recommendation_text_mode": "diff", + "motions_default_sorting": "number", + "motions_number_type": "per_category", + "motions_number_min_digits": 2, + "motions_number_with_blank": false, + "motions_amendments_enabled": true, + "motions_amendments_in_main_list": true, + "motions_amendments_of_amendments": true, + "motions_amendments_prefix": "-\u00c4", + "motions_amendments_text_mode": "paragraph", + "motions_amendments_multiple_paragraphs": true, + "motions_supporters_min_amount": 1, + "motions_export_title": "Motions", + "motions_export_preamble": "", + "motions_export_submitter_recommendation": true, + "motions_export_follow_recommendation": false, + "motion_poll_ballot_paper_selection": "CUSTOM_NUMBER", + "motion_poll_ballot_paper_number": 8, + "motion_poll_default_type": "pseudoanonymous", + "motion_poll_default_method": "YNA", + "motion_poll_default_onehundred_percent_base": "YNA", + "motion_poll_default_group_ids": [ + 2, + 3 + ], + "motion_poll_default_backend": "fast", + "motion_poll_projection_name_order_first": "last_name", + "motion_poll_projection_max_columns": 6, + "users_enable_presence_view": true, + "users_enable_vote_weight": false, + "users_enable_vote_delegations": true, + "users_allow_self_set_present": true, + "users_pdf_welcometitle": "Welcome to OpenSlides", + "users_pdf_welcometext": "[Place for your welcome and help text.]", + "users_pdf_wlan_encryption": "", + "users_email_sender": "Openslides", + "users_email_subject": "OpenSlides access data", + "users_email_body": "Dear {name},\n\nthis is your personal OpenSlides login:\n\n{url}\nUsername: {username}\nPassword: {password}\n\n\nThis email was generated automatically.", + "assignments_export_title": "Elections", + "assignment_poll_ballot_paper_selection": "CUSTOM_NUMBER", + "assignment_poll_ballot_paper_number": 8, + "assignment_poll_add_candidates_to_list_of_speakers": false, + "assignment_poll_enable_max_votes_per_option": false, + "assignment_poll_sort_poll_result_by_votes": true, + "assignment_poll_default_type": "pseudoanonymous", + "assignment_poll_default_method": "Y", + "assignment_poll_default_onehundred_percent_base": "valid", + "assignment_poll_default_group_ids": [ + 2, + 3 + ], + "assignment_poll_default_backend": "fast", + "poll_ballot_paper_selection": "CUSTOM_NUMBER", + "poll_ballot_paper_number": 8, + "poll_sort_poll_result_by_votes": true, + "poll_default_type": "nominal", + "poll_default_method": "votes", + "poll_default_onehundred_percent_base": "valid", + "poll_default_group_ids": [ + 3 + ], + "poll_default_live_voting_enabled": false, + "poll_default_backend": "fast", + "poll_couple_countdown": true, + "meeting_user_ids": [1, 2, 3], + "projector_ids": [ + 1, + 2 + ], + "all_projection_ids": [ + 1, + 2, + 3, + 4 + ], + "projector_message_ids": [ + 1 + ], + "projector_countdown_ids": [ + 1, + 2 + ], + "tag_ids": [ + 1, + 2, + 3 + ], + "agenda_item_ids": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15 + ], + "list_of_speakers_ids": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ], + "speaker_ids": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13 + ], + "topic_ids": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + "group_ids": [ + 1, + 2, + 3, + 4, + 5 + ], + "mediafile_ids": [ + 1 + ], + "motion_ids": [ + 1, + 2, + 3, + 4 + ], + "motion_submitter_ids": [ + 1, + 2, + 3, + 4 + ], + "motion_comment_section_ids": [ + 1 + ], + "motion_comment_ids": [ + 1 + ], + "motion_state_ids": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15 + ], + "motion_category_ids": [ + 1, + 2 + ], + "motion_block_ids": [ + 1 + ], + "motion_workflow_ids": [ + 1, + 2 + ], + "motion_change_recommendation_ids": [ + 4, + 5 + ], + "poll_ids": [ + 1, + 2, + 3, + 4, + 5 + ], + "option_ids": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13 + ], + "vote_ids": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "assignment_ids": [ + 1, + 2 + ], + "assignment_candidate_ids": [ + 1, + 2, + 3, + 4, + 5 + ], + "personal_note_ids": [ + 1 + ], + "chat_group_ids": [ + 1, + 2 + ], + "committee_id": 1, + "default_meeting_for_committee_id": 1, + "organization_tag_ids": [ + 1 + ], + "structure_level_ids": [1, 2, 3], + "present_user_ids": [ + 1 + ], + "user_ids": [ + 1, + 2, + 3 + ], + "reference_projector_id": 1, + "list_of_speakers_countdown_id": 1, + "poll_countdown_id": 2, + "default_projector_agenda_item_list_ids": [1], + "default_projector_topic_ids": [1], + "default_projector_list_of_speakers_ids": [2], + "default_projector_current_los_ids": [2], + "default_projector_motion_ids": [1], + "default_projector_amendment_ids": [1], + "default_projector_motion_block_ids": [1], + "default_projector_assignment_ids": [1], + "default_projector_mediafile_ids": [1], + "default_projector_message_ids": [1], + "default_projector_countdown_ids": [1], + "default_projector_assignment_poll_ids": [1], + "default_projector_motion_poll_ids": [1], + "default_projector_poll_ids": [1], + "projection_ids": [ + 3 + ], + "default_group_id": 1, + "admin_group_id": 2, + "meeting_mediafile_ids": [1] + } + }, + "group": { + "1": { + "id": 1, + "name": "Default", + "default_group_for_meeting_id": 1, + "permissions": [ + "agenda_item.can_see_internal", + "assignment.can_see", + "list_of_speakers.can_see", + "mediafile.can_see", + "meeting.can_see_frontpage", + "motion.can_see", + "projector.can_see", + "user.can_see" + ], + "weight": 1, + "read_chat_group_ids": [ + 1 + ], + "write_chat_group_ids": [ + 1 + ], + "meeting_id": 1 + }, + "2": { + "id": 2, + "name": "Admin", + "admin_group_for_meeting_id": 1, + "permissions": [], + "weight": 2, + "meeting_user_ids": [ + 1 + ], + "meeting_mediafile_access_group_ids": [ + 1 + ], + "meeting_mediafile_inherited_access_group_ids": [ + 1 + ], + "read_chat_group_ids": [ + 1, + 2 + ], + "write_chat_group_ids": [ + 1, + 2 + ], + "poll_ids": [ + 5 + ], + "used_as_motion_poll_default_id": 1, + "used_as_assignment_poll_default_id": 1, + "meeting_id": 1 + }, + "3": { + "id": 3, + "name": "Staff", + "permissions": [ + "agenda_item.can_manage", + "assignment.can_manage", + "assignment.can_nominate_self", + "list_of_speakers.can_be_speaker", + "list_of_speakers.can_manage", + "mediafile.can_manage", + "meeting.can_see_frontpage", + "meeting.can_see_history", + "motion.can_manage", + "poll.can_manage", + "projector.can_manage", + "tag.can_manage", + "user.can_manage" + ], + "weight": 3, + "meeting_mediafile_access_group_ids": [ + 1 + ], + "meeting_mediafile_inherited_access_group_ids": [ + 1 + ], + "read_comment_section_ids": [ + 1 + ], + "write_comment_section_ids": [ + 1 + ], + "used_as_motion_poll_default_id": 1, + "used_as_assignment_poll_default_id": 1, + "used_as_poll_default_id": 1, + "meeting_id": 1 + }, + "4": { + "id": 4, + "name": "Committees", + "permissions": [ + "agenda_item.can_see_internal", + "assignment.can_see", + "list_of_speakers.can_see", + "mediafile.can_see", + "meeting.can_see_frontpage", + "motion.can_create", + "motion.can_create_amendments", + "motion.can_support", + "projector.can_see", + "user.can_see" + ], + "weight": 4, + "meeting_id": 1 + }, + "5": { + "id": 5, + "name": "Delegates", + "permissions": [ + "agenda_item.can_see_internal", + "assignment.can_nominate_other", + "assignment.can_nominate_self", + "list_of_speakers.can_be_speaker", + "mediafile.can_see", + "meeting.can_see_autopilot", + "meeting.can_see_frontpage", + "motion.can_create", + "motion.can_create_amendments", + "motion.can_support", + "projector.can_see", + "user.can_see" + ], + "weight": 5, + "meeting_user_ids": [ + 2, + 3 + ], + "read_comment_section_ids": [ + 1 + ], + "write_comment_section_ids": [ + 1 + ], + "read_chat_group_ids": [ + 1, + 2 + ], + "write_chat_group_ids": [ + 1, + 2 + ], + "meeting_id": 1 + } + }, + "personal_note": { + "1": { + "id": 1, + "note": "

Some content..

", + "meeting_user_id": 1, + "content_object_id": "motion/2", + "meeting_id": 1 + } + }, + "tag": { + "1": { + "id": 1, + "name": "Tag1", + "tagged_ids": [ + "motion/2" + ], + "meeting_id": 1 + }, + "2": { + "id": 2, + "name": "Tag2", + "tagged_ids": [ + "assignment/2", + "agenda_item/4" + ], + "meeting_id": 1 + }, + "3": { + "id": 3, + "name": "Tag3", + "tagged_ids": [ + "motion/2", + "motion/3" + ], + "meeting_id": 1 + } + }, + "agenda_item": { + "3": { + "id": 3, + "type": "common", + "closed": false, + "weight": 2, + "level": 0, + "content_object_id": "topic/1", + "child_ids": [ + 14 + ], + "meeting_id": 1 + }, + "14": { + "id": 14, + "type": "internal", + "closed": false, + "is_internal": true, + "weight": 4, + "level": 1, + "content_object_id": "assignment/2", + "parent_id": 3, + "meeting_id": 1 + }, + "4": { + "id": 4, + "type": "common", + "closed": false, + "weight": 6, + "level": 0, + "content_object_id": "topic/2", + "tag_ids": [ + 2 + ], + "meeting_id": 1 + }, + "5": { + "id": 5, + "type": "internal", + "closed": false, + "is_internal": true, + "weight": 8, + "level": 0, + "content_object_id": "topic/3", + "meeting_id": 1 + }, + "6": { + "id": 6, + "type": "internal", + "closed": false, + "is_internal": true, + "weight": 10, + "level": 0, + "content_object_id": "topic/4", + "meeting_id": 1 + }, + "7": { + "id": 7, + "type": "common", + "closed": false, + "weight": 12, + "level": 0, + "content_object_id": "topic/5", + "meeting_id": 1 + }, + "8": { + "id": 8, + "type": "common", + "closed": false, + "weight": 14, + "level": 0, + "content_object_id": "topic/6", + "meeting_id": 1 + }, + "9": { + "id": 9, + "type": "common", + "closed": false, + "weight": 16, + "level": 0, + "content_object_id": "topic/7", + "meeting_id": 1 + }, + "10": { + "id": 10, + "type": "hidden", + "closed": false, + "weight": 18, + "level": 0, + "content_object_id": "topic/8", + "meeting_id": 1 + }, + "1": { + "id": 1, + "type": "common", + "closed": false, + "weight": 20, + "level": 0, + "content_object_id": "motion/1", + "meeting_id": 1 + }, + "2": { + "id": 2, + "type": "internal", + "closed": false, + "is_internal": true, + "weight": 22, + "level": 0, + "content_object_id": "motion/2", + "meeting_id": 1 + }, + "11": { + "id": 11, + "type": "internal", + "closed": false, + "is_internal": true, + "weight": 24, + "level": 0, + "content_object_id": "assignment/1", + "meeting_id": 1 + }, + "12": { + "id": 12, + "type": "internal", + "closed": false, + "is_internal": true, + "weight": 26, + "level": 0, + "content_object_id": "motion/3", + "meeting_id": 1 + }, + "13": { + "id": 13, + "type": "internal", + "closed": false, + "is_internal": true, + "weight": 28, + "level": 0, + "content_object_id": "motion/4", + "meeting_id": 1 + }, + "15": { + "id": 15, + "type": "internal", + "closed": false, + "is_internal": true, + "weight": 30, + "level": 0, + "content_object_id": "motion_block/1", + "meeting_id": 1 + } + }, + "list_of_speakers": { + "1": { + "id": 1, + "sequential_number": 1, + "content_object_id": "motion/1", + "closed": false, + "speaker_ids": [ + 11, + 12, + 13 + ], + "meeting_id": 1 + }, + "2": { + "id": 2, + "sequential_number": 2, + "content_object_id": "motion/2", + "closed": false, + "meeting_id": 1 + }, + "3": { + "id": 3, + "sequential_number": 3, + "content_object_id": "topic/1", + "closed": false, + "speaker_ids": [ + 1, + 2 + ], + "meeting_id": 1 + }, + "4": { + "id": 4, + "sequential_number": 4, + "content_object_id": "topic/2", + "closed": false, + "meeting_id": 1 + }, + "5": { + "id": 5, + "sequential_number": 5, + "content_object_id": "topic/3", + "closed": false, + "meeting_id": 1 + }, + "6": { + "id": 6, + "sequential_number": 6, + "content_object_id": "topic/4", + "closed": false, + "meeting_id": 1 + }, + "7": { + "id": 7, + "sequential_number": 7, + "content_object_id": "topic/5", + "closed": false, + "speaker_ids": [ + 3, + 4 + ], + "meeting_id": 1 + }, + "8": { + "id": 8, + "sequential_number": 8, + "content_object_id": "topic/6", + "closed": false, + "speaker_ids": [ + 5 + ], + "meeting_id": 1 + }, + "9": { + "id": 9, + "sequential_number": 9, + "content_object_id": "topic/7", + "closed": false, + "meeting_id": 1 + }, + "10": { + "id": 10, + "sequential_number": 10, + "content_object_id": "topic/8", + "closed": false, + "meeting_id": 1 + }, + "11": { + "id": 11, + "sequential_number": 11, + "content_object_id": "assignment/1", + "closed": false, + "speaker_ids": [ + 6, + 7, + 8 + ], + "meeting_id": 1 + }, + "12": { + "id": 12, + "sequential_number": 12, + "content_object_id": "motion/3", + "closed": false, + "meeting_id": 1 + }, + "13": { + "id": 13, + "sequential_number": 13, + "content_object_id": "motion/4", + "closed": false, + "meeting_id": 1 + }, + "14": { + "id": 14, + "sequential_number": 14, + "content_object_id": "assignment/2", + "closed": false, + "speaker_ids": [ + 9, + 10 + ], + "meeting_id": 1 + }, + "15": { + "id": 15, + "sequential_number": 15, + "content_object_id": "motion_block/1", + "closed": false, + "meeting_id": 1 + }, + "16": { + "id": 16, + "sequential_number": 16, + "content_object_id": "meeting_mediafile/1", + "closed": false, + "meeting_id": 1 + } + }, + "speaker": { + "11": { + "id": 11, + "weight": 11, + "begin_time": 1584512636, + "end_time": 1584512638, + "list_of_speakers_id": 1, + "meeting_user_id": 2, + "meeting_id": 1 + }, + "12": { + "id": 12, + "weight": 2, + "list_of_speakers_id": 1, + "meeting_user_id": 1, + "meeting_id": 1 + }, + "13": { + "id": 13, + "weight": 3, + "list_of_speakers_id": 1, + "meeting_user_id": 2, + "meeting_id": 1 + }, + "1": { + "id": 1, + "weight": 1, + "list_of_speakers_id": 3, + "meeting_user_id": 1, + "meeting_id": 1 + }, + "2": { + "id": 2, + "weight": 0, + "list_of_speakers_id": 3, + "meeting_user_id": 2, + "meeting_id": 1 + }, + "3": { + "id": 3, + "weight": 1, + "list_of_speakers_id": 7, + "meeting_user_id": 2, + "meeting_id": 1 + }, + "4": { + "id": 4, + "weight": 2, + "list_of_speakers_id": 7, + "meeting_user_id": 3, + "meeting_id": 1 + }, + "5": { + "id": 5, + "weight": 1, + "list_of_speakers_id": 8, + "meeting_user_id": 1, + "meeting_id": 1 + }, + "6": { + "id": 6, + "weight": 1, + "list_of_speakers_id": 11, + "meeting_user_id": 1, + "meeting_id": 1 + }, + "7": { + "id": 7, + "weight": 2, + "list_of_speakers_id": 11, + "meeting_user_id": 2, + "meeting_id": 1 + }, + "8": { + "id": 8, + "weight": 3, + "list_of_speakers_id": 11, + "meeting_user_id": 3, + "meeting_id": 1 + }, + "9": { + "id": 9, + "weight": 1, + "list_of_speakers_id": 14, + "meeting_user_id": 3, + "meeting_id": 1 + }, + "10": { + "id": 10, + "weight": 2, + "list_of_speakers_id": 14, + "meeting_user_id": 2, + "meeting_id": 1 + } + }, + "topic": { + "1": { + "id": 1, + "title": "A", + "sequential_number": 1, + "agenda_item_id": 3, + "list_of_speakers_id": 3, + "meeting_id": 1 + }, + "2": { + "id": 2, + "title": "B", + "sequential_number": 2, + "agenda_item_id": 4, + "list_of_speakers_id": 4, + "meeting_id": 1 + }, + "3": { + "id": 3, + "title": "C", + "sequential_number": 3, + "agenda_item_id": 5, + "list_of_speakers_id": 5, + "meeting_id": 1 + }, + "4": { + "id": 4, + "title": "D", + "sequential_number": 4, + "agenda_item_id": 6, + "list_of_speakers_id": 6, + "meeting_id": 1 + }, + "5": { + "id": 5, + "title": "E", + "sequential_number": 5, + "agenda_item_id": 7, + "list_of_speakers_id": 7, + "meeting_id": 1 + }, + "6": { + "id": 6, + "title": "F", + "sequential_number": 6, + "agenda_item_id": 8, + "list_of_speakers_id": 8, + "meeting_id": 1 + }, + "7": { + "id": 7, + "title": "G", + "sequential_number": 7, + "agenda_item_id": 9, + "list_of_speakers_id": 9, + "meeting_id": 1 + }, + "8": { + "id": 8, + "title": "H", + "sequential_number": 8, + "agenda_item_id": 10, + "list_of_speakers_id": 10, + "meeting_id": 1 + } + }, + "motion": { + "1": { + "id": 1, + "number": "A1", + "number_value": 1, + "sequential_number": 1, + "title": "test", + "text": "", + "modified_final_version": "

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi.Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem.Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut

", + "text_hash": "2d59f168675841ecc69e3040ae3eca7c", + "category_weight": 10000, + "sort_weight": 10000, + "created": 1584512346, + "last_modified": 1584512346, + "start_line_number": 1, + "amendment_ids": [ + 2 + ], + "state_id": 1, + "category_id": 2, + "submitter_ids": [ + 1 + ], + "poll_ids": [ + 1, + 2 + ], + "option_ids": [ + 1, + 3 + ], + "comment_ids": [ + 1 + ], + "agenda_item_id": 1, + "list_of_speakers_id": 1, + "meeting_id": 1 + }, + "2": { + "id": 2, + "number": "1 - 1", + "number_value": 1, + "sequential_number": 2, + "title": "\u00c4nderungsantrag zu 1", + "text": "

lömk

", + "text_hash": "0339e76557663e3a6f03b9e347409f97", + "category_weight": 10000, + "state_extension": "

regeer

", + "sort_weight": 10000, + "created": 1584512346, + "last_modified": 1584512346, + "start_line_number": 1, + "lead_motion_id": 1, + "state_id": 1, + "category_id": 1, + "submitter_ids": [ + 2 + ], + "agenda_item_id": 2, + "list_of_speakers_id": 2, + "tag_ids": [ + 1, + 3 + ], + "personal_note_ids": [ + 1 + ], + "meeting_id": 1 + }, + "3": { + "id": 3, + "number": "2", + "number_value": 2, + "sequential_number": 3, + "title": "ohne", + "text": "

sf

", + "text_hash": "60d31eb37595dd44584be5ef363283e3", + "category_weight": 100, + "sort_weight": 10000, + "created": 1584512346, + "last_modified": 1584512346, + "start_line_number": 1, + "state_id": 1, + "category_id": 2, + "block_id": 1, + "submitter_ids": [ + 3 + ], + "supporter_meeting_user_ids": [ + 3 + ], + "change_recommendation_ids": [ + 5 + ], + "agenda_item_id": 12, + "list_of_speakers_id": 12, + "tag_ids": [ + 3 + ], + "meeting_id": 1 + }, + "4": { + "id": 4, + "number": "3", + "number_value": 3, + "sequential_number": 4, + "title": "komplex", + "text": "

sdf sdfpdfkw wef

\n\n

wepkf 

\n\n

weüpfk 

\n\n

weüpfdfg

", + "text_hash": "af905e34b34d2b08b60c6af41153f078", + "category_weight": 10000, + "sort_weight": 10000, + "created": 1584512346, + "last_modified": 1584512346, + "start_line_number": 1, + "state_id": 7, + "block_id": 1, + "submitter_ids": [ + 4 + ], + "change_recommendation_ids": [ + 4 + ], + "agenda_item_id": 13, + "list_of_speakers_id": 13, + "projection_ids": [ + 2 + ], + "meeting_id": 1 + } + }, + "motion_submitter": { + "1": { + "id": 1, + "weight": 1, + "meeting_user_id": 1, + "motion_id": 1, + "meeting_id": 1 + }, + "2": { + "id": 2, + "weight": 1, + "meeting_user_id": 1, + "motion_id": 2, + "meeting_id": 1 + }, + "3": { + "id": 3, + "weight": 1, + "meeting_user_id": 1, + "motion_id": 3, + "meeting_id": 1 + }, + "4": { + "id": 4, + "weight": 1, + "meeting_user_id": 1, + "motion_id": 4, + "meeting_id": 1 + } + }, + "motion_comment": { + "1": { + "id": 1, + "comment": "

sgsdklf jhsölkf sdölkdsf jglkfd

", + "motion_id": 1, + "section_id": 1, + "meeting_id": 1 + } + }, + "motion_comment_section": { + "1": { + "id": 1, + "name": "Neu", + "weight": 10000, + "sequential_number": 1, + "comment_ids": [ + 1 + ], + "read_group_ids": [ + 3, + 5 + ], + "write_group_ids": [ + 3, + 5 + ], + "meeting_id": 1 + } + }, + "motion_category": { + "1": { + "id": 1, + "name": "Cad", + "prefix": "C", + "weight": 2, + "level": 0, + "sequential_number": 1, + "child_ids": [ + 2 + ], + "motion_ids": [ + 2 + ], + "meeting_id": 1 + }, + "2": { + "id": 2, + "name": "Bildung", + "prefix": "B", + "weight": 4, + "level": 1, + "sequential_number": 2, + "parent_id": 1, + "motion_ids": [ + 1, + 3 + ], + "meeting_id": 1 + } + }, + "motion_block": { + "1": { + "id": 1, + "title": "BLOCK A", + "sequential_number": 1, + "motion_ids": [ + 3, + 4 + ], + "agenda_item_id": 15, + "list_of_speakers_id": 15, + "projection_ids": [ + 1 + ], + "meeting_id": 1 + } + }, + "motion_change_recommendation": { + "4": { + "id": 4, + "type": "deletion", + "rejected": false, + "internal": false, + "line_from": 1, + "line_to": 2, + "text": "

sdf sdfpef

", + "creation_time": 1584512345, + "motion_id": 4, + "meeting_id": 1 + }, + "5": { + "id": 5, + "type": "replacement", + "rejected": false, + "internal": false, + "line_from": 1, + "line_to": 1, + "text": "

skp

", + "creation_time": 1584512667, + "motion_id": 3, + "meeting_id": 1 + } + }, + "motion_state": { + "1": { + "id": 1, + "name": "submitted", + "weight": 1, + "css_class": "lightblue", + "allow_support": true, + "allow_create_poll": true, + "allow_submitter_edit": true, + "set_number": true, + "merge_amendment_into_final": "undefined", + "next_state_ids": [ + 2, + 3, + 4 + ], + "motion_ids": [ + 1, + 2, + 3 + ], + "workflow_id": 1, + "first_state_of_workflow_id": 1, + "restrictions": [], + "show_state_extension_field": false, + "show_recommendation_extension_field": false, + "set_workflow_timestamp": true, + "allow_motion_forwarding": true, + "allow_amendment_forwarding": false, + "meeting_id": 1 + }, + "2": { + "id": 2, + "name": "accepted", + "weight": 2, + "recommendation_label": "Acceptance", + "css_class": "green", + "set_number": true, + "merge_amendment_into_final": "do_merge", + "previous_state_ids": [ + 1 + ], + "workflow_id": 1, + "restrictions": [], + "show_state_extension_field": false, + "show_recommendation_extension_field": false, + "allow_submitter_edit": false, + "allow_create_poll": false, + "allow_support": false, + "set_workflow_timestamp": false, + "allow_motion_forwarding": true, + "allow_amendment_forwarding": true, + "meeting_id": 1 + }, + "3": { + "id": 3, + "name": "rejected", + "weight": 3, + "recommendation_label": "Rejection", + "css_class": "red", + "set_number": true, + "merge_amendment_into_final": "do_not_merge", + "previous_state_ids": [ + 1 + ], + "workflow_id": 1, + "restrictions": [], + "show_state_extension_field": false, + "show_recommendation_extension_field": false, + "allow_submitter_edit": false, + "allow_create_poll": false, + "allow_support": false, + "allow_motion_forwarding": true, + "allow_amendment_forwarding": true, + "set_workflow_timestamp": false, + "meeting_id": 1 + }, + "4": { + "id": 4, + "name": "not decided", + "weight": 4, + "recommendation_label": "No decision", + "css_class": "grey", + "set_number": true, + "merge_amendment_into_final": "undefined", + "previous_state_ids": [ + 1 + ], + "workflow_id": 1, + "restrictions": [], + "show_state_extension_field": false, + "show_recommendation_extension_field": false, + "allow_submitter_edit": false, + "allow_create_poll": false, + "allow_support": false, + "allow_motion_forwarding": true, + "allow_amendment_forwarding": true, + "set_workflow_timestamp": false, + "meeting_id": 1 + }, + "5": { + "id": 5, + "name": "in progress", + "weight": 5, + "css_class": "lightblue", + "set_number": false, + "allow_submitter_edit": true, + "merge_amendment_into_final": "undefined", + "next_state_ids": [ + 6, + 10 + ], + "workflow_id": 2, + "first_state_of_workflow_id": 2, + "restrictions": [], + "show_state_extension_field": false, + "show_recommendation_extension_field": false, + "allow_create_poll": false, + "allow_support": false, + "set_workflow_timestamp": true, + "allow_motion_forwarding": true, + "allow_amendment_forwarding": true, + "meeting_id": 1 + }, + "6": { + "id": 6, + "name": "submitted", + "weight": 6, + "css_class": "lightblue", + "set_number": false, + "merge_amendment_into_final": "undefined", + "next_state_ids": [ + 7, + 10, + 15 + ], + "previous_state_ids": [ + 5 + ], + "workflow_id": 2, + "restrictions": [], + "show_state_extension_field": false, + "show_recommendation_extension_field": false, + "allow_submitter_edit": false, + "allow_create_poll": false, + "allow_support": true, + "allow_motion_forwarding": true, + "allow_amendment_forwarding": true, + "set_workflow_timestamp": false, + "meeting_id": 1 + }, + "7": { + "id": 7, + "name": "permitted", + "weight": 7, + "recommendation_label": "Permission", + "css_class": "lightblue", + "set_number": true, + "merge_amendment_into_final": "undefined", + "next_state_ids": [ + 8, + 9, + 10, + 11, + 12, + 13, + 14 + ], + "previous_state_ids": [ + 6 + ], + "motion_ids": [ + 4 + ], + "workflow_id": 2, + "restrictions": [], + "show_state_extension_field": false, + "show_recommendation_extension_field": false, + "allow_submitter_edit": false, + "allow_create_poll": true, + "allow_support": false, + "allow_motion_forwarding": true, + "allow_amendment_forwarding": true, + "set_workflow_timestamp": false, + "meeting_id": 1 + }, + "8": { + "id": 8, + "name": "accepted", + "weight": 8, + "recommendation_label": "Acceptance", + "css_class": "green", + "set_number": true, + "merge_amendment_into_final": "do_merge", + "previous_state_ids": [ + 7 + ], + "workflow_id": 2, + "restrictions": [], + "show_state_extension_field": false, + "show_recommendation_extension_field": false, + "allow_submitter_edit": false, + "allow_create_poll": false, + "allow_support": false, + "allow_motion_forwarding": true, + "allow_amendment_forwarding": true, + "set_workflow_timestamp": false, + "meeting_id": 1 + }, + "9": { + "id": 9, + "name": "rejected", + "weight": 9, + "recommendation_label": "Rejection", + "css_class": "red", + "set_number": true, + "merge_amendment_into_final": "do_not_merge", + "previous_state_ids": [ + 7 + ], + "workflow_id": 2, + "restrictions": [], + "show_state_extension_field": false, + "show_recommendation_extension_field": false, + "allow_submitter_edit": false, + "allow_create_poll": false, + "allow_support": false, + "allow_motion_forwarding": true, + "allow_amendment_forwarding": true, + "set_workflow_timestamp": false, + "meeting_id": 1 + }, + "10": { + "id": 10, + "name": "withdrawn", + "weight": 10, + "css_class": "grey", + "set_number": true, + "merge_amendment_into_final": "do_not_merge", + "previous_state_ids": [ + 5, + 6, + 7 + ], + "workflow_id": 2, + "restrictions": [], + "show_state_extension_field": false, + "show_recommendation_extension_field": false, + "allow_submitter_edit": false, + "allow_create_poll": false, + "allow_support": false, + "allow_motion_forwarding": true, + "allow_amendment_forwarding": true, + "set_workflow_timestamp": false, + "meeting_id": 1 + }, + "11": { + "id": 11, + "name": "adjourned", + "weight": 11, + "recommendation_label": "Adjournment", + "css_class": "grey", + "set_number": true, + "merge_amendment_into_final": "do_not_merge", + "previous_state_ids": [ + 7 + ], + "workflow_id": 2, + "restrictions": [], + "show_state_extension_field": false, + "show_recommendation_extension_field": false, + "allow_submitter_edit": false, + "allow_create_poll": false, + "allow_support": false, + "allow_motion_forwarding": true, + "allow_amendment_forwarding": true, + "set_workflow_timestamp": false, + "meeting_id": 1 + }, + "12": { + "id": 12, + "name": "not concerned", + "weight": 12, + "recommendation_label": "No concernment", + "css_class": "grey", + "set_number": true, + "merge_amendment_into_final": "do_not_merge", + "previous_state_ids": [ + 7 + ], + "workflow_id": 2, + "restrictions": [], + "show_state_extension_field": false, + "show_recommendation_extension_field": false, + "allow_submitter_edit": false, + "allow_create_poll": false, + "allow_support": false, + "allow_motion_forwarding": true, + "allow_amendment_forwarding": true, + "set_workflow_timestamp": false, + "meeting_id": 1 + }, + "13": { + "id": 13, + "name": "referred to committee", + "weight": 13, + "recommendation_label": "Referral to committee", + "css_class": "grey", + "set_number": true, + "merge_amendment_into_final": "do_not_merge", + "previous_state_ids": [ + 7 + ], + "workflow_id": 2, + "restrictions": [], + "show_state_extension_field": false, + "show_recommendation_extension_field": false, + "allow_submitter_edit": false, + "allow_create_poll": false, + "allow_support": false, + "allow_motion_forwarding": true, + "allow_amendment_forwarding": true, + "set_workflow_timestamp": false, + "meeting_id": 1 + }, + "14": { + "id": 14, + "name": "needs review", + "weight": 14, + "css_class": "grey", + "set_number": true, + "merge_amendment_into_final": "do_not_merge", + "previous_state_ids": [ + 7 + ], + "workflow_id": 2, + "restrictions": [], + "show_state_extension_field": false, + "show_recommendation_extension_field": false, + "allow_submitter_edit": false, + "allow_create_poll": false, + "allow_support": false, + "allow_motion_forwarding": true, + "allow_amendment_forwarding": true, + "set_workflow_timestamp": false, + "meeting_id": 1 + }, + "15": { + "id": 15, + "name": "rejected (not authorized)", + "weight": 15, + "recommendation_label": "Rejection (not authorized)", + "css_class": "grey", + "set_number": true, + "merge_amendment_into_final": "do_not_merge", + "previous_state_ids": [ + 6 + ], + "workflow_id": 2, + "restrictions": [], + "show_state_extension_field": false, + "show_recommendation_extension_field": false, + "allow_submitter_edit": false, + "allow_create_poll": false, + "allow_support": false, + "allow_motion_forwarding": true, + "allow_amendment_forwarding": true, + "set_workflow_timestamp": false, + "meeting_id": 1 + } + }, + "motion_workflow": { + "1": { + "id": 1, + "name": "Simple Workflow", + "sequential_number": 1, + "state_ids": [ + 1, + 2, + 3, + 4 + ], + "first_state_id": 1, + "default_workflow_meeting_id": 1, + "default_amendment_workflow_meeting_id": 1, + "meeting_id": 1 + }, + "2": { + "id": 2, + "name": "Complex Workflow", + "sequential_number": 2, + "state_ids": [ + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15 + ], + "first_state_id": 5, + "meeting_id": 1 + } + }, + "poll": { + "1": { + "id": 1, + "title": "1", + "type": "analog", + "backend": "fast", + "pollmethod": "YNA", + "state": "finished", + "min_votes_amount": 1, + "max_votes_amount": 1, + "max_votes_per_option": 1, + "onehundred_percent_base": "YNA", + "votesvalid": "2.000000", + "votesinvalid": "9.000000", + "votescast": "2.000000", + "sequential_number": 1, + "content_object_id": "motion/1", + "option_ids": [ + 1 + ], + "global_option_id": 2, + "global_abstain": false, + "global_no": false, + "global_yes": false, + "meeting_id": 1, + "live_voting_enabled": false + }, + "2": { + "id": 2, + "title": "2", + "type": "analog", + "backend": "fast", + "pollmethod": "YNA", + "state": "created", + "min_votes_amount": 1, + "max_votes_amount": 1, + "max_votes_per_option": 1, + "onehundred_percent_base": "YNA", + "sequential_number": 2, + "content_object_id": "motion/1", + "option_ids": [ + 3 + ], + "global_option_id": 4, + "global_abstain": false, + "global_no": false, + "global_yes": false, + "meeting_id": 1, + "live_voting_enabled": false + }, + "3": { + "id": 3, + "title": "1", + "type": "analog", + "backend": "fast", + "pollmethod": "YNA", + "state": "created", + "min_votes_amount": 1, + "max_votes_amount": 1, + "max_votes_per_option": 1, + "global_yes": false, + "global_no": true, + "global_abstain": true, + "onehundred_percent_base": "YNA", + "sequential_number": 3, + "content_object_id": "assignment/1", + "option_ids": [ + 5 + ], + "global_option_id": 6, + "meeting_id": 1, + "live_voting_enabled": false + }, + "4": { + "id": 4, + "title": "2", + "type": "analog", + "backend": "fast", + "pollmethod": "Y", + "state": "finished", + "min_votes_amount": 1, + "max_votes_amount": 1, + "max_votes_per_option": 1, + "global_yes": false, + "global_no": true, + "global_abstain": true, + "onehundred_percent_base": "Y", + "votesvalid": "9.000000", + "votesinvalid": "2.000000", + "votescast": "16.000000", + "sequential_number": 4, + "content_object_id": "assignment/1", + "option_ids": [ + 7, + 8, + 9 + ], + "global_option_id": 10, + "meeting_id": 1, + "live_voting_enabled": false + }, + "5": { + "id": 5, + "title": "Wahlgang", + "type": "named", + "backend": "fast", + "pollmethod": "Y", + "state": "finished", + "min_votes_amount": 1, + "max_votes_amount": 1, + "max_votes_per_option": 1, + "global_yes": false, + "global_no": true, + "global_abstain": false, + "onehundred_percent_base": "valid", + "votesvalid": "1.000000", + "votesinvalid": "0.000000", + "votescast": "1.000000", + "sequential_number": 5, + "content_object_id": "assignment/2", + "voted_ids": [ + 1 + ], + "entitled_group_ids": [ + 2 + ], + "option_ids": [ + 11, + 12 + ], + "global_option_id": 13, + "meeting_id": 1, + "live_voting_enabled": false + } + }, + "option": { + "1": { + "id": 1, + "yes": "2.000000", + "no": "4.000000", + "abstain": "1.000000", + "weight": 1, + "poll_id": 1, + "content_object_id": "motion/1", + "vote_ids": [ + 1, + 2, + 3 + ], + "meeting_id": 1 + }, + "2": { + "id": 2, + "yes": "0.000000", + "no": "0.000000", + "abstain": "0.000000", + "weight": 1, + "used_as_global_option_in_poll_id": 1, + "meeting_id": 1 + }, + "3": { + "id": 3, + "yes": "0.000000", + "no": "0.000000", + "abstain": "0.000000", + "weight": 1, + "poll_id": 2, + "content_object_id": "motion/1", + "meeting_id": 1 + }, + "4": { + "id": 4, + "yes": "0.000000", + "no": "0.000000", + "abstain": "0.000000", + "weight": 1, + "used_as_global_option_in_poll_id": 2, + "meeting_id": 1 + }, + "5": { + "id": 5, + "yes": "0.000000", + "no": "0.000000", + "abstain": "0.000000", + "weight": 1, + "poll_id": 3, + "content_object_id": "user/1", + "meeting_id": 1 + }, + "6": { + "id": 6, + "yes": "0.000000", + "no": "0.000000", + "abstain": "0.000000", + "weight": 1, + "used_as_global_option_in_poll_id": 3, + "meeting_id": 1 + }, + "7": { + "id": 7, + "yes": "3.000000", + "no": "0.000000", + "abstain": "0.000000", + "weight": 1, + "poll_id": 4, + "content_object_id": "user/1", + "vote_ids": [ + 4 + ], + "meeting_id": 1 + }, + "8": { + "id": 8, + "yes": "7.000000", + "no": "0.000000", + "abstain": "0.000000", + "weight": 2, + "poll_id": 4, + "content_object_id": "user/3", + "vote_ids": [ + 5 + ], + "meeting_id": 1 + }, + "9": { + "id": 9, + "yes": "2.000000", + "no": "0.000000", + "abstain": "0.000000", + "weight": 3, + "poll_id": 4, + "content_object_id": "user/2", + "vote_ids": [ + 6 + ], + "meeting_id": 1 + }, + "10": { + "id": 10, + "yes": "0.000000", + "no": "2.000000", + "abstain": "1.000000", + "weight": 1, + "used_as_global_option_in_poll_id": 4, + "vote_ids": [ + 7, + 8 + ], + "meeting_id": 1 + }, + "11": { + "id": 11, + "yes": "0.000000", + "no": "0.000000", + "abstain": "0.000000", + "weight": 1, + "poll_id": 5, + "content_object_id": "user/3", + "meeting_id": 1 + }, + "12": { + "id": 12, + "yes": "1.000000", + "no": "0.000000", + "abstain": "0.000000", + "weight": 2, + "poll_id": 5, + "content_object_id": "user/2", + "vote_ids": [ + 9 + ], + "meeting_id": 1 + }, + "13": { + "id": 13, + "yes": "0.000000", + "no": "0.000000", + "abstain": "0.000000", + "weight": 1, + "used_as_global_option_in_poll_id": 5, + "meeting_id": 1 + } + }, + "vote": { + "1": { + "id": 1, + "weight": "2.000000", + "value": "Y", + "user_token": "SNuxJc7W93bnhAiA", + "option_id": 1, + "meeting_id": 1 + }, + "2": { + "id": 2, + "weight": "4.000000", + "value": "N", + "user_token": "4bgn4RBjNlIeO7vj", + "option_id": 1, + "meeting_id": 1 + }, + "3": { + "id": 3, + "weight": "1.000000", + "value": "A", + "user_token": "xLBFgo3O1pAfGZ0h", + "option_id": 1, + "meeting_id": 1 + }, + "4": { + "id": 4, + "value": "Y", + "weight": "3.000000", + "user_token": "neT9r5YkT9U8yJfa", + "option_id": 7, + "meeting_id": 1 + }, + "5": { + "id": 5, + "value": "Y", + "weight": "7.000000", + "user_token": "U5YSuLUI1G5rNOHn", + "option_id": 8, + "meeting_id": 1 + }, + "6": { + "id": 6, + "value": "Y", + "weight": "2.000000", + "user_token": "jkNKIiJr8Dl0yOXI", + "option_id": 9, + "meeting_id": 1 + }, + "7": { + "id": 7, + "value": "N", + "weight": "2.000000", + "user_token": "Z1cxOviuelzPT2rm", + "option_id": 10, + "meeting_id": 1 + }, + "8": { + "id": 8, + "value": "A", + "weight": "1.000000", + "user_token": "daUZh16fXCAu5DBL", + "option_id": 10, + "meeting_id": 1 + }, + "9": { + "id": 9, + "value": "Y", + "weight": "1.000000", + "user_token": "ivgipZ18D9Xac8pd", + "user_id": 1, + "delegated_user_id": 1, + "option_id": 12, + "meeting_id": 1 + } + }, + "assignment": { + "1": { + "id": 1, + "title": "Wahl", + "open_posts": 1, + "phase": "voting", + "sequential_number": 1, + "candidate_ids": [ + 1, + 2, + 3 + ], + "poll_ids": [ + 3, + 4 + ], + "agenda_item_id": 11, + "list_of_speakers_id": 11, + "projection_ids": [ + 4 + ], + "meeting_id": 1 + }, + "2": { + "id": 2, + "title": "2. Wahl", + "description": "

B-Ware

", + "open_posts": 1, + "phase": "search", + "number_poll_candidates": true, + "sequential_number": 2, + "candidate_ids": [ + 4, + 5 + ], + "poll_ids": [ + 5 + ], + "agenda_item_id": 14, + "list_of_speakers_id": 14, + "tag_ids": [ + 2 + ], + "meeting_id": 1 + } + }, + "assignment_candidate": { + "1": { + "id": 1, + "weight": 1, + "assignment_id": 1, + "meeting_user_id": 1, + "meeting_id": 1 + }, + "2": { + "id": 2, + "weight": 2, + "assignment_id": 1, + "meeting_user_id": 3, + "meeting_id": 1 + }, + "3": { + "id": 3, + "weight": 3, + "assignment_id": 1, + "meeting_user_id": 2, + "meeting_id": 1 + }, + "4": { + "id": 4, + "weight": 1, + "assignment_id": 2, + "meeting_user_id": 3, + "meeting_id": 1 + }, + "5": { + "id": 5, + "weight": 2, + "assignment_id": 2, + "meeting_user_id": 2, + "meeting_id": 1 + } + }, + "mediafile": { + "1": { + "id": 1, + "title": "logos", + "token": null, + "is_directory": true, + "create_timestamp": 1584513763, + "owner_id": "meeting/1", + "meeting_mediafile_ids": [1] + } + }, + "meeting_mediafile": { + "1": { + "id": 1, + "meeting_id": 1, + "mediafile_id": 1, + "is_public": false, + "access_group_ids": [ + 2, + 3 + ], + "inherited_access_group_ids": [ + 2, + 3 + ], + "list_of_speakers_id": 16 + } + }, + "projector": { + "1": { + "id": 1, + "name": "Default projector", + "is_internal": false, + "scale": 0, + "scroll": 0, + "width": 1220, + "aspect_ratio_numerator": 4, + "aspect_ratio_denominator": 3, + "color": "#000000", + "background_color": "#ffffff", + "header_background_color": "#317796", + "header_font_color": "#f5f5f5", + "header_h1_color": "#317796", + "chyron_background_color": "#317796", + "chyron_font_color": "#ffffff", + "chyron_background_color_2": "#134768", + "chyron_font_color_2": "#ffffff", + "show_header_footer": true, + "show_title": true, + "show_logo": true, + "show_clock": true, + "sequential_number": 1, + "current_projection_ids": [ + 3, + 4 + ], + "preview_projection_ids": [ + 1, + 2 + ], + "used_as_reference_projector_meeting_id": 1, + "used_as_default_projector_for_agenda_item_list_in_meeting_id": 1, + "used_as_default_projector_for_topic_in_meeting_id": 1, + "used_as_default_projector_for_motion_in_meeting_id": 1, + "used_as_default_projector_for_amendment_in_meeting_id": 1, + "used_as_default_projector_for_motion_block_in_meeting_id": 1, + "used_as_default_projector_for_assignment_in_meeting_id": 1, + "used_as_default_projector_for_mediafile_in_meeting_id": 1, + "used_as_default_projector_for_message_in_meeting_id": 1, + "used_as_default_projector_for_countdown_in_meeting_id": 1, + "used_as_default_projector_for_assignment_poll_in_meeting_id": 1, + "used_as_default_projector_for_motion_poll_in_meeting_id": 1, + "used_as_default_projector_for_poll_in_meeting_id": 1, + "meeting_id": 1 + }, + "2": { + "id": 2, + "name": "Nebenprojektor", + "is_internal": false, + "scale": 0, + "scroll": 0, + "width": 1024, + "aspect_ratio_numerator": 16, + "aspect_ratio_denominator": 9, + "color": "#000000", + "background_color": "#888888", + "header_background_color": "#317796", + "header_font_color": "#f5f5f5", + "header_h1_color": "#317796", + "chyron_background_color": "#317796", + "chyron_font_color": "#ffffff", + "chyron_background_color_2": "#134768", + "chyron_font_color_2": "#ffffff", + "show_header_footer": true, + "show_title": true, + "show_logo": true, + "show_clock": true, + "sequential_number": 2, + "used_as_default_projector_for_list_of_speakers_in_meeting_id": 1, + "used_as_default_projector_for_current_los_in_meeting_id": 1, + "meeting_id": 1 + } + }, + "projection": { + "1": { + "id": 1, + "preview_projector_id": 1, + "content_object_id": "motion_block/1", + "stable": false, + "weight": 1, + "meeting_id": 1 + }, + "2": { + "id": 2, + "preview_projector_id": 1, + "content_object_id": "motion/4", + "stable": false, + "weight": 2, + "options": { + "mode": "diff" + }, + "meeting_id": 1 + }, + "3": { + "id": 3, + "current_projector_id": 1, + "content_object_id": "meeting/1", + "stable": true, + "type": "current_los", + "weight": 1, + "meeting_id": 1 + }, + "4": { + "id": 4, + "current_projector_id": 1, + "content_object_id": "assignment/1", + "stable": false, + "weight": 1, + "meeting_id": 1 + } + }, + "projector_message": { + "1": { + "id": 1, + "message": "

Hi!

", + "meeting_id": 1 + } + }, + "projector_countdown": { + "1": { + "id": 1, + "title": "Speaking time", + "description": "", + "default_time": 60, + "countdown_time": 60, + "running": false, + "used_as_list_of_speakers_countdown_meeting_id": 1, + "meeting_id": 1 + }, + "2": { + "id": 2, + "title": "Voting", + "description": "", + "default_time": 60, + "countdown_time": 60, + "running": false, + "used_as_poll_countdown_meeting_id": 1, + "meeting_id": 1 + } + }, + "chat_group": { + "1": { + "id": 1, + "name": "General", + "weight": 1, + "read_group_ids": [ + 1, + 2, + 5 + ], + "write_group_ids": [ + 1, + 2, + 5 + ], + "meeting_id": 1 + }, + "2": { + "id": 2, + "name": "Support", + "weight": 2, + "read_group_ids": [ + 2, + 5 + ], + "write_group_ids": [ + 2, + 5 + ], + "meeting_id": 1 + } + }, + "structure_level": { + "1": { + "id": 1, + "meeting_id": 1, + "name": "Test structure level", + "color": "#000000", + "default_time": 600, + "meeting_user_ids": [1] + }, + "2": { + "id": 2, + "meeting_id": 1, + "name": "Test structure level a", + "color": "#000000", + "default_time": 600, + "meeting_user_ids": [2] + }, + "3": { + "id": 3, + "meeting_id": 1, + "name": "Test structure level b", + "color": "#000000", + "default_time": 600, + "meeting_user_ids": [3] + } + } +} diff --git a/data/initial-data.json b/data/initial-data.json new file mode 100644 index 0000000000..c10ff09c2c --- /dev/null +++ b/data/initial-data.json @@ -0,0 +1,106 @@ +{ + "_migration_index": 68, + "gender":{ + "1":{ + "id": 1, + "name": "male", + "organization_id": 1, + "user_ids":[] + }, + "2":{ + "id": 2, + "name": "female", + "organization_id": 1, + "user_ids":[] + }, + "3":{ + "id": 3, + "name": "diverse", + "organization_id": 1, + "user_ids":[] + }, + "4":{ + "id": 4, + "name": "non-binary", + "organization_id": 1, + "user_ids":[] + } + }, + "organization": { + "1": { + "id": 1, + "name": "[Your organization]", + "legal_notice": "OpenSlides is a free web based presentation and assembly system for visualizing and controlling agenda, motions and elections of an assembly. The event organizer is resposible for the content.", + "login_text": "Welcome to OpenSlides. Please login.", + "gender_ids": [1, 2, 3, 4], + "default_language": "en", + "enable_electronic_voting": true, + "limit_of_meetings": 0, + "limit_of_users": 0, + "theme_id": 1, + "theme_ids": [1, 2, 3], + "users_email_sender": "OpenSlides", + "users_email_subject": "OpenSlides access data", + "users_email_body": "Dear {name},\n\nthis is your personal OpenSlides login:\n\n{url}\nUsername: {username}\nPassword: {password}\n\n\nThis email was generated automatically.", + "url": "https://example.com", + "template_meeting_ids": [], + "mediafile_ids": [], + "user_ids": [1], + "require_duplicate_from": false, + "saml_enabled": false, + "saml_login_button_text": "SAML Login", + "saml_attr_mapping": { + "saml_id": "username", + "title": "title", + "first_name": "firstName", + "last_name": "lastName", + "email": "email", + "gender": "gender", + "pronoun": "pronoun", + "is_active": "is_active", + "is_physical_person": "is_person", + "member_number": "member_number" + } + } + }, + "user": { + "1": { + "id": 1, + "username": "superadmin", + "last_name": "Administrator", + "is_active": true, + "is_physical_person": true, + "can_change_own_password": true, + "default_vote_weight": "1.000000", + "organization_management_level": "superadmin", + "organization_id": 1 + } + }, + "theme": { + "1": { + "id": 1, + "name": "OpenSlides Blue", + "accent_500": "#2196f3", + "primary_500": "#317796", + "warn_500": "#f06400", + "organization_id": 1, + "theme_for_organization_id": 1 + }, + "2": { + "id": 2, + "name": "OpenSlides Red", + "accent_500": "#03a9f4", + "primary_500": "#c31c23", + "warn_500": "#11c2a2", + "organization_id": 1 + }, + "3": { + "id": 3, + "name": "OpenSlides Green", + "accent_500": "#55c3b6", + "primary_500": "#46962c", + "warn_500": "#e359ce", + "organization_id": 1 + } + } +} diff --git a/openslides_backend/action/action_handler.py b/openslides_backend/action/action_handler.py index b0cf26ffa7..e3b134ef5b 100644 --- a/openslides_backend/action/action_handler.py +++ b/openslides_backend/action/action_handler.py @@ -4,6 +4,7 @@ from typing import Any, TypeVar, cast import fastjsonschema +from psycopg.errors import RaiseException from openslides_backend.services.database.extended_database import ExtendedDatabase from openslides_backend.services.postgresql.db_connection_handling import ( @@ -13,6 +14,7 @@ from ..shared.exceptions import ( ActionException, DatastoreLockedException, + RelationException, View400Exception, ) from ..shared.handlers.base_handler import BaseHandler @@ -118,42 +120,51 @@ def handle_request( except fastjsonschema.JsonSchemaException as exception: raise ActionException(exception.message) - with get_new_os_conn() as conn: - self.datastore = ExtendedDatabase(conn, self.logging, self.env) - results: ActionsResponseResults = [] - if atomic: - results = self.execute_write_requests(self.parse_actions, payload) - else: - - def transform_to_list( - tuple: tuple[WriteRequest | None, ActionResults | None], - ) -> tuple[list[WriteRequest], ActionResults | None]: - return ([tuple[0]] if tuple[0] is not None else [], tuple[1]) + try: + with get_new_os_conn() as conn: + self.datastore = ExtendedDatabase(conn, self.logging, self.env) + results: ActionsResponseResults = [] + if atomic: + results = self.execute_write_requests( + self.parse_actions, payload + ) + else: - for element in payload: - try: - result = self.execute_write_requests( - lambda e: transform_to_list(self.perform_action(e)), - element, + def transform_to_list( + tuple: tuple[WriteRequest | None, ActionResults | None], + ) -> tuple[list[WriteRequest], ActionResults | None]: + return ( + [tuple[0]] if tuple[0] is not None else [], + tuple[1], ) - results.append(result) - except ActionException as exception: - error = cast(ActionError, exception.get_json()) - results.append(error) - self.datastore.reset() - # execute cleanup methods - for on_success in self.on_success: - on_success() + for element in payload: + try: + result = self.execute_write_requests( + lambda e: transform_to_list(self.perform_action(e)), + element, + ) + results.append(result) + except ActionException as exception: + error = cast(ActionError, exception.get_json()) + results.append(error) + self.datastore.reset() - # Return action result - self.logger.info("Request was successful. Send response now.") - return ActionsResponse( - status_code=HTTPStatus.OK.value, - success=True, - message="Actions handled successfully", - results=results, - ) + # execute cleanup methods + for on_success in self.on_success: + on_success() + + # Return action result + self.logger.info("Request was successful. Send response now.") + return ActionsResponse( + status_code=HTTPStatus.OK.value, + success=True, + message="Actions handled successfully", + results=results, + ) + except RaiseException as e: + # This is raised at the end of transaction as the constraint trigger has to be initially deferred. + raise RelationException(f"Relation violates required constraint: {e}") def execute_internal_action(self, action: str, data: dict[str, Any]) -> None: """Helper function to execute an internal action with user id -1.""" diff --git a/openslides_backend/action/action_worker.py b/openslides_backend/action/action_worker.py index 40639945a4..b26a7366b6 100644 --- a/openslides_backend/action/action_worker.py +++ b/openslides_backend/action/action_worker.py @@ -10,6 +10,7 @@ from gunicorn.http.message import Request from gunicorn.http.wsgi import Response from gunicorn.workers.gthread import ThreadWorker +from psycopg.types.json import Jsonb from openslides_backend.services.database.extended_database import ExtendedDatabase from openslides_backend.services.postgresql.db_connection_handling import ( @@ -210,7 +211,7 @@ def final_action_worker_write( fields={ "state": state, "timestamp": current_time, - "result": response, + "result": Jsonb(response), }, ) ], diff --git a/openslides_backend/action/actions/agenda_item/delete.py b/openslides_backend/action/actions/agenda_item/delete.py index 07e9017d68..7faf792430 100644 --- a/openslides_backend/action/actions/agenda_item/delete.py +++ b/openslides_backend/action/actions/agenda_item/delete.py @@ -7,7 +7,6 @@ fqid_from_collection_and_id, id_from_fqid, ) -from ....shared.typing import DeletedModel from ...generics.delete import DeleteAction from ...util.default_schema import DefaultSchema from ...util.register import register_action @@ -31,15 +30,12 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: fqid, ["content_object_id"], ) - if agenda_item.get("content_object_id"): - content_object_fqid = agenda_item["content_object_id"] + if content_object_fqid := agenda_item.get("content_object_id"): if collection_from_fqid( content_object_fqid - ) == "topic" and not self.datastore.is_deleted(content_object_fqid): - self.apply_instance(DeletedModel(), fqid) + ) == "topic" and not self.datastore.is_to_be_deleted(content_object_fqid): self.execute_other_action( TopicDelete, [{"id": id_from_fqid(content_object_fqid)}], ) - self.apply_instance(DeletedModel(), content_object_fqid) return instance diff --git a/openslides_backend/action/actions/assignment_candidate/delete.py b/openslides_backend/action/actions/assignment_candidate/delete.py index 3320c364b2..39c731a946 100644 --- a/openslides_backend/action/actions/assignment_candidate/delete.py +++ b/openslides_backend/action/actions/assignment_candidate/delete.py @@ -34,7 +34,9 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: mapped_fields=["phase", "meeting_id"], lock_result=False, ) - if assignment.get("phase") == "finished" and not self.is_meeting_deleted( + if assignment.get( + "phase" + ) == "finished" and not self.is_meeting_to_be_deleted( assignment.get("meeting_id", 0) ): raise ActionException( diff --git a/openslides_backend/action/actions/group/delete.py b/openslides_backend/action/actions/group/delete.py index 03a9ab3213..c9e292b136 100644 --- a/openslides_backend/action/actions/group/delete.py +++ b/openslides_backend/action/actions/group/delete.py @@ -43,7 +43,7 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: "meeting_id", ], ) - if len(group.get("meeting_user_ids", [])) and not self.is_meeting_deleted( + if len(group.get("meeting_user_ids", [])) and not self.is_meeting_to_be_deleted( group["meeting_id"] ): raise ActionException("You cannot delete a group with users.") diff --git a/openslides_backend/action/actions/mediafile/delete.py b/openslides_backend/action/actions/mediafile/delete.py index b78de5452c..04b03dae87 100644 --- a/openslides_backend/action/actions/mediafile/delete.py +++ b/openslides_backend/action/actions/mediafile/delete.py @@ -30,7 +30,7 @@ def get_tree_ids(self, id_: int) -> list[int]: ) if node.get("child_ids"): for child_id in node["child_ids"]: - if not self.is_deleted( + if not self.is_to_be_deleted( fqid_from_collection_and_id("mediafile", child_id) ): tree_ids.extend(self.get_tree_ids(child_id)) diff --git a/openslides_backend/action/actions/meeting/import_.py b/openslides_backend/action/actions/meeting/import_.py index bd153bbe2c..275bce6f98 100644 --- a/openslides_backend/action/actions/meeting/import_.py +++ b/openslides_backend/action/actions/meeting/import_.py @@ -466,7 +466,7 @@ def replace_field_ids( entry: dict[str, Any], field: str, ) -> None: - model_field = model_registry[collection]().try_get_field(field) + model_field = model_registry[collection].try_get_field(field) if model_field is None: raise ActionException(f"{collection}/{field} is not allowed.") if isinstance(model_field, BaseRelationField): diff --git a/openslides_backend/action/actions/meeting/replace_projector_id.py b/openslides_backend/action/actions/meeting/replace_projector_id.py index 9f410f403a..ac304cbbe1 100644 --- a/openslides_backend/action/actions/meeting/replace_projector_id.py +++ b/openslides_backend/action/actions/meeting/replace_projector_id.py @@ -27,10 +27,10 @@ def get_updated_instances(self, payload: ActionData) -> ActionData: for instance in payload: projector_id = instance.pop("projector_id") fields = Meeting.all_default_projectors() - meeting = self.datastore.get( - fqid_from_collection_and_id(self.model.collection, instance["id"]), - fields + ["reference_projector_id"], - ) + fqid = fqid_from_collection_and_id(self.model.collection, instance["id"]) + if self.datastore.is_to_be_deleted(fqid): + continue + meeting = self.datastore.get(fqid, fields + ["reference_projector_id"]) changed = False for field in fields: change_list = meeting.get(field) diff --git a/openslides_backend/action/actions/motion_workflow/delete.py b/openslides_backend/action/actions/motion_workflow/delete.py index 6ecfeae590..89b21cd6c8 100644 --- a/openslides_backend/action/actions/motion_workflow/delete.py +++ b/openslides_backend/action/actions/motion_workflow/delete.py @@ -27,7 +27,7 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: fqid_from_collection_and_id("motion_workflow", instance["id"]), ["meeting_id"], ) - if not self.is_meeting_deleted(workflow["meeting_id"]): + if not self.is_meeting_to_be_deleted(workflow["meeting_id"]): meeting = self.datastore.get( fqid_from_collection_and_id("meeting", workflow["meeting_id"]), [ diff --git a/openslides_backend/action/actions/organization/initial_import.py b/openslides_backend/action/actions/organization/initial_import.py index 39ddf4e830..bff3e555cf 100644 --- a/openslides_backend/action/actions/organization/initial_import.py +++ b/openslides_backend/action/actions/organization/initial_import.py @@ -1,11 +1,15 @@ from collections.abc import Iterable +from datetime import datetime +from decimal import Decimal from typing import Any -from openslides_backend.migrations import get_backend_migration_index +from psycopg.types.json import Jsonb from ....i18n.translator import Translator from ....i18n.translator import translate as _ +from ....models.base import model_registry from ....models.checker import Checker, CheckException +from ....models.fields import DecimalField, JSONField, TimestampField from ....models.models import Organization from ....shared.exceptions import ActionException from ....shared.filters import FilterOperator @@ -63,7 +67,7 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: if not data: data = get_initial_data_file(INITIAL_DATA_FILE) instance["data"] = data - + self.convert_to_non_json_data_types(data) # check datavalidation checker = Checker(data=data, mode="all", migration_mode="permissive") try: @@ -85,6 +89,22 @@ def check_empty_datastore(self) -> None: ): raise ActionException("Datastore is not empty.") + def convert_to_non_json_data_types(self, data: dict[str, Any]) -> None: + "json cannot hold datetime, Decimal and Jsonb values like psycopg expects." + for collection, elements in data.items(): + if collection == "_migration_index": + continue + model_description = model_registry[collection] + for element in elements.values(): + for field_name, value in element.items(): + field = model_description.try_get_field(field_name) + if isinstance(field, DecimalField): + element[field_name] = Decimal(value) + elif isinstance(field, TimestampField): + element[field_name] = datetime.fromtimestamp(value) + elif isinstance(field, JSONField): + element[field_name] = Jsonb(value) + def translate_organization_and_theme(self, data: dict[str, Any]) -> None: organization = data["organization"]["1"] Translator.set_translation_language(organization["default_language"]) @@ -133,7 +153,8 @@ def build_write_request( def create_action_result_element( self, instance: dict[str, Any] ) -> ActionResultElement | None: - backend_migration_index = get_backend_migration_index() + backend_migration_index = 1 + # backend_migration_index = get_backend_migration_index() result = { "data_migration_index": self.data_migration_index, "backend_migration_index": backend_migration_index, diff --git a/openslides_backend/action/actions/projection/delete.py b/openslides_backend/action/actions/projection/delete.py index ac0872177d..08efce9f49 100644 --- a/openslides_backend/action/actions/projection/delete.py +++ b/openslides_backend/action/actions/projection/delete.py @@ -34,11 +34,11 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: if not ( projection.get("current_projector_id") or projection.get("preview_projector_id") - or self.is_meeting_deleted(projection["meeting_id"]) - or self.is_deleted(projection["content_object_id"]) + or self.is_meeting_to_be_deleted(projection["meeting_id"]) + or self.is_to_be_deleted(projection["content_object_id"]) or ( "history_projector_id" in projection - and self.is_deleted( + and self.is_to_be_deleted( fqid_from_collection_and_id( "projector", projection["history_projector_id"] ) diff --git a/openslides_backend/action/actions/projector/delete.py b/openslides_backend/action/actions/projector/delete.py index 118bd1d69d..468c14e8c3 100644 --- a/openslides_backend/action/actions/projector/delete.py +++ b/openslides_backend/action/actions/projector/delete.py @@ -27,7 +27,7 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: ) if ( meeting_id := projector.get("used_as_reference_projector_meeting_id") - ) and not self.is_meeting_deleted(meeting_id): + ) and not self.is_meeting_to_be_deleted(meeting_id): raise ActionException( "A used as reference projector is not allowed to delete." ) diff --git a/openslides_backend/action/actions/projector_countdown/delete.py b/openslides_backend/action/actions/projector_countdown/delete.py index 762c3006ed..6ea5121a58 100644 --- a/openslides_backend/action/actions/projector_countdown/delete.py +++ b/openslides_backend/action/actions/projector_countdown/delete.py @@ -31,7 +31,7 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: meeting_id = projector_countdown.get( "used_as_list_of_speakers_countdown_meeting_id" ) or projector_countdown.get("used_as_poll_countdown_meeting_id") - if meeting_id and not self.is_meeting_deleted(meeting_id): + if meeting_id and not self.is_meeting_to_be_deleted(meeting_id): raise ActionException( "List of speakers or poll countdown is not allowed to delete." ) diff --git a/openslides_backend/action/actions/user/conditional_speaker_cascade_mixin.py b/openslides_backend/action/actions/user/conditional_speaker_cascade_mixin.py index 0957453727..3f02fdd5e9 100644 --- a/openslides_backend/action/actions/user/conditional_speaker_cascade_mixin.py +++ b/openslides_backend/action/actions/user/conditional_speaker_cascade_mixin.py @@ -14,7 +14,7 @@ def conditionally_delete_speakers(self, speaker_ids: list[int]) -> None: speaker_to_read_ids = [ speaker_id for speaker_id in speaker_ids - if not self.datastore.is_deleted( + if not self.datastore.is_to_be_deleted( fqid_from_collection_and_id("speaker", speaker_id) ) ] diff --git a/openslides_backend/action/generics/delete.py b/openslides_backend/action/generics/delete.py index 4f979f54de..86fd9fd3dd 100644 --- a/openslides_backend/action/generics/delete.py +++ b/openslides_backend/action/generics/delete.py @@ -1,7 +1,7 @@ from collections.abc import Iterable -from typing import Any +from typing import Any, cast -from ...models.fields import OnDelete +from ...models.fields import BaseRelationField, OnDelete from ...shared.exceptions import ActionException, ProtectedModelsException from ...shared.interfaces.event import Event, EventType from ...shared.patterns import ( @@ -26,13 +26,19 @@ def base_update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: """ Takes care of on_delete handling. """ - # Fetch db instance with all relevant fields - # Executed before update_instance so that actions can manually set a - # DeletedModel or other changed_models without changing the result of this. this_fqid = fqid_from_collection_and_id(self.model.collection, instance["id"]) + + if self.datastore.is_to_be_deleted(this_fqid): + # Been there. Done that. + return instance + self.datastore.apply_to_be_deleted(this_fqid) + relevant_fields = [ field.get_own_field_name() for field in self.model.get_relation_fields() ] + # Fetch db instance with all relevant fields + # Executed before update_instance so that actions can manually set a + # DeletedModel or other changed_models without changing the result of this. db_instance = self.datastore.get( fqid=this_fqid, mapped_fields=relevant_fields, @@ -44,18 +50,20 @@ def base_update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: # Update instance and set relation fields to None. # Gather all delete actions with action data and also all models to be deleted delete_actions: list[tuple[FullQualifiedId, type[Action], ActionData]] = [] - self.datastore.apply_changed_model(this_fqid, DeletedModel()) - for field in self.model.get_relation_fields(): + for field_name in db_instance: + if field_name == "id": + continue + field = cast(BaseRelationField, self.model.get_field(field_name)) # Check on_delete. + # Extract all foreign keys as fqids from the model + value = db_instance.get(field_name, []) + foreign_fqids = transform_to_fqids(value, field.get_target_collection()) if field.on_delete != OnDelete.SET_NULL: - # Extract all foreign keys as fqids from the model - foreign_fqids: list[FullQualifiedId] = [] - value = db_instance.get(field.get_own_field_name(), []) - foreign_fqids = transform_to_fqids(value, field.get_target_collection()) - if field.on_delete == OnDelete.PROTECT: protected_fqids = [ - fqid for fqid in foreign_fqids if not self.is_deleted(fqid) + fqid + for fqid in foreign_fqids + if not self.datastore.is_to_be_deleted_for_protected(fqid) ] if protected_fqids: raise ProtectedModelsException(this_fqid, protected_fqids) @@ -63,8 +71,8 @@ def base_update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: # field.on_delete == OnDelete.CASCADE # Execute the delete action for all fqids for fqid in foreign_fqids: - if self.is_deleted(fqid): - # skip models that are already deleted + if self.datastore.is_to_be_deleted(fqid): + # Skip models that are already tracked for deletion continue delete_action_class = actions_map.get( f"{collection_from_fqid(fqid)}.delete" @@ -77,36 +85,40 @@ def base_update_instance(self, instance: dict[str, Any]) -> dict[str, Any]: # Assume that the delete action uses the standard action data action_data = [{"id": id_from_fqid(fqid)}] delete_actions.append((fqid, delete_action_class, action_data)) + self.datastore.apply_to_be_deleted_for_protected(fqid) else: # field.on_delete == OnDelete.SET_NULL - instance[field.get_own_field_name()] = None + if field.is_view_field: + instance[field_name] = None # Add additional relation models and execute all previously gathered delete actions # catch all protected models exception to gather all protected fqids all_protected_fqids: list[FullQualifiedId] = [] for fqid, delete_action_class, delete_action_data in delete_actions: try: - self.execute_other_action(delete_action_class, delete_action_data) - self.datastore.apply_changed_model(fqid, DeletedModel()) + # Skip models that were deleted in the meantime + if not self.datastore.is_deleted(fqid): + self.execute_other_action(delete_action_class, delete_action_data) except ProtectedModelsException as e: all_protected_fqids.extend(e.fqids) if all_protected_fqids: raise ProtectedModelsException(this_fqid, all_protected_fqids) + self.datastore.apply_changed_model(this_fqid, DeletedModel()) return instance def create_events(self, instance: dict[str, Any]) -> Iterable[Event]: fqid = fqid_from_collection_and_id(self.model.collection, instance["id"]) yield self.build_event(EventType.Delete, fqid) - def is_meeting_deleted(self, meeting_id: int) -> bool: + def is_meeting_to_be_deleted(self, meeting_id: int) -> bool: """ - Returns whether the given meeting was deleted during this request or not. + Returns whether the given meeting was/will be deleted during this request or not. """ - return self.datastore.is_deleted( + return self.datastore.is_to_be_deleted( fqid_from_collection_and_id("meeting", meeting_id) ) - def is_deleted(self, fqid: FullQualifiedId) -> bool: - return self.datastore.is_deleted(fqid) + def is_to_be_deleted(self, fqid: FullQualifiedId) -> bool: + return self.datastore.is_to_be_deleted(fqid) diff --git a/openslides_backend/action/mixins/archived_meeting_check_mixin.py b/openslides_backend/action/mixins/archived_meeting_check_mixin.py index 9bc8b68111..38cd653312 100644 --- a/openslides_backend/action/mixins/archived_meeting_check_mixin.py +++ b/openslides_backend/action/mixins/archived_meeting_check_mixin.py @@ -19,7 +19,7 @@ def check_for_archived_meeting(self, instance: dict[str, Any]) -> None: """check all instance fields for their meeting and if the meeting is active""" if self.skip_archived_meeting_check: return - model = model_registry[self.model.collection]() + model = model_registry[self.model.collection] meeting_ids: set[int] = set() if "meeting_id" in instance: meeting_ids.add(instance["meeting_id"]) diff --git a/openslides_backend/action/mixins/extend_history_mixin.py b/openslides_backend/action/mixins/extend_history_mixin.py index d6a57804ee..f1f9a38e11 100644 --- a/openslides_backend/action/mixins/extend_history_mixin.py +++ b/openslides_backend/action/mixins/extend_history_mixin.py @@ -19,9 +19,11 @@ class ExtendHistoryMixin(Action): def create_events(self, instance: dict[str, Any]) -> Iterable[Event]: yield from super().create_events(instance) field = self.model.get_field(self.extend_history_to) + fqid = fqid_from_collection_and_id(self.model.collection, instance["id"]) model = self.datastore.get( - fqid_from_collection_and_id(self.model.collection, instance["id"]), + fqid, [self.extend_history_to], + use_changed_models=not self.datastore.is_deleted(fqid), ) value = model[self.extend_history_to] if isinstance(field, GenericRelationField): diff --git a/openslides_backend/action/relations/relation_manager.py b/openslides_backend/action/relations/relation_manager.py index 167bf093fd..78636f9868 100644 --- a/openslides_backend/action/relations/relation_manager.py +++ b/openslides_backend/action/relations/relation_manager.py @@ -40,6 +40,7 @@ def get_relation_updates( # only relations are handled here if not isinstance(field, BaseRelationField): continue + handler = SingleRelationHandler( self.datastore, field, diff --git a/openslides_backend/action/relations/single_relation_handler.py b/openslides_backend/action/relations/single_relation_handler.py index d4a7456b87..44d89f1f7c 100644 --- a/openslides_backend/action/relations/single_relation_handler.py +++ b/openslides_backend/action/relations/single_relation_handler.py @@ -232,11 +232,11 @@ def relation_diffs( # Calculate add set and remove set new_fqids = set(rel_fqids) add = new_fqids - current_fqids - # filter out deleted models, so that in case of cascade deletion no data is lost + # filter out deleted models for improved performance remove = { fqid for fqid in current_fqids - new_fqids - if not self.datastore.is_deleted(fqid) + if not self.datastore.is_to_be_deleted(fqid) } return add, remove diff --git a/openslides_backend/models/base.py b/openslides_backend/models/base.py index e8cc6895d5..3f4eb0ec46 100644 --- a/openslides_backend/models/base.py +++ b/openslides_backend/models/base.py @@ -57,11 +57,12 @@ def has_field(self, field_name: str) -> bool: """ return bool(self.try_get_field(field_name)) - def try_get_field(self, field_name: str) -> fields.Field | None: + @classmethod + def try_get_field(cls, field_name: str) -> fields.Field | None: """ Returns the field for the given field name or None if field is not found. """ - field = getattr(self, field_name, None) + field = getattr(cls, field_name, None) if isinstance(field, fields.Field): return field return None diff --git a/openslides_backend/models/checker.py b/openslides_backend/models/checker.py index 24689e67d4..674da4c424 100644 --- a/openslides_backend/models/checker.py +++ b/openslides_backend/models/checker.py @@ -1,9 +1,12 @@ import re from collections.abc import Callable, Iterable -from decimal import InvalidOperation +from datetime import datetime +from decimal import Decimal, InvalidOperation +from math import floor from typing import Any, cast import fastjsonschema +from psycopg.types.json import Jsonb from openslides_backend.migrations import get_backend_migration_index from openslides_backend.models.base import model_registry @@ -113,8 +116,12 @@ def check_x_list(value: Any, fn: Callable) -> bool: def check_decimal(value: Any) -> bool: - return value is None or bool( - isinstance(value, str) and DECIMAL_PATTERN.match(value) + return ( + value is None + or bool(isinstance(value, str) and DECIMAL_PATTERN.match(value)) + or isinstance(value, Decimal) + and value.is_finite() + and cast(int, value.normalize().as_tuple().exponent) >= -6 ) @@ -125,18 +132,26 @@ def check_json(value: Any, root: bool = True) -> bool: return True if isinstance(value, list): return all(check_json(x, root=False) for x in value) - if isinstance(value, dict): + elif isinstance(value, dict): return all(check_json(x, root=False) for x in value.values()) + elif isinstance(value, Jsonb): + return check_json(value.obj, root=True) return False +def check_timestamp(value: Any) -> bool: + if isinstance(value, datetime): + value = floor(value.timestamp()) + return check_number(value) + + checker_map: dict[type[Field], Callable[..., bool]] = { CharField: check_string, HTMLStrictField: check_string, HTMLPermissiveField: check_string, GenericRelationField: check_string, IntegerField: check_number, - TimestampField: check_number, + TimestampField: check_timestamp, RelationField: check_number, FloatField: check_float, BooleanField: check_boolean, @@ -239,7 +254,8 @@ def get_fields(self, collection: str) -> Iterable[Field]: def run_check(self) -> None: self.check_json() - self.check_migration_index() + # TODO reenable when import migration works + # self.check_migration_index() self.check_collections() for collection, models in self.data.items(): if collection.startswith("_"): diff --git a/openslides_backend/services/database/database_writer.py b/openslides_backend/services/database/database_writer.py index 6948f382f3..d3e59e6475 100644 --- a/openslides_backend/services/database/database_writer.py +++ b/openslides_backend/services/database/database_writer.py @@ -6,6 +6,7 @@ CheckViolation, DatatypeMismatch, GeneratedAlways, + InFailedSqlTransaction, NotNullViolation, ProgrammingError, SyntaxError, @@ -530,6 +531,10 @@ def execute_sql( raise ModelDoesNotExist(error_fqid) id_ = result.get("id", 0) return id_ + except InFailedSqlTransaction as e: + raise BadCodingException( + f"Tried to set {error_fqid} in an already broken transaction: {e}" + ) except UniqueViolation as e: if "duplicate key value violates unique constraint" in e.args[0]: if "Key (id)" in e.args[0]: diff --git a/openslides_backend/services/database/extended_database.py b/openslides_backend/services/database/extended_database.py index fa9049bf41..ab3c23fae3 100644 --- a/openslides_backend/services/database/extended_database.py +++ b/openslides_backend/services/database/extended_database.py @@ -99,6 +99,8 @@ def __init__( self.env = env self.logger = logging.getLogger(__name__) self._changed_models = defaultdict(lambda: defaultdict(dict)) + self._to_be_deleted: set[FullQualifiedId] = set() + self._to_be_deleted_for_protected: set[FullQualifiedId] = set() self.connection = connection self.database_reader = DatabaseReader(self.connection, logging, env) self.database_writer = DatabaseWriter(self.connection, logging, env) @@ -118,6 +120,21 @@ def apply_changed_model( if "id" not in self._changed_models[collection][id_]: self._changed_models[collection][id_]["id"] = id_ + def apply_to_be_deleted(self, fqid: FullQualifiedId) -> None: + """ + The model will be marked as to be deleted. + This will not be undone when it is marked as deleted. + """ + self._to_be_deleted.add(fqid) + + def apply_to_be_deleted_for_protected(self, fqid: FullQualifiedId) -> None: + """ + The model will be marked as to be deleted. + This will not be undone when it is marked as deleted. + Only used for protected models deletion. + """ + self._to_be_deleted_for_protected.add(fqid) + def get_changed_model( self, collection_or_fqid: str, id_: int | None = None ) -> PartialModel: @@ -128,6 +145,19 @@ def get_changed_model( def get_changed_models(self, collection: str) -> dict[Id, PartialModel]: return self._changed_models.get(collection, dict()) + def is_to_be_deleted(self, fqid: FullQualifiedId) -> bool: + """ + Returns if the model was ever marked for deletion. + """ + return fqid in self._to_be_deleted + + def is_to_be_deleted_for_protected(self, fqid: FullQualifiedId) -> bool: + """ + Returns if the model was ever marked for deletion. + Only used for protected models deletion. + """ + return fqid in self._to_be_deleted_for_protected or fqid in self._to_be_deleted + def get( self, fqid: FullQualifiedId, diff --git a/openslides_backend/services/database/interface.py b/openslides_backend/services/database/interface.py index 1cb3ab081a..74ef705e11 100644 --- a/openslides_backend/services/database/interface.py +++ b/openslides_backend/services/database/interface.py @@ -36,6 +36,12 @@ def apply_changed_model( self, fqid: FullQualifiedId, instance: PartialModel, replace: bool = False ) -> None: ... + @abstractmethod + def apply_to_be_deleted(self, fqid: FullQualifiedId) -> None: ... + + @abstractmethod + def apply_to_be_deleted_for_protected(self, fqid: FullQualifiedId) -> None: ... + @abstractmethod def get_changed_model( self, collection_or_fqid: str, id_: Id | None = None @@ -44,6 +50,12 @@ def get_changed_model( @abstractmethod def get_changed_models(self, collection: str) -> dict[Id, PartialModel]: ... + @abstractmethod + def is_to_be_deleted(self, fqid: FullQualifiedId) -> bool: ... + + @abstractmethod + def is_to_be_deleted_for_protected(self, fqid: FullQualifiedId) -> bool: ... + @abstractmethod def get( self, diff --git a/requirements/partial/requirements_development.txt b/requirements/partial/requirements_development.txt index 2481abc6db..cada80d738 100644 --- a/requirements/partial/requirements_development.txt +++ b/requirements/partial/requirements_development.txt @@ -5,6 +5,7 @@ debugpy==1.8.14 flake8==7.3.0 isort==6.0.1 mypy==1.17.0 +sqlfluff==3.4.2 pip-check==3.1 pytest==8.4.1 pytest-cov==6.2.1 diff --git a/setup.cfg b/setup.cfg index 7a95ca062d..850b653921 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,6 +23,15 @@ disallow_untyped_defs = true exclude = tests/system/migrations namespace_packages = true +[sqlfluff] +dialect = postgres +large_file_skip_byte_limit = 0 +processes = 0 + +[sqlfluff:rules:capitalisation.identifiers] +ignore_words = WARNING, + + [tool:pytest] testpaths = tests/ filterwarnings = diff --git a/tests/system/action/agenda_item/test_assign.py b/tests/system/action/agenda_item/test_assign.py index e1922d1223..d62ec18f7b 100644 --- a/tests/system/action/agenda_item/test_assign.py +++ b/tests/system/action/agenda_item/test_assign.py @@ -5,6 +5,23 @@ class AgendaItemAssignActionTest(BaseActionTestCase): + PERMISSION_TEST_MODELS = { + "agenda_item/7": {"meeting_id": 1, "content_object_id": "topic/1"}, + "agenda_item/8": {"meeting_id": 1, "content_object_id": "topic/2"}, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 1, + }, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 1, + }, + "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, + "topic/2": {"meeting_id": 1, "title": "tropic", "sequential_number": 2}, + } + def test_assign_parent_none(self) -> None: self.create_meeting(222) self.set_models( @@ -32,6 +49,21 @@ def test_assign_parent_none(self) -> None: "child_ids": [], "content_object_id": "topic/3", }, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 222, + }, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 222, + }, + "list_of_speakers/64": { + "content_object_id": "topic/3", + "sequential_number": 13, + "meeting_id": 222, + }, "topic/1": { "meeting_id": 222, "title": "tropic", @@ -94,6 +126,21 @@ def test_assign_parent_set(self) -> None: "child_ids": [], "content_object_id": "topic/3", }, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 222, + }, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 222, + }, + "list_of_speakers/64": { + "content_object_id": "topic/3", + "sequential_number": 13, + "meeting_id": 222, + }, "topic/1": { "meeting_id": 222, "title": "tropic", @@ -137,6 +184,16 @@ def test_assign_multiple_action_data_items(self) -> None: { "agenda_item/7": {"meeting_id": 222, "content_object_id": "topic/1"}, "agenda_item/8": {"meeting_id": 222, "content_object_id": "topic/2"}, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 222, + }, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 222, + }, "topic/1": { "meeting_id": 222, "title": "tropic", @@ -164,24 +221,14 @@ def test_assign_multiple_action_data_items(self) -> None: def test_assign_no_permissions(self) -> None: self.base_permission_test( - { - "agenda_item/7": {"meeting_id": 1, "content_object_id": "topic/1"}, - "agenda_item/8": {"meeting_id": 1, "content_object_id": "topic/2"}, - "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - "topic/2": {"meeting_id": 1, "title": "tropic", "sequential_number": 2}, - }, + self.PERMISSION_TEST_MODELS, "agenda_item.assign", {"meeting_id": 1, "ids": [8], "parent_id": 7}, ) def test_assign_permissions(self) -> None: self.base_permission_test( - { - "agenda_item/7": {"meeting_id": 1, "content_object_id": "topic/1"}, - "agenda_item/8": {"meeting_id": 1, "content_object_id": "topic/2"}, - "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - "topic/2": {"meeting_id": 1, "title": "tropic", "sequential_number": 2}, - }, + self.PERMISSION_TEST_MODELS, "agenda_item.assign", {"meeting_id": 1, "ids": [8], "parent_id": 7}, Permissions.AgendaItem.CAN_MANAGE, @@ -189,12 +236,7 @@ def test_assign_permissions(self) -> None: def test_assign_permissions_with_locked_meeting(self) -> None: self.base_permission_test( - { - "agenda_item/7": {"meeting_id": 1, "content_object_id": "topic/1"}, - "agenda_item/8": {"meeting_id": 1, "content_object_id": "topic/2"}, - "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - "topic/2": {"meeting_id": 1, "title": "tropic", "sequential_number": 2}, - }, + self.PERMISSION_TEST_MODELS, "agenda_item.assign", {"meeting_id": 1, "ids": [8], "parent_id": 7}, OrganizationManagementLevel.SUPERADMIN, @@ -204,12 +246,7 @@ def test_assign_permissions_with_locked_meeting(self) -> None: def test_assign_permissions_with_locked_meeting_orgaadmin(self) -> None: self.base_permission_test( - { - "agenda_item/7": {"meeting_id": 1, "content_object_id": "topic/1"}, - "agenda_item/8": {"meeting_id": 1, "content_object_id": "topic/2"}, - "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - "topic/2": {"meeting_id": 1, "title": "tropic", "sequential_number": 2}, - }, + self.PERMISSION_TEST_MODELS, "agenda_item.assign", {"meeting_id": 1, "ids": [8], "parent_id": 7}, OrganizationManagementLevel.CAN_MANAGE_ORGANIZATION, diff --git a/tests/system/action/agenda_item/test_create.py b/tests/system/action/agenda_item/test_create.py index f798a32965..0f8be5791e 100644 --- a/tests/system/action/agenda_item/test_create.py +++ b/tests/system/action/agenda_item/test_create.py @@ -4,35 +4,49 @@ class AgendaItemSystemTest(BaseActionTestCase): + PERMISSION_TEST_MODELS = { + "motion/1": { + "title": "motion1", + "sequential_number": 1, + "state_id": 1, + "meeting_id": 1, + }, + "list_of_speakers/1": { + "content_object_id": "motion/1", + "sequential_number": 1, + "meeting_id": 1, + }, + } + def test_create_simple(self) -> None: self.create_meeting(2) - self.set_models( + self.create_motion(2) + response = self.request("agenda_item.create", {"content_object_id": "motion/1"}) + self.assert_status_code(response, 200) + self.assert_model_exists( + "agenda_item/1", { - "topic/1": {"meeting_id": 2, "title": "tropic", "sequential_number": 1}, - } + "meeting_id": 2, + "content_object_id": "motion/1", + "type": AgendaItem.AGENDA_ITEM, + "weight": 1, + "level": 0, + }, ) - response = self.request("agenda_item.create", {"content_object_id": "topic/1"}) - self.assert_status_code(response, 200) - model = self.get_model("agenda_item/1") - self.assertFalse(model.get("meta_deleted")) - self.assertEqual(model.get("meeting_id"), 2) - self.assertEqual(model.get("content_object_id"), "topic/1") - self.assertEqual(model.get("type"), AgendaItem.AGENDA_ITEM) - self.assertEqual(model.get("weight"), 1) - self.assertEqual(model.get("level"), 0) - - model = self.get_model("meeting/2") - self.assertEqual(model.get("agenda_item_ids"), [1]) - - model = self.get_model("topic/1") - self.assertEqual(model.get("agenda_item_id"), 1) + self.assert_model_exists("meeting/2", {"agenda_item_ids": [1]}) + self.assert_model_exists("motion/1", {"agenda_item_id": 1}) def test_create_more_fields(self) -> None: self.create_meeting() + self.create_motion(1) self.set_models( { "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - "topic/2": {"meeting_id": 1, "title": "jungle", "sequential_number": 2}, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 1, + }, "agenda_item/42": { "comment": "test", "meeting_id": 1, @@ -44,7 +58,7 @@ def test_create_more_fields(self) -> None: response = self.request( "agenda_item.create", { - "content_object_id": "topic/2", + "content_object_id": "motion/1", "comment": "test_comment_oiuoitesfd", "type": AgendaItem.INTERNAL_ITEM, "parent_id": 42, @@ -53,42 +67,47 @@ def test_create_more_fields(self) -> None: }, ) self.assert_status_code(response, 200) - agenda_item = self.get_model("agenda_item/43") - self.assertEqual(agenda_item["comment"], "test_comment_oiuoitesfd") - self.assertEqual(agenda_item["type"], "internal") - self.assertEqual(agenda_item["parent_id"], 42) - self.assertEqual(agenda_item["duration"], 360) - self.assertEqual(agenda_item["weight"], 1) - self.assertFalse(agenda_item.get("closed")) - assert agenda_item.get("level") == 1 - assert agenda_item.get("tag_ids") == [561] + self.assert_model_exists( + "agenda_item/43", + { + "comment": "test_comment_oiuoitesfd", + "type": "internal", + "parent_id": 42, + "duration": 360, + "weight": 1, + "closed": False, + "level": 1, + "tag_ids": [561], + }, + ) self.assert_model_exists( "tag/561", {"meeting_id": 1, "tagged_ids": ["agenda_item/43"]} ) def test_create_twice_without_parent(self) -> None: self.create_meeting() - self.set_models( - { - "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - "topic/2": {"meeting_id": 1, "title": "jungle", "sequential_number": 2}, - } - ) + self.create_motion(1) + self.create_motion(1, 2) for i in range(1, 3): response = self.request( "agenda_item.create", - {"content_object_id": f"topic/{i}"}, + {"content_object_id": f"motion/{i}"}, ) self.assert_status_code(response, 200) self.assert_model_exists(f"agenda_item/{i}", {"weight": i}) def test_create_parent_weight(self) -> None: self.create_meeting() + self.create_motion(1) + self.create_motion(1, 2) self.set_models( { "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - "topic/2": {"meeting_id": 1, "title": "jungle", "sequential_number": 2}, - "topic/3": {"meeting_id": 1, "title": "feever", "sequential_number": 3}, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 1, + }, "agenda_item/42": { "comment": "test", "meeting_id": 1, @@ -101,11 +120,11 @@ def test_create_parent_weight(self) -> None: "agenda_item.create", [ { - "content_object_id": "topic/2", + "content_object_id": "motion/1", "parent_id": 42, }, { - "content_object_id": "topic/3", + "content_object_id": "motion/2", "parent_id": 42, }, ], @@ -120,27 +139,22 @@ def test_create_parent_weight(self) -> None: def test_create_same_content_object(self) -> None: self.create_meeting() - self.set_models( - { - "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - } - ) + self.create_motion(1) response = self.request_multi( "agenda_item.create", [ { - "content_object_id": "topic/1", + "content_object_id": "motion/1", }, { - "content_object_id": "topic/1", + "content_object_id": "motion/1", }, ], ) self.assert_status_code(response, 400) self.assert_model_not_exists("agenda_item/1") self.assert_model_not_exists("agenda_item/2") - topic = self.get_model("topic/1") - self.assertEqual(topic.get("agenda_item_id"), None) + self.assert_model_exists("motion/1", {"agenda_item_id": None}) def test_create_content_object_does_not_exist(self) -> None: response = self.request("agenda_item.create", {"content_object_id": "topic/1"}) @@ -150,10 +164,15 @@ def test_create_content_object_does_not_exist(self) -> None: def test_create_differing_meeting_ids(self) -> None: self.create_meeting() self.create_meeting(4) + self.create_motion(4) self.set_models( { "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - "topic/2": {"meeting_id": 4, "title": "jungle", "sequential_number": 2}, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 1, + }, "agenda_item/1": { "comment": "test", "meeting_id": 1, @@ -162,7 +181,7 @@ def test_create_differing_meeting_ids(self) -> None: } ) response = self.request( - "agenda_item.create", {"content_object_id": "topic/2", "parent_id": 1} + "agenda_item.create", {"content_object_id": "motion/1", "parent_id": 1} ) self.assert_status_code(response, 400) self.assertIn( @@ -173,14 +192,10 @@ def test_create_differing_meeting_ids(self) -> None: def test_create_calc_fields_no_parent_agenda_type(self) -> None: self.create_meeting(2) - self.set_models( - { - "topic/1": {"meeting_id": 2, "title": "tropic", "sequential_number": 1}, - } - ) + self.create_motion(2) response = self.request( "agenda_item.create", - {"content_object_id": "topic/1", "type": AgendaItem.AGENDA_ITEM}, + {"content_object_id": "motion/1", "type": AgendaItem.AGENDA_ITEM}, ) self.assert_status_code(response, 200) model = self.get_model("agenda_item/1") @@ -190,14 +205,10 @@ def test_create_calc_fields_no_parent_agenda_type(self) -> None: def test_create_calc_fields_no_parent_hidden_type(self) -> None: self.create_meeting(2) - self.set_models( - { - "topic/1": {"meeting_id": 2, "title": "tropic", "sequential_number": 1}, - } - ) + self.create_motion(2) response = self.request( "agenda_item.create", - {"content_object_id": "topic/1", "type": AgendaItem.HIDDEN_ITEM}, + {"content_object_id": "motion/1", "type": AgendaItem.HIDDEN_ITEM}, ) self.assert_status_code(response, 200) model = self.get_model("agenda_item/1") @@ -207,16 +218,11 @@ def test_create_calc_fields_no_parent_hidden_type(self) -> None: def test_create_calc_fields_no_parent_internal_type(self) -> None: self.create_meeting(2) - self.set_models( - { - "topic/1": {"meeting_id": 2, "title": "tropic", "sequential_number": 1}, - "topic/2": {"meeting_id": 2, "title": "jungle", "sequential_number": 2}, - } - ) + self.create_motion(2) response = self.request( "agenda_item.create", { - "content_object_id": "topic/1", + "content_object_id": "motion/1", "type": AgendaItem.INTERNAL_ITEM, }, ) @@ -228,10 +234,15 @@ def test_create_calc_fields_no_parent_internal_type(self) -> None: def test_create_calc_fields_parent_agenda_internal(self) -> None: self.create_meeting(2) + self.create_motion(2) self.set_models( { - "topic/1": {"meeting_id": 2, "title": "tropic", "sequential_number": 1}, "topic/2": {"meeting_id": 2, "title": "jungle", "sequential_number": 2}, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 2, + }, "agenda_item/3": { "content_object_id": "topic/2", "type": AgendaItem.AGENDA_ITEM, @@ -245,7 +256,7 @@ def test_create_calc_fields_parent_agenda_internal(self) -> None: response = self.request( "agenda_item.create", { - "content_object_id": "topic/1", + "content_object_id": "motion/1", "type": AgendaItem.INTERNAL_ITEM, "parent_id": 3, }, @@ -258,10 +269,15 @@ def test_create_calc_fields_parent_agenda_internal(self) -> None: def test_create_calc_fields_parent_internal_internal(self) -> None: self.create_meeting(2) + self.create_motion(2) self.set_models( { - "topic/1": {"meeting_id": 2, "title": "tropic", "sequential_number": 1}, "topic/2": {"meeting_id": 2, "title": "jungle", "sequential_number": 2}, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 2, + }, "agenda_item/3": { "content_object_id": "topic/2", "type": AgendaItem.INTERNAL_ITEM, @@ -274,7 +290,7 @@ def test_create_calc_fields_parent_internal_internal(self) -> None: response = self.request( "agenda_item.create", { - "content_object_id": "topic/1", + "content_object_id": "motion/1", "type": AgendaItem.INTERNAL_ITEM, "parent_id": 3, }, @@ -287,10 +303,15 @@ def test_create_calc_fields_parent_internal_internal(self) -> None: def test_create_calc_fields_parent_internal_hidden(self) -> None: self.create_meeting(2) + self.create_motion(2) self.set_models( { - "topic/1": {"meeting_id": 2, "title": "tropic", "sequential_number": 1}, "topic/2": {"meeting_id": 2, "title": "jungle", "sequential_number": 2}, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 2, + }, "agenda_item/3": { "content_object_id": "topic/2", "type": AgendaItem.INTERNAL_ITEM, @@ -304,7 +325,7 @@ def test_create_calc_fields_parent_internal_hidden(self) -> None: response = self.request( "agenda_item.create", { - "content_object_id": "topic/1", + "content_object_id": "motion/1", "type": AgendaItem.HIDDEN_ITEM, "parent_id": 3, }, @@ -317,24 +338,24 @@ def test_create_calc_fields_parent_internal_hidden(self) -> None: def test_create_no_permissions(self) -> None: self.base_permission_test( - {"topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}}, + self.PERMISSION_TEST_MODELS, "agenda_item.create", - {"content_object_id": "topic/1"}, + {"content_object_id": "motion/1"}, ) def test_create_permissions(self) -> None: self.base_permission_test( - {"topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}}, + self.PERMISSION_TEST_MODELS, "agenda_item.create", - {"content_object_id": "topic/1"}, + {"content_object_id": "motion/1"}, Permissions.AgendaItem.CAN_MANAGE, ) def test_create_permissions_with_locked_meeting(self) -> None: self.base_locked_out_superadmin_permission_test( - {"topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}}, + self.PERMISSION_TEST_MODELS, "agenda_item.create", - {"content_object_id": "topic/1"}, + {"content_object_id": "motion/1"}, ) def test_create_replace_reverse_of_multi_content_object_id_required_error( @@ -349,6 +370,11 @@ def test_create_replace_reverse_of_multi_content_object_id_required_error( "title": "just do it", "sequential_number": 1, }, + "list_of_speakers/42": { + "content_object_id": "assignment/1", + "sequential_number": 12, + "meeting_id": 1, + }, "agenda_item/1": {"meeting_id": 1, "content_object_id": "assignment/1"}, } ) diff --git a/tests/system/action/agenda_item/test_delete.py b/tests/system/action/agenda_item/test_delete.py index 5299b3d864..e1f68df701 100644 --- a/tests/system/action/agenda_item/test_delete.py +++ b/tests/system/action/agenda_item/test_delete.py @@ -3,6 +3,11 @@ class AgendaItemActionTest(BaseActionTestCase): + + PERMISSION_TEST_MODELS = { + "agenda_item/111": {"content_object_id": "motion/34", "meeting_id": 1}, + } + def setUp(self) -> None: super().setUp() self.create_meeting(20) @@ -12,6 +17,11 @@ def test_delete_correct(self) -> None: self.set_models( { "agenda_item/111": {"meeting_id": 20, "content_object_id": "topic/1"}, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 20, + }, "topic/1": { "meeting_id": 20, "title": "tropic", @@ -28,6 +38,11 @@ def test_delete_wrong_id(self) -> None: self.set_models( { "agenda_item/112": {"meeting_id": 20, "content_object_id": "topic/1"}, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 20, + }, "topic/1": { "meeting_id": 20, "title": "tropic", @@ -49,6 +64,11 @@ def test_delete_topic(self) -> None: "agenda_item_id": 111, "meeting_id": 20, }, + "list_of_speakers/23": { + "content_object_id": "topic/34", + "sequential_number": 11, + "meeting_id": 20, + }, "agenda_item/111": {"content_object_id": "topic/34", "meeting_id": 20}, } ) @@ -104,9 +124,7 @@ def test_delete_no_permissions(self) -> None: self.create_meeting(1) self.create_motion(1, 34) self.base_permission_test( - { - "agenda_item/111": {"content_object_id": "motion/34", "meeting_id": 1}, - }, + self.PERMISSION_TEST_MODELS, "agenda_item.delete", {"id": 111}, ) @@ -115,9 +133,7 @@ def test_delete_permissions(self) -> None: self.create_meeting(1) self.create_motion(1, 34) self.base_permission_test( - { - "agenda_item/111": {"content_object_id": "motion/34", "meeting_id": 1}, - }, + self.PERMISSION_TEST_MODELS, "agenda_item.delete", {"id": 111}, Permissions.AgendaItem.CAN_MANAGE, @@ -127,9 +143,7 @@ def test_delete_permissions_locked_meeting(self) -> None: self.create_meeting(1) self.create_motion(1, 34) self.base_locked_out_superadmin_permission_test( - { - "agenda_item/111": {"content_object_id": "motion/34", "meeting_id": 1}, - }, + self.PERMISSION_TEST_MODELS, "agenda_item.delete", {"id": 111}, ) diff --git a/tests/system/action/agenda_item/test_numbering.py b/tests/system/action/agenda_item/test_numbering.py index 222e54af0b..1579515678 100644 --- a/tests/system/action/agenda_item/test_numbering.py +++ b/tests/system/action/agenda_item/test_numbering.py @@ -8,6 +8,33 @@ class AgendaItemNumberingTester(BaseActionTestCase): Tests agenda item numbering action. """ + PERMISSION_TEST_MODELS = { + "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, + "topic/2": {"meeting_id": 1, "title": "tropic", "sequential_number": 2}, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 1, + }, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 1, + }, + "agenda_item/1": { + "meeting_id": 1, + "weight": 10, + "type": AgendaItem.AGENDA_ITEM, + "content_object_id": "topic/1", + }, + "agenda_item/2": { + "meeting_id": 1, + "weight": 10, + "type": AgendaItem.AGENDA_ITEM, + "content_object_id": "topic/2", + }, + } + def setUp(self) -> None: super().setUp() self.create_meeting() @@ -18,6 +45,21 @@ def test_numbering(self) -> None: "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, "topic/2": {"meeting_id": 1, "title": "tropic", "sequential_number": 2}, "topic/3": {"meeting_id": 1, "title": "tropic", "sequential_number": 3}, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 1, + }, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 1, + }, + "list_of_speakers/64": { + "content_object_id": "topic/3", + "sequential_number": 13, + "meeting_id": 1, + }, "agenda_item/1": { "meeting_id": 1, "weight": 10, @@ -58,6 +100,21 @@ def test_numbering_prefix(self) -> None: "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, "topic/2": {"meeting_id": 1, "title": "tropic", "sequential_number": 2}, "topic/3": {"meeting_id": 1, "title": "tropic", "sequential_number": 3}, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 1, + }, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 1, + }, + "list_of_speakers/64": { + "content_object_id": "topic/3", + "sequential_number": 13, + "meeting_id": 1, + }, "agenda_item/1": { "meeting_id": 1, "weight": 10, @@ -98,6 +155,21 @@ def test_numbering_roman(self) -> None: "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, "topic/2": {"meeting_id": 1, "title": "tropic", "sequential_number": 2}, "topic/3": {"meeting_id": 1, "title": "tropic", "sequential_number": 3}, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 1, + }, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 1, + }, + "list_of_speakers/64": { + "content_object_id": "topic/3", + "sequential_number": 13, + "meeting_id": 1, + }, "agenda_item/1": { "meeting_id": 1, "weight": 10, @@ -134,6 +206,16 @@ def test_numbering_without_parents(self) -> None: { "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, "topic/2": {"meeting_id": 1, "title": "tropic", "sequential_number": 2}, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 1, + }, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 1, + }, "agenda_item/1": { "meeting_id": 1, "weight": 10, @@ -164,6 +246,16 @@ def test_numbering_with_non_public_items(self) -> None: "title": "tropic Al", "sequential_number": 2, }, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 1, + }, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 1, + }, "agenda_item/1": { "meeting_id": 1, "weight": 10, @@ -187,44 +279,14 @@ def test_numbering_with_non_public_items(self) -> None: def test_numbering_no_permissions(self) -> None: self.base_permission_test( - { - "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - "topic/2": {"meeting_id": 1, "title": "tropic", "sequential_number": 2}, - "agenda_item/1": { - "meeting_id": 1, - "weight": 10, - "type": AgendaItem.AGENDA_ITEM, - "content_object_id": "topic/1", - }, - "agenda_item/2": { - "meeting_id": 1, - "weight": 10, - "type": AgendaItem.AGENDA_ITEM, - "content_object_id": "topic/2", - }, - }, + self.PERMISSION_TEST_MODELS, "agenda_item.numbering", {"meeting_id": 1}, ) def test_numbering_permissions(self) -> None: self.base_permission_test( - { - "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - "topic/2": {"meeting_id": 1, "title": "tropic", "sequential_number": 2}, - "agenda_item/1": { - "meeting_id": 1, - "weight": 10, - "type": AgendaItem.AGENDA_ITEM, - "content_object_id": "topic/1", - }, - "agenda_item/2": { - "meeting_id": 1, - "weight": 10, - "type": AgendaItem.AGENDA_ITEM, - "content_object_id": "topic/2", - }, - }, + self.PERMISSION_TEST_MODELS, "agenda_item.numbering", {"meeting_id": 1}, Permissions.AgendaItem.CAN_MANAGE, @@ -232,22 +294,7 @@ def test_numbering_permissions(self) -> None: def test_numbering_permissions_locked_meeting(self) -> None: self.base_locked_out_superadmin_permission_test( - { - "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - "topic/2": {"meeting_id": 1, "title": "tropic", "sequential_number": 2}, - "agenda_item/1": { - "meeting_id": 1, - "weight": 10, - "type": AgendaItem.AGENDA_ITEM, - "content_object_id": "topic/1", - }, - "agenda_item/2": { - "meeting_id": 1, - "weight": 10, - "type": AgendaItem.AGENDA_ITEM, - "content_object_id": "topic/2", - }, - }, + self.PERMISSION_TEST_MODELS, "agenda_item.numbering", {"meeting_id": 1}, ) diff --git a/tests/system/action/agenda_item/test_sort.py b/tests/system/action/agenda_item/test_sort.py index a82475af1f..6a7a1fb7bc 100644 --- a/tests/system/action/agenda_item/test_sort.py +++ b/tests/system/action/agenda_item/test_sort.py @@ -3,6 +3,20 @@ class AgendaItemSortActionTest(BaseActionTestCase): + PERMISSION_TEST_MODELS = { + "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, + "list_of_speakers/42": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 1, + }, + "agenda_item/22": { + "meeting_id": 1, + "comment": "test1", + "content_object_id": "topic/1", + }, + } + def setUp(self) -> None: super().setUp() self.create_meeting(222) @@ -15,6 +29,11 @@ def test_sort_single_node_correct(self) -> None: "title": "jungle", "sequential_number": 2, }, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 11, + "meeting_id": 222, + }, "agenda_item/22": { "meeting_id": 222, "content_object_id": "topic/2", @@ -46,6 +65,16 @@ def test_sort_not_all_sorted(self) -> None: "title": "jungle", "sequential_number": 2, }, + "list_of_speakers/42": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 222, + }, + "list_of_speakers/64": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 222, + }, "agenda_item/22": { "meeting_id": 222, "content_object_id": "topic/1", @@ -67,35 +96,21 @@ def test_sort_not_all_sorted(self) -> None: def test_sort_complex_correct(self) -> None: self.set_models( { - "topic/1": { - "meeting_id": 222, - "title": "tropic", - "sequential_number": 1, - }, - "topic/2": { - "meeting_id": 222, - "title": "jungle", - "sequential_number": 2, - }, - "topic/3": { - "meeting_id": 222, - "title": "tropic", - "sequential_number": 3, - }, - "topic/4": { - "meeting_id": 222, - "title": "jungle", - "sequential_number": 4, - }, - "topic/5": { - "meeting_id": 222, - "title": "tropic", - "sequential_number": 5, - }, - "topic/6": { - "meeting_id": 222, - "title": "jungle", - "sequential_number": 6, + **{ + f"topic/{id_}": { + "meeting_id": 222, + "title": "tropic jungle", + "sequential_number": id_, + } + for id_ in range(1, 7) + }, + **{ + f"list_of_speakers/{10 + id_}": { + "content_object_id": f"topic/{id_}", + "sequential_number": 10 + id_, + "meeting_id": 222, + } + for id_ in range(1, 7) }, "agenda_item/1": { "meeting_id": 222, @@ -170,6 +185,21 @@ def test_sort_not_a_tree(self) -> None: "title": "tropic", "sequential_number": 3, }, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 222, + }, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 222, + }, + "list_of_speakers/64": { + "content_object_id": "topic/3", + "sequential_number": 13, + "meeting_id": 222, + }, "agenda_item/1": { "meeting_id": 222, "comment": "test_root", @@ -219,6 +249,21 @@ def test_sort_circle_fail(self) -> None: "title": "tropic", "sequential_number": 3, }, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 222, + }, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 222, + }, + "list_of_speakers/64": { + "content_object_id": "topic/3", + "sequential_number": 13, + "meeting_id": 222, + }, "agenda_item/1": { "meeting_id": 222, "comment": "test_root", @@ -270,6 +315,21 @@ def test_small_tree_correct(self) -> None: "title": "tropic", "sequential_number": 3, }, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 222, + }, + "list_of_speakers/42": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 222, + }, + "list_of_speakers/64": { + "content_object_id": "topic/3", + "sequential_number": 13, + "meeting_id": 222, + }, "agenda_item/1": { "meeting_id": 222, "comment": "test_root", @@ -312,28 +372,14 @@ def test_small_tree_correct(self) -> None: def test_sort_no_permissions(self) -> None: self.base_permission_test( - { - "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - "agenda_item/22": { - "meeting_id": 1, - "comment": "test1", - "content_object_id": "topic/1", - }, - }, + self.PERMISSION_TEST_MODELS, "agenda_item.sort", {"meeting_id": 1, "tree": [{"id": 22}]}, ) def test_sort_permissions(self) -> None: self.base_permission_test( - { - "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - "agenda_item/22": { - "meeting_id": 1, - "comment": "test1", - "content_object_id": "topic/1", - }, - }, + self.PERMISSION_TEST_MODELS, "agenda_item.sort", {"meeting_id": 1, "tree": [{"id": 22}]}, Permissions.AgendaItem.CAN_MANAGE, @@ -341,14 +387,7 @@ def test_sort_permissions(self) -> None: def test_sort_permissions_locked_meeting(self) -> None: self.base_locked_out_superadmin_permission_test( - { - "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - "agenda_item/22": { - "meeting_id": 1, - "comment": "test1", - "content_object_id": "topic/1", - }, - }, + self.PERMISSION_TEST_MODELS, "agenda_item.sort", {"meeting_id": 1, "tree": [{"id": 22}]}, ) diff --git a/tests/system/action/agenda_item/test_update.py b/tests/system/action/agenda_item/test_update.py index 74e25d3eac..8fe7f02a38 100644 --- a/tests/system/action/agenda_item/test_update.py +++ b/tests/system/action/agenda_item/test_update.py @@ -14,6 +14,11 @@ def setUp(self) -> None: "title": "tropic", "meeting_id": 1, }, + "list_of_speakers/23": { + "content_object_id": "topic/102", + "sequential_number": 11, + "meeting_id": 1, + }, "agenda_item/111": { "item_number": "101", "duration": 600, @@ -56,17 +61,18 @@ def test_update_type_change_with_children(self) -> None: self.set_models( { "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, - "topic/2": {"meeting_id": 1, "title": "jungle", "sequential_number": 2}, - "agenda_item/111": { - "child_ids": [222], + "list_of_speakers/42": { "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 1, }, + "agenda_item/111": {"child_ids": [222]}, "agenda_item/222": { "type": AgendaItem.AGENDA_ITEM, "item_number": "102", "parent_id": 111, "meeting_id": 1, - "content_object_id": "topic/2", + "content_object_id": "topic/1", }, } ) @@ -105,6 +111,16 @@ def test_update_multiple_with_tag(self) -> None: { "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, "topic/2": {"meeting_id": 1, "title": "jungle", "sequential_number": 2}, + "list_of_speakers/42": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 1, + }, + "list_of_speakers/64": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 1, + }, "tag/1": { "name": "tag", "meeting_id": 1, @@ -140,6 +156,21 @@ def update_multiple_with_type_variations( "topic/1": {"meeting_id": 1, "title": "tropic", "sequential_number": 1}, "topic/2": {"meeting_id": 1, "title": "jungle", "sequential_number": 2}, "topic/3": {"meeting_id": 1, "title": "jungle", "sequential_number": 3}, + "list_of_speakers/42": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 1, + }, + "list_of_speakers/64": { + "content_object_id": "topic/2", + "sequential_number": 12, + "meeting_id": 1, + }, + "list_of_speakers/128": { + "content_object_id": "topic/3", + "sequential_number": 13, + "meeting_id": 1, + }, "agenda_item/1": { "comment": "test1", "meeting_id": 1, diff --git a/tests/system/action/assignment/test_delete.py b/tests/system/action/assignment/test_delete.py index 6a88d740b8..061192a9a7 100644 --- a/tests/system/action/assignment/test_delete.py +++ b/tests/system/action/assignment/test_delete.py @@ -3,6 +3,19 @@ class AssignmentDeleteActionTest(BaseActionTestCase): + PERMISSION_TEST_MODELS = { + "assignment/111": { + "sequential_number": 1, + "meeting_id": 1, + "title": "title_srtgb123", + }, + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 1, + }, + } + def setUp(self) -> None: super().setUp() self.create_meeting(110) @@ -14,7 +27,12 @@ def test_delete_correct(self) -> None: "sequential_number": 1, "meeting_id": 110, "title": "title_srtgb123", - } + }, + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 110, + }, } ) response = self.request("assignment.delete", {"id": 111}) @@ -66,40 +84,34 @@ def test_delete_correct_cascading(self) -> None: def test_delete_wrong_id(self) -> None: self.set_models( { - "assignment/112": { + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 110, + }, + "assignment/111": { "sequential_number": 1, "title": "title_srtgb123", "meeting_id": 110, - } + }, } ) - response = self.request("assignment.delete", {"id": 111}) + response = self.request("assignment.delete", {"id": 112}) self.assert_status_code(response, 400) - model = self.get_model("assignment/112") - self.assertEqual(model.get("title"), "title_srtgb123") + self.assert_model_exists( + "assignment/111", {"title": "title_srtgb123", "list_of_speakers_id": 23} + ) def test_delete_no_permission(self) -> None: self.base_permission_test( - { - "assignment/111": { - "sequential_number": 1, - "meeting_id": 1, - "title": "title_srtgb123", - }, - }, + self.PERMISSION_TEST_MODELS, "assignment.delete", {"id": 111}, ) def test_delete_permission(self) -> None: self.base_permission_test( - { - "assignment/111": { - "sequential_number": 1, - "meeting_id": 1, - "title": "title_srtgb123", - }, - }, + self.PERMISSION_TEST_MODELS, "assignment.delete", {"id": 111}, Permissions.Assignment.CAN_MANAGE, @@ -107,13 +119,7 @@ def test_delete_permission(self) -> None: def test_delete_permission_locked_meeting(self) -> None: self.base_locked_out_superadmin_permission_test( - { - "assignment/111": { - "sequential_number": 1, - "meeting_id": 1, - "title": "title_srtgb123", - }, - }, + self.PERMISSION_TEST_MODELS, "assignment.delete", {"id": 111}, ) diff --git a/tests/system/action/assignment/test_update.py b/tests/system/action/assignment/test_update.py index 6a7619cde4..5db7cc91ee 100644 --- a/tests/system/action/assignment/test_update.py +++ b/tests/system/action/assignment/test_update.py @@ -3,6 +3,19 @@ class AssignmentUpdateActionTest(BaseActionTestCase): + PERMISSION_TEST_MODELS = { + "assignment/111": { + "sequential_number": 1, + "title": "title_srtgb123", + "meeting_id": 1, + }, + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 1, + }, + } + def setUp(self) -> None: super().setUp() self.create_meeting() @@ -15,6 +28,11 @@ def test_update_correct(self) -> None: "title": "title_srtgb123", "meeting_id": 1, }, + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 1, + }, } ) response = self.request( @@ -35,6 +53,11 @@ def test_update_correct_full_fields(self) -> None: "title": "title_srtgb123", "meeting_id": 1, }, + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 1, + }, "meeting_mediafile/11": { "is_public": True, "mediafile_id": 1, @@ -76,7 +99,12 @@ def test_update_wrong_id(self) -> None: "title": "title_srtgb123", "sequential_number": 1, "meeting_id": 1, - } + }, + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 1, + }, }, ) response = self.request( @@ -165,26 +193,14 @@ def test_update_phase_from_voting_to_voting(self) -> None: def test_update_no_permission(self) -> None: self.base_permission_test( - { - "assignment/111": { - "sequential_number": 1, - "title": "title_srtgb123", - "meeting_id": 1, - }, - }, + self.PERMISSION_TEST_MODELS, "assignment.update", {"id": 111, "title": "title_Xcdfgee"}, ) def test_update_permission(self) -> None: self.base_permission_test( - { - "assignment/111": { - "sequential_number": 1, - "title": "title_srtgb123", - "meeting_id": 1, - }, - }, + self.PERMISSION_TEST_MODELS, "assignment.update", {"id": 111, "title": "title_Xcdfgee"}, Permissions.Assignment.CAN_MANAGE, @@ -192,13 +208,7 @@ def test_update_permission(self) -> None: def test_update_permission_locked_meeting(self) -> None: self.base_locked_out_superadmin_permission_test( - { - "assignment/111": { - "sequential_number": 1, - "title": "title_srtgb123", - "meeting_id": 1, - }, - }, + self.PERMISSION_TEST_MODELS, "assignment.update", {"id": 111, "title": "title_Xcdfgee"}, ) diff --git a/tests/system/action/assignment_candidate/test_create.py b/tests/system/action/assignment_candidate/test_create.py index 2f866693f4..0d9ed7c6cc 100644 --- a/tests/system/action/assignment_candidate/test_create.py +++ b/tests/system/action/assignment_candidate/test_create.py @@ -28,6 +28,11 @@ def setUp(self) -> None: "meeting_id": 1, "phase": "voting", }, + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 1, + }, } def test_create_count_calls(self) -> None: @@ -41,6 +46,11 @@ def test_create_count_calls(self) -> None: "title": "title_xTcEkItp", "meeting_id": 1333, }, + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 1333, + }, } ) with CountDatastoreCalls() as counter: @@ -74,6 +84,11 @@ def test_create_wrong_field(self) -> None: "title": "title_xTcEkItp", "meeting_id": 1133, }, + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 1133, + }, "meeting_user/110": {"meeting_id": 1133, "user_id": 110}, } ) @@ -106,6 +121,11 @@ def test_create_finished(self) -> None: "meeting_id": 1333, "phase": "finished", }, + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 1333, + }, } ) response = self.request( diff --git a/tests/system/action/assignment_candidate/test_delete.py b/tests/system/action/assignment_candidate/test_delete.py index 9c897d67aa..f96b881b82 100644 --- a/tests/system/action/assignment_candidate/test_delete.py +++ b/tests/system/action/assignment_candidate/test_delete.py @@ -22,6 +22,11 @@ def setUp(self) -> None: "meeting_id": 1, "phase": "voting", }, + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 1, + }, "assignment_candidate/111": { "meeting_user_id": 110, "assignment_id": 111, @@ -41,6 +46,11 @@ def test_delete_correct(self) -> None: "title": "title_xTcEkItp", "meeting_id": 1333, }, + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 1333, + }, "assignment_candidate/111": { "meeting_user_id": 110, "assignment_id": 111, @@ -63,6 +73,11 @@ def test_delete_correct_empty_user(self) -> None: "title": "title_xTcEkItp", "meeting_id": 1333, }, + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 1333, + }, "assignment_candidate/111": { "meeting_user_id": None, "assignment_id": 111, @@ -90,6 +105,11 @@ def test_delete_wrong_id(self) -> None: "title": "title_xTcEkItp", "meeting_id": 1333, }, + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 1333, + }, "assignment_candidate/112": { "meeting_user_id": 110, "assignment_id": 111, @@ -119,6 +139,11 @@ def test_delete_finished(self) -> None: "meeting_id": 1333, "phase": "finished", }, + "list_of_speakers/23": { + "content_object_id": "assignment/111", + "sequential_number": 11, + "meeting_id": 1333, + }, "assignment_candidate/111": { "meeting_user_id": 110, "assignment_id": 111, diff --git a/tests/system/action/assignment_candidate/test_sort.py b/tests/system/action/assignment_candidate/test_sort.py index fcfe67e894..09b1477d75 100644 --- a/tests/system/action/assignment_candidate/test_sort.py +++ b/tests/system/action/assignment_candidate/test_sort.py @@ -13,6 +13,11 @@ def setUp(self) -> None: "title": "title_SNLGsvIV", "meeting_id": 1, }, + "list_of_speakers/23": { + "content_object_id": "assignment/222", + "sequential_number": 11, + "meeting_id": 1, + }, "user/233": {"username": "username_233"}, "user/234": {"username": "username_234"}, "meeting_user/233": {"meeting_id": 1, "user_id": 233}, @@ -38,6 +43,11 @@ def test_sort_correct_1(self) -> None: "title": "title_SNLGsvIV", "meeting_id": 1, }, + "list_of_speakers/23": { + "content_object_id": "assignment/222", + "sequential_number": 11, + "meeting_id": 1, + }, "user/233": {"username": "username_233"}, "user/234": {"username": "username_234"}, "meeting_user/233": {"meeting_id": 1, "user_id": 233}, @@ -73,6 +83,11 @@ def test_sort_missing_model(self) -> None: "title": "title_SNLGsvIV", "meeting_id": 1, }, + "list_of_speakers/23": { + "content_object_id": "assignment/222", + "sequential_number": 11, + "meeting_id": 1, + }, "user/233": {"username": "username_233"}, "user/234": {"username": "username_234"}, "meeting_user/233": {"meeting_id": 1, "user_id": 233}, @@ -103,6 +118,11 @@ def test_sort_another_section_db(self) -> None: "title": "title_SNLGsvIV", "meeting_id": 1, }, + "list_of_speakers/23": { + "content_object_id": "assignment/222", + "sequential_number": 11, + "meeting_id": 1, + }, "user/233": {"username": "username_233"}, "user/234": {"username": "username_234"}, "user/236": {"username": "username_236"}, diff --git a/tests/system/action/assignment_candidate/test_update.py b/tests/system/action/assignment_candidate/test_update.py index 3b31a61cdd..4ec556b02b 100644 --- a/tests/system/action/assignment_candidate/test_update.py +++ b/tests/system/action/assignment_candidate/test_update.py @@ -12,6 +12,11 @@ def setUp(self) -> None: "title": "title_SNLGsvIV", "meeting_id": 1, }, + "list_of_speakers/23": { + "content_object_id": "assignment/222", + "sequential_number": 11, + "meeting_id": 1, + }, "user/233": {"username": "username_233"}, "user/234": {"username": "username_234"}, "meeting_user/233": {"meeting_id": 1, "user_id": 233}, diff --git a/tests/system/action/base.py b/tests/system/action/base.py index 56bbafafe2..b8c1a5c9a6 100644 --- a/tests/system/action/base.py +++ b/tests/system/action/base.py @@ -236,6 +236,11 @@ def create_motion( "meeting_id": meeting_id, **motion_data, }, + f"list_of_speakers/{base}": { + "content_object_id": f"motion/{base}", + "sequential_number": base, + "meeting_id": meeting_id, + }, } ) diff --git a/tests/system/action/base_generic.py b/tests/system/action/base_generic.py new file mode 100644 index 0000000000..c2a706931a --- /dev/null +++ b/tests/system/action/base_generic.py @@ -0,0 +1,74 @@ +import yaml + +from meta.dev.src.generate_sql_schema import GenerateCodeBlocks, InternalHelper +from openslides_backend.services.postgresql.db_connection_handling import ( + get_new_os_conn, +) + +from .base import BaseActionTestCase + + +class BaseGenericTestCase(BaseActionTestCase): + """Base test class meant for systematic testing of generic action classes with new abstract collections.""" + + tables_to_reset: list[str] + yml: str + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + if cls.yml: + cls.create_table_view(cls.yml) + + @classmethod + def tearDownClass(cls) -> None: + super().tearDownClass() + with get_new_os_conn() as conn: + with conn.cursor() as curs: + curs.execute( + "".join( + f"""DROP TABLE {table} CASCADE;""" + for table in cls.tables_to_reset + ) + ) + + def tearDown(self) -> None: + super().tearDown() + with self.connection.cursor() as curs: + curs.execute( + "".join( + f"""TRUNCATE TABLE {table} RESTART IDENTITY CASCADE;""" + for table in self.tables_to_reset + ) + ) + + @classmethod + def create_table_view(cls, yml: str) -> None: + GenerateCodeBlocks.models = InternalHelper.MODELS = yaml.safe_load(yml) + + ( + pre_code, + table_name_code, + view_name_code, + alter_table_code, + final_info_code, + missing_handled_attributes, + im_table_code, + create_trigger_1_1_relation_not_null_code, + create_trigger_relationlistnotnull_code, + create_trigger_unique_ids_pair_code, + create_trigger_notify_code, + errors, + ) = GenerateCodeBlocks.generate_the_code() + with get_new_os_conn() as conn: + with conn.cursor() as curs: + curs.execute( + table_name_code + + im_table_code + + view_name_code + + alter_table_code + # + create_trigger_1_1_relation_not_null_code + + create_trigger_relationlistnotnull_code + + create_trigger_unique_ids_pair_code + ) + return diff --git a/tests/system/action/list_of_speakers/test_delete.py b/tests/system/action/list_of_speakers/test_delete.py index 0a0e61362f..714aa3f235 100644 --- a/tests/system/action/list_of_speakers/test_delete.py +++ b/tests/system/action/list_of_speakers/test_delete.py @@ -19,6 +19,7 @@ def setUp(self) -> None: "sequential_number": 42, "meeting_id": 78, }, + "agenda_item/23": {"content_object_id": "topic/42", "meeting_id": 78}, "list_of_speakers/111": { "content_object_id": "topic/42", "sequential_number": 10, @@ -49,9 +50,9 @@ def test_delete_correct_id(self) -> None: response = self.request("list_of_speakers.delete", {"id": 111}) self.assert_status_code(response, 400) - self.assertEqual( + self.assertIn( + "Relation violates required constraint: Trigger tr_ud_topic_list_of_speakers_id Exception: NOT NULL CONSTRAINT VIOLATED for topic/42/list_of_speakers_id from relationship before 111/content_object_id_topic_id", response.json["message"], - "Update of topic/42: You try to set following required fields to an empty value: ['list_of_speakers_id']", ) self.assert_model_exists("list_of_speakers/111") self.assert_model_exists("projection/1") diff --git a/tests/system/action/list_of_speakers/test_delete_all_speakers.py b/tests/system/action/list_of_speakers/test_delete_all_speakers.py index d29f05d071..4fe1c2ab9b 100644 --- a/tests/system/action/list_of_speakers/test_delete_all_speakers.py +++ b/tests/system/action/list_of_speakers/test_delete_all_speakers.py @@ -13,6 +13,7 @@ def setUp(self) -> None: "sequential_number": 2, "meeting_id": 1, }, + "agenda_item/42": {"content_object_id": "topic/32", "meeting_id": 1}, "list_of_speakers/111": { "content_object_id": "topic/32", "sequential_number": 11, @@ -34,6 +35,7 @@ def test_delete_all_correct(self) -> None: "sequential_number": 2, "meeting_id": 222, }, + "agenda_item/42": {"content_object_id": "topic/32", "meeting_id": 222}, "list_of_speakers/111": { "content_object_id": "topic/32", "sequential_number": 11, diff --git a/tests/system/action/list_of_speakers/test_re_add_last.py b/tests/system/action/list_of_speakers/test_re_add_last.py index 35e2f3ec90..ef395100c3 100644 --- a/tests/system/action/list_of_speakers/test_re_add_last.py +++ b/tests/system/action/list_of_speakers/test_re_add_last.py @@ -20,6 +20,7 @@ def setUp(self) -> None: "sequential_number": 42, "meeting_id": 1, }, + "agenda_item/32": {"content_object_id": "topic/32", "meeting_id": 1}, "list_of_speakers/111": { "content_object_id": "topic/32", "sequential_number": 11, @@ -88,6 +89,7 @@ def test_correct_in_closed_list(self) -> None: "sequential_number": 43, "meeting_id": 1, }, + "agenda_item/64": {"content_object_id": "topic/42", "meeting_id": 1}, "list_of_speakers/112": { "sequential_number": 12, "content_object_id": "topic/42", @@ -121,6 +123,7 @@ def test_no_speakers(self) -> None: "sequential_number": 43, "meeting_id": 1, }, + "agenda_item/64": {"content_object_id": "topic/42", "meeting_id": 1}, "list_of_speakers/112": { "sequential_number": 12, "content_object_id": "topic/42", @@ -217,6 +220,7 @@ def test_with_interposed_question(self) -> None: "sequential_number": 43, "meeting_id": 1, }, + "agenda_item/64": {"content_object_id": "topic/42", "meeting_id": 1}, "list_of_speakers/222": { "meeting_id": 1, "sequential_number": 12, @@ -264,6 +268,7 @@ def test_with_anonymous_interposed_questions(self) -> None: "sequential_number": 43, "meeting_id": 1, }, + "agenda_item/64": {"content_object_id": "topic/42", "meeting_id": 1}, "list_of_speakers/222": { "meeting_id": 1, "sequential_number": 12, @@ -307,6 +312,7 @@ def test_with_interposed_question_error(self) -> None: "sequential_number": 43, "meeting_id": 1, }, + "agenda_item/64": {"content_object_id": "topic/42", "meeting_id": 1}, "list_of_speakers/222": { "sequential_number": 12, "content_object_id": "topic/42", diff --git a/tests/system/action/list_of_speakers/test_update.py b/tests/system/action/list_of_speakers/test_update.py index 107cfdd685..a26c5f729d 100644 --- a/tests/system/action/list_of_speakers/test_update.py +++ b/tests/system/action/list_of_speakers/test_update.py @@ -1,5 +1,3 @@ -from typing import Any - from openslides_backend.permissions.permissions import Permissions from tests.system.action.base import BaseActionTestCase @@ -7,37 +5,25 @@ class ListOfSpeakersUpdateActionTest(BaseActionTestCase): def setUp(self) -> None: super().setUp() - self.permission_test_models: dict[str, dict[str, Any]] = { - "topic/42": { - "title": "leet improvement discussion", - "sequential_number": 42, - "meeting_id": 1, - }, - "list_of_speakers/111": { - "content_object_id": "topic/42", - "sequential_number": 10, - "closed": False, - "meeting_id": 1, - }, - } - - def test_update_correct(self) -> None: - self.create_meeting(222) + self.create_meeting() self.set_models( { - "topic/32": { + "topic/42": { "title": "leet improvement discussion", - "sequential_number": 2, - "meeting_id": 222, + "sequential_number": 42, + "meeting_id": 1, }, + "agenda_item/32": {"content_object_id": "topic/42", "meeting_id": 1}, "list_of_speakers/111": { - "content_object_id": "topic/32", + "content_object_id": "topic/42", "sequential_number": 10, "closed": False, - "meeting_id": 222, + "meeting_id": 1, }, } ) + + def test_update_correct(self) -> None: response = self.request("list_of_speakers.update", {"id": 111, "closed": True}) self.assert_status_code(response, 200) @@ -45,23 +31,6 @@ def test_update_correct(self) -> None: assert model.get("closed") is True def test_update_wrong_id(self) -> None: - self.create_meeting(222) - self.set_models( - { - "topic/32": { - "title": "leet improvement discussion", - "sequential_number": 42, - "meeting_id": 222, - }, - "list_of_speakers/111": { - "content_object_id": "topic/32", - "sequential_number": 10, - "closed": False, - "meeting_id": 222, - }, - } - ) - response = self.request("list_of_speakers.update", {"id": 112, "closed": True}) self.assert_status_code(response, 400) model = self.get_model("list_of_speakers/111") @@ -69,14 +38,14 @@ def test_update_wrong_id(self) -> None: def test_update_no_permissions(self) -> None: self.base_permission_test( - self.permission_test_models, + {}, "list_of_speakers.update", {"id": 111, "closed": True}, ) def test_update_permissions(self) -> None: self.base_permission_test( - self.permission_test_models, + {}, "list_of_speakers.update", {"id": 111, "closed": True}, Permissions.ListOfSpeakers.CAN_MANAGE, @@ -84,14 +53,14 @@ def test_update_permissions(self) -> None: def test_update_permissions_locked_meeting(self) -> None: self.base_locked_out_superadmin_permission_test( - self.permission_test_models, + {}, "list_of_speakers.update", {"id": 111, "closed": True}, ) def test_update_moderator_notes_no_permissions(self) -> None: self.base_permission_test( - self.permission_test_models, + {}, "list_of_speakers.update", {"id": 111, "moderator_notes": "test"}, Permissions.ListOfSpeakers.CAN_MANAGE, @@ -100,7 +69,7 @@ def test_update_moderator_notes_no_permissions(self) -> None: def test_update_moderator_notes_permissions(self) -> None: self.base_permission_test( - self.permission_test_models, + {}, "list_of_speakers.update", {"id": 111, "moderator_notes": "test"}, Permissions.ListOfSpeakers.CAN_MANAGE_MODERATOR_NOTES, diff --git a/tests/system/action/meeting_mediafile/test_delete.py b/tests/system/action/meeting_mediafile/test_delete.py index 5f1776f11d..8854fbe3dd 100644 --- a/tests/system/action/meeting_mediafile/test_delete.py +++ b/tests/system/action/meeting_mediafile/test_delete.py @@ -54,6 +54,12 @@ def test_delete_complex(self) -> None: "sequential_number": 5, "title": "pic me", }, + "agenda_item/6": {"content_object_id": "topic/5", "meeting_id": 1}, + "list_of_speakers/7": { + "content_object_id": "topic/5", + "sequential_number": 1, + "meeting_id": 1, + }, } ) response = self.request("meeting_mediafile.delete", {"id": 2}) diff --git a/tests/system/action/meeting_user/test_delete.py b/tests/system/action/meeting_user/test_delete.py index e207707992..8d57c5433b 100644 --- a/tests/system/action/meeting_user/test_delete.py +++ b/tests/system/action/meeting_user/test_delete.py @@ -28,6 +28,7 @@ def test_delete_with_speaker(self) -> None: "sequential_number": 11, "meeting_id": 10, }, + "agenda_item/6": {"content_object_id": "topic/11", "meeting_id": 101}, "list_of_speakers/11": { "sequential_number": 11, "content_object_id": "topic/11", diff --git a/tests/system/action/motion/test_create.py b/tests/system/action/motion/test_create.py index 3873d963e4..867e4ccea9 100644 --- a/tests/system/action/motion/test_create.py +++ b/tests/system/action/motion/test_create.py @@ -72,6 +72,11 @@ def test_create_simple_fields(self) -> None: "meeting_id": 1, "sequential_number": 78, }, + "list_of_speakers/23": { + "content_object_id": "motion_block/78", + "sequential_number": 11, + "meeting_id": 1, + }, "tag/56": {"name": "name_56", "meeting_id": 1}, "meeting_mediafile/80": { "meeting_id": 1, diff --git a/tests/system/action/motion/test_create_amendment.py b/tests/system/action/motion/test_create_amendment.py index cadc6cb8b1..f8896b1147 100644 --- a/tests/system/action/motion/test_create_amendment.py +++ b/tests/system/action/motion/test_create_amendment.py @@ -26,6 +26,11 @@ def setUp(self) -> None: "title": "Block 13", "sequential_number": 1, }, + "list_of_speakers/42": { + "content_object_id": "motion_block/13", + "sequential_number": 11, + "meeting_id": 1, + }, } ) self.default_action_data = { diff --git a/tests/system/action/motion/test_delete.py b/tests/system/action/motion/test_delete.py index 37a991851e..62ee14814c 100644 --- a/tests/system/action/motion/test_delete.py +++ b/tests/system/action/motion/test_delete.py @@ -23,6 +23,16 @@ def setUp(self) -> None: "lead_motion_id": 111, "sequential_number": 222, }, + "list_of_speakers/23": { + "content_object_id": "motion/112", + "sequential_number": 11, + "meeting_id": 1, + }, + "list_of_speakers/42": { + "content_object_id": "motion/222", + "sequential_number": 12, + "meeting_id": 1, + }, "motion_state/1": {"allow_submitter_edit": True}, "motion_submitter/12": { "meeting_id": 1, @@ -87,12 +97,7 @@ def test_delete_correct_cascading(self) -> None: ) self.set_models( { - "list_of_speakers/222": { - "closed": False, - "content_object_id": "motion/111", - "meeting_id": 1, - "sequential_number": 222, - }, + "list_of_speakers/111": {"closed": False}, "agenda_item/333": { "comment": "test_comment_ewoirzewoirioewr", "content_object_id": "motion/111", diff --git a/tests/system/action/motion/test_sort.py b/tests/system/action/motion/test_sort.py index a15f25a506..f9bd88210d 100644 --- a/tests/system/action/motion/test_sort.py +++ b/tests/system/action/motion/test_sort.py @@ -15,6 +15,11 @@ def setUp(self) -> None: "state_id": 1, "meeting_id": 1, }, + "list_of_speakers/23": { + "content_object_id": "motion/22", + "sequential_number": 11, + "meeting_id": 1, + }, } def test_sort_single_node_correct(self) -> None: diff --git a/tests/system/action/motion/test_update.py b/tests/system/action/motion/test_update.py index 073e32d01d..dd01005997 100644 --- a/tests/system/action/motion/test_update.py +++ b/tests/system/action/motion/test_update.py @@ -28,6 +28,11 @@ def setUp(self) -> None: "modified_final_version": "blablabla", "amendment_paragraphs": Jsonb({"3": "testtesttest"}), }, + "list_of_speakers/23": { + "content_object_id": "motion/111", + "sequential_number": 11, + "meeting_id": 1, + }, "meeting_user/1": { "meeting_id": 1, "user_id": 1, @@ -206,7 +211,11 @@ def test_update_correct_2(self) -> None: "meeting_id": 1, "title": "title_ddyvpXch", "sequential_number": 51, - "list_of_speakers_id": 1, + }, + "list_of_speakers/23": { + "content_object_id": "motion_block/51", + "sequential_number": 11, + "meeting_id": 1, }, } ) @@ -347,7 +356,11 @@ def test_update_metadata_missing_motion(self) -> None: "title": "title_ddyvpXch", "meeting_id": 1, "sequential_number": 51, - "list_of_speakers_id": 1, + }, + "list_of_speakers/23": { + "content_object_id": "motion_block/51", + "sequential_number": 11, + "meeting_id": 1, }, } ) @@ -576,7 +589,11 @@ def test_update_permission_metadata_allowed(self) -> None: "meeting_id": 1, "title": "blocky", "sequential_number": 4, - "list_of_speakers_id": 1, + }, + "list_of_speakers/23": { + "content_object_id": "motion_block/4", + "sequential_number": 11, + "meeting_id": 1, }, "tag/3": {"meeting_id": 1, "name": "bla"}, } @@ -630,6 +647,7 @@ def test_update_permission_submitter_allowed(self) -> None: def test_update_permission_metadata_and_submitter(self) -> None: self.setup_can_manage_metadata() self.permission_test_models["meeting_user/1"] = {"motion_submitter_ids": [1]} + del self.permission_test_models["list_of_speakers/23"] self.set_models(self.permission_test_models) self.set_models( { diff --git a/tests/system/action/motion_category/test_update.py b/tests/system/action/motion_category/test_update.py index c13ec23416..bf5f42e2b5 100644 --- a/tests/system/action/motion_category/test_update.py +++ b/tests/system/action/motion_category/test_update.py @@ -1,5 +1,3 @@ -from typing import Any - from openslides_backend.permissions.permissions import Permissions from tests.system.action.base import BaseActionTestCase @@ -18,17 +16,23 @@ def setUp(self) -> None: } } ) - self.permission_test_models: dict[str, dict[str, Any]] = { - "motion/89": { - "title": "motion 89", - "meeting_id": 1, - "state_id": 1, - "sequential_number": 89, - }, - } def test_update_correct_all_fields(self) -> None: - self.set_models(self.permission_test_models) + self.set_models( + { + "motion/89": { + "title": "motion 89", + "meeting_id": 1, + "state_id": 1, + "sequential_number": 89, + }, + "list_of_speakers/23": { + "content_object_id": "motion/89", + "sequential_number": 11, + "meeting_id": 1, + }, + } + ) response = self.request( "motion_category.update", { @@ -90,14 +94,14 @@ def test_update_non_unique_prefix(self) -> None: def test_update_no_permission(self) -> None: self.base_permission_test( - self.permission_test_models, + {}, "motion_category.update", {"id": 111, "name": "name_Xcdfgee"}, ) def test_update_permission(self) -> None: self.base_permission_test( - self.permission_test_models, + {}, "motion_category.update", {"id": 111, "name": "name_Xcdfgee"}, Permissions.Motion.CAN_MANAGE, @@ -105,7 +109,7 @@ def test_update_permission(self) -> None: def test_update_permission_locked_meeting(self) -> None: self.base_locked_out_superadmin_permission_test( - self.permission_test_models, + {}, "motion_category.update", {"id": 111, "name": "name_Xcdfgee"}, ) diff --git a/tests/system/action/option/test_create.py b/tests/system/action/option/test_create.py index 9cfc2692af..61e22bb15d 100644 --- a/tests/system/action/option/test_create.py +++ b/tests/system/action/option/test_create.py @@ -35,6 +35,11 @@ def test_create_with_both_text_and_content_object_id(self) -> None: "state_id": 111, "meeting_id": 111, }, + "list_of_speakers/23": { + "content_object_id": "motion/112", + "sequential_number": 11, + "meeting_id": 111, + }, } ) response = self.request( diff --git a/tests/system/action/option/test_delete.py b/tests/system/action/option/test_delete.py index 1599edfe91..3b6088c6e3 100644 --- a/tests/system/action/option/test_delete.py +++ b/tests/system/action/option/test_delete.py @@ -5,15 +5,10 @@ class OptionDeleteTest(BaseActionTestCase): def setUp(self) -> None: super().setUp() self.create_meeting() + self.create_motion(1) self.set_models( { "option/111": {"meeting_id": 1, "content_object_id": "motion/1"}, - "motion/1": { - "sequential_number": 1, - "title": "moshen", - "state_id": 1, - "meeting_id": 1, - }, } ) diff --git a/tests/system/action/option/test_update.py b/tests/system/action/option/test_update.py index acb6e056cd..b4d336880e 100644 --- a/tests/system/action/option/test_update.py +++ b/tests/system/action/option/test_update.py @@ -28,6 +28,12 @@ def setUp(self) -> None: "sequential_number": 1, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1", "meeting_id": 1}, + "list_of_speakers/23": { + "content_object_id": "topic/1", + "sequential_number": 11, + "meeting_id": 1, + }, "poll/65": { "title": "pool", "sequential_number": 1, diff --git a/tests/system/action/projection/test_create.py b/tests/system/action/projection/test_create.py index 5c9f846c68..c8a55ea73a 100644 --- a/tests/system/action/projection/test_create.py +++ b/tests/system/action/projection/test_create.py @@ -11,6 +11,11 @@ def test_create(self) -> None: "sequential_number": 1, "meeting_id": 1, }, + "list_of_speakers/23": { + "content_object_id": "assignment/1", + "sequential_number": 11, + "meeting_id": 1, + }, } ) response = self.request( diff --git a/tests/system/action/projector/test_add_to_preview.py b/tests/system/action/projector/test_add_to_preview.py index 87b6ae0637..46fe416d07 100644 --- a/tests/system/action/projector/test_add_to_preview.py +++ b/tests/system/action/projector/test_add_to_preview.py @@ -14,6 +14,11 @@ def setUp(self) -> None: "title": "test assignment", "sequential_number": 1, }, + "list_of_speakers/23": { + "content_object_id": "assignment/1", + "sequential_number": 11, + "meeting_id": 1, + }, "projector/2": {"meeting_id": 1, "sequential_number": 2}, "projector/3": {"meeting_id": 1, "sequential_number": 3}, "projection/10": { diff --git a/tests/system/action/projector/test_project.py b/tests/system/action/projector/test_project.py index 3b914a7e14..bb5c11b3f3 100644 --- a/tests/system/action/projector/test_project.py +++ b/tests/system/action/projector/test_project.py @@ -47,6 +47,16 @@ def setUp(self) -> None: "title": "assignment 453", "sequential_number": 453, }, + "list_of_speakers/23": { + "content_object_id": "assignment/452", + "sequential_number": 11, + "meeting_id": 1, + }, + "list_of_speakers/42": { + "content_object_id": "assignment/453", + "sequential_number": 12, + "meeting_id": 1, + }, } ) diff --git a/tests/system/action/speaker/test_create.py b/tests/system/action/speaker/test_create.py index f60e8b91a0..dd3c6ab201 100644 --- a/tests/system/action/speaker/test_create.py +++ b/tests/system/action/speaker/test_create.py @@ -27,6 +27,7 @@ def setUp(self) -> None: }, "meeting_user/17": {"meeting_id": 1, "user_id": 7}, "topic/1337": {"title": "leet", "sequential_number": 1337, "meeting_id": 1}, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", @@ -243,6 +244,7 @@ def test_create_add_2_speakers_in_1_action(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", @@ -291,6 +293,7 @@ def test_create_add_2_speakers_in_2_actions(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", @@ -347,6 +350,7 @@ def test_create_user_present(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", @@ -387,6 +391,7 @@ def test_create_user_not_present(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", @@ -434,6 +439,7 @@ def test_create_standard_speaker_in_only_talker_list(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", @@ -513,6 +519,7 @@ def test_create_standard_speaker_at_the_end_of_filled_list(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", @@ -550,6 +557,7 @@ def test_create_not_in_meeting(self) -> None: "sequential_number": 1337, "meeting_id": 4, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", @@ -830,6 +838,7 @@ def test_create_category_weights_with_ranks(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", @@ -907,6 +916,7 @@ def test_create_category_key_error_problem(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", diff --git a/tests/system/action/speaker/test_create_point_of_order.py b/tests/system/action/speaker/test_create_point_of_order.py index cb3e2572ae..befe9f35c3 100644 --- a/tests/system/action/speaker/test_create_point_of_order.py +++ b/tests/system/action/speaker/test_create_point_of_order.py @@ -28,6 +28,10 @@ def test_create_poo_in_only_talker_list(self) -> None: "sequential_number": 1337, "meeting_id": 7844, }, + "agenda_item/1": { + "content_object_id": "topic/1337", + "meeting_id": 7844, + }, "list_of_speakers/23": { "sequential_number": 23, "speaker_ids": [1], @@ -119,6 +123,10 @@ def test_create_poo_after_existing_poo_before_standard(self) -> None: "sequential_number": 1337, "meeting_id": 7844, }, + "agenda_item/1": { + "content_object_id": "topic/1337", + "meeting_id": 7844, + }, "list_of_speakers/23": { "sequential_number": 23, "speaker_ids": [1, 2, 3], @@ -225,6 +233,10 @@ def test_create_poo_after_existing_poo_before_standard_and_more(self) -> None: "sequential_number": 1337, "meeting_id": 7844, }, + "agenda_item/1": { + "content_object_id": "topic/1337", + "meeting_id": 7844, + }, "list_of_speakers/23": { "sequential_number": 23, "speaker_ids": [1, 2, 3, 4], @@ -321,6 +333,10 @@ def test_create_poo_after_existing_poo_at_the_end(self) -> None: "sequential_number": 1337, "meeting_id": 7844, }, + "agenda_item/1": { + "content_object_id": "topic/1337", + "meeting_id": 7844, + }, "list_of_speakers/23": { "sequential_number": 23, "speaker_ids": [1], @@ -370,6 +386,10 @@ def test_create_poo_already_exist(self) -> None: "sequential_number": 1337, "meeting_id": 7844, }, + "agenda_item/1": { + "content_object_id": "topic/1337", + "meeting_id": 7844, + }, "list_of_speakers/23": { "sequential_number": 23, "speaker_ids": [42], @@ -410,6 +430,10 @@ def test_create_poo_not_activated_in_meeting(self) -> None: "sequential_number": 1337, "meeting_id": 7844, }, + "agenda_item/1": { + "content_object_id": "topic/1337", + "meeting_id": 7844, + }, "list_of_speakers/23": { "sequential_number": 23, "speaker_ids": [], @@ -446,6 +470,10 @@ def test_create_poo_without_user_id(self) -> None: "sequential_number": 1337, "meeting_id": 7844, }, + "agenda_item/1": { + "content_object_id": "topic/1337", + "meeting_id": 7844, + }, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", @@ -484,6 +512,10 @@ def setup_create_poo_for_other_user(self, allow: bool = False) -> None: "sequential_number": 1337, "meeting_id": 7844, }, + "agenda_item/1": { + "content_object_id": "topic/1337", + "meeting_id": 7844, + }, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", diff --git a/tests/system/action/speaker/test_delete.py b/tests/system/action/speaker/test_delete.py index b9cf8e7bff..565037ed20 100644 --- a/tests/system/action/speaker/test_delete.py +++ b/tests/system/action/speaker/test_delete.py @@ -34,6 +34,7 @@ def setUp(self) -> None: "content_object_id": "topic/1337", "meeting_id": 1, }, + "agenda_item/8": {"meeting_id": 1, "content_object_id": "topic/1337"}, "speaker/890": { "meeting_user_id": 7, "list_of_speakers_id": 23, @@ -62,6 +63,7 @@ def test_delete_correct(self) -> None: "content_object_id": "topic/1337", "meeting_id": 111, }, + "agenda_item/8": {"meeting_id": 111, "content_object_id": "topic/1337"}, "speaker/890": { "meeting_user_id": 7, "list_of_speakers_id": 23, @@ -90,6 +92,7 @@ def test_delete_wrong_id(self) -> None: "sequential_number": 1337, "meeting_id": 111, }, + "agenda_item/8": {"meeting_id": 111, "content_object_id": "topic/1337"}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", @@ -157,6 +160,7 @@ def test_delete_correct_on_closed_los(self) -> None: "content_object_id": "topic/1337", "meeting_id": 111, }, + "agenda_item/8": {"meeting_id": 111, "content_object_id": "topic/1337"}, "speaker/890": { "meeting_user_id": 7, "list_of_speakers_id": 23, @@ -189,6 +193,7 @@ def test_delete_with_removed_user(self) -> None: "content_object_id": "topic/1337", "meeting_id": 111, }, + "agenda_item/8": {"meeting_id": 111, "content_object_id": "topic/1337"}, "speaker/890": { "meeting_user_id": 7, "list_of_speakers_id": 23, @@ -215,6 +220,7 @@ def test_delete_with_deleted_user(self) -> None: "content_object_id": "topic/1337", "meeting_id": 111, }, + "agenda_item/8": {"meeting_id": 111, "content_object_id": "topic/1337"}, "speaker/890": { "list_of_speakers_id": 23, "meeting_id": 111, @@ -323,6 +329,7 @@ def test_with_active_structure_level_speaker(self) -> None: "sequential_number": 1337, "meeting_id": 111, }, + "agenda_item/8": {"meeting_id": 111, "content_object_id": "topic/1337"}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", @@ -392,6 +399,7 @@ def test_with_paused_structure_level_speaker(self) -> None: "content_object_id": "topic/1337", "meeting_id": 111, }, + "agenda_item/8": {"meeting_id": 111, "content_object_id": "topic/1337"}, "speaker/890": { "meeting_user_id": 7, "list_of_speakers_id": 23, @@ -447,7 +455,7 @@ def add_coupled_countdown(self) -> int: "countdown_time": now + 100, "meeting_id": 1, }, - "speaker/890": {"begin_time": now + 100}, + "speaker/890": {"begin_time": datetime.fromtimestamp(now + 100)}, } ) return now @@ -495,6 +503,4 @@ def test_delete_dont_update_countdown(self) -> None: "meeting_id": 1, }, ) - self.assertAlmostEqual( - countdown["countdown_time"], datetime.fromtimestamp(now), delta=200 - ) + self.assertAlmostEqual(countdown["countdown_time"], now, delta=200) diff --git a/tests/system/action/speaker/test_end_speech.py b/tests/system/action/speaker/test_end_speech.py index e5daae15d7..524fc16a81 100644 --- a/tests/system/action/speaker/test_end_speech.py +++ b/tests/system/action/speaker/test_end_speech.py @@ -31,6 +31,7 @@ def setUp(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", diff --git a/tests/system/action/speaker/test_pause.py b/tests/system/action/speaker/test_pause.py index 73dc0629ea..c459aa39dc 100644 --- a/tests/system/action/speaker/test_pause.py +++ b/tests/system/action/speaker/test_pause.py @@ -21,6 +21,7 @@ def setUp(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", diff --git a/tests/system/action/speaker/test_sort.py b/tests/system/action/speaker/test_sort.py index 48da680d1f..57fddb31e3 100644 --- a/tests/system/action/speaker/test_sort.py +++ b/tests/system/action/speaker/test_sort.py @@ -13,6 +13,7 @@ def setUp(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/222": { "sequential_number": 222, "content_object_id": "topic/1337", @@ -31,6 +32,7 @@ def test_sort_correct_1(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/222": { "sequential_number": 222, "content_object_id": "topic/1337", @@ -58,6 +60,7 @@ def test_sort_missing_model(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/222": { "sequential_number": 222, "content_object_id": "topic/1337", @@ -84,6 +87,7 @@ def test_sort_another_section_db(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/222": { "sequential_number": 222, "content_object_id": "topic/1337", diff --git a/tests/system/action/speaker/test_speak.py b/tests/system/action/speaker/test_speak.py index 0f980d64a9..ab0961336e 100644 --- a/tests/system/action/speaker/test_speak.py +++ b/tests/system/action/speaker/test_speak.py @@ -21,6 +21,7 @@ def setUp(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", diff --git a/tests/system/action/speaker/test_unpause.py b/tests/system/action/speaker/test_unpause.py index 2f254691e9..f644035b80 100644 --- a/tests/system/action/speaker/test_unpause.py +++ b/tests/system/action/speaker/test_unpause.py @@ -21,6 +21,7 @@ def setUp(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", diff --git a/tests/system/action/speaker/test_update.py b/tests/system/action/speaker/test_update.py index 4ffdbea85e..bf3df6dc14 100644 --- a/tests/system/action/speaker/test_update.py +++ b/tests/system/action/speaker/test_update.py @@ -22,6 +22,7 @@ def setUp(self) -> None: "sequential_number": 1337, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, "list_of_speakers/23": { "sequential_number": 23, "content_object_id": "topic/1337", diff --git a/tests/system/action/structure_level_list_of_speakers/test_add_time.py b/tests/system/action/structure_level_list_of_speakers/test_add_time.py index 41f5501189..cf8f842bd4 100644 --- a/tests/system/action/structure_level_list_of_speakers/test_add_time.py +++ b/tests/system/action/structure_level_list_of_speakers/test_add_time.py @@ -24,6 +24,8 @@ def setUp(self) -> None: "sequential_number": 42, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/32", "meeting_id": 1}, + "agenda_item/2": {"content_object_id": "topic/42", "meeting_id": 1}, "list_of_speakers/1": { "meeting_id": 1, "sequential_number": 1, diff --git a/tests/system/action/structure_level_list_of_speakers/test_create.py b/tests/system/action/structure_level_list_of_speakers/test_create.py index 4a3a3c9237..1bfe6e8a40 100644 --- a/tests/system/action/structure_level_list_of_speakers/test_create.py +++ b/tests/system/action/structure_level_list_of_speakers/test_create.py @@ -17,6 +17,7 @@ def setUp(self) -> None: "sequential_number": 32, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/32", "meeting_id": 1}, "structure_level/1": {"meeting_id": 1, "name": "d."}, "list_of_speakers/2": { "meeting_id": 1, diff --git a/tests/system/action/structure_level_list_of_speakers/test_delete.py b/tests/system/action/structure_level_list_of_speakers/test_delete.py index 18c55ddf6f..f871733998 100644 --- a/tests/system/action/structure_level_list_of_speakers/test_delete.py +++ b/tests/system/action/structure_level_list_of_speakers/test_delete.py @@ -14,6 +14,7 @@ def test_delete(self) -> None: "sequential_number": 32, "meeting_id": 1, }, + "agenda_item/8": {"meeting_id": 1, "content_object_id": "topic/32"}, "structure_level/1": { "name": "monkey", "meeting_id": 1, diff --git a/tests/system/action/structure_level_list_of_speakers/test_update.py b/tests/system/action/structure_level_list_of_speakers/test_update.py index 7d65e479f7..dd2a2efd2d 100644 --- a/tests/system/action/structure_level_list_of_speakers/test_update.py +++ b/tests/system/action/structure_level_list_of_speakers/test_update.py @@ -26,6 +26,8 @@ def setUp(self) -> None: "sequential_number": 42, "meeting_id": 1, }, + "agenda_item/1": {"content_object_id": "topic/1337", "meeting_id": 1}, + "agenda_item/2": {"content_object_id": "topic/42", "meeting_id": 1}, "structure_level/1": { "name": "cake party", "meeting_id": 1, diff --git a/tests/system/action/test_action_command_format.py b/tests/system/action/test_action_command_format.py index e0b2e154fd..5db12bbb95 100644 --- a/tests/system/action/test_action_command_format.py +++ b/tests/system/action/test_action_command_format.py @@ -2,6 +2,10 @@ from openslides_backend.action.action_handler import ActionHandler from openslides_backend.action.util.typing import Payload +from openslides_backend.services.database.extended_database import ExtendedDatabase +from openslides_backend.services.postgresql.db_connection_handling import ( + get_new_os_conn, +) from .base import BaseActionTestCase @@ -12,6 +16,10 @@ class GeneralActionCommandFormat(BaseActionTestCase): per payload-action and it's own locked_fields """ + def setUp(self) -> None: + super().setUp() + self.create_meeting() + def get_action_handler(self) -> ActionHandler: logger = Mock() config = Mock() @@ -21,7 +29,6 @@ def get_action_handler(self) -> ActionHandler: return handler def test_parse_actions_create_2_actions(self) -> None: - self.create_meeting() payload: Payload = [ { "action": "group.create", @@ -44,30 +51,33 @@ def test_parse_actions_create_2_actions(self) -> None: ] action_handler = self.get_action_handler() - write_requests, _ = action_handler.parse_actions(payload) + with get_new_os_conn() as conn: + ex_db = ExtendedDatabase(conn, action_handler.logging, action_handler.env) + action_handler.datastore = ex_db + write_requests, _ = action_handler.parse_actions(payload) self.assertEqual(len(write_requests), 2) self.assertEqual(len(write_requests[0].events), 2) - self.assertCountEqual( - write_requests[0].locked_fields.keys(), - [ - "group/weight", - "meeting/1/group_ids", - ], - ) + # TODO later: either delete commented out code or implement locked fields + # self.assertCountEqual( + # write_requests[0].locked_fields.keys(), + # [ + # "group/weight", + # "meeting/1/group_ids", + # ], + # ) self.assertEqual(write_requests[0].events[0]["type"], "create") self.assertEqual(write_requests[0].events[1]["type"], "update") self.assertEqual(str(write_requests[0].events[0]["fqid"]), "group/4") self.assertEqual(str(write_requests[0].events[1]["fqid"]), "meeting/1") self.assertEqual(len(write_requests[1].events), 2) - self.assertCountEqual( - write_requests[1].locked_fields.keys(), - [ - "group/weight", - ], - ) + # self.assertCountEqual( + # write_requests[1].locked_fields.keys(), + # [ + # "group/weight", + # ], + # ) def test_parse_actions_create_1_2_events(self) -> None: - self.create_meeting() payload: Payload = [ { "action": "group.create", @@ -85,16 +95,20 @@ def test_parse_actions_create_1_2_events(self) -> None: ] action_handler = self.get_action_handler() - write_requests, _ = action_handler.parse_actions(payload) + with get_new_os_conn() as conn: + ex_db = ExtendedDatabase(conn, action_handler.logging, action_handler.env) + action_handler.datastore = ex_db + write_requests, _ = action_handler.parse_actions(payload) self.assertEqual(len(write_requests), 1) self.assertEqual(len(write_requests[0].events), 3) - self.assertCountEqual( - write_requests[0].locked_fields.keys(), - [ - "group/weight", - "meeting/1/group_ids", - ], - ) + # TODO later: either delete commented out code or implement locked fields + # self.assertCountEqual( + # write_requests[0].locked_fields.keys(), + # [ + # "group/weight", + # "meeting/1/group_ids", + # ], + # ) self.assertEqual(write_requests[0].events[0]["type"], "create") self.assertEqual(write_requests[0].events[1]["type"], "create") self.assertEqual(write_requests[0].events[2]["type"], "update") @@ -103,7 +117,6 @@ def test_parse_actions_create_1_2_events(self) -> None: self.assertEqual(str(write_requests[0].events[2]["fqid"]), "meeting/1") def test_create_2_actions(self) -> None: - self.create_meeting() response = self.request_json( [ { @@ -126,17 +139,12 @@ def test_create_2_actions(self) -> None: }, ], ) - self.assert_status_code(response, 400) - self.assertIn( - "Datastore service sends HTTP 400. The following locks were broken: 'group/weight'", - response.json["message"], - ) - self.assert_model_not_exists("group/4") - self.assert_model_not_exists("group/5") - self.assert_model_exists("meeting/1", {"group_ids": [1, 2, 3]}) + self.assert_status_code(response, 200) + self.assert_model_exists("group/4", {"name": "group 1", "weight": 1}) + self.assert_model_exists("group/5", {"name": "group 2", "weight": 2}) + self.assert_model_exists("meeting/1", {"group_ids": [1, 2, 3, 4, 5]}) def test_create_1_2_events(self) -> None: - self.create_meeting() response = self.request_multi( "group.create", [ @@ -155,165 +163,57 @@ def test_create_1_2_events(self) -> None: self.assert_model_exists("group/5", {"name": "group 2", "meeting_id": 1}) def test_update_2_actions(self) -> None: - self.set_models( - { - "meeting/1": { - "name": "name1", - "committee_id": 1, - "welcome_title": "t", - "is_active_in_organization_id": 1, - "language": "en", - }, - "meeting/2": { - "name": "name2", - "committee_id": 1, - "welcome_title": "t", - "is_active_in_organization_id": 1, - "language": "en", - }, - "committee/1": {"name": "test_committee"}, - } - ) + self.create_meeting(4) response = self.request_json( [ { "action": "meeting.update", - "data": [ - { - "id": 1, - "name": "name1_updated", - } - ], + "data": [{"id": 1, "name": "name1_updated"}], }, { "action": "meeting.update", - "data": [ - { - "id": 2, - "name": "name2_updated", - } - ], + "data": [{"id": 4, "name": "name2_updated"}], }, ], ) self.assert_status_code(response, 200) meeting1 = self.get_model("meeting/1") assert meeting1.get("name") == "name1_updated" - meeting2 = self.get_model("meeting/2") + meeting2 = self.get_model("meeting/4") assert meeting2.get("name") == "name2_updated" def test_update_1_2_events(self) -> None: - self.set_models( - { - "meeting/1": { - "name": "name1", - "committee_id": 1, - "welcome_title": "t", - "is_active_in_organization_id": 1, - "language": "en", - }, - "meeting/2": { - "name": "name2", - "committee_id": 1, - "welcome_title": "t", - "is_active_in_organization_id": 1, - "language": "en", - }, - "committee/1": {"name": "test_committee"}, - } - ) + self.create_meeting(4) response = self.request_multi( "meeting.update", [ - { - "id": 1, - "name": "name1_updated", - }, - { - "id": 2, - "name": "name2_updated", - }, + {"id": 1, "name": "name1_updated"}, + {"id": 4, "name": "name2_updated"}, ], ) self.assert_status_code(response, 200) meeting1 = self.get_model("meeting/1") assert meeting1.get("name") == "name1_updated" - meeting2 = self.get_model("meeting/2") + meeting2 = self.get_model("meeting/4") assert meeting2.get("name") == "name2_updated" def test_delete_2_actions(self) -> None: - self.set_models( - { - "meeting/1": { - "name": "name1", - "committee_id": 1, - "welcome_title": "t", - "is_active_in_organization_id": 1, - }, - "meeting/2": { - "name": "name2", - "committee_id": 1, - "welcome_title": "t", - "is_active_in_organization_id": 1, - }, - "committee/1": {"name": "test_committee", "meeting_ids": [1, 2]}, - } - ) + self.create_meeting(4) response = self.request_json( [ - { - "action": "meeting.delete", - "data": [ - { - "id": 1, - } - ], - }, - { - "action": "meeting.delete", - "data": [ - { - "id": 2, - } - ], - }, + {"action": "meeting.delete", "data": [{"id": 1}]}, + {"action": "meeting.delete", "data": [{"id": 4}]}, ], ) self.assert_status_code(response, 200) self.assert_model_not_exists("meeting/1") - self.assert_model_not_exists("meeting/2") - self.assert_model_exists("committee/1", {"meeting_ids": []}) + self.assert_model_not_exists("meeting/4") + self.assert_model_exists("committee/60", {"meeting_ids": None}) def test_delete_1_2_events(self) -> None: - self.set_models( - { - "meeting/1": { - "name": "name1", - "committee_id": 1, - "welcome_title": "t", - "is_active_in_organization_id": 1, - }, - "meeting/2": { - "name": "name2", - "committee_id": 1, - "welcome_title": "t", - "is_active_in_organization_id": 1, - }, - "committee/1": {"name": "test_committee", "meeting_ids": [1, 2]}, - } - ) - response = self.request_multi( - "meeting.delete", - [ - { - "id": 1, - }, - { - "id": 2, - }, - ], - ) + self.create_meeting(4) + response = self.request_multi("meeting.delete", [{"id": 1}, {"id": 4}]) self.assert_status_code(response, 200) self.assert_model_not_exists("meeting/1") - self.assert_model_not_exists("meeting/2") - self.assert_model_exists("committee/1", {"meeting_ids": []}) + self.assert_model_not_exists("meeting/4") + self.assert_model_exists("committee/60", {"meeting_ids": None}) diff --git a/tests/system/action/test_action_worker.py b/tests/system/action/test_action_worker.py index 282c9161a3..4b5fecc810 100644 --- a/tests/system/action/test_action_worker.py +++ b/tests/system/action/test_action_worker.py @@ -19,20 +19,7 @@ class ActionWorkerTest(BaseActionTestCase): def setUp(self) -> None: super().setUp() self.create_meeting(222) - self.set_models( - { - "motion_workflow/12": { - "name": "name_workflow1", - "first_state_id": 34, - "state_ids": [34], - }, - "motion_state/34": { - "name": "name_state34", - "meeting_id": 222, - "set_workflow_timestamp": True, - }, - } - ) + self.set_models({"motion_state/222": {"set_workflow_timestamp": True}}) def test_action_worker_ready_before_timeout_okay(self) -> None: """action thread used, but ended in time""" @@ -41,7 +28,7 @@ def test_action_worker_ready_before_timeout_okay(self) -> None: { "title": "test_title", "meeting_id": 222, - "workflow_id": 12, + "workflow_id": 222, "text": "test_text", }, ) @@ -57,7 +44,7 @@ def test_action_worker_ready_before_timeout_exception(self) -> None: { "title": "test_title", "meeting_id": 222, - "workflow_id": 12, + "workflow_id": 222, }, ) self.assert_status_code(response, 400) @@ -78,7 +65,7 @@ def test_action_worker_not_ready_before_timeout_okay(self) -> None: { "title": f"test_title {i+1}", "meeting_id": 222, - "workflow_id": 12, + "workflow_id": 222, "text": "test_text", } for i in range(count_motions) @@ -117,14 +104,8 @@ def test_internal_action_worker_not_ready_before_timeout_okay(self) -> None: chat_message_ids = [i + 1 for i in range(50)] self.set_models( { - "meeting/222": { - "chat_group_ids": [22], - "chat_message_ids": chat_message_ids, - }, - "meeting_user/1": {"chat_message_ids": chat_message_ids}, "chat_group/22": { "name": "blob", - "chat_message_ids": chat_message_ids, "read_group_ids": [222, 223, 224], "write_group_ids": [222], "meeting_id": 222, @@ -132,7 +113,7 @@ def test_internal_action_worker_not_ready_before_timeout_okay(self) -> None: **{ fqid_from_collection_and_id("chat_message", id_): { "content": f"Message {id_}", - "created": 1600000000 + id_, + "created": datetime.fromtimestamp(1600000000 + id_), "meeting_user_id": 1, "chat_group_id": 22, "meeting_id": 222, @@ -159,7 +140,7 @@ def test_internal_action_worker_not_ready_before_timeout_okay(self) -> None: ) if action_worker := self.get_thread_by_name("action_worker"): action_worker.join() - self.assert_model_exists("chat_group/22", {"chat_message_ids": []}) + self.assert_model_exists("chat_group/22", {"chat_message_ids": None}) for id_ in chat_message_ids: self.assert_model_not_exists( fqid_from_collection_and_id("chat_message", id_) @@ -176,7 +157,7 @@ def test_action_worker_not_ready_before_timeout_exception(self) -> None: { "title": f"test_title {i+1}", "meeting_id": 222, - "workflow_id": 12, + "workflow_id": 222, "text": "test_text", } for i in range(count_motions) @@ -185,7 +166,7 @@ def test_action_worker_not_ready_before_timeout_exception(self) -> None: { "title": f"test_title {count_motions+1}", "meeting_id": 222, - "workflow_id": 12, + "workflow_id": 222, } ) @@ -315,6 +296,8 @@ def thread_method(self: ActionWorkerTest) -> None: "name": "test", "state": ActionWorkerState.RUNNING, "user_id": 1, + "created": datetime.now(), + "timestamp": datetime.now(), }, ) ], @@ -347,6 +330,8 @@ def test_action_worker_delete_by_ids(self) -> None: "name": "test", "state": ActionWorkerState.RUNNING, "user_id": 1, + "created": datetime.now(), + "timestamp": datetime.now(), }, ) ], diff --git a/tests/system/action/test_archived_meeting.py b/tests/system/action/test_archived_meeting.py index 41cdb1e6a8..540ea56afa 100644 --- a/tests/system/action/test_archived_meeting.py +++ b/tests/system/action/test_archived_meeting.py @@ -4,15 +4,8 @@ class InMeetingActions(BaseActionTestCase): def setUp(self) -> None: super().setUp() - self.set_models( - { - "meeting/1": {"name": "test"}, - "motion/1": { - "title": "test_title", - "meeting_id": 1, - }, - } - ) + self.create_meeting(meeting_data={"is_active_in_organization_id": None}) + self.create_motion(1) def test_create_motion(self) -> None: response = self.request( @@ -24,7 +17,7 @@ def test_create_motion(self) -> None: ) self.assert_status_code(response, 400) self.assertIn( - "Meeting test/1 cannot be changed, because it is archived.", + "Meeting OpenSlides/1 cannot be changed, because it is archived.", response.json["message"], ) @@ -38,7 +31,7 @@ def test_update_motion(self) -> None: ) self.assert_status_code(response, 400) self.assertIn( - "Meeting test/1 cannot be changed, because it is archived.", + "Meeting OpenSlides/1 cannot be changed, because it is archived.", response.json["message"], ) @@ -51,7 +44,7 @@ def test_delete_motion(self) -> None: ) self.assert_status_code(response, 400) self.assertIn( - "Meeting test/1 cannot be changed, because it is archived.", + "Meeting OpenSlides/1 cannot be changed, because it is archived.", response.json["message"], ) @@ -59,27 +52,15 @@ def test_delete_motion(self) -> None: class MeetingActions(BaseActionTestCase): def setUp(self) -> None: super().setUp() - self.set_models( - { - "committee/1": { - "name": "committee1", - "meeting_ids": [1], - "organization_id": 1, - }, - "meeting/1": {"name": "test", "committee_id": 1, "motion_ids": [1]}, - "motion/1": { - "title": "test_title", - "meeting_id": 1, - }, - } - ) + self.create_meeting(meeting_data={"is_active_in_organization_id": None}) + self.create_motion(1) def test_create_meeting(self) -> None: response = self.request( "meeting.create", { "name": "test_meeting", - "committee_id": 1, + "committee_id": 60, "language": "en", "admin_ids": [1], }, @@ -99,40 +80,33 @@ def test_update_meeting(self) -> None: ) self.assert_status_code(response, 400) self.assertIn( - "Meeting test/1 cannot be changed, because it is archived.", + "Meeting OpenSlides/1 cannot be changed, because it is archived.", response.json["message"], ) def test_delete_meeting(self) -> None: + self.create_user_for_meeting(1) self.set_models( { - "meeting/1": { - "list_of_speakers_ids": [11, 12], - "speaker_ids": [1, 2, 3], - "group_ids": [1], - "meeting_user_ids": [3, 4], - }, - "group/1": {"meeting_user_ids": [3], "meeting_id": 1}, - "user/2": { - "username": "user2", - "is_active": True, - "meeting_user_ids": [3], - }, - "user/1": { - "meeting_user_ids": [4], - }, "meeting_user/3": { "user_id": 2, "meeting_id": 1, - "speaker_ids": [2, 3], - "group_ids": [1], }, "meeting_user/4": { "user_id": 1, "meeting_id": 1, - "speaker_ids": [1], }, - "list_of_speakers/11": {"meeting_id": 1, "speaker_ids": [1, 2]}, + "topic/23": { + "title": "to pic", + "sequential_number": 1, + "meeting_id": 1, + }, + "agenda_item/8": {"content_object_id": "topic/23", "meeting_id": 1}, + "list_of_speakers/11": { + "content_object_id": "topic/23", + "sequential_number": 11, + "meeting_id": 1, + }, "speaker/1": { "meeting_id": 1, "list_of_speakers_id": 11, @@ -143,7 +117,17 @@ def test_delete_meeting(self) -> None: "list_of_speakers_id": 11, "meeting_user_id": 3, }, - "list_of_speakers/12": {"meeting_id": 1, "speaker_ids": [3]}, + "topic/42": { + "title": "to pic", + "sequential_number": 2, + "meeting_id": 1, + }, + "agenda_item/9": {"content_object_id": "topic/42", "meeting_id": 1}, + "list_of_speakers/12": { + "content_object_id": "topic/42", + "sequential_number": 11, + "meeting_id": 1, + }, "speaker/3": { "meeting_id": 1, "list_of_speakers_id": 12, @@ -171,66 +155,26 @@ def test_delete_meeting(self) -> None: class OutMeetingActions(BaseActionTestCase): def setUp(self) -> None: super().setUp() - self.set_models( - { - "committee/1": { - "name": "committee1", - "meeting_ids": [1], - "organization_id": 1, - }, - "meeting/1": {"name": "test", "committee_id": 1}, - } - ) + self.create_meeting(meeting_data={"is_active_in_organization_id": None}) def test_change_user_group(self) -> None: - self.set_models( - { - "meeting/1": {"group_ids": [1, 2]}, - "group/1": {"meeting_user_ids": [2], "meeting_id": 1}, - "group/2": {"meeting_id": 1}, - "user/2": { - "username": "user2", - "is_active": True, - "meeting_user_ids": [2], - }, - "meeting_user/2": { - "meeting_id": 1, - "user_id": 2, - "group_ids": [1], - }, - } - ) + self.create_user_for_meeting(1) response = self.request( "meeting_user.update", { - "id": 2, + "id": 1, "group_ids": [2], }, ) self.assert_status_code(response, 400) self.assertIn( - "Meeting test/1 cannot be changed, because it is archived.", + "Meeting OpenSlides/1 cannot be changed, because it is archived.", response.json["message"], ) def test_delete_user(self) -> None: - self.set_models( - { - "meeting/1": {"group_ids": [1], "user_ids": [1, 2]}, - "group/1": {"meeting_user_ids": [2], "meeting_id": 1}, - "user/2": { - "username": "user2", - "is_active": True, - "meeting_user_ids": [2], - }, - "meeting_user/2": { - "meeting_id": 1, - "user_id": 2, - "group_ids": [1], - }, - } - ) - + self.set_user_groups(1, [3]) + self.create_user_for_meeting(1) response = self.request( "user.delete", { @@ -239,21 +183,21 @@ def test_delete_user(self) -> None: ) self.assert_status_code(response, 200) self.assert_model_not_exists("user/2") - self.assert_model_exists("group/1", {"meeting_user_ids": []}) - self.assert_model_exists("meeting/1", {"group_ids": [1], "user_ids": [1]}) + self.assert_model_exists("group/1", {"meeting_user_ids": None}) + self.assert_model_exists("meeting/1", {"group_ids": [1, 2, 3], "user_ids": [1]}) def test_delete_organization_tag(self) -> None: self.set_models( { - "committee/1": {"organization_tag_ids": [1, 2]}, - "meeting/1": {"organization_tag_ids": [1, 2]}, "organization_tag/1": { "name": "tag1", - "tagged_ids": ["meeting/1", "committee/1"], + "tagged_ids": ["meeting/1", "committee/60"], + "color": "#333333", }, "organization_tag/2": { "name": "tag2", - "tagged_ids": ["meeting/1", "committee/1"], + "tagged_ids": ["meeting/1", "committee/60"], + "color": "#333333", }, } ) @@ -265,5 +209,5 @@ def test_delete_organization_tag(self) -> None: ) self.assert_status_code(response, 200) self.assert_model_not_exists("organization_tag/1") - self.assert_model_exists("committee/1", {"organization_tag_ids": [2]}) + self.assert_model_exists("committee/60", {"organization_tag_ids": [2]}) self.assert_model_exists("meeting/1", {"organization_tag_ids": [2]}) diff --git a/tests/system/action/test_create_relation.py b/tests/system/action/test_create_relation.py index f85a5a92b9..15162312e1 100644 --- a/tests/system/action/test_create_relation.py +++ b/tests/system/action/test_create_relation.py @@ -11,7 +11,7 @@ from openslides_backend.models.base import Model from openslides_backend.shared.patterns import Collection -from .base import BaseActionTestCase +from .base_generic import BaseGenericTestCase class FakeModelCRA(Model): @@ -30,7 +30,7 @@ class FakeModelCRB(Model): name = fields.CharField() fake_model_cr_c_id = fields.RelationField( - to={"fake_model_cr_c": "fake_model_cr_b_id"}, required=True + to={"fake_model_cr_c": "fake_model_cr_b_id"}, required=True, is_view_field=True ) @@ -44,7 +44,7 @@ class FakeModelCRC(Model): to={"fake_model_cr_b": "fake_model_cr_c_id"}, required=True ) fake_model_cr_d_id = fields.RelationField( - to={"fake_model_cr_d": "fake_model_cr_c_ids"}, + to={"fake_model_cr_d": "fake_model_cr_c_ids"}, is_view_field=True ) @@ -99,7 +99,62 @@ class FakeModelCRDCreateAction(CreateAction): skip_archived_meeting_check = True -class TestCreateRelation(BaseActionTestCase): +class TestCreateRelation(BaseGenericTestCase): + collection_a = "fake_model_cr_a" + collection_b = "fake_model_cr_b" + collection_c = "fake_model_cr_c" + collection_d = "fake_model_cr_d" + tables_to_reset = [ + f"{collection_a}_t", + f"{collection_b}_t", + f"{collection_c}_t", + f"{collection_d}_t", + ] + yml = f""" + _meta: + id_field: &id_field + type: number + restriction_mode: A + constant: true + required: true + {collection_a}: + id: *id_field + req_field: + type: number + required: true + not_req_field: + type: number + {collection_b}: + id: *id_field + name: + type: text + fake_model_cr_c_id: + type: relation + to: {collection_c}/fake_model_cr_b_id + required: true + {collection_c}: + id: *id_field + name: + type: text + fake_model_cr_b_id: + type: relation + to: {collection_b}/fake_model_cr_c_id + reference: {collection_b} + required: true + fake_model_cr_d_id: + type: relation + to: {collection_d}/fake_model_cr_c_ids + reference: {collection_d} + {collection_d}: + id: *id_field + name: + type: text + fake_model_cr_c_ids: + type: relation + to: {collection_c}/fake_model_cr_d_id + required: true + """ + def test_simple_create(self) -> None: response = self.request( "fake_model_cr_a.create", {"req_field": 1, "not_req_field": 2} @@ -148,7 +203,7 @@ def test_create_impossible_v2(self) -> None: response = self.request("fake_model_cr_c.create", {"fake_model_cr_b_id": 1}) self.assert_status_code(response, 400) self.assertIn( - "Datastore service sends HTTP 400. Model 'fake_model_cr_b/1' does not exist.", + "Model 'fake_model_cr_b/1' does not exist.", response.json["message"], ) self.assert_model_not_exists("fake_model_cr_c/1") diff --git a/tests/system/action/test_delete_cascade.py b/tests/system/action/test_delete_cascade.py index 6b029e95f4..ba4160942b 100644 --- a/tests/system/action/test_delete_cascade.py +++ b/tests/system/action/test_delete_cascade.py @@ -4,7 +4,7 @@ from openslides_backend.models import fields from openslides_backend.models.base import Model -from .base import BaseActionTestCase +from .base_generic import BaseGenericTestCase class FakeModelCDA(Model): @@ -15,18 +15,22 @@ class FakeModelCDA(Model): fake_model_cd_b = fields.RelationField( to={"fake_model_cd_b": "fake_model_cd_a"}, on_delete=fields.OnDelete.CASCADE, + is_view_field=True, ) fake_model_cd_c = fields.RelationField( to={"fake_model_cd_c": "fake_model_cd_a"}, on_delete=fields.OnDelete.CASCADE, + is_view_field=True, ) fake_model_cd_b_set_null = fields.RelationField( to={"fake_model_cd_b": "fake_model_cd_a_set_null"}, on_delete=fields.OnDelete.SET_NULL, + is_view_field=True, ) - fake_model_cd_b_set_null_required = fields.RelationField( - to={"fake_model_cd_b": "fake_model_cd_a_set_null_required"}, + fake_model_cd_d_set_null_required = fields.RelationField( + to={"fake_model_cd_d": "fake_model_cd_a_set_null_required"}, on_delete=fields.OnDelete.SET_NULL, + is_view_field=True, ) @@ -39,18 +43,16 @@ class FakeModelCDB(Model): fake_model_cd_c_protect = fields.RelationField( to={"fake_model_cd_c": "fake_model_cd_b_protected"}, on_delete=fields.OnDelete.PROTECT, + is_view_field=True, ) fake_model_cd_c_cascade = fields.RelationField( to={"fake_model_cd_c": "fake_model_cd_b_cascaded"}, on_delete=fields.OnDelete.CASCADE, + is_view_field=True, ) fake_model_cd_a_set_null = fields.RelationField( to={"fake_model_cd_a": "fake_model_cd_b_set_null"}, ) - fake_model_cd_a_set_null_required = fields.RelationField( - to={"fake_model_cd_a": "fake_model_cd_b_set_null_required"}, - required=True, - ) class FakeModelCDC(Model): @@ -67,6 +69,17 @@ class FakeModelCDC(Model): ) +class FakeModelCDD(Model): + collection = "fake_model_cd_d" + verbose_name = "fake model for cascade deletion d" + id = fields.IntegerField() + + fake_model_cd_a_set_null_required = fields.RelationField( + to={"fake_model_cd_a": "fake_model_cd_d_set_null_required"}, + required=True, + ) + + @register_action("fake_model_cd_a.delete", action_type=ActionType.BACKEND_INTERNAL) class FakeModelCDADeleteAction(DeleteAction): model = FakeModelCDA() @@ -88,7 +101,90 @@ class FakeModelCDCDeleteAction(DeleteAction): skip_archived_meeting_check = True -class TestDeleteCascade(BaseActionTestCase): +@register_action("fake_model_cd_d.delete", action_type=ActionType.BACKEND_INTERNAL) +class FakeModelCDDDeleteAction(DeleteAction): + model = FakeModelCDC() + schema = {} # type: ignore + skip_archived_meeting_check = True + + +class TestDeleteCascade(BaseGenericTestCase): + collection_a = "fake_model_cd_a" + collection_b = "fake_model_cd_b" + collection_c = "fake_model_cd_c" + collection_d = "fake_model_cd_d" + tables_to_reset = [ + f"{collection_a}_t", + f"{collection_b}_t", + f"{collection_c}_t", + f"{collection_d}_t", + ] + yml = f""" + _meta: + id_field: &id_field + type: number + restriction_mode: A + constant: true + required: true + {collection_a}: + id: *id_field + {collection_b}: + type: relation + to: {collection_b}/{collection_a} + on_delete: CASCADE + {collection_c}: + type: relation + to: {collection_c}/{collection_a} + on_delete: CASCADE + {collection_b}_set_null: + type: relation + to: {collection_b}/{collection_a}_set_null + on_delete: SET_NULL + {collection_d}_set_null_required: + type: relation + to: {collection_d}/{collection_a}_set_null_required + on_delete: SET_NULL + {collection_b}: + id: *id_field + {collection_a}: + type: relation + to: {collection_a}/{collection_b} + reference: {collection_a} + {collection_c}_protect: + type: relation + to: {collection_c}/{collection_b}_protected + on_delete: PROTECT + {collection_c}_cascade: + type: relation + to: {collection_c}/{collection_b}_cascaded + on_delete: CASCADE + {collection_a}_set_null: + type: relation + to: {collection_a}/{collection_b}_set_null + reference: {collection_a} + {collection_c}: + id: *id_field + {collection_a}: + type: relation + to: {collection_a}/{collection_c} + reference: {collection_a} + {collection_b}_protected: + type: relation + to: {collection_b}/{collection_c}_protect + reference: {collection_b} + {collection_b}_cascaded: + type: relation + to: {collection_b}/{collection_c}_cascade + reference: {collection_b} + {collection_d}: + id: *id_field + {collection_a}_set_null_required: + type: relation + to: {collection_a}/{collection_d}_set_null_required + reference: {collection_a} + required: true + """ + def test_simple(self) -> None: self.set_models( { @@ -208,21 +304,19 @@ def test_set_null(self) -> None: def test_set_null_required(self) -> None: self.set_models( { - "fake_model_cd_a/1": { - "fake_model_cd_b_set_null_required": 1, - }, - "fake_model_cd_b/1": {"fake_model_cd_a_set_null_required": 1}, + "fake_model_cd_a/1": {}, + "fake_model_cd_d/1": {"fake_model_cd_a_set_null_required": 1}, } ) response = self.request("fake_model_cd_a.delete", {"id": 1}) self.assert_status_code(response, 400) self.assertIn( - "Update of fake_model_cd_b/1: You try to set following required fields to an empty value: ['fake_model_cd_a_set_null_required']", + "Update of fake_model_cd_d/1: You try to set following required fields to an empty value: ['fake_model_cd_a_set_null_required']", response.json["message"], ) self.assert_model_exists( - "fake_model_cd_a/1", {"fake_model_cd_b_set_null_required": 1} + "fake_model_cd_a/1", {"fake_model_cd_d_set_null_required": 1} ) self.assert_model_exists( - "fake_model_cd_b/1", {"fake_model_cd_a_set_null_required": 1} + "fake_model_cd_d/1", {"fake_model_cd_a_set_null_required": 1} ) diff --git a/tests/system/action/test_equal_fields_check.py b/tests/system/action/test_equal_fields_check.py index de172038ff..451d618b1e 100644 --- a/tests/system/action/test_equal_fields_check.py +++ b/tests/system/action/test_equal_fields_check.py @@ -1,3 +1,5 @@ +from pytest import mark + from openslides_backend.action.generics.create import CreateAction from openslides_backend.action.generics.update import UpdateAction from openslides_backend.action.util.action_type import ActionType @@ -5,7 +7,7 @@ from openslides_backend.models import fields from openslides_backend.models.base import Model -from .base import BaseActionTestCase +from .base_generic import BaseGenericTestCase class FakeModelEFA(Model): @@ -14,10 +16,10 @@ class FakeModelEFA(Model): id = fields.IntegerField() b_id = fields.RelationField( - to={"fake_model_ef_b": "meeting_id"}, + to={"fake_model_ef_b": "meeting_id"}, is_view_field=True ) - c_id = fields.RelationField( - to={"fake_model_ef_c": "meeting_id"}, + c_ids = fields.RelationListField( + to={"fake_model_ef_c": "meeting_id"}, is_view_field=True ) @@ -33,8 +35,7 @@ class FakeModelEFB(Model): equal_fields="meeting_id", ) c_ids = fields.RelationListField( - to={"fake_model_ef_c": "b_ids"}, - equal_fields="meeting_id", + to={"fake_model_ef_c": "b_ids"}, equal_fields="meeting_id", is_view_field=True ) c_generic_id = fields.GenericRelationField( to={"fake_model_ef_c": "b_generic_id"}, @@ -43,6 +44,7 @@ class FakeModelEFB(Model): c_generic_ids = fields.GenericRelationListField( to={"fake_model_ef_c": "b_generic_ids"}, equal_fields="meeting_id", + is_view_field=True, ) @@ -51,19 +53,17 @@ class FakeModelEFC(Model): verbose_name = "fake model for equal field check c" id = fields.IntegerField() - meeting_id = fields.RelationField(to={"fake_model_ef_a": "c_id"}) + meeting_id = fields.RelationField(to={"fake_model_ef_a": "c_ids"}) - b_id = fields.RelationField( - to={"fake_model_ef_b": "c_id"}, - ) + b_id = fields.RelationField(to={"fake_model_ef_b": "c_id"}, is_view_field=True) b_ids = fields.RelationListField( - to={"fake_model_ef_b": "c_ids"}, + to={"fake_model_ef_b": "c_ids"}, is_view_field=True ) b_generic_id = fields.GenericRelationField( - to={"fake_model_ef_b": "c_generic_id"}, + to={"fake_model_ef_b": "c_generic_id"}, is_view_field=True ) b_generic_ids = fields.GenericRelationListField( - to={"fake_model_ef_b": "c_generic_ids"}, + to={"fake_model_ef_b": "c_generic_ids"}, is_view_field=True ) @@ -81,11 +81,97 @@ class FakeModelEFBUpdateAction(UpdateAction): skip_archived_meeting_check = True -class TestEqualFieldsCheck(BaseActionTestCase): +class TestEqualFieldsCheck(BaseGenericTestCase): + collection_a = "fake_model_ef_a" + collection_b = "fake_model_ef_b" + collection_c = "fake_model_ef_c" + tables_to_reset = [ + f"{collection_a}_t", + f"{collection_b}_t", + f"{collection_c}_t", + "nm_fake_model_ef_b_c_ids_fake_model_ef_c_t", + ] + yml = f""" + _meta: + id_field: &id_field + type: number + restriction_mode: A + constant: true + required: true + {collection_a}: + id: *id_field + b_id: + type: relation + to: {collection_b}/meeting_id + reference: {collection_b} + c_ids: + type: relation-list + to: {collection_c}/meeting_id + reference: {collection_c} + {collection_b}: + id: *id_field + meeting_id: + type: relation + to: {collection_a}/b_id + reference: {collection_a} + c_id: + type: relation + to: {collection_c}/b_id + reference: {collection_c} + equal_fields: meeting_id + c_ids: + type: relation-list + to: {collection_c}/b_ids + equal_fields: meeting_id + c_generic_id: + type: generic-relation + reference: + - {collection_c} + to: + - {collection_c}/b_generic_id + equal_fields: meeting_id + c_generic_ids: + type: generic-relation-list + to: + collections: + - {collection_c} + field: b_generic_ids + equal_fields: meeting_id + {collection_c}: + id: *id_field + meeting_id: + type: relation + to: {collection_a}/c_ids + reference: {collection_a} + b_id: + type: relation + to: {collection_b}/c_id + reference: {collection_b} + equal_fields: meeting_id + b_ids: + type: relation-list + to: {collection_b}/c_ids + equal_fields: meeting_id + b_generic_id: + type: generic-relation + reference: + - {collection_b} + to: + - {collection_b}/c_generic_id + equal_fields: meeting_id + b_generic_ids: + type: generic-relation-list + to: + collections: + - {collection_b} + field: c_generic_ids + equal_fields: meeting_id + """ + def test_simple_pass(self) -> None: self.set_models( { - "fake_model_ef_a/1": {"c_id": 1}, + "fake_model_ef_a/1": {"c_ids": [1]}, "fake_model_ef_c/1": {"meeting_id": 1}, } ) @@ -97,7 +183,7 @@ def test_simple_fail(self) -> None: self.set_models( { "fake_model_ef_a/1": {}, - "fake_model_ef_a/2": {"c_id": 1}, + "fake_model_ef_a/2": {"c_ids": [1]}, "fake_model_ef_c/1": {"meeting_id": 2}, } ) @@ -112,7 +198,7 @@ def test_simple_fail(self) -> None: def test_simple_update_pass(self) -> None: self.set_models( { - "fake_model_ef_a/1": {"b_id": 1, "c_id": 1}, + "fake_model_ef_a/1": {"b_id": 1, "c_ids": [1, 2]}, "fake_model_ef_b/1": {"meeting_id": 1, "c_id": 1}, "fake_model_ef_c/1": {"meeting_id": 1, "b_id": 1}, "fake_model_ef_c/2": {"meeting_id": 1}, @@ -125,7 +211,7 @@ def test_simple_update_pass(self) -> None: def test_list_pass(self) -> None: self.set_models( { - "fake_model_ef_a/1": {"c_id": 1}, + "fake_model_ef_a/1": {"c_ids": [1]}, "fake_model_ef_c/1": {"meeting_id": 1}, } ) @@ -139,7 +225,7 @@ def test_list_fail(self) -> None: self.set_models( { "fake_model_ef_a/1": {}, - "fake_model_ef_a/2": {"c_id": 1}, + "fake_model_ef_a/2": {"c_ids": [1]}, "fake_model_ef_c/1": {"meeting_id": 2}, } ) @@ -156,7 +242,7 @@ def test_list_fail(self) -> None: def test_generic_pass(self) -> None: self.set_models( { - "fake_model_ef_a/1": {"c_id": 1}, + "fake_model_ef_a/1": {"c_ids": [1]}, "fake_model_ef_c/1": {"meeting_id": 1}, } ) @@ -171,7 +257,7 @@ def test_generic_fail(self) -> None: self.set_models( { "fake_model_ef_a/1": {}, - "fake_model_ef_a/2": {"c_id": 1}, + "fake_model_ef_a/2": {"c_ids": [1]}, "fake_model_ef_c/1": {"meeting_id": 2}, } ) @@ -186,10 +272,13 @@ def test_generic_fail(self) -> None: ) self.assert_model_not_exists("fake_model_ef_b/1") + @mark.skip( + "Currently generic relation lists on both ends aren't used. DDL should be fixed then." + ) def test_generic_list_pass(self) -> None: self.set_models( { - "fake_model_ef_a/1": {"c_id": 1}, + "fake_model_ef_a/1": {"c_ids": [1]}, "fake_model_ef_c/1": {"meeting_id": 1}, } ) @@ -204,7 +293,7 @@ def test_generic_list_fail(self) -> None: self.set_models( { "fake_model_ef_a/1": {}, - "fake_model_ef_a/2": {"c_id": 1}, + "fake_model_ef_a/2": {"c_ids": [1]}, "fake_model_ef_c/1": {"meeting_id": 2}, } ) diff --git a/tests/system/action/test_general.py b/tests/system/action/test_general.py index dc9e4d7109..aa8015835b 100644 --- a/tests/system/action/test_general.py +++ b/tests/system/action/test_general.py @@ -69,6 +69,9 @@ def test_request_handle_separately(self) -> None: response.json["message"], ) + @pytest.mark.skip( + reason="Test not relevant anymore after migration to relational database" + ) def test_migrations_route(self) -> None: response = self.client.post( get_route_path(ActionView.migrations_route), diff --git a/tests/system/action/test_migration_route.py b/tests/system/action/test_migration_route.py index 5c4cc33736..f0653aafe9 100644 --- a/tests/system/action/test_migration_route.py +++ b/tests/system/action/test_migration_route.py @@ -4,6 +4,8 @@ from typing import Any from unittest.mock import MagicMock, Mock, patch +import pytest + from openslides_backend.http.views.action_view import ActionView from openslides_backend.migrations import ( get_backend_migration_index, @@ -18,6 +20,9 @@ from .test_internal_actions import BaseInternalPasswordTest, BaseInternalRequestTest +@pytest.mark.skip( + reason="Test not relevant anymore after migration to relational database" +) class BaseMigrationRouteTest(BaseInternalRequestTest): """ Uses the anonymous client to call the migration route. diff --git a/tests/system/action/test_permissions.py b/tests/system/action/test_permissions.py index 9f1c902ae0..64e8061ece 100644 --- a/tests/system/action/test_permissions.py +++ b/tests/system/action/test_permissions.py @@ -4,9 +4,29 @@ from openslides_backend.models.base import Model from openslides_backend.permissions.management_levels import OrganizationManagementLevel from openslides_backend.permissions.permissions import Permissions +from openslides_backend.services.postgresql.db_connection_handling import ( + get_new_os_conn, +) from .base import BaseActionTestCase +collection_p = "fake_model_p" + + +def create_table_view() -> None: + sql = f""" + CREATE TABLE IF NOT EXISTS {collection_p}_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + meeting_id integer + ); + + DROP VIEW IF EXISTS "{collection_p}"; + CREATE VIEW "{collection_p}" AS SELECT * FROM {collection_p}_t; + """ + with get_new_os_conn() as conn: + with conn.cursor() as curs: + curs.execute(sql) + class FakeModelP(Model): collection = "fake_model_p" @@ -23,12 +43,33 @@ class FakeModelPCreate(CreateAction): class TestPermissions(BaseActionTestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + create_table_view() + + @classmethod + def tearDownClass(cls) -> None: + super().tearDownClass() + with get_new_os_conn() as conn: + with conn.cursor() as curs: + curs.execute(f"DROP TABLE {collection_p}_t CASCADE;") + def setUp(self) -> None: super().setUp() self.create_meeting() self.user_id = self.create_user("user") self.login(self.user_id) + def tearDown(self) -> None: + super().tearDown() + with self.connection.cursor() as curs: + curs.execute( + f""" + TRUNCATE TABLE {collection_p}_t RESTART IDENTITY; + """ + ) + def test_anonymous_disabled(self) -> None: self.set_anonymous(False) response = self.request( diff --git a/tests/system/action/test_update_relation.py b/tests/system/action/test_update_relation.py index 9aa8f9c58a..6fd194cd3b 100644 --- a/tests/system/action/test_update_relation.py +++ b/tests/system/action/test_update_relation.py @@ -3,9 +3,41 @@ from openslides_backend.action.util.register import register_action from openslides_backend.models import fields from openslides_backend.models.base import Model +from openslides_backend.services.postgresql.db_connection_handling import ( + get_new_os_conn, +) from .base import BaseActionTestCase +collection_a = "fake_model_ur_a" +collection_b = "fake_model_ur_b" + + +def create_table_view() -> None: + sql = f""" + CREATE TABLE IF NOT EXISTS {collection_a}_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL + ); + + CREATE TABLE IF NOT EXISTS {collection_b}_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + {collection_a}_id integer REFERENCES {collection_a}_t(id) INITIALLY DEFERRED, + {collection_a}_required_id integer REFERENCES {collection_a}_t(id) INITIALLY DEFERRED NOT NULL + ); + + DROP VIEW IF EXISTS "{collection_a}"; + CREATE VIEW "{collection_a}" AS SELECT *, + (select b.id from {collection_b}_t b where b.{collection_a}_id = a.id) as {collection_b}_id, + (select b.id from {collection_b}_t b where b.{collection_a}_required_id = a.id) as {collection_b}_required_id + FROM {collection_a}_t a; + + DROP VIEW IF EXISTS "{collection_b}"; + CREATE VIEW "{collection_b}" AS SELECT * FROM {collection_b}_t; + """ + with get_new_os_conn() as conn: + with conn.cursor() as curs: + curs.execute(sql) + class FakeModelURA(Model): collection = "fake_model_ur_a" @@ -14,9 +46,11 @@ class FakeModelURA(Model): fake_model_ur_b_id = fields.RelationField( to={"fake_model_ur_b": "fake_model_ur_a_id"}, + is_view_field=True, ) fake_model_ur_b_required_id = fields.RelationField( to={"fake_model_ur_b": "fake_model_ur_a_required_id"}, + is_view_field=True, ) @@ -42,13 +76,46 @@ class FakeModelURAUpdateAction(UpdateAction): class TestUpdateRelation(BaseActionTestCase): - def test_set_to_null(self) -> None: + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + create_table_view() + + @classmethod + def tearDownClass(cls) -> None: + super().tearDownClass() + with get_new_os_conn() as conn: + with conn.cursor() as curs: + curs.execute( + f""" + DROP TABLE {collection_b}_t CASCADE; + DROP TABLE {collection_a}_t CASCADE; + """ + ) + + def setUp(self) -> None: + super().setUp() self.set_models( { "fake_model_ur_a/1": {"fake_model_ur_b_id": 1}, - "fake_model_ur_b/1": {"fake_model_ur_a_id": 1}, + "fake_model_ur_b/1": { + "fake_model_ur_a_id": 1, + "fake_model_ur_a_required_id": 1, + }, } ) + + def tearDown(self) -> None: + super().tearDown() + with self.connection.cursor() as curs: + curs.execute( + f""" + TRUNCATE TABLE {collection_a}_t RESTART IDENTITY CASCADE; + TRUNCATE TABLE {collection_b}_t RESTART IDENTITY CASCADE; + """ + ) + + def test_set_to_null(self) -> None: response = self.request( "fake_model_ur_a.update", {"id": 1, "fake_model_ur_b_id": None} ) @@ -57,12 +124,6 @@ def test_set_to_null(self) -> None: self.assert_model_exists("fake_model_ur_b/1", {"fake_model_ur_a_id": None}) def test_set_required_to_null(self) -> None: - self.set_models( - { - "fake_model_ur_a/1": {"fake_model_ur_b_required_id": 1}, - "fake_model_ur_b/1": {"fake_model_ur_a_required_id": 1}, - } - ) response = self.request( "fake_model_ur_a.update", {"id": 1, "fake_model_ur_b_required_id": None} ) @@ -79,12 +140,6 @@ def test_set_required_to_null(self) -> None: ) def test_set_required_to_0(self) -> None: - self.set_models( - { - "fake_model_ur_a/1": {"fake_model_ur_b_required_id": 1}, - "fake_model_ur_b/1": {"fake_model_ur_a_required_id": 1}, - } - ) response = self.request( "fake_model_ur_a.update", {"id": 1, "fake_model_ur_b_required_id": 0} ) diff --git a/tests/system/action/topic/test_delete.py b/tests/system/action/topic/test_delete.py index 8d38d18857..a035d8ab08 100644 --- a/tests/system/action/topic/test_delete.py +++ b/tests/system/action/topic/test_delete.py @@ -13,7 +13,13 @@ def setUp(self) -> None: "sequential_number": 1, "title": "title_srtgb123", "meeting_id": 1, - } + }, + "list_of_speakers/23": { + "content_object_id": "topic/111", + "sequential_number": 11, + "meeting_id": 1, + }, + "agenda_item/8": {"meeting_id": 1, "content_object_id": "topic/111"}, } def test_delete_correct(self) -> None: @@ -24,6 +30,12 @@ def test_delete_correct(self) -> None: "title": "title_srtgb123", "meeting_id": 1, }, + "list_of_speakers/23": { + "content_object_id": "topic/111", + "sequential_number": 11, + "meeting_id": 1, + }, + "agenda_item/8": {"meeting_id": 1, "content_object_id": "topic/111"}, } ) response = self.request("topic.delete", {"id": 111}) @@ -31,9 +43,20 @@ def test_delete_correct(self) -> None: self.assert_model_not_exists("topic/111") def test_delete_wrong_id(self) -> None: - self.create_model( - "topic/112", - {"meeting_id": 1, "sequential_number": 12, "title": "title_srtgb123"}, + self.set_models( + { + "topic/112": { + "meeting_id": 1, + "sequential_number": 12, + "title": "title_srtgb123", + }, + "list_of_speakers/23": { + "content_object_id": "topic/112", + "sequential_number": 11, + "meeting_id": 1, + }, + "agenda_item/9": {"meeting_id": 1, "content_object_id": "topic/112"}, + } ) response = self.request("topic.delete", {"id": 111}) self.assert_status_code(response, 400) @@ -45,8 +68,6 @@ def test_delete_correct_cascading(self) -> None: "topic/111": { "sequential_number": 1, "title": "title_srtgb123", - "list_of_speakers_id": 222, - "agenda_item_id": 333, "meeting_id": 1, }, "list_of_speakers/222": { diff --git a/tests/system/action/user/test_delete.py b/tests/system/action/user/test_delete.py index f1493d9174..61128e8a78 100644 --- a/tests/system/action/user/test_delete.py +++ b/tests/system/action/user/test_delete.py @@ -99,6 +99,7 @@ def test_delete_with_speaker(self) -> None: "sequential_number": 1, "meeting_id": 1, }, + "agenda_item/8": {"meeting_id": 1, "content_object_id": "topic/1"}, } ) response = self.request("user.delete", {"id": 111}) @@ -142,6 +143,11 @@ def test_delete_with_candidate(self) -> None: "sequential_number": 123, "meeting_id": 1, }, + "list_of_speakers/23": { + "content_object_id": "assignment/123", + "sequential_number": 11, + "meeting_id": 1, + }, } ) response = self.request("user.delete", {"id": 111}) @@ -198,8 +204,11 @@ def test_delete_with_poll_candidate(self) -> None: "weight": 1, "meeting_id": 1, }, - "poll_candidate_list/1": {"meeting_id": 1, "option_id": 1}, - "option/1": {"meeting_id": 1}, + "poll_candidate_list/1": {"meeting_id": 1}, + "option/1": { + "meeting_id": 1, + "content_object_id": "poll_candidate_list/1", + }, } ) response = self.request("user.delete", {"id": 111}) diff --git a/tests/system/base.py b/tests/system/base.py index db41b5274b..b9714f4ddb 100644 --- a/tests/system/base.py +++ b/tests/system/base.py @@ -120,6 +120,7 @@ def run(self, result: TestResult | None = None) -> TestResult | None: """ Overrides the TestCases run method. Provides an ExtendedDatabase in self.datastore with an open psycopg connection. + Also stores its connection in self.connection. """ with get_new_os_conn() as conn: self.datastore = ExtendedDatabase(conn, MagicMock(), MagicMock())