remove update_projector and add inform_changed_data

This commit is contained in:
Oskar Hahn 2015-01-17 14:01:44 +01:00
parent 86261bda28
commit 1a1d072454
19 changed files with 123 additions and 293 deletions

View File

@ -233,7 +233,8 @@ def runserver(settings, args):
start_browser(browser_url) start_browser(browser_url)
# Now the settings is available and the function can be imported. # Now the settings is available and the function can be imported.
from openslides.utils.tornado_webserver import run_tornado # TODO: only start tornado when it is used as wsgi server
from openslides.utils.autoupdate import run_tornado
run_tornado(args.address, port) run_tornado(args.address, port)

View File

@ -12,17 +12,17 @@ from mptt.models import MPTTModel, TreeForeignKey
from openslides.config.api import config from openslides.config.api import config
from openslides.core.models import Tag from openslides.core.models import Tag
from openslides.projector.api import (get_active_slide, reset_countdown, from openslides.projector.api import (reset_countdown,
start_countdown, stop_countdown, start_countdown, stop_countdown)
update_projector, update_projector_overlay)
from openslides.projector.models import SlideMixin from openslides.projector.models import SlideMixin
from openslides.utils.exceptions import OpenSlidesError from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.models import AbsoluteUrlMixin from openslides.utils.models import AbsoluteUrlMixin
from openslides.utils.rest_api import RESTModelMixin
from openslides.utils.utils import to_roman from openslides.utils.utils import to_roman
from openslides.users.models import User from openslides.users.models import User
class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel): class Item(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, MPTTModel):
""" """
An Agenda Item An Agenda Item
@ -122,11 +122,6 @@ class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
class MPTTMeta: class MPTTMeta:
order_insertion_by = ['weight'] order_insertion_by = ['weight']
def save(self, *args, **kwargs):
super(Item, self).save(*args, **kwargs)
if self.parent and self.parent.is_active_slide():
update_projector()
def clean(self): def clean(self):
""" """
Ensures that the children of orga items are only orga items. Ensures that the children of orga items are only orga items.
@ -135,7 +130,7 @@ class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
raise ValidationError(_('Agenda items can not be child elements of an organizational item.')) raise ValidationError(_('Agenda items can not be child elements of an organizational item.'))
if self.type == self.ORGANIZATIONAL_ITEM and self.get_descendants().filter(type=self.AGENDA_ITEM).exists(): if self.type == self.ORGANIZATIONAL_ITEM and self.get_descendants().filter(type=self.AGENDA_ITEM).exists():
raise ValidationError(_('Organizational items can not have agenda items as child elements.')) raise ValidationError(_('Organizational items can not have agenda items as child elements.'))
return super(Item, self).clean() return super().clean()
def __str__(self): def __str__(self):
return self.get_title() return self.get_title()
@ -153,14 +148,14 @@ class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
elif link == 'delete': elif link == 'delete':
url = reverse('item_delete', args=[str(self.id)]) url = reverse('item_delete', args=[str(self.id)])
elif link == 'projector_list_of_speakers': elif link == 'projector_list_of_speakers':
url = '%s&type=list_of_speakers' % super(Item, self).get_absolute_url('projector') url = '%s&type=list_of_speakers' % super().get_absolute_url('projector')
elif link == 'projector_summary': elif link == 'projector_summary':
url = '%s&type=summary' % super(Item, self).get_absolute_url('projector') url = '%s&type=summary' % super().get_absolute_url('projector')
elif (link in ('projector', 'projector_preview') and elif (link in ('projector', 'projector_preview') and
self.content_object and isinstance(self.content_object, SlideMixin)): self.content_object and isinstance(self.content_object, SlideMixin)):
url = self.content_object.get_absolute_url(link) url = self.content_object.get_absolute_url(link)
else: else:
url = super(Item, self).get_absolute_url(link) url = super().get_absolute_url(link)
return url return url
def get_title(self): def get_title(self):
@ -359,7 +354,7 @@ class SpeakerManager(models.Manager):
return self.create(item=item, user=user, weight=weight + 1) return self.create(item=item, user=user, weight=weight + 1)
class Speaker(AbsoluteUrlMixin, models.Model): class Speaker(RESTModelMixin, AbsoluteUrlMixin, models.Model):
""" """
Model for the Speaker list. Model for the Speaker list.
""" """
@ -396,14 +391,6 @@ class Speaker(AbsoluteUrlMixin, models.Model):
('can_be_speaker', ugettext_noop('Can put oneself on the list of speakers')), ('can_be_speaker', ugettext_noop('Can put oneself on the list of speakers')),
) )
def save(self, *args, **kwargs):
super(Speaker, self).save(*args, **kwargs)
self.check_and_update_projector()
def delete(self, *args, **kwargs):
super(Speaker, self).delete(*args, **kwargs)
self.check_and_update_projector()
def __str__(self): def __str__(self):
return str(self.user) return str(self.user)
@ -417,17 +404,6 @@ class Speaker(AbsoluteUrlMixin, models.Model):
url = super(Speaker, self).get_absolute_url(link) url = super(Speaker, self).get_absolute_url(link)
return url return url
def check_and_update_projector(self):
"""
Checks, if the agenda item, or parts of it, is on the projector.
If yes, it updates the projector.
"""
if self.item.is_active_slide():
if get_active_slide().get('type', None) == 'list_of_speakers':
update_projector()
else:
update_projector_overlay('agenda_speaker')
def begin_speach(self): def begin_speach(self):
""" """
Let the user speak. Let the user speak.
@ -448,10 +424,6 @@ class Speaker(AbsoluteUrlMixin, models.Model):
if config['agenda_couple_countdown_and_speakers']: if config['agenda_couple_countdown_and_speakers']:
reset_countdown() reset_countdown()
start_countdown() start_countdown()
if self.item.is_active_slide():
# TODO: only update the overlay if the overlay is active and
# slide type is None.
update_projector_overlay('projector_countdown')
def end_speach(self): def end_speach(self):
""" """
@ -462,7 +434,9 @@ class Speaker(AbsoluteUrlMixin, models.Model):
# stop countdown # stop countdown
if config['agenda_couple_countdown_and_speakers']: if config['agenda_couple_countdown_and_speakers']:
stop_countdown() stop_countdown()
if self.item.is_active_slide():
# TODO: only update the overlay if the overlay is active and def get_root_rest_element(self):
# slide type is None. """
update_projector_overlay('projector_countdown') Returns the item to this instance, which is the root rest element.
"""
return self.item

View File

@ -21,8 +21,7 @@ from openslides.projector.api import (
get_active_object, get_active_object,
get_active_slide, get_active_slide,
get_projector_overlays_js, get_projector_overlays_js,
get_overlays, get_overlays)
update_projector)
from openslides.utils import rest_api from openslides.utils import rest_api
from openslides.utils.exceptions import OpenSlidesError from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.pdf import stylesheet from openslides.utils.pdf import stylesheet
@ -159,10 +158,6 @@ class Overview(TemplateView):
# TODO: assure, that it is a valid tree # TODO: assure, that it is a valid tree
context = self.get_context_data(**kwargs) context = self.get_context_data(**kwargs)
if get_active_slide()['callback'] == 'agenda':
update_projector()
context = self.get_context_data(**kwargs)
return self.render_to_response(context) return self.render_to_response(context)

View File

@ -11,15 +11,14 @@ from openslides.config.api import config
from openslides.poll.models import (BaseOption, BasePoll, BaseVote, from openslides.poll.models import (BaseOption, BasePoll, BaseVote,
CollectDefaultVotesMixin, CollectDefaultVotesMixin,
PublishPollMixin) PublishPollMixin)
from openslides.projector.api import get_active_object, update_projector from openslides.projector.models import SlideMixin
from openslides.projector.models import RelatedModelMixin, SlideMixin
from openslides.utils.exceptions import OpenSlidesError from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.models import AbsoluteUrlMixin from openslides.utils.models import AbsoluteUrlMixin
from openslides.utils.utils import html_strong from openslides.utils.utils import html_strong
from openslides.users.models import User from openslides.users.models import User
class AssignmentCandidate(RelatedModelMixin, models.Model): class AssignmentCandidate(models.Model):
""" """
Many2Many table between an assignment and the candidates. Many2Many table between an assignment and the candidates.
""" """
@ -34,12 +33,6 @@ class AssignmentCandidate(RelatedModelMixin, models.Model):
def __str__(self): def __str__(self):
return str(self.person) return str(self.person)
def get_related_model(self):
"""
Returns the assignment
"""
return self.assignment
class Assignment(SlideMixin, AbsoluteUrlMixin, models.Model): class Assignment(SlideMixin, AbsoluteUrlMixin, models.Model):
slide_callback_name = 'assignment' slide_callback_name = 'assignment'
@ -190,11 +183,6 @@ class Assignment(SlideMixin, AbsoluteUrlMixin, models.Model):
candidate = self.assignment_candidates.get(person=person) candidate = self.assignment_candidates.get(person=person)
candidate.elected = value candidate.elected = value
candidate.save() candidate.save()
# update projector if assignment or assignmentpoll slide is active
active_object = get_active_object()
if (type(active_object) is type(self) and active_object.pk == self.pk) or \
(type(active_object) is AssignmentPoll and active_object.assignment_id == self.pk):
update_projector()
def is_elected(self, person): def is_elected(self, person):
return person in self.elected return person in self.elected
@ -224,16 +212,13 @@ class Assignment(SlideMixin, AbsoluteUrlMixin, models.Model):
items = Item.objects.filter(content_type=ContentType.objects.get_for_model(Assignment), object_id=self.pk) items = Item.objects.filter(content_type=ContentType.objects.get_for_model(Assignment), object_id=self.pk)
for item in items: for item in items:
someone_added = None
for candidate in self.candidates: for candidate in self.candidates:
try: try:
someone_added = Speaker.objects.add(candidate, item) Speaker.objects.add(candidate, item)
except OpenSlidesError: except OpenSlidesError:
# The Speaker is already on the list. Do nothing. # The Speaker is already on the list. Do nothing.
# TODO: Find a smart way not to catch the error concerning AnonymousUser. # TODO: Find a smart way not to catch the error concerning AnonymousUser.
pass pass
if someone_added is not None:
someone_added.check_and_update_projector()
return poll return poll
@ -290,7 +275,7 @@ class AssignmentOption(BaseOption):
return str(self.candidate) return str(self.candidate)
class AssignmentPoll(SlideMixin, RelatedModelMixin, CollectDefaultVotesMixin, class AssignmentPoll(SlideMixin, CollectDefaultVotesMixin,
PublishPollMixin, AbsoluteUrlMixin, BasePoll): PublishPollMixin, AbsoluteUrlMixin, BasePoll):
slide_callback_name = 'assignmentpoll' slide_callback_name = 'assignmentpoll'
@ -323,9 +308,6 @@ class AssignmentPoll(SlideMixin, RelatedModelMixin, CollectDefaultVotesMixin,
def get_assignment(self): def get_assignment(self):
return self.assignment return self.assignment
def get_related_model(self):
return self.assignment
def get_vote_values(self): def get_vote_values(self):
if self.yesnoabstain: if self.yesnoabstain:
return [ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')] return [ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')]

View File

@ -11,8 +11,10 @@ class CoreAppConfig(AppConfig):
from . import main_menu, widgets # noqa from . import main_menu, widgets # noqa
# Import all required stuff. # Import all required stuff.
from django.db.models import signals
from openslides.config.signals import config_signal from openslides.config.signals import config_signal
from openslides.projector.api import register_slide_model from openslides.projector.api import register_slide_model
from openslides.utils.autoupdate import inform_changed_data_receiver
from openslides.utils.rest_api import router from openslides.utils.rest_api import router
from .signals import setup_general_config from .signals import setup_general_config
from .views import CustomSlideViewSet from .views import CustomSlideViewSet
@ -26,3 +28,8 @@ class CoreAppConfig(AppConfig):
# Register viewset. # Register viewset.
router.register('core/customslide', CustomSlideViewSet) router.register('core/customslide', CustomSlideViewSet)
# Update data when any model of any installed app is saved or deleted
signals.post_save.connect(inform_changed_data_receiver, dispatch_uid='inform_changed_data_receiver')
signals.post_delete.connect(inform_changed_data_receiver, dispatch_uid='inform_changed_data_receiver')
# TODO: test if the m2m_changed signal is also needed

View File

@ -4,7 +4,6 @@ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy, ugettext_noop from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
from openslides.projector.api import update_projector
post_database_setup = Signal() post_database_setup = Signal()
@ -108,8 +107,7 @@ def setup_general_config(sender, **kwargs):
widget=forms.TextInput(), widget=forms.TextInput(),
label=ugettext_lazy('Title'), label=ugettext_lazy('Title'),
help_text=ugettext_lazy('Also used for the default welcome slide.'), help_text=ugettext_lazy('Also used for the default welcome slide.'),
required=False), required=False))
on_change=update_projector)
welcome_text = ConfigVariable( welcome_text = ConfigVariable(
name='welcome_text', name='welcome_text',

View File

@ -173,3 +173,8 @@ CKEDITOR_CONFIGS = {
'default': CKEDITOR_DEFAULT_CONFIG, 'default': CKEDITOR_DEFAULT_CONFIG,
'images': CKEDITOR_IMG_CONFIG, 'images': CKEDITOR_IMG_CONFIG,
} }
# Use small alternative with tornado as frontend or big alternative with a
# webserver as wsgi server.
USE_TORNADO_AS_WSGI_SERVER = True

View File

@ -2,7 +2,6 @@ from django.http import HttpResponse
from openslides.config.api import config from openslides.config.api import config
from openslides.projector.api import get_active_slide from openslides.projector.api import get_active_slide
from openslides.utils.tornado_webserver import ProjectorSocketHandler
from openslides.utils.views import (AjaxView, CreateView, DeleteView, RedirectView, ListView, from openslides.utils.views import (AjaxView, CreateView, DeleteView, RedirectView, ListView,
UpdateView) UpdateView)
@ -119,8 +118,6 @@ class PdfNavBaseView(AjaxView):
Tell connected clients to load an other pdf page. Tell connected clients to load an other pdf page.
""" """
config['projector_active_slide'] = active_slide config['projector_active_slide'] = active_slide
ProjectorSocketHandler.send_updates(
{'calls': {'load_pdf_page': active_slide['page_num']}})
class PdfNextView(PdfNavBaseView): class PdfNextView(PdfNavBaseView):
@ -199,8 +196,4 @@ class PdfToggleFullscreenView(RedirectView):
def get_ajax_context(self, *args, **kwargs): def get_ajax_context(self, *args, **kwargs):
config['pdf_fullscreen'] = not config['pdf_fullscreen'] config['pdf_fullscreen'] = not config['pdf_fullscreen']
active_slide = get_active_slide()
if active_slide['callback'] == 'mediafile':
ProjectorSocketHandler.send_updates(
{'calls': {'toggle_fullscreen': config['pdf_fullscreen']}})
return {'fullscreen': config['pdf_fullscreen']} return {'fullscreen': config['pdf_fullscreen']}

View File

@ -9,7 +9,7 @@ from openslides.config.api import config
from openslides.core.models import Tag from openslides.core.models import Tag
from openslides.mediafile.models import Mediafile from openslides.mediafile.models import Mediafile
from openslides.poll.models import (BaseOption, BasePoll, BaseVote, CollectDefaultVotesMixin) from openslides.poll.models import (BaseOption, BasePoll, BaseVote, CollectDefaultVotesMixin)
from openslides.projector.models import RelatedModelMixin, SlideMixin from openslides.projector.models import SlideMixin
from jsonfield import JSONField from jsonfield import JSONField
from openslides.utils.models import AbsoluteUrlMixin from openslides.utils.models import AbsoluteUrlMixin
from openslides.users.models import User from openslides.users.models import User
@ -612,7 +612,7 @@ class MotionVersion(AbsoluteUrlMixin, models.Model):
return self.active_version.exists() return self.active_version.exists()
class MotionSubmitter(RelatedModelMixin, models.Model): class MotionSubmitter(models.Model):
"""Save the submitter of a Motion.""" """Save the submitter of a Motion."""
motion = models.ForeignKey('Motion', related_name="submitter") motion = models.ForeignKey('Motion', related_name="submitter")
@ -625,9 +625,6 @@ class MotionSubmitter(RelatedModelMixin, models.Model):
"""Return the name of the submitter as string.""" """Return the name of the submitter as string."""
return str(self.person) return str(self.person)
def get_related_model(self):
return self.motion
class MotionSupporter(models.Model): class MotionSupporter(models.Model):
"""Save the submitter of a Motion.""" """Save the submitter of a Motion."""
@ -723,7 +720,7 @@ class MotionOption(BaseOption):
"""The VoteClass, to witch this Class links.""" """The VoteClass, to witch this Class links."""
class MotionPoll(SlideMixin, RelatedModelMixin, CollectDefaultVotesMixin, class MotionPoll(SlideMixin, CollectDefaultVotesMixin,
AbsoluteUrlMixin, BasePoll): AbsoluteUrlMixin, BasePoll):
"""The Class to saves the vote result for a motion poll.""" """The Class to saves the vote result for a motion poll."""
@ -775,9 +772,6 @@ class MotionPoll(SlideMixin, RelatedModelMixin, CollectDefaultVotesMixin,
# or call this in save() # or call this in save()
self.get_option_class()(poll=self).save() self.get_option_class()(poll=self).save()
def get_related_model(self):
return self.motion
def get_percent_base_choice(self): def get_percent_base_choice(self):
return config['motion_poll_100_percent_base'] return config['motion_poll_100_percent_base']

View File

@ -10,7 +10,6 @@ from django.shortcuts import get_object_or_404
from openslides.agenda.views import CreateRelatedAgendaItemView as _CreateRelatedAgendaItemView from openslides.agenda.views import CreateRelatedAgendaItemView as _CreateRelatedAgendaItemView
from openslides.config.api import config from openslides.config.api import config
from openslides.poll.views import PollFormView from openslides.poll.views import PollFormView
from openslides.projector.api import get_active_slide, update_projector
from openslides.utils.utils import html_strong, htmldiff from openslides.utils.utils import html_strong, htmldiff
from openslides.utils.views import (CreateView, CSVImportView, DeleteView, DetailView, from openslides.utils.views import (CreateView, CSVImportView, DeleteView, DetailView,
ListView, PDFView, QuestionView, ListView, PDFView, QuestionView,
@ -142,14 +141,6 @@ class MotionEditMixin(object):
self.object.tags.clear() self.object.tags.clear()
self.object.tags.add(*form.cleaned_data['tags']) self.object.tags.add(*form.cleaned_data['tags'])
# Update the projector if the motion is on it. This can not be done in
# the model, because bulk_create does not call the save method.
active_slide = get_active_slide()
active_slide_pk = active_slide.get('pk', None)
if (active_slide['callback'] == 'motion' and
str(self.object.pk) == str(active_slide_pk)):
update_projector()
messages.success(self.request, self.get_success_message()) messages.success(self.request, self.get_success_message())
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())

View File

@ -4,7 +4,6 @@ from time import time
from django.template.loader import render_to_string from django.template.loader import render_to_string
from openslides.config.api import config from openslides.config.api import config
from openslides.utils.tornado_webserver import ProjectorSocketHandler
from openslides.utils.exceptions import OpenSlidesError from openslides.utils.exceptions import OpenSlidesError
from .signals import projector_overlays from .signals import projector_overlays
@ -28,41 +27,6 @@ class SlideError(OpenSlidesError):
pass pass
def update_projector():
"""
Sends the data to the clients, who listen to the projector.
"""
# TODO: only send necessary html
ProjectorSocketHandler.send_updates({'content': get_projector_content()})
def update_projector_overlay(overlay):
"""
Update one or all overlay on the projector.
Checks if the overlay is activated and updates it in this case.
The argument 'overlay' has to be an overlay object, the name of a
ovleray or None. If it is None, all overlays will be updated.
"""
if overlay is None:
overlays = [item for item in get_overlays().values()]
elif isinstance(overlay, str):
overlays = [get_overlays()[overlay]]
else:
overlays = [overlay]
overlay_dict = {}
for overlay in overlays:
if overlay.is_active():
overlay_dict[overlay.name] = {
'html': overlay.get_projector_html(),
'javascript': overlay.get_javascript()}
else:
overlay_dict[overlay.name] = None
ProjectorSocketHandler.send_updates({'overlays': overlay_dict})
def call_on_projector(calls): def call_on_projector(calls):
""" """
Sends data to the projector. Sends data to the projector.
@ -70,10 +34,10 @@ def call_on_projector(calls):
The argument call has to be a dictionary with the javascript function name The argument call has to be a dictionary with the javascript function name
as key and the argument for it as value. as key and the argument for it as value.
""" """
# TODO: remove this function
projector_js_cache = config['projector_js_cache'] projector_js_cache = config['projector_js_cache']
projector_js_cache.update(calls) projector_js_cache.update(calls)
config['projector_js_cache'] = projector_js_cache config['projector_js_cache'] = projector_js_cache
ProjectorSocketHandler.send_updates({'calls': calls})
def get_projector_content(slide_dict=None): def get_projector_content(slide_dict=None):
@ -181,8 +145,6 @@ def set_active_slide(callback, **kwargs):
""" """
kwargs.update(callback=callback) kwargs.update(callback=callback)
config['projector_active_slide'] = kwargs config['projector_active_slide'] = kwargs
update_projector()
update_projector_overlay(None)
def get_active_slide(): def get_active_slide():

View File

@ -1,42 +1,8 @@
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from openslides.utils.utils import int_or_none from openslides.utils.utils import int_or_none
class RelatedModelMixin(object):
"""
Mixin for motion related models, that appear on the motion slide.
"""
def save(self, *args, **kwargs):
"""
Saves the model and updates the projector, if the motion in on it.
"""
from .api import update_projector
value = super(RelatedModelMixin, self).save(*args, **kwargs)
if self.get_related_model().is_active_slide():
update_projector()
return value
def delete(self, *args, **kwargs):
"""
Deletes the model and updates the projector, if the motion in on it.
"""
from .api import update_projector
value = super(RelatedModelMixin, self).delete(*args, **kwargs)
if self.get_related_model().is_active_slide():
update_projector()
return value
def get_related_model(self):
"""
Return the pk of the related model.
"""
raise ImproperlyConfigured(
'%s has to have a method "get_related_model_pk"' % type(self))
class SlideMixin(object): class SlideMixin(object):
""" """
A Mixin for a Django-Model, for making the model a slide. A Mixin for a Django-Model, for making the model a slide.
@ -47,31 +13,6 @@ class SlideMixin(object):
Name of the callback to render the model as slide. Name of the callback to render the model as slide.
""" """
def save(self, *args, **kwargs):
"""
Updates the projector, if the object is on the projector and changed.
"""
from openslides.projector.api import update_projector
value = super(SlideMixin, self).save(*args, **kwargs)
if self.is_active_slide():
update_projector()
return value
def delete(self, *args, **kwargs):
"""
Updates the projector if the object is on the projector and is deleted.
"""
from openslides.projector.api import update_projector
# Checking active slide has to be done before calling super().delete()
# because super().delete() deletes the object and than we have no
# access to the former existing primary key any more. But updating
# projector has to be done after deleting the object of course.
update_required = self.is_active_slide()
value = super(SlideMixin, self).delete(*args, **kwargs)
if update_required:
update_projector()
return value
def get_absolute_url(self, link='projector'): def get_absolute_url(self, link='projector'):
""" """
Return the url to activate the slide, if link == 'projector'. Return the url to activate the slide, if link == 'projector'.

View File

@ -1,12 +1,10 @@
from openslides.config.api import config from openslides.config.api import config
from openslides.mediafile.models import Mediafile
from openslides.utils.tornado_webserver import ProjectorSocketHandler
from openslides.utils.views import RedirectView, TemplateView from openslides.utils.views import RedirectView, TemplateView
from .api import (call_on_projector, get_active_slide, from .api import (call_on_projector, get_active_slide,
get_overlays, get_projector_content, get_overlays, get_projector_content,
get_projector_overlays_js, reset_countdown, set_active_slide, get_projector_overlays_js, reset_countdown, set_active_slide,
start_countdown, stop_countdown, update_projector_overlay) start_countdown, stop_countdown)
class ProjectorView(TemplateView): class ProjectorView(TemplateView):
@ -53,14 +51,13 @@ class ActivateView(RedirectView):
# we dont have to use set_active_slide, because is causes a content # we dont have to use set_active_slide, because is causes a content
# reload. # reload.
kwargs.update({'page_num': 1, 'pk': request.GET.get('pk')}) kwargs.update({'page_num': 1, 'pk': request.GET.get('pk')})
url = Mediafile.objects.get(pk=kwargs['pk'], is_presentable=True).mediafile.url # TODO: fix me
# url = Mediafile.objects.get(pk=kwargs['pk'], is_presentable=True).mediafile.url
config['projector_active_slide'] = kwargs config['projector_active_slide'] = kwargs
ProjectorSocketHandler.send_updates( # ProjectorSocketHandler.send_updates(
{'calls': {'load_pdf': {'url': url, 'page_num': kwargs['page_num']}}}) # {'calls': {'load_pdf': {'url': url, 'page_num': kwargs['page_num']}}})
else: else:
set_active_slide(kwargs['callback'], **dict(request.GET.items())) set_active_slide(kwargs['callback'], **dict(request.GET.items()))
call_on_projector({'scroll': config['projector_scroll'],
'scale': config['projector_scale']})
class ProjectorControllView(RedirectView): class ProjectorControllView(RedirectView):
@ -121,7 +118,7 @@ class CountdownControllView(RedirectView):
pass pass
else: else:
reset_countdown() reset_countdown()
update_projector_overlay('projector_countdown') # TODO: send signal to update data
def get_ajax_context(self, **kwargs): def get_ajax_context(self, **kwargs):
return { return {
@ -143,7 +140,7 @@ class OverlayMessageView(RedirectView):
config['projector_message'] = request.POST['message_text'] config['projector_message'] = request.POST['message_text']
elif 'message-clean' in request.POST: elif 'message-clean' in request.POST:
config['projector_message'] = '' config['projector_message'] = ''
update_projector_overlay('projector_message') # TODO: update data
def get_ajax_context(self, **kwargs): def get_ajax_context(self, **kwargs):
return { return {
@ -165,12 +162,12 @@ class ActivateOverlay(RedirectView):
if kwargs['activate']: if kwargs['activate']:
if not overlay.is_active(): if not overlay.is_active():
overlay.set_active(True) overlay.set_active(True)
update_projector_overlay(overlay) # Push new overlay to projector, somehow...
self.active = True self.active = True
else: else:
if overlay.is_active(): if overlay.is_active():
overlay.set_active(False) overlay.set_active(False)
update_projector_overlay(overlay) # Push new overlay to projector, somehow...
self.active = False self.active = False
def get_ajax_context(self, **kwargs): def get_ajax_context(self, **kwargs):

View File

@ -18,7 +18,11 @@ from tornado.wsgi import WSGIContainer
class DjangoStaticFileHandler(StaticFileHandler): class DjangoStaticFileHandler(StaticFileHandler):
"""Handels static data by using the django finders.""" """
Handels static data by using the django finders.
Only needed in the "small" version with tornado as wsgi server.
"""
def initialize(self): def initialize(self):
"""Overwrite some attributes.""" """Overwrite some attributes."""
@ -47,25 +51,31 @@ class DjangoStaticFileHandler(StaticFileHandler):
return absolute_path return absolute_path
class ProjectorSocketHandler(SockJSConnection): class OpenSlidesSockJSConnection(SockJSConnection):
""" """
Handels the websocket for the projector. Sockjs connections for OpenSlides.
""" """
waiters = set() waiters = set()
def on_open(self, info): def on_open(self, info):
ProjectorSocketHandler.waiters.add(self) OpenSlidesSockJSConnection.waiters.add(self)
def on_close(self): def on_close(self):
ProjectorSocketHandler.waiters.remove(self) OpenSlidesSockJSConnection.waiters.remove(self)
@classmethod @classmethod
def send_updates(cls, data): def send_updates(cls, data):
# TODO: use a bluk send
for waiter in cls.waiters: for waiter in cls.waiters:
waiter.send(data) waiter.send(data)
def run_tornado(addr, port): def run_tornado(addr, port):
"""
Starts the tornado webserver as wsgi server for OpenSlides.
It runs in one thread.
"""
# Don't try to read the command line args from openslides # Don't try to read the command line args from openslides
parse_command_line(args=[]) parse_command_line(args=[])
@ -81,9 +91,9 @@ def run_tornado(addr, port):
app = WSGIContainer(get_wsgi_application()) app = WSGIContainer(get_wsgi_application())
# Collect urls # Collect urls
projectpr_socket_js_router = SockJSRouter(ProjectorSocketHandler, '/projector/socket')
from openslides.core.chatbox import ChatboxSocketHandler from openslides.core.chatbox import ChatboxSocketHandler
chatbox_socket_js_router = SockJSRouter(ChatboxSocketHandler, '/core/chatbox') chatbox_socket_js_router = SockJSRouter(ChatboxSocketHandler, '/core/chatbox')
sock_js_router = SockJSRouter(OpenSlidesSockJSConnection, '/sockjs')
other_urls = [ other_urls = [
(r"%s(.*)" % settings.STATIC_URL, DjangoStaticFileHandler), (r"%s(.*)" % settings.STATIC_URL, DjangoStaticFileHandler),
(r'%s(.*)' % settings.MEDIA_URL, StaticFileHandler, {'path': settings.MEDIA_ROOT}), (r'%s(.*)' % settings.MEDIA_URL, StaticFileHandler, {'path': settings.MEDIA_ROOT}),
@ -91,7 +101,36 @@ def run_tornado(addr, port):
# Start the application # Start the application
debug = settings.DEBUG debug = settings.DEBUG
tornado_app = Application(projectpr_socket_js_router.urls + chatbox_socket_js_router.urls + other_urls, debug=debug) tornado_app = Application(sock_js_router.urls + chatbox_socket_js_router.urls + other_urls, debug=debug)
server = HTTPServer(tornado_app) server = HTTPServer(tornado_app)
server.listen(port=port, address=addr) server.listen(port=port, address=addr)
IOLoop.instance().start() IOLoop.instance().start()
def inform_changed_data(*args):
"""
Informs all users about changed data.
The arguments are Django/OpenSlides models.
"""
rest_urls = set()
for instance in args:
try:
rest_urls.add(instance.get_root_rest_url())
except AttributeError:
# instance has no method get_root_rest_url
pass
if settings.USE_TORNADO_AS_WSGI_SERVER:
for url in rest_urls:
OpenSlidesSockJSConnection.send_updates(url)
else:
pass
# TODO: fix me
def inform_changed_data_receiver(sender, instance, **kwargs):
"""
Receiver for the inform_changed_data function to use in a signal.
"""
inform_changed_data(instance)

View File

@ -1,3 +1,28 @@
from django.core.urlresolvers import reverse
from rest_framework import permissions, routers, viewsets # noqa from rest_framework import permissions, routers, viewsets # noqa
router = routers.DefaultRouter() router = routers.DefaultRouter()
class RESTModelMixin:
"""
Mixin for django models which are used in our rest api.
"""
def get_root_rest_element(self):
"""
Returns the root rest instance.
Uses self as default.
"""
return self
def get_root_rest_url(self):
"""
Returns the detail url of the root model of this object.
"""
# Gets the default url-name in the same way as django rest framework
# does in relations.HyperlinkedModelSerializer
root_instance = self.get_root_rest_element()
rest_url = '%s-detail' % type(root_instance)._meta.object_name.lower()
return reverse(rest_url, args=[str(root_instance.pk)])

View File

@ -72,23 +72,18 @@ class ListOfSpeakerModelTests(TestCase):
self.assertIsNotNone(Speaker.objects.get(user=self.speaker1, item=self.item1).end_time) self.assertIsNotNone(Speaker.objects.get(user=self.speaker1, item=self.item1).end_time)
self.assertIsNotNone(speaker2_item1.begin_time) self.assertIsNotNone(speaker2_item1.begin_time)
@patch('openslides.agenda.models.update_projector_overlay') def test_speach_coupled_with_countdown(self):
def test_speach_coupled_with_countdown(self, mock_update_projector_overlay):
config['agenda_couple_countdown_and_speakers'] = True config['agenda_couple_countdown_and_speakers'] = True
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1) speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
self.item1.is_active_slide = MagicMock(return_value=True) self.item1.is_active_slide = MagicMock(return_value=True)
speaker1_item1.begin_speach() speaker1_item1.begin_speach()
self.assertEqual(config['countdown_state'], 'active') self.assertEqual(config['countdown_state'], 'active')
mock_update_projector_overlay.assert_called_with('projector_countdown')
mock_update_projector_overlay.reset_mock()
speaker1_item1.end_speach() speaker1_item1.end_speach()
self.assertEqual(config['countdown_state'], 'paused') self.assertEqual(config['countdown_state'], 'paused')
mock_update_projector_overlay.assert_called_with('projector_countdown')
@patch('openslides.agenda.models.update_projector_overlay') def test_begin_speach_not_coupled_with_countdown(self):
def test_begin_speach_not_coupled_with_countdown(self, mock_update_projector_overlay):
config['agenda_couple_countdown_and_speakers'] = False config['agenda_couple_countdown_and_speakers'] = False
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1) speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
@ -98,7 +93,6 @@ class ListOfSpeakerModelTests(TestCase):
config['countdown_state'] = 'active' config['countdown_state'] = 'active'
speaker1_item1.end_speach() speaker1_item1.end_speach()
self.assertEqual(config['countdown_state'], 'active') self.assertEqual(config['countdown_state'], 'active')
self.assertFalse(mock_update_projector_overlay.called)
class SpeakerViewTestCase(TestCase): class SpeakerViewTestCase(TestCase):

View File

@ -5,58 +5,6 @@ from openslides.utils.test import TestCase
class ApiFunctions(TestCase): class ApiFunctions(TestCase):
@patch('openslides.projector.api.get_projector_content')
@patch('openslides.projector.api.ProjectorSocketHandler')
def test_update_projector(self, mock_ProjectorSocketHandler,
mock_get_projector_content):
mock_get_projector_content.return_value = 'mock_string'
projector_api.update_projector()
mock_ProjectorSocketHandler.send_updates.assert_called_with(
{'content': 'mock_string'})
@patch('openslides.projector.api.get_overlays')
@patch('openslides.projector.api.ProjectorSocketHandler')
def test_update_projector_overlay(self, mock_ProjectorSocketHandler,
mock_get_overlays):
mock_overlay = MagicMock()
mock_overlay.name = 'mock_overlay_name'
mock_overlay.get_projector_html.return_value = 'mock_html_code'
mock_overlay.get_javascript.return_value = 'mock_javascript'
mock_get_overlays.return_value = {'mock_overlay': mock_overlay}
# Test with active overlay
mock_overlay.is_active.return_value = False
projector_api.update_projector_overlay(None)
mock_ProjectorSocketHandler.send_updates.assert_called_with(
{'overlays': {'mock_overlay_name': None}})
# Test with active overlay
mock_overlay.is_active.return_value = True
projector_api.update_projector_overlay(None)
expected_data = {'overlays': {'mock_overlay_name': {
'html': 'mock_html_code',
'javascript': 'mock_javascript'}}}
mock_ProjectorSocketHandler.send_updates.assert_called_with(expected_data)
# Test with overlay name as argument
projector_api.update_projector_overlay('mock_overlay')
mock_ProjectorSocketHandler.send_updates.assert_called_with(expected_data)
# Test with overlay object as argument
projector_api.update_projector_overlay(mock_overlay)
mock_ProjectorSocketHandler.send_updates.assert_called_with(expected_data)
@patch('openslides.projector.api.config')
@patch('openslides.projector.api.ProjectorSocketHandler')
def test_call_on_projector(self, mock_ProjectorSocketHandler, mock_config):
mock_config.__getitem__.return_value = {}
data = {'some_call': 'argument'}
projector_api.call_on_projector(data)
mock_ProjectorSocketHandler.send_updates.assert_called_with(
{'calls': data})
mock_config.__getitem__.assert_called_with('projector_js_cache')
mock_config.__setitem__.assert_called_with('projector_js_cache', data)
@patch('openslides.projector.api.default_slide') @patch('openslides.projector.api.default_slide')
def test_get_projector_content(self, mock_default_slide): def test_get_projector_content(self, mock_default_slide):
mock_slide = MagicMock() mock_slide = MagicMock()
@ -156,17 +104,13 @@ class ApiFunctions(TestCase):
mock_SlideModel.objects.get.side_effect = Exception mock_SlideModel.objects.get.side_effect = Exception
self.assertRaises(projector_api.SlideError, used_args[1], pk=1) self.assertRaises(projector_api.SlideError, used_args[1], pk=1)
@patch('openslides.projector.api.update_projector_overlay') def test_set_active_slide(self):
@patch('openslides.projector.api.update_projector')
def test_set_active_slide(self, mock_update_projector, mock_update_projector_overlay):
mock_config = {} mock_config = {}
with patch('openslides.projector.api.config', mock_config): with patch('openslides.projector.api.config', mock_config):
projector_api.set_active_slide('callback_name', some='kwargs') projector_api.set_active_slide('callback_name', some='kwargs')
self.assertEqual(mock_config, self.assertEqual(mock_config,
{'projector_active_slide': {'callback': 'callback_name', {'projector_active_slide': {'callback': 'callback_name',
'some': 'kwargs'}}) 'some': 'kwargs'}})
mock_update_projector.assert_called_with()
mock_update_projector_overlay.assert_called_with(None)
def test_get_active_slide(self): def test_get_active_slide(self):
mock_config = {'projector_active_slide': 'value'} mock_config = {'projector_active_slide': 'value'}

View File

@ -41,10 +41,9 @@ class ProjectorViewTest(TestCase):
class ActivateViewTest(TestCase): class ActivateViewTest(TestCase):
rf = RequestFactory() rf = RequestFactory()
@patch('openslides.projector.views.call_on_projector')
@patch('openslides.projector.views.config') @patch('openslides.projector.views.config')
@patch('openslides.projector.views.set_active_slide') @patch('openslides.projector.views.set_active_slide')
def test_get(self, mock_set_active_slide, mock_config, mock_call_on_projector): def test_get(self, mock_set_active_slide, mock_config):
view = views.ActivateView() view = views.ActivateView()
view.request = self.rf.get('/?some_key=some_value') view.request = self.rf.get('/?some_key=some_value')
@ -54,7 +53,6 @@ class ActivateViewTest(TestCase):
**{'some_key': 'some_value'}) **{'some_key': 'some_value'})
mock_config.get_default.assert_has_calls([]) mock_config.get_default.assert_has_calls([])
self.assertEqual(mock_config.__setitem__.call_count, 0) self.assertEqual(mock_config.__setitem__.call_count, 0)
self.assertTrue(mock_call_on_projector.called)
class ProjectorControllViewTest(TestCase): class ProjectorControllViewTest(TestCase):

View File

@ -7,7 +7,6 @@ from django.core.exceptions import ImproperlyConfigured
from openslides.__main__ import ( from openslides.__main__ import (
add_general_arguments, add_general_arguments,
django_command_line_utility, django_command_line_utility,
runserver,
start, start,
syncdb) syncdb)
from openslides.config.api import config from openslides.config.api import config
@ -117,15 +116,6 @@ class TestOtherFunctions(TestCase):
self.assertTrue(mock_syncdb.called) self.assertTrue(mock_syncdb.called)
mock_runserver.assert_called_with(None, mock_args) mock_runserver.assert_called_with(None, mock_args)
@patch('openslides.__main__.get_port')
@patch('openslides.utils.tornado_webserver.run_tornado')
@patch('openslides.__main__.start_browser')
def test_runserver(self, mock_start_browser, mock_run_tornado, mock_get_port):
mock_get_port.return_value = 8000
mock_args = MagicMock()
runserver(settings=None, args=mock_args)
self.assertTrue(mock_run_tornado.called)
@patch('openslides.__main__.os.path.exists') @patch('openslides.__main__.os.path.exists')
@patch('openslides.__main__.os.makedirs') @patch('openslides.__main__.os.makedirs')
@patch('openslides.__main__.execute_from_command_line') @patch('openslides.__main__.execute_from_command_line')