diff --git a/autoupdate b/autoupdate index c1211219d..3ede3a13c 160000 --- a/autoupdate +++ b/autoupdate @@ -1 +1 @@ -Subproject commit c1211219d81965b10780ecfa5a1de31f9e30d31e +Subproject commit 3ede3a13cb1b236b3c0d50b9641c318f2eb2727f diff --git a/server/openslides/agenda/views.py b/server/openslides/agenda/views.py index 7629336e9..f0210aaf3 100644 --- a/server/openslides/agenda/views.py +++ b/server/openslides/agenda/views.py @@ -12,8 +12,7 @@ from openslides.utils.rest_api import ( Response, UpdateModelMixin, ValidationError, - detail_route, - list_route, + action, status, ) from openslides.utils.views import TreeSortMixin @@ -143,7 +142,7 @@ class ItemViewSet(ModelViewSet, TreeSortMixin): return response - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def numbering(self, request): """ Auto numbering of the agenda according to the config. Manually added @@ -157,7 +156,7 @@ class ItemViewSet(ModelViewSet, TreeSortMixin): Item.objects.number_all(numeral_system=config["agenda_numeral_system"]) return Response({"detail": "The agenda has been numbered."}) - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def sort(self, request): """ Sorts the whole agenda represented in a tree of ids. The request data should be a list (the root) @@ -172,7 +171,7 @@ class ItemViewSet(ModelViewSet, TreeSortMixin): """ return self.sort_tree(request, Item, "weight", "parent_id") - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) @transaction.atomic def assign(self, request): """ @@ -294,7 +293,7 @@ class ListOfSpeakersViewSet(UpdateModelMixin, TreeSortMixin, GenericViewSet): result = False return result - @detail_route(methods=["POST", "PATCH", "DELETE"]) + @action(detail=True, methods=["POST", "PATCH", "DELETE"]) @transaction.atomic def manage_speaker(self, request, pk=None): """ @@ -443,7 +442,7 @@ class ListOfSpeakersViewSet(UpdateModelMixin, TreeSortMixin, GenericViewSet): return Response() - @detail_route(methods=["PUT", "DELETE"]) + @action(detail=True, methods=["PUT", "DELETE"]) def speak(self, request, pk=None): """ Special view endpoint to begin and end speech of speakers. Send PUT @@ -494,7 +493,7 @@ class ListOfSpeakersViewSet(UpdateModelMixin, TreeSortMixin, GenericViewSet): # Initiate response. return Response({"detail": message}) - @detail_route(methods=["POST"]) + @action(detail=True, methods=["POST"]) def sort_speakers(self, request, pk=None): """ Special view endpoint to sort the list of speakers. @@ -533,7 +532,7 @@ class ListOfSpeakersViewSet(UpdateModelMixin, TreeSortMixin, GenericViewSet): # Initiate response. return Response({"detail": "List of speakers successfully sorted."}) - @detail_route(methods=["POST"]) + @action(detail=True, methods=["POST"]) def readd_last_speaker(self, request, pk=None): """ Special view endpoint to re-add the last finished speaker to the list of speakers. @@ -572,7 +571,7 @@ class ListOfSpeakersViewSet(UpdateModelMixin, TreeSortMixin, GenericViewSet): return Response() - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def delete_all_speakers(self, request): Speaker.objects.all().delete() inform_changed_data(ListOfSpeakers.objects.all()) diff --git a/server/openslides/assignments/views.py b/server/openslides/assignments/views.py index e4fb120ff..1ff0d41d7 100644 --- a/server/openslides/assignments/views.py +++ b/server/openslides/assignments/views.py @@ -7,12 +7,7 @@ from openslides.core.config import config from openslides.poll.views import BaseOptionViewSet, BasePollViewSet, BaseVoteViewSet from openslides.utils.auth import has_perm from openslides.utils.autoupdate import inform_changed_data -from openslides.utils.rest_api import ( - ModelViewSet, - Response, - ValidationError, - detail_route, -) +from openslides.utils.rest_api import ModelViewSet, Response, ValidationError, action from openslides.utils.utils import is_int from .models import ( @@ -63,7 +58,7 @@ class AssignmentViewSet(ModelViewSet): def perform_create(self, serializer): serializer.save(request_user=self.request.user) - @detail_route(methods=["post", "delete"]) + @action(detail=True, methods=["post", "delete"]) def candidature_self(self, request, pk=None): """ View to nominate self as candidate (POST) or withdraw own @@ -116,7 +111,7 @@ class AssignmentViewSet(ModelViewSet): assignment.remove_candidate(request.user) return "You have withdrawn your candidature successfully." - @detail_route(methods=["post", "delete"]) + @action(detail=True, methods=["post", "delete"]) def candidature_other(self, request, pk=None): """ View to nominate other users (POST) or delete their candidature @@ -186,7 +181,7 @@ class AssignmentViewSet(ModelViewSet): {"detail": "Candidate {0} was withdrawn successfully.", "args": [str(user)]} ) - @detail_route(methods=["post"]) + @action(detail=True, methods=["post"]) def sort_related_users(self, request, pk=None): """ Special view endpoint to sort the assignment related users. diff --git a/server/openslides/chat/views.py b/server/openslides/chat/views.py index a47286316..3c83fabad 100644 --- a/server/openslides/chat/views.py +++ b/server/openslides/chat/views.py @@ -14,7 +14,7 @@ from openslides.utils.rest_api import ( GenericViewSet, ModelViewSet, Response, - detail_route, + action, status, ) @@ -52,7 +52,7 @@ class ChatGroupViewSet(ModelViewSet): inform_changed_data(ChatMessage.objects.filter(chatgroup=self.get_object())) return response - @detail_route(methods=["POST"]) + @action(detail=True, methods=["POST"]) def clear(self, request, *args, **kwargs): """ Deletes all chat messages of the group. diff --git a/server/openslides/core/views.py b/server/openslides/core/views.py index 4059c408a..57fe2a68d 100644 --- a/server/openslides/core/views.py +++ b/server/openslides/core/views.py @@ -35,8 +35,7 @@ from ..utils.rest_api import ( ModelViewSet, Response, ValidationError, - detail_route, - list_route, + action, ) from .config import config from .exceptions import ConfigError, ConfigNotFound @@ -186,7 +185,7 @@ class ProjectorViewSet(ModelViewSet): projection_default.save() return super(ProjectorViewSet, self).destroy(*args, **kwargs) - @detail_route(methods=["post"]) + @action(detail=True, methods=["post"]) def project(self, request, pk): """ Sets the `elements` and `elements_preview` and adds one item to the @@ -237,7 +236,7 @@ class ProjectorViewSet(ModelViewSet): projector.save() return Response() - @detail_route(methods=["post"]) + @action(detail=True, methods=["post"]) def control_view(self, request, pk): """ REST API operation to control the projector view, i. e. scale and @@ -296,7 +295,7 @@ class ProjectorViewSet(ModelViewSet): inform_changed_data(projector_instance) return Response() - @detail_route(methods=["post"]) + @action(detail=True, methods=["post"]) def set_scroll(self, request, pk): """ REST API operation to scroll the projector. @@ -315,7 +314,7 @@ class ProjectorViewSet(ModelViewSet): {"detail": "Setting scroll to {0} was successful.", "args": [request.data]} ) - @detail_route(methods=["post"]) + @action(detail=True, methods=["post"]) def set_reference_projector(self, request, pk): """ REST API operation to set the projector with the given pk as the new reference projector for all projectors. @@ -434,7 +433,7 @@ class ConfigViewSet(ModelViewSet): # Return response. return Response({"key": key, "value": value}) - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def bulk_update(self, request): """ Updates many config variables: @@ -467,7 +466,7 @@ class ConfigViewSet(ModelViewSet): return Response({"errors": errors}) - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def reset_groups(self, request): """ Resets multiple groups. The request data contains all diff --git a/server/openslides/mediafiles/views.py b/server/openslides/mediafiles/views.py index 32d46e34b..d74db036f 100644 --- a/server/openslides/mediafiles/views.py +++ b/server/openslides/mediafiles/views.py @@ -15,7 +15,7 @@ from openslides.utils.rest_api import ( ModelViewSet, Response, ValidationError, - list_route, + action, status, ) @@ -178,7 +178,7 @@ class MediafileViewSet(ModelViewSet): inform_changed_data(self.get_object().get_children_deep()) return response - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def move(self, request): """ { @@ -249,7 +249,7 @@ class MediafileViewSet(ModelViewSet): return Response() - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def bulk_delete(self, request): """ Deletes mediafiles *from one directory*. Expected data: diff --git a/server/openslides/motions/views.py b/server/openslides/motions/views.py index 94fbfe5b4..af688a055 100644 --- a/server/openslides/motions/views.py +++ b/server/openslides/motions/views.py @@ -16,14 +16,7 @@ from ..core.config import config from ..core.models import Tag from ..utils.auth import has_perm, in_some_groups from ..utils.autoupdate import inform_changed_data, inform_deleted_data -from ..utils.rest_api import ( - ModelViewSet, - Response, - ReturnDict, - ValidationError, - detail_route, - list_route, -) +from ..utils.rest_api import ModelViewSet, Response, ReturnDict, ValidationError, action from ..utils.views import TreeSortMixin from .models import ( Category, @@ -316,7 +309,7 @@ class MotionViewSet(TreeSortMixin, ModelViewSet): # We do not add serializer.data to response so nobody gets unrestricted data here. return Response() - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def sort(self, request): """ Sorts all motions represented in a tree of ids. The request data should be a list (the root) @@ -331,7 +324,7 @@ class MotionViewSet(TreeSortMixin, ModelViewSet): """ return self.sort_tree(request, Motion, "weight", "sort_parent_id") - @detail_route(methods=["POST", "DELETE"]) + @action(detail=True, methods=["POST", "DELETE"]) def manage_comments(self, request, pk=None): """ Create, update and delete motion comments. @@ -402,7 +395,7 @@ class MotionViewSet(TreeSortMixin, ModelViewSet): return Response({"detail": message}) - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) @transaction.atomic def manage_multiple_submitters(self, request): """ @@ -488,7 +481,7 @@ class MotionViewSet(TreeSortMixin, ModelViewSet): } ) - @detail_route(methods=["post", "delete"]) + @action(detail=True, methods=["post", "delete"]) def support(self, request, pk=None): """ Special view endpoint to support a motion or withdraw support @@ -534,7 +527,7 @@ class MotionViewSet(TreeSortMixin, ModelViewSet): # Initiate response. return Response({"detail": message}) - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) @transaction.atomic def manage_multiple_category(self, request): """ @@ -621,7 +614,7 @@ class MotionViewSet(TreeSortMixin, ModelViewSet): } ) - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) @transaction.atomic def manage_multiple_motion_block(self, request): """ @@ -715,7 +708,7 @@ class MotionViewSet(TreeSortMixin, ModelViewSet): } ) - @detail_route(methods=["put"]) + @action(detail=True, methods=["put"]) def set_state(self, request, pk=None): """ Special view endpoint to set and reset a state of a motion. @@ -774,7 +767,7 @@ class MotionViewSet(TreeSortMixin, ModelViewSet): return Response({"detail": message}) - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) @transaction.atomic def manage_multiple_state(self, request): """ @@ -864,7 +857,7 @@ class MotionViewSet(TreeSortMixin, ModelViewSet): } ) - @detail_route(methods=["put"]) + @action(detail=True, methods=["put"]) def set_recommendation(self, request, pk=None): """ Special view endpoint to set a recommendation of a motion. @@ -926,7 +919,7 @@ class MotionViewSet(TreeSortMixin, ModelViewSet): } ) - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) @transaction.atomic def manage_multiple_recommendation(self, request): """ @@ -1020,7 +1013,7 @@ class MotionViewSet(TreeSortMixin, ModelViewSet): } ) - @detail_route(methods=["post"]) + @action(detail=True, methods=["post"]) def follow_recommendation(self, request, pk=None): motion = self.get_object() if motion.recommendation is None: @@ -1048,7 +1041,7 @@ class MotionViewSet(TreeSortMixin, ModelViewSet): return Response({"detail": "Recommendation followed successfully."}) - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) @transaction.atomic def manage_multiple_tags(self, request): """ @@ -1407,7 +1400,7 @@ class MotionCommentSectionViewSet(ModelViewSet): inform_changed_data(MotionComment.objects.filter(section=section)) return response - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def sort(self, request, *args, **kwargs): """ Changes the sorting of comment sections. Every id must be given exactly once. @@ -1494,7 +1487,7 @@ class CategoryViewSet(TreeSortMixin, ModelViewSet): result = False return result - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def sort_categories(self, request): """ Sorts all categoreis represented in a tree of ids. The request data should be @@ -1510,7 +1503,7 @@ class CategoryViewSet(TreeSortMixin, ModelViewSet): """ return self.sort_tree(request, Category, "weight", "parent_id") - @detail_route(methods=["post"]) + @action(detail=True, methods=["post"]) @transaction.atomic def sort_motions(self, request, pk=None): """ @@ -1556,7 +1549,7 @@ class CategoryViewSet(TreeSortMixin, ModelViewSet): inform_changed_data(motions) return Response() - @detail_route(methods=["post"]) + @action(detail=True, methods=["post"]) def numbering(self, request, pk=None): """ Special view endpoint to number all motions in this category and all @@ -1609,7 +1602,7 @@ class MotionBlockViewSet(ModelViewSet): def perform_create(self, serializer): serializer.save(request_user=self.request.user) - @detail_route(methods=["post"]) + @action(detail=True, methods=["post"]) def follow_recommendations(self, request, pk=None): """ View to set the states of all motions of this motion block each to diff --git a/server/openslides/poll/views.py b/server/openslides/poll/views.py index e7bb24496..06b4eaa5b 100644 --- a/server/openslides/poll/views.py +++ b/server/openslides/poll/views.py @@ -14,7 +14,7 @@ from openslides.utils.rest_api import ( ModelViewSet, Response, ValidationError, - detail_route, + action, ) from .models import BasePoll @@ -119,7 +119,7 @@ class BasePollViewSet(ModelViewSet): # The implementation can be found in concret view set e. g. MotionPollViewSet. pass - @detail_route(methods=["POST"]) + @action(detail=True, methods=["POST"]) @transaction.atomic def start(self, request, pk): poll = self.get_object() @@ -132,7 +132,7 @@ class BasePollViewSet(ModelViewSet): self.extend_history_information(["Voting started"]) return Response() - @detail_route(methods=["POST"]) + @action(detail=True, methods=["POST"]) @transaction.atomic def stop(self, request, pk): poll = self.get_object() @@ -152,7 +152,7 @@ class BasePollViewSet(ModelViewSet): self.extend_history_information(["Voting stopped"]) return Response() - @detail_route(methods=["POST"]) + @action(detail=True, methods=["POST"]) @transaction.atomic def publish(self, request, pk): poll = self.get_object() @@ -172,7 +172,7 @@ class BasePollViewSet(ModelViewSet): inform_changed_data(poll.get_options()) return Response() - @detail_route(methods=["POST"]) + @action(detail=True, methods=["POST"]) @transaction.atomic def pseudoanonymize(self, request, pk): poll = self.get_object() @@ -188,7 +188,7 @@ class BasePollViewSet(ModelViewSet): self.extend_history_information(["Voting anonymized"]) return Response() - @detail_route(methods=["POST"]) + @action(detail=True, methods=["POST"]) @transaction.atomic def reset(self, request, pk): poll = self.get_object() @@ -196,7 +196,7 @@ class BasePollViewSet(ModelViewSet): self.extend_history_information(["Voting reset"]) return Response() - @detail_route(methods=["POST"]) + @action(detail=True, methods=["POST"]) @transaction.atomic def vote(self, request, pk): """ @@ -254,7 +254,7 @@ class BasePollViewSet(ModelViewSet): return Response() - @detail_route(methods=["POST"]) + @action(detail=True, methods=["POST"]) @transaction.atomic def refresh(self, request, pk): poll = self.get_object() diff --git a/server/openslides/users/views.py b/server/openslides/users/views.py index c605c0302..6dc2537a2 100644 --- a/server/openslides/users/views.py +++ b/server/openslides/users/views.py @@ -42,8 +42,7 @@ from ..utils.rest_api import ( Response, SimpleMetadata, ValidationError, - detail_route, - list_route, + action, status, ) from ..utils.validate import validate_json @@ -260,7 +259,7 @@ class UserViewSet(ModelViewSet): self.perform_destroy(instance) return Response(status=status.HTTP_204_NO_CONTENT) - @detail_route(methods=["post"]) + @action(detail=True, methods=["post"]) def reset_password(self, request, pk=None): """ View to reset the password of the given user (by url) using a provided password. @@ -287,7 +286,7 @@ class UserViewSet(ModelViewSet): user.save() return Response({"detail": "Password successfully reset."}) - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def bulk_generate_passwords(self, request): """ Generates new random passwords for many users. The request user is excluded @@ -307,7 +306,7 @@ class UserViewSet(ModelViewSet): user.save() return Response() - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def bulk_reset_passwords_to_default(self, request): """ resets the password of all given users to their default ones. The @@ -339,7 +338,7 @@ class UserViewSet(ModelViewSet): user.save() return Response() - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def bulk_set_state(self, request): """ Sets the "state" of may users. The "state" means boolean attributes like active @@ -377,7 +376,7 @@ class UserViewSet(ModelViewSet): return Response() - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def bulk_alter_groups(self, request): """ Adds or removes groups from given users. The request user is excluded. @@ -410,7 +409,7 @@ class UserViewSet(ModelViewSet): inform_changed_data(users) return Response() - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def bulk_delete(self, request): """ Deletes many users. The request user will be excluded. Expected data: @@ -437,7 +436,7 @@ class UserViewSet(ModelViewSet): queryset = queryset.filter(auth_type=auth_type) return queryset.exclude(pk=request.user.id).filter(pk__in=ids) - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) @transaction.atomic def mass_import(self, request): """ @@ -490,7 +489,7 @@ class UserViewSet(ModelViewSet): } ) - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) def mass_invite_email(self, request): """ Endpoint to send invitation emails to all given users (by id). Returns the @@ -664,7 +663,7 @@ class GroupViewSet(ModelViewSet): inform_changed_data(affected_users) return Response(status=status.HTTP_204_NO_CONTENT) - @detail_route(methods=["post"]) + @action(detail=True, methods=["post"]) @transaction.atomic def set_permission(self, request, *args, **kwargs): """ @@ -764,7 +763,7 @@ class PersonalNoteViewSet(ModelViewSet): result = False return result - @list_route(methods=["post"]) + @action(detail=False, methods=["post"]) @transaction.atomic def create_or_update(self, request, *args, **kwargs): """ diff --git a/server/openslides/utils/rest_api.py b/server/openslides/utils/rest_api.py index 51ead6fdd..20b1505f7 100644 --- a/server/openslides/utils/rest_api.py +++ b/server/openslides/utils/rest_api.py @@ -3,7 +3,7 @@ from typing import Any, Dict, Iterable, Type from django.db.models import Model from rest_framework import status -from rest_framework.decorators import detail_route, list_route +from rest_framework.decorators import action from rest_framework.exceptions import APIException from rest_framework.metadata import SimpleMetadata from rest_framework.mixins import ( @@ -43,9 +43,8 @@ from . import logging __all__ = [ "APIException", - "detail_route", + "action", "DecimalField", - "list_route", "SimpleMetadata", "DestroyModelMixin", "CharField", diff --git a/server/requirements/production.txt b/server/requirements/production.txt index 9278ff1e1..232b18df5 100644 --- a/server/requirements/production.txt +++ b/server/requirements/production.txt @@ -12,7 +12,7 @@ aioredis>=1.1.0,<1.3 bleach>=3.3.0 daphne>=2.2,<2.5 Django>=2.1,<2.3 -djangorestframework>=3.9.4,<3.10 +djangorestframework>=3.11.2 jsonfield2>=3.0,<3.1 attrs>=19.2.0 jsonschema>=3.0,<3.1