Merge pull request #1407 from ostcar/super_signal
Remove update_projector and add inform_changed_data.
This commit is contained in:
commit
2b744942e7
@ -233,7 +233,8 @@ def runserver(settings, args):
|
||||
start_browser(browser_url)
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
|
@ -12,17 +12,17 @@ from mptt.models import MPTTModel, TreeForeignKey
|
||||
|
||||
from openslides.config.api import config
|
||||
from openslides.core.models import Tag
|
||||
from openslides.projector.api import (get_active_slide, reset_countdown,
|
||||
start_countdown, stop_countdown,
|
||||
update_projector, update_projector_overlay)
|
||||
from openslides.projector.api import (reset_countdown,
|
||||
start_countdown, stop_countdown)
|
||||
from openslides.projector.models import SlideMixin
|
||||
from openslides.utils.exceptions import OpenSlidesError
|
||||
from openslides.utils.models import AbsoluteUrlMixin
|
||||
from openslides.utils.rest_api import RESTModelMixin
|
||||
from openslides.utils.utils import to_roman
|
||||
from openslides.users.models import User
|
||||
|
||||
|
||||
class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
||||
class Item(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
||||
"""
|
||||
An Agenda Item
|
||||
|
||||
@ -122,11 +122,6 @@ class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
||||
class MPTTMeta:
|
||||
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):
|
||||
"""
|
||||
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.'))
|
||||
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.'))
|
||||
return super(Item, self).clean()
|
||||
return super().clean()
|
||||
|
||||
def __str__(self):
|
||||
return self.get_title()
|
||||
@ -153,14 +148,14 @@ class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
||||
elif link == 'delete':
|
||||
url = reverse('item_delete', args=[str(self.id)])
|
||||
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':
|
||||
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
|
||||
self.content_object and isinstance(self.content_object, SlideMixin)):
|
||||
url = self.content_object.get_absolute_url(link)
|
||||
else:
|
||||
url = super(Item, self).get_absolute_url(link)
|
||||
url = super().get_absolute_url(link)
|
||||
return url
|
||||
|
||||
def get_title(self):
|
||||
@ -359,7 +354,7 @@ class SpeakerManager(models.Manager):
|
||||
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.
|
||||
"""
|
||||
@ -396,14 +391,6 @@ class Speaker(AbsoluteUrlMixin, models.Model):
|
||||
('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):
|
||||
return str(self.user)
|
||||
|
||||
@ -417,17 +404,6 @@ class Speaker(AbsoluteUrlMixin, models.Model):
|
||||
url = super(Speaker, self).get_absolute_url(link)
|
||||
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):
|
||||
"""
|
||||
Let the user speak.
|
||||
@ -448,10 +424,6 @@ class Speaker(AbsoluteUrlMixin, models.Model):
|
||||
if config['agenda_couple_countdown_and_speakers']:
|
||||
reset_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):
|
||||
"""
|
||||
@ -462,7 +434,9 @@ class Speaker(AbsoluteUrlMixin, models.Model):
|
||||
# stop countdown
|
||||
if config['agenda_couple_countdown_and_speakers']:
|
||||
stop_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 get_root_rest_element(self):
|
||||
"""
|
||||
Returns the item to this instance, which is the root rest element.
|
||||
"""
|
||||
return self.item
|
||||
|
@ -21,8 +21,7 @@ from openslides.projector.api import (
|
||||
get_active_object,
|
||||
get_active_slide,
|
||||
get_projector_overlays_js,
|
||||
get_overlays,
|
||||
update_projector)
|
||||
get_overlays)
|
||||
from openslides.utils import rest_api
|
||||
from openslides.utils.exceptions import OpenSlidesError
|
||||
from openslides.utils.pdf import stylesheet
|
||||
@ -159,10 +158,6 @@ class Overview(TemplateView):
|
||||
# TODO: assure, that it is a valid tree
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
@ -11,15 +11,14 @@ from openslides.config.api import config
|
||||
from openslides.poll.models import (BaseOption, BasePoll, BaseVote,
|
||||
CollectDefaultVotesMixin,
|
||||
PublishPollMixin)
|
||||
from openslides.projector.api import get_active_object, update_projector
|
||||
from openslides.projector.models import RelatedModelMixin, SlideMixin
|
||||
from openslides.projector.models import SlideMixin
|
||||
from openslides.utils.exceptions import OpenSlidesError
|
||||
from openslides.utils.models import AbsoluteUrlMixin
|
||||
from openslides.utils.utils import html_strong
|
||||
from openslides.users.models import User
|
||||
|
||||
|
||||
class AssignmentCandidate(RelatedModelMixin, models.Model):
|
||||
class AssignmentCandidate(models.Model):
|
||||
"""
|
||||
Many2Many table between an assignment and the candidates.
|
||||
"""
|
||||
@ -34,12 +33,6 @@ class AssignmentCandidate(RelatedModelMixin, models.Model):
|
||||
def __str__(self):
|
||||
return str(self.person)
|
||||
|
||||
def get_related_model(self):
|
||||
"""
|
||||
Returns the assignment
|
||||
"""
|
||||
return self.assignment
|
||||
|
||||
|
||||
class Assignment(SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||
slide_callback_name = 'assignment'
|
||||
@ -190,11 +183,6 @@ class Assignment(SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||
candidate = self.assignment_candidates.get(person=person)
|
||||
candidate.elected = value
|
||||
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):
|
||||
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)
|
||||
for item in items:
|
||||
someone_added = None
|
||||
for candidate in self.candidates:
|
||||
try:
|
||||
someone_added = Speaker.objects.add(candidate, item)
|
||||
Speaker.objects.add(candidate, item)
|
||||
except OpenSlidesError:
|
||||
# The Speaker is already on the list. Do nothing.
|
||||
# TODO: Find a smart way not to catch the error concerning AnonymousUser.
|
||||
pass
|
||||
if someone_added is not None:
|
||||
someone_added.check_and_update_projector()
|
||||
|
||||
return poll
|
||||
|
||||
@ -290,7 +275,7 @@ class AssignmentOption(BaseOption):
|
||||
return str(self.candidate)
|
||||
|
||||
|
||||
class AssignmentPoll(SlideMixin, RelatedModelMixin, CollectDefaultVotesMixin,
|
||||
class AssignmentPoll(SlideMixin, CollectDefaultVotesMixin,
|
||||
PublishPollMixin, AbsoluteUrlMixin, BasePoll):
|
||||
|
||||
slide_callback_name = 'assignmentpoll'
|
||||
@ -323,9 +308,6 @@ class AssignmentPoll(SlideMixin, RelatedModelMixin, CollectDefaultVotesMixin,
|
||||
def get_assignment(self):
|
||||
return self.assignment
|
||||
|
||||
def get_related_model(self):
|
||||
return self.assignment
|
||||
|
||||
def get_vote_values(self):
|
||||
if self.yesnoabstain:
|
||||
return [ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')]
|
||||
|
@ -11,8 +11,10 @@ class CoreAppConfig(AppConfig):
|
||||
from . import main_menu, widgets # noqa
|
||||
|
||||
# Import all required stuff.
|
||||
from django.db.models import signals
|
||||
from openslides.config.signals import config_signal
|
||||
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 .signals import setup_general_config
|
||||
from .views import CustomSlideViewSet
|
||||
@ -26,3 +28,8 @@ class CoreAppConfig(AppConfig):
|
||||
|
||||
# Register viewset.
|
||||
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
|
||||
|
@ -4,7 +4,6 @@ from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||
|
||||
from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
|
||||
from openslides.projector.api import update_projector
|
||||
|
||||
post_database_setup = Signal()
|
||||
|
||||
@ -108,8 +107,7 @@ def setup_general_config(sender, **kwargs):
|
||||
widget=forms.TextInput(),
|
||||
label=ugettext_lazy('Title'),
|
||||
help_text=ugettext_lazy('Also used for the default welcome slide.'),
|
||||
required=False),
|
||||
on_change=update_projector)
|
||||
required=False))
|
||||
|
||||
welcome_text = ConfigVariable(
|
||||
name='welcome_text',
|
||||
|
@ -173,3 +173,8 @@ CKEDITOR_CONFIGS = {
|
||||
'default': CKEDITOR_DEFAULT_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
|
||||
|
@ -2,7 +2,6 @@ from django.http import HttpResponse
|
||||
|
||||
from openslides.config.api import config
|
||||
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,
|
||||
UpdateView)
|
||||
|
||||
@ -119,8 +118,6 @@ class PdfNavBaseView(AjaxView):
|
||||
Tell connected clients to load an other pdf page.
|
||||
"""
|
||||
config['projector_active_slide'] = active_slide
|
||||
ProjectorSocketHandler.send_updates(
|
||||
{'calls': {'load_pdf_page': active_slide['page_num']}})
|
||||
|
||||
|
||||
class PdfNextView(PdfNavBaseView):
|
||||
@ -199,8 +196,4 @@ class PdfToggleFullscreenView(RedirectView):
|
||||
|
||||
def get_ajax_context(self, *args, **kwargs):
|
||||
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']}
|
||||
|
@ -9,7 +9,7 @@ from openslides.config.api import config
|
||||
from openslides.core.models import Tag
|
||||
from openslides.mediafile.models import Mediafile
|
||||
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 openslides.utils.models import AbsoluteUrlMixin
|
||||
from openslides.users.models import User
|
||||
@ -612,7 +612,7 @@ class MotionVersion(AbsoluteUrlMixin, models.Model):
|
||||
return self.active_version.exists()
|
||||
|
||||
|
||||
class MotionSubmitter(RelatedModelMixin, models.Model):
|
||||
class MotionSubmitter(models.Model):
|
||||
"""Save the submitter of a Motion."""
|
||||
|
||||
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 str(self.person)
|
||||
|
||||
def get_related_model(self):
|
||||
return self.motion
|
||||
|
||||
|
||||
class MotionSupporter(models.Model):
|
||||
"""Save the submitter of a Motion."""
|
||||
@ -723,7 +720,7 @@ class MotionOption(BaseOption):
|
||||
"""The VoteClass, to witch this Class links."""
|
||||
|
||||
|
||||
class MotionPoll(SlideMixin, RelatedModelMixin, CollectDefaultVotesMixin,
|
||||
class MotionPoll(SlideMixin, CollectDefaultVotesMixin,
|
||||
AbsoluteUrlMixin, BasePoll):
|
||||
"""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()
|
||||
self.get_option_class()(poll=self).save()
|
||||
|
||||
def get_related_model(self):
|
||||
return self.motion
|
||||
|
||||
def get_percent_base_choice(self):
|
||||
return config['motion_poll_100_percent_base']
|
||||
|
||||
|
@ -10,7 +10,6 @@ from django.shortcuts import get_object_or_404
|
||||
from openslides.agenda.views import CreateRelatedAgendaItemView as _CreateRelatedAgendaItemView
|
||||
from openslides.config.api import config
|
||||
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.views import (CreateView, CSVImportView, DeleteView, DetailView,
|
||||
ListView, PDFView, QuestionView,
|
||||
@ -142,14 +141,6 @@ class MotionEditMixin(object):
|
||||
self.object.tags.clear()
|
||||
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())
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
|
@ -4,7 +4,6 @@ from time import time
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from openslides.config.api import config
|
||||
from openslides.utils.tornado_webserver import ProjectorSocketHandler
|
||||
from openslides.utils.exceptions import OpenSlidesError
|
||||
|
||||
from .signals import projector_overlays
|
||||
@ -28,41 +27,6 @@ class SlideError(OpenSlidesError):
|
||||
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):
|
||||
"""
|
||||
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
|
||||
as key and the argument for it as value.
|
||||
"""
|
||||
# TODO: remove this function
|
||||
projector_js_cache = config['projector_js_cache']
|
||||
projector_js_cache.update(calls)
|
||||
config['projector_js_cache'] = projector_js_cache
|
||||
ProjectorSocketHandler.send_updates({'calls': calls})
|
||||
|
||||
|
||||
def get_projector_content(slide_dict=None):
|
||||
@ -181,8 +145,6 @@ def set_active_slide(callback, **kwargs):
|
||||
"""
|
||||
kwargs.update(callback=callback)
|
||||
config['projector_active_slide'] = kwargs
|
||||
update_projector()
|
||||
update_projector_overlay(None)
|
||||
|
||||
|
||||
def get_active_slide():
|
||||
|
@ -1,42 +1,8 @@
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
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):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
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'):
|
||||
"""
|
||||
Return the url to activate the slide, if link == 'projector'.
|
||||
|
@ -1,12 +1,10 @@
|
||||
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 .api import (call_on_projector, get_active_slide,
|
||||
get_overlays, get_projector_content,
|
||||
get_projector_overlays_js, reset_countdown, set_active_slide,
|
||||
start_countdown, stop_countdown, update_projector_overlay)
|
||||
start_countdown, stop_countdown)
|
||||
|
||||
|
||||
class ProjectorView(TemplateView):
|
||||
@ -53,14 +51,13 @@ class ActivateView(RedirectView):
|
||||
# we dont have to use set_active_slide, because is causes a content
|
||||
# reload.
|
||||
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
|
||||
ProjectorSocketHandler.send_updates(
|
||||
{'calls': {'load_pdf': {'url': url, 'page_num': kwargs['page_num']}}})
|
||||
# ProjectorSocketHandler.send_updates(
|
||||
# {'calls': {'load_pdf': {'url': url, 'page_num': kwargs['page_num']}}})
|
||||
else:
|
||||
set_active_slide(kwargs['callback'], **dict(request.GET.items()))
|
||||
call_on_projector({'scroll': config['projector_scroll'],
|
||||
'scale': config['projector_scale']})
|
||||
|
||||
|
||||
class ProjectorControllView(RedirectView):
|
||||
@ -121,7 +118,7 @@ class CountdownControllView(RedirectView):
|
||||
pass
|
||||
else:
|
||||
reset_countdown()
|
||||
update_projector_overlay('projector_countdown')
|
||||
# TODO: send signal to update data
|
||||
|
||||
def get_ajax_context(self, **kwargs):
|
||||
return {
|
||||
@ -143,7 +140,7 @@ class OverlayMessageView(RedirectView):
|
||||
config['projector_message'] = request.POST['message_text']
|
||||
elif 'message-clean' in request.POST:
|
||||
config['projector_message'] = ''
|
||||
update_projector_overlay('projector_message')
|
||||
# TODO: update data
|
||||
|
||||
def get_ajax_context(self, **kwargs):
|
||||
return {
|
||||
@ -165,12 +162,12 @@ class ActivateOverlay(RedirectView):
|
||||
if kwargs['activate']:
|
||||
if not overlay.is_active():
|
||||
overlay.set_active(True)
|
||||
update_projector_overlay(overlay)
|
||||
# Push new overlay to projector, somehow...
|
||||
self.active = True
|
||||
else:
|
||||
if overlay.is_active():
|
||||
overlay.set_active(False)
|
||||
update_projector_overlay(overlay)
|
||||
# Push new overlay to projector, somehow...
|
||||
self.active = False
|
||||
|
||||
def get_ajax_context(self, **kwargs):
|
||||
|
@ -18,7 +18,11 @@ from tornado.wsgi import WSGIContainer
|
||||
|
||||
|
||||
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):
|
||||
"""Overwrite some attributes."""
|
||||
@ -47,25 +51,31 @@ class DjangoStaticFileHandler(StaticFileHandler):
|
||||
return absolute_path
|
||||
|
||||
|
||||
class ProjectorSocketHandler(SockJSConnection):
|
||||
class OpenSlidesSockJSConnection(SockJSConnection):
|
||||
"""
|
||||
Handels the websocket for the projector.
|
||||
Sockjs connections for OpenSlides.
|
||||
"""
|
||||
waiters = set()
|
||||
|
||||
def on_open(self, info):
|
||||
ProjectorSocketHandler.waiters.add(self)
|
||||
OpenSlidesSockJSConnection.waiters.add(self)
|
||||
|
||||
def on_close(self):
|
||||
ProjectorSocketHandler.waiters.remove(self)
|
||||
OpenSlidesSockJSConnection.waiters.remove(self)
|
||||
|
||||
@classmethod
|
||||
def send_updates(cls, data):
|
||||
# TODO: use a bluk send
|
||||
for waiter in cls.waiters:
|
||||
waiter.send(data)
|
||||
|
||||
|
||||
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
|
||||
parse_command_line(args=[])
|
||||
|
||||
@ -81,9 +91,9 @@ def run_tornado(addr, port):
|
||||
app = WSGIContainer(get_wsgi_application())
|
||||
|
||||
# Collect urls
|
||||
projectpr_socket_js_router = SockJSRouter(ProjectorSocketHandler, '/projector/socket')
|
||||
from openslides.core.chatbox import ChatboxSocketHandler
|
||||
chatbox_socket_js_router = SockJSRouter(ChatboxSocketHandler, '/core/chatbox')
|
||||
sock_js_router = SockJSRouter(OpenSlidesSockJSConnection, '/sockjs')
|
||||
other_urls = [
|
||||
(r"%s(.*)" % settings.STATIC_URL, DjangoStaticFileHandler),
|
||||
(r'%s(.*)' % settings.MEDIA_URL, StaticFileHandler, {'path': settings.MEDIA_ROOT}),
|
||||
@ -91,7 +101,36 @@ def run_tornado(addr, port):
|
||||
|
||||
# Start the application
|
||||
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.listen(port=port, address=addr)
|
||||
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)
|
@ -1,3 +1,28 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from rest_framework import permissions, routers, viewsets # noqa
|
||||
|
||||
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)])
|
||||
|
@ -72,23 +72,18 @@ class ListOfSpeakerModelTests(TestCase):
|
||||
self.assertIsNotNone(Speaker.objects.get(user=self.speaker1, item=self.item1).end_time)
|
||||
self.assertIsNotNone(speaker2_item1.begin_time)
|
||||
|
||||
@patch('openslides.agenda.models.update_projector_overlay')
|
||||
def test_speach_coupled_with_countdown(self, mock_update_projector_overlay):
|
||||
def test_speach_coupled_with_countdown(self):
|
||||
config['agenda_couple_countdown_and_speakers'] = True
|
||||
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
|
||||
self.item1.is_active_slide = MagicMock(return_value=True)
|
||||
|
||||
speaker1_item1.begin_speach()
|
||||
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()
|
||||
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, mock_update_projector_overlay):
|
||||
def test_begin_speach_not_coupled_with_countdown(self):
|
||||
config['agenda_couple_countdown_and_speakers'] = False
|
||||
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
|
||||
|
||||
@ -98,7 +93,6 @@ class ListOfSpeakerModelTests(TestCase):
|
||||
config['countdown_state'] = 'active'
|
||||
speaker1_item1.end_speach()
|
||||
self.assertEqual(config['countdown_state'], 'active')
|
||||
self.assertFalse(mock_update_projector_overlay.called)
|
||||
|
||||
|
||||
class SpeakerViewTestCase(TestCase):
|
||||
|
@ -5,58 +5,6 @@ from openslides.utils.test import 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')
|
||||
def test_get_projector_content(self, mock_default_slide):
|
||||
mock_slide = MagicMock()
|
||||
@ -156,17 +104,13 @@ class ApiFunctions(TestCase):
|
||||
mock_SlideModel.objects.get.side_effect = Exception
|
||||
self.assertRaises(projector_api.SlideError, used_args[1], pk=1)
|
||||
|
||||
@patch('openslides.projector.api.update_projector_overlay')
|
||||
@patch('openslides.projector.api.update_projector')
|
||||
def test_set_active_slide(self, mock_update_projector, mock_update_projector_overlay):
|
||||
def test_set_active_slide(self):
|
||||
mock_config = {}
|
||||
with patch('openslides.projector.api.config', mock_config):
|
||||
projector_api.set_active_slide('callback_name', some='kwargs')
|
||||
self.assertEqual(mock_config,
|
||||
{'projector_active_slide': {'callback': 'callback_name',
|
||||
'some': 'kwargs'}})
|
||||
mock_update_projector.assert_called_with()
|
||||
mock_update_projector_overlay.assert_called_with(None)
|
||||
|
||||
def test_get_active_slide(self):
|
||||
mock_config = {'projector_active_slide': 'value'}
|
||||
|
@ -41,10 +41,9 @@ class ProjectorViewTest(TestCase):
|
||||
class ActivateViewTest(TestCase):
|
||||
rf = RequestFactory()
|
||||
|
||||
@patch('openslides.projector.views.call_on_projector')
|
||||
@patch('openslides.projector.views.config')
|
||||
@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.request = self.rf.get('/?some_key=some_value')
|
||||
|
||||
@ -54,7 +53,6 @@ class ActivateViewTest(TestCase):
|
||||
**{'some_key': 'some_value'})
|
||||
mock_config.get_default.assert_has_calls([])
|
||||
self.assertEqual(mock_config.__setitem__.call_count, 0)
|
||||
self.assertTrue(mock_call_on_projector.called)
|
||||
|
||||
|
||||
class ProjectorControllViewTest(TestCase):
|
||||
|
@ -7,7 +7,6 @@ from django.core.exceptions import ImproperlyConfigured
|
||||
from openslides.__main__ import (
|
||||
add_general_arguments,
|
||||
django_command_line_utility,
|
||||
runserver,
|
||||
start,
|
||||
syncdb)
|
||||
from openslides.config.api import config
|
||||
@ -117,15 +116,6 @@ class TestOtherFunctions(TestCase):
|
||||
self.assertTrue(mock_syncdb.called)
|
||||
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.makedirs')
|
||||
@patch('openslides.__main__.execute_from_command_line')
|
||||
|
Loading…
Reference in New Issue
Block a user