Added view to add and remove users from the list of speakers.

This commit is contained in:
Norman Jäckel 2015-05-26 18:21:30 +02:00
parent 9c51313a82
commit 95be18b78e
3 changed files with 262 additions and 2 deletions

View File

@ -4,6 +4,7 @@ from cgi import escape
from collections import defaultdict from collections import defaultdict
from json import dumps from json import dumps
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.staticfiles.templatetags.staticfiles import static from django.contrib.staticfiles.templatetags.staticfiles import static
from django.template.loader import render_to_string from django.template.loader import render_to_string
@ -18,8 +19,15 @@ from openslides.projector.api import (
get_active_object, get_active_object,
get_projector_overlays_js, get_projector_overlays_js,
get_overlays) get_overlays)
from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.pdf import stylesheet from openslides.utils.pdf import stylesheet
from openslides.utils.rest_api import ModelViewSet, list_route, Response from openslides.utils.rest_api import (
ModelViewSet,
Response,
ValidationError,
detail_route,
list_route,
)
from openslides.utils.views import ( from openslides.utils.views import (
AjaxMixin, AjaxMixin,
PDFView, PDFView,
@ -27,7 +35,7 @@ from openslides.utils.views import (
SingleObjectMixin, SingleObjectMixin,
TemplateView) TemplateView)
from .models import Item from .models import Item, Speaker
from .serializers import ItemSerializer from .serializers import ItemSerializer
@ -208,6 +216,87 @@ class ItemViewSet(ModelViewSet):
queryset = queryset.exclude(type__exact=Item.ORGANIZATIONAL_ITEM) queryset = queryset.exclude(type__exact=Item.ORGANIZATIONAL_ITEM)
return queryset return queryset
@detail_route(methods=['POST', 'DELETE'])
def manage_speaker(self, request, pk=None):
"""
Special view endpoint to add users to the list of speakers or remove
them. Send POST {'user': <user_id>} to add a new speaker. Omit
data to add yourself. Send DELETE {'speaker': <speaker_id>} to remove
someone from the list of speakers. Omit data to remove yourself.
Checks also whether the requesting user can do this. He needs at
least the permissions 'agenda.can_see' (see
self.check_permission()). In case of adding himself the permission
'agenda.can_be_speaker' is required. In case of adding someone else
the permission 'agenda.can_manage' is required. In case of removing
someone else 'agenda.can_manage' is required. In case of removing
himself no other permission is required.
"""
# Retrieve item.
item = self.get_object()
if request.method == 'POST':
# Retrieve user_id
user_id = request.data.get('user')
# Check permissions and other conditions. Get user instance.
if user_id is None:
# Add oneself
if not self.request.user.has_perm('agenda.can_be_speaker'):
self.permission_denied(request)
if item.speaker_list_closed:
raise ValidationError({'detail': _('The list of speakers is closed.')})
user = self.request.user
else:
# Add someone else.
if not self.request.user.has_perm('agenda.can_manage'):
self.permission_denied(request)
try:
user = get_user_model().objects.get(pk=int(user_id))
except (ValueError, get_user_model().DoesNotExist):
raise ValidationError({'detail': _('User does not exist.')})
# Try to add the user. This ensurse that a user is not twice in the
# list of coming speakers.
try:
Speaker.objects.add(user, item)
except OpenSlidesError as e:
raise ValidationError({'detail': e})
message = _('User was successfully added to the list of speakers.')
else:
# request.method == 'DELETE'
# Retrieve speaker_id
speaker_id = request.data.get('speaker')
# Check permissions and other conditions. Get speaker instance.
if speaker_id is None:
# Remove oneself
queryset = Speaker.objects.filter(
item=item, user=self.request.user).exclude(weight=None)
try:
# We assume that there aren't multiple entries because this
# is forbidden by the Manager's add method. We assume that
# there is only one speaker instance or none.
speaker = queryset.get()
except Speaker.DoesNotExist:
raise ValidationError({'detail': _('You are not on the list of speakers.')})
else:
# Remove someone else.
if not self.request.user.has_perm('agenda.can_manage'):
self.permission_denied(request)
try:
speaker = Speaker.objects.get(pk=speaker_id)
except Speaker.DoesNotExist:
raise ValidationError({'detail': _('Speaker does not exist.')})
# Delete the speaker.
speaker.delete()
message = _('Speaker was successfully removed from the list of speakers.')
# Initiate response.
return Response({'detail': message})
@list_route(methods=['get', 'put']) @list_route(methods=['get', 'put'])
def tree(self, request): def tree(self, request):
""" """

View File

@ -0,0 +1,117 @@
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from rest_framework.test import APIClient
from openslides.agenda.models import Item, Speaker
from openslides.utils.test import TestCase
class ManageSpeaker(TestCase):
"""
Tests managing speakers.
"""
def setUp(self):
self.client = APIClient()
self.client.login(username='admin', password='admin')
self.item = Item.objects.create(title='test_title_aZaedij4gohn5eeQu8fe')
self.user = get_user_model().objects.create_user(
username='test_user_jooSaex1bo5ooPhuphae',
password='test_password_e6paev4zeeh9n')
def test_add_oneself(self):
response = self.client.post(
reverse('item-manage-speaker', args=[self.item.pk]))
self.assertEqual(response.status_code, 200)
self.assertTrue(Speaker.objects.all().exists())
def test_add_oneself_twice(self):
Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item)
response = self.client.post(
reverse('item-manage-speaker', args=[self.item.pk]))
self.assertEqual(response.status_code, 400)
def test_add_oneself_when_closed(self):
self.item.speaker_list_closed = True
self.item.save()
response = self.client.post(
reverse('item-manage-speaker', args=[self.item.pk]))
self.assertEqual(response.status_code, 400)
def test_remove_oneself(self):
Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item)
response = self.client.delete(
reverse('item-manage-speaker', args=[self.item.pk]))
self.assertEqual(response.status_code, 200)
self.assertFalse(Speaker.objects.all().exists())
def test_remove_self_not_on_list(self):
response = self.client.delete(
reverse('item-manage-speaker', args=[self.item.pk]))
self.assertEqual(response.status_code, 400)
def test_add_someone_else(self):
response = self.client.post(
reverse('item-manage-speaker', args=[self.item.pk]),
{'user': self.user.pk})
self.assertEqual(response.status_code, 200)
self.assertTrue(Speaker.objects.filter(item=self.item, user=self.user).exists())
def test_invalid_data_string_instead_of_integer(self):
response = self.client.post(
reverse('item-manage-speaker', args=[self.item.pk]),
{'user': 'string_instead_of_integer'})
self.assertEqual(response.status_code, 400)
def test_invalid_data_user_does_not_exist(self):
# ID of a user that does not exist.
# Be careful: Here we do not test that the user does not exist.
inexistent_user_pk = self.user.pk + 1000
response = self.client.post(
reverse('item-manage-speaker', args=[self.item.pk]),
{'user': inexistent_user_pk})
self.assertEqual(response.status_code, 400)
def test_add_someone_else_twice(self):
Speaker.objects.add(self.user, self.item)
response = self.client.post(
reverse('item-manage-speaker', args=[self.item.pk]),
{'user': self.user.pk})
self.assertEqual(response.status_code, 400)
def test_add_someone_else_non_admin(self):
admin = get_user_model().objects.get(username='admin')
group_staff = admin.groups.get(name='Staff')
group_delegates = type(group_staff).objects.get(name='Delegates')
admin.groups.add(group_delegates)
admin.groups.remove(group_staff)
response = self.client.post(
reverse('item-manage-speaker', args=[self.item.pk]),
{'user': self.user.pk})
self.assertEqual(response.status_code, 403)
def test_remove_someone_else(self):
speaker = Speaker.objects.add(self.user, self.item)
response = self.client.delete(
reverse('item-manage-speaker', args=[self.item.pk]),
{'speaker': speaker.pk})
self.assertEqual(response.status_code, 200)
self.assertFalse(Speaker.objects.filter(item=self.item, user=self.user).exists())
def test_remove_someone_else_not_on_list(self):
response = self.client.delete(
reverse('item-manage-speaker', args=[self.item.pk]),
{'speaker': '1'})
self.assertEqual(response.status_code, 400)
def test_remove_someone_else_non_admin(self):
admin = get_user_model().objects.get(username='admin')
group_staff = admin.groups.get(name='Staff')
group_delegates = type(group_staff).objects.get(name='Delegates')
admin.groups.add(group_delegates)
admin.groups.remove(group_staff)
speaker = Speaker.objects.add(self.user, self.item)
response = self.client.delete(
reverse('item-manage-speaker', args=[self.item.pk]),
{'speaker': speaker.pk})
self.assertEqual(response.status_code, 403)

