diff --git a/openslides/agenda/__init__.py b/openslides/agenda/__init__.py index 004f26495..320161958 100644 --- a/openslides/agenda/__init__.py +++ b/openslides/agenda/__init__.py @@ -6,9 +6,9 @@ The OpenSlides agenda app appends the functionality to OpenSlides to manage agendas. - It includes time-management and list of speakers. + It includes time-management and lists of speakers. - :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ diff --git a/openslides/agenda/forms.py b/openslides/agenda/forms.py index 38f4ae81a..783398765 100644 --- a/openslides/agenda/forms.py +++ b/openslides/agenda/forms.py @@ -6,7 +6,7 @@ Forms for the agenda app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ @@ -69,7 +69,7 @@ class AppendSpeakerForm(CssClassMixin, forms.Form): Checks, that the user is not already on the list. """ speaker = self.cleaned_data['speaker'] - if Speaker.objects.filter(person=speaker, item=self.item, time=None).exists(): + if Speaker.objects.filter(person=speaker, item=self.item, begin_time=None).exists(): raise forms.ValidationError(ugettext_lazy( '%s is already on the list of speakers.' % unicode(speaker))) diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index f21886910..9de46ee4d 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -6,7 +6,7 @@ Models for the agenda app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ @@ -99,10 +99,44 @@ class Item(MPTTModel, SlideMixin): True, if the list of speakers is closed. """ + class Meta: + permissions = ( + ('can_see_agenda', ugettext_noop("Can see agenda")), + ('can_manage_agenda', ugettext_noop("Can manage agenda")), + ('can_see_orga_items', ugettext_noop("Can see orga items and time scheduling of agenda"))) + + class MPTTMeta: + order_insertion_by = ['weight'] + + def __unicode__(self): + return self.get_title() + + def get_absolute_url(self, link='view'): + """ + Return the URL to this item. By default it is the link to its + view or the view of a related object. + + The link can be: + * view + * edit + * delete + """ + if link == 'view': + if self.related_sid: + return self.get_related_slide().get_absolute_url(link) + return reverse('item_view', args=[str(self.id)]) + if link == 'edit': + if self.related_sid: + return self.get_related_slide().get_absolute_url(link) + return reverse('item_edit', args=[str(self.id)]) + if link == 'delete': + return reverse('item_delete', args=[str(self.id)]) + def get_related_slide(self): """ - return the object, of which the item points. + Return the object at which the item points. """ + # TODO: Rename it to 'get_related_object' object = get_slide_from_sid(self.related_sid, element=True) if object is None: self.title = 'Item for deleted slide: %s' % self.related_sid @@ -114,7 +148,7 @@ class Item(MPTTModel, SlideMixin): def get_related_type(self): """ - return the type of the releated slide. + Return the type of the releated slide. """ return self.get_related_slide().prefix @@ -129,7 +163,7 @@ class Item(MPTTModel, SlideMixin): def get_title(self): """ - return the title of this item. + Return the title of this item. """ if self.related_sid is None: return self.title @@ -137,7 +171,7 @@ class Item(MPTTModel, SlideMixin): def get_title_supplement(self): """ - return a supplement for the title. + Return a supplement for the title. """ if self.related_sid is None: return '' @@ -148,30 +182,36 @@ class Item(MPTTModel, SlideMixin): def slide(self): """ - Return a map with all Data for the Slide + Return a map with all data for the slide. + + There are four cases: + * summary slide + * list of speakers + * related slide, i. e. the slide of the related object + * normal slide of the item + + The method returns only one of them according to the config value + 'presentation_argument' and the attribut 'related_sid'. """ if config['presentation_argument'] == 'summary': - data = { - 'title': self.get_title(), - 'items': self.get_children(), - 'template': 'projector/AgendaSummary.html', - } + data = {'title': self.get_title(), + 'items': self.get_children(), + 'template': 'projector/AgendaSummary.html'} + elif config['presentation_argument'] == 'show_list_of_speakers': - speakers = Speaker.objects.filter(time=None, item=self.pk).order_by('weight') - old_speakers = Speaker.objects.filter(item=self.pk).exclude(time=None).order_by('time') - slice_items = max(0, old_speakers.count()-2) + list_of_speakers = self.get_list_of_speakers( + old_speakers_count=config['agenda_show_last_speakers']) data = {'title': self.get_title(), 'template': 'projector/agenda_list_of_speaker.html', - 'speakers': speakers, - 'old_speakers': old_speakers[slice_items:]} + 'list_of_speakers': list_of_speakers} elif self.related_sid: data = self.get_related_slide().slide() + else: - data = { - 'item': self, - 'title': self.get_title(), - 'template': 'projector/AgendaText.html', - } + data = {'item': self, + 'title': self.get_title(), + 'template': 'projector/AgendaText.html'} + return data def set_closed(self, closed=True): @@ -209,44 +249,72 @@ class Item(MPTTModel, SlideMixin): super(Item, self).delete() Item.objects.rebuild() - def get_absolute_url(self, link='view'): + def get_list_of_speakers(self, old_speakers_count=None, coming_speakers_count=None): """ - Return the URL to this item. By default it is the Link to its - slide - - link can be: - * view - * edit - * delete + Returns the list of speakers as a list of dictionaries. Each + dictionary contains a prefix, the speaker and its type. Types + are old_speaker, actual_speaker and coming_speaker. """ - if link == 'view': - if self.related_sid: - return self.get_related_slide().get_absolute_url(link) - return reverse('item_view', args=[str(self.id)]) - if link == 'edit': - if self.related_sid: - return self.get_related_slide().get_absolute_url(link) - return reverse('item_edit', args=[str(self.id)]) - if link == 'delete': - return reverse('item_delete', args=[str(self.id)]) + speaker_query = Speaker.objects.filter(item=self) + list_of_speakers = [] - def __unicode__(self): - return self.get_title() + # Parse old speakers + old_speakers = speaker_query.exclude(begin_time=None).exclude(end_time=None).order_by('end_time') + if old_speakers_count is None: + old_speakers_count = old_speakers.count() + last_old_speakers_count = max(0, old_speakers.count() - old_speakers_count) + old_speakers = old_speakers[last_old_speakers_count:] + for number, speaker in enumerate(old_speakers): + prefix = old_speakers_count - number + speaker_dict = { + 'prefix': '-%d' % prefix, + 'speaker': speaker, + 'type': 'old_speaker', + 'first_in_group': False, + 'last_in_group': False} + if number == 0: + speaker_dict['first_in_group'] = True + if number == old_speakers_count - 1: + speaker_dict['last_in_group'] = True + list_of_speakers.append(speaker_dict) - class Meta: - permissions = ( - ('can_see_agenda', ugettext_noop("Can see agenda")), - ('can_manage_agenda', ugettext_noop("Can manage agenda")), - ('can_see_orga_items', ugettext_noop("Can see orga items and time scheduling of agenda")), - ) + # Parse actual speaker + try: + actual_speaker = speaker_query.filter(end_time=None).exclude(begin_time=None).get() + except Speaker.DoesNotExist: + pass + else: + list_of_speakers.append({ + 'prefix': '0', + 'speaker': actual_speaker, + 'type': 'actual_speaker', + 'first_in_group': True, + 'last_in_group': True}) - class MPTTMeta: - order_insertion_by = ['weight'] + # Parse coming speakers + coming_speakers = speaker_query.filter(begin_time=None).order_by('weight') + if coming_speakers_count is None: + coming_speakers_count = coming_speakers.count() + coming_speakers = coming_speakers[:max(0, coming_speakers_count)] + for number, speaker in enumerate(coming_speakers): + speaker_dict = { + 'prefix': number + 1, + 'speaker': speaker, + 'type': 'coming_speaker', + 'first_in_group': False, + 'last_in_group': False} + if number == 0: + speaker_dict['first_in_group'] = True + if number == coming_speakers_count - 1: + speaker_dict['last_in_group'] = True + list_of_speakers.append(speaker_dict) + + return list_of_speakers class SpeakerManager(models.Manager): def add(self, person, item): - if self.filter(person=person, item=item, time=None).exists(): + if self.filter(person=person, item=item, begin_time=None).exists(): raise OpenSlidesError(_('%(person)s is already on the list of speakers of item %(id)s.') % {'person': person, 'id': item.id}) weight = (self.filter(item=item).aggregate( models.Max('weight'))['weight__max'] or 0) @@ -270,9 +338,14 @@ class Speaker(models.Model): ForeinKey to the AgendaItem to which the person want to speak. """ - time = models.DateTimeField(null=True) + begin_time = models.DateTimeField(null=True) """ - Saves the time, when the speaker has spoken. None, if he has not spoken yet. + Saves the time, when the speaker begins to speak. None, if he has not spoken yet. + """ + + end_time = models.DateTimeField(null=True) + """ + Saves the time, when the speaker ends his speach. None, if he is not finished yet. """ weight = models.IntegerField(null=True) @@ -295,12 +368,26 @@ class Speaker(models.Model): return reverse('agenda_speaker_delete', args=[self.item.pk, self.pk]) - def speak(self): + def begin_speach(self): """ Let the person speak. - Set the weight to None and the time to now. + Set the weight to None and the time to now. If anyone is still + speaking, end his speach. """ + 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() self.weight = None - self.time = datetime.now() + self.begin_time = datetime.now() + self.save() + + def end_speach(self): + """ + The speach is finished. Set the time to now. + """ + self.end_time = datetime.now() self.save() diff --git a/openslides/agenda/signals.py b/openslides/agenda/signals.py index 4d9d99527..e7f92afed 100644 --- a/openslides/agenda/signals.py +++ b/openslides/agenda/signals.py @@ -12,11 +12,11 @@ from django.dispatch import receiver from django import forms -from django.utils.translation import ugettext_lazy, ugettext_noop +from django.utils.translation import ugettext_lazy, ugettext_noop, ugettext as _ from django.template.loader import render_to_string from openslides.config.signals import config_signal -from openslides.config.api import ConfigVariable, ConfigPage +from openslides.config.api import config, ConfigVariable, ConfigPage from openslides.projector.signals import projector_overlays from openslides.projector.projector import Overlay @@ -40,8 +40,15 @@ def setup_agenda_config_page(sender, **kwargs): form_field=forms.CharField( widget=forms.DateTimeInput(format='%d.%m.%Y %H:%M'), required=False, - label=ugettext_lazy('Begin of event'), - help_text=ugettext_lazy('Input format: DD.MM.YYYY HH:MM'))) + label=_('Begin of event'), + help_text=_('Input format: DD.MM.YYYY HH:MM'))) + + agenda_show_last_speakers = ConfigVariable( + name='agenda_show_last_speakers', + default_value=1, + form_field=forms.IntegerField( + min_value=0, + label=_('Number of last speakers to be shown on the projector'))) extra_stylefiles = ['styles/timepicker.css', 'styles/jquery-ui/jquery-ui.custom.min.css'] extra_javascript = ['javascript/jquery-ui.custom.min.js', @@ -53,7 +60,7 @@ def setup_agenda_config_page(sender, **kwargs): url='agenda', required_permission='config.can_manage', weight=20, - variables=(agenda_start_event_date_time,), + variables=(agenda_start_event_date_time, agenda_show_last_speakers), extra_context={'extra_stylefiles': extra_stylefiles, 'extra_javascript': extra_javascript}) @@ -80,8 +87,10 @@ def agenda_list_of_speakers(sender, **kwargs): # Only show list of speakers on Agenda-Items return None clear_projector_cache() - speakers = Speaker.objects.filter(time=None, item=slide)[:5] - context = {'speakers': speakers} + list_of_speakers = slide.get_list_of_speakers( + old_speakers_count=config['agenda_show_last_speakers'], + coming_speakers_count=5) + context = {'list_of_speakers': list_of_speakers} return render_to_string('agenda/overlay_speaker_projector.html', context) return Overlay(name, get_widget_html, get_projector_html) diff --git a/openslides/agenda/static/javascript/agenda.js b/openslides/agenda/static/javascript/agenda.js index fd12a897d..068f5fb2e 100644 --- a/openslides/agenda/static/javascript/agenda.js +++ b/openslides/agenda/static/javascript/agenda.js @@ -24,8 +24,8 @@ function hideClosedSlides(hide) { return false; } -$('#speaker_list_changed_form').submit(function() { - $('#sort_order').val($('#list_of_speakers').sortable("toArray")); +$('#coming_speakers_changed_form').submit(function() { + $('#sort_order').val($('#coming_speakers').sortable("toArray")); }); $(function() { @@ -76,10 +76,10 @@ $(function() { //# $('#hide_closed_items').attr('checked', true); //# } - if ($('#list_of_speakers').length > 0) { - $('#list_of_speakers').sortable({axis: "y", containment: "parent", update: function(event, ui) { - $('#speaker_list_changed_form').show(); + if ($('#coming_speakers').length > 0) { + $('#coming_speakers').sortable({axis: "y", containment: "parent", update: function(event, ui) { + $('#coming_speakers_changed_form').show(); }}); - $('#list_of_speakers').disableSelection(); + $('#coming_speakers').disableSelection(); } }); diff --git a/openslides/agenda/static/styles/agenda.css b/openslides/agenda/static/styles/agenda.css index f5d1e311b..682330f15 100644 --- a/openslides/agenda/static/styles/agenda.css +++ b/openslides/agenda/static/styles/agenda.css @@ -22,15 +22,15 @@ table#agendatime td { white-space: nowrap; } -#list_of_speakers li { - line-height: 30px; -} - -#list_of_speakers { +div#complete_list_of_speakers li { list-style-type: none; } -#list_of_speakers span.ui-icon { +div#complete_list_of_speakers li { + line-height: 30px; +} + +#coming_speakers span.ui-icon { position: absolute; margin-left: -15px; margin-top: 6px; diff --git a/openslides/agenda/templates/agenda/overlay_speaker_projector.html b/openslides/agenda/templates/agenda/overlay_speaker_projector.html index e1596a4cb..287536aa0 100644 --- a/openslides/agenda/templates/agenda/overlay_speaker_projector.html +++ b/openslides/agenda/templates/agenda/overlay_speaker_projector.html @@ -1,13 +1,57 @@ {% load i18n %} +{% load tags %} + +
- {% if speakers %} + {% if list_of_speakers %}

