diff --git a/CHANGELOG b/CHANGELOG index 70bb1e665..3457d4e7d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -46,7 +46,8 @@ Core: - Fixing error when clearing empty chat [#3199]. - Added notify system [#3212]. - Enhanced performance esp. for server restart and first connection of all - clients by refactorting autoupdate, Collection and AccessPermission [#3223]. + clients by refactoring autoupdate, Collection and AccessPermission [#3223]. +- Fixes autoupdate bug for a user without user.can_see_name permission [#3233]. Mediafiles: - Fixed reloading of PDF on page change [#3274] diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index 0de3884f2..f34db09fd 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -103,6 +103,10 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV raise ValidationError({'detail': str(e)}) message = _('User %s was successfully added to the list of speakers.') % user + # Send new speaker via autoupdate because users without permission + # to see users may not have it but can get it now. + inform_changed_data([user]) + else: # request.method == 'DELETE' speaker_ids = request.data.get('speaker') diff --git a/openslides/assignments/views.py b/openslides/assignments/views.py index f7012dc7d..3ebf251ea 100644 --- a/openslides/assignments/views.py +++ b/openslides/assignments/views.py @@ -79,6 +79,9 @@ class AssignmentViewSet(ModelViewSet): self.permission_denied(request) # If the request.user is already a candidate he can nominate himself nevertheless. assignment.set_candidate(request.user) + # Send new candidate via autoupdate because users without permission + # to see users may not have it but can get it now. + inform_changed_data([request.user]) return _('You were nominated successfully.') def withdraw_self(self, request, assignment): @@ -140,6 +143,9 @@ class AssignmentViewSet(ModelViewSet): if assignment.is_candidate(user): raise ValidationError({'detail': _('User %s is already nominated.') % user}) assignment.set_candidate(user) + # Send new candidate via autoupdate because users without permission + # to see users may not have it but can get it now. + inform_changed_data(user) return _('User %s was nominated successfully.') % user def delete_other(self, request, user, assignment): diff --git a/openslides/core/views.py b/openslides/core/views.py index 5b09cff78..856920b9b 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -723,6 +723,9 @@ class ChatMessageViewSet(ModelViewSet): method so that the request.user can be saved into the model field. """ serializer.save(user=self.request.user) + # Send chatter via autoupdate because users without permission + # to see users may not have it but can get it now. + inform_changed_data([self.request.user]) @list_route(methods=['post']) def clear(self, request): diff --git a/openslides/mediafiles/models.py b/openslides/mediafiles/models.py index 85667dc87..c5b0d6c5f 100644 --- a/openslides/mediafiles/models.py +++ b/openslides/mediafiles/models.py @@ -2,6 +2,7 @@ from django.conf import settings from django.db import models from django.utils.translation import ugettext as _ +from ..utils.autoupdate import inform_changed_data from ..utils.models import RESTModelMixin from .access_permissions import MediafileAccessPermissions @@ -52,6 +53,16 @@ class Mediafile(RESTModelMixin, models.Model): """ return self.title + def save(self, *args, **kwargs): + """ + Saves mediafile (mainly on create and update requests). + """ + result = super().save(*args, **kwargs) + # Send uploader via autoupdate because users without permission + # to see users may not have it but can get it now. + inform_changed_data(self.uploader) + return result + def get_filesize(self): """ Transforms bytes to kilobytes or megabytes. Returns the size as string. diff --git a/openslides/motions/serializers.py b/openslides/motions/serializers.py index 79997fa30..47e25bd01 100644 --- a/openslides/motions/serializers.py +++ b/openslides/motions/serializers.py @@ -1,8 +1,8 @@ from django.db import transaction from django.utils.translation import ugettext as _ -from openslides.poll.serializers import default_votes_validator -from openslides.utils.rest_api import ( +from ..poll.serializers import default_votes_validator +from ..utils.rest_api import ( CharField, DictField, Field, @@ -12,8 +12,7 @@ from openslides.utils.rest_api import ( SerializerMethodField, ValidationError, ) -from openslides.utils.validate import validate_html - +from ..utils.validate import validate_html from .models import ( Category, Motion, diff --git a/openslides/motions/views.py b/openslides/motions/views.py index 05a58c9c4..b527a55f4 100644 --- a/openslides/motions/views.py +++ b/openslides/motions/views.py @@ -137,6 +137,13 @@ class MotionViewSet(ModelViewSet): # Write the log message and initiate response. motion.write_log([ugettext_noop('Motion created')], request.user) + + # Send new submitters and supporters via autoupdate because users + # without permission to see users may not have them but can get it now. + new_users = list(motion.submitters.all()) + new_users.extend(motion.supporters.all()) + inform_changed_data(new_users) + headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) @@ -198,6 +205,13 @@ class MotionViewSet(ModelViewSet): not has_perm(request.user, 'motions.can_manage')): updated_motion.supporters.clear() updated_motion.write_log([ugettext_noop('All supporters removed')], request.user) + + # Send new submitters and supporters via autoupdate because users + # without permission to see users may not have them but can get it now. + new_users = list(updated_motion.submitters.all()) + new_users.extend(updated_motion.supporters.all()) + inform_changed_data(new_users) + return Response(serializer.data) @detail_route(methods=['put', 'delete']) @@ -265,6 +279,9 @@ class MotionViewSet(ModelViewSet): raise ValidationError({'detail': _('You can not support this motion.')}) motion.supporters.add(request.user) motion.write_log([ugettext_noop('Motion supported')], request.user) + # Send new supporter via autoupdate because users without permission + # to see users may not have it but can get it now. + inform_changed_data([request.user]) message = _('You have supported this motion successfully.') else: # Unsupport motion. diff --git a/openslides/utils/collection.py b/openslides/utils/collection.py index 9c1dad52e..f23426362 100644 --- a/openslides/utils/collection.py +++ b/openslides/utils/collection.py @@ -102,13 +102,13 @@ class CollectionElement: """ from .autoupdate import format_for_autoupdate - # TODO: Revert this after get_projector_data is also enhanced like get_restricted_data. + # TODO: Revert this after get_projector_data is also enhanced like get_restricted_data. See also #3282. if method == 'get_restricted_data': container = self + elif not self.is_deleted(): + container = self.get_full_data() else: - # TODO: Find a better solution for this hotfix, see issue #3282. - if not self.is_deleted(): - container = self.get_full_data() + container = None # End of hack if not self.is_deleted(): diff --git a/tests/unit/agenda/test_views.py b/tests/unit/agenda/test_views.py index 11efb7638..de4fdd9c6 100644 --- a/tests/unit/agenda/test_views.py +++ b/tests/unit/agenda/test_views.py @@ -15,9 +15,10 @@ class ItemViewSetManageSpeaker(TestCase): self.view_instance.get_object = get_object_mock = MagicMock() get_object_mock.return_value = self.mock_item = MagicMock() + @patch('openslides.agenda.views.inform_changed_data') @patch('openslides.agenda.views.has_perm') @patch('openslides.agenda.views.Speaker') - def test_add_oneself_as_speaker(self, mock_speaker, mock_has_perm): + def test_add_oneself_as_speaker(self, mock_speaker, mock_has_perm, mock_icd): self.request.method = 'POST' self.request.user = 1 mock_has_perm.return_value = True @@ -28,10 +29,11 @@ class ItemViewSetManageSpeaker(TestCase): mock_speaker.objects.add.assert_called_with(self.request.user, self.mock_item) + @patch('openslides.agenda.views.inform_changed_data') @patch('openslides.agenda.views.has_perm') @patch('openslides.agenda.views.get_user_model') @patch('openslides.agenda.views.Speaker') - def test_add_someone_else_as_speaker(self, mock_speaker, mock_get_user_model, mock_has_perm): + def test_add_someone_else_as_speaker(self, mock_speaker, mock_get_user_model, mock_has_perm, mock_icd): self.request.method = 'POST' self.request.user = 1 self.request.data = {'user': '2'} # It is assumed that the request user has pk!=2. diff --git a/tests/unit/motions/test_views.py b/tests/unit/motions/test_views.py index 1d7f71bf1..21b841c44 100644 --- a/tests/unit/motions/test_views.py +++ b/tests/unit/motions/test_views.py @@ -17,9 +17,10 @@ class MotionViewSetCreate(TestCase): self.view_instance.get_serializer = get_serializer_mock = MagicMock() get_serializer_mock.return_value = self.mock_serializer = MagicMock() + @patch('openslides.motions.views.inform_changed_data') @patch('openslides.motions.views.has_perm') @patch('openslides.motions.views.config') - def test_simple_create(self, mock_config, mock_has_perm): + def test_simple_create(self, mock_config, mock_has_perm, mock_icd): self.request.user = 1 mock_has_perm.return_value = True @@ -41,9 +42,10 @@ class MotionViewSetUpdate(TestCase): self.view_instance.get_serializer = get_serializer_mock = MagicMock() get_serializer_mock.return_value = self.mock_serializer = MagicMock() + @patch('openslides.motions.views.inform_changed_data') @patch('openslides.motions.views.has_perm') @patch('openslides.motions.views.config') - def test_simple_update(self, mock_config, mock_has_perm): + def test_simple_update(self, mock_config, mock_has_perm, mock_icd): self.request.user = 1 self.request.data.get.return_value = versioning_mock = MagicMock() mock_has_perm.return_value = True