New Feature: List of speakers.

This commit is contained in:
Oskar Hahn 2013-03-18 12:34:47 +01:00
parent fc36ef2d7b
commit fa95119936
16 changed files with 698 additions and 39 deletions

View File

@ -6,8 +6,10 @@
The OpenSlides agenda app appends the functionality to OpenSlides to
manage agendas.
It includes time-management and list of speakers.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from . import signals
from . import signals, slides

View File

@ -17,7 +17,8 @@ from django.utils.translation import ugettext_lazy
from mptt.forms import TreeNodeChoiceField
from openslides.utils.forms import CssClassMixin
from .models import Item
from openslides.utils.person.forms import PersonFormField
from .models import Item, Speaker
class ItemForm(forms.ModelForm, CssClassMixin):
@ -57,3 +58,21 @@ class ItemOrderForm(CssClassMixin, forms.Form):
widget=forms.HiddenInput(attrs={'class': 'menu-mlid'}))
parent = forms.IntegerField(
widget=forms.HiddenInput(attrs={'class': 'menu-plid'}))
class AppendSpeakerForm(CssClassMixin, forms.Form):
speaker = PersonFormField(
widget=forms.Select(attrs={'class': 'medium-input'}),
label=ugettext_lazy("Set a person to the speaker list."))
def __init__(self, item, *args, **kwargs):
self.item = item
return super(AppendSpeakerForm, self).__init__(*args, **kwargs)
def clean_speaker(self):
speaker = self.cleaned_data['speaker']
if Speaker.objects.filter(person=speaker, item=self.item, time=None).exists():
raise forms.ValidationError(ugettext_lazy(
'%s is allready on the list of speakers.'
% speaker))
return speaker

View File

@ -10,17 +10,20 @@
:license: GNU GPL, see LICENSE for more details.
"""
from datetime import datetime
from django.db import models
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext
from mptt.models import MPTTModel, TreeForeignKey
from openslides.utils.exceptions import OpenSlidesError
from openslides.config.api import config
from openslides.projector.projector import SlideMixin
from openslides.projector.api import (
register_slidemodel, get_slide_from_sid, register_slidefunc)
from .slides import agenda_show
from openslides.utils.person.models import PersonField
class Item(MPTTModel, SlideMixin):
@ -39,15 +42,48 @@ class Item(MPTTModel, SlideMixin):
(ORGANIZATIONAL_ITEM, _('Organizational item')))
title = models.CharField(null=True, max_length=255, verbose_name=_("Title"))
"""Title of the agenda item."""
text = models.TextField(null=True, blank=True, verbose_name=_("Text"))
"""The optional text of the agenda item."""
comment = models.TextField(null=True, blank=True, verbose_name=_("Comment"))
"""Optional comment to the agenda item. Will not be shoun to normal users."""
closed = models.BooleanField(default=False, verbose_name=_("Closed"))
"""Flag, if the item is finished."""
weight = models.IntegerField(default=0, verbose_name=_("Weight"))
"""Weight to sort the item in the agenda."""
parent = TreeForeignKey('self', null=True, blank=True,
related_name='children')
type = models.IntegerField(max_length=1, choices=ITEM_TYPE, default=AGENDA_ITEM, verbose_name=_("Type"))
duration = models.CharField(null=True, blank=True, max_length=5, verbose_name=_("Duration (hh:mm)"))
"""The parent item in the agenda tree."""
type = models.IntegerField(max_length=1, choices=ITEM_TYPE,
default=AGENDA_ITEM, verbose_name=_("Type"))
"""
Type of the agenda item.
See Agenda.ITEM_TYPE for more informations.
"""
duration = models.CharField(null=True, blank=True, max_length=5,
verbose_name=_("Duration (hh:mm)"))
"""The intended duration for the topic."""
related_sid = models.CharField(null=True, blank=True, max_length=63)
"""
Slide-ID to another object to show it in the agenda.
For example a motion or assignment.
"""
speaker_list_closed = models.BooleanField(
default=False, verbose_name=_("List of speakers is closed"))
"""
True, if the list of speakers is closed.
"""
def get_related_slide(self):
"""
@ -106,6 +142,11 @@ class Item(MPTTModel, SlideMixin):
'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')
data = {'title': _('List of speakers for %s') % self.get_title(),
'template': 'projector/agenda_list_of_speaker.html',
'speakers': speakers}
elif self.related_sid:
data = self.get_related_slide().slide()
else:
@ -186,5 +227,43 @@ class Item(MPTTModel, SlideMixin):
order_insertion_by = ['weight']
register_slidemodel(Item, control_template='agenda/control_item.html')
register_slidefunc('agenda', agenda_show, weight=-1, name=_('Agenda'))
class SpeakerManager(models.Manager):
def add(self, person, item):
if self.filter(person=person, item=item, time=None).exists():
raise OpenSlidesError(_('%s is allready on the list of speakers from item %d') % (person, item.id))
weight = (self.filter(item=item).aggregate(
models.Max('weight'))['weight__max'] or 0)
return self.create(item=item, person=person, weight=weight + 1)
class Speaker(models.Model):
"""
Model for the Speaker list.
"""
objects = SpeakerManager()
person = PersonField()
item = models.ForeignKey(Item)
time = models.TimeField(null=True)
weight = models.IntegerField(null=True)
class Meta:
permissions = (
('can_be_speaker', ugettext_noop('Can be speaker')),
)
def __unicode__(self):
return unicode(self.person)
def get_absolute_url(self, link='detail'):
if link == 'detail' or link == 'view':
return self.person.get_absolute_url('detail')
if link == 'delete':
return reverse('agenda_speaker_delete',
args=[self.item.pk, self.pk])
def speak(self):
self.weight = None
self.time = datetime.now()
self.save()

