From 77d027c1cce59c5b412bf5eb13dfd5850c967aeb Mon Sep 17 00:00:00 2001 From: Emanuel Schuetze Date: Fri, 4 Sep 2015 18:24:41 +0200 Subject: [PATCH] Agenda templates - Added manage controls for list of speakers of agenda items. - New slide for list of speakers. - Fixed typo (speach -> speech) --- openslides/agenda/models.py | 12 +- openslides/agenda/projector.py | 2 +- openslides/agenda/signals.py | 2 +- openslides/agenda/static/js/agenda/agenda.js | 56 ++++++-- .../static/templates/agenda/item-detail.html | 134 ++++++++++++------ .../templates/agenda/slide-item-detail.html | 27 +++- openslides/agenda/views.py | 14 +- tests/integration/agenda/test_viewsets.py | 16 +-- tests/old/agenda/test_list_of_speakers.py | 8 +- tests/unit/agenda/test_views.py | 14 +- 10 files changed, 197 insertions(+), 88 deletions(-) diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index b61324e05..39fbf62cc 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -377,7 +377,7 @@ class Speaker(RESTModelMixin, models.Model): end_time = models.DateTimeField(null=True) """ - Saves the time, when the speaker ends his speach. None, if he is not finished yet. + Saves the time, when the speaker ends his speech. None, if he is not finished yet. """ weight = models.IntegerField(null=True) @@ -393,19 +393,19 @@ class Speaker(RESTModelMixin, models.Model): def __str__(self): return str(self.user) - def begin_speach(self): + def begin_speech(self): """ Let the user speak. Set the weight to None and the time to now. If anyone is still - speaking, end his speach. + speaking, end his speech. """ try: actual_speaker = Speaker.objects.filter(item=self.item, end_time=None).exclude(begin_time=None).get() except Speaker.DoesNotExist: pass else: - actual_speaker.end_speach() + actual_speaker.end_speech() self.weight = None self.begin_time = datetime.now() self.save() @@ -416,9 +416,9 @@ class Speaker(RESTModelMixin, models.Model): # start_countdown() pass - def end_speach(self): + def end_speech(self): """ - The speach is finished. Set the time to now. + The speech is finished. Set the time to now. """ self.end_time = datetime.now() self.save() diff --git a/openslides/agenda/projector.py b/openslides/agenda/projector.py index 5227a08b4..13dac488c 100644 --- a/openslides/agenda/projector.py +++ b/openslides/agenda/projector.py @@ -81,7 +81,7 @@ class ItemDetailSlide(ProjectorElement): view_class=ItemViewSet, view_action='retrieve', pk=str(item.pk)) - for speaker in item.speaker_set.all(): + for speaker in item.speakers.all(): yield ProjectorRequirement( view_class=speaker.user.get_view_class(), view_action='retrieve', diff --git a/openslides/agenda/signals.py b/openslides/agenda/signals.py index b22f62da8..cdf07efbd 100644 --- a/openslides/agenda/signals.py +++ b/openslides/agenda/signals.py @@ -48,7 +48,7 @@ def setup_agenda_config(sender, **kwargs): default_value=False, input_type='boolean', label=ugettext_lazy('Couple countdown with the list of speakers'), - help_text=ugettext_lazy('[Begin speach] starts the countdown, [End speach] stops the countdown.'), + help_text=ugettext_lazy('[Begin speech] starts the countdown, [End speech] stops the countdown.'), weight=230, group=ugettext_lazy('Agenda')) diff --git a/openslides/agenda/static/js/agenda/agenda.js b/openslides/agenda/static/js/agenda/agenda.js index a6ab2867f..62d4868f9 100644 --- a/openslides/agenda/static/js/agenda/agenda.js +++ b/openslides/agenda/static/js/agenda/agenda.js @@ -181,7 +181,7 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) // close/open list of speakers of current item $scope.closeList = function (listClosed) { - item.speakerListClosed = listClosed; + item.speaker_list_closed = listClosed; Agenda.save(item); }; // add user to list of speakers @@ -198,7 +198,34 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) $scope.removeSpeaker = function (speakerId) { $http.delete('/rest/agenda/item/' + item.id + '/manage_speaker/', {headers: {'Content-Type': 'application/json'}, - data: JSON.stringify({speaker: speakerId})}); + data: JSON.stringify({speaker: speakerId})}) + .error(function(data){ + $scope.alert = { type: 'danger', msg: data.detail, show: true }; + }); + }; + // begin speech of selected/next speaker + $scope.beginSpeech = function (speakerId) { + $http.put('/rest/agenda/item/' + item.id + '/speak/', {'speaker': speakerId}) + .success(function(data){ + $scope.alert.show = false; + }) + .error(function(data){ + $scope.alert = { type: 'danger', msg: data.detail, show: true }; + }); + }; + // end speech of current speaker + $scope.endSpeech = function () { + $http.delete('/rest/agenda/item/' + item.id + '/speak/', + {headers: {'Content-Type': 'application/json'}, + data: JSON.stringify()}) + .error(function(data){ + $scope.alert = { type: 'danger', msg: data.detail, show: true }; + }); + }; + // project list of speakers + $scope.projectListOfSpeakers = function () { + $http.post('/rest/core/projector/1/prune_elements/', + [{name: 'agenda/item', id: item.id, list_of_speakers: true}]); }; }) @@ -301,15 +328,22 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda']) }); }) -.controller('SlideItemDetailCtrl', function($scope, Agenda) { - // Attention! Each object that is used here has to be dealt on server side. - // Add it to the coresponding get_requirements method of the ProjectorElement - // class. - var id = $scope.element.context.id; - Agenda.find(id); - Agenda.bindOne(id, $scope, 'item'); - -}) +.controller('SlideItemDetailCtrl', [ + '$scope', + 'Agenda', + 'User', + function($scope, Agenda, User) { + // Attention! Each object that is used here has to be dealt on server side. + // Add it to the coresponding get_requirements method of the ProjectorElement + // class. + var id = $scope.element.context.id; + Agenda.find(id); + User.findAll(); + Agenda.bindOne(id, $scope, 'item'); + // get flag for list-of-speakers-slide (true/false) + $scope.is_list_of_speakers = $scope.element.context.list_of_speakers; + } +]) .controller('SlideItemListCtrl', function($scope, $http, Agenda) { // Attention! Each object that is used here has to be dealt on server side. diff --git a/openslides/agenda/static/templates/agenda/item-detail.html b/openslides/agenda/static/templates/agenda/item-detail.html index 44e9374dc..8d3ff908e 100644 --- a/openslides/agenda/static/templates/agenda/item-detail.html +++ b/openslides/agenda/static/templates/agenda/item-detail.html @@ -23,62 +23,114 @@
{{ item.text }}
-

