From 0853701cdd3cac5f879019ab018504ffbdfd0c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Wed, 27 May 2015 15:42:32 +0200 Subject: [PATCH] Added view to begin and end speach. --- openslides/agenda/views.py | 58 +++++++++++++++++++-- tests/integration/agenda/test_viewsets.py | 62 +++++++++++++++++++++++ tests/unit/agenda/test_views.py | 39 +++++++++++++- 3 files changed, 154 insertions(+), 5 deletions(-) diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index f74d223fd..dc3f34935 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -262,7 +262,7 @@ class ItemViewSet(ModelViewSet): Speaker.objects.add(user, item) except OpenSlidesError as e: raise ValidationError({'detail': e}) - message = _('User was successfully added to the list of speakers.') + message = _('User %s was successfully added to the list of speakers.') % user else: # request.method == 'DELETE' @@ -286,13 +286,63 @@ class ItemViewSet(ModelViewSet): 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: + speaker = Speaker.objects.get(pk=int(speaker_id)) + except (ValueError, Speaker.DoesNotExist): raise ValidationError({'detail': _('Speaker does not exist.')}) # Delete the speaker. speaker.delete() - message = _('Speaker was successfully removed from the list of speakers.') + message = _('Speaker %s was successfully removed from the list of speakers.') % speaker + + # Initiate response. + return Response({'detail': message}) + + @detail_route(methods=['PUT', 'DELETE']) + def speak(self, request, pk=None): + """ + Special view endpoint to begin and end speach of speakers. Send PUT + {'speaker': } to begin speach. Omit data to begin speach of + the next speaker. Send DELETE to end speach of current speaker. + + Checks also whether the requesting user can do this. He needs at + least the permissions 'agenda.can_see' (see + self.check_permission()). Also the permission 'agenda.can_manage' + is required. + """ + # Check permission. + if not self.request.user.has_perm('agenda.can_manage'): + self.permission_denied(request) + + # Retrieve item. + item = self.get_object() + + if request.method == 'PUT': + # Retrieve speaker_id + speaker_id = request.data.get('speaker') + if speaker_id is None: + speaker = item.get_next_speaker() + if speaker is None: + raise ValidationError({'detail': _('The list of speakers is empty.')}) + else: + try: + speaker = Speaker.objects.get(pk=int(speaker_id)) + except (ValueError, Speaker.DoesNotExist): + raise ValidationError({'detail': _('Speaker does not exist.')}) + speaker.begin_speach() + message = _('User is now speaking.') + + else: + # request.method == 'DELETE' + try: + # We assume that there aren't multiple entries because this + # is forbidden by the Model's begin_speach method. We assume that + # there is only one speaker instance or none. + current_speaker = Speaker.objects.filter(item=item, end_time=None).exclude(begin_time=None).get() + except Speaker.DoesNotExist: + raise ValidationError( + {'detail': _('There is no one speaking at the moment according to %(item)s.') % {'item': item}}) + current_speaker.end_speach() + message = _('The speach is finished now.') # Initiate response. return Response({'detail': message}) diff --git a/tests/integration/agenda/test_viewsets.py b/tests/integration/agenda/test_viewsets.py index 5e17ded2a..0e6e8a468 100644 --- a/tests/integration/agenda/test_viewsets.py +++ b/tests/integration/agenda/test_viewsets.py @@ -104,6 +104,12 @@ class ManageSpeaker(TestCase): {'speaker': '1'}) self.assertEqual(response.status_code, 400) + def test_remove_someone_else_invalid_data(self): + response = self.client.delete( + reverse('item-manage-speaker', args=[self.item.pk]), + {'speaker': 'invalid'}) + 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') @@ -115,3 +121,59 @@ class ManageSpeaker(TestCase): reverse('item-manage-speaker', args=[self.item.pk]), {'speaker': speaker.pk}) self.assertEqual(response.status_code, 403) + + +class Speak(TestCase): + """ + Tests view to begin or end speach. + """ + def setUp(self): + self.client = APIClient() + self.client.login(username='admin', password='admin') + self.item = Item.objects.create(title='test_title_KooDueco3zaiGhiraiho') + self.user = get_user_model().objects.create_user( + username='test_user_Aigh4vohb3seecha4aa4', + password='test_password_eneupeeVo5deilixoo8j') + + def test_begin_speach(self): + Speaker.objects.add(self.user, self.item) + speaker = Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item) + self.assertTrue(Speaker.objects.get(pk=speaker.pk).begin_time is None) + response = self.client.put( + reverse('item-speak', args=[self.item.pk]), + {'speaker': speaker.pk}) + self.assertEqual(response.status_code, 200) + self.assertFalse(Speaker.objects.get(pk=speaker.pk).begin_time is None) + + def test_begin_speach_next_speaker(self): + speaker = Speaker.objects.add(self.user, self.item) + Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item) + self.assertTrue(Speaker.objects.get(pk=speaker.pk).begin_time is None) + response = self.client.put(reverse('item-speak', args=[self.item.pk])) + self.assertEqual(response.status_code, 200) + self.assertFalse(Speaker.objects.get(pk=speaker.pk).begin_time is None) + + def test_begin_speach_invalid_speaker_id(self): + response = self.client.put( + reverse('item-speak', args=[self.item.pk]), + {'speaker': '1'}) + self.assertEqual(response.status_code, 400) + + def test_begin_speach_invalid_data(self): + response = self.client.put( + reverse('item-speak', args=[self.item.pk]), + {'speaker': 'invalid'}) + self.assertEqual(response.status_code, 400) + + def test_end_speach(self): + speaker = Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item) + speaker.begin_speach() + self.assertFalse(Speaker.objects.get(pk=speaker.pk).begin_time is None) + self.assertTrue(Speaker.objects.get(pk=speaker.pk).end_time is None) + response = self.client.delete(reverse('item-speak', args=[self.item.pk])) + self.assertEqual(response.status_code, 200) + self.assertFalse(Speaker.objects.get(pk=speaker.pk).end_time is None) + + def test_end_speach_no_current_speaker(self): + response = self.client.delete(reverse('item-speak', args=[self.item.pk])) + self.assertEqual(response.status_code, 400) diff --git a/tests/unit/agenda/test_views.py b/tests/unit/agenda/test_views.py index 12ee38e2f..7945d0d8b 100644 --- a/tests/unit/agenda/test_views.py +++ b/tests/unit/agenda/test_views.py @@ -50,5 +50,42 @@ class ItemViewSetManageSpeaker(TestCase): 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.assert_called_with(pk=1) mock_speaker.objects.get.return_value.delete.assert_called_with() + + +class ItemViewSetSpeak(TestCase): + """ + Tests views of ItemViewSet to begin and end speach. + """ + 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() + + def test_begin_speach(self): + self.request.method = 'PUT' + self.request.user.has_perm.return_value = True + self.request.data = {} + self.mock_item.get_next_speaker.return_value = mock_next_speaker = MagicMock() + self.view_instance.speak(self.request) + mock_next_speaker.begin_speach.assert_called_with() + + @patch('openslides.agenda.views.Speaker') + def test_begin_speach_specific_speaker(self, mock_speaker): + self.request.method = 'PUT' + self.request.user.has_perm.return_value = True + self.request.data = {'speaker': '1'} + mock_speaker.objects.get.return_value = mock_next_speaker = MagicMock() + self.view_instance.speak(self.request) + mock_next_speaker.begin_speach.assert_called_with() + + @patch('openslides.agenda.views.Speaker') + def test_end_speach(self, mock_speaker): + self.request.method = 'DELETE' + self.request.user.has_perm.return_value = True + mock_speaker.objects.filter.return_value.exclude.return_value.get.return_value = mock_speaker = MagicMock() + self.view_instance.speak(self.request) + mock_speaker.end_speach.assert_called_with()