2011-07-31 10:46:29 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
2013-03-18 12:34:47 +01:00
|
|
|
# TODO: Rename all views and template names
|
2013-02-16 10:41:22 +01:00
|
|
|
|
2015-01-21 12:58:46 +01:00
|
|
|
from cgi import escape
|
2013-02-18 18:56:37 +01:00
|
|
|
from datetime import datetime, timedelta
|
2014-04-06 15:46:49 +02:00
|
|
|
from json import dumps
|
2012-02-21 13:17:42 +01:00
|
|
|
|
2011-07-31 10:46:29 +02:00
|
|
|
from django.contrib import messages
|
2014-04-06 15:46:49 +02:00
|
|
|
from django.contrib.contenttypes.models import ContentType
|
|
|
|
from django.contrib.staticfiles.templatetags.staticfiles import static
|
2013-09-25 10:01:01 +02:00
|
|
|
from django.core.urlresolvers import reverse
|
2012-07-04 12:50:33 +02:00
|
|
|
from django.db import transaction
|
|
|
|
from django.db.models import Model
|
2014-04-06 15:46:49 +02:00
|
|
|
from django.template.loader import render_to_string
|
|
|
|
from django.utils.datastructures import SortedDict
|
|
|
|
from django.utils.safestring import mark_safe
|
2013-09-25 10:01:01 +02:00
|
|
|
from django.utils.translation import ugettext as _
|
|
|
|
from django.utils.translation import ugettext_lazy
|
|
|
|
from reportlab.platypus import Paragraph
|
2012-02-20 17:46:45 +01:00
|
|
|
|
2013-03-01 17:13:12 +01:00
|
|
|
from openslides.config.api import config
|
2014-04-06 15:46:49 +02:00
|
|
|
from openslides.projector.api import (
|
|
|
|
get_active_object,
|
|
|
|
get_active_slide,
|
|
|
|
get_projector_overlays_js,
|
|
|
|
get_overlays,
|
|
|
|
update_projector)
|
2013-03-18 12:34:47 +01:00
|
|
|
from openslides.utils.exceptions import OpenSlidesError
|
2013-09-25 10:01:01 +02:00
|
|
|
from openslides.utils.pdf import stylesheet
|
2012-07-04 12:50:33 +02:00
|
|
|
from openslides.utils.utils import html_strong
|
2014-03-27 20:30:15 +01:00
|
|
|
from openslides.utils.views import (
|
2014-04-06 15:46:49 +02:00
|
|
|
AjaxMixin,
|
2014-03-27 20:30:15 +01:00
|
|
|
CreateView,
|
|
|
|
CSVImportView,
|
|
|
|
DeleteView,
|
|
|
|
FormView,
|
|
|
|
PDFView,
|
2014-04-27 21:01:23 +02:00
|
|
|
QuestionView,
|
2014-03-27 20:30:15 +01:00
|
|
|
RedirectView,
|
|
|
|
SingleObjectMixin,
|
|
|
|
TemplateView,
|
|
|
|
UpdateView)
|
|
|
|
|
|
|
|
from .csv_import import import_agenda_items
|
2013-09-25 10:01:01 +02:00
|
|
|
from .forms import AppendSpeakerForm, ItemForm, ItemOrderForm, RelatedItemForm
|
2013-03-18 12:34:47 +01:00
|
|
|
from .models import Item, Speaker
|
2012-02-20 17:46:45 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Overview(TemplateView):
|
2012-07-04 12:50:33 +02:00
|
|
|
"""
|
2013-04-29 20:03:50 +02:00
|
|
|
Show all agenda items, and update their range via post.
|
2012-07-04 12:50:33 +02:00
|
|
|
"""
|
2014-05-15 20:07:09 +02:00
|
|
|
required_permission = 'agenda.can_see_agenda'
|
2012-02-20 17:46:45 +01:00
|
|
|
template_name = 'agenda/overview.html'
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
2012-03-18 17:11:58 +01:00
|
|
|
context = super(Overview, self).get_context_data(**kwargs)
|
2013-01-05 23:52:29 +01:00
|
|
|
|
|
|
|
if self.request.user.has_perm('agenda.can_see_orga_items'):
|
|
|
|
items = Item.objects.all()
|
|
|
|
else:
|
2013-02-01 15:13:40 +01:00
|
|
|
items = Item.objects.filter(type__exact=Item.AGENDA_ITEM)
|
2013-01-05 23:52:29 +01:00
|
|
|
|
2013-07-29 11:42:53 +02:00
|
|
|
# Save the items as a list (not a queryset). This is important,
|
|
|
|
# because in other case, django-mtpp reloads the items in the
|
|
|
|
# template. But we add some attributes (in this function), which are
|
|
|
|
# not in the database and would be lost if the items were reloaded.
|
|
|
|
# TODO: Try to remove this line in later versions of django-mptt
|
|
|
|
items = list(items)
|
|
|
|
|
2013-02-16 10:41:22 +01:00
|
|
|
start = config['agenda_start_event_date_time']
|
2013-02-05 16:36:57 +01:00
|
|
|
if start is None or len(start) == 0:
|
|
|
|
start = None
|
|
|
|
else:
|
|
|
|
start = datetime.strptime(start, '%d.%m.%Y %H:%M')
|
|
|
|
|
2013-01-29 14:28:42 +01:00
|
|
|
duration = timedelta()
|
2013-01-31 20:32:29 +01:00
|
|
|
|
|
|
|
for item in items:
|
2013-11-19 13:48:55 +01:00
|
|
|
if item.duration is not None and len(item.duration) > 0:
|
|
|
|
if ':' in item.duration:
|
|
|
|
duration_list = item.duration.split(':')
|
|
|
|
duration += timedelta(hours=int(duration_list[0]),
|
|
|
|
minutes=int(duration_list[1]))
|
|
|
|
else:
|
|
|
|
hours = int(item.duration) / 60
|
|
|
|
minutes = int(item.duration) - (60 * hours)
|
|
|
|
duration += timedelta(hours=hours,
|
|
|
|
minutes=minutes)
|
|
|
|
if minutes < 10:
|
|
|
|
minutes = "%s%s" % (0, minutes)
|
|
|
|
item.duration = "%s:%s" % (hours, minutes)
|
2014-03-27 20:38:13 +01:00
|
|
|
if start is not None:
|
2013-02-05 16:36:57 +01:00
|
|
|
item.tooltip = start + duration
|
2013-01-29 14:28:42 +01:00
|
|
|
|
2013-01-30 17:39:53 +01:00
|
|
|
if start is None:
|
2013-02-05 16:36:57 +01:00
|
|
|
end = None
|
|
|
|
else:
|
2013-02-16 10:41:22 +01:00
|
|
|
end = start + duration
|
2013-01-30 17:39:53 +01:00
|
|
|
|
2013-02-18 18:56:37 +01:00
|
|
|
duration = u'%d:%02d' % (
|
|
|
|
(duration.days * 24 + duration.seconds / 3600.0),
|
|
|
|
(duration.seconds / 60.0 % 60))
|
2013-01-30 17:39:53 +01:00
|
|
|
|
2013-08-04 12:59:11 +02:00
|
|
|
active_slide = get_active_slide()
|
|
|
|
if active_slide['callback'] == 'agenda':
|
2013-09-07 10:14:54 +02:00
|
|
|
agenda_is_active = active_slide.get('pk', 'agenda') == 'agenda'
|
2013-08-04 12:59:11 +02:00
|
|
|
active_type = active_slide.get('type', 'text')
|
|
|
|
else:
|
|
|
|
agenda_is_active = None
|
|
|
|
active_type = None
|
|
|
|
|
2012-02-20 17:46:45 +01:00
|
|
|
context.update({
|
2013-01-05 23:52:29 +01:00
|
|
|
'items': items,
|
2013-08-04 12:59:11 +02:00
|
|
|
'agenda_is_active': agenda_is_active,
|
2013-01-30 17:39:53 +01:00
|
|
|
'duration': duration,
|
|
|
|
'start': start,
|
2013-05-06 20:17:13 +02:00
|
|
|
'end': end,
|
2013-08-04 12:59:11 +02:00
|
|
|
'active_type': active_type})
|
2012-02-20 17:46:45 +01:00
|
|
|
return context
|
|
|
|
|
2012-07-04 12:50:33 +02:00
|
|
|
@transaction.commit_manually
|
2012-02-20 17:46:45 +01:00
|
|
|
def post(self, request, *args, **kwargs):
|
2012-07-04 12:50:33 +02:00
|
|
|
if not request.user.has_perm('agenda.can_manage_agenda'):
|
2012-11-24 14:01:21 +01:00
|
|
|
messages.error(
|
|
|
|
request,
|
2012-08-05 22:31:20 +02:00
|
|
|
_('You are not authorized to manage the agenda.'))
|
2013-09-25 10:01:01 +02:00
|
|
|
context = self.get_context_data(**kwargs)
|
2012-07-04 12:50:33 +02:00
|
|
|
return self.render_to_response(context)
|
2013-08-04 12:59:11 +02:00
|
|
|
|
2012-07-04 12:50:33 +02:00
|
|
|
transaction.commit()
|
2011-07-31 10:46:29 +02:00
|
|
|
for item in Item.objects.all():
|
2012-02-06 22:22:16 +01:00
|
|
|
form = ItemOrderForm(request.POST, prefix="i%d" % item.id)
|
2011-07-31 10:46:29 +02:00
|
|
|
if form.is_valid():
|
|
|
|
try:
|
2012-04-18 14:36:03 +02:00
|
|
|
parent = Item.objects.get(id=form.cleaned_data['parent'])
|
2011-07-31 10:46:29 +02:00
|
|
|
except Item.DoesNotExist:
|
2012-04-18 14:36:03 +02:00
|
|
|
parent = None
|
2014-04-28 01:03:10 +02:00
|
|
|
else:
|
|
|
|
if item.type == item.AGENDA_ITEM and parent.type == item.ORGANIZATIONAL_ITEM:
|
|
|
|
transaction.rollback()
|
|
|
|
messages.error(
|
2014-05-12 21:09:09 +02:00
|
|
|
request, _('Agenda items can not be child elements of an organizational item.'))
|
2014-04-28 01:03:10 +02:00
|
|
|
break
|
2012-04-18 14:36:03 +02:00
|
|
|
item.parent = parent
|
2014-04-28 01:03:10 +02:00
|
|
|
item.weight = form.cleaned_data['weight']
|
2012-04-18 14:36:03 +02:00
|
|
|
Model.save(item)
|
2012-07-04 12:50:33 +02:00
|
|
|
else:
|
|
|
|
transaction.rollback()
|
2012-11-24 14:01:21 +01:00
|
|
|
messages.error(
|
|
|
|
request, _('Errors when reordering of the agenda'))
|
2013-09-08 10:38:50 +02:00
|
|
|
break
|
|
|
|
else:
|
|
|
|
Item.objects.rebuild()
|
2013-11-19 13:48:55 +01:00
|
|
|
# TODO: assure, that it is a valid tree
|
2013-09-08 10:38:50 +02:00
|
|
|
context = self.get_context_data(**kwargs)
|
2012-07-04 12:50:33 +02:00
|
|
|
transaction.commit()
|
2013-08-04 12:59:11 +02:00
|
|
|
|
|
|
|
if get_active_slide()['callback'] == 'agenda':
|
|
|
|
update_projector()
|
|
|
|
context = self.get_context_data(**kwargs)
|
|
|
|
transaction.commit()
|
2012-02-20 17:46:45 +01:00
|
|
|
return self.render_to_response(context)
|
2011-07-31 10:46:29 +02:00
|
|
|
|
|
|
|
|
2013-03-18 12:34:47 +01:00
|
|
|
class AgendaItemView(SingleObjectMixin, FormView):
|
2012-07-04 12:50:33 +02:00
|
|
|
"""
|
|
|
|
Show an agenda item.
|
|
|
|
"""
|
2013-03-18 12:34:47 +01:00
|
|
|
# TODO: use 'SingleObjectTemplateResponseMixin' to choose the right template name
|
2012-04-15 09:55:21 +02:00
|
|
|
template_name = 'agenda/view.html'
|
|
|
|
model = Item
|
|
|
|
context_object_name = 'item'
|
2013-03-18 12:34:47 +01:00
|
|
|
form_class = AppendSpeakerForm
|
|
|
|
|
2014-05-15 20:07:09 +02:00
|
|
|
def check_permission(self, request, *args, **kwargs):
|
2013-10-19 22:16:43 +02:00
|
|
|
"""
|
|
|
|
Checks if the user has the required permission.
|
|
|
|
"""
|
|
|
|
if self.get_object().type == Item.ORGANIZATIONAL_ITEM:
|
2014-05-15 20:07:09 +02:00
|
|
|
check_permission = request.user.has_perm('agenda.can_see_orga_items')
|
2013-10-19 22:16:43 +02:00
|
|
|
else:
|
2014-05-15 20:07:09 +02:00
|
|
|
check_permission = request.user.has_perm('agenda.can_see_agenda')
|
|
|
|
return check_permission
|
2013-10-19 22:16:43 +02:00
|
|
|
|
2013-03-18 12:34:47 +01:00
|
|
|
def get_context_data(self, **kwargs):
|
2014-12-22 18:09:05 +01:00
|
|
|
list_of_speakers = self.get_object().get_list_of_speakers()
|
2013-08-04 12:59:11 +02:00
|
|
|
active_slide = get_active_slide()
|
|
|
|
active_type = active_slide.get('type', None)
|
2013-03-18 12:34:47 +01:00
|
|
|
kwargs.update({
|
2014-12-22 18:09:05 +01:00
|
|
|
'object': self.get_object(),
|
2013-04-29 20:03:50 +02:00
|
|
|
'list_of_speakers': list_of_speakers,
|
2013-08-04 12:59:11 +02:00
|
|
|
'is_on_the_list_of_speakers': Speaker.objects.filter(
|
2014-12-22 18:09:05 +01:00
|
|
|
item=self.get_object(), begin_time=None, person=self.request.user).exists(),
|
2013-08-04 12:59:11 +02:00
|
|
|
'active_type': active_type,
|
2013-03-18 12:34:47 +01:00
|
|
|
})
|
|
|
|
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
|
2012-04-15 09:55:21 +02:00
|
|
|
|
|
|
|
|
2014-12-22 18:09:05 +01:00
|
|
|
class SetClosed(SingleObjectMixin, RedirectView):
|
2011-07-31 10:46:29 +02:00
|
|
|
"""
|
2012-07-04 12:50:33 +02:00
|
|
|
Close or open an item.
|
2011-07-31 10:46:29 +02:00
|
|
|
"""
|
2014-05-15 20:07:09 +02:00
|
|
|
required_permission = 'agenda.can_manage_agenda'
|
2012-02-20 17:46:45 +01:00
|
|
|
allow_ajax = True
|
2013-02-03 19:43:36 +01:00
|
|
|
url_name = 'item_overview'
|
2012-02-20 19:39:26 +01:00
|
|
|
model = Item
|
2011-09-02 20:46:24 +02:00
|
|
|
|
2012-02-20 17:46:45 +01:00
|
|
|
def get_ajax_context(self, **kwargs):
|
2013-09-25 12:53:44 +02:00
|
|
|
closed = self.kwargs['closed']
|
2011-08-31 23:47:31 +02:00
|
|
|
if closed:
|
2014-12-22 18:09:05 +01:00
|
|
|
link = reverse('item_open', args=[self.get_object().pk])
|
2011-08-31 23:47:31 +02:00
|
|
|
else:
|
2014-12-22 18:09:05 +01:00
|
|
|
link = reverse('item_close', args=[self.get_object().pk])
|
2013-09-25 12:53:44 +02:00
|
|
|
return super(SetClosed, self).get_ajax_context(closed=closed, link=link)
|
2012-02-20 17:46:45 +01:00
|
|
|
|
|
|
|
def pre_redirect(self, request, *args, **kwargs):
|
|
|
|
closed = kwargs['closed']
|
2014-12-22 18:09:05 +01:00
|
|
|
self.get_object().set_closed(closed)
|
2012-02-20 17:46:45 +01:00
|
|
|
return super(SetClosed, self).pre_redirect(request, *args, **kwargs)
|
|
|
|
|
2013-03-18 12:34:47 +01:00
|
|
|
def get_url_name_args(self):
|
|
|
|
return []
|
|
|
|
|
2011-07-31 10:46:29 +02:00
|
|
|
|
2012-02-20 17:46:45 +01:00
|
|
|
class ItemUpdate(UpdateView):
|
2012-07-04 12:50:33 +02:00
|
|
|
"""
|
|
|
|
Update an existing item.
|
|
|
|
"""
|
2014-05-15 20:07:09 +02:00
|
|
|
required_permission = 'agenda.can_manage_agenda'
|
2012-02-20 17:46:45 +01:00
|
|
|
template_name = 'agenda/edit.html'
|
|
|
|
model = Item
|
|
|
|
context_object_name = 'item'
|
2013-02-03 19:43:36 +01:00
|
|
|
success_url_name = 'item_overview'
|
2013-09-25 12:53:44 +02:00
|
|
|
url_name_args = []
|
2011-07-31 10:46:29 +02:00
|
|
|
|
2013-06-14 09:00:37 +02:00
|
|
|
def get_form_class(self):
|
2014-12-22 18:09:05 +01:00
|
|
|
if self.get_object().content_object:
|
2013-09-07 00:18:13 +02:00
|
|
|
form = RelatedItemForm
|
2013-06-14 09:00:37 +02:00
|
|
|
else:
|
2013-09-07 00:18:13 +02:00
|
|
|
form = ItemForm
|
|
|
|
return form
|
2013-06-14 09:00:37 +02:00
|
|
|
|
2012-02-20 17:46:45 +01:00
|
|
|
|
|
|
|
class ItemCreate(CreateView):
|
2012-07-04 12:50:33 +02:00
|
|
|
"""
|
|
|
|
Create a new item.
|
|
|
|
"""
|
2014-05-15 20:07:09 +02:00
|
|
|
required_permission = 'agenda.can_manage_agenda'
|
2012-02-20 17:46:45 +01:00
|
|
|
template_name = 'agenda/edit.html'
|
|
|
|
model = Item
|
|
|
|
context_object_name = 'item'
|
|
|
|
form_class = ItemForm
|
2013-02-03 19:43:36 +01:00
|
|
|
success_url_name = 'item_overview'
|
2013-09-25 12:53:44 +02:00
|
|
|
url_name_args = []
|
2012-02-20 17:46:45 +01:00
|
|
|
|
|
|
|
|
|
|
|
class ItemDelete(DeleteView):
|
2011-07-31 10:46:29 +02:00
|
|
|
"""
|
2012-07-04 12:50:33 +02:00
|
|
|
Delete an item.
|
2011-07-31 10:46:29 +02:00
|
|
|
"""
|
2014-05-15 20:07:09 +02:00
|
|
|
required_permission = 'agenda.can_manage_agenda'
|
2012-02-20 17:46:45 +01:00
|
|
|
model = Item
|
2013-02-03 19:43:36 +01:00
|
|
|
question_url_name = 'item_overview'
|
2013-02-01 17:35:27 +01:00
|
|
|
success_url_name = 'item_overview'
|
2013-10-17 01:28:29 +02:00
|
|
|
url_name_args = []
|
2012-02-20 17:46:45 +01:00
|
|
|
|
2012-08-10 21:00:13 +02:00
|
|
|
def get_answer_options(self):
|
2013-10-17 08:29:54 +02:00
|
|
|
"""
|
|
|
|
Returns the possible answers to the delete view.
|
|
|
|
|
|
|
|
'all' is a possible answer, when the item has children.
|
|
|
|
"""
|
|
|
|
# Cache the result in the request, so when the children are deleted, the
|
|
|
|
# result does not change
|
|
|
|
try:
|
|
|
|
options = self.item_delete_answer_options
|
|
|
|
except AttributeError:
|
2014-12-22 18:09:05 +01:00
|
|
|
if self.get_object().children.exists():
|
2013-10-17 08:29:54 +02:00
|
|
|
options = [('all', _("Yes, with all child items."))] + self.answer_options
|
|
|
|
else:
|
|
|
|
options = self.answer_options
|
|
|
|
self.item_delete_answer_options = options
|
|
|
|
return options
|
2012-02-09 02:29:38 +01:00
|
|
|
|
2013-10-24 18:48:16 +02:00
|
|
|
def on_clicked_yes(self):
|
|
|
|
"""
|
|
|
|
Deletes the item but not its children.
|
|
|
|
"""
|
2014-12-22 18:09:05 +01:00
|
|
|
self.get_object().delete(with_children=False)
|
2013-10-24 18:48:16 +02:00
|
|
|
|
|
|
|
def on_clicked_all(self):
|
|
|
|
"""
|
|
|
|
Deletes the item and its children.
|
|
|
|
"""
|
2014-12-22 18:09:05 +01:00
|
|
|
self.get_object().delete(with_children=True)
|
2013-10-24 18:48:16 +02:00
|
|
|
|
|
|
|
def get_final_message(self):
|
|
|
|
"""
|
|
|
|
Prints the success message to the user.
|
|
|
|
"""
|
|
|
|
# OpenSlidesError (invalid answer) should never be raised here because
|
|
|
|
# this method should only be called if the answer is 'yes' or 'all'.
|
|
|
|
if self.get_answer() == 'yes':
|
2014-12-22 18:09:05 +01:00
|
|
|
message = _('Item %s was successfully deleted.') % html_strong(self.get_object())
|
2013-10-24 18:48:16 +02:00
|
|
|
else:
|
2014-12-22 18:09:05 +01:00
|
|
|
message = _('Item %s and its children were successfully deleted.') % html_strong(self.get_object())
|
2013-10-24 18:48:16 +02:00
|
|
|
return message
|
2013-09-07 00:18:13 +02:00
|
|
|
|
|
|
|
|
|
|
|
class CreateRelatedAgendaItemView(SingleObjectMixin, RedirectView):
|
|
|
|
"""
|
|
|
|
View to create and agenda item for a related object.
|
|
|
|
|
|
|
|
This view is only for subclassing in views of related apps. You
|
|
|
|
have to define 'model = ....'
|
|
|
|
"""
|
2014-05-15 20:07:09 +02:00
|
|
|
required_permission = 'agenda.can_manage_agenda'
|
2013-09-07 00:18:13 +02:00
|
|
|
url_name = 'item_overview'
|
|
|
|
url_name_args = []
|
|
|
|
|
|
|
|
def pre_redirect(self, request, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Create the agenda item.
|
|
|
|
"""
|
2014-12-22 18:09:05 +01:00
|
|
|
self.item = Item.objects.create(content_object=self.get_object())
|
2012-02-20 17:46:45 +01:00
|
|
|
|
2012-02-21 13:17:42 +01:00
|
|
|
|
2014-05-03 19:38:12 +02:00
|
|
|
class AgendaNumberingView(QuestionView):
|
2014-05-15 20:07:09 +02:00
|
|
|
required_permission = 'agenda.can_manage_agenda'
|
2014-04-27 21:01:23 +02:00
|
|
|
question_url_name = 'item_overview'
|
|
|
|
url_name = 'item_overview'
|
2014-05-03 19:38:12 +02:00
|
|
|
question_message = ugettext_lazy('Do you really want to generate agenda numbering? Manually added item numbers will be overwritten!')
|
2014-04-27 21:01:23 +02:00
|
|
|
url_name_args = []
|
|
|
|
|
|
|
|
def on_clicked_yes(self):
|
2014-04-28 01:03:10 +02:00
|
|
|
for item in Item.objects.all():
|
2014-04-27 21:01:23 +02:00
|
|
|
item.item_number = item.calc_item_no()
|
|
|
|
item.save()
|
|
|
|
|
|
|
|
def get_final_message(self):
|
2014-05-03 19:38:12 +02:00
|
|
|
return ugettext_lazy('The agenda has been numbered.')
|
2014-04-27 21:01:23 +02:00
|
|
|
|
|
|
|
|
2012-04-14 10:54:22 +02:00
|
|
|
class AgendaPDF(PDFView):
|
2012-07-04 12:50:33 +02:00
|
|
|
"""
|
|
|
|
Create a full agenda-PDF.
|
|
|
|
"""
|
2014-05-15 20:07:09 +02:00
|
|
|
required_permission = 'agenda.can_see_agenda'
|
2012-07-23 12:57:47 +02:00
|
|
|
filename = ugettext_lazy('Agenda')
|
|
|
|
document_title = ugettext_lazy('Agenda')
|
2012-02-21 13:17:42 +01:00
|
|
|
|
|
|
|
def append_to_pdf(self, story):
|
2013-02-01 15:13:40 +01:00
|
|
|
for item in Item.objects.filter(type__exact=Item.AGENDA_ITEM):
|
2012-02-21 13:17:42 +01:00
|
|
|
ancestors = item.get_ancestors()
|
|
|
|
if ancestors:
|
2012-07-04 12:50:33 +02:00
|
|
|
space = " " * 6 * ancestors.count()
|
2012-11-24 14:01:21 +01:00
|
|
|
story.append(Paragraph(
|
2015-01-21 12:58:46 +01:00
|
|
|
"%s%s" % (space, escape(item.get_title())),
|
2012-07-04 12:50:33 +02:00
|
|
|
stylesheet['Subitem']))
|
2012-02-21 13:17:42 +01:00
|
|
|
else:
|
2015-01-21 12:58:46 +01:00
|
|
|
story.append(Paragraph(escape(item.get_title()), stylesheet['Item']))
|
2012-03-16 14:31:59 +01:00
|
|
|
|
2013-02-16 10:41:22 +01:00
|
|
|
|
2013-03-18 12:34:47 +01:00
|
|
|
class SpeakerAppendView(SingleObjectMixin, RedirectView):
|
|
|
|
"""
|
|
|
|
Set the request.user to the speaker list.
|
|
|
|
"""
|
2014-05-15 20:07:09 +02:00
|
|
|
required_permission = 'agenda.can_be_speaker'
|
2013-03-18 12:34:47 +01:00
|
|
|
url_name = 'item_view'
|
|
|
|
model = Item
|
|
|
|
|
|
|
|
def pre_redirect(self, request, *args, **kwargs):
|
2014-12-22 18:09:05 +01:00
|
|
|
if self.get_object().speaker_list_closed:
|
2013-04-22 19:59:05 +02:00
|
|
|
messages.error(request, _('The list of speakers is closed.'))
|
2013-03-18 12:34:47 +01:00
|
|
|
else:
|
|
|
|
try:
|
2014-12-22 18:09:05 +01:00
|
|
|
Speaker.objects.add(item=self.get_object(), person=request.user)
|
2013-03-18 12:34:47 +01:00
|
|
|
except OpenSlidesError, e:
|
|
|
|
messages.error(request, e)
|
2013-10-22 19:12:42 +02:00
|
|
|
else:
|
|
|
|
messages.success(request, _('You were successfully added to the list of speakers.'))
|
2013-03-18 12:34:47 +01:00
|
|
|
|
|
|
|
|
|
|
|
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'
|
|
|
|
|
2014-05-15 20:07:09 +02:00
|
|
|
def check_permission(self, request, *args, **kwargs):
|
2013-03-18 12:34:47 +01:00
|
|
|
"""
|
|
|
|
Check the permission to delete a speaker.
|
|
|
|
"""
|
|
|
|
if 'speaker' in kwargs:
|
|
|
|
return request.user.has_perm('agenda.can_manage_agenda')
|
|
|
|
else:
|
2013-04-29 20:03:50 +02:00
|
|
|
# Any person who is on the list of speakers can delete himself from the list.
|
2013-03-18 12:34:47 +01:00
|
|
|
return True
|
|
|
|
|
|
|
|
def get(self, *args, **kwargs):
|
2014-12-22 18:09:05 +01:00
|
|
|
if self.get_object() is None:
|
2013-03-18 12:34:47 +01:00
|
|
|
return super(RedirectView, self).get(*args, **kwargs)
|
2014-12-22 18:09:05 +01:00
|
|
|
else:
|
|
|
|
return super(SpeakerDeleteView, self).get(*args, **kwargs)
|
2013-03-18 12:34:47 +01:00
|
|
|
|
|
|
|
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:
|
2014-12-22 18:09:05 +01:00
|
|
|
speaker = self._object
|
|
|
|
except AttributeError:
|
|
|
|
speaker_pk = self.kwargs.get('speaker')
|
|
|
|
if speaker_pk is not None:
|
|
|
|
queryset = Speaker.objects.filter(pk=speaker_pk)
|
|
|
|
else:
|
|
|
|
queryset = Speaker.objects.filter(
|
|
|
|
item=self.kwargs['pk'], person=self.request.user).exclude(weight=None)
|
|
|
|
try:
|
|
|
|
speaker = queryset.get()
|
|
|
|
except Speaker.DoesNotExist:
|
|
|
|
speaker = None
|
|
|
|
if speaker_pk is not None:
|
|
|
|
messages.error(self.request, _('You are not on the list of speakers.'))
|
|
|
|
self._object = speaker
|
|
|
|
return speaker
|
2013-03-18 12:34:47 +01:00
|
|
|
|
|
|
|
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):
|
|
|
|
"""
|
2013-03-18 12:34:47 +01:00
|
|
|
Mark the speaking person.
|
2013-03-18 12:34:47 +01:00
|
|
|
"""
|
2014-05-15 20:07:09 +02:00
|
|
|
required_permission = 'agenda.can_manage_agenda'
|
2013-03-18 12:34:47 +01:00
|
|
|
url_name = 'item_view'
|
|
|
|
model = Item
|
|
|
|
|
|
|
|
def pre_redirect(self, *args, **kwargs):
|
|
|
|
try:
|
|
|
|
speaker = Speaker.objects.filter(
|
|
|
|
person=kwargs['person_id'],
|
2014-12-22 18:09:05 +01:00
|
|
|
item=self.get_object(),
|
2013-04-25 16:18:16 +02:00
|
|
|
begin_time=None).get()
|
|
|
|
except Speaker.DoesNotExist: # TODO: Check the MultipleObjectsReturned error here?
|
2013-04-22 19:59:05 +02:00
|
|
|
messages.error(
|
|
|
|
self.request,
|
|
|
|
_('%(person)s is not on the list of %(item)s.')
|
2014-12-22 18:09:05 +01:00
|
|
|
% {'person': kwargs['person_id'], 'item': self.get_object()})
|
2013-03-18 12:34:47 +01:00
|
|
|
else:
|
2013-04-25 16:18:16 +02:00
|
|
|
speaker.begin_speach()
|
|
|
|
|
|
|
|
def get_url_name_args(self):
|
2014-12-22 18:09:05 +01:00
|
|
|
return [self.get_object().pk]
|
2013-04-25 16:18:16 +02:00
|
|
|
|
|
|
|
|
|
|
|
class SpeakerEndSpeachView(SingleObjectMixin, RedirectView):
|
|
|
|
"""
|
|
|
|
The speach of the actual speaker is finished.
|
|
|
|
"""
|
2014-05-15 20:07:09 +02:00
|
|
|
required_permission = 'agenda.can_manage_agenda'
|
2013-04-25 16:18:16 +02:00
|
|
|
url_name = 'item_view'
|
|
|
|
model = Item
|
|
|
|
|
|
|
|
def pre_redirect(self, *args, **kwargs):
|
|
|
|
try:
|
|
|
|
speaker = Speaker.objects.filter(
|
2014-12-22 18:09:05 +01:00
|
|
|
item=self.get_object(),
|
2013-04-25 16:18:16 +02:00
|
|
|
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.')
|
2014-12-22 18:09:05 +01:00
|
|
|
% {'item': self.get_object()})
|
2013-04-25 16:18:16 +02:00
|
|
|
else:
|
|
|
|
speaker.end_speach()
|
2013-03-18 12:34:47 +01:00
|
|
|
|
|
|
|
def get_url_name_args(self):
|
2014-12-22 18:09:05 +01:00
|
|
|
return [self.get_object().pk]
|
2013-03-18 12:34:47 +01:00
|
|
|
|
|
|
|
|
2013-03-18 12:34:47 +01:00
|
|
|
class SpeakerListCloseView(SingleObjectMixin, RedirectView):
|
2013-03-18 12:34:47 +01:00
|
|
|
"""
|
2013-03-18 12:34:47 +01:00
|
|
|
View to close and reopen a list of speakers.
|
2013-03-18 12:34:47 +01:00
|
|
|
"""
|
2014-05-15 20:07:09 +02:00
|
|
|
required_permission = 'agenda.can_manage_agenda'
|
2013-03-18 12:34:47 +01:00
|
|
|
model = Item
|
2013-03-18 12:34:47 +01:00
|
|
|
reopen = False
|
2013-03-18 12:34:47 +01:00
|
|
|
url_name = 'item_view'
|
|
|
|
|
|
|
|
def pre_redirect(self, *args, **kwargs):
|
2014-12-22 18:09:05 +01:00
|
|
|
self.get_object().speaker_list_closed = not self.reopen
|
|
|
|
self.get_object().save()
|
2013-03-18 12:34:47 +01:00
|
|
|
|
|
|
|
def get_url_name_args(self):
|
2014-12-22 18:09:05 +01:00
|
|
|
return [self.get_object().pk]
|
2013-03-18 12:34:47 +01:00
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
"""
|
2014-05-15 20:07:09 +02:00
|
|
|
required_permission = 'agenda.can_manage_agenda'
|
2013-03-18 12:34:47 +01:00
|
|
|
model = Item
|
|
|
|
url_name = 'item_view'
|
|
|
|
|
|
|
|
@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.
|
|
|
|
"""
|
|
|
|
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:
|
2014-12-22 18:09:05 +01:00
|
|
|
speaker = Speaker.objects.filter(item=self.get_object()).get(pk=speaker_pk)
|
2013-03-18 12:34:47 +01:00
|
|
|
except:
|
|
|
|
transaction.rollback()
|
|
|
|
break
|
|
|
|
speaker.weight = counter + 1
|
|
|
|
speaker.save()
|
|
|
|
else:
|
|
|
|
transaction.commit()
|
2013-03-18 12:34:47 +01:00
|
|
|
return None
|
|
|
|
messages.error(request, _('Could not change order. Invalid data.'))
|
2013-03-18 12:34:47 +01:00
|
|
|
|
|
|
|
def get_url_name_args(self):
|
2014-12-22 18:09:05 +01:00
|
|
|
return [self.get_object().pk]
|
2013-03-18 12:34:47 +01:00
|
|
|
|
|
|
|
|
2013-03-18 12:34:47 +01:00
|
|
|
class CurrentListOfSpeakersView(RedirectView):
|
|
|
|
"""
|
2013-05-30 00:24:26 +02:00
|
|
|
Redirect to the current list of speakers and set the request.user on it,
|
|
|
|
begins speach of the next speaker or ends the speach of the current speaker.
|
2013-03-18 12:34:47 +01:00
|
|
|
"""
|
2013-05-06 20:17:13 +02:00
|
|
|
set_speaker = False
|
2013-05-24 01:44:58 +02:00
|
|
|
next_speaker = False
|
2013-05-30 00:24:26 +02:00
|
|
|
end_speach = False
|
2013-05-06 20:17:13 +02:00
|
|
|
|
2013-03-18 12:34:47 +01:00
|
|
|
def get_item(self):
|
|
|
|
"""
|
|
|
|
Returns the current Item, or None, if the current Slide is not an Agenda Item.
|
|
|
|
"""
|
2014-05-17 14:21:57 +02:00
|
|
|
slide = get_active_object()
|
|
|
|
if slide is None or isinstance(slide, Item):
|
|
|
|
# No Slide or an agenda item is active
|
|
|
|
item = slide
|
2013-03-18 12:34:47 +01:00
|
|
|
else:
|
2014-05-17 14:21:57 +02:00
|
|
|
# A related Item is active
|
|
|
|
try:
|
|
|
|
item = Item.objects.filter(
|
|
|
|
content_type=ContentType.objects.get_for_model(slide),
|
|
|
|
object_id=slide.pk)[0]
|
|
|
|
except IndexError:
|
|
|
|
item = None
|
|
|
|
|
|
|
|
return item
|
2013-03-18 12:34:47 +01:00
|
|
|
|
|
|
|
def get_redirect_url(self):
|
|
|
|
"""
|
|
|
|
Returns the URL to the item_view if:
|
|
|
|
|
2013-05-12 02:06:55 +02:00
|
|
|
* the current slide is an item,
|
2013-05-24 01:44:58 +02:00
|
|
|
* the user has the permission to see the item,
|
|
|
|
* the user who wants to be a speaker has this permission and
|
2013-05-12 02:06:55 +02:00
|
|
|
* the list of speakers of the item is not closed,
|
2013-03-18 12:34:47 +01:00
|
|
|
|
|
|
|
in other case, it returns the URL to the dashboard.
|
|
|
|
|
2013-05-30 00:24:26 +02:00
|
|
|
This method also adds the request.user to the list of speakers if he
|
2013-05-12 02:06:55 +02:00
|
|
|
has the right permissions and the list is not closed.
|
2013-05-24 01:44:58 +02:00
|
|
|
|
2013-05-30 00:24:26 +02:00
|
|
|
This method also begins the speach of the next speaker if the flag
|
2013-05-24 01:44:58 +02:00
|
|
|
next_speaker is given.
|
2013-05-30 00:24:26 +02:00
|
|
|
|
|
|
|
This method also ends the speach of the current speaker if the flag
|
|
|
|
end_speach is given.
|
2013-03-18 12:34:47 +01:00
|
|
|
"""
|
|
|
|
item = self.get_item()
|
|
|
|
request = self.request
|
2013-05-24 01:44:58 +02:00
|
|
|
|
2013-03-18 12:34:47 +01:00
|
|
|
if item is None:
|
|
|
|
messages.error(request, _(
|
|
|
|
'There is no list of speakers for the current slide. '
|
2013-04-22 19:59:05 +02:00
|
|
|
'Please choose the agenda item manually from the agenda.'))
|
2013-12-09 23:56:01 +01:00
|
|
|
return reverse('core_dashboard')
|
2013-03-18 12:34:47 +01:00
|
|
|
|
2013-05-06 20:17:13 +02:00
|
|
|
if self.set_speaker:
|
|
|
|
if item.speaker_list_closed:
|
|
|
|
messages.error(request, _('The list of speakers is closed.'))
|
2013-05-24 01:44:58 +02:00
|
|
|
reverse_to_dashboard = True
|
|
|
|
else:
|
|
|
|
if self.request.user.has_perm('agenda.can_be_speaker'):
|
|
|
|
try:
|
|
|
|
Speaker.objects.add(self.request.user, item)
|
2013-06-09 17:51:30 +02:00
|
|
|
except OpenSlidesError, e:
|
|
|
|
messages.error(request, e)
|
2013-10-22 19:12:42 +02:00
|
|
|
else:
|
|
|
|
messages.success(request, _('You were successfully added to the list of speakers.'))
|
2013-05-24 01:44:58 +02:00
|
|
|
finally:
|
|
|
|
reverse_to_dashboard = False
|
|
|
|
else:
|
|
|
|
messages.error(request, _('You can not put yourself on the list of speakers.'))
|
|
|
|
reverse_to_dashboard = True
|
|
|
|
else:
|
|
|
|
reverse_to_dashboard = False
|
2013-05-12 02:06:55 +02:00
|
|
|
|
2013-05-24 01:44:58 +02:00
|
|
|
if self.next_speaker:
|
|
|
|
next_speaker_object = item.get_next_speaker()
|
|
|
|
if next_speaker_object:
|
|
|
|
next_speaker_object.begin_speach()
|
|
|
|
messages.success(request, _('%s is now speaking.') % next_speaker_object)
|
2013-03-18 12:34:47 +01:00
|
|
|
else:
|
2013-05-24 01:44:58 +02:00
|
|
|
messages.error(request, _('The list of speakers is empty.'))
|
|
|
|
if not self.set_speaker:
|
|
|
|
reverse_to_dashboard = True
|
2013-03-18 12:34:47 +01:00
|
|
|
|
2013-05-30 00:24:26 +02:00
|
|
|
if self.end_speach:
|
|
|
|
try:
|
|
|
|
current_speaker = item.speaker_set.filter(end_time=None).exclude(begin_time=None).get()
|
|
|
|
except Speaker.DoesNotExist:
|
|
|
|
messages.error(request, _('There is no one speaking at the moment.'))
|
|
|
|
else:
|
|
|
|
current_speaker.end_speach()
|
|
|
|
messages.success(request, _('%s is now finished.') % current_speaker)
|
|
|
|
reverse_to_dashboard = True
|
|
|
|
|
2013-10-19 22:16:43 +02:00
|
|
|
if item.type == Item.ORGANIZATIONAL_ITEM:
|
|
|
|
if reverse_to_dashboard or not self.request.user.has_perm('agenda.can_see_orga_items'):
|
2013-12-09 23:56:01 +01:00
|
|
|
return reverse('core_dashboard')
|
2013-10-19 22:16:43 +02:00
|
|
|
else:
|
|
|
|
return reverse('item_view', args=[item.pk])
|
2013-03-18 12:34:47 +01:00
|
|
|
else:
|
2013-10-19 22:16:43 +02:00
|
|
|
if reverse_to_dashboard or not self.request.user.has_perm('agenda.can_see_agenda'):
|
2013-12-09 23:56:01 +01:00
|
|
|
return reverse('core_dashboard')
|
2013-10-19 22:16:43 +02:00
|
|
|
else:
|
|
|
|
return reverse('item_view', args=[item.pk])
|
2014-03-27 20:30:15 +01:00
|
|
|
|
|
|
|
|
2014-04-06 15:46:49 +02:00
|
|
|
class CurrentListOfSpeakersProjectorView(AjaxMixin, TemplateView):
|
|
|
|
"""
|
|
|
|
View with the current list of speakers depending on the active slide.
|
|
|
|
Usefule for the projector.
|
|
|
|
"""
|
|
|
|
template_name = 'agenda/current_list_of_speakers_projector.html'
|
|
|
|
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Returns response object depending on request type (ajax or normal).
|
|
|
|
"""
|
|
|
|
if request.is_ajax():
|
|
|
|
value = self.ajax_get(request, *args, **kwargs)
|
|
|
|
else:
|
|
|
|
value = super(CurrentListOfSpeakersProjectorView, self).get(request, *args, **kwargs)
|
|
|
|
return value
|
|
|
|
|
|
|
|
def get_item(self):
|
|
|
|
"""
|
|
|
|
Returns the item of the current slide is an agenda item slide or a
|
|
|
|
slide of a related model else returns None.
|
|
|
|
"""
|
|
|
|
slide_object = get_active_object()
|
|
|
|
if slide_object is None or isinstance(slide_object, Item):
|
|
|
|
item = slide_object
|
|
|
|
else:
|
|
|
|
# TODO: If there is more than one item, use the first one in the
|
|
|
|
# mptt tree that is not closed.
|
|
|
|
try:
|
|
|
|
item = Item.objects.filter(
|
|
|
|
content_type=ContentType.objects.get_for_model(slide_object),
|
|
|
|
object_id=slide_object.pk)[0]
|
|
|
|
except IndexError:
|
|
|
|
item = None
|
|
|
|
return item
|
|
|
|
|
|
|
|
def get_content(self):
|
|
|
|
"""
|
|
|
|
Returns the content of this slide.
|
|
|
|
"""
|
|
|
|
item = self.get_item()
|
|
|
|
if item is None:
|
|
|
|
content = mark_safe('<h1>%s</h1><i>%s</i>\n' % (_('List of speakers'), _('Not available.')))
|
|
|
|
else:
|
|
|
|
content_dict = {
|
|
|
|
'title': item.get_title(),
|
|
|
|
'item': item,
|
|
|
|
'list_of_speakers': item.get_list_of_speakers(
|
|
|
|
old_speakers_count=config['agenda_show_last_speakers'])}
|
|
|
|
content = render_to_string('agenda/item_slide_list_of_speaker.html', content_dict)
|
|
|
|
return content
|
|
|
|
|
|
|
|
def get_overlays_and_overlay_js(self):
|
|
|
|
"""
|
|
|
|
Returns the overlays and their JavaScript for this slide as a
|
|
|
|
two-tuple. The overlay 'agenda_speaker' is always excluded.
|
|
|
|
|
|
|
|
The required JavaScript fot this view is inserted.
|
|
|
|
"""
|
|
|
|
overlays = get_overlays(only_active=True)
|
|
|
|
overlays.pop('agenda_speaker', None)
|
|
|
|
overlay_js = get_projector_overlays_js(as_json=True)
|
|
|
|
# Note: The JavaScript content of overlay 'agenda_speaker' is not
|
|
|
|
# excluded because this overlay has no such content at the moment.
|
|
|
|
extra_js = SortedDict()
|
|
|
|
extra_js['load_file'] = static('js/agenda_current_list_of_speakers_projector.js')
|
|
|
|
extra_js['call'] = 'reloadListOfSpeakers();'
|
|
|
|
extra_js = dumps(extra_js)
|
|
|
|
overlay_js.append(extra_js)
|
|
|
|
return overlays, overlay_js
|
|
|
|
|
|
|
|
def get_context_data(self, **context):
|
|
|
|
"""
|
|
|
|
Returns the context for the projector template. Contains the content
|
|
|
|
of this slide.
|
|
|
|
"""
|
|
|
|
overlays, overlay_js = self.get_overlays_and_overlay_js()
|
|
|
|
return super(CurrentListOfSpeakersProjectorView, self).get_context_data(
|
|
|
|
content=self.get_content(),
|
|
|
|
overlays=overlays,
|
|
|
|
overlay_js=overlay_js,
|
|
|
|
**context)
|
|
|
|
|
|
|
|
def get_ajax_context(self, **context):
|
|
|
|
"""
|
|
|
|
Returns the context including the slide content for ajax response. The
|
|
|
|
overlay 'agenda_speaker' is always excluded.
|
|
|
|
"""
|
|
|
|
overlay_dict = {}
|
|
|
|
for overlay in get_overlays().values():
|
|
|
|
if overlay.is_active() and overlay.name != 'agenda_speaker':
|
|
|
|
overlay_dict[overlay.name] = {
|
|
|
|
'html': overlay.get_projector_html(),
|
|
|
|
'javascript': overlay.get_javascript()}
|
|
|
|
else:
|
|
|
|
overlay_dict[overlay.name] = None
|
|
|
|
return super(CurrentListOfSpeakersProjectorView, self).get_ajax_context(
|
|
|
|
content=self.get_content(),
|
|
|
|
overlays=overlay_dict,
|
|
|
|
**context)
|
|
|
|
|
|
|
|
|
2014-03-27 20:30:15 +01:00
|
|
|
class ItemCSVImportView(CSVImportView):
|
|
|
|
"""
|
|
|
|
Imports agenda items from an uploaded csv file.
|
|
|
|
"""
|
|
|
|
import_function = staticmethod(import_agenda_items)
|
2014-05-15 20:07:09 +02:00
|
|
|
required_permission = 'agenda.can_manage_agenda'
|
2014-03-27 20:30:15 +01:00
|
|
|
success_url_name = 'item_overview'
|
|
|
|
template_name = 'agenda/item_form_csv_import.html'
|