Rework of the projector with websocket
* Set a static projector title * absolute_urls for the activate links * update the projector when a slide changes (in save()) * insert the absolute_url template filter * Preview to slides * renamed is_active to is_active_slide * The SlideMixin has to come before the PersonMixin * Update list of speakers * Render Countdown via JS * Reconnect projector after connection lost * Overlays can allways be active and do not appear in the widget * Rewrote the clock as overlay
This commit is contained in:
parent
d2f5302e28
commit
ecf5248962
@ -4,7 +4,7 @@
|
|||||||
<ul style="line-height: 180%">
|
<ul style="line-height: 180%">
|
||||||
{% trans 'I am on the list of speakers of the following items:' %}
|
{% trans 'I am on the list of speakers of the following items:' %}
|
||||||
{% for item in items %}
|
{% for item in items %}
|
||||||
<li><a href="{% model_url item %}">{{ item }}</a></li>
|
<li><a href="{{ item|absolute_url }}">{{ item }}</a></li>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<li><i>{% trans 'None' %}</i></li>
|
<li><i>{% trans 'None' %}</i></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -15,7 +15,7 @@
|
|||||||
{% trans 'I submitted the following motions:' %}
|
{% trans 'I submitted the following motions:' %}
|
||||||
{% for motion in submitted_motions %}
|
{% for motion in submitted_motions %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% model_url motion %}">
|
<a href="{{ motion|absolute_url }}">
|
||||||
{{ motion.identifier|add:' | '|default:'' }}
|
{{ motion.identifier|add:' | '|default:'' }}
|
||||||
{{ motion }}
|
{{ motion }}
|
||||||
</a>
|
</a>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
{% trans 'I support the following motions:' %}
|
{% trans 'I support the following motions:' %}
|
||||||
{% for motion in supported_motions %}
|
{% for motion in supported_motions %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% model_url motion %}">
|
<a href="{{ motion|absolute_url }}">
|
||||||
{% if motion.identifier %}
|
{% if motion.identifier %}
|
||||||
[# {{ motion.identifier }}]
|
[# {{ motion.identifier }}]
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -50,7 +50,7 @@
|
|||||||
<ul style="line-height: 180%">
|
<ul style="line-height: 180%">
|
||||||
{% trans 'I am candidate for the following elections:' %}
|
{% trans 'I am candidate for the following elections:' %}
|
||||||
{% for assignment in assignments %}
|
{% for assignment in assignments %}
|
||||||
<li><a href="{% model_url assignment %}">{{ assignment }}</a></li>
|
<li><a href="{{ assignment|absolute_url }}">{{ assignment }}</a></li>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<li><i>{% trans 'None' %}</i></li>
|
<li><i>{% trans 'None' %}</i></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -23,19 +23,19 @@ from mptt.models import MPTTModel, TreeForeignKey
|
|||||||
|
|
||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.projector.projector import SlideMixin
|
|
||||||
from openslides.projector.api import (
|
|
||||||
register_slidemodel, get_slide_from_sid, register_slidefunc)
|
|
||||||
from openslides.utils.person.models import PersonField
|
from openslides.utils.person.models import PersonField
|
||||||
|
from openslides.projector.api import (
|
||||||
|
update_projector, get_active_slide, update_projector_overlay)
|
||||||
|
from openslides.projector.models import SlideMixin
|
||||||
|
|
||||||
|
|
||||||
class Item(MPTTModel, SlideMixin):
|
class Item(SlideMixin, MPTTModel):
|
||||||
"""
|
"""
|
||||||
An Agenda Item
|
An Agenda Item
|
||||||
|
|
||||||
MPTT-model. See http://django-mptt.github.com/django-mptt/
|
MPTT-model. See http://django-mptt.github.com/django-mptt/
|
||||||
"""
|
"""
|
||||||
prefix = 'item'
|
slide_callback_name = 'agenda'
|
||||||
|
|
||||||
AGENDA_ITEM = 1
|
AGENDA_ITEM = 1
|
||||||
ORGANIZATIONAL_ITEM = 2
|
ORGANIZATIONAL_ITEM = 2
|
||||||
@ -98,6 +98,7 @@ class Item(MPTTModel, SlideMixin):
|
|||||||
"""
|
"""
|
||||||
Field for generic relation to a related object. Id of the object.
|
Field for generic relation to a related object. Id of the object.
|
||||||
"""
|
"""
|
||||||
|
# TODO: rename it to object_pk
|
||||||
|
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = generic.GenericForeignKey()
|
||||||
"""
|
"""
|
||||||
@ -119,6 +120,14 @@ class Item(MPTTModel, SlideMixin):
|
|||||||
class MPTTMeta:
|
class MPTTMeta:
|
||||||
order_insertion_by = ['weight']
|
order_insertion_by = ['weight']
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super(Item, self).save(*args, **kwargs)
|
||||||
|
active_slide = get_active_slide()
|
||||||
|
active_slide_pk = active_slide.get('pk', None)
|
||||||
|
if (active_slide['callback'] == 'agenda' and
|
||||||
|
unicode(self.parent_id) == unicode(active_slide_pk)):
|
||||||
|
update_projector()
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.get_title()
|
return self.get_title()
|
||||||
|
|
||||||
@ -134,6 +143,11 @@ class Item(MPTTModel, SlideMixin):
|
|||||||
return reverse('item_edit', args=[str(self.id)])
|
return reverse('item_edit', args=[str(self.id)])
|
||||||
if link == 'delete':
|
if link == 'delete':
|
||||||
return reverse('item_delete', args=[str(self.id)])
|
return reverse('item_delete', args=[str(self.id)])
|
||||||
|
if link == 'projector_list_of_speakers':
|
||||||
|
return '%s&type=list_of_speakers' % super(Item, self).get_absolute_url('projector')
|
||||||
|
if link == 'projector_summary':
|
||||||
|
return '%s&type=summary' % super(Item, self).get_absolute_url('projector')
|
||||||
|
return super(Item, self).get_absolute_url(link)
|
||||||
|
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
"""
|
"""
|
||||||
@ -157,41 +171,6 @@ class Item(MPTTModel, SlideMixin):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise NotImplementedError('You have to provide a get_agenda_title_supplement method on your related model.')
|
raise NotImplementedError('You have to provide a get_agenda_title_supplement method on your related model.')
|
||||||
|
|
||||||
def slide(self):
|
|
||||||
"""
|
|
||||||
Return a map with all data for the slide.
|
|
||||||
|
|
||||||
There are four cases:
|
|
||||||
* summary slide
|
|
||||||
* list of speakers
|
|
||||||
* related item, i. e. the slide of the related object
|
|
||||||
* normal slide of the item
|
|
||||||
|
|
||||||
The method returns only one of them according to the config value
|
|
||||||
'presentation_argument' and the attribute 'content_object'.
|
|
||||||
"""
|
|
||||||
if config['presentation_argument'] == 'summary':
|
|
||||||
data = {'title': self.get_title(),
|
|
||||||
'items': self.get_children().filter(type__exact=Item.AGENDA_ITEM),
|
|
||||||
'template': 'projector/AgendaSummary.html'}
|
|
||||||
|
|
||||||
elif config['presentation_argument'] == 'show_list_of_speakers':
|
|
||||||
list_of_speakers = self.get_list_of_speakers(
|
|
||||||
old_speakers_count=config['agenda_show_last_speakers'])
|
|
||||||
data = {'title': self.get_title(),
|
|
||||||
'item': self,
|
|
||||||
'template': 'projector/agenda_list_of_speaker.html',
|
|
||||||
'list_of_speakers': list_of_speakers}
|
|
||||||
elif self.content_object:
|
|
||||||
data = self.content_object.slide()
|
|
||||||
|
|
||||||
else:
|
|
||||||
data = {'item': self,
|
|
||||||
'title': self.get_title(),
|
|
||||||
'template': 'projector/AgendaText.html'}
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def set_closed(self, closed=True):
|
def set_closed(self, closed=True):
|
||||||
"""
|
"""
|
||||||
Changes the closed-status of the item.
|
Changes the closed-status of the item.
|
||||||
@ -351,6 +330,14 @@ class Speaker(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 __unicode__(self):
|
def __unicode__(self):
|
||||||
return unicode(self.person)
|
return unicode(self.person)
|
||||||
|
|
||||||
@ -361,6 +348,21 @@ class Speaker(models.Model):
|
|||||||
return reverse('agenda_speaker_delete',
|
return reverse('agenda_speaker_delete',
|
||||||
args=[self.item.pk, self.pk])
|
args=[self.item.pk, self.pk])
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
active_slide = get_active_slide()
|
||||||
|
active_slide_pk = active_slide.get('pk', None)
|
||||||
|
slide_type = active_slide.get('type', None)
|
||||||
|
if (active_slide['callback'] == 'agenda' and
|
||||||
|
unicode(self.item_id) == unicode(active_slide_pk)):
|
||||||
|
if slide_type == 'list_of_speakers':
|
||||||
|
update_projector()
|
||||||
|
elif slide_type is None:
|
||||||
|
update_projector_overlay('agenda_speaker')
|
||||||
|
|
||||||
def begin_speach(self):
|
def begin_speach(self):
|
||||||
"""
|
"""
|
||||||
Let the person speak.
|
Let the person speak.
|
||||||
|
@ -25,8 +25,7 @@ from openslides.config.api import config, ConfigVariable, ConfigPage
|
|||||||
|
|
||||||
from openslides.projector.signals import projector_overlays
|
from openslides.projector.signals import projector_overlays
|
||||||
from openslides.projector.projector import Overlay
|
from openslides.projector.projector import Overlay
|
||||||
from openslides.projector.api import (get_active_slide, get_slide_from_sid,
|
from openslides.projector.api import get_active_slide
|
||||||
clear_projector_cache)
|
|
||||||
|
|
||||||
from .models import Speaker, Item
|
from .models import Speaker, Item
|
||||||
|
|
||||||
@ -94,23 +93,27 @@ def agenda_list_of_speakers(sender, **kwargs):
|
|||||||
def get_projector_html():
|
def get_projector_html():
|
||||||
"""
|
"""
|
||||||
Returns an html-code to show on the projector.
|
Returns an html-code to show on the projector.
|
||||||
|
|
||||||
|
The overlay is only shown on agenda-items and not on the
|
||||||
|
list-of-speakers slide.
|
||||||
"""
|
"""
|
||||||
slide = get_slide_from_sid(get_active_slide(only_sid=True), element=True)
|
active_slide = get_active_slide()
|
||||||
if not isinstance(slide, Item):
|
slide_type = active_slide.get('type', None)
|
||||||
# Only show list of speakers overlay on agenda items
|
active_slide_pk = active_slide.get('pk', None)
|
||||||
|
if (active_slide['callback'] == 'agenda' and
|
||||||
|
slide_type != 'list_of_speakers' and
|
||||||
|
active_slide_pk is not None):
|
||||||
|
item = Item.objects.get(pk=active_slide_pk)
|
||||||
|
list_of_speakers = item.get_list_of_speakers(
|
||||||
|
old_speakers_count=config['agenda_show_last_speakers'],
|
||||||
|
coming_speakers_count=5)
|
||||||
|
context = {
|
||||||
|
'list_of_speakers': list_of_speakers,
|
||||||
|
'closed': item.speaker_list_closed,
|
||||||
|
}
|
||||||
|
return render_to_string('agenda/overlay_speaker_projector.html', context)
|
||||||
|
else:
|
||||||
return None
|
return None
|
||||||
if config['presentation_argument'] == 'show_list_of_speakers':
|
|
||||||
# Do not show list of speakers overlay on the list of speakers slide
|
|
||||||
return None
|
|
||||||
clear_projector_cache()
|
|
||||||
list_of_speakers = slide.get_list_of_speakers(
|
|
||||||
old_speakers_count=config['agenda_show_last_speakers'],
|
|
||||||
coming_speakers_count=5)
|
|
||||||
context = {
|
|
||||||
'list_of_speakers': list_of_speakers,
|
|
||||||
'closed': slide.speaker_list_closed,
|
|
||||||
}
|
|
||||||
return render_to_string('agenda/overlay_speaker_projector.html', context)
|
|
||||||
|
|
||||||
return Overlay(name, get_widget_html, get_projector_html)
|
return Overlay(name, get_widget_html, get_projector_html)
|
||||||
|
|
||||||
|
@ -10,20 +10,63 @@
|
|||||||
:license: GNU GPL, see LICENSE for more details.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy, ugettext as _
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
from openslides.projector.api import register_slidemodel, register_slidefunc
|
from openslides.config.api import config
|
||||||
|
from openslides.projector.api import register_slide, get_projector_content
|
||||||
|
|
||||||
from .models import Item
|
from .models import Item
|
||||||
|
|
||||||
|
|
||||||
def agenda_show():
|
def agenda_slide(**kwargs):
|
||||||
data = {}
|
"""
|
||||||
items = Item.objects.filter(parent=None, type__exact=Item.AGENDA_ITEM)
|
Return the html code for all slides of the agenda app.
|
||||||
data['title'] = _("Agenda")
|
|
||||||
data['items'] = items
|
|
||||||
data['template'] = 'projector/AgendaSummary.html'
|
|
||||||
return data
|
|
||||||
|
|
||||||
register_slidemodel(Item, control_template='agenda/control_item.html')
|
If no id is given, show a summary of all parent items.
|
||||||
register_slidefunc('agenda', agenda_show, weight=-1, name=ugettext_lazy('Agenda'))
|
|
||||||
|
If an id is given, show the item depending of the argument 'type'.
|
||||||
|
|
||||||
|
If 'type' is not set, show only the item.
|
||||||
|
|
||||||
|
If 'type' is 'summary', show a summary of all children of the item.
|
||||||
|
|
||||||
|
If 'type' is 'list_of_speakers', show the list of speakers for the item.
|
||||||
|
"""
|
||||||
|
item_pk = kwargs.get('pk', None)
|
||||||
|
slide_type = kwargs.get('type', None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
item = Item.objects.get(pk=item_pk)
|
||||||
|
except Item.DoesNotExist:
|
||||||
|
item = None
|
||||||
|
|
||||||
|
if slide_type == 'summary' or item is None:
|
||||||
|
context = {}
|
||||||
|
if item is None:
|
||||||
|
items = Item.objects.filter(parent=None, type__exact=Item.AGENDA_ITEM)
|
||||||
|
else:
|
||||||
|
items = item.get_children().filter(type__exact=Item.AGENDA_ITEM)
|
||||||
|
context['title'] = item.get_title()
|
||||||
|
context['items'] = items
|
||||||
|
return render_to_string('agenda/item_slide_summary.html', context)
|
||||||
|
|
||||||
|
elif slide_type == 'list_of_speakers':
|
||||||
|
list_of_speakers = item.get_list_of_speakers(
|
||||||
|
old_speakers_count=config['agenda_show_last_speakers'])
|
||||||
|
context = {'title': item.get_title(),
|
||||||
|
'item': item,
|
||||||
|
'list_of_speakers': list_of_speakers}
|
||||||
|
return render_to_string('agenda/item_slide_list_of_speaker.html', context)
|
||||||
|
|
||||||
|
elif item.content_object:
|
||||||
|
slide_dict = {
|
||||||
|
'callback': item.content_object.slide_callback_name,
|
||||||
|
'pk': item.content_object.pk}
|
||||||
|
return get_projector_content(slide_dict)
|
||||||
|
|
||||||
|
else:
|
||||||
|
context = {'item': item}
|
||||||
|
return render_to_string('agenda/item_slide.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
register_slide('agenda', agenda_slide)
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load tags %}
|
{% load tags %}
|
||||||
|
|
||||||
<div class="{% if node.active %}activeline{% endif %}">
|
<div class="{% if node.is_active_slide %}activeline{% endif %}">
|
||||||
{% if perms.agenda.can_manage_agenda or perms.projector.can_manage_projector %}
|
{% if perms.agenda.can_manage_agenda or perms.projector.can_manage_projector %}
|
||||||
<div class="manage">
|
<div class="manage">
|
||||||
<span style="width: 1px; white-space: nowrap;">
|
<span style="width: 1px; white-space: nowrap;">
|
||||||
{% if perms.projector.can_manage_projector %}
|
{% if perms.projector.can_manage_projector %}
|
||||||
<a href="{% url 'projector_activate_slide' node.sid %}"
|
<a href="{{ node|absolute_url:'projector' }}"
|
||||||
class="activate_link btn {% if node.active and not show_list and not summary %}btn-primary{% endif %} btn-mini"
|
class="activate_link btn {% if node.is_active_slide and active_type == 'text' %}btn-primary{% endif %} btn-mini"
|
||||||
rel="tooltip" data-original-title="{% trans 'Show agenda item' %}">
|
rel="tooltip" data-original-title="{% trans 'Show agenda item' %}">
|
||||||
<i class="icon-facetime-video {% if node.active and not show_list and not summary %}icon-white{% endif %}"></i>
|
<i class="icon-facetime-video {% if node.is_active_slide and active_type == 'text' %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'projector_activate_slide' node.sid 'show_list_of_speakers' %}"
|
<a href="{{ node|absolute_url:'projector_list_of_speakers' }}"
|
||||||
class="activate_link btn btn-mini {% if node.active and show_list %}btn-primary{% endif %}"
|
class="activate_link btn btn-mini {% if node.is_active_slide and active_type == 'list_of_speakers' %}btn-primary{% endif %}"
|
||||||
rel="tooltip" data-original-title="{% trans 'Show list of speakers' %}">
|
rel="tooltip" data-original-title="{% trans 'Show list of speakers' %}">
|
||||||
<i class="icon icon-bell {% if node.active and show_list %}icon-white{% endif %}"></i>
|
<i class="icon icon-bell {% if node.is_active_slide and active_type == 'list_of_speakers' %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.agenda.can_manage_agenda %}
|
{% if perms.agenda.can_manage_agenda %}
|
||||||
<a href="{% model_url node 'edit' %}" rel="tooltip" data-original-title="{% trans 'Edit' %}" class="btn btn-mini">
|
<a href="{{ node|absolute_url:'edit' }}" rel="tooltip" data-original-title="{% trans 'Edit' %}" class="btn btn-mini">
|
||||||
<i class="icon-pencil"></i>
|
<i class="icon-pencil"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% model_url node 'delete' %}" rel="tooltip" data-original-title="{% trans 'Delete' %}" class="btn btn-mini">
|
<a href="{{ node|absolute_url:'delete' }}" rel="tooltip" data-original-title="{% trans 'Delete' %}" class="btn btn-mini">
|
||||||
<i class="icon-remove"></i>
|
<i class="icon-remove"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% if node.closed %}{% url 'item_open' node.id %}{% else %}{% url 'item_close' node.id %}{% endif %}"
|
<a href="{% if node.closed %}{% url 'item_open' node.id %}{% else %}{% url 'item_close' node.id %}{% endif %}"
|
||||||
@ -30,10 +30,10 @@
|
|||||||
<i class="{% if node.closed %}icon-checked-new{% else %}icon-unchecked-new{% endif %}"></i>
|
<i class="{% if node.closed %}icon-checked-new{% else %}icon-unchecked-new{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if not node.is_leaf_node %}
|
{% if not node.is_leaf_node %}
|
||||||
<a href="{% url 'projector_activate_slide' node.sid 'summary' %}"
|
<a href="{{ node|absolute_url:'projector_summary' }}"
|
||||||
class="activate_link btn btn-mini {% if node.active and summary %}btn-primary{% endif %}"
|
class="activate_link btn btn-mini {% if node.is_active_slide and active_type == 'summary' %}btn-primary{% endif %}"
|
||||||
title="{% trans 'Show summary for this item' %}">
|
title="{% trans 'Show summary for this item' %}">
|
||||||
<i class="icon-summary {% if node.active and summary %}icon-white{% endif %}"></i>
|
<i class="icon-summary {% if node.is_active_slide and active_type == 'summary' %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -79,7 +79,7 @@
|
|||||||
{{ form.self }}
|
{{ form.self }}
|
||||||
{{ form.parent }}
|
{{ form.parent }}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<a href="{% model_url node 'view' %}">{% if node.type == node.ORGANIZATIONAL_ITEM %}<i>[{% endif %}{{ node }}{% if node.type == node.ORGANIZATIONAL_ITEM %}]</i>{% endif %}</a>
|
<a href="{{ node|absolute_url }}">{% if node.type == node.ORGANIZATIONAL_ITEM %}<i>[{% endif %}{{ node }}{% if node.type == node.ORGANIZATIONAL_ITEM %}]</i>{% endif %}</a>
|
||||||
{{ node.get_title_supplement|safe }}
|
{{ node.get_title_supplement|safe }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
8
openslides/agenda/templates/agenda/item_slide.html
Normal file
8
openslides/agenda/templates/agenda/item_slide.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% if item.text %}
|
||||||
|
<h1>{{ item }}</h1>
|
||||||
|
<span>{{ item.text|safe|linebreaks }}</span>
|
||||||
|
{% else %}
|
||||||
|
<div class="item_fullscreen">{{ item }}</div>
|
||||||
|
{% endif %}
|
@ -0,0 +1,40 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
/* List of speakers – projector slide */
|
||||||
|
ul#list_of_speakers {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;}
|
||||||
|
#list_of_speakers li {
|
||||||
|
font-size: 130%;
|
||||||
|
line-height: 150%;}
|
||||||
|
#list_of_speakers .old_speaker {
|
||||||
|
color: #9FA9B7;}
|
||||||
|
#list_of_speakers .actual_speaker {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.5em;}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<h2 style="margin-top: -10px;">
|
||||||
|
{% trans 'List of speakers' %}
|
||||||
|
{% if item.speaker_list_closed %}(<span class="closed">{% trans 'closed' %}</span>){% endif %}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% if list_of_speakers %}
|
||||||
|
<ul id="list_of_speakers">
|
||||||
|
{% for speaker_dict in list_of_speakers %}
|
||||||
|
<li class="{{ speaker_dict.type }}"> {# old_speaker, actual_speaker, coming_speaker #}
|
||||||
|
{% if speaker_dict.type == 'coming_speaker' %}
|
||||||
|
{{ speaker_dict.prefix }}.
|
||||||
|
{% endif %}
|
||||||
|
{{ speaker_dict.speaker }}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<i>{% trans 'The list of speakers is empty.' %}</i>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
@ -0,0 +1,9 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<h1>{% if title %}{{ title }}{% else %}{% trans "Agenda" %}{% endif %}</h1>
|
||||||
|
|
||||||
|
<ul class="itemlist">
|
||||||
|
{% for item in items %}
|
||||||
|
<li{% if item.closed %} class="closed" {% endif %}>{{ item }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
@ -83,7 +83,7 @@
|
|||||||
<th class="manage">{% trans "Actions" %}</th>
|
<th class="manage">{% trans "Actions" %}</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="topline{% if active_sid == 'agenda' %} activeline{% endif %}">
|
<tr class="topline{% if agenda_is_active %} activeline{% endif %}">
|
||||||
<td class="title">
|
<td class="title">
|
||||||
{% trans "Agenda" %}
|
{% trans "Agenda" %}
|
||||||
</td>
|
</td>
|
||||||
@ -98,9 +98,9 @@
|
|||||||
{% if perms.projector.can_manage_projector %}
|
{% if perms.projector.can_manage_projector %}
|
||||||
<span>
|
<span>
|
||||||
<a href="{% url 'projector_activate_slide' 'agenda' %}"
|
<a href="{% url 'projector_activate_slide' 'agenda' %}"
|
||||||
class="activate_link btn {% if active_sid == 'agenda' %}btn-primary{% endif %} btn-mini"
|
class="activate_link btn {% if agenda_is_active %}btn-primary{% endif %} btn-mini"
|
||||||
rel="tooltip" data-original-title="{% trans 'Show agenda' %}">
|
rel="tooltip" data-original-title="{% trans 'Show agenda' %}">
|
||||||
<i class="icon-facetime-video {% if active_sid == 'agenda' %}icon-white{% endif %}"></i>
|
<i class="icon-facetime-video {% if agenda_is_active %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -22,10 +22,10 @@
|
|||||||
<small class="pull-right">
|
<small class="pull-right">
|
||||||
<a href="{% url 'item_overview' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
|
<a href="{% url 'item_overview' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
|
||||||
{% if perms.projector.can_manage_projector %}
|
{% if perms.projector.can_manage_projector %}
|
||||||
<a href="{% url 'projector_activate_slide' item.sid %}"
|
<a href="{{ item|absolute_url:'projector' }}"
|
||||||
class="activate_link btn btn-mini {% if item.active and not show_list %}btn-primary{% endif %}"
|
class="activate_link btn btn-mini {% if item.is_active and active_type != 'list_of_speakers' %}btn-primary{% endif %}"
|
||||||
rel="tooltip" data-original-title="{% trans 'Show item' %}">
|
rel="tooltip" data-original-title="{% trans 'Show item' %}">
|
||||||
<i class="icon icon-facetime-video {% if item.active and not show_list %}icon-white{% endif %}"></i>
|
<i class="icon icon-facetime-video {% if item.is_active and active_type != 'list_of_speakers' %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.agenda.can_manage_agenda %}
|
{% if perms.agenda.can_manage_agenda %}
|
||||||
@ -35,8 +35,8 @@
|
|||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu pull-right">
|
<ul class="dropdown-menu pull-right">
|
||||||
<li><a href="{% model_url item 'update' %}"><i class="icon-pencil"></i> {% trans 'Edit item' %}</a></li>
|
<li><a href="{{ item|absolute_url:'update' }}"><i class="icon-pencil"></i> {% trans 'Edit item' %}</a></li>
|
||||||
<li><a href="{% model_url item 'delete' %}"><i class="icon-remove"></i> {% trans 'Delete item' %}</a></li>
|
<li><a href="{{ item|absolute_url:'delete' }}"><i class="icon-remove"></i> {% trans 'Delete item' %}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -68,10 +68,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.projector.can_manage_projector %}
|
{% if perms.projector.can_manage_projector %}
|
||||||
<a href="{% url 'projector_activate_slide' item.sid 'show_list_of_speakers' %}"
|
<a href="{{ item|absolute_url:'projector_list_of_speakers' }}"
|
||||||
class="activate_link btn btn-mini {% if item.active and show_list %}btn-primary{% endif %}"
|
class="activate_link btn btn-mini {% if item.is_active and active_type == 'list_of_speakers' %}btn-primary{% endif %}"
|
||||||
rel="tooltip" data-original-title="{% trans 'Show list of speakers' %}">
|
rel="tooltip" data-original-title="{% trans 'Show list of speakers' %}">
|
||||||
<i class="icon icon-bell {% if item.active and show_list %}icon-white{% endif %}"></i>
|
<i class="icon icon-bell {% if item.is_active and active_type == 'list_of_speakers' %}icon-white{% endif %}"></i>
|
||||||
{% trans 'Show list' %}
|
{% trans 'Show list' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -119,7 +119,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<small class="grey">[{{ speaker_dict.speaker.begin_time }}{% if speaker_dict.type == 'old_speaker' %} – {{ speaker_dict.speaker.end_time }}{% endif %}]</small>
|
<small class="grey">[{{ speaker_dict.speaker.begin_time }}{% if speaker_dict.type == 'old_speaker' %} – {{ speaker_dict.speaker.end_time }}{% endif %}]</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% model_url speaker_dict.speaker %}">{{ speaker_dict.speaker }}</a>
|
<a href="{{ speaker_dict.speaker|absolute_url }}">{{ speaker_dict.speaker }}</a>
|
||||||
{% if perms.agenda.can_manage_agenda %}
|
{% if perms.agenda.can_manage_agenda %}
|
||||||
{% if speaker_dict.type == 'actual_speaker' %}
|
{% if speaker_dict.type == 'actual_speaker' %}
|
||||||
<a href="{% url 'agenda_speaker_end_speach' item.pk %}" class="btn btn-mini btn-danger"><i class="icon-bell icon-white"></i> {% trans 'End speach' %}</a>
|
<a href="{% url 'agenda_speaker_end_speach' item.pk %}" class="btn btn-mini btn-danger"><i class="icon-bell icon-white"></i> {% trans 'End speach' %}</a>
|
||||||
@ -127,7 +127,7 @@
|
|||||||
<a href="{% url 'agenda_speaker_speak' item.pk speaker_dict.speaker.person.person_id %}"
|
<a href="{% url 'agenda_speaker_speak' item.pk speaker_dict.speaker.person.person_id %}"
|
||||||
class="btn btn-mini"><i class="icon-bell"></i> {% trans "Begin speach" %}</a>
|
class="btn btn-mini"><i class="icon-bell"></i> {% trans "Begin speach" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% model_url speaker_dict.speaker 'delete' %}"
|
<a href="{{ speaker_dict.speaker|absolute_url:'delete' }}"
|
||||||
rel="tooltip" data-original-title="{% trans 'Delete' %}" class="btn btn-mini">
|
rel="tooltip" data-original-title="{% trans 'Delete' %}" class="btn btn-mini">
|
||||||
<i class="icon-remove"></i>
|
<i class="icon-remove"></i>
|
||||||
</a>
|
</a>
|
||||||
|
@ -2,49 +2,49 @@
|
|||||||
{% load tags %}
|
{% load tags %}
|
||||||
|
|
||||||
<ul style="line-height: 180%">
|
<ul style="line-height: 180%">
|
||||||
<li class="{% if agenda.active %}activeline{% endif %}">
|
<li class="{% if agenda_is_active %}activeline{% endif %}">
|
||||||
<a href="{% url 'projector_activate_slide' agenda.key %}"
|
<a href="{% url 'projector_activate_slide' 'agenda' %}"
|
||||||
class="activate_link btn {% if agenda.active %}btn-primary{% endif %} btn-mini"
|
class="activate_link btn {% if agenda_is_active %}btn-primary{% endif %} btn-mini"
|
||||||
rel="tooltip" data-original-title="{% trans 'Show' %}">
|
rel="tooltip" data-original-title="{% trans 'Show' %}">
|
||||||
<i class="icon-facetime-video {% if agenda.active %}icon-white{% endif %}"></i>
|
<i class="icon-facetime-video {% if agenda_is_active %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'projctor_preview_slide' agenda.key %}" title="{% trans 'Preview' %}" class="icon preview right">
|
<a href="{% url 'projector_preview' 'agenda' %}" title="{% trans 'Preview' %}" class="icon preview right">
|
||||||
<span></span>
|
<span></span>
|
||||||
</a>
|
</a>
|
||||||
{{ agenda.name }}
|
{% trans "Agenda" %}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<hr>
|
<hr>
|
||||||
<ul style="line-height: 180%">
|
<ul style="line-height: 180%">
|
||||||
{% for item in items %}
|
{% for item in items %}
|
||||||
<li class="{% if item.active %}activeline{% endif %}">
|
<li class="{% if item.is_active_slide %}activeline{% endif %}">
|
||||||
<a href="{% url 'projector_activate_slide' item.sid %}"
|
<a href="{{ item|absolute_url:'projector' }}"
|
||||||
class="activate_link btn {% if item.active and not summary and not speakers %}btn-primary{% endif %} btn-mini"
|
class="activate_link btn {% if item.is_active_slide and active_type == 'text' %}btn-primary{% endif %} btn-mini"
|
||||||
rel="tooltip" data-original-title="{% trans 'Show' %}">
|
rel="tooltip" data-original-title="{% trans 'Show' %}">
|
||||||
<i class="icon-facetime-video {% if item.active and not summary and not speakers %}icon-white{% endif %}"></i>
|
<i class="icon-facetime-video {% if item.is_active_slide and active_type == 'text' %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% model_url item 'edit' %}" title="{% trans 'Edit' %}" class="btn btn-mini right">
|
<a href="{{ item|absolute_url:'edit' }}" title="{% trans 'Edit' %}" class="btn btn-mini right">
|
||||||
<i class="icon-pencil"></i>
|
<i class="icon-pencil"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'projctor_preview_slide' item.sid %}" title="{% trans 'Preview' %}" class="btn btn-mini right">
|
<a href="{{ item|absolute_url:'projector_preview' }}" title="{% trans 'Preview' %}" class="btn btn-mini right">
|
||||||
<i class="icon-search"></i>
|
<i class="icon-search"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'projector_activate_slide' item.sid 'show_list_of_speakers' %}"
|
<a href="{{ item|absolute_url:'projector_list_of_speakers' }}"
|
||||||
class="activate_link btn btn-mini right {% if item.active and speakers %}btn-primary{% endif %}"
|
class="activate_link btn btn-mini right {% if item.is_active_slide and active_type == 'list_of_speakers' %}btn-primary{% endif %}"
|
||||||
rel="tooltip" data-original-title="{% trans 'Show list of speakers' %}">
|
rel="tooltip" data-original-title="{% trans 'Show list of speakers' %}">
|
||||||
<i class="icon icon-bell {% if item.active and speakers %}icon-white{% endif %}"></i>
|
<i class="icon icon-bell {% if item.is_active_slide and active_type == 'list_of_speakers' %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if not item.is_leaf_node %}
|
{% if not item.is_leaf_node %}
|
||||||
<a href="{% url 'projector_activate_slide' item.sid 'summary' %}"
|
<a href="{{ item|absolute_url:'projector_summary' }}"
|
||||||
class="activate_link btn btn-mini {% if item.active and summary %}btn-primary{% endif %} right"
|
class="activate_link btn btn-mini {% if item.is_active_slide and active_type == 'summary' %}btn-primary{% endif %} right"
|
||||||
rel="tooltip" data-original-title="{% trans 'Show summary for this item' %}">
|
rel="tooltip" data-original-title="{% trans 'Show summary for this item' %}">
|
||||||
<i class="icon-summary {% if item.active and summary %}icon-white{% endif %}"></i>
|
<i class="icon-summary {% if item.is_active_slide and active_type == 'summary' %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for p in item.get_ancestors %}
|
{% for p in item.get_ancestors %}
|
||||||
<span class="indentation"></span>
|
<span class="indentation"></span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<a href="{% model_url item 'view' %}">{% if item.type == item.ORGANIZATIONAL_ITEM %}<i>[{% endif %}{{ item }}{% if item.type == item.ORGANIZATIONAL_ITEM %}]</i>{% endif %}</a>
|
<a href="{{ item|absolute_url }}">{% if item.type == item.ORGANIZATIONAL_ITEM %}<i>[{% endif %}{{ item }}{% if item.type == item.ORGANIZATIONAL_ITEM %}]</i>{% endif %}</a>
|
||||||
{{ item.get_title_supplement|safe }}
|
{{ item.get_title_supplement|safe }}
|
||||||
</li>
|
</li>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
{% extends "base-projector.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
{% if title %} {{ title }} {% else %} {% trans "Agenda" %} {% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{% if title %}{{ title }}{% else %}{% trans "Agenda" %}{% endif %}</h1>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block scrollcontent %}
|
|
||||||
<ul class="itemlist">
|
|
||||||
{% for item in items %}
|
|
||||||
<li{% if item.closed %} class="closed" {% endif %}>{{ item }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
@ -1,19 +0,0 @@
|
|||||||
{% extends "base-projector.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{{ item }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% if item.text %}
|
|
||||||
<h1>{{ item }}</h1>
|
|
||||||
{% else %}
|
|
||||||
<div class="item_fullscreen">{{ item }}</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block scrollcontent %}
|
|
||||||
{% if item.text %}
|
|
||||||
<span>{{ item.text|safe|linebreaks }}</span>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@ -1,46 +0,0 @@
|
|||||||
{% extends 'base-projector.html' %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{{ item }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<style type="text/css">
|
|
||||||
/* List of speakers – projector slide */
|
|
||||||
ul#list_of_speakers {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;}
|
|
||||||
#list_of_speakers li {
|
|
||||||
font-size: 130%;
|
|
||||||
line-height: 150%;}
|
|
||||||
#list_of_speakers .old_speaker {
|
|
||||||
color: #9FA9B7;}
|
|
||||||
#list_of_speakers .actual_speaker {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 0.5em;}
|
|
||||||
</style>
|
|
||||||
<h1>{{ title }}</h1>
|
|
||||||
<h2 style="margin-top: -10px;">
|
|
||||||
{% trans 'List of speakers' %}
|
|
||||||
{% if item.speaker_list_closed %}(<span class="closed">{% trans 'closed' %}</span>){% endif %}
|
|
||||||
</h2>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block scrollcontent %}
|
|
||||||
<p>
|
|
||||||
{% if list_of_speakers %}
|
|
||||||
<ul id="list_of_speakers">
|
|
||||||
{% for speaker_dict in list_of_speakers %}
|
|
||||||
<li class="{{ speaker_dict.type }}"> {# old_speaker, actual_speaker, coming_speaker #}
|
|
||||||
{% if speaker_dict.type == 'coming_speaker' %}
|
|
||||||
{{ speaker_dict.prefix }}.
|
|
||||||
{% endif %}
|
|
||||||
{{ speaker_dict.speaker }}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% else %}
|
|
||||||
<i>{% trans 'The list of speakers is empty.' %}</i>
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
{% endblock %}
|
|
@ -29,8 +29,8 @@ from openslides.utils.views import (
|
|||||||
DetailView, FormView, SingleObjectMixin)
|
DetailView, FormView, SingleObjectMixin)
|
||||||
from openslides.utils.template import Tab
|
from openslides.utils.template import Tab
|
||||||
from openslides.utils.utils import html_strong
|
from openslides.utils.utils import html_strong
|
||||||
from openslides.projector.api import get_active_slide, get_slide_from_sid
|
from openslides.projector.api import get_active_slide, update_projector
|
||||||
from openslides.projector.projector import Widget, SLIDE
|
from openslides.projector.projector import Widget
|
||||||
from .models import Item, Speaker
|
from .models import Item, Speaker
|
||||||
from .forms import ItemOrderForm, ItemForm, AppendSpeakerForm, RelatedItemForm
|
from .forms import ItemOrderForm, ItemForm, AppendSpeakerForm, RelatedItemForm
|
||||||
|
|
||||||
@ -83,14 +83,21 @@ class Overview(TemplateView):
|
|||||||
(duration.days * 24 + duration.seconds / 3600.0),
|
(duration.days * 24 + duration.seconds / 3600.0),
|
||||||
(duration.seconds / 60.0 % 60))
|
(duration.seconds / 60.0 % 60))
|
||||||
|
|
||||||
|
active_slide = get_active_slide()
|
||||||
|
if active_slide['callback'] == 'agenda':
|
||||||
|
agenda_active = active_slide.get('pk', 'agenda') == 'agenda'
|
||||||
|
active_type = active_slide.get('type', 'text')
|
||||||
|
else:
|
||||||
|
agenda_is_active = None
|
||||||
|
active_type = None
|
||||||
|
|
||||||
context.update({
|
context.update({
|
||||||
'items': items,
|
'items': items,
|
||||||
'active_sid': get_active_slide(only_sid=True),
|
'agenda_is_active': agenda_is_active,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'start': start,
|
'start': start,
|
||||||
'end': end,
|
'end': end,
|
||||||
'summary': config['presentation_argument'] == 'summary',
|
'active_type': active_type})
|
||||||
'show_list': config['presentation_argument'] == 'show_list_of_speakers'})
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@transaction.commit_manually
|
@transaction.commit_manually
|
||||||
@ -100,6 +107,7 @@ class Overview(TemplateView):
|
|||||||
request,
|
request,
|
||||||
_('You are not authorized to manage the agenda.'))
|
_('You are not authorized to manage the agenda.'))
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
|
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
for item in Item.objects.all():
|
for item in Item.objects.all():
|
||||||
form = ItemOrderForm(request.POST, prefix="i%d" % item.id)
|
form = ItemOrderForm(request.POST, prefix="i%d" % item.id)
|
||||||
@ -121,6 +129,11 @@ 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)
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
|
|
||||||
|
if get_active_slide()['callback'] == 'agenda':
|
||||||
|
update_projector()
|
||||||
|
context = self.get_context_data(**kwargs)
|
||||||
|
transaction.commit()
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
|
|
||||||
|
|
||||||
@ -138,11 +151,16 @@ class AgendaItemView(SingleObjectMixin, FormView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
list_of_speakers = self.object.get_list_of_speakers()
|
list_of_speakers = self.object.get_list_of_speakers()
|
||||||
|
|
||||||
|
active_slide = get_active_slide()
|
||||||
|
active_type = active_slide.get('type', None)
|
||||||
|
|
||||||
kwargs.update({
|
kwargs.update({
|
||||||
'object': self.object,
|
'object': self.object,
|
||||||
'list_of_speakers': list_of_speakers,
|
'list_of_speakers': list_of_speakers,
|
||||||
'is_on_the_list_of_speakers': Speaker.objects.filter(item=self.object, begin_time=None, person=self.request.user).exists(),
|
'is_on_the_list_of_speakers': Speaker.objects.filter(
|
||||||
'show_list': config['presentation_argument'] == 'show_list_of_speakers',
|
item=self.object, begin_time=None, person=self.request.user).exists(),
|
||||||
|
'active_type': active_type,
|
||||||
})
|
})
|
||||||
return super(AgendaItemView, self).get_context_data(**kwargs)
|
return super(AgendaItemView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
@ -486,11 +504,14 @@ class CurrentListOfSpeakersView(RedirectView):
|
|||||||
"""
|
"""
|
||||||
Returns the current Item, or None, if the current Slide is not an Agenda Item.
|
Returns the current Item, or None, if the current Slide is not an Agenda Item.
|
||||||
"""
|
"""
|
||||||
slide = get_slide_from_sid(get_active_slide(only_sid=True), element=True)
|
active_slide = get_active_slide()
|
||||||
if not isinstance(slide, Item):
|
if active_slide['callback'] == 'agenda':
|
||||||
return None
|
try:
|
||||||
|
return Item.objects.get(pk=active_slide.get('pk', None))
|
||||||
|
except Item.DoesNotExist:
|
||||||
|
return None
|
||||||
else:
|
else:
|
||||||
return slide
|
return None
|
||||||
|
|
||||||
def get_redirect_url(self):
|
def get_redirect_url(self):
|
||||||
"""
|
"""
|
||||||
@ -583,6 +604,14 @@ def get_widgets(request):
|
|||||||
"""
|
"""
|
||||||
Returns the agenda widget for the projector tab.
|
Returns the agenda widget for the projector tab.
|
||||||
"""
|
"""
|
||||||
|
active_slide = get_active_slide()
|
||||||
|
if active_slide['callback'] == 'agenda':
|
||||||
|
agenda_is_active = active_slide.get('pk', 'agenda') == 'agenda'
|
||||||
|
active_type = active_slide.get('type', 'text')
|
||||||
|
else:
|
||||||
|
agenda_is_active = None
|
||||||
|
active_type = None
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Widget(
|
Widget(
|
||||||
request,
|
request,
|
||||||
@ -590,10 +619,9 @@ def get_widgets(request):
|
|||||||
display_name=_('Agenda'),
|
display_name=_('Agenda'),
|
||||||
template='agenda/widget.html',
|
template='agenda/widget.html',
|
||||||
context={
|
context={
|
||||||
'agenda': SLIDE['agenda'],
|
'agenda_is_active': agenda_is_active,
|
||||||
'items': Item.objects.all(),
|
'items': Item.objects.all(),
|
||||||
'summary': config['presentation_argument'] == 'summary',
|
'active_type': active_type},
|
||||||
'speakers': config['presentation_argument'] == 'show_list_of_speakers'},
|
|
||||||
permission_required='projector.can_manage_projector',
|
permission_required='projector.can_manage_projector',
|
||||||
default_column=1,
|
default_column=1,
|
||||||
default_weight=20),
|
default_weight=20),
|
||||||
|
@ -11,3 +11,4 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from . import signals
|
from . import signals
|
||||||
|
from . import slides
|
||||||
|
@ -18,8 +18,7 @@ from django.utils.datastructures import SortedDict
|
|||||||
from openslides.utils.person import PersonField
|
from openslides.utils.person import PersonField
|
||||||
from openslides.utils.utils import html_strong
|
from openslides.utils.utils import html_strong
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.projector.api import register_slidemodel
|
from openslides.projector.models import SlideMixin
|
||||||
from openslides.projector.projector import SlideMixin
|
|
||||||
from openslides.poll.models import (
|
from openslides.poll.models import (
|
||||||
BasePoll, CountInvalid, CountVotesCast, BaseOption, PublishPollMixin, BaseVote)
|
BasePoll, CountInvalid, CountVotesCast, BaseOption, PublishPollMixin, BaseVote)
|
||||||
|
|
||||||
@ -37,8 +36,9 @@ class AssignmentCandidate(models.Model):
|
|||||||
unique_together = ("assignment", "person")
|
unique_together = ("assignment", "person")
|
||||||
|
|
||||||
|
|
||||||
class Assignment(models.Model, SlideMixin):
|
class Assignment(SlideMixin, models.Model):
|
||||||
prefix = ugettext_noop('assignment')
|
slide_callback_name = 'assignment'
|
||||||
|
|
||||||
STATUS = (
|
STATUS = (
|
||||||
('sea', ugettext_lazy('Searching for candidates')),
|
('sea', ugettext_lazy('Searching for candidates')),
|
||||||
('vot', ugettext_lazy('Voting')),
|
('vot', ugettext_lazy('Voting')),
|
||||||
@ -53,6 +53,27 @@ class Assignment(models.Model, SlideMixin):
|
|||||||
verbose_name=ugettext_lazy("Comment on the ballot paper"))
|
verbose_name=ugettext_lazy("Comment on the ballot paper"))
|
||||||
status = models.CharField(max_length=3, choices=STATUS, default='sea')
|
status = models.CharField(max_length=3, choices=STATUS, default='sea')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
permissions = (
|
||||||
|
('can_see_assignment', ugettext_noop('Can see assignments')), # TODO: Add plural s to the codestring
|
||||||
|
('can_nominate_other', ugettext_noop('Can nominate another person')),
|
||||||
|
('can_nominate_self', ugettext_noop('Can nominate oneself')),
|
||||||
|
('can_manage_assignment', ugettext_noop('Can manage assignments')), # TODO: Add plural s also to the codestring
|
||||||
|
)
|
||||||
|
ordering = ('name',)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self, link='detail'):
|
||||||
|
if link == 'detail' or link == 'view':
|
||||||
|
return reverse('assignment_detail', args=[str(self.id)])
|
||||||
|
if link == 'update' or link == 'update':
|
||||||
|
return reverse('assignment_update', args=[str(self.id)])
|
||||||
|
if link == 'delete':
|
||||||
|
return reverse('assignment_delete', args=[str(self.id)])
|
||||||
|
return super(Assignment, self).get_absolute_url(link)
|
||||||
|
|
||||||
def set_status(self, status):
|
def set_status(self, status):
|
||||||
status_dict = dict(self.STATUS)
|
status_dict = dict(self.STATUS)
|
||||||
if status not in status_dict:
|
if status not in status_dict:
|
||||||
@ -206,45 +227,6 @@ class Assignment(models.Model, SlideMixin):
|
|||||||
def get_agenda_title_supplement(self):
|
def get_agenda_title_supplement(self):
|
||||||
return '(%s)' % _('Assignment')
|
return '(%s)' % _('Assignment')
|
||||||
|
|
||||||
def slide(self):
|
|
||||||
"""
|
|
||||||
return the slide dict
|
|
||||||
"""
|
|
||||||
polls = self.poll_set
|
|
||||||
data = super(Assignment, self).slide()
|
|
||||||
data['assignment'] = self
|
|
||||||
data['title'] = self.name
|
|
||||||
data['some_polls_available'] = polls.exists()
|
|
||||||
data['polls'] = polls.filter(published=True)
|
|
||||||
data['vote_results'] = self.vote_results(only_published=True)
|
|
||||||
data['assignment_publish_winner_results_only'] = \
|
|
||||||
config['assignment_publish_winner_results_only']
|
|
||||||
data['template'] = 'projector/Assignment.html'
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get_absolute_url(self, link='detail'):
|
|
||||||
if link == 'detail' or link == 'view':
|
|
||||||
return reverse('assignment_detail', args=[str(self.id)])
|
|
||||||
if link == 'update' or link == 'edit':
|
|
||||||
return reverse('assignment_update', args=[str(self.id)])
|
|
||||||
if link == 'delete':
|
|
||||||
return reverse('assignment_delete', args=[str(self.id)])
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
permissions = (
|
|
||||||
('can_see_assignment', ugettext_noop('Can see assignments')), # TODO: Add plural s to the codestring
|
|
||||||
('can_nominate_other', ugettext_noop('Can nominate another person')),
|
|
||||||
('can_nominate_self', ugettext_noop('Can nominate oneself')),
|
|
||||||
('can_manage_assignment', ugettext_noop('Can manage assignments')), # TODO: Add plural s also to the codestring
|
|
||||||
)
|
|
||||||
ordering = ('name',)
|
|
||||||
verbose_name = ugettext_noop('Assignment')
|
|
||||||
|
|
||||||
register_slidemodel(Assignment)
|
|
||||||
|
|
||||||
|
|
||||||
class AssignmentVote(BaseVote):
|
class AssignmentVote(BaseVote):
|
||||||
option = models.ForeignKey('AssignmentOption')
|
option = models.ForeignKey('AssignmentOption')
|
||||||
@ -294,7 +276,7 @@ class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin):
|
|||||||
return self.assignment.poll_set.filter(id__lte=self.id).count()
|
return self.assignment.poll_set.filter(id__lte=self.id).count()
|
||||||
|
|
||||||
@models.permalink
|
@models.permalink
|
||||||
def get_absolute_url(self, link='view'):
|
def get_absolute_url(self, link='detail'):
|
||||||
if link == 'view' or link == 'detail' or link == 'update':
|
if link == 'view' or link == 'detail' or link == 'update':
|
||||||
return ('assignment_poll_view', [str(self.id)])
|
return ('assignment_poll_view', [str(self.id)])
|
||||||
if link == 'delete':
|
if link == 'delete':
|
||||||
|
38
openslides/assignment/slides.py
Normal file
38
openslides/assignment/slides.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
openslides.assignment.slides
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Slides for the assignment app.
|
||||||
|
|
||||||
|
:copyright: 2011–2013 by OpenSlides team, see AUTHORS.
|
||||||
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
from openslides.config.api import config
|
||||||
|
from openslides.projector.api import register_slide
|
||||||
|
from .models import Assignment
|
||||||
|
|
||||||
|
|
||||||
|
def assignment_slide(**kwargs):
|
||||||
|
"""
|
||||||
|
Slide for an Assignment
|
||||||
|
"""
|
||||||
|
assignment_pk = kwargs.get('pk', None)
|
||||||
|
try:
|
||||||
|
assignment = Assignment.objects.get(pk=assignment_pk)
|
||||||
|
except Assignment.DoesNotExist:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
polls = assignment.poll_set
|
||||||
|
context = {
|
||||||
|
'polls': polls.filter(published=True),
|
||||||
|
'vote_results': assignment.vote_results(only_published=True),
|
||||||
|
'assignment': assignment}
|
||||||
|
|
||||||
|
return render_to_string('assignment/slide.html', context)
|
||||||
|
|
||||||
|
register_slide(Assignment.slide_callback_name, assignment_slide)
|
@ -65,7 +65,7 @@
|
|||||||
<ol>
|
<ol>
|
||||||
{% for person in assignment.get_participants %}
|
{% for person in assignment.get_participants %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ person|absolute_url:'detail' }}">{{ person }}</a>
|
<a href="{{ person|absolute_url }}">{{ person }}</a>
|
||||||
{% if perms.assignment.can_manage_assignment %}
|
{% if perms.assignment.can_manage_assignment %}
|
||||||
{% if assignment.status == "sea" or assignment.status == "vot" %}
|
{% if assignment.status == "sea" or assignment.status == "vot" %}
|
||||||
<a href="{% url 'assignment_delother' assignment.id person.person_id %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Remove candidate' %}"><i class="icon-remove"></i></a>
|
<a href="{% url 'assignment_delother' assignment.id person.person_id %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Remove candidate' %}"><i class="icon-remove"></i></a>
|
||||||
@ -126,7 +126,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
{% for person in blocked_candidates %}
|
{% for person in blocked_candidates %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ person|absolute_url:'detail' }}">{{ person }}</a>
|
<a href="{{ person|absolute_url }}">{{ person }}</a>
|
||||||
<a class="btn btn-mini" href="{% url 'assignment_delother' assignment.id person.person_id %}"
|
<a class="btn btn-mini" href="{% url 'assignment_delother' assignment.id person.person_id %}"
|
||||||
rel="tooltip" data-original-title="{% trans 'Remove candidate' %}">
|
rel="tooltip" data-original-title="{% trans 'Remove candidate' %}">
|
||||||
<i class="icon-ban-circle"></i>
|
<i class="icon-ban-circle"></i>
|
||||||
@ -168,16 +168,16 @@
|
|||||||
<i class="icon-unchecked-new"></i>
|
<i class="icon-unchecked-new"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ poll|absolute_url:'update' }}" class="btn btn-mini"
|
<a href="{% url 'assignment_poll_view' poll.id %}" class="btn btn-mini"
|
||||||
rel="tooltip" data-original-title="{% trans 'Edit' %}"><i class="icon-pencil"></i></a>
|
rel="tooltip" data-original-title="{% trans 'Edit' %}"><i class="icon-pencil"></i></a>
|
||||||
<a href="{{ poll|absolute_url:'delete' }}" class="btn btn-mini"
|
<a href="{% url 'assignment_poll_delete' poll.id %}" class="btn btn-mini"
|
||||||
rel="tooltip" data-original-title="{% trans 'Delete' %}"><i class="icon-remove"></i></a>
|
rel="tooltip" data-original-title="{% trans 'Delete' %}"><i class="icon-remove"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</th>
|
</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
{% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
||||||
<th class="span1 nobr">
|
<th class="span1 nobr">
|
||||||
<a href="{% url 'assignment_poll_create' assignment.id %}" class="btn btn-mini">
|
<a href="{% url 'assignment_gen_poll' assignment.id %}" class="btn btn-mini">
|
||||||
<i class="icon-plus"></i> {% trans 'New ballot' %}
|
<i class="icon-plus"></i> {% trans 'New ballot' %}
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
@ -200,7 +200,7 @@
|
|||||||
<a class="election_link" href="{% url 'assignment_user_elected' assignment.id candidate.person_id %}"></a>
|
<a class="election_link" href="{% url 'assignment_user_elected' assignment.id candidate.person_id %}"></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ candidate|absolute_url:'detail' }}">{{ candidate }}</a>
|
<a href="{{ candidate|absolute_url }}">{{ candidate }}</a>
|
||||||
</td>
|
</td>
|
||||||
{% for vote in poll_list %}
|
{% for vote in poll_list %}
|
||||||
<td style="white-space:nowrap;">
|
<td style="white-space:nowrap;">
|
||||||
|
110
openslides/assignment/templates/assignment/slide.html
Normal file
110
openslides/assignment/templates/assignment/slide.html
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load tags %}
|
||||||
|
|
||||||
|
{% if assignment.status != "fin" %}
|
||||||
|
<div id="sidebar">
|
||||||
|
<div class="box">
|
||||||
|
<p><b>{% trans "Status" %}:</b><br>
|
||||||
|
{% trans assignment.get_status_display %}</p>
|
||||||
|
{% if assignment.status == "sea" or assignment.status == "vot" %}
|
||||||
|
<p><b>{% trans "Number of available posts" %}:</b><br>
|
||||||
|
{{ assignment.posts }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<h1>{% trans "Election" %}: {{ assignment }}</h1>
|
||||||
|
{% if not assignment.candidates %}
|
||||||
|
<p>
|
||||||
|
<div class="text">{{ assignment.description|linebreaks }}</div>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if assignment.candidates and assignment.status != "fin" %}
|
||||||
|
<h3>{% trans "Candidates" %}</h3>
|
||||||
|
<ol>
|
||||||
|
{% for candidate in assignment.candidates %}
|
||||||
|
<li>{{ candidate }} </li>
|
||||||
|
{% empty %}
|
||||||
|
<li style="list-style: none outside none;">
|
||||||
|
<i>{% trans "No candidates available." %}</i>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
<p><br></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if polls.exists %}
|
||||||
|
<h3>{% trans "Election results" %}</h3>
|
||||||
|
<table>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Candidates" %}</th>
|
||||||
|
{% for poll in polls %}
|
||||||
|
<th>
|
||||||
|
<nobr>{{ poll.get_ballot }}. {% trans "ballot" %}</nobr>
|
||||||
|
</th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% for candidate, poll_list in vote_results.items %}
|
||||||
|
<tr class="{% cycle 'odd' '' as rowcolors %}">
|
||||||
|
<td class="candidate{% if candidate in assignment.elected %} elected{% endif %}">
|
||||||
|
{% if candidate in assignment.elected %}
|
||||||
|
<a class="elected">
|
||||||
|
<img src="{% static 'img/voting-yes.png' %}" title="{% trans 'Candidate is elected' %}">
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{{ candidate }}
|
||||||
|
</td>
|
||||||
|
{% for vote in poll_list %}
|
||||||
|
<td style="white-space:nowrap;"{% if candidate in assignment.elected %} class="elected"{% endif %}>
|
||||||
|
{% if not "assignment_publish_winner_results_only"|get_config or candidate in assignment.elected %}
|
||||||
|
{% if 'Yes' in vote and 'No' in vote and 'Abstain' in vote %}
|
||||||
|
<img src="{% static 'img/voting-yes.png' %}" title="{% trans 'Yes' %}"> {{ vote.Yes }}<br>
|
||||||
|
<img src="{% static 'img/voting-no.png' %}" title="{% trans 'No' %}"> {{ vote.No }}<br>
|
||||||
|
<img src="{% static 'img/voting-abstention.png' %}" title="{% trans 'Abstention' %}"> {{ vote.Abstain }}<br>
|
||||||
|
{% elif 'Votes' in vote %}
|
||||||
|
<img src="{% static 'img/voting-yes.png' %}" title="{% trans 'Yes' %}"> {{ vote.Votes }}
|
||||||
|
{% elif vote == None %}
|
||||||
|
{% trans 'was not a <br> candidate'%}
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<tr class="{% cycle rowcolors %}">
|
||||||
|
<td>{% trans 'Invalid votes' %}</td>
|
||||||
|
{% for poll in polls %}
|
||||||
|
<td style="white-space:nowrap;">
|
||||||
|
{% if poll.has_votes %}
|
||||||
|
<img src="{% static 'img/voting-invalid.png' %}" title="{% trans 'Invalid' %}">
|
||||||
|
{{ poll.print_votesinvalid }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr class="total">
|
||||||
|
<td>
|
||||||
|
<strong>{% trans 'Votes cast' %}</strong>
|
||||||
|
</td>
|
||||||
|
{% for poll in polls %}
|
||||||
|
|
||||||
|
<td style="white-space:nowrap;">
|
||||||
|
{% if poll.has_votes %}
|
||||||
|
<img src="{% static 'img/voting-total.png' %}" title="{% trans 'Votes cast' %}">
|
||||||
|
<strong>{{ poll.print_votescast }}</strong>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
@ -3,17 +3,17 @@
|
|||||||
|
|
||||||
<ul style="line-height: 180%">
|
<ul style="line-height: 180%">
|
||||||
{% for assignment in assignments %}
|
{% for assignment in assignments %}
|
||||||
<li class="{% if assignment.active %}activeline{% endif %}">
|
<li class="{% if assignment.is_active_slide %}activeline{% endif %}">
|
||||||
<a href="{% url 'projector_activate_slide' assignment.sid %}" class="activate_link btn {% if assignment.active %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
<a href="{{ assignment|absolute_url:'projector' }}" class="activate_link btn {% if assignment.active %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
||||||
<i class="icon-facetime-video {% if assignment.active %}icon-white{% endif %}"></i>
|
<i class="icon-facetime-video {% if assignment.active %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ assignment|absolute_url:'update' }}" title="{% trans 'Edit' %}" class="btn btn-mini right">
|
<a href="{{ assignment|absolute_url:'update' }}" title="{% trans 'Edit' %}" class="btn btn-mini right">
|
||||||
<i class="icon-pencil"></i>
|
<i class="icon-pencil"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'projctor_preview_slide' assignment.sid %}" title="{% trans 'Preview' %}" class="btn btn-mini right">
|
<a href="{{ assignment|absolute_url:'projector_preview' }}" title="{% trans 'Preview' %}" class="btn btn-mini right">
|
||||||
<i class="icon-search"></i>
|
<i class="icon-search"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ assignment|absolute_url:'detail' }}">{{ assignment }}</a>
|
<a href="{{ assignment|absolute_url }}">{{ assignment }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<li>{% trans 'No assignments available.' %}</li>
|
<li>{% trans 'No assignments available.' %}</li>
|
||||||
|
@ -1,122 +0,0 @@
|
|||||||
{% extends "base-projector.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
{% block title %}{{ assignment }}{% endblock %}
|
|
||||||
|
|
||||||
{% block header %}
|
|
||||||
<link type="text/css" rel="stylesheet" media="all" href="{% static 'styles/assignment.css' %}" />
|
|
||||||
<script type="text/javascript" src="{% static 'javascript/assignment.js' %}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% if assignment.status != "fin" %}
|
|
||||||
<div id="sidebar">
|
|
||||||
<div class="box">
|
|
||||||
<p><b>{% trans "Status" %}:</b><br>
|
|
||||||
{% trans assignment.get_status_display %}</p>
|
|
||||||
{% if assignment.status == "sea" or assignment.status == "vot" %}
|
|
||||||
<p><b>{% trans "Number of available posts" %}:</b><br>
|
|
||||||
{{ assignment.posts }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<h1>{% trans "Election" %}: {{ assignment }}</h1>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block scrollcontent %}
|
|
||||||
{% if not assignment.candidates %}
|
|
||||||
<p>
|
|
||||||
<div class="text">{{ assignment.description|linebreaks }}</div>
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if assignment.candidates and assignment.status != "fin" %}
|
|
||||||
<h3>{% trans "Candidates" %}</h3>
|
|
||||||
<ol>
|
|
||||||
{% for candidate in assignment.candidates %}
|
|
||||||
<li>{{ candidate }} </li>
|
|
||||||
{% empty %}
|
|
||||||
<li style="list-style: none outside none;">
|
|
||||||
<i>{% trans "No candidates available." %}</i>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ol>
|
|
||||||
<p><br></p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if polls.exists %}
|
|
||||||
<h3>{% trans "Election results" %}</h3>
|
|
||||||
<table>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th>{% trans "Candidates" %}</th>
|
|
||||||
{% for poll in polls %}
|
|
||||||
<th>
|
|
||||||
<nobr>{{ poll.get_ballot }}. {% trans "ballot" %}</nobr>
|
|
||||||
</th>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{% for candidate, poll_list in vote_results.items %}
|
|
||||||
<tr class="{% cycle 'odd' '' as rowcolors %}">
|
|
||||||
<td class="candidate{% if candidate in assignment.elected %} elected{% endif %}">
|
|
||||||
{% if candidate in assignment.elected %}
|
|
||||||
<a class="elected">
|
|
||||||
<img src="{% static 'img/voting-yes.png' %}" title="{% trans 'Candidate is elected' %}">
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{{ candidate }}
|
|
||||||
</td>
|
|
||||||
{% for vote in poll_list %}
|
|
||||||
<td style="white-space:nowrap;"{% if candidate in assignment.elected %} class="elected"{% endif %}>
|
|
||||||
{% if not assignment_publish_winner_results_only or candidate in assignment.elected %}
|
|
||||||
{% if 'Yes' in vote and 'No' in vote and 'Abstain' in vote %}
|
|
||||||
<img src="{% static 'img/voting-yes.png' %}" title="{% trans 'Yes' %}"> {{ vote.Yes }}<br>
|
|
||||||
<img src="{% static 'img/voting-no.png' %}" title="{% trans 'No' %}"> {{ vote.No }}<br>
|
|
||||||
<img src="{% static 'img/voting-abstention.png' %}" title="{% trans 'Abstention' %}"> {{ vote.Abstain }}<br>
|
|
||||||
{% elif 'Votes' in vote %}
|
|
||||||
<img src="{% static 'img/voting-yes.png' %}" title="{% trans 'Yes' %}"> {{ vote.Votes }}
|
|
||||||
{% elif vote == None %}
|
|
||||||
{% trans 'was not a <br> candidate'%}
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
<tr class="{% cycle rowcolors %}">
|
|
||||||
<td>{% trans 'Invalid votes' %}</td>
|
|
||||||
{% for poll in polls %}
|
|
||||||
<td style="white-space:nowrap;">
|
|
||||||
{% if poll.has_votes %}
|
|
||||||
<img src="{% static 'img/voting-invalid.png' %}" title="{% trans 'Invalid' %}">
|
|
||||||
{{ poll.print_votesinvalid }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
<tr class="total">
|
|
||||||
<td>
|
|
||||||
<strong>{% trans 'Votes cast' %}</strong>
|
|
||||||
</td>
|
|
||||||
{% for poll in polls %}
|
|
||||||
|
|
||||||
<td style="white-space:nowrap;">
|
|
||||||
{% if poll.has_votes %}
|
|
||||||
<img src="{% static 'img/voting-total.png' %}" title="{% trans 'Votes cast' %}">
|
|
||||||
<strong>{{ poll.print_votescast }}</strong>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@ -30,12 +30,12 @@
|
|||||||
<td>{{ mediafile.filetype }}</td>
|
<td>{{ mediafile.filetype }}</td>
|
||||||
<td>{{ mediafile.get_filesize }}</td>
|
<td>{{ mediafile.get_filesize }}</td>
|
||||||
<td>{{ mediafile.timestamp }}</td>
|
<td>{{ mediafile.timestamp }}</td>
|
||||||
<td><a href="{% model_url mediafile.uploader 'view' %}">{{ mediafile.uploader }}</a></td>
|
<td><a href="{{ mediafile.uploader|absolute_url }}">{{ mediafile.uploader }}</a></td>
|
||||||
{% if perms.mediafile.can_manage %}
|
{% if perms.mediafile.can_manage %}
|
||||||
<td>
|
<td>
|
||||||
<span style="width: 1px; white-space: nowrap;">
|
<span style="width: 1px; white-space: nowrap;">
|
||||||
<a href="{% model_url mediafile 'update' %}" rel="tooltip" data-original-title="{% trans 'Edit' %}" class="btn btn-mini"><i class="icon-pencil"></i></a>
|
<a href="{{ mediafile|absolute_url:'update' }}" rel="tooltip" data-original-title="{% trans 'Edit' %}" class="btn btn-mini"><i class="icon-pencil"></i></a>
|
||||||
<a href="{% model_url mediafile 'delete' %}" rel="tooltip" data-original-title="{% trans 'Delete' %}" class="btn btn-mini"><i class="icon-remove"></i></a>
|
<a href="{{ mediafile|absolute_url:'delete' }}" rel="tooltip" data-original-title="{% trans 'Delete' %}" class="btn btn-mini"><i class="icon-remove"></i></a>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -29,7 +29,6 @@ from openslides.config.api import config
|
|||||||
from openslides.poll.models import (
|
from openslides.poll.models import (
|
||||||
BaseOption, BasePoll, CountVotesCast, CountInvalid, BaseVote)
|
BaseOption, BasePoll, CountVotesCast, CountInvalid, BaseVote)
|
||||||
from openslides.participant.models import User
|
from openslides.participant.models import User
|
||||||
from openslides.projector.api import register_slidemodel
|
|
||||||
from openslides.projector.models import SlideMixin
|
from openslides.projector.models import SlideMixin
|
||||||
from openslides.agenda.models import Item
|
from openslides.agenda.models import Item
|
||||||
|
|
||||||
@ -43,9 +42,9 @@ class Motion(SlideMixin, models.Model):
|
|||||||
This class is the main entry point to all other classes related to a motion.
|
This class is the main entry point to all other classes related to a motion.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prefix = ugettext_noop('motion')
|
slide_callback_name = 'motion'
|
||||||
"""
|
"""
|
||||||
Prefix for the slide system.
|
Name of the callback for the slide-system.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
active_version = models.ForeignKey('MotionVersion', null=True,
|
active_version = models.ForeignKey('MotionVersion', null=True,
|
||||||
@ -193,6 +192,7 @@ class Motion(SlideMixin, models.Model):
|
|||||||
return reverse('motion_edit', args=[str(self.id)])
|
return reverse('motion_edit', args=[str(self.id)])
|
||||||
if link == 'delete':
|
if link == 'delete':
|
||||||
return reverse('motion_delete', args=[str(self.id)])
|
return reverse('motion_delete', args=[str(self.id)])
|
||||||
|
return super(Motion, self).get_absolute_url(link)
|
||||||
|
|
||||||
def version_data_changed(self, version):
|
def version_data_changed(self, version):
|
||||||
"""
|
"""
|
||||||
@ -464,16 +464,6 @@ class Motion(SlideMixin, models.Model):
|
|||||||
Workflow.objects.get(pk=config['motion_workflow']).state_set.all()[0])
|
Workflow.objects.get(pk=config['motion_workflow']).state_set.all()[0])
|
||||||
self.set_state(new_state)
|
self.set_state(new_state)
|
||||||
|
|
||||||
def slide(self):
|
|
||||||
"""
|
|
||||||
Return the slide dict.
|
|
||||||
"""
|
|
||||||
data = super(Motion, self).slide()
|
|
||||||
data['motion'] = self
|
|
||||||
data['title'] = self.title
|
|
||||||
data['template'] = 'projector/Motion.html'
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get_agenda_title(self):
|
def get_agenda_title(self):
|
||||||
"""
|
"""
|
||||||
Return a title for the agenda.
|
Return a title for the agenda.
|
||||||
|
@ -10,7 +10,26 @@
|
|||||||
:license: GNU GPL, see LICENSE for more details.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from openslides.projector.api import register_slidemodel
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
from openslides.projector.api import register_slide
|
||||||
from .models import Motion
|
from .models import Motion
|
||||||
|
|
||||||
register_slidemodel(Motion)
|
|
||||||
|
def motion_slide(**kwargs):
|
||||||
|
"""
|
||||||
|
Slide for the motion app.
|
||||||
|
"""
|
||||||
|
motion_pk = kwargs.get('pk', None)
|
||||||
|
try:
|
||||||
|
motion = Motion.objects.get(pk=motion_pk)
|
||||||
|
except Motion.DoesNotExist:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'motion': motion,
|
||||||
|
'title': motion.title}
|
||||||
|
|
||||||
|
return render_to_string('motion/slide.html', context)
|
||||||
|
|
||||||
|
register_slide(Motion.slide_callback_name, motion_slide)
|
||||||
|
@ -42,12 +42,12 @@
|
|||||||
<ul class="dropdown-menu pull-right">
|
<ul class="dropdown-menu pull-right">
|
||||||
<!-- edit -->
|
<!-- edit -->
|
||||||
{% if allowed_actions.edit %}
|
{% if allowed_actions.edit %}
|
||||||
<li><a href="{% model_url motion 'edit' %}"><i class="icon-pencil"></i> {% trans 'Edit motion' %}</a></li>
|
<li><a href="{{ motion|absolute_url:'edit' }}"><i class="icon-pencil"></i> {% trans 'Edit motion' %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.motion.can_manage_motion %}
|
{% if perms.motion.can_manage_motion %}
|
||||||
<!-- delete -->
|
<!-- delete -->
|
||||||
{% if allowed_actions.delete %}
|
{% if allowed_actions.delete %}
|
||||||
<li><a href="{% model_url motion 'delete' %}"><i class="icon-remove"></i> {% trans 'Delete motion' %}</a></li>
|
<li><a href="{{ motion|absolute_url:'delete' }}"><i class="icon-remove"></i> {% trans 'Delete motion' %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- create agenda item -->
|
<!-- create agenda item -->
|
||||||
@ -68,14 +68,14 @@
|
|||||||
<p><span class="label label-warning">
|
<p><span class="label label-warning">
|
||||||
<i class="icon-warning-sign icon-white"></i> {% trans "This is not the newest version." %}
|
<i class="icon-warning-sign icon-white"></i> {% trans "This is not the newest version." %}
|
||||||
</span>
|
</span>
|
||||||
<a href="{% model_url last_version %}" class="btn btn-small">{% trans "Go to the newest version" %}
|
<a href="{{ last_version|absolute_url }}" class="btn btn-small">{% trans "Go to the newest version" %}
|
||||||
(# {{ last_version.version_number }})</a></p>
|
(# {{ last_version.version_number }})</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if version.version_number != active_version.version_number %}
|
{% if version.version_number != active_version.version_number %}
|
||||||
<p><span class="label label-warning">
|
<p><span class="label label-warning">
|
||||||
<i class="icon-warning-sign icon-white"></i> {% trans "This version is not authorized." %}
|
<i class="icon-warning-sign icon-white"></i> {% trans "This version is not authorized." %}
|
||||||
</span>
|
</span>
|
||||||
<a href="{% model_url active_version %}" class="btn btn-small">{% trans "Go to the authorized version" %}
|
<a href="{{ active_version|absolute_url }}" class="btn btn-small">{% trans "Go to the authorized version" %}
|
||||||
(# {{ active_version.version_number }})</a></p>
|
(# {{ active_version.version_number }})</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
@ -123,11 +123,11 @@
|
|||||||
<input type="radio" value="{{ version.version_number }}" name="rev2">
|
<input type="radio" value="{{ version.version_number }}" name="rev2">
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% model_url version %}" title="{% trans 'Show' %}" class="btn btn-mini">
|
<a href="{{ version|absolute_url }}" title="{% trans 'Show' %}" class="btn btn-mini">
|
||||||
<i class="icon-search"></i>
|
<i class="icon-search"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if version != motion.active_version %}
|
{% if version != motion.active_version %}
|
||||||
<a href="{% model_url version 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini">
|
<a href="{{ version|absolute_url:'delete' }}" title="{% trans 'Delete' %}" class="btn btn-mini">
|
||||||
<i class="icon-remove"></i>
|
<i class="icon-remove"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -166,7 +166,7 @@
|
|||||||
<!-- Submitter -->
|
<!-- Submitter -->
|
||||||
<h5>{% trans "Submitter" %}:</h5>
|
<h5>{% trans "Submitter" %}:</h5>
|
||||||
{% for submitter in motion.submitter.all %}
|
{% for submitter in motion.submitter.all %}
|
||||||
<a href="{% model_url submitter.person 'view' %}">{{ submitter }}</a>{% if not forloop.last %}, {% endif %}
|
<a href="{{ submitter.person|absolute_url }}">{{ submitter }}</a>{% if not forloop.last %}, {% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<!-- Supporters -->
|
<!-- Supporters -->
|
||||||
@ -177,7 +177,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<ol>
|
<ol>
|
||||||
{% for supporter in motion.supporter.all %}
|
{% for supporter in motion.supporter.all %}
|
||||||
<li><a href="{% model_url supporter.person 'view' %}">{{ supporter }}</a></li>
|
<li><a href="{{ supporter.person|absolute_url }}">{{ supporter }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -196,8 +196,8 @@
|
|||||||
{% if perms.motion.can_manage_motion or poll.has_votes %}
|
{% if perms.motion.can_manage_motion or poll.has_votes %}
|
||||||
<li><b>{{ poll.poll_number|ordinal|safe }} {% trans "vote" %}</b>
|
<li><b>{{ poll.poll_number|ordinal|safe }} {% trans "vote" %}</b>
|
||||||
{% if perms.motion.can_manage_motion %}
|
{% if perms.motion.can_manage_motion %}
|
||||||
<a class="btn btn-mini" href="{% model_url poll 'edit' %}" title="{% trans 'Edit Vote' %}"><i class="icon-pencil"></i></a>
|
<a class="btn btn-mini" href="{{ poll|absolute_url:'edit' }}" title="{% trans 'Edit Vote' %}"><i class="icon-pencil"></i></a>
|
||||||
<a class="btn btn-mini" href="{% model_url poll 'delete' %}" title="{% trans 'Delete Vote' %}"><i class="icon-remove"></i></a>
|
<a class="btn btn-mini" href="{{ poll|absolute_url:'delete' }}" title="{% trans 'Delete Vote' %}"><i class="icon-remove"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<br>
|
<br>
|
||||||
{% if poll.has_votes %}
|
{% if poll.has_votes %}
|
||||||
|
69
openslides/motion/templates/motion/slide.html
Normal file
69
openslides/motion/templates/motion/slide.html
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
<div id="sidebar">
|
||||||
|
<div class="box">
|
||||||
|
<!-- Status -->
|
||||||
|
<p><b>{% trans "Status" %}:</b><br>
|
||||||
|
{% trans motion.state.name %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- poll results -->
|
||||||
|
{% with motion.polls.all as polls %}
|
||||||
|
{% if polls.exists and polls.0.has_votes %}
|
||||||
|
<p><b>{% trans "Poll result" %}:</b>
|
||||||
|
{% for poll in polls %}
|
||||||
|
{% if poll.has_votes %}
|
||||||
|
{% if polls|length > 1 %}
|
||||||
|
<p>{{ forloop.counter }}. {% trans "Vote" %}:</p>
|
||||||
|
{% endif %}
|
||||||
|
{% with poll.get_options.0 as option %}
|
||||||
|
<div class="results">
|
||||||
|
<img src="{% static 'img/voting-yes.png' %}" title="{% trans 'Yes' %}"> {{ option.Yes }} <br>
|
||||||
|
<img src="{% static 'img/voting-no.png' %}" title="{% trans 'No' %}"> {{ option.No }} <br>
|
||||||
|
<img src="{% static 'img/voting-abstention.png' %}" title="{% trans 'Abstention' %}"> {{ option.Abstain }}<br>
|
||||||
|
<img src="{% static 'img/voting-invalid.png' %}" title="{% trans 'Invalid' %}"> {{ poll.print_votesinvalid }}<br>
|
||||||
|
<hr>
|
||||||
|
<img src="{% static 'img/voting-total.png' %}" title="{% trans 'Votes cast' %}"> {{ poll.print_votescast }}
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
{% else %}
|
||||||
|
{% if poll|length == 1 %}
|
||||||
|
<i>{% trans "No poll results available." %}</i>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<!-- Submitter -->
|
||||||
|
<p><b>{% trans "Submitter" %}:</b><br>
|
||||||
|
{% for submitter in motion.submitter.all %}
|
||||||
|
{{ submitter.person }}{% if not forloop.last %},<br> {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>
|
||||||
|
{% if motion.identifier %}
|
||||||
|
{% trans "Motion" %} {{ motion.identifier }}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Motion" %} [---]
|
||||||
|
{% endif %}
|
||||||
|
<small>
|
||||||
|
{% if motion.last_version.version_number > 1 %}({% trans 'Version' %} {{ motion.active_version.version_number }}){% endif %}
|
||||||
|
</small>
|
||||||
|
</h1>
|
||||||
|
<b>{{ motion.active_version.title }}</b>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<div class="text">{{ motion.active_version.text|safe }}</div>
|
||||||
|
{% if motion.active_version.reason %}
|
||||||
|
<br>
|
||||||
|
<div class="reason"><p><b>{% trans "Reason" %}:</b></p>
|
||||||
|
{{ motion.active_version.reason|safe }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
@ -3,17 +3,17 @@
|
|||||||
|
|
||||||
<ul style="line-height: 180%">
|
<ul style="line-height: 180%">
|
||||||
{% for motion in motions %}
|
{% for motion in motions %}
|
||||||
<li class="{% if motion.active %}activeline{% endif %}">
|
<li class="{% if motion.is_active_slide %}activeline{% endif %}">
|
||||||
<a href="{% url 'projector_activate_slide' motion.sid %}" class="activate_link btn {% if motion.active %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
<a href="{{ motion|absolute_url:'projector' }}" class="activate_link btn {% if motion.is_active_slide %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
||||||
<i class="icon-facetime-video {% if motion.active %}icon-white{% endif %}"></i>
|
<i class="icon-facetime-video {% if motion.is_active_slide %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% model_url motion 'edit' %}" title="{% trans 'Edit' %}" class="btn btn-mini right">
|
<a href="{{ motion|absolute_url:'edit' }}" title="{% trans 'Edit' %}" class="btn btn-mini right">
|
||||||
<i class="icon-pencil"></i>
|
<i class="icon-pencil"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'projctor_preview_slide' motion.sid %}" title="{% trans 'Preview' %}" class="btn btn-mini right">
|
<a href="{{ motion|absolute_url:'projector_preview' }}" title="{% trans 'Preview' %}" class="btn btn-mini right">
|
||||||
<i class="icon-search"></i>
|
<i class="icon-search"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% model_url motion %}">
|
<a href="{{ motion|absolute_url }}">
|
||||||
{{ motion.identifier|add:' | '|default:'' }}
|
{{ motion.identifier|add:' | '|default:'' }}
|
||||||
{{ motion }}
|
{{ motion }}
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
{% extends "base-projector.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
{% block title %}{{ block.super }} - {% trans "Motion" %} {{ motion.identifier }}{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
<div id="sidebar">
|
|
||||||
<div class="box">
|
|
||||||
<!-- Status -->
|
|
||||||
<p><b>{% trans "Status" %}:</b><br>
|
|
||||||
{% trans motion.state.name %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- poll results -->
|
|
||||||
{% with motion.polls.all as polls %}
|
|
||||||
{% if polls.exists and polls.0.has_votes %}
|
|
||||||
<p><b>{% trans "Poll result" %}:</b>
|
|
||||||
{% for poll in polls %}
|
|
||||||
{% if poll.has_votes %}
|
|
||||||
{% if polls|length > 1 %}
|
|
||||||
<p>{{forloop.counter}}. {% trans "Vote" %}:</p>
|
|
||||||
{% endif %}
|
|
||||||
{% with poll.get_options.0 as option %}
|
|
||||||
<div class="results">
|
|
||||||
<img src="{% static 'img/voting-yes.png' %}" title="{% trans 'Yes' %}"> {{ option.Yes }} <br>
|
|
||||||
<img src="{% static 'img/voting-no.png' %}" title="{% trans 'No' %}"> {{ option.No }} <br>
|
|
||||||
<img src="{% static 'img/voting-abstention.png' %}" title="{% trans 'Abstention' %}"> {{ option.Abstain }}<br>
|
|
||||||
<img src="{% static 'img/voting-invalid.png' %}" title="{% trans 'Invalid' %}"> {{ poll.print_votesinvalid }}<br>
|
|
||||||
<hr>
|
|
||||||
<img src="{% static 'img/voting-total.png' %}" title="{% trans 'Votes cast' %}"> {{ poll.print_votescast }}
|
|
||||||
</div>
|
|
||||||
{% endwith %}
|
|
||||||
{% else %}
|
|
||||||
{% if poll|length == 1 %}
|
|
||||||
<i>{% trans "No poll results available." %}</i>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
<!-- Submitter -->
|
|
||||||
<p><b>{% trans "Submitter" %}:</b><br>
|
|
||||||
{% for submitter in motion.submitter.all %}
|
|
||||||
{{ submitter.person }}{% if not forloop.last %},<br> {% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1>
|
|
||||||
{% if motion.identifier %}
|
|
||||||
{% trans "Motion" %} {{ motion.identifier }}
|
|
||||||
{% else %}
|
|
||||||
{% trans "Motion" %} [---]
|
|
||||||
{% endif %}
|
|
||||||
<small>
|
|
||||||
{% if motion.last_version.version_number > 1 %}({% trans 'Version' %} {{ motion.active_version.version_number }}){% endif %}
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
<b>{{ motion.active_version.title }}</b>
|
|
||||||
<hr>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block scrollcontent %}
|
|
||||||
<p>
|
|
||||||
<div class="text">{{ motion.active_version.text|safe }}</div>
|
|
||||||
{% if motion.active_version.reason %}
|
|
||||||
<br>
|
|
||||||
<div class="reason"><p><b>{% trans "Reason" %}:</b></p>
|
|
||||||
{{ motion.active_version.reason|safe }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
{% endblock %}
|
|
@ -30,7 +30,7 @@ from openslides.utils.template import Tab
|
|||||||
from openslides.utils.utils import html_strong, htmldiff
|
from openslides.utils.utils import html_strong, htmldiff
|
||||||
from openslides.poll.views import PollFormView
|
from openslides.poll.views import PollFormView
|
||||||
from openslides.projector.api import get_active_slide
|
from openslides.projector.api import get_active_slide
|
||||||
from openslides.projector.projector import Widget, SLIDE
|
from openslides.projector.projector import Widget
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.agenda.views import CreateRelatedAgendaItemView as _CreateRelatedAgendaItemView
|
from openslides.agenda.views import CreateRelatedAgendaItemView as _CreateRelatedAgendaItemView
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
from django.utils.translation import ugettext_noop
|
from django.utils.translation import ugettext_noop
|
||||||
|
|
||||||
from . import signals
|
from . import signals
|
||||||
|
from . import slides
|
||||||
|
|
||||||
|
|
||||||
NAME = ugettext_noop('Participant')
|
NAME = ugettext_noop('Participant')
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
:license: GNU GPL, see LICENSE for more details.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
from django.contrib.auth.models import User as DjangoUser, Group as DjangoGroup, Permission
|
from django.contrib.auth.models import User as DjangoUser, Group as DjangoGroup, Permission
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@ -20,13 +21,13 @@ from django.utils.translation import ugettext_lazy, ugettext_noop
|
|||||||
from openslides.utils.person import PersonMixin, Person
|
from openslides.utils.person import PersonMixin, Person
|
||||||
from openslides.utils.person.signals import receive_persons
|
from openslides.utils.person.signals import receive_persons
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.projector.api import register_slidemodel
|
from openslides.projector.models import SlideMixin
|
||||||
from openslides.projector.projector import SlideMixin
|
|
||||||
|
|
||||||
|
|
||||||
class User(PersonMixin, Person, SlideMixin, DjangoUser):
|
class User(SlideMixin, PersonMixin, Person, DjangoUser):
|
||||||
prefix = 'user' # This is for the slides
|
slide_callback_name = 'user'
|
||||||
person_prefix = 'user'
|
person_prefix = 'user'
|
||||||
|
|
||||||
GENDER_CHOICES = (
|
GENDER_CHOICES = (
|
||||||
('male', ugettext_lazy('Male')),
|
('male', ugettext_lazy('Male')),
|
||||||
('female', ugettext_lazy('Female')),
|
('female', ugettext_lazy('Female')),
|
||||||
@ -55,6 +56,30 @@ class User(PersonMixin, Person, SlideMixin, DjangoUser):
|
|||||||
max_length=100, blank=True, default='',
|
max_length=100, blank=True, default='',
|
||||||
verbose_name=ugettext_lazy("Default password"))
|
verbose_name=ugettext_lazy("Default password"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
permissions = (
|
||||||
|
('can_see_participant', ugettext_noop('Can see participants')),
|
||||||
|
('can_manage_participant', ugettext_noop('Can manage participants')),
|
||||||
|
)
|
||||||
|
ordering = ('last_name',)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
if self.name_suffix:
|
||||||
|
return u"%s (%s)" % (self.clean_name, self.name_suffix)
|
||||||
|
return u"%s" % self.clean_name
|
||||||
|
|
||||||
|
def get_absolute_url(self, link='detail'):
|
||||||
|
"""
|
||||||
|
Return the URL to the user.
|
||||||
|
"""
|
||||||
|
if link == 'detail' or link == 'view':
|
||||||
|
return reverse('user_view', args=[str(self.id)])
|
||||||
|
if link == 'edit':
|
||||||
|
return reverse('user_edit', args=[str(self.id)])
|
||||||
|
if link == 'delete':
|
||||||
|
return reverse('user_delete', args=[str(self.id)])
|
||||||
|
return super(User, self).get_absolute_url(link)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def clean_name(self):
|
def clean_name(self):
|
||||||
if self.title:
|
if self.title:
|
||||||
@ -86,50 +111,9 @@ class User(PersonMixin, Person, SlideMixin, DjangoUser):
|
|||||||
return self.first_name.lower()
|
return self.first_name.lower()
|
||||||
return self.last_name.lower()
|
return self.last_name.lower()
|
||||||
|
|
||||||
@models.permalink
|
|
||||||
def get_absolute_url(self, link='detail'):
|
|
||||||
"""
|
|
||||||
Return the URL to this user.
|
|
||||||
|
|
||||||
link can be:
|
class Group(SlideMixin, PersonMixin, Person, DjangoGroup):
|
||||||
* detail
|
slide_callback_name = 'group'
|
||||||
* edit
|
|
||||||
* delete
|
|
||||||
"""
|
|
||||||
if link == 'detail' or link == 'view':
|
|
||||||
return ('user_view', [str(self.id)])
|
|
||||||
if link == 'edit':
|
|
||||||
return ('user_edit', [str(self.id)])
|
|
||||||
if link == 'delete':
|
|
||||||
return ('user_delete', [str(self.id)])
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
if self.name_suffix:
|
|
||||||
return u"%s (%s)" % (self.clean_name, self.name_suffix)
|
|
||||||
return u"%s" % self.clean_name
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
# Rename permissions
|
|
||||||
permissions = (
|
|
||||||
('can_see_participant', ugettext_noop('Can see participants')), # TODO: Add plural s to the codestring
|
|
||||||
('can_manage_participant', ugettext_noop('Can manage participants')), # TODO: Add plural s to the codestring
|
|
||||||
)
|
|
||||||
ordering = ('last_name',)
|
|
||||||
|
|
||||||
def slide(self):
|
|
||||||
"""
|
|
||||||
Returns a map with the data for the slides.
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
'shown_user': self,
|
|
||||||
'title': self.clean_name,
|
|
||||||
'template': 'projector/UserSlide.html'}
|
|
||||||
|
|
||||||
register_slidemodel(User)
|
|
||||||
|
|
||||||
|
|
||||||
class Group(PersonMixin, Person, SlideMixin, DjangoGroup):
|
|
||||||
prefix = 'group' # This is for the slides
|
|
||||||
person_prefix = 'group'
|
person_prefix = 'group'
|
||||||
|
|
||||||
django_group = models.OneToOneField(DjangoGroup, editable=False, parent_link=True)
|
django_group = models.OneToOneField(DjangoGroup, editable=False, parent_link=True)
|
||||||
@ -138,39 +122,23 @@ class Group(PersonMixin, Person, SlideMixin, DjangoGroup):
|
|||||||
help_text=ugettext_lazy('For example as submitter of a motion.'))
|
help_text=ugettext_lazy('For example as submitter of a motion.'))
|
||||||
description = models.TextField(blank=True, verbose_name=ugettext_lazy("Description"))
|
description = models.TextField(blank=True, verbose_name=ugettext_lazy("Description"))
|
||||||
|
|
||||||
@models.permalink
|
class Meta:
|
||||||
def get_absolute_url(self, link='detail'):
|
ordering = ('name',)
|
||||||
"""
|
|
||||||
Return the URL to this user group.
|
|
||||||
|
|
||||||
link can be:
|
|
||||||
* view
|
|
||||||
* edit
|
|
||||||
* delete
|
|
||||||
"""
|
|
||||||
if link == 'detail' or link == 'view':
|
|
||||||
return ('user_group_view', [str(self.id)])
|
|
||||||
if link == 'edit':
|
|
||||||
return ('user_group_edit', [str(self.id)])
|
|
||||||
if link == 'delete':
|
|
||||||
return ('user_group_delete', [str(self.id)])
|
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return unicode(self.name)
|
return unicode(self.name)
|
||||||
|
|
||||||
class Meta:
|
def get_absolute_url(self, link='detail'):
|
||||||
ordering = ('name',)
|
|
||||||
|
|
||||||
def slide(self):
|
|
||||||
"""
|
"""
|
||||||
Returns a map with the data for the slides.
|
Return the URL to the user group.
|
||||||
"""
|
"""
|
||||||
return {
|
if link == 'detail' or link == 'view':
|
||||||
'group': self,
|
return reverse('user_group_view', args=[str(self.id)])
|
||||||
'title': self.name,
|
if link == 'update' or link == 'edit':
|
||||||
'template': 'projector/GroupSlide.html'}
|
return reverse('user_group_edit', args=[str(self.id)])
|
||||||
|
if link == 'delete':
|
||||||
register_slidemodel(Group)
|
return reverse('user_group_delete', args=[str(self.id)])
|
||||||
|
return super(Group, self).get_absolute_url(link)
|
||||||
|
|
||||||
|
|
||||||
class UsersAndGroupsToPersons(object):
|
class UsersAndGroupsToPersons(object):
|
||||||
|
48
openslides/participant/slides.py
Normal file
48
openslides/participant/slides.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
openslides.motion.slides
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Defines the slides for the User app.
|
||||||
|
|
||||||
|
:copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS.
|
||||||
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
from openslides.projector.api import register_slide
|
||||||
|
from .models import User, Group
|
||||||
|
|
||||||
|
|
||||||
|
def user_slide(**kwargs):
|
||||||
|
"""
|
||||||
|
Slide for the user model.
|
||||||
|
"""
|
||||||
|
user_pk = kwargs.get('pk', None)
|
||||||
|
try:
|
||||||
|
user = User.objects.get(pk=user_pk)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
context = {'shown_user': user}
|
||||||
|
return render_to_string('participant/user_slide.html', context)
|
||||||
|
|
||||||
|
register_slide(User.slide_callback_name, user_slide)
|
||||||
|
|
||||||
|
|
||||||
|
def group_slide(**kwargs):
|
||||||
|
"""
|
||||||
|
Slide for the group model.
|
||||||
|
"""
|
||||||
|
group_pk = kwargs.get('pk', None)
|
||||||
|
try:
|
||||||
|
group = Group.objects.get(pk=group_pk)
|
||||||
|
except Group.DoesNotExist:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
context = {'group': group}
|
||||||
|
return render_to_string('participant/group_slide.html', context)
|
||||||
|
|
||||||
|
register_slide(Group.slide_callback_name, group_slide)
|
@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
{% for member in group_members %}
|
{% for member in group_members %}
|
||||||
<li><a href="{% model_url member 'view' %}">{{ member }}</a></li>
|
<li><a href="{{ member|absolute_url }}">{{ member }}</a></li>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<p>{% trans "No members available." %}</p>
|
<p>{% trans "No members available." %}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% model_url group 'view' %}">{% trans group.name %}</a>
|
<a href="{{ group|absolute_url }}">{% trans group.name %}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span style="width: 1px; white-space: nowrap;">
|
<span style="width: 1px; white-space: nowrap;">
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div class="item_fullscreen">{% trans group.name %}
|
||||||
|
<span>
|
||||||
|
<p><i>{{ group.user_set.all.count }} {% trans "participants" %}</i></p>
|
||||||
|
</span>
|
||||||
|
</div>
|
@ -3,17 +3,17 @@
|
|||||||
|
|
||||||
<ul style="line-height: 180%">
|
<ul style="line-height: 180%">
|
||||||
{% for group in groups %}
|
{% for group in groups %}
|
||||||
<li class="{% if group.active %}activeline{% endif %}">
|
<li class="{% if group.is_active_slide %}activeline{% endif %}">
|
||||||
<a href="{% url 'projector_activate_slide' group.sid %}" class="activate_link btn {% if group.active %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
<a href="{{ group|absolute_url:'projector' }}" class="activate_link btn {% if group.is_active_slide %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
||||||
<i class="icon-facetime-video {% if group.active %}icon-white{% endif %}"></i>
|
<i class="icon-facetime-video {% if group.is_active_slide %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% model_url group 'edit' %}" title="{% trans 'Edit' %}" class="btn btn-mini right">
|
<a href="{{ group|absolute_url:'update' }}" title="{% trans 'Edit' %}" class="btn btn-mini right">
|
||||||
<i class="icon-pencil"></i>
|
<i class="icon-pencil"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'projctor_preview_slide' group.sid %}" title="{% trans 'Preview' %}" class="btn btn-mini right">
|
<a href="{{ group|absolute_url:'projector_preview' }}" title="{% trans 'Preview' %}" class="btn btn-mini right">
|
||||||
<i class="icon-search"></i>
|
<i class="icon-search"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% model_url group 'view' %}">{% trans group.name %}</a>
|
<a href="{{ group|absolute_url }}">{% trans group.name %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
15
openslides/participant/templates/participant/user_slide.html
Normal file
15
openslides/participant/templates/participant/user_slide.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
<div class="item_fullscreen">{{ shown_user }}
|
||||||
|
<span>
|
||||||
|
{% if shown_user.committee %}
|
||||||
|
<p>{{ shown_user.committee }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<p>
|
||||||
|
{% if shown_user.groups.all %}
|
||||||
|
{% for group in shown_user.groups.all %}
|
||||||
|
{% trans group.name %}{% if not forloop.last %}, {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
|
</div>
|
@ -1,19 +1,18 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load tags %}
|
{% load tags %}
|
||||||
|
|
||||||
<ul style="line-height: 180%">
|
<ul style="line-height: 180%">
|
||||||
{% for user in users %}
|
{% for user in users %}
|
||||||
<li class="{% if user.active %}activeline{% endif %}">
|
<li class="{% if user.is_active_slide %}activeline{% endif %}">
|
||||||
<a href="{% url 'projector_activate_slide' user.sid %}" class="activate_link btn {% if user.active %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
<a href="{{ user|absolute_url:'projector' }}" class="activate_link btn {% if user.is_active_slide %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
||||||
<i class="icon-facetime-video {% if user.active %}icon-white{% endif %}"></i>
|
<i class="icon-facetime-video {% if user.is_active_slide %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% model_url user 'edit' %}" title="{% trans 'Edit' %}" class="btn btn-mini right">
|
<a href="{{ user|absolute_url:'edit' }}" title="{% trans 'Edit' %}" class="btn btn-mini right">
|
||||||
<i class="icon-pencil"></i>
|
<i class="icon-pencil"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'projctor_preview_slide' user.sid %}" title="{% trans 'Preview' %}" class="btn btn-mini right">
|
<a href="{{ user|absolute_url:'projector_preview' }}" title="{% trans 'Preview' %}" class="btn btn-mini right">
|
||||||
<i class="icon-search"></i>
|
<i class="icon-search"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% model_url user 'view' %}">{{ user }}</a>
|
<a href="{{ user|absolute_url }}">{{ user }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<li>{% trans 'No participants available.' %}</li>
|
<li>{% trans 'No participants available.' %}</li>
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
{% extends "base-projector.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{{ title }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="item_fullscreen">{% trans group.name %}
|
|
||||||
<span>
|
|
||||||
<p><i>{{ group.user_set.all.count }} {% trans "participants" %}</i></p>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -1,22 +0,0 @@
|
|||||||
{% extends "base-projector.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{{ title }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="item_fullscreen">{{ shown_user }}
|
|
||||||
<span>
|
|
||||||
{% if shown_user.committee %}
|
|
||||||
<p>{{ shown_user.committee }}</p>
|
|
||||||
{% endif %}
|
|
||||||
<p>
|
|
||||||
{% if shown_user.groups.all %}
|
|
||||||
{% for group in shown_user.groups.all %}
|
|
||||||
{% trans group.name %}{% if not forloop.last %}, {% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -43,11 +43,12 @@ from openslides.utils.views import (
|
|||||||
RedirectView, SingleObjectMixin, ListView, QuestionMixin, DetailView)
|
RedirectView, SingleObjectMixin, ListView, QuestionMixin, DetailView)
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.projector.projector import Widget
|
from openslides.projector.projector import Widget
|
||||||
from openslides.participant.api import gen_username, gen_password, import_users
|
|
||||||
from openslides.participant.forms import (
|
from .api import gen_username, gen_password, import_users
|
||||||
|
from .forms import (
|
||||||
UserCreateForm, UserUpdateForm, UsersettingsForm,
|
UserCreateForm, UserUpdateForm, UsersettingsForm,
|
||||||
UserImportForm, GroupForm)
|
UserImportForm, GroupForm)
|
||||||
from openslides.participant.models import User, Group, get_protected_perm
|
from .models import User, Group, get_protected_perm
|
||||||
|
|
||||||
|
|
||||||
class UserOverview(ListView):
|
class UserOverview(ListView):
|
||||||
|
@ -10,4 +10,4 @@
|
|||||||
:license: GNU GPL, see LICENSE for more details.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from . import signals
|
from . import signals, slides
|
||||||
|
@ -10,114 +10,146 @@
|
|||||||
:license: GNU GPL, see LICENSE for more details.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
from django.utils.importlib import import_module
|
from django.utils.importlib import import_module
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.projector.projector import SLIDE, Slide
|
from openslides.utils.tornado_webserver import ProjectorSocketHandler
|
||||||
|
from .signals import projector_overlays
|
||||||
|
|
||||||
|
|
||||||
def split_sid(sid):
|
slide_callback = {}
|
||||||
|
"""
|
||||||
|
A dictonary where the key is the name of a slide, and the value is a
|
||||||
|
callable object which returns the html code for a slide.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def update_projector():
|
||||||
"""
|
"""
|
||||||
Slit a SID in the model-part and in the model-id
|
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 overlay on the projector.
|
||||||
|
|
||||||
|
Checks if the overlay is activated and updates it in this case.
|
||||||
|
|
||||||
|
'overlay' has to be an overlay object, the name of a ovleray or None.
|
||||||
|
|
||||||
|
If 'overlay' is None, then all overlays are updated.
|
||||||
|
"""
|
||||||
|
if isinstance(overlay, basestring):
|
||||||
|
overlay = get_overlays()[overlay]
|
||||||
|
if overlay is None:
|
||||||
|
overlays = [overlay for overlay in get_overlays().values()]
|
||||||
|
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 get_projector_content(slide_dict=None):
|
||||||
|
"""
|
||||||
|
Returns the HTML-Content block of the projector.
|
||||||
|
"""
|
||||||
|
if slide_dict is None:
|
||||||
|
slide_dict = config['projector_active_slide'].copy()
|
||||||
|
callback = slide_dict.pop('callback', None)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = sid.split('-')
|
return slide_callback[callback](**slide_dict)
|
||||||
except AttributeError:
|
|
||||||
return None
|
|
||||||
if len(data) == 2:
|
|
||||||
model = data[0]
|
|
||||||
id = data[1]
|
|
||||||
return (model, id)
|
|
||||||
if len(data) == 1:
|
|
||||||
try:
|
|
||||||
return (SLIDE[data[0]].key, None)
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
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:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if id is not None:
|
|
||||||
try:
|
|
||||||
object = SLIDE[key].model.objects.get(pk=id)
|
|
||||||
except SLIDE[key].model.DoesNotExist:
|
|
||||||
return None
|
|
||||||
if element:
|
|
||||||
return object
|
|
||||||
return object.slide()
|
|
||||||
try:
|
|
||||||
return SLIDE[key].func()
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return default_slide()
|
||||||
|
|
||||||
|
|
||||||
def get_active_slide(only_sid=False):
|
def default_slide():
|
||||||
"""
|
"""
|
||||||
Returns the active slide. If no slide is active, or it can not find an Item,
|
Returns the HTML Code for the default slide.
|
||||||
return None
|
|
||||||
|
|
||||||
if only_sid is True, returns only the id of this item. Returns None if not
|
|
||||||
Item is active.
|
|
||||||
"""
|
"""
|
||||||
sid = config["presentation"]
|
return render_to_string('projector/default_slide.html')
|
||||||
|
|
||||||
if only_sid:
|
|
||||||
return sid
|
|
||||||
return get_slide_from_sid(sid)
|
|
||||||
|
|
||||||
|
|
||||||
def set_active_slide(sid, argument=None):
|
def get_overlays():
|
||||||
|
"""
|
||||||
|
Returns all overlay objects.
|
||||||
|
|
||||||
|
The returned value is a dictonary with the name of the overlay as key, and
|
||||||
|
the overlay object as value.
|
||||||
|
"""
|
||||||
|
overlays = {}
|
||||||
|
for receiver, overlay in projector_overlays.send(sender='get_overlays'):
|
||||||
|
overlays[overlay.name] = overlay
|
||||||
|
return overlays
|
||||||
|
|
||||||
|
|
||||||
|
def get_projector_overlays():
|
||||||
|
"""
|
||||||
|
Returns the HTML code for all active overlays.
|
||||||
|
"""
|
||||||
|
overlays = [{'name': key, 'html': overlay.get_projector_html()}
|
||||||
|
for key, overlay in get_overlays().items()
|
||||||
|
if overlay.is_active()]
|
||||||
|
return render_to_string('projector/all_overlays.html', {'overlays': overlays})
|
||||||
|
|
||||||
|
|
||||||
|
def get_projector_overlays_js():
|
||||||
|
"""
|
||||||
|
Returns JS-Code for the overlays.
|
||||||
|
|
||||||
|
The retuned value is a list of json objects.
|
||||||
|
"""
|
||||||
|
js = []
|
||||||
|
for key, overlay in get_overlays().items():
|
||||||
|
if overlay.is_active():
|
||||||
|
overlay_js = overlay.get_javascript()
|
||||||
|
if overlay_js:
|
||||||
|
js.append(json.dumps(overlay_js))
|
||||||
|
return js
|
||||||
|
|
||||||
|
|
||||||
|
def register_slide(name, callback):
|
||||||
|
"""
|
||||||
|
Register a function as slide callback.
|
||||||
|
"""
|
||||||
|
slide_callback[name] = callback
|
||||||
|
|
||||||
|
|
||||||
|
def set_active_slide(callback, kwargs={}):
|
||||||
"""
|
"""
|
||||||
Set the active Slide.
|
Set the active Slide.
|
||||||
|
|
||||||
|
callback: The name of the slide callback.
|
||||||
|
kwargs: Keyword arguments for the slide callback.
|
||||||
"""
|
"""
|
||||||
config["presentation"] = sid
|
kwargs.update(callback=callback)
|
||||||
config['presentation_argument'] = argument
|
config['projector_active_slide'] = kwargs
|
||||||
clear_projector_cache()
|
update_projector()
|
||||||
|
update_projector_overlay(None)
|
||||||
|
|
||||||
|
|
||||||
def clear_projector_cache():
|
def get_active_slide():
|
||||||
cache.delete('projector_content')
|
|
||||||
cache.delete('projector_scrollcontent')
|
|
||||||
cache.delete('projector_data')
|
|
||||||
|
|
||||||
|
|
||||||
def register_slidemodel(model, model_name=None, control_template=None, weight=0):
|
|
||||||
"""
|
"""
|
||||||
Register a Model as a slide.
|
Returns the dictonary, witch defindes the active slide.
|
||||||
"""
|
"""
|
||||||
# TODO: control_template should never be None
|
return config['projector_active_slide']
|
||||||
if model_name is None:
|
|
||||||
model_name = model.prefix
|
|
||||||
|
|
||||||
category = model.__module__.split('.')[0]
|
|
||||||
SLIDE[model_name] = Slide(model_slide=True, model=model, category=category,
|
|
||||||
key=model.prefix, model_name=model_name,
|
|
||||||
control_template=control_template, weight=weight)
|
|
||||||
|
|
||||||
|
|
||||||
def register_slidefunc(key, func, control_template=None, weight=0, name=''):
|
|
||||||
"""
|
|
||||||
Register a function for as a slide.
|
|
||||||
"""
|
|
||||||
if control_template is None:
|
|
||||||
control_template = 'projector/default_control_slidefunc.html'
|
|
||||||
category = func.__module__.split('.')[0]
|
|
||||||
SLIDE[key] = Slide(model_slide=False, func=func, category=category,
|
|
||||||
key=key, control_template=control_template, weight=weight,
|
|
||||||
name=name,)
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_widgets(request, session=False):
|
def get_all_widgets(request, session=False):
|
||||||
@ -135,9 +167,11 @@ def get_all_widgets(request, session=False):
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
module_widgets = mod.get_widgets(request)
|
mod_get_widgets = mod.get_widgets
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
continue
|
continue
|
||||||
|
else:
|
||||||
|
module_widgets = mod_get_widgets(request)
|
||||||
all_module_widgets.extend(module_widgets)
|
all_module_widgets.extend(module_widgets)
|
||||||
all_module_widgets.sort(key=lambda widget: widget.default_weight)
|
all_module_widgets.sort(key=lambda widget: widget.default_weight)
|
||||||
session_widgets = request.session.get('widgets', {})
|
session_widgets = request.session.get('widgets', {})
|
||||||
|
@ -13,34 +13,92 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
from openslides.projector.api import register_slidemodel
|
from openslides.utils.utils import int_or_none
|
||||||
from openslides.projector.projector import SlideMixin
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectorSlide(models.Model, SlideMixin):
|
class SlideMixin(object):
|
||||||
|
"""
|
||||||
|
A Mixin for a Django-Model, for making the model a slide.
|
||||||
|
"""
|
||||||
|
|
||||||
|
slide_callback_name = None
|
||||||
|
"""
|
||||||
|
Name of the callback to render the model as slide.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Updates the projector, if 'self' is the active slide.
|
||||||
|
"""
|
||||||
|
from openslides.projector.api import update_projector
|
||||||
|
super(SlideMixin, self).save(*args, **kwargs)
|
||||||
|
if self.is_active_slide():
|
||||||
|
update_projector()
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
from openslides.projector.api import update_projector
|
||||||
|
super(SlideMixin, self).delete(*args, **kwargs)
|
||||||
|
if self.is_active_slide():
|
||||||
|
update_projector()
|
||||||
|
|
||||||
|
def get_slide_callback_name(self):
|
||||||
|
"""
|
||||||
|
Returns the name of the slide callback, which is used to render the slide.
|
||||||
|
"""
|
||||||
|
if self.slide_callback_name is None:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
"SlideMixin requires either a definition of 'slide_callback_name'"
|
||||||
|
" or an implementation of 'get_slide_callback_name()'")
|
||||||
|
else:
|
||||||
|
return self.slide_callback_name
|
||||||
|
|
||||||
|
def get_absolute_url(self, link='projector'):
|
||||||
|
"""
|
||||||
|
Return the url to activate the slide, if link == 'projector'
|
||||||
|
"""
|
||||||
|
if link == 'projector':
|
||||||
|
url_name = 'projector_activate_slide'
|
||||||
|
elif link == 'projector_preview':
|
||||||
|
url_name = 'projector_preview'
|
||||||
|
|
||||||
|
if link in ('projector', 'projector_preview'):
|
||||||
|
return '%s?pk=%d' % (
|
||||||
|
reverse(url_name,
|
||||||
|
args=[self.get_slide_callback_name()]),
|
||||||
|
self.pk)
|
||||||
|
return super(SlideMixin, self).get_absolute_url(link)
|
||||||
|
|
||||||
|
def is_active_slide(self):
|
||||||
|
"""
|
||||||
|
Return True, if the the slide is the active slide.
|
||||||
|
"""
|
||||||
|
from openslides.projector.api import get_active_slide
|
||||||
|
active_slide = get_active_slide()
|
||||||
|
pk = int_or_none(active_slide.get('pk', None))
|
||||||
|
|
||||||
|
return (active_slide['callback'] == self.get_slide_callback_name() and
|
||||||
|
self.pk == pk)
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectorSlide(SlideMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
Model for Slides, only for the projector. Also called custom slides.
|
Model for Slides, only for the projector. Also called custom slides.
|
||||||
"""
|
"""
|
||||||
prefix = 'ProjectorSlide'
|
slide_callback_name = 'projector_slide'
|
||||||
|
|
||||||
title = models.CharField(max_length=256, verbose_name=ugettext_lazy("Title"))
|
title = models.CharField(max_length=256, verbose_name=ugettext_lazy("Title"))
|
||||||
text = models.TextField(null=True, blank=True, verbose_name=ugettext_lazy("Text"))
|
text = models.TextField(null=True, blank=True, verbose_name=ugettext_lazy("Text"))
|
||||||
weight = models.IntegerField(default=0, verbose_name=ugettext_lazy("Weight"))
|
weight = models.IntegerField(default=0, verbose_name=ugettext_lazy("Weight"))
|
||||||
|
|
||||||
def slide(self):
|
def get_absolute_url(self, link='update'):
|
||||||
return {
|
if link == 'edit' or link == 'update':
|
||||||
'slide': self,
|
return reverse('customslide_edit', args=[str(self.pk)])
|
||||||
'title': self.title,
|
|
||||||
'template': 'projector/ProjectorSlide.html',
|
|
||||||
}
|
|
||||||
|
|
||||||
@models.permalink
|
|
||||||
def get_absolute_url(self, link='edit'):
|
|
||||||
if link == 'edit':
|
|
||||||
return ('customslide_edit', [str(self.id)])
|
|
||||||
if link == 'delete':
|
if link == 'delete':
|
||||||
return ('customslide_delete', [str(self.id)])
|
return reverse('customslide_delete', args=[str(self.pk)])
|
||||||
|
return super(ProjectorSlide, self).get_absolute_url(link)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.title
|
return self.title
|
||||||
@ -51,6 +109,3 @@ class ProjectorSlide(models.Model, SlideMixin):
|
|||||||
('can_see_projector', ugettext_noop("Can see the projector")),
|
('can_see_projector', ugettext_noop("Can see the projector")),
|
||||||
('can_see_dashboard', ugettext_noop("Can see the dashboard")),
|
('can_see_dashboard', ugettext_noop("Can see the dashboard")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
register_slidemodel(ProjectorSlide, control_template='projector/control_customslide.html')
|
|
||||||
|
@ -20,97 +20,6 @@ from django.template import RequestContext
|
|||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
|
|
||||||
|
|
||||||
SLIDE = {}
|
|
||||||
|
|
||||||
|
|
||||||
class SlideMixin(object):
|
|
||||||
"""
|
|
||||||
A Mixin for a Django-Model, for making the model a slide.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def slide(self):
|
|
||||||
"""
|
|
||||||
Return a map with all Data for the Slide.
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
'slide': self,
|
|
||||||
'title': 'dummy-title',
|
|
||||||
'template': 'projector/default.html',
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def sid(self):
|
|
||||||
"""
|
|
||||||
Return the sid from this Slide
|
|
||||||
"""
|
|
||||||
return "%s-%d" % (self.prefix, self.id)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def active(self):
|
|
||||||
"""
|
|
||||||
Return True, if the the slide is the active slide.
|
|
||||||
"""
|
|
||||||
if self.id is None:
|
|
||||||
return False
|
|
||||||
from openslides.projector.api import get_active_slide
|
|
||||||
return get_active_slide(only_sid=True) == self.sid
|
|
||||||
|
|
||||||
def set_active(self):
|
|
||||||
"""
|
|
||||||
Appoint this item as the active slide.
|
|
||||||
"""
|
|
||||||
from openslides.projector.api import set_active_slide
|
|
||||||
set_active_slide(self.sid)
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
if self.active:
|
|
||||||
from api import clear_projector_cache
|
|
||||||
clear_projector_cache()
|
|
||||||
return super(SlideMixin, self).save(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
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=''):
|
|
||||||
"""
|
|
||||||
model_slide: Boolean if the value is a Model.
|
|
||||||
func: The function to call. Only if modelslide is False.
|
|
||||||
model: The model. Only if modelslide is True.
|
|
||||||
model_name: The name shown for the model.
|
|
||||||
category: The category to show this Slide.
|
|
||||||
key: the key in the slide object to find itself.
|
|
||||||
"""
|
|
||||||
self.model_slide = model_slide
|
|
||||||
self.func = func
|
|
||||||
self.model = model
|
|
||||||
self.model_name = model_name
|
|
||||||
self.category = category
|
|
||||||
self.key = key
|
|
||||||
self.control_template = control_template
|
|
||||||
self.weight = weight
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
@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:
|
|
||||||
return 'No Model'
|
|
||||||
|
|
||||||
|
|
||||||
class Widget(object):
|
class Widget(object):
|
||||||
"""
|
"""
|
||||||
Class for a Widget for the Projector-Tab.
|
Class for a Widget for the Projector-Tab.
|
||||||
@ -158,23 +67,52 @@ class Overlay(object):
|
|||||||
Represents an overlay which can be seen on the projector.
|
Represents an overlay which can be seen on the projector.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, get_widget_html, get_projector_html):
|
def __init__(self, name, get_widget_html, get_projector_html,
|
||||||
|
get_javascript=None, allways_active=False):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.widget_html_callback = get_widget_html
|
self.widget_html_callback = get_widget_html
|
||||||
self.projector_html_callback = get_projector_html
|
self.projector_html_callback = get_projector_html
|
||||||
|
self.javascript_callback = get_javascript
|
||||||
|
self.allways_active = allways_active
|
||||||
|
|
||||||
def get_widget_html(self):
|
def get_widget_html(self):
|
||||||
return self.widget_html_callback()
|
value = None
|
||||||
|
if self.widget_html_callback is not None:
|
||||||
|
value = self.widget_html_callback()
|
||||||
|
return value
|
||||||
|
|
||||||
def get_projector_html(self):
|
def get_projector_html(self):
|
||||||
try:
|
return self.get_html_wrapper(self.projector_html_callback())
|
||||||
return self._projector_html
|
|
||||||
except AttributeError:
|
def get_javascript(self):
|
||||||
self._projector_html = self.projector_html_callback()
|
if self.javascript_callback is None:
|
||||||
return self.get_projector_html()
|
value = {}
|
||||||
|
else:
|
||||||
|
value = self.javascript_callback()
|
||||||
|
return value
|
||||||
|
|
||||||
|
def get_html_wrapper(self, inner_html):
|
||||||
|
full_html = ''
|
||||||
|
if inner_html is not None:
|
||||||
|
full_html = '<div id="overlay_%s">%s</div>' % (self.name, inner_html)
|
||||||
|
return full_html
|
||||||
|
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
return self.name in config['projector_active_overlays']
|
return self.allways_active or self.name in config['projector_active_overlays']
|
||||||
|
|
||||||
|
def set_active(self, active):
|
||||||
|
"""
|
||||||
|
Publish or depublish the ovleray on the projector.
|
||||||
|
|
||||||
|
publish, if active is true,
|
||||||
|
depublish, if active is false.
|
||||||
|
"""
|
||||||
|
active_overlays = set(config['projector_active_overlays'])
|
||||||
|
if active:
|
||||||
|
active_overlays.add(self.name)
|
||||||
|
else:
|
||||||
|
active_overlays.discard(self.name)
|
||||||
|
config['projector_active_overlays'] = list(active_overlays)
|
||||||
|
|
||||||
def show_on_projector(self):
|
def show_on_projector(self):
|
||||||
return self.is_active() and self.get_projector_html() is not None
|
return self.is_active() and self.get_projector_html() is not None
|
||||||
|
@ -15,13 +15,12 @@ from django.dispatch import Signal, receiver
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.core.context_processors import csrf
|
from django.core.context_processors import csrf
|
||||||
|
from django.contrib.staticfiles.templatetags.staticfiles import static
|
||||||
|
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config, ConfigVariable, ConfigPage
|
||||||
from openslides.config.signals import config_signal
|
from openslides.config.signals import config_signal
|
||||||
from openslides.config.api import ConfigVariable, ConfigPage
|
|
||||||
|
|
||||||
from .projector import Overlay
|
from .projector import Overlay
|
||||||
from .api import clear_projector_cache
|
|
||||||
|
|
||||||
|
|
||||||
projector_overlays = Signal(providing_args=['request'])
|
projector_overlays = Signal(providing_args=['request'])
|
||||||
@ -34,13 +33,13 @@ def setup_projector_config_variables(sender, **kwargs):
|
|||||||
config page.
|
config page.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
presentation = ConfigVariable(
|
projector = ConfigVariable(
|
||||||
name='presentation',
|
name='projector_active_slide',
|
||||||
default_value='')
|
default_value={'callback': None})
|
||||||
|
"""
|
||||||
presentation_argument = ConfigVariable(
|
The active slide. The config-value is a dictonary with at least the entrie
|
||||||
name='presentation_argument',
|
'callback'.
|
||||||
default_value=None)
|
"""
|
||||||
|
|
||||||
projector_message = ConfigVariable(
|
projector_message = ConfigVariable(
|
||||||
name='projector_message',
|
name='projector_message',
|
||||||
@ -76,7 +75,7 @@ def setup_projector_config_variables(sender, **kwargs):
|
|||||||
|
|
||||||
return ConfigPage(
|
return ConfigPage(
|
||||||
title='No title here', url='bar', required_permission=None, variables=(
|
title='No title here', url='bar', required_permission=None, variables=(
|
||||||
presentation, presentation_argument, projector_message,
|
projector, projector_message,
|
||||||
countdown_time, countdown_start_stamp, countdown_pause_stamp,
|
countdown_time, countdown_start_stamp, countdown_pause_stamp,
|
||||||
countdown_state, bigger, up, projector_active_overlays))
|
countdown_state, bigger, up, projector_active_overlays))
|
||||||
|
|
||||||
@ -97,30 +96,32 @@ def countdown(sender, **kwargs):
|
|||||||
'countdown_time': config['countdown_time'],
|
'countdown_time': config['countdown_time'],
|
||||||
'countdown_state': config['countdown_state']}
|
'countdown_state': config['countdown_state']}
|
||||||
context.update(csrf(request))
|
context.update(csrf(request))
|
||||||
return render_to_string('projector/overlay_countdown_widget.html', context)
|
return render_to_string('projector/overlay_countdown_widget.html',
|
||||||
|
context)
|
||||||
|
|
||||||
|
def get_projector_js():
|
||||||
|
"""
|
||||||
|
Returns JavaScript for the projector
|
||||||
|
"""
|
||||||
|
start = int(config['countdown_start_stamp'])
|
||||||
|
duration = int(config['countdown_time'])
|
||||||
|
pause = int(config['countdown_pause_stamp'])
|
||||||
|
state = config['countdown_state']
|
||||||
|
|
||||||
|
return {
|
||||||
|
'load_file': static('javascript/countdown.js'),
|
||||||
|
'projector_countdown_start': start,
|
||||||
|
'projector_countdown_duration': duration,
|
||||||
|
'projector_countdown_pause': pause,
|
||||||
|
'projector_countdown_state': state}
|
||||||
|
|
||||||
def get_projector_html():
|
def get_projector_html():
|
||||||
"""
|
"""
|
||||||
Returns an html-code to show on the projector.
|
Returns an html-code to show on the projector.
|
||||||
"""
|
"""
|
||||||
start = config['countdown_start_stamp']
|
return render_to_string('projector/overlay_countdown_projector.html')
|
||||||
duration = config['countdown_time']
|
|
||||||
pause = config['countdown_pause_stamp']
|
|
||||||
if config['countdown_state'] == 'active':
|
|
||||||
seconds = max(0, int(start + duration - time()))
|
|
||||||
elif config['countdown_state'] == 'paused':
|
|
||||||
seconds = max(0, int(start + duration - pause))
|
|
||||||
elif config['countdown_state'] == 'inactive':
|
|
||||||
seconds = max(0, int(duration))
|
|
||||||
else:
|
|
||||||
seconds = 0
|
|
||||||
if seconds == 0:
|
|
||||||
config['countdown_state'] = 'expired'
|
|
||||||
clear_projector_cache()
|
|
||||||
return render_to_string('projector/overlay_countdown_projector.html',
|
|
||||||
{'seconds': '%02d:%02d' % (seconds / 60, seconds % 60)})
|
|
||||||
|
|
||||||
return Overlay(name, get_widget_html, get_projector_html)
|
return Overlay(name, get_widget_html, get_projector_html, get_projector_js)
|
||||||
|
|
||||||
|
|
||||||
@receiver(projector_overlays, dispatch_uid="projector_message")
|
@receiver(projector_overlays, dispatch_uid="projector_message")
|
||||||
@ -148,3 +149,26 @@ def projector_message(sender, **kwargs):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
return Overlay(name, get_widget_html, get_projector_html)
|
return Overlay(name, get_widget_html, get_projector_html)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(projector_overlays, dispatch_uid="projector_clock")
|
||||||
|
def projector_clock(sender, **kwargs):
|
||||||
|
"""
|
||||||
|
Receiver to show the clock on the projector.
|
||||||
|
"""
|
||||||
|
name = 'projector_clock'
|
||||||
|
|
||||||
|
def get_projector_html():
|
||||||
|
"""
|
||||||
|
Returns the html-code for the clock.
|
||||||
|
"""
|
||||||
|
return render_to_string('projector/overlay_clock_projector.html')
|
||||||
|
|
||||||
|
def get_projector_js():
|
||||||
|
"""
|
||||||
|
Returns JavaScript for the projector
|
||||||
|
"""
|
||||||
|
return {'load_file': static('javascript/clock.js')}
|
||||||
|
|
||||||
|
return Overlay(name, None, get_projector_html, get_projector_js,
|
||||||
|
allways_active=True)
|
||||||
|
36
openslides/projector/slides.py
Normal file
36
openslides/projector/slides.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
openslides.projector.slides
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Slides for the projector app.
|
||||||
|
|
||||||
|
:copyright: 2011–2013 by OpenSlides team, see AUTHORS.
|
||||||
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
from openslides.config.api import config
|
||||||
|
from openslides.projector.api import register_slide
|
||||||
|
|
||||||
|
from .models import ProjectorSlide
|
||||||
|
|
||||||
|
|
||||||
|
def projector_slide(**kwargs):
|
||||||
|
"""
|
||||||
|
Return the html code for a custom slide.
|
||||||
|
"""
|
||||||
|
slide_pk = kwargs.get('pk', None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
slide = ProjectorSlide.objects.get(pk=slide_pk)
|
||||||
|
except ProjectorSlide.DoesNotExist:
|
||||||
|
slide = None
|
||||||
|
|
||||||
|
context = {'slide': slide}
|
||||||
|
return render_to_string('projector/slide_projectorslide.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
register_slide('projector_slide', projector_slide)
|
8
openslides/projector/static/javascript/clock.js
Normal file
8
openslides/projector/static/javascript/clock.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
function update_clock() {
|
||||||
|
var currentTime = new Date();
|
||||||
|
var currentHours = currentTime.getHours();
|
||||||
|
var currentMinutes = currentTime.getMinutes();
|
||||||
|
$('#currentTime').html(currentHours + ':' + currentMinutes);
|
||||||
|
setTimeout('update_clock()', 200);
|
||||||
|
}
|
||||||
|
update_clock();
|
25
openslides/projector/static/javascript/countdown.js
Normal file
25
openslides/projector/static/javascript/countdown.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
function update_countdown() {
|
||||||
|
var time = new Date().getTime() / 1000;
|
||||||
|
var seconds;
|
||||||
|
var start = projector.projector_countdown_start;
|
||||||
|
var duration = projector.projector_countdown_duration;
|
||||||
|
var pause = projector.projector_countdown_pause;
|
||||||
|
|
||||||
|
switch (projector.projector_countdown_state) {
|
||||||
|
case 'active':
|
||||||
|
seconds = start + duration - time;
|
||||||
|
break;
|
||||||
|
case 'paused':
|
||||||
|
seconds = start + duration - pause;
|
||||||
|
break;
|
||||||
|
case 'inactive':
|
||||||
|
seconds = duration;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (seconds !== undefined) {
|
||||||
|
seconds = Math.max(0, Math.floor(seconds));
|
||||||
|
$('#overlay_countdown_inner').html(seconds);
|
||||||
|
}
|
||||||
|
setTimeout('update_countdown()', 200);
|
||||||
|
}
|
||||||
|
update_countdown();
|
@ -1,62 +1,67 @@
|
|||||||
/**
|
/**
|
||||||
* OpenSlides projector functions
|
* OpenSlides projector functions
|
||||||
*
|
*
|
||||||
* :copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
|
* :copyright: 2011–2013 by OpenSlides team, see AUTHORS.
|
||||||
* :license: GNU GPL, see LICENSE for more details.
|
* :license: GNU GPL, see LICENSE for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
content_hash = null;
|
$(document).ready(function() {
|
||||||
|
if ($('#content.reload').length > 0) {
|
||||||
|
updater.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function presentation_reload() {
|
var projector = {
|
||||||
if ($('#config > #ajax').html() == 'on') {
|
_loaded_files: {},
|
||||||
$.ajax({
|
|
||||||
type: 'GET',
|
load_file: function(src) {
|
||||||
url: '/projector/',
|
if (projector._loaded_files[src] === undefined) {
|
||||||
dataType: 'json',
|
projector._loaded_files[src] = document.createElement('script');
|
||||||
success: function(data) {
|
projector._loaded_files[src].setAttribute("type","text/javascript");
|
||||||
$('#currentTime').removeClass('ajax_error');
|
projector._loaded_files[src].setAttribute("src", src);
|
||||||
var new_content_hash = data['content_hash'];
|
$('head').append(projector._loaded_files[src]);
|
||||||
if (new_content_hash != content_hash) {
|
}
|
||||||
$('#content').html(data.content);
|
},
|
||||||
content_hash = new_content_hash;
|
|
||||||
|
update_data: function(data) {
|
||||||
|
$.each(data, function (key, value) {
|
||||||
|
if (key === 'load_file')
|
||||||
|
projector.load_file(value);
|
||||||
|
else
|
||||||
|
projector[key] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var updater = {
|
||||||
|
socket: null,
|
||||||
|
|
||||||
|
start: function() {
|
||||||
|
var url = "ws://" + location.host + "/projector/socket/";
|
||||||
|
updater.socket = new WebSocket(url);
|
||||||
|
updater.socket.onmessage = function(event) {
|
||||||
|
updater.updateProjector(JSON.parse(event.data));
|
||||||
|
}
|
||||||
|
updater.socket.onclose = function() {
|
||||||
|
setTimeout('updater.start()', 5000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateProjector: function(data) {
|
||||||
|
$('#content').html(data.content);
|
||||||
|
var overlays = data.overlays;
|
||||||
|
$.each(overlays, function (key, value) {
|
||||||
|
var overlay = $('#overlays #overlay_' + key)
|
||||||
|
if (!value)
|
||||||
|
overlay.remove();
|
||||||
|
else {
|
||||||
|
if (overlay.length) {
|
||||||
|
overlay.html(value.html)
|
||||||
|
} else {
|
||||||
|
$('#overlays').append(value.html);
|
||||||
}
|
}
|
||||||
$('#scrollcontent').html(data.scrollcontent);
|
projector.update_data(value.javascript);
|
||||||
document.title = data.title;
|
|
||||||
$('#currentTime').html(data.time);
|
|
||||||
$('#content').clearQueue();
|
|
||||||
// content font-size
|
|
||||||
$('#content').animate({'font-size': data.bigger + '%'}, 200);
|
|
||||||
$('#content #sidebar').css({'font-size': '18px'}, 0);
|
|
||||||
$('#scrollcontent').animate({'font-size': data.bigger + '%'}, 100);
|
|
||||||
// content position
|
|
||||||
$('#scrollcontent').animate({'margin-top': data.up + 'em'}, 100);
|
|
||||||
// overlays
|
|
||||||
$('#overlays div').remove();
|
|
||||||
$.each(data['overlays'], function (index, overlay){
|
|
||||||
$('#overlays').append('<div id="overlay_' + overlay['name'] + '">' + overlay['html'] + '</div>');
|
|
||||||
});
|
|
||||||
setTimeout("presentation_reload()", 1000);
|
|
||||||
},
|
|
||||||
error: function () {
|
|
||||||
$('#currentTime').addClass('ajax_error');
|
|
||||||
setTimeout("presentation_reload()", 1000);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
function switchajax() {
|
|
||||||
if ($('#config > #ajax').html() == 'on') {
|
|
||||||
$('#config > #ajax').html('off');
|
|
||||||
$('#ajaxswitcher').html('<a href="#" onClick="switchajax()">Ajax Anschalten</a>');
|
|
||||||
} else {
|
|
||||||
$('#config > #ajax').html('on');
|
|
||||||
$('#ajaxswitcher').html('<a href="#" onClick="switchajax()">Ajax Ausschalten</a>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
switchajax();
|
|
||||||
switchajax();
|
|
||||||
presentation_reload();
|
|
||||||
});
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{% load tags %}
|
{% load tags %}
|
||||||
|
{% load i18n %}
|
||||||
{% load staticfiles %}
|
{% load staticfiles %}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@ -6,47 +7,28 @@
|
|||||||
<head>
|
<head>
|
||||||
<link type="text/css" rel="stylesheet" href="{% static 'styles/projector.css' %}">
|
<link type="text/css" rel="stylesheet" href="{% static 'styles/projector.css' %}">
|
||||||
<link rel="shortcut icon" href="{% static 'img/favicon.png' %}" type="image/png" />
|
<link rel="shortcut icon" href="{% static 'img/favicon.png' %}" type="image/png" />
|
||||||
<script type="text/javascript" src="{% static 'javascript/jquery.min.js' %}"></script>
|
<title>{{ 'event_name'|get_config }} – {% trans 'Projector' %}</title>
|
||||||
<script type="text/javascript" src="{% static 'javascript/projector.js' %}"></script>
|
|
||||||
<title>{% block title %}{% get_config 'event_name' %}{% endblock %}</title>
|
|
||||||
{% block header %}
|
|
||||||
{% endblock %}
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="config" style="display:none;">
|
|
||||||
<div id="ajax">{{ ajax }}</div>
|
|
||||||
</div>
|
|
||||||
<div id="ajaxswitcher"></div>
|
|
||||||
|
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<div id="logo"><img src="{% static 'img/logo-projector.png' %}"></div>
|
<div id="logo"><img src="{% static 'img/logo-projector.png' %}"></div>
|
||||||
<div class="event_name">{% get_config 'event_name' %}</div>
|
<div class="event_name">{% get_config 'event_name' %}</div>
|
||||||
<div class="event_description">{% get_config 'event_description' %}</div>
|
<div class="event_description">{% get_config 'event_description' %}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="currentTime">
|
|
||||||
{% now "H:i" %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="overlays">
|
<div id="overlays">
|
||||||
{% for overlay in overlays %}
|
{{ overlays }}
|
||||||
<div id="overlay_{{ overlay.name }}">
|
|
||||||
{{ overlay.html|safe }}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="contentwrapper">
|
<div id="content" {% if reload %}class="reload"{% endif %}>
|
||||||
<div id="content">
|
{{ content }}
|
||||||
{% block content %}
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
<div id="scrollcontentcontainer">
|
|
||||||
<div id="scrollcontent">
|
|
||||||
{% block scrollcontent %}
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<script type="text/javascript" src="{% static 'javascript/jquery.min.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'javascript/projector.js' %}"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
{% for js in overlay_js %}
|
||||||
|
projector.update_data({{ js|safe }});
|
||||||
|
{% endfor %}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,19 +0,0 @@
|
|||||||
{% extends "base-projector.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{{ slide.title }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% if slide.text %}
|
|
||||||
<h1>{{ slide.title }}</h1>
|
|
||||||
{% else %}
|
|
||||||
<div class="item_fullscreen">{{ slide.title }}</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block scrollcontent %}
|
|
||||||
{% if slide.text %}
|
|
||||||
<span>{{ slide.text|safe|linebreaks }}</span>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@ -0,0 +1,3 @@
|
|||||||
|
{% for overlay in overlays %}
|
||||||
|
{{ overlay.html|safe }}
|
||||||
|
{% endfor %}
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<ul style="line-height: 180%">
|
<ul style="line-height: 180%">
|
||||||
<li class="{% if welcomepage_is_active %}activeline{% endif %}">
|
<li class="{% if welcomepage_is_active %}activeline{% endif %}">
|
||||||
<a href="{% url 'projector_activate_welcomepage' %}" class="activate_link btn {% if welcomepage_is_active %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
<a href="{% url 'projector_activate_slide' 'default' %}" class="activate_link btn {% if welcomepage_is_active %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
||||||
<i class="icon-facetime-video {% if welcomepage_is_active %}icon-white{% endif %}"></i>
|
<i class="icon-facetime-video {% if welcomepage_is_active %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'projctor_preview_welcomepage' %}" title="{% trans 'Preview' %}" class="btn btn-mini right">
|
<a href="{% url 'projctor_preview_welcomepage' %}" title="{% trans 'Preview' %}" class="btn btn-mini right">
|
||||||
@ -15,18 +15,18 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<ul style="line-height: 180%">
|
<ul style="line-height: 180%">
|
||||||
{% for slide in slides %}
|
{% for slide in slides %}
|
||||||
<li class="{% if slide.active %}activeline{% endif %}">
|
<li class="{% if slide.is_active_slide %}activeline{% endif %}">
|
||||||
<a href="{% url 'projector_activate_slide' slide.sid %}" class="activate_link btn {% if slide.active %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
<a href="{{ slide|absolute_url:'projector' }}" class="activate_link btn {% if slide.is_active_slide %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
||||||
<i class="icon-facetime-video {% if slide.active %}icon-white{% endif %}"></i>
|
<i class="icon-facetime-video {% if slide.is_active_slide %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'customslide_edit' slide.id %}">{{ slide }}</a>
|
<a href="{{ slide|absolute_url:'update' }}">{{ slide }}</a>
|
||||||
<a href="{% url 'customslide_delete' slide.id %}" title="{% trans 'Delete' %}" class="btn btn-mini right">
|
<a href="{{ slide|absolute_url:'delete' }}" title="{% trans 'Delete' %}" class="btn btn-mini right">
|
||||||
<i class="icon-remove"></i>
|
<i class="icon-remove"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'customslide_edit' slide.id %}" title="{% trans 'Edit' %}" class="btn btn-mini right">
|
<a href="{{ slide|absolute_url:'update' }}" title="{% trans 'Edit' %}" class="btn btn-mini right">
|
||||||
<i class="icon-pencil"></i>
|
<i class="icon-pencil"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'projctor_preview_slide' slide.sid %}" title="{% trans 'Preview' %}" class="btn btn-mini right">
|
<a href="{{ slide|absolute_url:'projector_preview' }}" title="{% trans 'Preview' %}" class="btn btn-mini right">
|
||||||
<i class="icon-search"></i>
|
<i class="icon-search"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
{% extends "base-projector.html" %}
|
|
||||||
|
|
||||||
{% load tags %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="item_fullscreen">{% get_config 'welcome_title' %}</div>
|
|
||||||
{% endblock %}
|
|
@ -0,0 +1,4 @@
|
|||||||
|
{% load tags %}
|
||||||
|
|
||||||
|
<div class="item_fullscreen">{% get_config 'welcome_title' %}</div>
|
||||||
|
|
@ -0,0 +1 @@
|
|||||||
|
<div id="currentTime"></div>
|
@ -13,5 +13,4 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div id="overlay_countdown_inner">
|
<div id="overlay_countdown_inner">
|
||||||
{{ seconds }}
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% if slide.text %}
|
||||||
|
<h1>{{ slide.title }}</h1>
|
||||||
|
{% else %}
|
||||||
|
<div class="item_fullscreen">{{ slide.title }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
{% if slide.text %}
|
||||||
|
<span>{{ slide.text|safe|linebreaks }}</span>
|
||||||
|
{% endif %}
|
@ -21,19 +21,23 @@ from openslides.projector.views import (DashboardView, ActivateView,
|
|||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^$',
|
url(r'^$',
|
||||||
Projector.as_view(),
|
Projector.as_view(),
|
||||||
{'sid': None},
|
|
||||||
name='projector_show',
|
name='projector_show',
|
||||||
),
|
),
|
||||||
|
|
||||||
url(r'^preview/$',
|
url(r'^preview/$',
|
||||||
Projector.as_view(),
|
Projector.as_view(),
|
||||||
{'sid': None},
|
{'callback': None},
|
||||||
name='projctor_preview_welcomepage',
|
name='projctor_preview_welcomepage',
|
||||||
),
|
),
|
||||||
|
|
||||||
url(r'^preview/(?P<sid>[^/]*)/$',
|
url(r'^preview/(?P<callback>[^/]*)/$',
|
||||||
Projector.as_view(),
|
Projector.as_view(),
|
||||||
name='projctor_preview_slide',
|
name='projector_preview',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^activate/(?P<callback>[^/]*)/$',
|
||||||
|
ActivateView.as_view(),
|
||||||
|
name='projector_activate_slide',
|
||||||
),
|
),
|
||||||
|
|
||||||
url(r'^dashboard/$',
|
url(r'^dashboard/$',
|
||||||
@ -41,22 +45,6 @@ urlpatterns = patterns('',
|
|||||||
name='dashboard',
|
name='dashboard',
|
||||||
),
|
),
|
||||||
|
|
||||||
url(r'^activate/$',
|
|
||||||
ActivateView.as_view(),
|
|
||||||
{'sid': None},
|
|
||||||
name='projector_activate_welcomepage',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^activate/(?P<sid>[^/]*)/$',
|
|
||||||
ActivateView.as_view(),
|
|
||||||
name='projector_activate_slide',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^activate/(?P<sid>[^/]*)/(?P<argument>[^/]*)/$',
|
|
||||||
ActivateView.as_view(),
|
|
||||||
name='projector_activate_slide',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^widgets/$',
|
url(r'^widgets/$',
|
||||||
SelectWidgetsView.as_view(),
|
SelectWidgetsView.as_view(),
|
||||||
name='projector_select_widgets',
|
name='projector_select_widgets',
|
||||||
|
@ -28,15 +28,16 @@ from openslides.utils.views import (
|
|||||||
TemplateView, RedirectView, CreateView, UpdateView, DeleteView, AjaxMixin)
|
TemplateView, RedirectView, CreateView, UpdateView, DeleteView, AjaxMixin)
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from .api import (
|
from .api import (
|
||||||
get_active_slide, set_active_slide, get_slide_from_sid, get_all_widgets,
|
get_projector_content, get_projector_overlays, get_all_widgets,
|
||||||
clear_projector_cache)
|
set_active_slide, update_projector, get_active_slide, update_projector_overlay,
|
||||||
|
get_overlays, get_projector_overlays_js)
|
||||||
from .forms import SelectWidgetsForm
|
from .forms import SelectWidgetsForm
|
||||||
from .models import ProjectorSlide
|
from .models import ProjectorSlide
|
||||||
from .projector import Widget
|
from .projector import Widget
|
||||||
from .signals import projector_overlays
|
from .signals import projector_overlays
|
||||||
|
|
||||||
|
|
||||||
class DashboardView(TemplateView, AjaxMixin):
|
class DashboardView(AjaxMixin, TemplateView):
|
||||||
"""
|
"""
|
||||||
Overview over all possible slides, the overlays and a liveview.
|
Overview over all possible slides, the overlays and a liveview.
|
||||||
"""
|
"""
|
||||||
@ -50,96 +51,33 @@ class DashboardView(TemplateView, AjaxMixin):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class Projector(TemplateView, AjaxMixin):
|
class Projector(TemplateView):
|
||||||
"""
|
"""
|
||||||
The Projector-Page.
|
The Projector-Page.
|
||||||
"""
|
"""
|
||||||
permission_required = 'projector.can_see_projector'
|
permission_required = 'projector.can_see_projector'
|
||||||
|
template_name = 'projector.html'
|
||||||
@property
|
|
||||||
def data(self):
|
|
||||||
try:
|
|
||||||
return self._data
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
sid = self.kwargs['sid']
|
|
||||||
if sid is None:
|
|
||||||
try:
|
|
||||||
data = get_active_slide()
|
|
||||||
except AttributeError: # TODO: It has to be an Slide.DoesNotExist
|
|
||||||
data = None
|
|
||||||
ajax = 'on'
|
|
||||||
active_sid = get_active_slide(True)
|
|
||||||
else:
|
|
||||||
data = get_slide_from_sid(sid)
|
|
||||||
ajax = 'off'
|
|
||||||
|
|
||||||
if data is None:
|
|
||||||
data = {
|
|
||||||
'title': config['event_name'],
|
|
||||||
'template': 'projector/default.html',
|
|
||||||
}
|
|
||||||
data['ajax'] = ajax
|
|
||||||
|
|
||||||
# Projector overlays
|
|
||||||
data['overlays'] = []
|
|
||||||
# Do not show overlays on slide preview
|
|
||||||
if self.kwargs['sid'] is None:
|
|
||||||
for receiver, overlay in projector_overlays.send(sender=self):
|
|
||||||
if overlay.show_on_projector():
|
|
||||||
data['overlays'].append({'name': overlay.name,
|
|
||||||
'html': overlay.get_projector_html()})
|
|
||||||
|
|
||||||
self._data = data
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get_template_names(self):
|
|
||||||
return [self.data['template']]
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(Projector, self).get_context_data(**kwargs)
|
slide_dict = dict(self.request.GET.items())
|
||||||
context.update(self.data)
|
callback = self.kwargs.get('callback', None)
|
||||||
return context
|
if callback:
|
||||||
|
slide_dict['callback'] = callback
|
||||||
|
|
||||||
def get_ajax_context(self, **kwargs):
|
if not slide_dict:
|
||||||
content = cache.get('projector_content')
|
kwargs.update({
|
||||||
if not content:
|
'content': get_projector_content(),
|
||||||
content = render_block_to_string(
|
'overlays': get_projector_overlays(),
|
||||||
self.get_template_names()[0],
|
'overlay_js': get_projector_overlays_js(),
|
||||||
'content', self.data)
|
'reload': True})
|
||||||
cache.set('projector_content', content, 1)
|
|
||||||
|
|
||||||
scrollcontent = cache.get('projector_scrollcontent')
|
# For the Preview
|
||||||
if not scrollcontent:
|
else:
|
||||||
scrollcontent = render_block_to_string(
|
kwargs.update({
|
||||||
self.get_template_names()[0],
|
'content': get_projector_content(slide_dict),
|
||||||
'scrollcontent', self.data)
|
'reload': False})
|
||||||
cache.set('projector_scrollcontent', scrollcontent, 1)
|
|
||||||
|
|
||||||
# TODO: do not call the hole data-methode, if we only need some vars
|
return super(Projector, self).get_context_data(**kwargs)
|
||||||
data = cache.get('projector_data')
|
|
||||||
if not data:
|
|
||||||
data = self.data
|
|
||||||
cache.set('projector_data', data)
|
|
||||||
|
|
||||||
context = super(Projector, self).get_ajax_context(**kwargs)
|
|
||||||
content_hash = hash(content)
|
|
||||||
context.update({
|
|
||||||
'content': content,
|
|
||||||
'scrollcontent': scrollcontent,
|
|
||||||
'time': datetime.now().strftime('%H:%M'),
|
|
||||||
'overlays': data['overlays'],
|
|
||||||
'title': data['title'],
|
|
||||||
'bigger': config['bigger'],
|
|
||||||
'up': config['up'],
|
|
||||||
'content_hash': content_hash,
|
|
||||||
})
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
if request.is_ajax():
|
|
||||||
return self.ajax_get(request, *args, **kwargs)
|
|
||||||
return super(Projector, self).get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class ActivateView(RedirectView):
|
class ActivateView(RedirectView):
|
||||||
@ -151,10 +89,7 @@ class ActivateView(RedirectView):
|
|||||||
allow_ajax = True
|
allow_ajax = True
|
||||||
|
|
||||||
def pre_redirect(self, request, *args, **kwargs):
|
def pre_redirect(self, request, *args, **kwargs):
|
||||||
try:
|
set_active_slide(kwargs['callback'], kwargs=dict(request.GET.items()))
|
||||||
set_active_slide(kwargs['sid'], kwargs['argument'])
|
|
||||||
except KeyError:
|
|
||||||
set_active_slide(kwargs['sid'])
|
|
||||||
config['up'] = 0
|
config['up'] = 0
|
||||||
config['bigger'] = 100
|
config['bigger'] = 100
|
||||||
|
|
||||||
@ -267,9 +202,9 @@ class CountdownEdit(RedirectView):
|
|||||||
pass
|
pass
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
update_projector_overlay('projector_countdown')
|
||||||
|
|
||||||
def get_ajax_context(self, **kwargs):
|
def get_ajax_context(self, **kwargs):
|
||||||
clear_projector_cache()
|
|
||||||
return {
|
return {
|
||||||
'state': config['countdown_state'],
|
'state': config['countdown_state'],
|
||||||
'countdown_time': config['countdown_time'],
|
'countdown_time': config['countdown_time'],
|
||||||
@ -289,9 +224,9 @@ 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')
|
||||||
|
|
||||||
def get_ajax_context(self, **kwargs):
|
def get_ajax_context(self, **kwargs):
|
||||||
clear_projector_cache()
|
|
||||||
return {
|
return {
|
||||||
'overlay_message': config['projector_message'],
|
'overlay_message': config['projector_message'],
|
||||||
}
|
}
|
||||||
@ -306,21 +241,20 @@ class ActivateOverlay(RedirectView):
|
|||||||
permission_required = 'projector.can_manage_projector'
|
permission_required = 'projector.can_manage_projector'
|
||||||
|
|
||||||
def pre_redirect(self, request, *args, **kwargs):
|
def pre_redirect(self, request, *args, **kwargs):
|
||||||
self.name = kwargs['name']
|
overlay = get_overlays()[kwargs['name']]
|
||||||
active_overlays = config['projector_active_overlays']
|
self.name = overlay.name
|
||||||
if kwargs['activate']:
|
if kwargs['activate']:
|
||||||
if self.name not in active_overlays:
|
if not overlay.is_active():
|
||||||
active_overlays.append(self.name)
|
overlay.set_active(True)
|
||||||
config['projector_active_overlays'] = active_overlays
|
update_projector_overlay(overlay)
|
||||||
self.active = True
|
self.active = True
|
||||||
elif not kwargs['activate']:
|
else:
|
||||||
if self.name in active_overlays:
|
if overlay.is_active():
|
||||||
active_overlays.remove(self.name)
|
overlay.set_active(False)
|
||||||
config['projector_active_overlays'] = active_overlays
|
update_projector_overlay(overlay)
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
def get_ajax_context(self, **kwargs):
|
def get_ajax_context(self, **kwargs):
|
||||||
clear_projector_cache()
|
|
||||||
return {'active': self.active, 'name': self.name}
|
return {'active': self.active, 'name': self.name}
|
||||||
|
|
||||||
|
|
||||||
@ -399,7 +333,8 @@ def get_widgets(request):
|
|||||||
# Overlay widget
|
# Overlay widget
|
||||||
overlays = []
|
overlays = []
|
||||||
for receiver, overlay in projector_overlays.send(sender='overlay_widget', request=request):
|
for receiver, overlay in projector_overlays.send(sender='overlay_widget', request=request):
|
||||||
overlays.append(overlay)
|
if overlay.widget_html_callback is not None:
|
||||||
|
overlays.append(overlay)
|
||||||
context = {'overlays': overlays}
|
context = {'overlays': overlays}
|
||||||
context.update(csrf(request))
|
context.update(csrf(request))
|
||||||
widgets.append(Widget(
|
widgets.append(Widget(
|
||||||
@ -413,6 +348,7 @@ def get_widgets(request):
|
|||||||
context=context))
|
context=context))
|
||||||
|
|
||||||
# Custom slide widget
|
# Custom slide widget
|
||||||
|
welcomepage_is_active = get_active_slide().get('callback', 'default') == 'default'
|
||||||
widgets.append(Widget(
|
widgets.append(Widget(
|
||||||
request,
|
request,
|
||||||
name='custom_slide',
|
name='custom_slide',
|
||||||
@ -420,7 +356,7 @@ def get_widgets(request):
|
|||||||
template='projector/custom_slide_widget.html',
|
template='projector/custom_slide_widget.html',
|
||||||
context={
|
context={
|
||||||
'slides': ProjectorSlide.objects.all().order_by('weight'),
|
'slides': ProjectorSlide.objects.all().order_by('weight'),
|
||||||
'welcomepage_is_active': not bool(config["presentation"])},
|
'welcomepage_is_active': welcomepage_is_active},
|
||||||
permission_required='projector.can_manage_projector',
|
permission_required='projector.can_manage_projector',
|
||||||
default_column=2,
|
default_column=2,
|
||||||
default_weight=30))
|
default_weight=30))
|
||||||
|
@ -8,15 +8,15 @@
|
|||||||
:license: GNU GPL, see LICENSE for more details.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
|
||||||
import posixpath
|
import posixpath
|
||||||
from urllib import unquote
|
from urllib import unquote
|
||||||
|
|
||||||
from tornado.httpserver import HTTPServer
|
from tornado.httpserver import HTTPServer
|
||||||
from tornado.ioloop import IOLoop
|
from tornado.ioloop import IOLoop
|
||||||
from tornado.web import FallbackHandler, Application, StaticFileHandler
|
from tornado.web import FallbackHandler, Application, StaticFileHandler
|
||||||
|
from tornado.websocket import WebSocketHandler
|
||||||
from tornado.wsgi import WSGIContainer
|
from tornado.wsgi import WSGIContainer
|
||||||
from tornado.options import options, parse_command_line
|
from tornado.options import parse_command_line
|
||||||
|
|
||||||
from django.core.handlers.wsgi import WSGIHandler as Django_WSGIHandler
|
from django.core.handlers.wsgi import WSGIHandler as Django_WSGIHandler
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -38,6 +38,25 @@ class DjangoStaticFileHandler(StaticFileHandler):
|
|||||||
return super(DjangoStaticFileHandler, self).get(absolute_path, include_body)
|
return super(DjangoStaticFileHandler, self).get(absolute_path, include_body)
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectorSocketHandler(WebSocketHandler):
|
||||||
|
waiters = set()
|
||||||
|
|
||||||
|
## def allow_draft76(self):
|
||||||
|
## # for iOS 5.0 Safari
|
||||||
|
## return True
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
ProjectorSocketHandler.waiters.add(self)
|
||||||
|
|
||||||
|
def on_close(self):
|
||||||
|
ProjectorSocketHandler.waiters.remove(self)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def send_updates(cls, slide):
|
||||||
|
for waiter in cls.waiters:
|
||||||
|
waiter.write_message(slide)
|
||||||
|
|
||||||
|
|
||||||
def run_tornado(addr, port, reload=False):
|
def run_tornado(addr, port, reload=False):
|
||||||
# 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=[])
|
||||||
@ -54,7 +73,9 @@ def run_tornado(addr, port, reload=False):
|
|||||||
tornado_app = Application([
|
tornado_app = Application([
|
||||||
(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}),
|
||||||
('.*', FallbackHandler, dict(fallback=app))], debug=reload)
|
(r'/projector/socket/', ProjectorSocketHandler),
|
||||||
|
('.*', FallbackHandler, dict(fallback=app))
|
||||||
|
], debug=reload)
|
||||||
|
|
||||||
server = HTTPServer(tornado_app)
|
server = HTTPServer(tornado_app)
|
||||||
server.listen(port=port,
|
server.listen(port=port,
|
||||||
|
@ -141,3 +141,10 @@ def htmldiff(text1, text2):
|
|||||||
|
|
||||||
diff = difflib.HtmlDiff(wrapcolumn=60)
|
diff = difflib.HtmlDiff(wrapcolumn=60)
|
||||||
return diff.make_table(text1.splitlines(), text2.splitlines())
|
return diff.make_table(text1.splitlines(), text2.splitlines())
|
||||||
|
|
||||||
|
|
||||||
|
def int_or_none(object):
|
||||||
|
try:
|
||||||
|
return int(object)
|
||||||
|
except TypeError:
|
||||||
|
return None
|
||||||
|
@ -480,6 +480,7 @@ def send_register_tab(sender, request, context, **kwargs):
|
|||||||
extra_stylefiles = context['extra_stylefiles']
|
extra_stylefiles = context['extra_stylefiles']
|
||||||
else:
|
else:
|
||||||
extra_stylefiles = []
|
extra_stylefiles = []
|
||||||
|
# TODO: Do not go over the filesystem by any request
|
||||||
for app in settings.INSTALLED_APPS:
|
for app in settings.INSTALLED_APPS:
|
||||||
try:
|
try:
|
||||||
mod = import_module(app + '.views')
|
mod = import_module(app + '.views')
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from openslides.projector.projector import SlideMixin
|
|
||||||
from openslides.projector.api import register_slidemodel
|
|
||||||
|
|
||||||
|
|
||||||
class ReleatedItem(SlideMixin, models.Model):
|
|
||||||
prefix = 'releateditem'
|
|
||||||
|
|
||||||
|
class RelatedItem(models.Model):
|
||||||
name = models.CharField(max_length='255')
|
name = models.CharField(max_length='255')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Releated Item CHFNGEJ5634DJ34F'
|
verbose_name = 'Related Item CHFNGEJ5634DJ34F'
|
||||||
|
|
||||||
def get_agenda_title(self):
|
def get_agenda_title(self):
|
||||||
return self.name
|
return self.name
|
||||||
@ -22,11 +17,5 @@ class ReleatedItem(SlideMixin, models.Model):
|
|||||||
return '/absolute-url-here/'
|
return '/absolute-url-here/'
|
||||||
|
|
||||||
|
|
||||||
class BadReleatedItem(SlideMixin, models.Model):
|
class BadRelatedItem(models.Model):
|
||||||
prefix = 'badreleateditem'
|
|
||||||
|
|
||||||
name = models.CharField(max_length='255')
|
name = models.CharField(max_length='255')
|
||||||
|
|
||||||
|
|
||||||
register_slidemodel(ReleatedItem)
|
|
||||||
register_slidemodel(BadReleatedItem)
|
|
||||||
|
@ -14,6 +14,7 @@ from openslides.utils.test import TestCase
|
|||||||
from openslides.participant.models import User, Group
|
from openslides.participant.models import User, Group
|
||||||
from openslides.agenda.models import Item, Speaker
|
from openslides.agenda.models import Item, Speaker
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
|
from openslides.projector.api import set_active_slide
|
||||||
|
|
||||||
|
|
||||||
class ListOfSpeakerModelTests(TestCase):
|
class ListOfSpeakerModelTests(TestCase):
|
||||||
@ -214,36 +215,30 @@ class SpeakerListOpenView(SpeakerViewTestCase):
|
|||||||
|
|
||||||
class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
||||||
def test_global_redirect_url(self):
|
def test_global_redirect_url(self):
|
||||||
self.assertFalse(config['presentation'])
|
|
||||||
response = self.speaker1_client.get('/agenda/list_of_speakers/')
|
response = self.speaker1_client.get('/agenda/list_of_speakers/')
|
||||||
self.assertRedirects(response, '/projector/dashboard/')
|
self.assertRedirects(response, '/projector/dashboard/')
|
||||||
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
||||||
|
|
||||||
response = self.admin_client.get('/projector/activate/item-1/')
|
set_active_slide('agenda', {'pk': 1})
|
||||||
self.assertEqual(config['presentation'], 'item-1')
|
|
||||||
response = self.speaker1_client.get('/agenda/list_of_speakers/')
|
response = self.speaker1_client.get('/agenda/list_of_speakers/')
|
||||||
self.assertRedirects(response, '/agenda/1/')
|
self.assertRedirects(response, '/agenda/1/')
|
||||||
|
|
||||||
def test_global_add_url(self):
|
def test_global_add_url(self):
|
||||||
self.assertFalse(config['presentation'])
|
|
||||||
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
|
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
|
||||||
self.assertRedirects(response, '/projector/dashboard/')
|
self.assertRedirects(response, '/projector/dashboard/')
|
||||||
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
||||||
|
|
||||||
response = self.admin_client.get('/projector/activate/item-1/')
|
set_active_slide('agenda', {'pk': 1})
|
||||||
self.assertEqual(config['presentation'], 'item-1')
|
|
||||||
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
|
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
|
||||||
self.assertRedirects(response, '/agenda/1/')
|
self.assertRedirects(response, '/agenda/1/')
|
||||||
self.assertEqual(Speaker.objects.get(item__pk='1').person, self.speaker1)
|
self.assertEqual(Speaker.objects.get(item__pk='1').person, self.speaker1)
|
||||||
|
|
||||||
def test_global_next_speaker_url(self):
|
def test_global_next_speaker_url(self):
|
||||||
self.assertFalse(config['presentation'])
|
|
||||||
response = self.admin_client.get('/agenda/list_of_speakers/next/')
|
response = self.admin_client.get('/agenda/list_of_speakers/next/')
|
||||||
self.assertRedirects(response, '/projector/dashboard/')
|
self.assertRedirects(response, '/projector/dashboard/')
|
||||||
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
||||||
|
|
||||||
response = self.admin_client.get('/projector/activate/item-1/')
|
set_active_slide('agenda', {'pk': 1})
|
||||||
self.assertEqual(config['presentation'], 'item-1')
|
|
||||||
response = self.admin_client.get('/agenda/list_of_speakers/next/')
|
response = self.admin_client.get('/agenda/list_of_speakers/next/')
|
||||||
self.assertRedirects(response, '/projector/dashboard/')
|
self.assertRedirects(response, '/projector/dashboard/')
|
||||||
self.assertMessage(response, 'The list of speakers is empty.')
|
self.assertMessage(response, 'The list of speakers is empty.')
|
||||||
@ -255,13 +250,11 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
|||||||
self.assertTrue(Speaker.objects.get(item__pk='1').begin_time is not None)
|
self.assertTrue(Speaker.objects.get(item__pk='1').begin_time is not None)
|
||||||
|
|
||||||
def test_global_end_speach_url(self):
|
def test_global_end_speach_url(self):
|
||||||
self.assertFalse(config['presentation'])
|
|
||||||
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
|
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
|
||||||
self.assertRedirects(response, '/projector/dashboard/')
|
self.assertRedirects(response, '/projector/dashboard/')
|
||||||
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
||||||
|
|
||||||
response = self.admin_client.get('/projector/activate/item-1/')
|
set_active_slide('agenda', {'pk': 1})
|
||||||
self.assertEqual(config['presentation'], 'item-1')
|
|
||||||
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
|
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
|
||||||
self.assertRedirects(response, '/projector/dashboard/')
|
self.assertRedirects(response, '/projector/dashboard/')
|
||||||
self.assertMessage(response, 'There is no one speaking at the moment.')
|
self.assertMessage(response, 'There is no one speaking at the moment.')
|
||||||
|
@ -14,12 +14,12 @@ from django.test.client import Client
|
|||||||
from django.db.models.query import EmptyQuerySet
|
from django.db.models.query import EmptyQuerySet
|
||||||
|
|
||||||
from openslides.utils.test import TestCase
|
from openslides.utils.test import TestCase
|
||||||
from openslides.projector.api import get_active_slide
|
from openslides.projector.api import get_active_slide, set_active_slide
|
||||||
from openslides.participant.models import User
|
from openslides.participant.models import User
|
||||||
from openslides.agenda.models import Item
|
from openslides.agenda.models import Item
|
||||||
from openslides.agenda.slides import agenda_show
|
from openslides.agenda.slides import agenda_slide
|
||||||
|
|
||||||
from .models import ReleatedItem, BadReleatedItem # TODO: Rename releated to related
|
from .models import RelatedItem, BadRelatedItem
|
||||||
|
|
||||||
|
|
||||||
class ItemTest(TestCase):
|
class ItemTest(TestCase):
|
||||||
@ -28,8 +28,8 @@ class ItemTest(TestCase):
|
|||||||
self.item2 = Item.objects.create(title='item2')
|
self.item2 = Item.objects.create(title='item2')
|
||||||
self.item3 = Item.objects.create(title='item1A', parent=self.item1)
|
self.item3 = Item.objects.create(title='item1A', parent=self.item1)
|
||||||
self.item4 = Item.objects.create(title='item1Aa', parent=self.item3)
|
self.item4 = Item.objects.create(title='item1Aa', parent=self.item3)
|
||||||
self.releated = ReleatedItem.objects.create(name='ekdfjen458gj1siek45nv')
|
self.related = RelatedItem.objects.create(name='ekdfjen458gj1siek45nv')
|
||||||
self.item5 = Item.objects.create(title='item5', content_object=self.releated)
|
self.item5 = Item.objects.create(title='item5', content_object=self.related)
|
||||||
|
|
||||||
def testClosed(self):
|
def testClosed(self):
|
||||||
self.assertFalse(self.item1.closed)
|
self.assertFalse(self.item1.closed)
|
||||||
@ -78,24 +78,18 @@ class ItemTest(TestCase):
|
|||||||
self.assertEqual(self.item1.get_absolute_url('edit'), '/agenda/1/edit/')
|
self.assertEqual(self.item1.get_absolute_url('edit'), '/agenda/1/edit/')
|
||||||
self.assertEqual(self.item1.get_absolute_url('delete'), '/agenda/1/del/')
|
self.assertEqual(self.item1.get_absolute_url('delete'), '/agenda/1/del/')
|
||||||
|
|
||||||
def test_agenda_slide(self):
|
def test_related_item(self):
|
||||||
data = agenda_show()
|
self.assertEqual(self.item5.get_title(), self.related.name)
|
||||||
self.assertEqual(list(data['items']), list(Item.objects.all().filter(parent=None)))
|
|
||||||
self.assertEqual(data['template'], 'projector/AgendaSummary.html')
|
|
||||||
self.assertEqual(data['title'], 'Agenda')
|
|
||||||
|
|
||||||
def test_releated_item(self):
|
|
||||||
self.assertEqual(self.item5.get_title(), self.releated.name)
|
|
||||||
self.assertEqual(self.item5.get_title_supplement(), 'test item')
|
self.assertEqual(self.item5.get_title_supplement(), 'test item')
|
||||||
self.assertEqual(self.item5.content_type.name, 'Releated Item CHFNGEJ5634DJ34F')
|
self.assertEqual(self.item5.content_type.name, 'Related Item CHFNGEJ5634DJ34F')
|
||||||
|
|
||||||
def test_deleted_releated_item(self):
|
def test_deleted_related_item(self):
|
||||||
self.releated.delete()
|
self.related.delete()
|
||||||
self.assertFalse(ReleatedItem.objects.all().exists())
|
self.assertFalse(RelatedItem.objects.all().exists())
|
||||||
self.assertEqual(Item.objects.get(pk=self.item5.pk).title, '< Item for deleted slide (ekdfjen458gj1siek45nv) >')
|
self.assertEqual(Item.objects.get(pk=self.item5.pk).title, '< Item for deleted slide (ekdfjen458gj1siek45nv) >')
|
||||||
|
|
||||||
def test_bad_releated_item(self):
|
def test_bad_related_item(self):
|
||||||
bad = BadReleatedItem.objects.create(name='dhfne94irkgl2047fzvb')
|
bad = BadRelatedItem.objects.create(name='dhfne94irkgl2047fzvb')
|
||||||
item = Item.objects.create(title='item_jghfndzrh46w738kdmc', content_object=bad)
|
item = Item.objects.create(title='item_jghfndzrh46w738kdmc', content_object=bad)
|
||||||
self.assertRaisesMessage(
|
self.assertRaisesMessage(
|
||||||
NotImplementedError,
|
NotImplementedError,
|
||||||
@ -138,20 +132,6 @@ class ViewTest(TestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(len(response.context['items']), len(Item.objects.all()))
|
self.assertEqual(len(response.context['items']), len(Item.objects.all()))
|
||||||
|
|
||||||
def testActivate(self):
|
|
||||||
c = self.adminClient
|
|
||||||
|
|
||||||
response = c.get('/projector/activate/%s/' % self.item1.sid)
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
self.assertTrue(self.item1.active)
|
|
||||||
self.assertFalse(self.item2.active)
|
|
||||||
|
|
||||||
response = c.get('/projector/activate/%s/' % 'agenda')
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
self.assertFalse(self.item2.active)
|
|
||||||
self.assertFalse(self.item1.active)
|
|
||||||
self.assertEqual(get_active_slide(only_sid=True), 'agenda')
|
|
||||||
|
|
||||||
def testClose(self):
|
def testClose(self):
|
||||||
c = self.adminClient
|
c = self.adminClient
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user