From 00eb365b9149a1ba8cc16fb9bb1d008716ee6022 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sun, 1 Jul 2012 15:35:05 +0200 Subject: [PATCH 1/3] rewrote active_slide in generic view also reordert some imports --- openslides/agenda/views.py | 16 ++-- openslides/application/forms.py | 65 +++++++++------- openslides/application/views.py | 31 ++++---- openslides/projector/api.py | 4 +- openslides/projector/projector.py | 7 +- openslides/projector/urls.py | 7 +- openslides/projector/views.py | 125 ++++++++++++++++-------------- openslides/utils/views.py | 52 +++++++------ 8 files changed, 162 insertions(+), 145 deletions(-) diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index 57b933f16..10102d11f 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -18,18 +18,18 @@ from django.utils.translation import ugettext as _ from django.core.context_processors import csrf from django.views.generic.detail import SingleObjectMixin -from utils.pdf import stylesheet -from utils.views import (TemplateView, RedirectView, UpdateView, CreateView, +from openslides.utils.pdf import stylesheet +from openslides.utils.views import (TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView, FormView, DetailView) -from utils.template import Tab +from openslides.utils.template import Tab -from config.models import config +from openslides.config.models import config -from projector.api import get_active_slide, set_active_slide -from projector.projector import Widget, SLIDE +from openslides.projector.api import get_active_slide, set_active_slide +from openslides.projector.projector import Widget, SLIDE -from agenda.models import Item -from agenda.forms import ItemOrderForm, ItemForm, ConfigForm +from openslides.agenda.models import Item +from openslides.agenda.forms import ItemOrderForm, ItemForm, ConfigForm class Overview(TemplateView): diff --git a/openslides/application/forms.py b/openslides/application/forms.py index 203739c87..6cbddb16c 100644 --- a/openslides/application/forms.py +++ b/openslides/application/forms.py @@ -10,15 +10,15 @@ :license: GNU GPL, see LICENSE for more details. """ -from django.forms import ModelForm, Form, CharField, Textarea, TextInput, ModelMultipleChoiceField, ModelChoiceField, BooleanField, FileField, FileInput, IntegerField, ChoiceField, Select +from django import forms from django.contrib.auth.models import User -from utils.forms import CssClassMixin -from utils.translation_ext import ugettext as _ -from application.models import Application +from openslides.utils.forms import CssClassMixin +from openslides.utils.translation_ext import ugettext as _ +from openslides.application.models import Application -class UserModelChoiceField(ModelChoiceField): +class UserModelChoiceField(forms.ModelChoiceField): """ Extend ModelChoiceField for users so that the choices are listed as 'first_name last_name' instead of just 'username'. @@ -27,7 +27,7 @@ class UserModelChoiceField(ModelChoiceField): return obj.get_full_name() -class UserModelMultipleChoiceField(ModelMultipleChoiceField): +class UserModelMultipleChoiceField(forms.ModelMultipleChoiceField): """ Extend ModelMultipleChoiceField for users so that the choices are listed as 'first_name last_name' instead of just 'username'. @@ -36,18 +36,18 @@ class UserModelMultipleChoiceField(ModelMultipleChoiceField): return obj.get_full_name() -class ApplicationForm(Form, CssClassMixin): - title = CharField(widget=TextInput(), label=_("Title")) - text = CharField(widget=Textarea(), label=_("Text")) - reason = CharField(widget=Textarea(), required=False, label=_("Reason")) +class ApplicationForm(forms.Form, CssClassMixin): + title = forms.CharField(widget=forms.TextInput(), label=_("Title")) + text = forms.CharField(widget=forms.Textarea(), label=_("Text")) + reason = forms.CharField(widget=forms.Textarea(), required=False, label=_("Reason")) class ApplicationFormTrivialChanges(ApplicationForm): - trivial_change = BooleanField(required=False, label=_("Trivial change"), + trivial_change = forms.BooleanField(required=False, label=_("Trivial change"), help_text=_("Trivial changes don't create a new version.")) -class ApplicationManagerForm(ModelForm, CssClassMixin): +class ApplicationManagerForm(forms.ModelForm, CssClassMixin): submitter = UserModelChoiceField( queryset=User.objects.all().exclude(profile=None).order_by("first_name"), label=_("Submitter"), @@ -65,27 +65,34 @@ class ApplicationManagerFormSupporter(ApplicationManagerForm): ) -class ApplicationImportForm(Form, CssClassMixin): - csvfile = FileField(widget=FileInput(attrs={'size':'50'}), label=_("CSV File")) - import_permitted = BooleanField(required=False, label=_("Import applications with status \"permitted\""), help_text=_("Set the initial status for each application to \"permitted\"")) +class ApplicationImportForm(forms.Form, CssClassMixin): + csvfile = forms.FileField( + widget=forms.FileInput(attrs={'size':'50'}), + label=_("CSV File"), + ) + import_permitted = forms.BooleanField( + required=False, + label=_("Import applications with status \"permitted\""), + help_text=_("Set the initial status for each application to \"permitted\""), + ) -class ConfigForm(Form, CssClassMixin): - application_min_supporters = IntegerField( - widget=TextInput(attrs={'class':'small-input'}), +class ConfigForm(forms.Form, CssClassMixin): + application_min_supporters = forms.IntegerField( + widget=forms.TextInput(attrs={'class':'small-input'}), label=_("Number of (minimum) required supporters for a application"), initial=4, min_value=0, max_value=8, help_text=_("Choose 0 to disable the supporting system"), ) - application_preamble = CharField( - widget=TextInput(), + application_preamble = forms.CharField( + widget=forms.TextInput(), required=False, label=_("Application preamble") ) - application_pdf_ballot_papers_selection = ChoiceField( - widget=Select(), + application_pdf_ballot_papers_selection = forms.ChoiceField( + widget=forms.Select(), required=False, label=_("Number of ballot papers (selection)"), choices=[ @@ -94,24 +101,24 @@ class ConfigForm(Form, CssClassMixin): ("CUSTOM_NUMBER", _("Use the following custom number")), ] ) - application_pdf_ballot_papers_number = IntegerField( - widget=TextInput(attrs={'class':'small-input'}), + application_pdf_ballot_papers_number = forms.IntegerField( + widget=forms.TextInput(attrs={'class':'small-input'}), required=False, min_value=1, label=_("Custom number of ballot papers") ) - application_pdf_title = CharField( - widget=TextInput(), + application_pdf_title = forms.CharField( + widget=forms.TextInput(), required=False, label=_("Title for PDF document (all applications)") ) - application_pdf_preamble = CharField( - widget=Textarea(), + application_pdf_preamble = forms.CharField( + widget=forms.Textarea(), required=False, label=_("Preamble text for PDF document (all applications)") ) - application_allow_trivial_change = BooleanField( + application_allow_trivial_change = forms.BooleanField( label=_("Allow trivial changes"), help_text=_('Warning: Trivial changes undermine the application permission system.'), required=False, diff --git a/openslides/application/views.py b/openslides/application/views.py index 6a8d07126..f96c2c4bd 100644 --- a/openslides/application/views.py +++ b/openslides/application/views.py @@ -34,26 +34,28 @@ from django.core.context_processors import csrf from django.utils.translation import ugettext as _ from django.utils.translation import ungettext from django.db import transaction +from django.conf import settings -from config.models import config +from openslides.config.models import config -from settings import SITE_ROOT +from openslides.utils.pdf import stylesheet +from openslides.utils.views import PDFView, RedirectView, DeleteView, FormView -from utils.pdf import stylesheet -from utils.views import PDFView, RedirectView, DeleteView, FormView - -from utils.utils import template, permission_required, \ +from openslides.utils.utils import template, permission_required, \ render_to_forbitten, del_confirm_form, gen_confirm_form -from utils.template import Tab +from openslides.utils.template import Tab -from projector.projector import Widget +from openslides.projector.projector import Widget -from poll.views import PollFormView +from openslides.poll.views import PollFormView -from agenda.models import Item +from openslides.participant.models import Profile +from openslides.participant.api import gen_username, gen_password -from application.models import Application, AVersion, ApplicationPoll -from application.forms import ( +from openslides.agenda.models import Item + +from openslides.application.models import Application, AVersion, ApplicationPoll +from openslides.application.forms import ( ApplicationForm, ApplicationFormTrivialChanges, ApplicationManagerForm, @@ -62,9 +64,6 @@ from application.forms import ( ConfigForm, ) -from participant.models import Profile -from participant.api import gen_username, gen_password - @permission_required('application.can_see_application') @template('application/overview.html') @@ -853,7 +852,7 @@ class ApplicationPollPDF(PDFView): pdf_document.build(story) def append_to_pdf(self, story): - imgpath = os.path.join(SITE_ROOT, 'static/images/circle.png') + imgpath = os.path.join(settings.SITE_ROOT, 'static/images/circle.png') circle = "  " % imgpath cell = [] cell.append(Spacer(0,0.8*cm)) diff --git a/openslides/projector/api.py b/openslides/projector/api.py index 3efe3cdb3..bafd1bbcf 100644 --- a/openslides/projector/api.py +++ b/openslides/projector/api.py @@ -12,8 +12,8 @@ from django.template.loader import render_to_string -from config.models import config -from projector import SLIDE, Slide, Widget +from openslides.config.models import config +from openslides.projector.projector import SLIDE, Slide, Widget def split_sid(sid): diff --git a/openslides/projector/projector.py b/openslides/projector/projector.py index d3e0d3cfb..ccc88812b 100644 --- a/openslides/projector/projector.py +++ b/openslides/projector/projector.py @@ -15,7 +15,7 @@ from time import time from django.dispatch import receiver from django.template.loader import render_to_string -from config.models import config +from openslides.config.models import config from openslides.projector.signals import projector_overlays @@ -42,10 +42,7 @@ class SlideMixin(object): """ Return the sid from this Slide """ - for key, value in SLIDE.iteritems(): - if type(self) == value.model: - return "%s-%d" % (key, self.id) - return None + return "%s-%d" % (self.prefix, self.id) @property def active(self): diff --git a/openslides/projector/urls.py b/openslides/projector/urls.py index 36a5179d2..02ec7ff55 100644 --- a/openslides/projector/urls.py +++ b/openslides/projector/urls.py @@ -17,18 +17,19 @@ from openslides.utils.views import CreateView from openslides.projector.models import ProjectorSlide from openslides.projector.views import (ControlView, ActivateView, CustomSlideCreateView, CustomSlideUpdateView, CustomSlideDeleteView, - CountdownEdit, ProjectorEdit) + CountdownEdit, ProjectorEdit, Projector) urlpatterns = patterns('projector.views', - url(r'^$', 'active_slide', + url(r'^$', + Projector.as_view(), {'sid': None}, name='projector_show', ), url(r'^preview/(?P[^/]*)/$', - 'active_slide', + Projector.as_view(), name='projctor_preview_slide', ), diff --git a/openslides/projector/views.py b/openslides/projector/views.py index ee7bb2daa..d1218f30d 100644 --- a/openslides/projector/views.py +++ b/openslides/projector/views.py @@ -14,30 +14,25 @@ from datetime import datetime from time import time from django.contrib import messages -from django.shortcuts import render_to_response, redirect -from django.template import RequestContext from django.core.urlresolvers import reverse from django.utils.translation import ugettext as _ from django.utils.datastructures import SortedDict from django.utils.importlib import import_module from django.dispatch import receiver -from django.template.loader import render_to_string from django.db.models import Q +from django.conf import settings +from openslides.utils.views import (TemplateView, RedirectView, CreateView, + UpdateView, DeleteView, AjaxMixin) +from openslides.utils.template import render_block_to_string, Tab -from utils.views import TemplateView, RedirectView, CreateView, UpdateView, DeleteView -from utils.utils import (template, permission_required, del_confirm_form, - ajax_request) -from utils.template import render_block_to_string, Tab -from openslides.utils.signals import template_manipulation +from openslides.config.models import config -from config.models import config -import settings - -from api import get_active_slide, set_active_slide, projector_message_set, projector_message_delete, get_slide_from_sid -from projector import SLIDE, Widget -from models import ProjectorOverlay, ProjectorSlide -from openslides.projector.signals import projector_overlays, projector_control_box +from openslides.projector.api import (get_active_slide, set_active_slide, + projector_message_set, projector_message_delete, get_slide_from_sid) +from openslides.projector.projector import SLIDE, Widget +from openslides.projector.models import ProjectorOverlay, ProjectorSlide +from openslides.projector.signals import projector_overlays class ControlView(TemplateView): @@ -150,59 +145,73 @@ class CustomSlideDeleteView(DeleteView): messages.success(request, _("Custom slide %s was successfully deleted.") % self.object) -@permission_required('projector.can_see_projector') -def active_slide(request, sid=None): - """ - Shows the active Slide. - """ - if sid is None: +class Projector(TemplateView, AjaxMixin): + permission_required = 'projector.can_see_projector' + + @property + def data(self): try: - data = get_active_slide() - except AttributeError: #TODO: It has to be an Slide.DoesNotExist - data = None - ajax = 'on' - else: - data = get_slide_from_sid(sid) - ajax = 'off' + return self._data + except AttributeError: + pass + sid = self.kwargs['sid'] + if sid is None: + try: + data = get_active_slide() + except AttributeError: #TODO: It has to be an Slide.DoesNotExist + data = None + ajax = 'on' + else: + data = get_slide_from_sid(sid) + ajax = 'off' + print data - if data is None: - data = { - 'title': config['event_name'], - 'template': 'projector/default.html', - } - data['overlays'] = [] - data['overlay'] = '' - data['ajax'] = ajax + if data is None: + data = { + 'title': config['event_name'], + 'template': 'projector/default.html', + } + data['overlays'] = [] + data['ajax'] = ajax - # Projector Overlays - sid = get_active_slide(True) - active_defs = ProjectorOverlay.objects.filter(active=True).filter(Q(sid=sid) | Q(sid=None)).values_list('def_name', flat=True) - for receiver, response in projector_overlays.send(sender=sid, register=False, call=active_defs): - if response is not None: - data['overlays'].append(response) + # Projector Overlays + if self.kwargs['sid'] is None: + active_defs = ProjectorOverlay.objects.filter(active=True).filter(Q(sid=sid) | Q(sid=None)).values_list('def_name', flat=True) + for receiver, response in projector_overlays.send(sender=sid, register=False, call=active_defs): + if response is not None: + data['overlays'].append(response) + self._data = data + return data + def get_template_names(self): + return [self.data['template']] - template_manipulation.send(sender='projector', request=request, context=data) - if request.is_ajax(): - content = render_block_to_string(data['template'], 'content', data) - scrollcontent = render_block_to_string(data['template'], 'scrollcontent', data) - jsondata = { + def get_context_data(self, **kwargs): + context = super(Projector, self).get_context_data(**kwargs) + context.update(self.data) + return context + + def get_ajax_context(self, **kwargs): + content = render_block_to_string(self.get_template_names()[0], 'content', self.data) + scrollcontent = render_block_to_string(self.get_template_names()[0], 'scrollcontent', self.data) + + context = super(Projector, self).get_ajax_context(**kwargs) + context.update({ 'content': content, 'scrollcontent': scrollcontent, - 'overlays': data['overlays'], - 'title': data['title'], 'time': datetime.now().strftime('%H:%M'), + 'overlays': self.data['overlays'], + 'title': self.data['title'], 'bigger': config['bigger'], 'up': config['up'], - 'overlay': data['overlay'] - } - return ajax_request(jsondata) - else: - return render_to_response( - data['template'], - data, - context_instance=RequestContext(request) - ) + }) + return context + + def get(self, request, *args, **kwargs): + if request.is_ajax(): + return self.ajax_get(request, *args, **kwargs) + return super(Projector, self).get(request, *args, **kwargs) + class ProjectorEdit(RedirectView): diff --git a/openslides/utils/views.py b/openslides/utils/views.py index 4365c5a27..83dd425b7 100644 --- a/openslides/utils/views.py +++ b/openslides/utils/views.py @@ -13,27 +13,31 @@ try: import json except ImportError: + # for python 2.5 support import simplejson as json try: from cStringIO import StringIO except ImportError: + # Is this exception realy necessary? from StringIO import StringIO -from reportlab.platypus import SimpleDocTemplate, Paragraph, Frame, PageBreak, Spacer, Table, LongTable, TableStyle, Image +from reportlab.platypus import (SimpleDocTemplate, Paragraph, Frame, PageBreak, + Spacer, Table, LongTable, TableStyle, Image) from reportlab.lib.units import cm -from django.conf import settings from django.contrib import messages -from django.utils.translation import ugettext as _ -from django.http import HttpResponseServerError, HttpResponse, HttpResponseRedirect +from django.contrib.auth.decorators import login_required +from django.core.context_processors import csrf from django.core.urlresolvers import reverse +from django.conf import settings +from django.dispatch import receiver +from django.http import HttpResponseServerError, HttpResponse, HttpResponseRedirect +from django.utils.decorators import method_decorator +from django.utils.translation import ugettext as _ +from django.utils.importlib import import_module from django.template import loader, RequestContext from django.template.loader import render_to_string -from django.contrib.auth.decorators import login_required -from django.utils.decorators import method_decorator -from django.dispatch import receiver -from django.utils.importlib import import_module from django.views.generic import ( TemplateView as _TemplateView, RedirectView as _RedirectView, @@ -45,14 +49,12 @@ from django.views.generic import ( ) from django.views.generic.detail import SingleObjectMixin from django.views.generic.list import TemplateResponseMixin -from django.utils.importlib import import_module -from django.core.context_processors import csrf -import settings -from utils import render_to_forbitten +from openslides.config.models import config + +from openslides.utils.utils import render_to_forbitten from openslides.utils.signals import template_manipulation -from pdf import firstPage, laterPages -from config.models import config +from openslides.utils.pdf import firstPage, laterPages NO_PERMISSION_REQUIRED = 'No permission required' @@ -93,6 +95,14 @@ class PermissionMixin(object): return _View.dispatch(self, request, *args, **kwargs) +class AjaxMixin(object): + def get_ajax_context(self, **kwargs): + return {} + + def ajax_get(self, request, *args, **kwargs): + return HttpResponse(json.dumps(self.get_ajax_context(**kwargs))) + + class TemplateView(PermissionMixin, _TemplateView): def get_context_data(self, **kwargs): context = super(TemplateView, self).get_context_data(**kwargs) @@ -107,15 +117,12 @@ class ListView(PermissionMixin, SetCookieMixin, _ListView): return context -class AjaxView(PermissionMixin, View): +class AjaxView(PermissionMixin, AjaxMixin, View): def get(self, request, *args, **kwargs): - return HttpResponse(json.dumps(self.get_ajax_context(**kwargs))) - - def get_ajax_context(self, **kwargs): - return {} + return self.ajax_get(request, *args, **kwargs) -class RedirectView(PermissionMixin, _RedirectView): +class RedirectView(PermissionMixin, AjaxMixin, _RedirectView): permanent = False allow_ajax = False @@ -132,15 +139,12 @@ class RedirectView(PermissionMixin, _RedirectView): self.pre_post_redirect(request, *args, **kwargs) if self.request.is_ajax() and self.allow_ajax: - return HttpResponse(json.dumps(self.get_ajax_context(**kwargs))) + return self.ajax_get(request, *args, **kwargs) return super(RedirectView, self).get(request, *args, **kwargs) def get_redirect_url(self, **kwargs): return reverse(super(RedirectView, self).get_redirect_url(**kwargs)) - def get_ajax_context(self, **kwargs): - return {} - class FormView(PermissionMixin, _FormView): def get_success_url(self): From ff19e6d2c8f955fff7ae4059184176401112d6b6 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sun, 1 Jul 2012 16:10:26 +0200 Subject: [PATCH 2/3] #247 summary of items for the projector --- openslides/agenda/models.py | 22 +++++++++++++------ .../agenda/templates/agenda/item_row.html | 3 +++ .../templates/projector/AgendaSummary.html | 2 +- openslides/projector/api.py | 4 ++-- openslides/projector/urls.py | 5 ++--- openslides/projector/views.py | 6 +++-- 6 files changed, 27 insertions(+), 15 deletions(-) diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index 817518c55..36e5df50b 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -68,13 +68,21 @@ class Item(MPTTModel, SlideMixin): """ Return a map with all Data for the Slide """ - if self.releated_sid: - return self.get_releated_slide().slide() - data = { - 'item': self, - 'title': self.get_title(), - 'template': 'projector/AgendaText.html', - } + if config['presentation_argument'] == 'summary': + print 'soweit schonmal' + data = { + 'title': self.get_title(), + 'items': self.get_children(), + 'template': 'projector/AgendaSummary.html', + } + elif self.releated_sid: + data = self.get_releated_slide().slide() + else: + data = { + 'item': self, + 'title': self.get_title(), + 'template': 'projector/AgendaText.html', + } return data def set_closed(self, closed=True): diff --git a/openslides/agenda/templates/agenda/item_row.html b/openslides/agenda/templates/agenda/item_row.html index 8cc4c27a2..a4bb9b353 100644 --- a/openslides/agenda/templates/agenda/item_row.html +++ b/openslides/agenda/templates/agenda/item_row.html @@ -39,6 +39,9 @@ {% if perms.agenda.can_manage_agenda %} + {% if not item.is_leaf_node %} + + {% endif %} {% endif %} diff --git a/openslides/agenda/templates/projector/AgendaSummary.html b/openslides/agenda/templates/projector/AgendaSummary.html index 8c1926aa1..87ef6da55 100644 --- a/openslides/agenda/templates/projector/AgendaSummary.html +++ b/openslides/agenda/templates/projector/AgendaSummary.html @@ -7,7 +7,7 @@ {% endblock %} {% block content %} -

{%trans "Agenda" %}

+

{% if title %}{{ title }}{% else %}{% trans "Agenda" %}{% endif %}

{% endblock %} {% block scrollcontent %} diff --git a/openslides/projector/api.py b/openslides/projector/api.py index bafd1bbcf..59bc41c2d 100644 --- a/openslides/projector/api.py +++ b/openslides/projector/api.py @@ -68,8 +68,9 @@ def get_active_slide(only_sid=False): return get_slide_from_sid(sid) -def set_active_slide(sid): +def set_active_slide(sid, argument=None): config["presentation"] = sid + config['presentation_argument'] = argument def register_slidemodel(model, model_name=None, control_template=None, weight=0): @@ -115,7 +116,6 @@ def projector_message_set(message, sid=None): overlay = ProjectorOverlay.objects.get(def_name='Message') except ProjectorOverlay.DoesNotExist: overlay = ProjectorOverlay(def_name='Message', active=True) - print "hier mal ein ", sid overlay.sid=sid overlay.save() diff --git a/openslides/projector/urls.py b/openslides/projector/urls.py index 02ec7ff55..7343b1cb8 100644 --- a/openslides/projector/urls.py +++ b/openslides/projector/urls.py @@ -43,10 +43,9 @@ urlpatterns = patterns('projector.views', name='projector_activate_slide', ), - url(r'^activate/(?P[^/]*)/summary/$', + url(r'^activate/(?P[^/]*)/(?P[^/]*)/$', ActivateView.as_view(), - {'summary': True}, - name='projector_activate_summary', + name='projector_activate_slide', ), url(r'^new/$', diff --git a/openslides/projector/views.py b/openslides/projector/views.py index d1218f30d..28298b9c7 100644 --- a/openslides/projector/views.py +++ b/openslides/projector/views.py @@ -99,7 +99,10 @@ class ActivateView(RedirectView): allow_ajax = True def pre_redirect(self, request, *args, **kwargs): - set_active_slide(kwargs['sid']) + try: + set_active_slide(kwargs['sid'], kwargs['argument']) + except KeyError: + set_active_slide(kwargs['sid']) config['up'] = 0 config['bigger'] = 100 @@ -164,7 +167,6 @@ class Projector(TemplateView, AjaxMixin): else: data = get_slide_from_sid(sid) ajax = 'off' - print data if data is None: data = { From f903f350b81ceedb978d2af757c23b1e9e189c94 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sun, 1 Jul 2012 16:13:34 +0200 Subject: [PATCH 3/3] insert item-summary link in the widget --- .../agenda/static/images/icons/view-list-tree.png | Bin 0 -> 299 bytes openslides/agenda/templates/agenda/widget.html | 3 +++ 2 files changed, 3 insertions(+) create mode 100644 openslides/agenda/static/images/icons/view-list-tree.png diff --git a/openslides/agenda/static/images/icons/view-list-tree.png b/openslides/agenda/static/images/icons/view-list-tree.png new file mode 100644 index 0000000000000000000000000000000000000000..0f8264c78d78dbbea7fa8f458a86f5394e0f61d1 GIT binary patch literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~&H|6fVxZ#d zAk65bF}ngN$X?><>&kwUk(E)CS^wIs#Xup~0G|+7APoU*Ntf7?E_0_p5Xn5{+kH8t z`)JGR3*8&9Z{ECl%a$!$w{G3Jeao)%kI$SrfA;M8Yu9evyZ7Mv^Oyhr{Re`yCss8; zQs1}!-eZk--+wFyrAj--46$I g4&-IjzSzGt?v*H?vj6d;ULe  {% endfor %} {{ item }} + {% if not item.is_leaf_node %} + + {% endif %} {% empty %}
  • {% trans 'No items available.' %}