Duration

+

Duration

{{ item.duration }}
-

Comment

+

Comment

{{ item.comment }}
-

List of speakers - closed +

List of speakers - - -

+ + + Project list + + -
    -
  1. - {{ speaker.user.get_full_name() }} - -
  2. -
+
+ -
- {{ alert.msg }} -
- - - {{ $select.selected.get_full_name() }} - - -
-
-
- - - - - +
+

Old speakers:

+
    +
  1. + {{ speaker.user.get_full_name() }} + + [{{speaker.begin_time | date:'yyyy-MM-dd HH:mm:ss'}} – + {{speaker.end_time | date:'yyyy-MM-dd HH:mm:ss'}}] + +
+
+ +

Current speaker:

+ + {{ speaker.user.get_full_name() }} + + +

Next speakers:

+
    +
  1. + {{ speaker.user.get_full_name() }} + + +
+ +
+ + {{alert.msg}} + +
+ + + {{ $select.selected.get_full_name() }} + + +
+
+
+ + + + + +
+

+ + +

+ +

diff --git a/openslides/agenda/static/templates/agenda/slide-item-detail.html b/openslides/agenda/static/templates/agenda/slide-item-detail.html index e589e7086..1cc52272a 100644 --- a/openslides/agenda/static/templates/agenda/slide-item-detail.html +++ b/openslides/agenda/static/templates/agenda/slide-item-detail.html @@ -1,4 +1,27 @@
-

{{ item.title }}

-
{{ item.text }}
+

+ {{ item.title }} + + List of speakers + Closed + +

+ +
{{ item.text }}
+ + +
+ + + +

+ + {{ speaker.user.get_full_name() }} + + +

    +
  1. + {{ speaker.user.get_full_name() }} +
