diff --git a/openslides/projector/api.py b/openslides/projector/api.py index 59bc41c2d..9daf943f9 100644 --- a/openslides/projector/api.py +++ b/openslides/projector/api.py @@ -17,6 +17,9 @@ from openslides.projector.projector import SLIDE, Slide, Widget def split_sid(sid): + """ + Slit a SID in the model-part and in the model-id + """ try: data = sid.split('-') except AttributeError: @@ -34,6 +37,11 @@ def split_sid(sid): def get_slide_from_sid(sid, element=False): + """ + Return the Slide for an given sid. + If element== False, return the slide-dict, + else, return the object. + """ try: key, id = split_sid(sid) except TypeError: @@ -56,10 +64,10 @@ def get_slide_from_sid(sid, element=False): def get_active_slide(only_sid=False): """ Returns the active slide. If no slide is active, or it can not find an Item, - it raise an error + return None - if only_sid is True, returns only the id of this item. Returns None if not Item - is active. Does not Raise Item.DoesNotExist + if only_sid is True, returns only the id of this item. Returns None if not + Item is active. """ sid = config["presentation"] @@ -69,12 +77,18 @@ def get_active_slide(only_sid=False): def set_active_slide(sid, argument=None): + """ + Set the active Slide. + """ config["presentation"] = sid config['presentation_argument'] = argument -def register_slidemodel(model, model_name=None, control_template=None, weight=0): - #TODO: Warn if there already is a slide with this prefix +def register_slidemodel(model, model_name=None, control_template=None, + weight=0): + """ + Register a Model as a slide. + """ if model_name is None: model_name = model.prefix @@ -94,7 +108,9 @@ def register_slidemodel(model, model_name=None, control_template=None, weight=0) def register_slidefunc(key, func, control_template=None, weight=0, name=''): - #TODO: Warn if there already is a slide with this prefix + """ + Register a function for as a slide. + """ if control_template is None: control_template = 'projector/default_control_slidefunc.html' category = func.__module__.split('.')[0] @@ -110,6 +126,10 @@ def register_slidefunc(key, func, control_template=None, weight=0, name=''): def projector_message_set(message, sid=None): + """ + Set the overlay-message. + if sid is set, only show the message on the sid-slide. + """ from models import ProjectorOverlay config['projector_message'] = message try: @@ -121,4 +141,7 @@ def projector_message_set(message, sid=None): def projector_message_delete(): + """ + Delete the overlay-message. + """ config['projector_message'] = '' diff --git a/openslides/projector/models.py b/openslides/projector/models.py index 4853baf7d..040986fd7 100644 --- a/openslides/projector/models.py +++ b/openslides/projector/models.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ openslides.projector.models - ~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Models for the projector app. @@ -12,22 +12,25 @@ from django.db import models from django.dispatch import receiver +from django.utils.translation import ugettext as _, ugettext_noop from openslides.config.signals import default_config_value -from api import register_slidemodel -from projector import SlideMixin +from openslides.projector.api import register_slidemodel +from openslides.projector.projector import SlideMixin + +from openslides.config.models import config -from config.models import config -from utils.translation_ext import ugettext as _ class ProjectorSlide(models.Model, SlideMixin): + """ + Model for Slides, only for the projector. Also called custom slides. + """ prefix = 'ProjectorSlide' title = models.CharField(max_length=256, verbose_name=_("Title")) text = models.TextField(null=True, blank=True, verbose_name=_("Text")) - #weight = models.IntegerField(default=0, verbose_name=_("Weight")) def slide(self): return { @@ -46,12 +49,19 @@ class ProjectorSlide(models.Model, SlideMixin): class Meta: permissions = ( - ('can_manage_projector', _("Can manage the projector", fixstr=True)), - ('can_see_projector', _("Can see projector", fixstr=True)), + ('can_manage_projector', ugettext_noop("Can manage the projector")), + ('can_see_projector', ugettext_noop("Can see projector")), ) +register_slidemodel(ProjectorSlide, + control_template='projector/control_customslide.html') + + class ProjectorOverlay(models.Model): + """ + Save information for a overlay. + """ active = models.BooleanField(verbose_name=_('Active')) def_name = models.CharField(max_length=64) sid = models.CharField(max_length=64, null=True, blank=True) @@ -62,9 +72,6 @@ class ProjectorOverlay(models.Model): return self.def_name -register_slidemodel(ProjectorSlide, control_template='projector/control_customslide.html') - - @receiver(default_config_value, dispatch_uid="projector_default_config") def default_config(sender, key, **kwargs): return { diff --git a/openslides/projector/projector.py b/openslides/projector/projector.py index ccc88812b..4d2e2d326 100644 --- a/openslides/projector/projector.py +++ b/openslides/projector/projector.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ openslides.projector.projector - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Slide functions for the projector app. @@ -29,7 +29,7 @@ class SlideMixin(object): def slide(self): """ - Return a map with all Data for a Slide + Return a map with all Data for the Slide. """ return { 'slide': self, @@ -47,19 +47,22 @@ class SlideMixin(object): @property def active(self): """ - Return True, if the the slide is the active one. + Return True, if the the slide is the active slide. """ from api import get_active_slide - return True if get_active_slide(only_sid=True) == self.sid else False + return get_active_slide(only_sid=True) == self.sid def set_active(self): """ - Appoint this item as the active one. + Appoint this item as the active slide. """ - config["presentation"] = "%s-%d" % (self.prefix, self.id) + set_active_slide(self.sid) class Slide(object): + """ + Represents a Slide for the projector. Can be a modelinstanz, or a function. + """ def __init__(self, model_slide=False, func=None, model=None, category=None, key=None, model_name='', control_template='', weight=0, name=''): """ @@ -82,10 +85,16 @@ class Slide(object): @property def active(self): + """ + Return True if the Slide is active, else: False. + """ from api import get_active_slide return get_active_slide(True) == self.key def get_items(self): + """ + If the Slide is a Slide from a Model, return all Objects. + """ try: return self.model.objects.all() except AttributeError: @@ -93,6 +102,9 @@ class Slide(object): class Widget(object): + """ + Class for a Widget for the Projector-Tab. + """ def __init__(self, name, html=None, template=None, context={}): self.name = name if html is not None: diff --git a/openslides/projector/signals.py b/openslides/projector/signals.py index 17605a373..f034ccd06 100644 --- a/openslides/projector/signals.py +++ b/openslides/projector/signals.py @@ -13,5 +13,3 @@ from django.dispatch import Signal projector_overlays = Signal(providing_args=['register', 'call']) - -projector_control_box = Signal() diff --git a/openslides/projector/tests.py b/openslides/projector/tests.py deleted file mode 100644 index 35a9258b3..000000000 --- a/openslides/projector/tests.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - openslides.projector.tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Unit tests for the projector app. - - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. - :license: GNU GPL, see LICENSE for more details. -""" - -from django.test import TestCase - - -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.assertEqual(1 + 1, 2) diff --git a/openslides/projector/urls.py b/openslides/projector/urls.py index e83cc0fa7..481a4d5df 100644 --- a/openslides/projector/urls.py +++ b/openslides/projector/urls.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ openslides.projector.urls - ~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~ URL list for the projector app. @@ -10,11 +10,8 @@ :license: GNU GPL, see LICENSE for more details. """ -from django.conf.urls.defaults import * +from django.conf.urls.defaults import patterns, url -from openslides.utils.views import CreateView - -from openslides.projector.models import ProjectorSlide from openslides.projector.views import (ControlView, ActivateView, CustomSlideCreateView, CustomSlideUpdateView, CustomSlideDeleteView, CountdownEdit, ProjectorEdit, Projector, ActivateOverlay) @@ -104,12 +101,6 @@ urlpatterns = patterns('projector.views', name='projector_clean', ), -# TODO: Merge the following lines with this one: - ## url(r'^countdown/(?P[^/]*)/$', - ## CountdownEdit.as_view(), - ## name='countdown_edit', - ## ), - url(r'^countdown/reset/$', CountdownEdit.as_view(), {'command': 'reset'}, diff --git a/openslides/projector/views.py b/openslides/projector/views.py index 8a8d5612a..5d6aa0456 100644 --- a/openslides/projector/views.py +++ b/openslides/projector/views.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ openslides.projector.views - ~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~ Views for the projector app. @@ -13,41 +13,48 @@ from datetime import datetime from time import time +from django.conf import settings from django.contrib import messages from django.core.urlresolvers import reverse -from django.utils.translation import ugettext as _ +from django.db.models import Q +from django.dispatch import receiver from django.utils.datastructures import SortedDict from django.utils.importlib import import_module -from django.dispatch import receiver -from django.db.models import Q -from django.conf import settings +from django.utils.translation import ugettext as _ +from openslides.utils.template import render_block_to_string, Tab +from openslides.utils.utils import html_strong from openslides.utils.views import (TemplateView, RedirectView, CreateView, UpdateView, DeleteView, AjaxMixin) -from openslides.utils.template import render_block_to_string, Tab from openslides.config.models import config 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.projector import SLIDE, Widget from openslides.projector.signals import projector_overlays class ControlView(TemplateView, AjaxMixin): + """ + Overview over all possible slides, the overlays and a liveview. + """ template_name = 'projector/control.html' permission_required = 'projector.can_manage_projector' def get_projector_overlays(self): overlays = [] - for receiver, name in projector_overlays.send(sender='registerer', register=True): + for receiver, name in projector_overlays.send(sender='registerer', + register=True): if name is not None: try: - projector_overlay = ProjectorOverlay.objects.get(def_name=name) + projector_overlay = ProjectorOverlay.objects.get( + def_name=name) except ProjectorOverlay.DoesNotExist: active = name == 'Message' - projector_overlay = ProjectorOverlay(def_name=name, active=active) + projector_overlay = ProjectorOverlay(def_name=name, + active=active) projector_overlay.save() overlays.append(projector_overlay) return overlays @@ -57,13 +64,6 @@ class ControlView(TemplateView, AjaxMixin): projector_message_set(request.POST['message_text']) elif 'message-clean' in request.POST: projector_message_delete() - else: - for overlay in self.get_projector_overlays(): - if overlay.def_name in request.POST: - overlay.active = True - else: - overlay.active = False - overlay.save() if request.is_ajax(): return self.ajax_get(request, *args, **kwargs) return self.get(request, *args, **kwargs) @@ -101,87 +101,10 @@ class ControlView(TemplateView, AjaxMixin): return context -class ActivateOverlay(RedirectView): - url = 'projector_control' - allow_ajax = True - - @property - def overlay(self): - try: - return self._overlay - except AttributeError: - self._overlay = ProjectorOverlay.objects.get(def_name=self.kwargs['name']) - return self._overlay - - def pre_redirect(self, request, *args, **kwargs): - if kwargs['activate']: - self.overlay.active = True - else: - self.overlay.active = False - self.overlay.save() - - def get_ajax_context(self, **kwargs): - return { - 'active': self.overlay.active, - 'def_name': self.overlay.def_name, - } - - -class ActivateView(RedirectView): - url = 'projector_control' - allow_ajax = True - - def pre_redirect(self, request, *args, **kwargs): - try: - set_active_slide(kwargs['sid'], kwargs['argument']) - except KeyError: - set_active_slide(kwargs['sid']) - config['up'] = 0 - config['bigger'] = 100 - - -class CustomSlideCreateView(CreateView): - permission_required = 'agenda.can_manage_agenda' - template_name = 'projector/new.html' - model = ProjectorSlide - context_object_name = 'customslide' - success_url = 'projector_control' - apply_url = 'customslide_edit' - - def get_success_url(self): - messages.success(self.request, _("Custom slide %s was successfully created.") % self.request.POST['title']) - if 'apply' in self.request.POST: - return reverse(self.get_apply_url(), args=[self.object.id]) - return reverse(super(CreateView, self).get_success_url()) - - -class CustomSlideUpdateView(UpdateView): - permission_required = 'projector.can_manage_projector' - template_name = 'projector/new.html' - model = ProjectorSlide - context_object_name = 'customslide' - success_url = 'projector_control' - apply_url = 'customslide_edit' - - def get_success_url(self): - messages.success(self.request, _("Custom slide %s was successfully modified.") % self.request.POST['title']) - if 'apply' in self.request.POST: - return '' - return reverse(super(UpdateView, self).get_success_url()) - - -class CustomSlideDeleteView(DeleteView): - permission_required = 'projector.can_manage_projector' - model = ProjectorSlide - url = 'projector_control' - - def pre_post_redirect(self, request, *args, **kwargs): - self.object = self.get_object() - self.object.delete() - messages.success(request, _("Custom slide %s was successfully deleted.") % self.object) - - class Projector(TemplateView, AjaxMixin): + """ + The Projector-Page. + """ permission_required = 'projector.can_see_projector' @property @@ -211,8 +134,11 @@ class Projector(TemplateView, AjaxMixin): # 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): + 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 @@ -227,8 +153,10 @@ class Projector(TemplateView, AjaxMixin): 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) + 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) content_hash = hash(content) @@ -250,8 +178,27 @@ class Projector(TemplateView, AjaxMixin): return super(Projector, self).get(request, *args, **kwargs) +class ActivateView(RedirectView): + """ + Activate a Slide. + """ + permission_required = 'projector.can_manage_projector' + url = 'projector_control' + allow_ajax = True + + def pre_redirect(self, request, *args, **kwargs): + try: + set_active_slide(kwargs['sid'], kwargs['argument']) + except KeyError: + set_active_slide(kwargs['sid']) + config['up'] = 0 + config['bigger'] = 100 + class ProjectorEdit(RedirectView): + """ + Scale or scroll the projector. + """ permission_required = 'projector.can_manage_projector' url = 'projector_control' allow_ajax = True @@ -273,6 +220,9 @@ class ProjectorEdit(RedirectView): class CountdownEdit(RedirectView): + """ + Start, stop or reset the countdown. + """ permission_required = 'projector.can_manage_projector' url = 'projector_control' allow_ajax = True @@ -293,8 +243,8 @@ class CountdownEdit(RedirectView): start_stamp = config['countdown_start_stamp'] pause_stamp = config['countdown_pause_stamp'] now = time() - - config['countdown_start_stamp'] = now - (pause_stamp - start_stamp) + config['countdown_start_stamp'] = now - \ + (pause_stamp - start_stamp) else: config['countdown_start_stamp'] = time() @@ -306,7 +256,8 @@ class CountdownEdit(RedirectView): config['countdown_state'] = 'paused' elif command == 'set-default': try: - config['countdown_time'] = int(self.request.GET['countdown_time']) + config['countdown_time'] = \ + int(self.request.GET['countdown_time']) except ValueError: pass except AttributeError: @@ -319,7 +270,74 @@ class CountdownEdit(RedirectView): } +class ActivateOverlay(RedirectView): + """ + Activate or deactivate an overlay. + """ + url = 'projector_control' + allow_ajax = True + permission_required = 'projector.can_manage_projector' + + @property + def overlay(self): + try: + return self._overlay + except AttributeError: + self._overlay = ProjectorOverlay.objects.get( + def_name=self.kwargs['name']) + return self._overlay + + def pre_redirect(self, request, *args, **kwargs): + if kwargs['activate']: + self.overlay.active = True + else: + self.overlay.active = False + self.overlay.save() + + def get_ajax_context(self, **kwargs): + return { + 'active': self.overlay.active, + 'def_name': self.overlay.def_name, + } + + +class CustomSlideCreateView(CreateView): + """ + Create a custom slide. + """ + permission_required = 'agenda.can_manage_agenda' + template_name = 'projector/new.html' + model = ProjectorSlide + context_object_name = 'customslide' + success_url = 'projector_control' + apply_url = 'customslide_edit' + + +class CustomSlideUpdateView(UpdateView): + """ + Update a custom slide. + """ + permission_required = 'projector.can_manage_projector' + template_name = 'projector/new.html' + model = ProjectorSlide + context_object_name = 'customslide' + success_url = 'projector_control' + apply_url = 'customslide_edit' + + +class CustomSlideDeleteView(DeleteView): + """ + Delete a custom slide. + """ + permission_required = 'projector.can_manage_projector' + model = ProjectorSlide + url = 'projector_control' + + def register_tab(request): + """ + Register the projector tab. + """ selected = True if request.path.startswith('/projector/') else False return Tab( title=_('Projector'), @@ -330,6 +348,9 @@ def register_tab(request): def get_widgets(request): + """ + Return the custom slide widget. + """ return [ Widget( name='projector', diff --git a/openslides/utils/views.py b/openslides/utils/views.py index 5141e19ef..efde945dc 100644 --- a/openslides/utils/views.py +++ b/openslides/utils/views.py @@ -52,7 +52,7 @@ from django.views.generic.list import TemplateResponseMixin from openslides.config.models import config -from openslides.utils.utils import render_to_forbitten +from openslides.utils.utils import render_to_forbitten, html_strong from openslides.utils.signals import template_manipulation from openslides.utils.pdf import firstPage, laterPages @@ -164,6 +164,7 @@ class FormView(PermissionMixin, _FormView): class UpdateView(PermissionMixin, _UpdateView): def get_success_url(self): + messages.success(self.request, self.get_success_message()) if 'apply' in self.request.POST: return '' return reverse(super(UpdateView, self).get_success_url()) @@ -177,9 +178,13 @@ class UpdateView(PermissionMixin, _UpdateView): messages.error(self.request, _('Please check the form for errors.')) return super(UpdateView, self).form_invalid(form) + def get_success_message(self): + return _('%s was successfully modified.') % html_strong(self.object) + class CreateView(PermissionMixin, _CreateView): def get_success_url(self): + messages.success(self.request, self.get_success_message()) if 'apply' in self.request.POST: return reverse(self.get_apply_url(), args=[self.object.id]) return reverse(super(CreateView, self).get_success_url()) @@ -197,13 +202,16 @@ class CreateView(PermissionMixin, _CreateView): messages.error(self.request, _('Please check the form for errors.')) return super(CreateView, self).form_invalid(form) + def get_success_message(self): + return _('%s was successfully created.') % html_strong(self.object) + class DeleteView(RedirectView, SingleObjectMixin): def get_confirm_question(self): - return _('Do you really want to delete %s?') % self.object + return _('Do you really want to delete %s?') % html_strong(self.object) def get_success_message(self): - return _('Item %s was successfully deleted.') % self.object + return _('%s was successfully deleted.') % html_strong(self.object) def pre_redirect(self, request, *args, **kwargs): self.confirm_form(request, self.object)