diff --git a/openslides/assignments/views.py b/openslides/assignments/views.py index e5c2b045d..2e760fa4b 100644 --- a/openslides/assignments/views.py +++ b/openslides/assignments/views.py @@ -216,7 +216,7 @@ class AssignmentViewSet(ModelViewSet): raise ValidationError({'detail': _('Can not create poll because there are no candidates.')}) with transaction.atomic(): assignment.create_poll() - return Response({'detail': _(' Poll created successfully.')}) + return Response({'detail': _('Poll created successfully.')}) class AssignmentPollViewSet(UpdateModelMixin, DestroyModelMixin, GenericViewSet): diff --git a/openslides/motions/apps.py b/openslides/motions/apps.py index c5c62bd2b..4ff94b6ac 100644 --- a/openslides/motions/apps.py +++ b/openslides/motions/apps.py @@ -18,7 +18,7 @@ class MotionsAppConfig(AppConfig): from openslides.core.signals import config_signal from openslides.utils.rest_api import router from .signals import create_builtin_workflows, setup_motion_config - from .views import CategoryViewSet, MotionViewSet, WorkflowViewSet + from .views import CategoryViewSet, MotionViewSet, MotionPollViewSet, WorkflowViewSet # Connect signals. config_signal.connect(setup_motion_config, dispatch_uid='setup_motion_config') @@ -27,4 +27,5 @@ class MotionsAppConfig(AppConfig): # Register viewsets. router.register('motions/category', CategoryViewSet) router.register('motions/motion', MotionViewSet) + router.register('motions/motionpoll', MotionPollViewSet) router.register('motions/workflow', WorkflowViewSet) diff --git a/openslides/motions/serializers.py b/openslides/motions/serializers.py index 8ecbd9d0e..dd36f98a8 100644 --- a/openslides/motions/serializers.py +++ b/openslides/motions/serializers.py @@ -4,6 +4,7 @@ from django.utils.translation import ugettext as _ from openslides.core.config import config from openslides.utils.rest_api import ( CharField, + DictField, IntegerField, ModelSerializer, PrimaryKeyRelatedField, @@ -107,15 +108,50 @@ class MotionPollSerializer(ModelSerializer): Serializer for motion.models.MotionPoll objects. """ motionoption_set = MotionOptionSerializer(many=True, read_only=True) + votes = DictField( + child=IntegerField(min_value=-2), + write_only=True) class Meta: model = MotionPoll fields = ( + 'id', 'poll_number', 'motionoption_set', 'votesvalid', 'votesinvalid', - 'votescast',) + 'votescast', + 'votes',) + read_only_fields = ('poll_number',) + + @transaction.atomic + def update(self, instance, validated_data): + """ + Customized update method for polls. To update votes use the write + only field 'votes'. + + Example data: + + "votes": {"Yes": 10, "No": 4, "Abstain": -2} + """ + # Update votes. + votes = validated_data.get('votes') + if votes: + if len(votes) != len(instance.get_vote_values()): + raise ValidationError({ + 'detail': _('You have to submit data for %d vote values.') % len(instance.get_vote_values())}) + for vote_value, vote_weight in votes.items(): + if vote_value not in instance.get_vote_values(): + raise ValidationError({ + 'detail': _('Vote value %s is invalid.') % vote_value}) + instance.set_vote_objects_with_values(instance.get_options().get(), votes) + + # Update remaining writeable fields. + instance.votesvalid = validated_data.get('votesvalid', instance.votesvalid) + instance.votesinvalid = validated_data.get('votesinvalid', instance.votesinvalid) + instance.votescast = validated_data.get('votescast', instance.votescast) + instance.save() + return instance class MotionVersionSerializer(ModelSerializer): diff --git a/openslides/motions/views.py b/openslides/motions/views.py index 26c3bd5cd..d9a991c4c 100644 --- a/openslides/motions/views.py +++ b/openslides/motions/views.py @@ -1,3 +1,4 @@ +from django.db import transaction from django.http import Http404 from django.shortcuts import get_object_or_404 from django.utils.text import slugify @@ -8,17 +9,22 @@ from rest_framework import status from openslides.core.config import config from openslides.utils.rest_api import ( + DestroyModelMixin, + GenericViewSet, ModelViewSet, Response, + UpdateModelMixin, ValidationError, detail_route, ) from openslides.utils.views import PDFView, SingleObjectMixin +from .exceptions import WorkflowError from .models import Category, Motion, MotionPoll, MotionVersion, Workflow from .pdf import motion_poll_to_pdf, motion_to_pdf, motions_to_pdf from .serializers import ( CategorySerializer, + MotionPollSerializer, MotionSerializer, WorkflowSerializer, ) @@ -31,7 +37,8 @@ class MotionViewSet(ModelViewSet): API endpoint for motions. There are the following views: metadata, list, retrieve, create, - partial_update, update, destroy, manage_version, support and set_state. + partial_update, update, destroy, manage_version, support, set_state and + create_poll. """ queryset = Motion.objects.all() serializer_class = MotionSerializer @@ -49,7 +56,7 @@ class MotionViewSet(ModelViewSet): self.request.user.has_perm('motions.can_create') and (not config['motions_stop_submitting'] or self.request.user.has_perm('motions.can_manage'))) - elif self.action in ('destroy', 'manage_version', 'set_state'): + elif self.action in ('destroy', 'manage_version', 'set_state', 'create_poll'): result = (self.request.user.has_perm('motions.can_see') and self.request.user.has_perm('motions.can_manage')) elif self.action == 'support': @@ -231,6 +238,36 @@ class MotionViewSet(ModelViewSet): person=request.user) return Response({'detail': message}) + @detail_route(methods=['post']) + def create_poll(self, request, pk=None): + """ + View to create a poll. It is a POST request without any data. + """ + motion = self.get_object() + try: + with transaction.atomic(): + motion.create_poll() + except WorkflowError as e: + raise ValidationError({'detail': e}) + return Response({'detail': _('Poll created successfully.')}) + + +class MotionPollViewSet(UpdateModelMixin, DestroyModelMixin, GenericViewSet): + """ + API endpoint for motion polls. + + There are the following views: update and destroy. + """ + queryset = MotionPoll.objects.all() + serializer_class = MotionPollSerializer + + def check_view_permissions(self): + """ + Returns True if the user has required permissions. + """ + return (self.request.user.has_perm('motions.can_see') and + self.request.user.has_perm('motions.can_manage')) + class CategoryViewSet(ModelViewSet): """