{% trans "List of speakers" %}:

-
    - {% for speaker in speakers %} -
  1. {{ speaker }}
  2. +
+ {% else %} {% trans 'The list of speakers is empty.' %} {% endif %} diff --git a/openslides/agenda/templates/agenda/view.html b/openslides/agenda/templates/agenda/view.html index 46ec94e29..bad1cf15a 100644 --- a/openslides/agenda/templates/agenda/view.html +++ b/openslides/agenda/templates/agenda/view.html @@ -70,54 +70,11 @@ {% trans 'Show list' %} - {% endif %}

-{% if old_speakers %} -
- {% trans "Last speakers" %}: - {% if old_speakers|length > 1 %} -
- -
- {% endif %} -
-
- {% for speaker in old_speakers %} - {% if not forloop.last %} - {{forloop.counter}}. - [{{ speaker.time }}h] - {{ speaker }} - {% if perms.agenda.can_manage_agenda %} - - - - {% endif %} -
- {% endif %} - {% endfor %} -
- {% if old_speakers %} - {% with last=old_speakers|last %} - {{ old_speakers|length }}. - [{{ last.time }}h] - {{ last }} - {% if perms.agenda.can_manage_agenda %} - - - - {% endif %} - - {% endwith %} - {% endif %} -
-{% endif %} - {% if perms.agenda.can_manage_agenda %} - {% endif %} - -
- {% trans "Next speakers:" %} - + {% endif %} + {% endfor %}