View File

@ -12,12 +12,18 @@
from django.utils.translation import ugettext as _
from openslides.projector.api import register_slidemodel, register_slidefunc
from .models import Item
def agenda_show():
from openslides.agenda.models import Item
data = {}
items = Item.objects.filter(parent=None, type__exact=Item.AGENDA_ITEM)
data['title'] = _("Agenda")
data['items'] = items
data['template'] = 'projector/AgendaSummary.html'
return data
register_slidemodel(Item, control_template='agenda/control_item.html')
register_slidefunc('agenda', agenda_show, weight=-1, name=_('Agenda'))

View File

@ -34,6 +34,19 @@ function hideClosedSlides(hide) {
return false;
}
function toggleOldSpeakers() {
$('#show_old_speakers').toggle();
$('#old_speakers').toggle();
}
$('.toggle_old_speakers > a').click(function() {
toggleOldSpeakers();
});
$('#speaker_list_changed_form').submit(function() {
$('#sort_order').val($('#list_of_speakers').sortable("toArray"));
});
$(function() {
// change participant status (on/off)
$('.close_link').click(function(event) {
@ -72,11 +85,21 @@ $(function() {
$('#hide_closed_items').attr('checked', true);
}
});
if ($.cookie('Slide.HideClosed') === null) {
$('#hide_closed_items').attr('checked', false);
$.cookie('Slide.HideClosed', 0);
} else if ($.cookie('Slide.HideClosed') == 1) {
hideClosedSlides(true);
$('#hide_closed_items').attr('checked', true);
}
// TODO: Fix this code and reactivate it again
//# if ($.cookie('Slide.HideClosed') === null) {
//# $('#hide_closed_items').attr('checked', false);
//# $.cookie('Slide.HideClosed', 0);
//# } else if ($.cookie('Slide.HideClosed') == 1) {
//# hideClosedSlides(true);
//# $('#hide_closed_items').attr('checked', true);
//# }
// List of Speakers
toggleOldSpeakers();
$('#list_of_speakers').sortable({axis: "y", containment: "parent", update: function(event, ui) {
$('#speaker_list_changed_form').show();
}});
$('#list_of_speakers').disableSelection();
});

View File

@ -1,9 +1,18 @@
{% extends "base.html" %}
{% load i18n %}
{% load tags %}
{% load staticfiles %}
{% block title %}{{ block.super }} {{ item.title }}{% endblock %}
{% block javascript %}
{{ block.super }}
{% comment %} TODO: import the sortable-plugin in our custom jquery-file {% endcomment %}
<script src="http://code.jquery.com/ui/1.10.2/jquery-ui.js"></script>
<script src="{% static 'javascript/agenda.js' %}"></script>
{% endblock %}
{% block content %}
<h1>
{{ item }}
@ -38,4 +47,86 @@
<p>{{ item.comment|linebreaks }}</p>
{% endif %}
{% endif %}
{# List of Speakers #}
<h2>{% trans "Speaker List" %}{% if item.speaker_list_closed %}({% trans 'Closed' %}){% endif %}</h2>
{% if item.speaker_list_closed %}
<a href="{% url 'agenda_speaker_open' item.pk %}">{% trans 'Open list of speakers' %}</a>
{% else %}
<a href="{% url 'agenda_speaker_close' item.pk %}">{% trans 'Close list of speakers' %}</a>
{% endif %}
<p><a href="{% url 'projector_activate_slide' item.sid 'show_list_of_speakers' %}">Show list of speakers</a></p>
{% if old_speakers %}
<div id="show_old_speakers" style="display:none" class="toggle_old_speakers">
<a href="#">{% trans "Show old speakers" %}</a>
{% if last_speaker %}
<p>{% trans "Last speaker:" %} <a href="{% model_url last_speaker %}">{{ last_speaker }}</a></p>
{% endif %}
</div>
<div id="old_speakers" class="toggle_old_speakers">
<a href="#">{% trans "Hide old speakers" %}</a>
<ol>
{% for speaker in old_speakers %}
<li>
{{ speaker.time }}
<a href="{% model_url speaker %}">{{ speaker }}</a>
<a href="{% model_url speaker 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini">
<i class="icon-remove"></i>
</a>
</li>
{% endfor %}
</ol>
</div>
{% endif %}
{% 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 %}
<button type="button" class="close" data-dismiss="alert">×</button>
<p>{% trans "Do you want to save the changed order of speakers?" %}</p>
<input id="sort_order" name="sort_order" type="hidden"></hidden>
<p>
<button class="btn" type="submit">{% trans 'Yes' %}</button>
<a href="{% url 'item_view' item.pk %}" class="btn">{% trans 'No' %}</a>
</p>
</form>
{% endif %}
{% if speakers %}
<ol {% if perms.agenda.can_manage_agenda %}id="list_of_speakers"{% endif %}>
{% for speaker in speakers %}
<li id="speaker_{{ speaker.pk }}">
<a href="{% model_url speaker %}">{{ speaker }}</a>
<a href="{% url 'agenda_speaker_speak' item.pk speaker.person.person_id %}">speak</a>
<a href="{% model_url speaker 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini">
<i class="icon-remove"></i>
</a>
</li>
{% endfor %}
</ol>
{% else %}
<p>{% trans 'The list of speakers is empty' %}</p>
{% endif %}
{% if is_speaker %}
<a href="{% url 'agenda_speaker_delete' object.id %}">{% trans "Remove me vom speakers list." %}</a>
{% elif not object.speaker_list_closed %}
<a href="{% url 'agenda_speaker_append' object.id %}">{% trans "Put me on speakers list." %}</a>
{% endif %}
{% if perms.can_manage_agenda %}
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-primary" type="submit" title="{% trans 'Apply' %}"><i class="icon-ok icon-white"></i></button>
{% if perms.participant.can_see_participant and perms.participant.can_manage_participant %}
<a href="{% url 'user_new' %}" class="btn" title="{% trans 'Add new participant' %}"><i class="icon-add-user"></i></a>
{% endif %}
</form>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,21 @@
{% extends "base-projector.html" %}
{% load i18n %}
{% block title %}{{ block.super }} - {{ item }}{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
{% endblock %}
{% block scrollcontent %}
{% if speakers %}
<ol>
{% for speaker in speakers %}
<li>{{ speaker }}</li>
{% endfor %}
</ol>
{% else %}
{% trans 'The list of speakers is empty' %}
{% endif %}
{% endblock %}

View File

@ -12,8 +12,9 @@
from django.conf.urls import url, patterns
from openslides.agenda.views import (
Overview, View, SetClosed, ItemUpdate,
ItemCreate, ItemDelete, AgendaPDF)
Overview, AgendaItemView, SetClosed, ItemUpdate, SpeakerSpeakView,
ItemCreate, ItemDelete, AgendaPDF, SpeakerAppendView, SpeakerDeleteView,
SpeakerListOpenView, SpeakerChangeOrderView)
urlpatterns = patterns(
'',
@ -23,7 +24,7 @@ urlpatterns = patterns(
),
url(r'^(?P<pk>\d+)/$',
View.as_view(),
AgendaItemView.as_view(),
name='item_view',
),
@ -58,4 +59,40 @@ urlpatterns = patterns(
AgendaPDF.as_view(),
name='print_agenda',
),
# Speaker List
url(r'^(?P<pk>\d+)/speaker/$',
SpeakerAppendView.as_view(),
name='agenda_speaker_append',
),
url(r'^(?P<pk>\d+)/speaker/open/$',
SpeakerListOpenView.as_view(open_list=True),
name='agenda_speaker_open',
),
url(r'^(?P<pk>\d+)/speaker/close/$',
SpeakerListOpenView.as_view(),
name='agenda_speaker_close',
),
url(r'^(?P<pk>\d+)/speaker/del/$',
SpeakerDeleteView.as_view(),
name='agenda_speaker_delete',
),
url(r'^(?P<pk>\d+)/speaker/(?P<speaker>\d+)/del/$',
SpeakerDeleteView.as_view(),
name='agenda_speaker_delete',
),
url(r'^(?P<pk>\d+)/speaker/(?P<person_id>[^/]+)/speak/$',
SpeakerSpeakView.as_view(),
name='agenda_speaker_speak',
),
url(r'^(?P<pk>\d+)/speaker/change_order$',
SpeakerChangeOrderView.as_view(),
name='agenda_speaker_change_order',
),
)

View File

@ -9,6 +9,7 @@
:copyright: 2011, 2012 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
# TODO: Rename all views and template names
from reportlab.platypus import Paragraph
from datetime import datetime, timedelta
@ -22,15 +23,16 @@ from django.views.generic.detail import SingleObjectMixin
from openslides.config.api import config
from openslides.utils.pdf import stylesheet
from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.views import (
TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView,
DetailView, FormView)
DetailView, FormView, SingleObjectMixin)
from openslides.utils.template import Tab
from openslides.utils.utils import html_strong
from openslides.projector.api import get_active_slide
from openslides.projector.projector import Widget, SLIDE
from .models import Item
from .forms import ItemOrderForm, ItemForm
from .models import Item, Speaker
from .forms import ItemOrderForm, ItemForm, AppendSpeakerForm
class Overview(TemplateView):
@ -112,14 +114,43 @@ class Overview(TemplateView):
return self.render_to_response(context)
class View(DetailView):
class AgendaItemView(SingleObjectMixin, FormView):
"""
Show an agenda item.
"""
# TODO: use 'SingleObjectTemplateResponseMixin' to choose the right template name
permission_required = 'agenda.can_see_agenda'
template_name = 'agenda/view.html'
model = Item
context_object_name = 'item'
form_class = AppendSpeakerForm
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.exclude(time=None).order_by('time'))
try:
last_speaker = old_speakers[-1]
except IndexError:
last_speaker = None
kwargs.update({
'object': self.object,
'speakers': speakers,
'old_speakers': old_speakers,
'last_speaker': last_speaker,
'is_speaker': Speaker.objects.filter(
time=None, person=self.request.user, item=self.object).exists(),
})
return super(AgendaItemView, self).get_context_data(**kwargs)
def form_valid(self, form):
Speaker.objects.add(person=form.cleaned_data['speaker'], item=self.get_object())
return self.render_to_response(self.get_context_data(form=form))
def get_form_kwargs(self):
kwargs = super(AgendaItemView, self).get_form_kwargs()
kwargs['item'] = self.get_object()
return kwargs
class SetClosed(RedirectView, SingleObjectMixin):
@ -149,6 +180,9 @@ class SetClosed(RedirectView, SingleObjectMixin):
self.object.set_closed(closed)
return super(SetClosed, self).pre_redirect(request, *args, **kwargs)
def get_url_name_args(self):
return []
class ItemUpdate(UpdateView):
"""
@ -224,6 +258,159 @@ class AgendaPDF(PDFView):
story.append(Paragraph(item.get_title(), stylesheet['Item']))
class SpeakerAppendView(SingleObjectMixin, RedirectView):
"""
Set the request.user to the speaker list.
"""
permission_required = 'agenda.can_be_speaker'
url_name = 'item_view'
model = Item
def pre_redirect(self, request, *args, **kwargs):
self.object = self.get_object()
if self.object.speaker_list_closed:
messages.error(request, _('List of speakers is closed.'))
else:
try:
Speaker.objects.add(item=self.object, person=request.user)
except OpenSlidesError, e:
messages.error(request, e)
class SpeakerDeleteView(DeleteView):
"""
Delete the request.user or a specific user from the speaker list.
"""
success_url_name = 'item_view'
question_url_name = 'item_view'
def has_permission(self, request, *args, **kwargs):
"""
Check the permission to delete a speaker.
"""
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
return True
def get(self, *args, **kwargs):
try:
return super(SpeakerDeleteView, self).get(*args, **kwargs)
except Speaker.DoesNotExist:
messages.error(self.request, _('You are not on the list of speakers.'))
return super(RedirectView, self).get(*args, **kwargs)
def get_object(self):
"""
Returns the speaker object.
If 'speaker' is in kwargs, this speaker object is returnd. Else, a speaker
object with the request.user as speaker.
"""
try:
return Speaker.objects.get(pk=self.kwargs['speaker'])
except KeyError:
return Speaker.objects.filter(
item=self.kwargs['pk'], person=self.request.user).exclude(weight=None).get()
def get_url_name_args(self):
return [self.kwargs['pk']]
def get_question(self):
if 'speaker' in self.kwargs:
return super(SpeakerDeleteView, self).get_question()
else:
return _('Do you really want to remove yourself from the list of speakers?')
class SpeakerSpeakView(SingleObjectMixin, RedirectView):
"""
Mark a speaker, that he can speak.
"""
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(
person=kwargs['person_id'],
item=self.object.pk).exclude(
weight=None).get()
except Speaker.DoesNotExist:
messages.error(self.request, _('Person %s is not on the list of item %s.'
% (kwargs['person_id'], self.object)))
else:
speaker.speak()
def get_url_name_args(self):
return [self.object.pk]
class SpeakerListOpenView(SingleObjectMixin, RedirectView):
"""
View to open and close a list of speakers.
"""
permission_required = 'agenda.can_manage_agenda'
model = Item
open_list = False
url_name = 'item_view'
def pre_redirect(self, *args, **kwargs):
self.object = self.get_object()
self.object.speaker_list_closed = not self.open_list
self.object.save()
def get_url_name_args(self):
return [self.object.pk]
class SpeakerChangeOrderView(SingleObjectMixin, RedirectView):
"""
Change the order of the speakers.
Has to be called as post-request with the new order of the speaker ids.
"""
permission_required = 'agenda.can_manage_agenda'
model = Item
url_name = 'item_view'
def pre_redirect(self, args, **kwargs):
self.object = self.get_object()
@transaction.commit_manually
def pre_post_redirect(self, request, *args, **kwargs):
"""
Reorder the list of speaker.
Take the string 'sort_order' from the post-data, and use this order.
"""
self.object = self.get_object()
transaction.commit()
for (counter, speaker) in enumerate(self.request.POST['sort_order'].split(',')):
try:
speaker_pk = int(speaker.split('_')[1])
except IndexError:
transaction.rollback()
break
try:
speaker = Speaker.objects.filter(item=self.object).get(pk=speaker_pk)
except:
transaction.rollback()
break
speaker.weight = counter + 1
speaker.save()
else:
transaction.commit()
def get_url_name_args(self):
return [self.object.pk]
def register_tab(request):
"""
Registers the agenda tab.

