OpenSlides/openslides/motions/views.py
Norman Jäckel b30afbd635 Added several motion REST API views.
Added motion creation view, motion update view, version permit and delete view, view to support motions, view to set and reset state. Refactored motion submitters and supporters.
2015-05-13 12:22:50 +02:00

396 lines
15 KiB
Python

from django.http import Http404
from django.utils.text import slugify
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_noop
from django.shortcuts import get_object_or_404
from reportlab.platypus import SimpleDocTemplate
from rest_framework import status
from openslides.config.api import config
from openslides.utils.rest_api import ModelViewSet, Response, ValidationError, detail_route
from openslides.utils.views import (PDFView, SingleObjectMixin)
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, MotionSerializer, WorkflowSerializer
class MotionViewSet(ModelViewSet):
"""
API endpoint to list, retrieve, create, update and destroy motions.
"""
queryset = Motion.objects.all()
serializer_class = MotionSerializer
def check_permissions(self, request):
"""
Calls self.permission_denied() if the requesting user has not the
permission to see motions and in case of destroy requests the
permission to manage motions.
"""
if (not request.user.has_perm('motions.can_see') or
(self.action == 'destroy' and not request.user.has_perm('motions.can_manage'))):
self.permission_denied(request)
def create(self, request, *args, **kwargs):
"""
Customized view endpoint to create a new motion.
Checks also whether the requesting user can submit a new motion. He
needs at least the permissions 'motions.can_see' (see
self.check_permission()) and 'motions.can_create'. If the
submitting of new motions by non-staff users is stopped via config
variable 'motion_stop_submitting', the requesting user needs also
to have the permission 'motions.can_manage'.
"""
# Check permissions.
if (not request.user.has_perm('motions.can_create') or
(not config['motion_stop_submitting'] and
not request.user.has_perm('motions.can_manage'))):
self.permission_denied(request)
# Check permission to send submitter and supporter data.
if (not request.user.has_perm('motions.can_manage') and
(request.data.getlist('submitters') or request.data.getlist('supporters'))):
# Non-staff users are not allowed to send submitter or supporter data.
self.permission_denied(request)
# Validate data and create motion.
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
motion = serializer.save(request_user=request.user)
# Write the log message and initiate response.
motion.write_log([ugettext_noop('Motion created')], request.user)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def update(self, request, *args, **kwargs):
"""
Customized view endpoint to update a motion.
Checks also whether the requesting user can update the motion. He
needs at least the permissions 'motions.can_see' (see
self.check_permission()). Also the instance method
get_allowed_actions() is evaluated.
"""
# Get motion.
motion = self.get_object()
# Check permissions.
if not motion.get_allowed_actions(request.user)['update']:
self.permission_denied(request)
# Check permission to send submitter and supporter data.
if (not request.user.has_perm('motions.can_manage') and
(request.data.getlist('submitters') or request.data.getlist('supporters'))):
# Non-staff users are not allowed to send submitter or supporter data.
self.permission_denied(request)
# Validate data and update motion.
serializer = self.get_serializer(
motion,
data=request.data,
partial=kwargs.get('partial', False))
serializer.is_valid(raise_exception=True)
updated_motion = serializer.save()
# Write the log message, check removal of supporters and initiate response.
# TODO: Log if a version was updated.
updated_motion.write_log([ugettext_noop('Motion updated')], request.user)
if (config['motion_remove_supporters'] and updated_motion.state.allow_support and
not request.user.has_perm('motions.can_manage')):
updated_motion.supporters.clear()
updated_motion.write_log([ugettext_noop('All supporters removed')], request.user)
return Response(serializer.data)
@detail_route(methods=['put', 'delete'])
def manage_version(self, request, pk=None):
"""
Special view endpoint to permit and delete a version of a motion.
Send PUT {'version_number': <number>} to permit and DELETE
{'version_number': <number>} to delete a version. Deleting the
active version is not allowed. Only managers can use this view.
"""
# Check permission.
if not request.user.has_perm('motions.can_manage'):
self.permission_denied(request)
# Retrieve motion and version.
motion = self.get_object()
version_number = request.data.get('version_number')
try:
version = motion.versions.get(version_number=version_number)
except MotionVersion.DoesNotExist:
raise Http404('Version %s not found.' % version_number)
# Permit or delete version.
if request.method == 'PUT':
# Permit version.
motion.active_version = version
motion.save(update_fields=['active_version'])
motion.write_log(
message_list=[ugettext_noop('Version'),
' %d ' % version.version_number,
ugettext_noop('permitted')],
person=self.request.user)
message = _('Version %d permitted successfully.') % version.version_number
else:
# Delete version.
# request.method == 'DELETE'
if version == motion.active_version:
raise ValidationError({'detail': _('You can not delete the active version of a motion.')})
version.delete()
motion.write_log(
message_list=[ugettext_noop('Version'),
' %d ' % version.version_number,
ugettext_noop('deleted')],
person=self.request.user)
message = _('Version %d deleted successfully.') % version.version_number
# Initiate response.
return Response({'detail': message})
@detail_route(methods=['post', 'delete'])
def support(self, request, pk=None):
"""
Special view endpoint to support a motion or withdraw support
(unsupport).
Send POST to support and DELETE to unsupport.
Checks also whether the requesting user can do this. He needs at
least the permissions 'motions.can_see' (see
self.check_permission()). Also the the permission
'motions.can_support' is required and the instance method
get_allowed_actions() is evaluated.
"""
# Check permission.
if not request.user.has_perm('motions.can_support'):
self.permission_denied(request)
# Retrieve motion and allowed actions.
motion = self.get_object()
allowed_actions = motion.get_allowed_actions(request.user)
# Support or unsupport motion.
if request.method == 'POST':
# Support motion.
if not allowed_actions['support']:
raise ValidationError({'detail': _('You can not support this motion.')})
motion.supporters.add(request.user)
motion.write_log([ugettext_noop('Motion supported')], request.user)
message = _('You have supported this motion successfully.')
else:
# Unsupport motion.
# request.method == 'DELETE'
if not allowed_actions['unsupport']:
raise ValidationError({'detail': _('You can not unsupport this motion.')})
motion.supporters.remove(request.user)
motion.write_log([ugettext_noop('Motion unsupported')], request.user)
message = _('You have unsupported this motion successfully.')
# Initiate response.
return Response({'detail': message})
@detail_route(methods=['put'])
def set_state(self, request, pk=None):
"""
Special view endpoint to set and reset a state of a motion.
Send PUT {'state': <state_id>} to set and just PUT {} to reset the
state. Only managers can use this view.
"""
# Check permission.
if not request.user.has_perm('motions.can_manage'):
self.permission_denied(request)
# Retrieve motion and state.
motion = self.get_object()
state = request.data.get('state')
# Set or reset state.
if state is not None:
# Check data and set state.
try:
state_id = int(state)
except ValueError:
raise ValidationError({'detail': _('Invalid data. State must be an integer.')})
if state_id not in [item.id for item in motion.state.next_states.all()]:
raise ValidationError(
{'detail': _('You can not set the state to %(state_id)d.') % {'state_id': state_id}})
motion.set_state(state_id)
else:
# Reset state.
motion.reset_state()
# Save motion.
motion.save(update_fields=['state', 'identifier'])
message = _('The state of the motion was set to %s.') % motion.state.name
# Write the log message and initiate response.
motion.write_log(
message_list=[ugettext_noop('State set to'), ' ', motion.state.name],
person=request.user)
return Response({'detail': message})
class PollMixin(object):
"""
Mixin for the PollUpdateView and the PollDeleteView.
"""
required_permission = 'motions.can_manage'
success_url_name = 'motion_detail'
def get_object(self):
"""
Return a MotionPoll object.
Use the motion id and the poll_number from the url kwargs to get the
object.
"""
try:
obj = self._object
except AttributeError:
queryset = MotionPoll.objects.filter(
motion=self.kwargs['pk'],
poll_number=self.kwargs['poll_number'])
obj = get_object_or_404(queryset)
self._object = obj
return obj
def get_url_name_args(self):
"""
Return the arguments to create the url to the success_url.
"""
return [self.get_object().motion.pk]
class PollPDFView(PollMixin, PDFView):
"""
Generates a ballotpaper.
"""
required_permission = 'motions.can_manage'
top_space = 0
def get_filename(self):
"""
Return the filename for the PDF.
"""
return u'%s%s_%s' % (_("Motion"), str(self.get_object().poll_number), _("Poll"))
def get_template(self, buffer):
return SimpleDocTemplate(
buffer, topMargin=-6, bottomMargin=-6, leftMargin=0, rightMargin=0,
showBoundary=False)
def build_document(self, pdf_document, story):
pdf_document.build(story)
def append_to_pdf(self, pdf):
"""
Append PDF objects.
"""
motion_poll_to_pdf(pdf, self.get_object())
class MotionPDFView(SingleObjectMixin, PDFView):
"""
Create the PDF for one or for all motions.
If self.print_all_motions is True, the view returns a PDF with all motions.
If self.print_all_motions is False, the view returns a PDF with only one
motion.
"""
model = Motion
top_space = 0
print_all_motions = False
def check_permission(self, request, *args, **kwargs):
"""
Checks if the requesting user has the permission to see the motion as
PDF.
"""
if self.print_all_motions:
is_allowed = request.user.has_perm('motions.can_see')
else:
is_allowed = self.get_object().get_allowed_actions(request.user)['see']
return is_allowed
def get_object(self, *args, **kwargs):
if self.print_all_motions:
obj = None
else:
obj = super().get_object(*args, **kwargs)
return obj
def get_filename(self):
"""
Return the filename for the PDF.
"""
if self.print_all_motions:
return _("Motions")
else:
if self.get_object().identifier:
suffix = self.get_object().identifier.replace(' ', '')
else:
suffix = self.get_object().title.replace(' ', '_')
suffix = slugify(suffix)
return '%s-%s' % (_("Motion"), suffix)
def append_to_pdf(self, pdf):
"""
Append PDF objects.
"""
if self.print_all_motions:
motions = []
for motion in Motion.objects.all():
if (not motion.state.required_permission_to_see or
self.request.user.has_perm(motion.state.required_permission_to_see)):
motions.append(motion)
motions_to_pdf(pdf, motions)
else:
motion_to_pdf(pdf, self.get_object())
class CategoryViewSet(ModelViewSet):
"""
API endpoint to list, retrieve, create, update and destroy categories.
"""
queryset = Category.objects.all()
serializer_class = CategorySerializer
def check_permissions(self, request):
"""
Calls self.permission_denied() if the requesting user has not the
permission to see motions and in case of create, update or destroy
requests the permission to manage motions.
"""
if (not request.user.has_perm('motions.can_see') or
(self.action in ('create', 'update', 'destroy') and not
request.user.has_perm('motions.can_manage'))):
self.permission_denied(request)
class WorkflowViewSet(ModelViewSet):
"""
API endpoint to list, retrieve, create, update and destroy workflows.
"""
queryset = Workflow.objects.all()
serializer_class = WorkflowSerializer
def check_permissions(self, request):
"""
Calls self.permission_denied() if the requesting user has not the
permission to see motions and in case of create, update or destroy
requests the permission to manage motions.
"""
if (not request.user.has_perm('motions.can_see') or
(self.action in ('create', 'update', 'destroy') and not
request.user.has_perm('motions.can_manage'))):
self.permission_denied(request)