From 5a821ecf490e06b93176bc615060aaef254c52b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Mon, 27 Feb 2017 15:37:01 +0100 Subject: [PATCH] Fixed use of PATCH and PUT. Fixed #1871. --- openslides/assignments/views.py | 2 +- openslides/core/views.py | 21 +++++------ openslides/motions/static/js/motions/site.js | 4 +-- openslides/motions/views.py | 37 ++++++++++++-------- tests/unit/motions/test_views.py | 1 + 5 files changed, 38 insertions(+), 27 deletions(-) diff --git a/openslides/assignments/views.py b/openslides/assignments/views.py index 3b30c0d18..f7012dc7d 100644 --- a/openslides/assignments/views.py +++ b/openslides/assignments/views.py @@ -235,7 +235,7 @@ class AssignmentPollViewSet(UpdateModelMixin, DestroyModelMixin, GenericViewSet) """ API endpoint for assignment polls. - There are the following views: update and destroy. + There are the following views: update, partial_update and destroy. """ queryset = AssignmentPoll.objects.all() serializer_class = AssignmentAllPollSerializer diff --git a/openslides/core/views.py b/openslides/core/views.py index 7ba51b2d3..e0757501b 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -199,9 +199,7 @@ class ProjectorViewSet(ModelViewSet): """ API endpoint for the projector slide info. - There are the following views: metadata, list, retrieve, - activate_elements, prune_elements, update_elements, - deactivate_elements, clear_elements and control_view. + There are the following views: See strings in check_view_permissions(). """ access_permissions = ProjectorAccessPermissions() queryset = Projector.objects.all() @@ -575,7 +573,7 @@ class TagViewSet(ModelViewSet): # Every authenticated user can see the metadata. # Anonymous users can do so if they are enabled. result = self.request.user.is_authenticated() or anonymous_is_enabled() - elif self.action in ('create', 'update', 'destroy'): + elif self.action in ('create', 'partial_update', 'update', 'destroy'): result = has_perm(self.request.user, 'core.can_manage_tags') else: result = False @@ -616,7 +614,8 @@ class ConfigViewSet(ViewSet): """ API endpoint for the config. - There are the following views: metadata, list, retrieve and update. + There are the following views: metadata, list, retrieve, update and + partial_update. """ access_permissions = ConfigAccessPermissions() metadata_class = ConfigMetadata @@ -632,7 +631,7 @@ class ConfigViewSet(ViewSet): # retrieve the config. Anonymous users can do so if they are # enabled. result = self.request.user.is_authenticated() or anonymous_is_enabled() - elif self.action == 'update': + elif self.action in ('partial_update', 'update'): result = has_perm(self.request.user, 'core.can_manage_config') else: result = False @@ -742,7 +741,8 @@ class ProjectorMessageViewSet(ModelViewSet): """ API endpoint for messages. - There are the following views: list, retrieve, create, update and destroy. + There are the following views: list, retrieve, create, update, + partial_update and destroy. """ access_permissions = ProjectorMessageAccessPermissions() queryset = ProjectorMessage.objects.all() @@ -753,7 +753,7 @@ class ProjectorMessageViewSet(ModelViewSet): """ if self.action in ('list', 'retrieve'): result = self.get_access_permissions().check_permissions(self.request.user) - elif self.action in ('create', 'update', 'destroy'): + elif self.action in ('create', 'partial_update', 'update', 'destroy'): result = has_perm(self.request.user, 'core.can_manage_projector') else: result = False @@ -764,7 +764,8 @@ class CountdownViewSet(ModelViewSet): """ API endpoint for Countdown. - There are the following views: list, retrieve, create, update and destroy. + There are the following views: list, retrieve, create, update, + partial_update and destroy. """ access_permissions = CountdownAccessPermissions() queryset = Countdown.objects.all() @@ -775,7 +776,7 @@ class CountdownViewSet(ModelViewSet): """ if self.action in ('list', 'retrieve'): result = self.get_access_permissions().check_permissions(self.request.user) - elif self.action in ('create', 'update', 'destroy'): + elif self.action in ('create', 'partial_update', 'update', 'destroy'): result = has_perm(self.request.user, 'core.can_manage_projector') else: result = False diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index c762353b7..83bfdd4ee 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -1324,7 +1324,7 @@ angular.module('OpenSlidesApp.motions.site', [ // inject the changed change recommendation (copy) object back into DS store MotionChangeRecommendation.inject(change); // save changed change recommendation object on server - MotionChangeRecommendation.save(change, { method: 'PATCH' }).then( + MotionChangeRecommendation.save(change).then( function(success) { $scope.closeThisDialog(); }, @@ -1531,7 +1531,7 @@ angular.module('OpenSlidesApp.motions.site', [ // inject the changed motion (copy) object back into DS store Motion.inject(motion); // save change motion object on server - Motion.save(motion, { method: 'PATCH' }).then( + Motion.save(motion).then( function(success) { // type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item, // see openslides.agenda.models.Item.ITEM_TYPE. diff --git a/openslides/motions/views.py b/openslides/motions/views.py index 8b087ff5a..b8fa59815 100644 --- a/openslides/motions/views.py +++ b/openslides/motions/views.py @@ -12,6 +12,7 @@ from rest_framework import status from ..core.config import config from ..utils.auth import has_perm from ..utils.autoupdate import inform_changed_data +from ..utils.collection import CollectionElement from ..utils.rest_api import ( DestroyModelMixin, GenericViewSet, @@ -85,13 +86,16 @@ class MotionViewSet(ModelViewSet): """ Customized view endpoint to create a new motion. """ - # Check if parent motion exists - parent_motion = None - if 'parent_id' in request.data: + # Check if parent motion exists. + if request.data.get('parent_id') is not None: try: - parent_motion = Motion.objects.get(pk=request.data['parent_id']) + parent_motion = CollectionElement.from_values( + Motion.get_collection_string(), + request.data['parent_id']) except Motion.DoesNotExist: raise ValidationError({'detail': _('The parent motion does not exist.')}) + else: + parent_motion = None # Check permission to send some data. if not has_perm(request.user, 'motions.can_manage'): @@ -101,16 +105,15 @@ class MotionViewSet(ModelViewSet): 'reason', 'comments', # This is checked later. ] - if parent_motion: # For creating amendments. + if parent_motion is not None: + # For creating amendments. whitelist.extend([ 'parent_id', 'category_id', # This will be set to the matching 'motion_block_id', # values from parent_motion. ]) - request.data['category_id'] = ( - parent_motion.category.id if parent_motion.category else None) - request.data['motion_block_id'] = ( - parent_motion.motion_block.id if parent_motion.motion_block else None) + request.data['category_id'] = parent_motion.get_full_data().get('category_id') + request.data['motion_block_id'] = parent_motion.get_full_data().get('motion_block_id') for key in request.data.keys(): if key not in whitelist: # Non-staff users are allowed to send only some data. @@ -155,14 +158,17 @@ class MotionViewSet(ModelViewSet): # Check permission to send only some data. if not has_perm(request.user, 'motions.can_manage'): + # Remove fields that the user is not allowed to change. + # The list() is required because we want to use del inside the loop. + keys = list(request.data.keys()) whitelist = ( 'title', 'text', - 'reason',) - keys = list(request.data.keys()) + 'reason', + 'comments', # This is checked later. + ) for key in keys: if key not in whitelist: - # Non-staff users are allowed to send only some data. Ignore other data. del request.data[key] if not has_perm(request.user, 'motions.can_see_and_manage_comments'): try: @@ -364,7 +370,7 @@ class MotionPollViewSet(UpdateModelMixin, DestroyModelMixin, GenericViewSet): """ API endpoint for motion polls. - There are the following views: update and destroy. + There are the following views: update, partial_update and destroy. """ queryset = MotionPoll.objects.all() serializer_class = MotionPollSerializer @@ -414,7 +420,8 @@ class MotionChangeRecommendationViewSet(ModelViewSet): elif self.action == 'metadata': result = has_perm(self.request.user, 'motions.can_see') elif self.action in ('create', 'destroy', 'partial_update', 'update'): - result = has_perm(self.request.user, 'motions.can_manage') + result = (has_perm(self.request.user, 'motions.can_see') and + has_perm(self.request.user, 'motions.can_manage')) else: result = False return result @@ -615,6 +622,8 @@ class WorkflowViewSet(ModelViewSet): return result +# Special API views + class MotionDocxTemplateView(APIView): """ Returns the template for motions docx export diff --git a/tests/unit/motions/test_views.py b/tests/unit/motions/test_views.py index 5835f8916..1d7f71bf1 100644 --- a/tests/unit/motions/test_views.py +++ b/tests/unit/motions/test_views.py @@ -10,6 +10,7 @@ class MotionViewSetCreate(TestCase): """ def setUp(self): self.request = MagicMock() + self.request.data.get.return_value = None self.view_instance = MotionViewSet() self.view_instance.request = self.request self.view_instance.format_kwarg = MagicMock()