View File

@ -0,0 +1,54 @@
from unittest import TestCase
from unittest.mock import MagicMock, patch
from openslides.agenda.views import ItemViewSet
class ItemViewSetManageSpeaker(TestCase):
"""
Tests views of ItemViewSet to manage speakers.
"""
def setUp(self):
self.request = MagicMock()
self.view_instance = ItemViewSet()
self.view_instance.request = self.request
self.view_instance.get_object = get_object_mock = MagicMock()
get_object_mock.return_value = self.mock_item = MagicMock()
@patch('openslides.agenda.views.Speaker')
def test_add_oneself_as_speaker(self, mock_speaker):
self.request.method = 'POST'
self.request.user.has_perm.return_value = True
self.request.data = {}
self.mock_item.speaker_list_closed = False
self.view_instance.manage_speaker(self.request)
mock_speaker.objects.add.assert_called_with(self.request.user, self.mock_item)
@patch('openslides.agenda.views.get_user_model')
@patch('openslides.agenda.views.Speaker')
def test_add_someone_else_as_speaker(self, mock_speaker, mock_get_user_model):
self.request.method = 'POST'
self.request.user.has_perm.return_value = True
self.request.data = {'user': '2'} # It is assumed that the request user has pk!=2.
mock_get_user_model.return_value = MockUser = MagicMock()
MockUser.objects.get.return_value = mock_user = MagicMock()
self.view_instance.manage_speaker(self.request)
MockUser.objects.get.assert_called_with(pk=2)
mock_speaker.objects.add.assert_called_with(mock_user, self.mock_item)
@patch('openslides.agenda.views.Speaker')
def test_remove_oneself(self, mock_speaker):
self.request.method = 'DELETE'
self.request.data = {}
self.view_instance.manage_speaker(self.request)
mock_queryset = mock_speaker.objects.filter.return_value.exclude.return_value
mock_queryset.get.return_value.delete.assert_called_with()
@patch('openslides.agenda.views.Speaker')
def test_remove_someone_else(self, mock_speaker):
self.request.method = 'DELETE'
self.request.user.has_perm.return_value = True
self.request.data = {'speaker': '1'}
self.view_instance.manage_speaker(self.request)
mock_speaker.objects.get.assert_called_with(pk='1')
mock_speaker.objects.get.return_value.delete.assert_called_with()