Added view to add and remove users from the list of speakers.
This commit is contained in:
parent
9c51313a82
commit
95be18b78e
@ -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):
|
||||||
"""
|
"""
|
||||||
|
117
tests/integration/agenda/test_viewsets.py
Normal file
117
tests/integration/agenda/test_viewsets.py
Normal 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)
|
54
tests/unit/agenda/test_views.py
Normal file
54
tests/unit/agenda/test_views.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user