View File

@ -115,8 +115,6 @@
{% endif %}
</div>
{% endfor %}
<p>
</p>
{% endif %}
</form>
{% endif %}

View File

@ -86,16 +86,16 @@ class User(PersonMixin, Person, SlideMixin, DjangoUser):
return self.last_name.lower()
@models.permalink
def get_absolute_url(self, link='view'):
def get_absolute_url(self, link='detail'):
"""
Return the URL to this user.
link can be:
* view
* detail
* edit
* delete
"""
if link == 'view':
if link == 'detail' or link == 'view':
return ('user_view', [str(self.id)])
if link == 'edit':
return ('user_edit', [str(self.id)])

View File

@ -77,8 +77,10 @@ def create_builtin_groups(sender, **kwargs):
perm_2 = Permission.objects.get(content_type=ct_projector, codename='can_see_dashboard')
ct_agenda = ContentType.objects.get(app_label='agenda', model='item')
ct_speaker = ContentType.objects.get(app_label='agenda', model='speaker')
perm_3 = Permission.objects.get(content_type=ct_agenda, codename='can_see_agenda')
perm_3a = Permission.objects.get(content_type=ct_agenda, codename='can_see_orga_items')
can_speak = Permission.objects.get(content_type=ct_speaker, codename='can_be_speaker')
ct_motion = ContentType.objects.get(app_label='motion', model='motion')
perm_4 = Permission.objects.get(content_type=ct_motion, codename='can_see_motion')
@ -95,7 +97,7 @@ def create_builtin_groups(sender, **kwargs):
group_anonymous = Group.objects.create(name=ugettext_noop('Anonymous'), pk=1)
group_anonymous.permissions.add(perm_1, perm_2, perm_3, perm_3a, perm_4, perm_5, perm_6, perm_6a)
group_registered = Group.objects.create(name=ugettext_noop('Registered'), pk=2)
group_registered.permissions.add(perm_1, perm_2, perm_3, perm_3a, perm_4, perm_5, perm_6, perm_6a)
group_registered.permissions.add(perm_1, perm_2, perm_3, perm_3a, perm_4, perm_5, perm_6, perm_6a, can_speak)
# Delegates
perm_7 = Permission.objects.get(content_type=ct_motion, codename='can_create_motion')

