Merge pull request #625 from normanjaeckel/ListOfSpeakersRework
Add second time field for list of speakers.
This commit is contained in:
commit
8af50a36cc
@ -6,9 +6,9 @@
|
|||||||
The OpenSlides agenda app appends the functionality to OpenSlides to
|
The OpenSlides agenda app appends the functionality to OpenSlides to
|
||||||
manage agendas.
|
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.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
Forms for the agenda app.
|
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.
|
: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.
|
Checks, that the user is not already on the list.
|
||||||
"""
|
"""
|
||||||
speaker = self.cleaned_data['speaker']
|
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(
|
raise forms.ValidationError(ugettext_lazy(
|
||||||
'%s is already on the list of speakers.'
|
'%s is already on the list of speakers.'
|
||||||
% unicode(speaker)))
|
% unicode(speaker)))
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
Models for the agenda app.
|
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.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -99,10 +99,44 @@ class Item(MPTTModel, SlideMixin):
|
|||||||
True, if the list of speakers is closed.
|
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):
|
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)
|
object = get_slide_from_sid(self.related_sid, element=True)
|
||||||
if object is None:
|
if object is None:
|
||||||
self.title = 'Item for deleted slide: %s' % self.related_sid
|
self.title = 'Item for deleted slide: %s' % self.related_sid
|
||||||
@ -114,7 +148,7 @@ class Item(MPTTModel, SlideMixin):
|
|||||||
|
|
||||||
def get_related_type(self):
|
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
|
return self.get_related_slide().prefix
|
||||||
|
|
||||||
@ -129,7 +163,7 @@ class Item(MPTTModel, SlideMixin):
|
|||||||
|
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
"""
|
"""
|
||||||
return the title of this item.
|
Return the title of this item.
|
||||||
"""
|
"""
|
||||||
if self.related_sid is None:
|
if self.related_sid is None:
|
||||||
return self.title
|
return self.title
|
||||||
@ -137,7 +171,7 @@ class Item(MPTTModel, SlideMixin):
|
|||||||
|
|
||||||
def get_title_supplement(self):
|
def get_title_supplement(self):
|
||||||
"""
|
"""
|
||||||
return a supplement for the title.
|
Return a supplement for the title.
|
||||||
"""
|
"""
|
||||||
if self.related_sid is None:
|
if self.related_sid is None:
|
||||||
return ''
|
return ''
|
||||||
@ -148,30 +182,36 @@ class Item(MPTTModel, SlideMixin):
|
|||||||
|
|
||||||
def slide(self):
|
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':
|
if config['presentation_argument'] == 'summary':
|
||||||
data = {
|
data = {'title': self.get_title(),
|
||||||
'title': self.get_title(),
|
'items': self.get_children(),
|
||||||
'items': self.get_children(),
|
'template': 'projector/AgendaSummary.html'}
|
||||||
'template': 'projector/AgendaSummary.html',
|
|
||||||
}
|
|
||||||
elif config['presentation_argument'] == 'show_list_of_speakers':
|
elif config['presentation_argument'] == 'show_list_of_speakers':
|
||||||
speakers = Speaker.objects.filter(time=None, item=self.pk).order_by('weight')
|
list_of_speakers = self.get_list_of_speakers(
|
||||||
old_speakers = Speaker.objects.filter(item=self.pk).exclude(time=None).order_by('time')
|
old_speakers_count=config['agenda_show_last_speakers'])
|
||||||
slice_items = max(0, old_speakers.count()-2)
|
|
||||||
data = {'title': self.get_title(),
|
data = {'title': self.get_title(),
|
||||||
'template': 'projector/agenda_list_of_speaker.html',
|
'template': 'projector/agenda_list_of_speaker.html',
|
||||||
'speakers': speakers,
|
'list_of_speakers': list_of_speakers}
|
||||||
'old_speakers': old_speakers[slice_items:]}
|
|
||||||
elif self.related_sid:
|
elif self.related_sid:
|
||||||
data = self.get_related_slide().slide()
|
data = self.get_related_slide().slide()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
data = {
|
data = {'item': self,
|
||||||
'item': self,
|
'title': self.get_title(),
|
||||||
'title': self.get_title(),
|
'template': 'projector/AgendaText.html'}
|
||||||
'template': 'projector/AgendaText.html',
|
|
||||||
}
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def set_closed(self, closed=True):
|
def set_closed(self, closed=True):
|
||||||
@ -209,44 +249,72 @@ class Item(MPTTModel, SlideMixin):
|
|||||||
super(Item, self).delete()
|
super(Item, self).delete()
|
||||||
Item.objects.rebuild()
|
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
|
Returns the list of speakers as a list of dictionaries. Each
|
||||||
slide
|
dictionary contains a prefix, the speaker and its type. Types
|
||||||
|
are old_speaker, actual_speaker and coming_speaker.
|
||||||
link can be:
|
|
||||||
* view
|
|
||||||
* edit
|
|
||||||
* delete
|
|
||||||
"""
|
"""
|
||||||
if link == 'view':
|
speaker_query = Speaker.objects.filter(item=self)
|
||||||
if self.related_sid:
|
list_of_speakers = []
|
||||||
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 __unicode__(self):
|
# Parse old speakers
|
||||||
return self.get_title()
|
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:
|
# Parse actual speaker
|
||||||
permissions = (
|
try:
|
||||||
('can_see_agenda', ugettext_noop("Can see agenda")),
|
actual_speaker = speaker_query.filter(end_time=None).exclude(begin_time=None).get()
|
||||||
('can_manage_agenda', ugettext_noop("Can manage agenda")),
|
except Speaker.DoesNotExist:
|
||||||
('can_see_orga_items', ugettext_noop("Can see orga items and time scheduling of agenda")),
|
pass
|
||||||
)
|
else:
|
||||||
|
list_of_speakers.append({
|
||||||
|
'prefix': '0',
|
||||||
|
'speaker': actual_speaker,
|
||||||
|
'type': 'actual_speaker',
|
||||||
|
'first_in_group': True,
|
||||||
|
'last_in_group': True})
|
||||||
|
|
||||||
class MPTTMeta:
|
# Parse coming speakers
|
||||||
order_insertion_by = ['weight']
|
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):
|
class SpeakerManager(models.Manager):
|
||||||
def add(self, person, item):
|
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})
|
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(
|
weight = (self.filter(item=item).aggregate(
|
||||||
models.Max('weight'))['weight__max'] or 0)
|
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.
|
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)
|
weight = models.IntegerField(null=True)
|
||||||
@ -295,12 +368,26 @@ class Speaker(models.Model):
|
|||||||
return reverse('agenda_speaker_delete',
|
return reverse('agenda_speaker_delete',
|
||||||
args=[self.item.pk, self.pk])
|
args=[self.item.pk, self.pk])
|
||||||
|
|
||||||
def speak(self):
|
def begin_speach(self):
|
||||||
"""
|
"""
|
||||||
Let the person speak.
|
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.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()
|
self.save()
|
||||||
|
@ -12,11 +12,11 @@
|
|||||||
|
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django import forms
|
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 django.template.loader import render_to_string
|
||||||
|
|
||||||
from openslides.config.signals import config_signal
|
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.signals import projector_overlays
|
||||||
from openslides.projector.projector import Overlay
|
from openslides.projector.projector import Overlay
|
||||||
@ -40,8 +40,15 @@ def setup_agenda_config_page(sender, **kwargs):
|
|||||||
form_field=forms.CharField(
|
form_field=forms.CharField(
|
||||||
widget=forms.DateTimeInput(format='%d.%m.%Y %H:%M'),
|
widget=forms.DateTimeInput(format='%d.%m.%Y %H:%M'),
|
||||||
required=False,
|
required=False,
|
||||||
label=ugettext_lazy('Begin of event'),
|
label=_('Begin of event'),
|
||||||
help_text=ugettext_lazy('Input format: DD.MM.YYYY HH:MM')))
|
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_stylefiles = ['styles/timepicker.css', 'styles/jquery-ui/jquery-ui.custom.min.css']
|
||||||
extra_javascript = ['javascript/jquery-ui.custom.min.js',
|
extra_javascript = ['javascript/jquery-ui.custom.min.js',
|
||||||
@ -53,7 +60,7 @@ def setup_agenda_config_page(sender, **kwargs):
|
|||||||
url='agenda',
|
url='agenda',
|
||||||
required_permission='config.can_manage',
|
required_permission='config.can_manage',
|
||||||
weight=20,
|
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_context={'extra_stylefiles': extra_stylefiles,
|
||||||
'extra_javascript': extra_javascript})
|
'extra_javascript': extra_javascript})
|
||||||
|
|
||||||
@ -80,8 +87,10 @@ def agenda_list_of_speakers(sender, **kwargs):
|
|||||||
# Only show list of speakers on Agenda-Items
|
# Only show list of speakers on Agenda-Items
|
||||||
return None
|
return None
|
||||||
clear_projector_cache()
|
clear_projector_cache()
|
||||||
speakers = Speaker.objects.filter(time=None, item=slide)[:5]
|
list_of_speakers = slide.get_list_of_speakers(
|
||||||
context = {'speakers': 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 render_to_string('agenda/overlay_speaker_projector.html', context)
|
||||||
|
|
||||||
return Overlay(name, get_widget_html, get_projector_html)
|
return Overlay(name, get_widget_html, get_projector_html)
|
||||||
|
@ -24,8 +24,8 @@ function hideClosedSlides(hide) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#speaker_list_changed_form').submit(function() {
|
$('#coming_speakers_changed_form').submit(function() {
|
||||||
$('#sort_order').val($('#list_of_speakers').sortable("toArray"));
|
$('#sort_order').val($('#coming_speakers').sortable("toArray"));
|
||||||
});
|
});
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
@ -76,10 +76,10 @@ $(function() {
|
|||||||
//# $('#hide_closed_items').attr('checked', true);
|
//# $('#hide_closed_items').attr('checked', true);
|
||||||
//# }
|
//# }
|
||||||
|
|
||||||
if ($('#list_of_speakers').length > 0) {
|
if ($('#coming_speakers').length > 0) {
|
||||||
$('#list_of_speakers').sortable({axis: "y", containment: "parent", update: function(event, ui) {
|
$('#coming_speakers').sortable({axis: "y", containment: "parent", update: function(event, ui) {
|
||||||
$('#speaker_list_changed_form').show();
|
$('#coming_speakers_changed_form').show();
|
||||||
}});
|
}});
|
||||||
$('#list_of_speakers').disableSelection();
|
$('#coming_speakers').disableSelection();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -22,15 +22,15 @@ table#agendatime td {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
#list_of_speakers li {
|
div#complete_list_of_speakers li {
|
||||||
line-height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#list_of_speakers {
|
|
||||||
list-style-type: none;
|
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;
|
position: absolute;
|
||||||
margin-left: -15px;
|
margin-left: -15px;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
|
@ -1,13 +1,57 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load tags %}
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
#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 .5em;
|
||||||
|
margin: 1em;
|
||||||
|
z-index: 2;
|
||||||
|
width: 30%;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
#overlay_list_of_speaker_box h3 {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
#overlay_list_of_speaker_box ul {
|
||||||
|
list-style-type: None;
|
||||||
|
padding-left: 0;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
#overlay_list_of_speaker_box li {
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
#overlay_list_of_speaker_box li span.number {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
width: 1.3em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<div id="overlay_list_of_speaker_box">
|
<div id="overlay_list_of_speaker_box">
|
||||||
{% if speakers %}
|
{% if list_of_speakers %}
|
||||||
<h3>{% trans "List of speakers" %}:</h3>
|
<h3>{% trans "List of speakers" %}:</h3>
|
||||||
<ol>
|
<ul>
|
||||||
{% for speaker in speakers %}
|
{% for speaker_dict in list_of_speakers %}
|
||||||
<li>{{ speaker }}</li>
|
<li>
|
||||||
|
{% if speaker_dict.type == 'actual_speaker' %}
|
||||||
|
<span class="number">→</span><strong>
|
||||||
|
{% else %}
|
||||||
|
<span class="number">{{ speaker_dict.prefix }}</span>
|
||||||
|
{% endif %}
|
||||||
|
<span class="name">{{ speaker_dict.speaker }}</span>
|
||||||
|
{% if speaker_dict.type == 'actual_speaker' %}
|
||||||
|
</strong>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i>{% trans 'The list of speakers is empty.' %}</i>
|
<i>{% trans 'The list of speakers is empty.' %}</i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -70,54 +70,11 @@
|
|||||||
<i class="icon icon-facetime-video {% if item.active and show_list %}icon-white{% endif %}"></i>
|
<i class="icon icon-facetime-video {% if item.active and show_list %}icon-white{% endif %}"></i>
|
||||||
{% trans 'Show list' %}
|
{% trans 'Show list' %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if old_speakers %}
|
|
||||||
<div class="well">
|
|
||||||
<b>{% trans "Last speakers" %}:</b>
|
|
||||||
{% if old_speakers|length > 1 %}
|
|
||||||
<div class="btn-group" data-toggle="buttons-checkbox">
|
|
||||||
<button type="button" class="btn btn-mini" data-toggle="collapse" data-target="#all_speakers">
|
|
||||||
{% trans "Show all speakers" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<br>
|
|
||||||
<div id="all_speakers" class="collapse out">
|
|
||||||
{% for speaker in old_speakers %}
|
|
||||||
{% if not forloop.last %}
|
|
||||||
<small>{{forloop.counter}}.
|
|
||||||
[{{ speaker.time }}h]
|
|
||||||
<a href="{% model_url speaker %}">{{ speaker }}</a>
|
|
||||||
{% if perms.agenda.can_manage_agenda %}
|
|
||||||
<a href="{% model_url speaker 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini">
|
|
||||||
<i class="icon-remove"></i>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</small><br>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% if old_speakers %}
|
|
||||||
{% with last=old_speakers|last %}
|
|
||||||
<small>{{ old_speakers|length }}.
|
|
||||||
[{{ last.time }}h]
|
|
||||||
<a href="{% model_url last %}">{{ last }}</a>
|
|
||||||
{% if perms.agenda.can_manage_agenda %}
|
|
||||||
<a href="{% model_url last 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini">
|
|
||||||
<i class="icon-remove"></i>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</small>
|
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if perms.agenda.can_manage_agenda %}
|
{% if perms.agenda.can_manage_agenda %}
|
||||||
<form id="speaker_list_changed_form" action="{% url 'agenda_speaker_change_order' item.pk %}" method="post" style="display:none" class="alert alert-warning">{% csrf_token %}
|
<form id="coming_speakers_changed_form" action="{% url 'agenda_speaker_change_order' item.pk %}" method="post" style="display:none" class="alert alert-warning">{% csrf_token %}
|
||||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||||
<p>{% trans "Do you want to save the changed order of speakers?" %}</p>
|
<p>{% trans "Do you want to save the changed order of speakers?" %}</p>
|
||||||
<input id="sort_order" name="sort_order" type="hidden"></hidden>
|
<input id="sort_order" name="sort_order" type="hidden"></hidden>
|
||||||
@ -128,29 +85,55 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<div id="complete_list_of_speakers" class="well">
|
||||||
<div class="well">
|
{% for speaker_dict in list_of_speakers %}
|
||||||
<b>{% trans "Next speakers:" %}</b>
|
{% if speaker_dict.first_in_group %}
|
||||||
<ul {% if perms.agenda.can_manage_agenda %}id="list_of_speakers"{% endif %}>
|
{% if speaker_dict.type == 'old_speaker' %}
|
||||||
{% for speaker in speakers %}
|
<b>{% trans "Last speakers" %}:</b>
|
||||||
<li id="speaker_{{ speaker.pk }}">
|
<div class="btn-group" data-toggle="buttons-checkbox">
|
||||||
<span {% if perms.agenda.can_manage_agenda %}class="ui-icon ui-icon-arrowthick-2-n-s"{% endif %}></span>
|
<button type="button" class="btn btn-mini" data-toggle="collapse" data-target="#old_speakers">
|
||||||
<span>{{ forloop.counter }}.</span>
|
{% trans "Show all speakers" %}
|
||||||
<a href="{% model_url speaker %}">{{ speaker }}</a>
|
</button>
|
||||||
{% if perms.agenda.can_manage_agenda %}
|
</div>
|
||||||
<a href="{% url 'agenda_speaker_speak' item.pk speaker.person.person_id %}" class="btn btn-mini"><i class="icon-bell"></i> {% trans "Next speaker" %}</a>
|
{% elif speaker_dict.type == 'actual_speaker' %}
|
||||||
<a href="{% model_url speaker 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini">
|
<b>{% trans 'Actual speaker' %}:</b>
|
||||||
<i class="icon-remove"></i>
|
{% else %}
|
||||||
</a>
|
<b>{% trans "Next speakers" %}:</b>
|
||||||
|
{% endif %}
|
||||||
|
<ul
|
||||||
|
{% if speaker_dict.type == 'old_speaker' %}
|
||||||
|
id="old_speakers" class="collapse out"
|
||||||
|
{% elif speaker_dict.type == 'coming_speaker' %}
|
||||||
|
id="coming_speakers"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
>
|
||||||
{% empty %}
|
{% endif %}
|
||||||
<i>{% trans "The list of speakers is empty." %}</i>
|
<li id="speaker_{{ speaker_dict.speaker.pk }}">
|
||||||
{% endfor %}
|
{% if speaker_dict.type == 'coming_speaker' %}
|
||||||
</ul>
|
<span {% if perms.agenda.can_manage_agenda %}class="ui-icon ui-icon-arrowthick-2-n-s"{% endif %}></span>
|
||||||
|
{{ speaker_dict.prefix }}.
|
||||||
|
{% else %}
|
||||||
|
[{{ speaker_dict.speaker.begin_time }}{% if speaker_dict.type == 'old_speaker' %} – {{ speaker_dict.speaker.end_time }}{% endif %}]
|
||||||
|
{% endif %}
|
||||||
|
<a href="{% model_url speaker_dict.speaker %}">{{ speaker_dict.speaker }}</a>
|
||||||
|
{% if perms.agenda.can_manage_agenda %}
|
||||||
|
{% if speaker_dict.type == 'actual_speaker' %}
|
||||||
|
<a href="{% url 'agenda_speaker_end_speach' item.pk %}" class="btn btn-mini"><i class="icon-bell"></i> {% trans 'End speach' %}</a>
|
||||||
|
{% elif speaker_dict.type == 'coming_speaker' %}
|
||||||
|
<a href="{% url 'agenda_speaker_speak' item.pk speaker_dict.speaker.person.person_id %}" class="btn btn-mini"><i class="icon-bell"></i> {% trans "Next speaker" %}</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{% model_url speaker_dict.speaker 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini">
|
||||||
|
<i class="icon-remove"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% if speaker_dict.last_in_group %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% if is_speaker %}
|
{% if is_on_the_list_of_speakers %}
|
||||||
<a href="{% url 'agenda_speaker_delete' object.id %}" class="btn">{% trans "Remove me from the list" %}</a>
|
<a href="{% url 'agenda_speaker_delete' object.id %}" class="btn">{% trans "Remove me from the list" %}</a>
|
||||||
{% elif not object.speaker_list_closed and perms.can_be_speaker %}
|
{% elif not object.speaker_list_closed and perms.can_be_speaker %}
|
||||||
<a href="{% url 'agenda_speaker_append' object.id %}" class="btn">{% trans "Put me on the list" %}</a>
|
<a href="{% url 'agenda_speaker_append' object.id %}" class="btn">{% trans "Put me on the list" %}</a>
|
||||||
@ -174,6 +157,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
{% extends "base-projector.html" %}
|
{% extends 'base-projector.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load tags %}
|
||||||
|
|
||||||
{% block title %}{{ block.super }} - {{ item }}{% endblock %}
|
{% block title %}{{ block.super }} – {{ item }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{{ title }}</h1>
|
<h1>{{ title }}</h1>
|
||||||
@ -10,21 +11,36 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scrollcontent %}
|
{% block scrollcontent %}
|
||||||
{% if old_speakers|length > 0 %}
|
<style type="text/css">
|
||||||
<ul class="list_of_speakers last_speakers">
|
ul#list_of_speakers {
|
||||||
{% for speaker in old_speakers %}
|
list-style-type: None;
|
||||||
<li>{{ speaker }}</li>
|
}
|
||||||
{% endfor %}
|
#overlay_list_of_speaker_box li {
|
||||||
</ul>
|
margin-bottom: 1px;
|
||||||
{% endif %}
|
}
|
||||||
|
#overlay_list_of_speaker_box li span.number {
|
||||||
{% if speakers %}
|
display: block;
|
||||||
<ol class="list_of_speakers">
|
float: left;
|
||||||
{% for speaker in speakers %}
|
width: 1.3em;
|
||||||
<li>{{ speaker }}</li>
|
}
|
||||||
|
</style>
|
||||||
|
{% if list_of_speakers %}
|
||||||
|
<ul id="list_of_speakers">
|
||||||
|
{% for speaker_dict in list_of_speakers %}
|
||||||
|
<li>
|
||||||
|
{% if speaker_dict.type == 'actual_speaker' %}
|
||||||
|
<span class="number">→</span><strong>
|
||||||
|
{% else %}
|
||||||
|
<span class="number">{{ speaker_dict.prefix }} </span>
|
||||||
|
{% endif %}
|
||||||
|
<span class="name">{{ speaker_dict.speaker }}</span>
|
||||||
|
{% if speaker_dict.type == 'actual_speaker' %}
|
||||||
|
</strong>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p><i>{% trans 'The list of speakers is empty.' %}</i></p>
|
<i>{% trans 'The list of speakers is empty.' %}</i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -6,13 +6,13 @@
|
|||||||
|
|
||||||
URL list for the agenda app.
|
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.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.conf.urls import url, patterns
|
from django.conf.urls import url, patterns
|
||||||
from openslides.agenda.views import (
|
from openslides.agenda.views import (
|
||||||
Overview, AgendaItemView, SetClosed, ItemUpdate, SpeakerSpeakView,
|
Overview, AgendaItemView, SetClosed, ItemUpdate, SpeakerSpeakView, SpeakerEndSpeachView,
|
||||||
ItemCreate, ItemDelete, AgendaPDF, SpeakerAppendView, SpeakerDeleteView,
|
ItemCreate, ItemDelete, AgendaPDF, SpeakerAppendView, SpeakerDeleteView,
|
||||||
SpeakerListCloseView, SpeakerChangeOrderView, CurrentListOfSpeakersView)
|
SpeakerListCloseView, SpeakerChangeOrderView, CurrentListOfSpeakersView)
|
||||||
|
|
||||||
@ -91,6 +91,11 @@ urlpatterns = patterns(
|
|||||||
name='agenda_speaker_speak',
|
name='agenda_speaker_speak',
|
||||||
),
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<pk>\d+)/speaker/end_speach/$',
|
||||||
|
SpeakerEndSpeachView.as_view(),
|
||||||
|
name='agenda_speaker_end_speach',
|
||||||
|
),
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/speaker/change_order/$',
|
url(r'^(?P<pk>\d+)/speaker/change_order/$',
|
||||||
SpeakerChangeOrderView.as_view(),
|
SpeakerChangeOrderView.as_view(),
|
||||||
name='agenda_speaker_change_order',
|
name='agenda_speaker_change_order',
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
Views for the agenda app.
|
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.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
# TODO: Rename all views and template names
|
# TODO: Rename all views and template names
|
||||||
@ -37,7 +37,7 @@ from .forms import ItemOrderForm, ItemForm, AppendSpeakerForm
|
|||||||
|
|
||||||
class Overview(TemplateView):
|
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'
|
permission_required = 'agenda.can_see_agenda'
|
||||||
template_name = 'agenda/overview.html'
|
template_name = 'agenda/overview.html'
|
||||||
@ -127,15 +127,11 @@ class AgendaItemView(SingleObjectMixin, FormView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
speakers = Speaker.objects.filter(time=None, item=self.object.pk).order_by('weight')
|
list_of_speakers = self.object.get_list_of_speakers()
|
||||||
old_speakers = list(Speaker.objects.filter(item=self.object.pk)
|
|
||||||
.exclude(time=None).order_by('time'))
|
|
||||||
kwargs.update({
|
kwargs.update({
|
||||||
'object': self.object,
|
'object': self.object,
|
||||||
'speakers': speakers,
|
'list_of_speakers': list_of_speakers,
|
||||||
'old_speakers': old_speakers,
|
'is_on_the_list_of_speakers': Speaker.objects.filter(item=self.object, begin_time=None, person=self.request.user).exists(),
|
||||||
'is_speaker': Speaker.objects.filter(
|
|
||||||
time=None, person=self.request.user, item=self.object).exists(),
|
|
||||||
'show_list': config['presentation_argument'] == 'show_list_of_speakers',
|
'show_list': config['presentation_argument'] == 'show_list_of_speakers',
|
||||||
})
|
})
|
||||||
return super(AgendaItemView, self).get_context_data(**kwargs)
|
return super(AgendaItemView, self).get_context_data(**kwargs)
|
||||||
@ -288,7 +284,7 @@ class SpeakerDeleteView(DeleteView):
|
|||||||
if 'speaker' in kwargs:
|
if 'speaker' in kwargs:
|
||||||
return request.user.has_perm('agenda.can_manage_agenda')
|
return request.user.has_perm('agenda.can_manage_agenda')
|
||||||
else:
|
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
|
return True
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
def get(self, *args, **kwargs):
|
||||||
@ -334,15 +330,41 @@ class SpeakerSpeakView(SingleObjectMixin, RedirectView):
|
|||||||
try:
|
try:
|
||||||
speaker = Speaker.objects.filter(
|
speaker = Speaker.objects.filter(
|
||||||
person=kwargs['person_id'],
|
person=kwargs['person_id'],
|
||||||
item=self.object.pk).exclude(
|
item=self.object,
|
||||||
weight=None).get()
|
begin_time=None).get()
|
||||||
except Speaker.DoesNotExist:
|
except Speaker.DoesNotExist: # TODO: Check the MultipleObjectsReturned error here?
|
||||||
messages.error(
|
messages.error(
|
||||||
self.request,
|
self.request,
|
||||||
_('%(person)s is not on the list of %(item)s.')
|
_('%(person)s is not on the list of %(item)s.')
|
||||||
% {'person': kwargs['person_id'], 'item': self.object})
|
% {'person': kwargs['person_id'], 'item': self.object})
|
||||||
else:
|
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):
|
def get_url_name_args(self):
|
||||||
return [self.object.pk]
|
return [self.object.pk]
|
||||||
|
@ -73,19 +73,6 @@ body{
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 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 ***/
|
/*** CONTENT ***/
|
||||||
#contentwrapper {
|
#contentwrapper {
|
||||||
|
@ -41,7 +41,8 @@ class ListOfSpeakerModelTests(TestCase):
|
|||||||
|
|
||||||
# Check time and weight
|
# Check time and weight
|
||||||
for object in (speaker1_item1, speaker2_item1, speaker1_item2):
|
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_item1.weight, 1)
|
||||||
self.assertEqual(speaker1_item2.weight, 1)
|
self.assertEqual(speaker1_item2.weight, 1)
|
||||||
self.assertEqual(speaker2_item1.weight, 2)
|
self.assertEqual(speaker2_item1.weight, 2)
|
||||||
@ -52,13 +53,25 @@ class ListOfSpeakerModelTests(TestCase):
|
|||||||
self.item1.save()
|
self.item1.save()
|
||||||
self.assertTrue(Item.objects.get(pk=self.item1.pk).speaker_list_closed)
|
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)
|
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
|
||||||
|
self.assertIsNone(speaker1_item1.begin_time)
|
||||||
self.assertIsNone(speaker1_item1.time)
|
self.assertIsNone(speaker1_item1.end_time)
|
||||||
speaker1_item1.speak()
|
speaker1_item1.begin_speach()
|
||||||
self.assertIsNotNone(Speaker.objects.get(pk=speaker1_item1.pk).time)
|
self.assertIsNotNone(Speaker.objects.get(pk=speaker1_item1.pk).begin_time)
|
||||||
self.assertIsNone(Speaker.objects.get(pk=speaker1_item1.pk).weight)
|
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):
|
class SpeakerViewTestCase(TestCase):
|
||||||
@ -160,7 +173,21 @@ class TestSpeakerSpeakView(SpeakerViewTestCase):
|
|||||||
speaker = Speaker.objects.add(self.speaker1, self.item1)
|
speaker = Speaker.objects.add(self.speaker1, self.item1)
|
||||||
response = self.check_url(url, self.admin_client, 302)
|
response = self.check_url(url, self.admin_client, 302)
|
||||||
speaker = Speaker.objects.get(pk=speaker.pk)
|
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)
|
self.assertIsNone(speaker.weight)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user