Merge pull request #1608 from emanuelschuetze/speakerscontrol
Added manage controls for list of speakers of agenda items.
This commit is contained in:
commit
35fa5470d2
@ -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()
|
||||
|
@ -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',
|
||||
|
@ -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'))
|
||||
|
||||
|
@ -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.
|
||||
|
@ -23,62 +23,114 @@
|
||||
<div class="white-space-pre-line">{{ item.text }}</div>
|
||||
|
||||
<div os-perm="agenda.can_manage">
|
||||
<h3 os-perm="agenda.can_manage" translate>Duration</h3>
|
||||
<h2 os-perm="agenda.can_manage" translate>Duration</h2>
|
||||
{{ item.duration }}
|
||||
</div>
|
||||
|
||||
<div os-perm="agenda.can_manage">
|
||||
<h3 os-perm="agenda.can_manage" translate>Comment</h3>
|
||||
<h2 os-perm="agenda.can_manage" translate>Comment</h2>
|
||||
<div class="white-space-pre-line">{{ item.comment }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<h3 translate>List of speakers
|
||||
<span ng-if="item.speakerListClosed" class="label label-danger" translate>closed</span>
|
||||
<h2 translate>List of speakers
|
||||
<span os-perms="agenda.can_manage">
|
||||
<button ng-if="item.speakerListClosed" ng-click="closeList(false)"
|
||||
class="btn btn-sm btn-default" translate>
|
||||
Open list
|
||||
<button ng-if="item.speaker_list_closed" ng-click="closeList(false)"
|
||||
class="btn btn-sm btn-danger" translate>
|
||||
Closed
|
||||
</button>
|
||||
<button ng-if="!item.speakerListClosed" ng-click="closeList(true)"
|
||||
class="btn btn-sm btn-default" translate>
|
||||
Close list
|
||||
<button ng-if="!item.speaker_list_closed" ng-click="closeList(true)"
|
||||
class="btn btn-sm btn-success" translate>
|
||||
Opened
|
||||
</button>
|
||||
</span>
|
||||
</h3>
|
||||
<!-- project list -->
|
||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
||||
ng-class="{ 'btn-primary': item.isProjected() }"
|
||||
ng-click="projectListOfSpeakers()">
|
||||
<i class="fa fa-video-camera"></i> Project list
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
<!-- TODO:
|
||||
* project list
|
||||
* show old/current/next speakers
|
||||
* start/stop speech
|
||||
* button 'put/remove me on/from the list'
|
||||
* check permissions
|
||||
* show only 'add me' OR 'remove me' button
|
||||
-->
|
||||
<ol>
|
||||
<li ng-repeat="speaker in item.speakers">
|
||||
{{ speaker.user.get_full_name() }}
|
||||
<button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)"
|
||||
class="btn btn-default btn-xs">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ol>
|
||||
<div class="well">
|
||||
<button class="btn btn-default btn-xs" type="button"
|
||||
data-toggle="collapse" data-target="#old_speakers"
|
||||
aria-expanded="false" aria-controls="collapseExample">
|
||||
Show all old speakers
|
||||
</button>
|
||||
|
||||
<div os-perms="agenda.can_manage" class="form-group col-sm-6">
|
||||
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">{{ alert.msg }}</alert>
|
||||
<div class="input-group">
|
||||
<ui-select ng-model="speaker.selected" ng-change="addSpeaker(speaker.selected.id)">
|
||||
<ui-select-match placeholder="{{ 'Select or search a participant...' | translate }}">
|
||||
{{ $select.selected.get_full_name() }}
|
||||
</ui-select-match>
|
||||
<ui-select-choices repeat="user in users | filter: $select.search">
|
||||
<div ng-bind-html="user.get_full_name() | highlight: $select.search"></div>
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
<span class="input-group-btn">
|
||||
<a ng-click="speaker={}" class="btn btn-default">
|
||||
<i class="fa fa-times-circle"></i>
|
||||
</a>
|
||||
</span>
|
||||
<div class="collapse" id="old_speakers">
|
||||
<h3 translate>Old speakers:</h3>
|
||||
<ol>
|
||||
<li ng-repeat="speaker in item.speakers | filter: {end_time: '!!'}">
|
||||
{{ speaker.user.get_full_name() }}
|
||||
<small class="grey">
|
||||
[{{speaker.begin_time | date:'yyyy-MM-dd HH:mm:ss'}} –
|
||||
{{speaker.end_time | date:'yyyy-MM-dd HH:mm:ss'}}]
|
||||
</small>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h3 translate>Current speaker:</h3>
|
||||
<strong ng-repeat="speaker in item.speakers | filter: {end_time: null, begin_time: '!!'}">
|
||||
{{ speaker.user.get_full_name() }}
|
||||
</strong>
|
||||
|
||||
<h3 translate>Next speakers:</h3>
|
||||
<ol>
|
||||
<li ng-repeat="speaker in item.speakers | filter: {begin_time: null}">
|
||||
{{ speaker.user.get_full_name() }}
|
||||
<button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)"
|
||||
class="btn btn-default btn-xs">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
<button os-perms="agenda.can_manage" ng-click="beginSpeech(speaker.id)"
|
||||
class="btn btn-default btn-xs">
|
||||
<i class="fa fa-play"></i>
|
||||
</button>
|
||||
</ol>
|
||||
|
||||
<div class="form-group">
|
||||
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{alert.msg}}
|
||||
</alert>
|
||||
<div os-perms="agenda.can_manage" class="input-group">
|
||||
<ui-select ng-model="speaker.selected" ng-change="addSpeaker(speaker.selected.id)">
|
||||
<ui-select-match placeholder="{{ 'Select or search a participant...' | translate }}">
|
||||
{{ $select.selected.get_full_name() }}
|
||||
</ui-select-match>
|
||||
<ui-select-choices repeat="user in users | filter: $select.search">
|
||||
<div ng-bind-html="user.get_full_name() | highlight: $select.search"></div>
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
<span class="input-group-btn">
|
||||
<a ng-click="speaker={}" class="btn btn-default">
|
||||
<i class="fa fa-times-circle"></i>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<p os-perm="agenda.can_be_speaker">
|
||||
<button ng-click="addSpeaker()" class="btn btn-default">
|
||||
<i class="fa fa-plus"></i>
|
||||
<translate>Add me</translate>
|
||||
</button>
|
||||
<button ng-click="removeSpeaker()" class="btn btn-default">
|
||||
<i class="fa fa-minus"></i>
|
||||
<translate>Remove me</translate>
|
||||
</button>
|
||||
<p os-perms="agenda.can_manage">
|
||||
<button ng-click="beginSpeech()"
|
||||
class="btn btn-primary">
|
||||
<i class="fa fa-play"></i>
|
||||
<translate>Start next speaker</translate>
|
||||
</button>
|
||||
<button ng-click="endSpeech()"
|
||||
class="btn btn-default">
|
||||
<i class="fa fa-stop"></i>
|
||||
<translate>Stop current speaker</translate>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,27 @@
|
||||
<div ng-controller="SlideItemDetailCtrl" class="content">
|
||||
<h1>{{ item.title }}</h1>
|
||||
<div class="white-space-pre-line">{{ item.text }}</div>
|
||||
<h1>
|
||||
{{ item.title }}
|
||||
<small ng-if="is_list_of_speakers">
|
||||
<span translate>List of speakers</span>
|
||||
<span ng-if="item.speaker_list_closed" class="label label-danger" translate>Closed</span>
|
||||
</small>
|
||||
</h1>
|
||||
<!-- Item text -->
|
||||
<div ng-if="!is_list_of_speakers" class="white-space-pre-line">{{ item.text }}</div>
|
||||
|
||||
<!-- List of speakers -->
|
||||
<div ng-if="is_list_of_speakers">
|
||||
<!-- TODO: show last old speakers -->
|
||||
|
||||
<!-- current speaker -->
|
||||
<p>
|
||||
<strong ng-repeat="speaker in item.speakers | filter: {end_time: null, begin_time: '!!'}">
|
||||
{{ speaker.user.get_full_name() }}
|
||||
</strong>
|
||||
<!-- next speakers -->
|
||||
<ol id="list_of_speakers">
|
||||
<li ng-repeat="speaker in item.speakers | filter: {begin_time: null}">
|
||||
{{ speaker.user.get_full_name() }}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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': <speaker_id>} 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': <speaker_id>} 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})
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user