View File

@ -23,7 +23,7 @@ class Person(object):
"""
raise NotImplementedError('Any person object needs a person_id')
def __repr__(self):
def __unicode__(self):
"""
Return a string for this person.
"""

View File

@ -42,6 +42,8 @@ class PersonField(models.fields.Field):
"""
if value is None:
return None
elif isinstance(value, basestring):
return value
else:
return value.person_id

View File

@ -149,15 +149,26 @@ class QuestionMixin(object):
def get_redirect_url(self, **kwargs):
if self.request.method == 'GET':
return reverse(self.question_url_name, args=self.get_url_name_args())
return reverse(self.question_url_name, args=self.get_question_url_name_args())
else:
return reverse(self.success_url_name, args=self.get_url_name_args())
return reverse(self.success_url_name, args=self.get_success_url_name_args())
def get_question_url_name_args(self):
return self.get_url_name_args()
def get_success_url_name_args(self):
return self.get_url_name_args()
def get_url_name_args(self):
try:
return [self.object.pk]
except AttributeError:
return []
def pre_redirect(self, request, *args, **kwargs):
# Prints the question in a GET request
"""
Prints the question in a GET request.
"""
self.confirm_form()
def get_question(self):
@ -251,6 +262,9 @@ class RedirectView(PermissionMixin, AjaxMixin, _RedirectView):
return super(RedirectView, self).get_redirect_url(**kwargs)
def get_url_name_args(self):
try:
return [self.object.pk]
except AttributeError:
return []
@ -319,6 +333,9 @@ class DeleteView(SingleObjectMixin, QuestionMixin, RedirectView):
def get_success_message(self):
return _('%s was successfully deleted.') % html_strong(self.object)
def get_url_name_args(self):
return []
class DetailView(PermissionMixin, ExtraContextMixin, _DetailView):
def get(self, request, *args, **kwargs):

