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
|
2015-03-29 14:42:27 +02:00
|
|
|
from collections import defaultdict
|
2014-04-06 15:46:49 +02:00
|
|
|
from json import dumps
|
2012-02-21 13:17:42 +01:00
|
|
|
|
2014-04-06 15:46:49 +02:00
|
|
|
from django.contrib.contenttypes.models import ContentType
|
|
|
|
from django.contrib.staticfiles.templatetags.staticfiles import static
|
|
|
|
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_projector_overlays_js,
|
2015-01-17 14:01:44 +01:00
|
|
|
get_overlays)
|
2013-09-25 10:01:01 +02:00
|
|
|
from openslides.utils.pdf import stylesheet
|
2015-03-29 14:42:27 +02:00
|
|
|
from openslides.utils.rest_api import ModelViewSet, list_route, Response
|
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
|
|
|
PDFView,
|
|
|
|
RedirectView,
|
|
|
|
SingleObjectMixin,
|
2015-03-09 15:40:54 +01:00
|
|
|
TemplateView)
|
2014-03-27 20:30:15 +01:00
|
|
|
|
2015-03-09 15:40:54 +01:00
|
|
|
from .models import Item
|
2015-01-06 00:14:49 +01:00
|
|
|
from .serializers import ItemSerializer
|
2012-02-20 17:46:45 +01:00
|
|
|
|
|
|
|
|
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 = ....'
|
|
|
|
"""
|
2015-03-26 05:36:10 +01:00
|
|
|
required_permission = 'agenda.can_manage'
|
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
|
|
|
|
2012-04-14 10:54:22 +02:00
|
|
|
class AgendaPDF(PDFView):
|
2012-07-04 12:50:33 +02:00
|
|
|
"""
|
|
|
|
Create a full agenda-PDF.
|
|
|
|
"""
|
2015-03-26 05:36:10 +01:00
|
|
|
required_permission = 'agenda.can_see'
|
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
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
2015-02-12 18:48:14 +01:00
|
|
|
class ItemViewSet(ModelViewSet):
|
2015-01-06 00:14:49 +01:00
|
|
|
"""
|
2015-01-24 16:35:50 +01:00
|
|
|
API endpoint to list, retrieve, create, update and destroy agenda items.
|
2015-01-06 00:14:49 +01:00
|
|
|
"""
|
2015-02-12 18:48:14 +01:00
|
|
|
queryset = Item.objects.all()
|
2015-01-06 00:14:49 +01:00
|
|
|
serializer_class = ItemSerializer
|
|
|
|
|
|
|
|
def check_permissions(self, request):
|
|
|
|
"""
|
|
|
|
Calls self.permission_denied() if the requesting user has not the
|
2015-01-24 16:35:50 +01:00
|
|
|
permission to see the agenda and in case of create, update or destroy
|
|
|
|
requests the permission to manage the agenda and to see organizational
|
|
|
|
items.
|
2015-01-06 00:14:49 +01:00
|
|
|
"""
|
2015-03-26 05:36:10 +01:00
|
|
|
if (not request.user.has_perm('agenda.can_see') or
|
2015-01-17 14:25:05 +01:00
|
|
|
(self.action in ('create', 'update', 'destroy') and not
|
2015-03-26 05:36:10 +01:00
|
|
|
(request.user.has_perm('agenda.can_manage') and
|
2015-01-17 14:25:05 +01:00
|
|
|
request.user.has_perm('agenda.can_see_orga_items')))):
|
2015-01-06 00:14:49 +01:00
|
|
|
self.permission_denied(request)
|
|
|
|
|
|
|
|
def check_object_permissions(self, request, obj):
|
|
|
|
"""
|
|
|
|
Checks if the requesting user has permission to see also an
|
|
|
|
organizational item if it is one.
|
|
|
|
"""
|
|
|
|
if obj.type == obj.ORGANIZATIONAL_ITEM and not request.user.has_perm('agenda.can_see_orga_items'):
|
|
|
|
self.permission_denied(request)
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
"""
|
2015-01-24 16:35:50 +01:00
|
|
|
Filters organizational items if the user has no permission to see them.
|
2015-01-06 00:14:49 +01:00
|
|
|
"""
|
2015-02-12 18:48:14 +01:00
|
|
|
queryset = super().get_queryset()
|
2015-01-17 14:25:05 +01:00
|
|
|
if not self.request.user.has_perm('agenda.can_see_orga_items'):
|
2015-01-06 00:14:49 +01:00
|
|
|
queryset = queryset.exclude(type__exact=Item.ORGANIZATIONAL_ITEM)
|
|
|
|
return queryset
|
2015-03-29 14:42:27 +02:00
|
|
|
|
|
|
|
@list_route(methods=['get', 'put'])
|
|
|
|
def tree(self, request):
|
|
|
|
"""
|
|
|
|
Returns or sets the agenda tree.
|
|
|
|
"""
|
|
|
|
if request.method == 'PUT':
|
|
|
|
if not (request.user.has_perm('agenda.can_manage') and
|
|
|
|
request.user.has_perm('agenda.can_see_orga_items')):
|
|
|
|
self.permission_denied(request)
|
|
|
|
return self.set_tree(request.data['tree'])
|
|
|
|
return self.get_tree()
|
|
|
|
|
|
|
|
def get_tree(self):
|
|
|
|
"""
|
|
|
|
Returns the agenda tree.
|
|
|
|
"""
|
|
|
|
item_list = Item.objects.order_by('weight')
|
|
|
|
|
|
|
|
# Index the items to get the children for each item
|
|
|
|
item_children = defaultdict(list)
|
|
|
|
for item in item_list:
|
|
|
|
if item.parent:
|
|
|
|
item_children[item.parent_id].append(item)
|
|
|
|
|
|
|
|
def get_children(item):
|
|
|
|
"""
|
|
|
|
Returns a list with all the children for item.
|
|
|
|
|
|
|
|
Returns an empty list if item has no children.
|
|
|
|
"""
|
|
|
|
return [dict(id=child.pk, children=get_children(child))
|
|
|
|
for child in item_children[item.pk]]
|
|
|
|
|
|
|
|
return Response(dict(id=item.pk, children=get_children(item))
|
|
|
|
for item in item_list if not item.parent)
|
|
|
|
|
|
|
|
def set_tree(self, tree):
|
|
|
|
"""
|
|
|
|
Sets the agenda tree.
|
|
|
|
|
|
|
|
The tree has to be a nested object. For example:
|
|
|
|
[{"id": 1}, {"id": 2, "children": [{"id": 3}]}]
|
|
|
|
"""
|
|
|
|
|
|
|
|
def walk_items(tree, parent=None):
|
|
|
|
"""
|
|
|
|
Generator that returns each item in the tree as tuple.
|
|
|
|
|
|
|
|
This tuples have tree values. The item id, the item parent and the
|
|
|
|
weight of the item.
|
|
|
|
"""
|
|
|
|
for weight, element in enumerate(tree):
|
|
|
|
yield (element['id'], parent, weight)
|
|
|
|
yield from walk_items(element.get('children', []), element['id'])
|
|
|
|
|
|
|
|
touched_items = set()
|
|
|
|
for item_pk, parent_pk, weight in walk_items(tree):
|
|
|
|
# Check that the item is only once in the tree to prevent invalid trees
|
|
|
|
if item_pk in touched_items:
|
|
|
|
detail = "Item %d is more then once in the tree" % item_pk
|
|
|
|
break
|
|
|
|
touched_items.add(item_pk)
|
|
|
|
|
|
|
|
Item.objects.filter(pk=item_pk).update(
|
|
|
|
parent_id=parent_pk,
|
|
|
|
weight=weight)
|
|
|
|
else:
|
|
|
|
# Everithing is fine. Return a response with status_code 200 an no content
|
|
|
|
return Response()
|
|
|
|
return Response({'detail': detail}, status=400)
|