- {% if is_speaker %} + {% if is_on_the_list_of_speakers %} {% trans "Remove me from the list" %} {% elif not object.speaker_list_closed and perms.can_be_speaker %} {% trans "Put me on the list" %} @@ -174,6 +157,6 @@ {% endfor %} {% endif %} -

+ {% endblock %} diff --git a/openslides/agenda/templates/projector/agenda_list_of_speaker.html b/openslides/agenda/templates/projector/agenda_list_of_speaker.html index edd00ae2f..c966e9093 100644 --- a/openslides/agenda/templates/projector/agenda_list_of_speaker.html +++ b/openslides/agenda/templates/projector/agenda_list_of_speaker.html @@ -1,8 +1,9 @@ -{% extends "base-projector.html" %} +{% extends 'base-projector.html' %} {% load i18n %} +{% load tags %} -{% block title %}{{ block.super }} - {{ item }}{% endblock %} +{% block title %}{{ block.super }} – {{ item }}{% endblock %} {% block content %}

{{ title }}

@@ -10,21 +11,36 @@ {% endblock %} {% block scrollcontent %} - {% if old_speakers|length > 0 %} - - {% endif %} - - {% if speakers %} -
    - {% for speaker in speakers %} -
  1. {{ speaker }}
  2. + + {% if list_of_speakers %} +
