Merge pull request #1537 from normanjaeckel/RESTPoll

Added create, updated and destroy view for assignment polls.
This commit is contained in:
Oskar Hahn 2015-06-15 16:34:11 +02:00
commit a604f36634
5 changed files with 118 additions and 18 deletions

View File

@ -17,7 +17,7 @@ class AssignmentAppConfig(AppConfig):
from openslides.utils.signals import template_manipulation from openslides.utils.signals import template_manipulation
from .signals import setup_assignment_config from .signals import setup_assignment_config
from .template import add_assignment_stylesheets from .template import add_assignment_stylesheets
from .views import AssignmentViewSet from .views import AssignmentViewSet, AssignmentPollViewSet
# Connect signals. # Connect signals.
config_signal.connect(setup_assignment_config, dispatch_uid='setup_assignment_config') config_signal.connect(setup_assignment_config, dispatch_uid='setup_assignment_config')
@ -33,3 +33,4 @@ class AssignmentAppConfig(AppConfig):
# Register viewsets. # Register viewsets.
router.register('assignments/assignment', AssignmentViewSet) router.register('assignments/assignment', AssignmentViewSet)
router.register('assignments/assignmentpoll', AssignmentPollViewSet)

View File

@ -247,7 +247,7 @@ class Assignment(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
def create_poll(self): def create_poll(self):
""" """
Creates an new poll for the assignment and adds all candidates to all Creates a new poll for the assignment and adds all candidates to all
lists of speakers of related agenda items. lists of speakers of related agenda items.
""" """
candidates = self.candidates.all() candidates = self.candidates.all()

View File

@ -1,4 +1,13 @@
from openslides.utils.rest_api import ListSerializer, ModelSerializer from django.db import transaction
from django.utils.translation import ugettext as _
from openslides.utils.rest_api import (
DictField,
IntegerField,
ListField,
ListSerializer,
ModelSerializer,
ValidationError)
from .models import ( from .models import (
models, models,
@ -64,6 +73,10 @@ class AssignmentAllPollSerializer(ModelSerializer):
Serializes all polls. Serializes all polls.
""" """
assignmentoption_set = AssignmentOptionSerializer(many=True, read_only=True) assignmentoption_set = AssignmentOptionSerializer(many=True, read_only=True)
votes = ListField(
child=DictField(
child=IntegerField(min_value=-2)),
write_only=True)
class Meta: class Meta:
model = AssignmentPoll model = AssignmentPoll
@ -75,7 +88,46 @@ class AssignmentAllPollSerializer(ModelSerializer):
'assignmentoption_set', 'assignmentoption_set',
'votesvalid', 'votesvalid',
'votesinvalid', 'votesinvalid',
'votescast',) 'votescast',
'votes',)
read_only_fields = ('yesnoabstain',)
@transaction.atomic
def update(self, instance, validated_data):
"""
Customized update method for polls. To update votes use the write
only field 'votes'.
Example data for a 'yesnoabstain' poll with two candidates:
"votes": [{"Yes": 10, "No": 4, "Abstain": -2},
{"Yes": -1, "No": 0, "Abstain": -2}]
"""
# Update votes.
votes = validated_data.get('votes')
if votes:
options = list(instance.get_options())
if len(votes) != len(options):
raise ValidationError({
'detail': _('You have to submit data for %d candidates.') % len(options)})
for index, option in enumerate(options):
if len(votes[index]) != 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[index].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(option, votes[index])
# Update remaining writeable fields.
instance.description = validated_data.get('description', instance.description)
instance.published = validated_data.get('published', instance.published)
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 AssignmentShortPollSerializer(AssignmentAllPollSerializer): class AssignmentShortPollSerializer(AssignmentAllPollSerializer):

View File

@ -1,5 +1,6 @@
from cgi import escape from cgi import escape
from django.db import transaction
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ungettext from django.utils.translation import ungettext
from reportlab.lib import colors from reportlab.lib import colors
@ -10,34 +11,46 @@ from reportlab.platypus import (PageBreak, Paragraph, SimpleDocTemplate, Spacer,
from openslides.config.api import config from openslides.config.api import config
from openslides.users.models import Group, User # TODO: remove this from openslides.users.models import Group, User # TODO: remove this
from openslides.utils.pdf import stylesheet from openslides.utils.pdf import stylesheet
from openslides.utils.rest_api import ModelViewSet, Response, ValidationError, detail_route from openslides.utils.rest_api import (
DestroyModelMixin,
GenericViewSet,
ModelViewSet,
Response,
UpdateModelMixin,
ValidationError,
detail_route)
from openslides.utils.views import PDFView from openslides.utils.views import PDFView
from .models import Assignment, AssignmentPoll from .models import Assignment, AssignmentPoll
from .serializers import AssignmentFullSerializer, AssignmentShortSerializer from .serializers import (
AssignmentAllPollSerializer,
AssignmentFullSerializer,
AssignmentShortSerializer
)
class AssignmentViewSet(ModelViewSet): class AssignmentViewSet(ModelViewSet):
""" """
API endpoint to list, retrieve, create, update and destroy assignments and API endpoint to list, retrieve, create, update and destroy assignments
to manage candidatures. and to manage candidatures.
""" """
queryset = Assignment.objects.all() queryset = Assignment.objects.all()
def check_permissions(self, request): def check_permissions(self, request):
""" """
Calls self.permission_denied() if the requesting user has not the Calls self.permission_denied() if the requesting user has not the
permission to see assignments and in case of create, update or destroy permission to see assignments and in case of create, update,
requests the permission to manage assignments. partial_update or destroy requests the permission to manage
assignments.
""" """
if (not request.user.has_perm('assignments.can_see') or if (not request.user.has_perm('assignments.can_see') or
(self.action in ('create', 'update', 'destroy') and not (self.action in ('create', 'update', 'partial_update', 'destroy') and
request.user.has_perm('assignments.can_manage'))): not request.user.has_perm('assignments.can_manage'))):
self.permission_denied(request) self.permission_denied(request)
def get_serializer_class(self): def get_serializer_class(self):
""" """
Returns different serializer classes with respect to users permissions. Returns different serializer classes according to users permissions.
""" """
if self.request.user.has_perm('assignments.can_manage'): if self.request.user.has_perm('assignments.can_manage'):
serializer_class = AssignmentFullSerializer serializer_class = AssignmentFullSerializer
@ -48,8 +61,8 @@ class AssignmentViewSet(ModelViewSet):
@detail_route(methods=['post', 'delete']) @detail_route(methods=['post', 'delete'])
def candidature_self(self, request, pk=None): def candidature_self(self, request, pk=None):
""" """
View to nominate self as candidate (POST) or withdraw own candidature View to nominate self as candidate (POST) or withdraw own
(DELETE). candidature (DELETE).
""" """
if not request.user.has_perm('assignments.can_nominate_self'): if not request.user.has_perm('assignments.can_nominate_self'):
self.permission_denied(request) self.permission_denied(request)
@ -157,8 +170,8 @@ class AssignmentViewSet(ModelViewSet):
@detail_route(methods=['post', 'delete']) @detail_route(methods=['post', 'delete'])
def mark_elected(self, request, pk=None): def mark_elected(self, request, pk=None):
""" """
View to mark other users as elected (POST) undo this (DELETE). The View to mark other users as elected (POST) or undo this (DELETE).
client has to send {'user': <id>}. The client has to send {'user': <id>}.
""" """
if not request.user.has_perm('assignments.can_manage'): if not request.user.has_perm('assignments.can_manage'):
self.permission_denied(request) self.permission_denied(request)
@ -178,6 +191,37 @@ class AssignmentViewSet(ModelViewSet):
message = _('User %s was successfully unelected.') % user message = _('User %s was successfully unelected.') % user
return Response({'detail': message}) 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.
"""
if not request.user.has_perm('assignments.can_manage'):
self.permission_denied(request)
assignment = self.get_object()
if not assignment.candidates.exists():
raise ValidationError({'detail': _('Can not create poll because there are no candidates.')})
with transaction.atomic():
assignment.create_poll()
return Response({'detail': _(' Poll created successfully.')})
class AssignmentPollViewSet(UpdateModelMixin, DestroyModelMixin, GenericViewSet):
"""
API endpoint to update and destroy assignment polls.
"""
queryset = AssignmentPoll.objects.all()
serializer_class = AssignmentAllPollSerializer
def check_permissions(self, request):
"""
Calls self.permission_denied() if the requesting user has not the
permission to see assignments and to manage assignments.
"""
if (not request.user.has_perm('assignments.can_see') or
not request.user.has_perm('assignments.can_manage')):
self.permission_denied(request)
class AssignmentPDF(PDFView): class AssignmentPDF(PDFView):
required_permission = 'assignments.can_see' required_permission = 'assignments.can_see'

View File

@ -6,17 +6,20 @@ from django.core.urlresolvers import reverse
from rest_framework.decorators import detail_route # noqa from rest_framework.decorators import detail_route # noqa
from rest_framework.serializers import ( # noqa from rest_framework.serializers import ( # noqa
CharField, CharField,
DictField,
Field, Field,
IntegerField, IntegerField,
ListField,
ListSerializer, ListSerializer,
ModelSerializer, ModelSerializer,
PrimaryKeyRelatedField, PrimaryKeyRelatedField,
RelatedField, RelatedField,
SerializerMethodField, SerializerMethodField,
ValidationError) ValidationError)
from rest_framework.mixins import DestroyModelMixin, UpdateModelMixin # noqa
from rest_framework.response import Response # noqa from rest_framework.response import Response # noqa
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet, ViewSet # noqa from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet, ViewSet # noqa
from rest_framework.decorators import list_route # noqa from rest_framework.decorators import list_route # noqa
from .exceptions import OpenSlidesError from .exceptions import OpenSlidesError