+
diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index 532c651e5..2d1a526da 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -152,9 +152,9 @@ class ItemViewSet(ModelViewSet): @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. + Special view endpoint to begin and end speech of speakers. Send PUT + {'speaker': } to begin speech. Omit data to begin speech of + the next speaker. Send DELETE to end speech of current speaker. """ # Retrieve item. item = self.get_object() @@ -171,21 +171,21 @@ class ItemViewSet(ModelViewSet): speaker = Speaker.objects.get(pk=int(speaker_id)) except (ValueError, Speaker.DoesNotExist): raise ValidationError({'detail': _('Speaker does not exist.')}) - speaker.begin_speach() + speaker.begin_speech() 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 + # is forbidden by the Model's begin_speech 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.') + current_speaker.end_speech() + message = _('The speech 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 1f296a563..06efa50b7 100644 --- a/tests/integration/agenda/test_viewsets.py +++ b/tests/integration/agenda/test_viewsets.py @@ -125,7 +125,7 @@ class ManageSpeaker(TestCase): class Speak(TestCase): """ - Tests view to begin or end speach. + Tests view to begin or end speech. """ def setUp(self): self.client = APIClient() @@ -135,7 +135,7 @@ class Speak(TestCase): username='test_user_Aigh4vohb3seecha4aa4', password='test_password_eneupeeVo5deilixoo8j') - def test_begin_speach(self): + def test_begin_speech(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) @@ -145,7 +145,7 @@ class Speak(TestCase): 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): + def test_begin_speech_next_speaker(self): speaker = Speaker.objects.add(self.user, self.item) Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item) @@ -154,27 +154,27 @@ class Speak(TestCase): 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): + def test_begin_speech_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): + def test_begin_speech_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): + def test_end_speech(self): speaker = Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item) - speaker.begin_speach() + speaker.begin_speech() 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): + def test_end_speech_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/old/agenda/test_list_of_speakers.py b/tests/old/agenda/test_list_of_speakers.py index c6dac3de0..4e00f2197 100644 --- a/tests/old/agenda/test_list_of_speakers.py +++ b/tests/old/agenda/test_list_of_speakers.py @@ -46,18 +46,18 @@ class ListOfSpeakerModelTests(TestCase): speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1) self.assertIsNone(speaker1_item1.begin_time) self.assertIsNone(speaker1_item1.end_time) - speaker1_item1.begin_speach() + speaker1_item1.begin_speech() self.assertIsNotNone(Speaker.objects.get(pk=speaker1_item1.pk).begin_time) self.assertIsNone(Speaker.objects.get(pk=speaker1_item1.pk).weight) - speaker1_item1.end_speach() + speaker1_item1.end_speech() self.assertIsNotNone(Speaker.objects.get(pk=speaker1_item1.pk).end_time) def test_finish_when_other_speaker_begins(self): speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1) speaker2_item1 = Speaker.objects.add(self.speaker2, self.item1) - speaker1_item1.begin_speach() + speaker1_item1.begin_speech() self.assertIsNone(speaker1_item1.end_time) self.assertIsNone(speaker2_item1.begin_time) - speaker2_item1.begin_speach() + speaker2_item1.begin_speech() self.assertIsNotNone(Speaker.objects.get(user=self.speaker1, item=self.item1).end_time) self.assertIsNotNone(speaker2_item1.begin_time) diff --git a/tests/unit/agenda/test_views.py b/tests/unit/agenda/test_views.py index 7945d0d8b..9f9ccc778 100644 --- a/tests/unit/agenda/test_views.py +++ b/tests/unit/agenda/test_views.py @@ -56,7 +56,7 @@ class ItemViewSetManageSpeaker(TestCase): class ItemViewSetSpeak(TestCase): """ - Tests views of ItemViewSet to begin and end speach. + Tests views of ItemViewSet to begin and end speech. """ def setUp(self): self.request = MagicMock() @@ -65,27 +65,27 @@ class ItemViewSetSpeak(TestCase): self.view_instance.get_object = get_object_mock = MagicMock() get_object_mock.return_value = self.mock_item = MagicMock() - def test_begin_speach(self): + def test_begin_speech(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() + mock_next_speaker.begin_speech.assert_called_with() @patch('openslides.agenda.views.Speaker') - def test_begin_speach_specific_speaker(self, mock_speaker): + def test_begin_speech_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() + mock_next_speaker.begin_speech.assert_called_with() @patch('openslides.agenda.views.Speaker') - def test_end_speach(self, mock_speaker): + def test_end_speech(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() + mock_speaker.end_speech.assert_called_with()