diff --git a/server/openslides/assignments/migrations/0024_assignmentpoll_entitled_users_remove_duplicates.py b/server/openslides/assignments/migrations/0024_assignmentpoll_entitled_users_remove_duplicates.py new file mode 100644 index 000000000..79ef5d5a6 --- /dev/null +++ b/server/openslides/assignments/migrations/0024_assignmentpoll_entitled_users_remove_duplicates.py @@ -0,0 +1,18 @@ +# Generated by jsangmeister on 2021-04-08 11:27 + +from django.db import migrations + +from ...poll.migrations.poll_migration_helper import remove_entitled_users_duplicates + + +class Migration(migrations.Migration): + + dependencies = [ + ("assignments", "0023_assignmentpoll_change_fields_2"), + ] + + operations = [ + migrations.RunPython( + remove_entitled_users_duplicates("assignments", "AssignmentPoll") + ), + ] diff --git a/server/openslides/motions/migrations/0043_motionpoll_entitled_users_remove_duplicates.py b/server/openslides/motions/migrations/0043_motionpoll_entitled_users_remove_duplicates.py new file mode 100644 index 000000000..19d0a93a8 --- /dev/null +++ b/server/openslides/motions/migrations/0043_motionpoll_entitled_users_remove_duplicates.py @@ -0,0 +1,16 @@ +# Generated by jsangmeister on 2021-04-08 11:27 + +from django.db import migrations + +from ...poll.migrations.poll_migration_helper import remove_entitled_users_duplicates + + +class Migration(migrations.Migration): + + dependencies = [ + ("motions", "0042_motionpoll_change_fields_2"), + ] + + operations = [ + migrations.RunPython(remove_entitled_users_duplicates("motions", "MotionPoll")), + ] diff --git a/server/openslides/poll/migrations/poll_migration_helper.py b/server/openslides/poll/migrations/poll_migration_helper.py index 3e6478ad3..27b9414b3 100644 --- a/server/openslides/poll/migrations/poll_migration_helper.py +++ b/server/openslides/poll/migrations/poll_migration_helper.py @@ -34,3 +34,25 @@ def calculate_vote_fields(poll_model_collection, poll_model_name): poll.save(skip_autoupdate=True) return _calculate_vote_fields + + +def remove_entitled_users_duplicates(poll_model_collection, poll_model_name): + """ + Takes all polls of the given model and removes any duplicate entries from + entitled_users_at_stop + """ + + def _remove_entitled_users_duplicates(apps, schema_editor): + PollModel = apps.get_model(poll_model_collection, poll_model_name) + for poll in PollModel.objects.all(): + if poll.entitled_users_at_stop: + new_entitled_users = [] + entitled_users_ids = set() + for entry in poll.entitled_users_at_stop: + if entry["user_id"] not in entitled_users_ids: + entitled_users_ids.add(entry["user_id"]) + new_entitled_users.append(entry) + poll.entitled_users_at_stop = new_entitled_users + poll.save(skip_autoupdate=True) + + return _remove_entitled_users_duplicates diff --git a/server/openslides/poll/models.py b/server/openslides/poll/models.py index d18ff0e12..a72e43617 100644 --- a/server/openslides/poll/models.py +++ b/server/openslides/poll/models.py @@ -275,9 +275,11 @@ class BasePoll(models.Model): def calculate_entitled_users(self): entitled_users = [] + entitled_users_ids = set() for group in self.groups.all(): for user in group.user_set.all(): - if user.is_present: + if user.is_present and user.id not in entitled_users_ids: + entitled_users_ids.add(user.id) entitled_users.append( { "user_id": user.id, diff --git a/server/tests/integration/motions/test_polls.py b/server/tests/integration/motions/test_polls.py index 4743c72dd..730f9505e 100644 --- a/server/tests/integration/motions/test_polls.py +++ b/server/tests/integration/motions/test_polls.py @@ -1208,6 +1208,19 @@ class StopMotionPoll(TestCase): ], ) + def test_stop_poll_assert_no_duplicate_entitled_users(self): + self.setup_entitled_users() + delegate_group = get_group_model().objects.get(pk=GROUP_DELEGATE_PK) + self.admin.groups.add(delegate_group) + self.poll.groups.add(delegate_group) + + response = self.client.post(reverse("motionpoll-stop", args=[self.poll.pk])) + self.assertHttpStatusVerbose(response, status.HTTP_200_OK) + self.assertEqual( + MotionPoll.objects.get().entitled_users_at_stop, + [{"user_id": self.admin.id, "voted": False, "vote_delegated_to_id": None}], + ) + class PublishMotionPoll(TestCase): def advancedSetUp(self):