View File

@ -0,0 +1,175 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Unit test for the list of speakers
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.test.client import Client
from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.test import TestCase
from openslides.participant.models import User
from openslides.agenda.models import Item, Speaker
class ListOfSpeakerModelTests(TestCase):
def setUp(self):
self.item1 = Item.objects.create(title='item1')
self.item2 = Item.objects.create(title='item2')
self.speaker1 = User.objects.create(username='user1')
self.speaker2 = User.objects.create(username='user2')
def test_append_speaker(self):
# Append speaker1 to the list of item1
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
self.assertTrue(Speaker.objects.filter(person=self.speaker1, item=self.item1).exists())
# Append speaker1 to the list of item2
speaker1_item2 = Speaker.objects.add(self.speaker1, self.item2)
self.assertTrue(Speaker.objects.filter(person=self.speaker1, item=self.item2).exists())
# Append speaker2 to the list of item1
speaker2_item1 = Speaker.objects.add(self.speaker2, self.item1)
self.assertTrue(Speaker.objects.filter(person=self.speaker2, item=self.item1).exists())
# Try to append speaker 1 again to the list of item1
with self.assertRaises(OpenSlidesError):
Speaker.objects.add(self.speaker1, self.item1)
# Check time and weight
for object in (speaker1_item1, speaker2_item1, speaker1_item2):
self.assertIsNone(object.time)
self.assertEqual(speaker1_item1.weight, 1)
self.assertEqual(speaker1_item2.weight, 1)
self.assertEqual(speaker2_item1.weight, 2)
def test_open_close_list_of_speaker(self):
self.assertFalse(Item.objects.get(pk=self.item1.pk).speaker_list_closed)
self.item1.speaker_list_closed = True
self.item1.save()
self.assertTrue(Item.objects.get(pk=self.item1.pk).speaker_list_closed)
def test_speak(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(Speaker.objects.get(pk=speaker1_item1.pk).weight)
class SpeakerViewTestCase(TestCase):
def setUp(self):
# Admin
self.admin = User.objects.create_superuser('admin', 'admin@admin.admin', 'admin')
self.admin_client = Client()
self.admin_client.login(username='admin', password='admin')
# Speaker1
self.speaker1 = User.objects.create_user('speaker1', 'speaker1@user.user', 'speaker')
self.speaker1_client = Client()
self.speaker1_client.login(username='speaker1', password='speaker')
# Speaker2
self.speaker2 = User.objects.create_user('speaker2', 'speaker2@user.user', 'speaker')
self.speaker2_client = Client()
self.speaker2_client.login(username='speaker2', password='speaker')
# Items
self.item1 = Item.objects.create(title='item1')
self.item2 = Item.objects.create(title='item2')
def check_url(self, url, test_client, response_cose):
response = test_client.get(url)
self.assertEqual(response.status_code, response_cose)
return response
def assertMessage(self, response, message):
self.assertTrue(message in response.cookies['messages'].value,
'"%s" is not a message of the response. (But: %s)'
% (message, response.cookies['messages'].value))
class TestSpeakerAppendView(SpeakerViewTestCase):
def test_get(self):
self.assertFalse(Speaker.objects.filter(person=self.speaker1, item=self.item1).exists())
self.assertEqual(Speaker.objects.filter(item=self.item1).count(), 0)
# Set speaker1 to item1
self.check_url('/agenda/1/speaker/', self.speaker1_client, 302)
self.assertTrue(Speaker.objects.filter(person=self.speaker1, item=self.item1).exists())
self.assertEqual(Speaker.objects.filter(item=self.item1).count(), 1)
# Try to set speaker 1 to item 1 again
response = self.check_url('/agenda/1/speaker/', self.speaker1_client, 302)
self.assertEqual(Speaker.objects.filter(item=self.item1).count(), 1)
self.assertMessage(response, 'speaker1 is allready on the list of speakers from item 1')
def test_closed_list(self):
self.item1.speaker_list_closed = True
self.item1.save()
response = self.check_url('/agenda/1/speaker/', self.speaker1_client, 302)
self.assertEqual(Speaker.objects.filter(item=self.item1).count(), 0)
self.assertMessage(response, 'List of speakers is closed.')
class TestAgendaItemView(SpeakerViewTestCase):
def test_post(self):
# Set speaker1 to item1
response = self.admin_client.post(
'/agenda/1/', {'speaker': self.speaker1.person_id})
self.assertTrue(Speaker.objects.filter(person=self.speaker1, item=self.item1).exists())
# Try it again
response = self.admin_client.post(
'/agenda/1/', {'speaker': self.speaker1.person_id})
self.assertFormError(response, 'form', 'speaker', 'speaker1 is allready on the list of speakers.')
class TestSpeakerDeleteView(SpeakerViewTestCase):
def test_get(self):
self.check_url('/agenda/1/speaker/del/', self.speaker1_client, 302)
def test_post_as_admin(self):
speaker = Speaker.objects.add(self.speaker1, self.item1)
response = self.admin_client.post(
'/agenda/1/speaker/%d/del/' % speaker.pk, {'yes': 'yes'})
self.assertEqual(response.status_code, 302)
self.assertFalse(Speaker.objects.filter(person=self.speaker1, item=self.item1).exists())
def test_post_as_user(self):
speaker = Speaker.objects.add(self.speaker1, self.item1)
response = self.speaker1_client.post(
'/agenda/1/speaker/del/', {'yes': 'yes'})
self.assertEqual(response.status_code, 302)
self.assertFalse(Speaker.objects.filter(person=self.speaker1, item=self.item1).exists())
class TestSpeakerSpeakView(SpeakerViewTestCase):
def test_get(self):
url = '/agenda/1/speaker/%s/speak/' % self.speaker1.person_id
response = self.check_url(url, self.admin_client, 302)
self.assertMessage(response, 'Person user:2 is not on the list of item item1.')
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.assertIsNone(speaker.weight)
class SpeakerListOpenView(SpeakerViewTestCase):
def test_get(self):
response = self.check_url('/agenda/1/speaker/close/', self.admin_client, 302)
item = Item.objects.get(pk=self.item1.pk)
self.assertTrue(item.speaker_list_closed)
response = self.check_url('/agenda/1/speaker/open/', self.admin_client, 302)
item = Item.objects.get(pk=self.item1.pk)
self.assertFalse(item.speaker_list_closed)