+ {% else %} -

{% trans 'The list of speakers is empty.' %}

+ {% trans 'The list of speakers is empty.' %} {% endif %} {% endblock %} diff --git a/openslides/agenda/urls.py b/openslides/agenda/urls.py index bf6416b67..2d7ad1e9f 100644 --- a/openslides/agenda/urls.py +++ b/openslides/agenda/urls.py @@ -6,13 +6,13 @@ URL list for the agenda app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ from django.conf.urls import url, patterns from openslides.agenda.views import ( - Overview, AgendaItemView, SetClosed, ItemUpdate, SpeakerSpeakView, + Overview, AgendaItemView, SetClosed, ItemUpdate, SpeakerSpeakView, SpeakerEndSpeachView, ItemCreate, ItemDelete, AgendaPDF, SpeakerAppendView, SpeakerDeleteView, SpeakerListCloseView, SpeakerChangeOrderView, CurrentListOfSpeakersView) @@ -91,6 +91,11 @@ urlpatterns = patterns( name='agenda_speaker_speak', ), + url(r'^(?P\d+)/speaker/end_speach/$', + SpeakerEndSpeachView.as_view(), + name='agenda_speaker_end_speach', + ), + url(r'^(?P\d+)/speaker/change_order/$', SpeakerChangeOrderView.as_view(), name='agenda_speaker_change_order', diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index 234be94b0..0f340bbed 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -6,7 +6,7 @@ Views for the agenda app. - :copyright: 2011, 2012 by the OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by the OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ # TODO: Rename all views and template names @@ -37,7 +37,7 @@ from .forms import ItemOrderForm, ItemForm, AppendSpeakerForm class Overview(TemplateView): """ - Show all agenda items, and update there range via post. + Show all agenda items, and update their range via post. """ permission_required = 'agenda.can_see_agenda' template_name = 'agenda/overview.html' @@ -127,15 +127,11 @@ class AgendaItemView(SingleObjectMixin, FormView): def get_context_data(self, **kwargs): self.object = self.get_object() - speakers = Speaker.objects.filter(time=None, item=self.object.pk).order_by('weight') - old_speakers = list(Speaker.objects.filter(item=self.object.pk) - .exclude(time=None).order_by('time')) + list_of_speakers = self.object.get_list_of_speakers() kwargs.update({ 'object': self.object, - 'speakers': speakers, - 'old_speakers': old_speakers, - 'is_speaker': Speaker.objects.filter( - time=None, person=self.request.user, item=self.object).exists(), + 'list_of_speakers': list_of_speakers, + 'is_on_the_list_of_speakers': Speaker.objects.filter(item=self.object, begin_time=None, person=self.request.user).exists(), 'show_list': config['presentation_argument'] == 'show_list_of_speakers', }) return super(AgendaItemView, self).get_context_data(**kwargs) @@ -288,7 +284,7 @@ class SpeakerDeleteView(DeleteView): if 'speaker' in kwargs: return request.user.has_perm('agenda.can_manage_agenda') else: - # Any person how is on the list of speakers can delete him self from the list + # Any person who is on the list of speakers can delete himself from the list. return True def get(self, *args, **kwargs): @@ -334,15 +330,41 @@ class SpeakerSpeakView(SingleObjectMixin, RedirectView): try: speaker = Speaker.objects.filter( person=kwargs['person_id'], - item=self.object.pk).exclude( - weight=None).get() - except Speaker.DoesNotExist: + item=self.object, + begin_time=None).get() + except Speaker.DoesNotExist: # TODO: Check the MultipleObjectsReturned error here? messages.error( self.request, _('%(person)s is not on the list of %(item)s.') % {'person': kwargs['person_id'], 'item': self.object}) else: - speaker.speak() + speaker.begin_speach() + + def get_url_name_args(self): + return [self.object.pk] + + +class SpeakerEndSpeachView(SingleObjectMixin, RedirectView): + """ + The speach of the actual speaker is finished. + """ + permission_required = 'agenda.can_manage_agenda' + url_name = 'item_view' + model = Item + + def pre_redirect(self, *args, **kwargs): + self.object = self.get_object() + try: + speaker = Speaker.objects.filter( + item=self.object, + end_time=None).exclude(begin_time=None).get() + except Speaker.DoesNotExist: + messages.error( + self.request, + _('There is no one speaking at the moment according to %(item)s.') + % {'item': self.object}) + else: + speaker.end_speach() def get_url_name_args(self): return [self.object.pk] diff --git a/openslides/projector/static/styles/projector.css b/openslides/projector/static/styles/projector.css index 8ceb44822..fbff317f2 100644 --- a/openslides/projector/static/styles/projector.css +++ b/openslides/projector/static/styles/projector.css @@ -73,19 +73,6 @@ body{ width: 100%; height: 100%; } -#overlay_list_of_speaker_box { - position: fixed; - bottom: 0; - right: 0; - border-radius: 0.4em; - border: 0.1em solid #777777; - background-color: #cccccc; - opacity: 0.9; - padding: 0 1em; - margin: 1em; - z-index: 2; - width: 50%; -} /*** CONTENT ***/ #contentwrapper { diff --git a/tests/agenda/test_list_of_speakers.py b/tests/agenda/test_list_of_speakers.py index 9233d6fd0..8a6d82ca4 100644 --- a/tests/agenda/test_list_of_speakers.py +++ b/tests/agenda/test_list_of_speakers.py @@ -41,7 +41,8 @@ class ListOfSpeakerModelTests(TestCase): # Check time and weight for object in (speaker1_item1, speaker2_item1, speaker1_item2): - self.assertIsNone(object.time) + self.assertIsNone(object.begin_time) + self.assertIsNone(object.end_time) self.assertEqual(speaker1_item1.weight, 1) self.assertEqual(speaker1_item2.weight, 1) self.assertEqual(speaker2_item1.weight, 2) @@ -52,13 +53,25 @@ class ListOfSpeakerModelTests(TestCase): self.item1.save() self.assertTrue(Item.objects.get(pk=self.item1.pk).speaker_list_closed) - def test_speak(self): + def test_speak_and_finish(self): speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1) - - self.assertIsNone(speaker1_item1.time) - speaker1_item1.speak() - self.assertIsNotNone(Speaker.objects.get(pk=speaker1_item1.pk).time) + self.assertIsNone(speaker1_item1.begin_time) + self.assertIsNone(speaker1_item1.end_time) + speaker1_item1.begin_speach() + 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() + 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() + self.assertIsNone(speaker1_item1.end_time) + self.assertIsNone(speaker2_item1.begin_time) + speaker2_item1.begin_speach() + self.assertIsNotNone(Speaker.objects.get(person=self.speaker1, item=self.item1).end_time) + self.assertIsNotNone(speaker2_item1.begin_time) class SpeakerViewTestCase(TestCase): @@ -160,7 +173,21 @@ class TestSpeakerSpeakView(SpeakerViewTestCase): speaker = Speaker.objects.add(self.speaker1, self.item1) response = self.check_url(url, self.admin_client, 302) speaker = Speaker.objects.get(pk=speaker.pk) - self.assertIsNotNone(speaker.time) + self.assertIsNotNone(speaker.begin_time) + self.assertIsNone(speaker.weight) + + +class TestSpeakerEndSpeachView(SpeakerViewTestCase): + def test_get(self): + url = '/agenda/1/speaker/end_speach/' + response = self.check_url(url, self.admin_client, 302) + self.assertMessage(response, 'There is no one speaking at the moment according to item1.') + speaker = Speaker.objects.add(self.speaker1, self.item1) + speaker.begin_speach() + response = self.check_url(url, self.admin_client, 302) + speaker = Speaker.objects.get(pk=speaker.pk) + self.assertIsNotNone(speaker.begin_time) + self.assertIsNotNone(speaker.end_time) self.assertIsNone(speaker.weight)