Merge pull request #3570 from FinnStutzenstein/markSpeaker

Mark speakers
This commit is contained in:
Emanuel Schütze 2018-02-14 10:24:36 +01:00 committed by GitHub
commit df523ce526
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 132 additions and 4 deletions

View File

@ -13,6 +13,7 @@ Agenda:
- Fixed issue when sorting a new inserted speaker [#3210].
- New permission for managing lists of speakers [#3366].
- Fixed multiple request on creation of agenda related items [#3341].
- Added possibility to mark speakers [#3570].
Motions:
- New export dialog [#3185].

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-02-06 12:26
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda', '0003_auto_20170818_1202'),
]
operations = [
migrations.AddField(
model_name='speaker',
name='marked',
field=models.BooleanField(default=False),
),
]

View File

@ -401,6 +401,11 @@ class Speaker(RESTModelMixin, models.Model):
The sort order of the list of speakers. None, if he has already spoken.
"""
marked = models.BooleanField(default=False)
"""
Marks a speaker.
"""
class Meta:
default_permissions = ()
permissions = (

View File

@ -15,6 +15,7 @@ class SpeakerSerializer(ModelSerializer):
'begin_time',
'end_time',
'weight',
'marked',
'item', # js-data needs the item-id in the nested object to define relations.
)

View File

@ -7,7 +7,7 @@
.currentSpeaker {
font-weight: bold;
i {
i.fa-microphone {
padding-right: 5px;
}
}

View File

@ -569,6 +569,18 @@ angular.module('OpenSlidesApp.agenda.site', [
);
}
};
// Marking a speaker
$scope.toggleMarked = function (speaker) {
$http.patch('/rest/agenda/item/' + $scope.item.id + '/manage_speaker/', {
user: speaker.user.id,
marked: !speaker.marked,
}).then(function (success) {
$scope.alert.show = false;
}, function (error) {
$scope.alert = ErrorMessage.forAlert(error);
});
};
}
])

View File

@ -42,6 +42,7 @@
<ol class="indentation">
<li ng-repeat="speaker in lastSpeakers">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
<small class="grey">
{{ getDuration(speaker) | osSecondsToTime }} <translate>minutes</translate>
(<translate>Start time</translate>:
@ -59,10 +60,17 @@
<p ng-repeat="speaker in currentSpeaker" class="currentSpeaker spacer indentation">
<i class="fa fa-microphone fa-lg"></i>
{{ speaker.user.get_full_name() }}
<span os-perms="!agenda.can_manage_list_of_speakers">
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</span>
<button os-perms="agenda.can_manage_list_of_speakers" ng-click="endSpeech()"
class="btn btn-default btn-sm" title="{{ 'End speech' | translate }}">
<i class="fa fa-microphone-slash"></i> <translate>Stop</translate>
</button>
<button os-perms="agenda.can_manage_list_of_speakers" ng-click="toggleMarked(speaker)"
class="btn btn-default btn-sm" title="{{ 'Mark speaker' | translate }}">
<i class="fa" ng-class="speaker.marked ? 'fa-star' : 'fa-star-o'"></i>
</button>
<button os-perms="agenda.can_manage_list_of_speakers" ng-click="removeSpeaker(speaker.id)"
class="btn btn-default btn-sm" title="{{ 'Remove' | translate }}">
<i class="fa fa-times"></i>
@ -77,11 +85,18 @@
<i os-perms="agenda.can_manage_list_of_speakers" ui-tree-handle="" class="fa fa-arrows-v"></i>
{{ $index + 1 }}.
{{ speaker.user.get_full_name() }}
<span os-perms="!agenda.can_manage_list_of_speakers">
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</span>
&nbsp;
<button os-perms="agenda.can_manage_list_of_speakers" ng-click="beginSpeech(speaker.id)"
class="btn btn-default btn-sm" title="{{ 'Begin speech' | translate }}">
<i class="fa fa-microphone"></i> <translate>Start</translate>
</button>
<button os-perms="agenda.can_manage_list_of_speakers" ng-click="toggleMarked(speaker)"
class="btn btn-default btn-sm" title="{{ 'Mark speaker' | translate }}">
<i class="fa" ng-class="speaker.marked ? 'fa-star' : 'fa-star-o'"></i>
</button>
<button os-perms="agenda.can_manage_list_of_speakers" ng-click="removeSpeaker(speaker.id)"
class="btn btn-default btn-sm" title="{{ 'Remove' | translate }}">
<i class="fa fa-times"></i>

View File

@ -7,6 +7,7 @@
| limitTo: config('agenda_show_last_speakers') : (lastSpeakers.length - config('agenda_show_last_speakers'))"
class="lastSpeakers">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</p>
<!-- Current speaker -->
@ -15,6 +16,7 @@
<i class="fa fa-microphone fa-lg"></i>
<span class="pull-right" style="width:calc(100% - 30px);">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</span>
</div>
@ -25,6 +27,7 @@
| orderBy:'weight'
| limitTo: 3">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</li>
</ol>
<p ng-if="nextSpeakers.length > 3" class="lastSpeakers">

View File

@ -18,13 +18,16 @@
| limitTo: config('agenda_show_last_speakers') : (lastSpeakers.length - config('agenda_show_last_speakers'))"
class="lastSpeakers">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</p>
<!-- Current speaker -->
<p ng-repeat="speaker in currentspeakers = (agendaItem.speakers
| filter: {end_time: null, begin_time: '!!'})"
class="currentSpeaker nobr">
<i class="fa fa-microphone fa-lg"></i> {{ speaker.user.get_full_name() }}
<i class="fa fa-microphone fa-lg"></i>
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</p>
<!-- Next speakers -->
@ -33,6 +36,7 @@
| filter: {begin_time: null}
| orderBy:'weight'">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</li>
</ol>
</div>

View File

@ -16,16 +16,20 @@
<p ng-repeat="speaker in lastSpeakers = (item.speakers | filter: {end_time: '!!', begin_time: '!!'}) |
limitTo: config('agenda_show_last_speakers') : (lastSpeakers.length - config('agenda_show_last_speakers'))" class="lastSpeakers">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
<!-- Current speaker -->
<p ng-repeat="speaker in item.speakers | filter: {end_time: null, begin_time: '!!'}"
class="currentSpeaker">
<i class="fa fa-microphone fa-lg"></i> {{ speaker.user.get_full_name() }}
<i class="fa fa-microphone fa-lg"></i>
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
<!-- Next speakers -->
<ol class="nextSpeakers">
<li ng-repeat="speaker in item.speakers | filter: {begin_time: null} | orderBy:'weight'">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</ol>
</div>
</div>

View File

@ -57,7 +57,7 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
result = False
return result
@detail_route(methods=['POST', 'DELETE'])
@detail_route(methods=['POST', 'PATCH', 'DELETE'])
def manage_speaker(self, request, pk=None):
"""
Special view endpoint to add users to the list of speakers or remove
@ -65,6 +65,7 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
data to add yourself. Send DELETE {'speaker': <speaker_id>} or
DELETE {'speaker': [<speaker_id>, <speaker_id>, ...]} to remove one or
more speakers from the list of speakers. Omit data to remove yourself.
Send PATCH {'user': <user_id>, 'marked': <bool>} to mark the speaker.
Checks also whether the requesting user can do this. He needs at
least the permissions 'agenda.can_see' (see
@ -110,6 +111,39 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
# to see users may not have it but can get it now.
inform_changed_data([user])
# Toggle 'marked' for the speaker
elif request.method == 'PATCH':
# Check permissions
if not has_perm(self.request.user, 'agenda.can_manage_list_of_speakers'):
self.permission_denied(request)
# Retrieve user_id
user_id = request.data.get('user')
try:
user = get_user_model().objects.get(pk=int(user_id))
except (ValueError, get_user_model().DoesNotExist):
raise ValidationError({'detail': _('User does not exist.')})
marked = request.data.get('marked')
if not isinstance(marked, bool):
raise ValidationError({'detail': _('Marked has to be a bool.')})
queryset = Speaker.objects.filter(item=item, user=user)
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': _('The user is not in the list of speakers.')})
else:
speaker.marked = marked
speaker.save()
if speaker.marked:
message = _('You are successfully marked the speaker.')
else:
message = _('You are successfully unmarked the speaker.')
else:
# request.method == 'DELETE'
speaker_ids = request.data.get('speaker')

View File

@ -252,6 +252,35 @@ class ManageSpeaker(TestCase):
{'speaker': speaker.pk})
self.assertEqual(response.status_code, 403)
def test_mark_speaker(self):
Speaker.objects.add(self.user, self.item)
response = self.client.patch(
reverse('item-manage-speaker', args=[self.item.pk]),
{
'user': self.user.pk,
'marked': True,
},
format='json'
)
self.assertEqual(response.status_code, 200)
self.assertTrue(Speaker.objects.get().marked)
def test_mark_speaker_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)
CollectionElement.from_instance(admin)
Speaker.objects.add(self.user, self.item)
response = self.client.patch(
reverse('item-manage-speaker', args=[self.item.pk]),
{'user': self.user.pk})
self.assertEqual(response.status_code, 403)
class Speak(TestCase):
"""