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%">
|
||||
{% trans 'I am on the list of speakers of the following items:' %}
|
||||
{% for item in items %}
|
||||
<li><a href="{% model_url item %}">{{ item }}</a></li>
|
||||
<li><a href="{{ item|absolute_url }}">{{ item }}</a></li>
|
||||
{% empty %}
|
||||
<li><i>{% trans 'None' %}</i></li>
|
||||
{% endfor %}
|
||||
@ -15,7 +15,7 @@
|
||||
{% trans 'I submitted the following motions:' %}
|
||||
{% for motion in submitted_motions %}
|
||||
<li>
|
||||
<a href="{% model_url motion %}">
|
||||
<a href="{{ motion|absolute_url }}">
|
||||
{{ motion.identifier|add:' | '|default:'' }}
|
||||
{{ motion }}
|
||||
</a>
|
||||
@ -31,7 +31,7 @@
|
||||
{% trans 'I support the following motions:' %}
|
||||
{% for motion in supported_motions %}
|
||||
<li>
|
||||
<a href="{% model_url motion %}">
|
||||
<a href="{{ motion|absolute_url }}">
|
||||
{% if motion.identifier %}
|
||||
[# {{ motion.identifier }}]
|
||||
{% else %}
|
||||
@ -50,7 +50,7 @@
|
||||
<ul style="line-height: 180%">
|
||||
{% trans 'I am candidate for the following elections:' %}
|
||||
{% for assignment in assignments %}
|
||||
<li><a href="{% model_url assignment %}">{{ assignment }}</a></li>
|
||||
<li><a href="{{ assignment|absolute_url }}">{{ assignment }}</a></li>
|
||||
{% empty %}
|
||||
<li><i>{% trans 'None' %}</i></li>
|
||||
{% endfor %}
|
||||
|
@ -23,19 +23,19 @@ from mptt.models import MPTTModel, TreeForeignKey
|
||||
|
||||
from openslides.utils.exceptions import OpenSlidesError
|
||||
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.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
|
||||
|
||||
MPTT-model. See http://django-mptt.github.com/django-mptt/
|
||||
"""
|
||||
prefix = 'item'
|
||||
slide_callback_name = 'agenda'
|
||||
|
||||
AGENDA_ITEM = 1
|
||||
ORGANIZATIONAL_ITEM = 2
|
||||
@ -98,6 +98,7 @@ class Item(MPTTModel, SlideMixin):
|
||||
"""
|
||||
Field for generic relation to a related object. Id of the object.
|
||||
"""
|
||||
# TODO: rename it to object_pk
|
||||
|
||||
content_object = generic.GenericForeignKey()
|
||||
"""
|
||||
@ -119,6 +120,14 @@ class Item(MPTTModel, SlideMixin):
|
||||
class MPTTMeta:
|
||||
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):
|
||||
return self.get_title()
|
||||
|
||||
@ -134,6 +143,11 @@ class Item(MPTTModel, SlideMixin):
|
||||
return reverse('item_edit', args=[str(self.id)])
|
||||
if link == 'delete':
|
||||
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):
|
||||
"""
|
||||
@ -157,41 +171,6 @@ class Item(MPTTModel, SlideMixin):
|
||||
except AttributeError:
|
||||
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):
|
||||
"""
|
||||
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')),
|
||||
)
|
||||
|
||||
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):
|
||||
return unicode(self.person)
|
||||
|
||||
@ -361,6 +348,21 @@ class Speaker(models.Model):
|
||||
return reverse('agenda_speaker_delete',
|
||||
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):
|
||||
"""
|
||||
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.projector import Overlay
|
||||
from openslides.projector.api import (get_active_slide, get_slide_from_sid,
|
||||
clear_projector_cache)
|
||||
from openslides.projector.api import get_active_slide
|
||||
|
||||
from .models import Speaker, Item
|
||||
|
||||
@ -94,23 +93,27 @@ def agenda_list_of_speakers(sender, **kwargs):
|
||||
def get_projector_html():
|
||||
"""
|
||||
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)
|
||||
if not isinstance(slide, Item):
|
||||
# Only show list of speakers overlay on agenda items
|
||||
active_slide = get_active_slide()
|
||||
slide_type = active_slide.get('type', None)
|
||||
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
|
||||
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)
|
||||
|
||||
|
@ -10,20 +10,63 @@
|
||||
: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
|
||||
|
||||
|
||||
def agenda_show():
|
||||
data = {}
|
||||
items = Item.objects.filter(parent=None, type__exact=Item.AGENDA_ITEM)
|
||||
data['title'] = _("Agenda")
|
||||
data['items'] = items
|
||||
data['template'] = 'projector/AgendaSummary.html'
|
||||
return data
|
||||
def agenda_slide(**kwargs):
|
||||
"""
|
||||
Return the html code for all slides of the agenda app.
|
||||
|
||||
register_slidemodel(Item, control_template='agenda/control_item.html')
|
||||
register_slidefunc('agenda', agenda_show, weight=-1, name=ugettext_lazy('Agenda'))
|
||||
If no id is given, show a summary of all parent items.
|
||||
|
||||
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 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 %}
|
||||
<div class="manage">
|
||||
<span style="width: 1px; white-space: nowrap;">
|
||||
{% if perms.projector.can_manage_projector %}
|
||||
<a href="{% url 'projector_activate_slide' node.sid %}"
|
||||
class="activate_link btn {% if node.active and not show_list and not summary %}btn-primary{% endif %} btn-mini"
|
||||
<a href="{{ node|absolute_url:'projector' }}"
|
||||
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' %}">
|
||||
<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 href="{% url 'projector_activate_slide' node.sid 'show_list_of_speakers' %}"
|
||||
class="activate_link btn btn-mini {% if node.active and show_list %}btn-primary{% endif %}"
|
||||
<a href="{{ node|absolute_url:'projector_list_of_speakers' }}"
|
||||
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' %}">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% 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>
|
||||
</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>
|
||||
</a>
|
||||
<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>
|
||||
</a>
|
||||
{% if not node.is_leaf_node %}
|
||||
<a href="{% url 'projector_activate_slide' node.sid 'summary' %}"
|
||||
class="activate_link btn btn-mini {% if node.active and summary %}btn-primary{% endif %}"
|
||||
<a href="{{ node|absolute_url:'projector_summary' }}"
|
||||
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' %}">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@ -79,7 +79,7 @@
|
||||
{{ form.self }}
|
||||
{{ form.parent }}
|
||||
{% 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 }}
|
||||
</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>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr class="topline{% if active_sid == 'agenda' %} activeline{% endif %}">
|
||||
<tr class="topline{% if agenda_is_active %} activeline{% endif %}">
|
||||
<td class="title">
|
||||
{% trans "Agenda" %}
|
||||
</td>
|
||||
@ -98,9 +98,9 @@
|
||||
{% if perms.projector.can_manage_projector %}
|
||||
<span>
|
||||
<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' %}">
|
||||
<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>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
@ -22,10 +22,10 @@
|
||||
<small class="pull-right">
|
||||
<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 %}
|
||||
<a href="{% url 'projector_activate_slide' item.sid %}"
|
||||
class="activate_link btn btn-mini {% if item.active and not show_list %}btn-primary{% endif %}"
|
||||
<a href="{{ item|absolute_url:'projector' }}"
|
||||
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' %}">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% if perms.agenda.can_manage_agenda %}
|
||||
@ -35,8 +35,8 @@
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<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="{% model_url item 'delete' %}"><i class="icon-remove"></i> {% trans 'Delete item' %}</a></li>
|
||||
<li><a href="{{ item|absolute_url:'update' }}"><i class="icon-pencil"></i> {% trans 'Edit item' %}</a></li>
|
||||
<li><a href="{{ item|absolute_url:'delete' }}"><i class="icon-remove"></i> {% trans 'Delete item' %}</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -68,10 +68,10 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if perms.projector.can_manage_projector %}
|
||||
<a href="{% url 'projector_activate_slide' item.sid 'show_list_of_speakers' %}"
|
||||
class="activate_link btn btn-mini {% if item.active and show_list %}btn-primary{% endif %}"
|
||||
<a href="{{ item|absolute_url:'projector_list_of_speakers' }}"
|
||||
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' %}">
|
||||
<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' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -119,15 +119,15 @@
|
||||
{% else %}
|
||||
<small class="grey">[{{ speaker_dict.speaker.begin_time }}{% if speaker_dict.type == 'old_speaker' %} – {{ speaker_dict.speaker.end_time }}{% endif %}]</small>
|
||||
{% 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 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>
|
||||
{% elif speaker_dict.type == 'coming_speaker' %}
|
||||
<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>
|
||||
{% 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">
|
||||
<i class="icon-remove"></i>
|
||||
</a>
|
||||
|
@ -2,49 +2,49 @@
|
||||
{% load tags %}
|
||||
|
||||
<ul style="line-height: 180%">
|
||||
<li class="{% if agenda.active %}activeline{% endif %}">
|
||||
<a href="{% url 'projector_activate_slide' agenda.key %}"
|
||||
class="activate_link btn {% if agenda.active %}btn-primary{% endif %} btn-mini"
|
||||
<li class="{% if agenda_is_active %}activeline{% endif %}">
|
||||
<a href="{% url 'projector_activate_slide' 'agenda' %}"
|
||||
class="activate_link btn {% if agenda_is_active %}btn-primary{% endif %} btn-mini"
|
||||
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 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>
|
||||
</a>
|
||||
{{ agenda.name }}
|
||||
{% trans "Agenda" %}
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul style="line-height: 180%">
|
||||
{% for item in items %}
|
||||
<li class="{% if item.active %}activeline{% endif %}">
|
||||
<a href="{% url 'projector_activate_slide' item.sid %}"
|
||||
class="activate_link btn {% if item.active and not summary and not speakers %}btn-primary{% endif %} btn-mini"
|
||||
<li class="{% if item.is_active_slide %}activeline{% endif %}">
|
||||
<a href="{{ item|absolute_url:'projector' }}"
|
||||
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' %}">
|
||||
<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 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>
|
||||
</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>
|
||||
</a>
|
||||
<a href="{% url 'projector_activate_slide' item.sid 'show_list_of_speakers' %}"
|
||||
class="activate_link btn btn-mini right {% if item.active and speakers %}btn-primary{% endif %}"
|
||||
<a href="{{ item|absolute_url:'projector_list_of_speakers' }}"
|
||||
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' %}">
|
||||
<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>
|
||||
{% if not item.is_leaf_node %}
|
||||
<a href="{% url 'projector_activate_slide' item.sid 'summary' %}"
|
||||
class="activate_link btn btn-mini {% if item.active and summary %}btn-primary{% endif %} right"
|
||||
<a href="{{ item|absolute_url:'projector_summary' }}"
|
||||
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' %}">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% for p in item.get_ancestors %}
|
||||
<span class="indentation"></span>
|
||||
{% 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 }}
|
||||
</li>
|
||||
{% 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)
|
||||
from openslides.utils.template import Tab
|
||||
from openslides.utils.utils import html_strong
|
||||
from openslides.projector.api import get_active_slide, get_slide_from_sid
|
||||
from openslides.projector.projector import Widget, SLIDE
|
||||
from openslides.projector.api import get_active_slide, update_projector
|
||||
from openslides.projector.projector import Widget
|
||||
from .models import Item, Speaker
|
||||
from .forms import ItemOrderForm, ItemForm, AppendSpeakerForm, RelatedItemForm
|
||||
|
||||
@ -83,14 +83,21 @@ class Overview(TemplateView):
|
||||
(duration.days * 24 + duration.seconds / 3600.0),
|
||||
(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({
|
||||
'items': items,
|
||||
'active_sid': get_active_slide(only_sid=True),
|
||||
'agenda_is_active': agenda_is_active,
|
||||
'duration': duration,
|
||||
'start': start,
|
||||
'end': end,
|
||||
'summary': config['presentation_argument'] == 'summary',
|
||||
'show_list': config['presentation_argument'] == 'show_list_of_speakers'})
|
||||
'active_type': active_type})
|
||||
return context
|
||||
|
||||
@transaction.commit_manually
|
||||
@ -100,6 +107,7 @@ class Overview(TemplateView):
|
||||
request,
|
||||
_('You are not authorized to manage the agenda.'))
|
||||
return self.render_to_response(context)
|
||||
|
||||
transaction.commit()
|
||||
for item in Item.objects.all():
|
||||
form = ItemOrderForm(request.POST, prefix="i%d" % item.id)
|
||||
@ -121,6 +129,11 @@ class Overview(TemplateView):
|
||||
# TODO: assure, that it is a valid tree
|
||||
context = self.get_context_data(**kwargs)
|
||||
transaction.commit()
|
||||
|
||||
if get_active_slide()['callback'] == 'agenda':
|
||||
update_projector()
|
||||
context = self.get_context_data(**kwargs)
|
||||
transaction.commit()
|
||||
return self.render_to_response(context)
|
||||
|
||||
|
||||
@ -138,11 +151,16 @@ class AgendaItemView(SingleObjectMixin, FormView):
|
||||
def get_context_data(self, **kwargs):
|
||||
self.object = self.get_object()
|
||||
list_of_speakers = self.object.get_list_of_speakers()
|
||||
|
||||
active_slide = get_active_slide()
|
||||
active_type = active_slide.get('type', None)
|
||||
|
||||
kwargs.update({
|
||||
'object': self.object,
|
||||
'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(),
|
||||
'show_list': config['presentation_argument'] == 'show_list_of_speakers',
|
||||
'is_on_the_list_of_speakers': Speaker.objects.filter(
|
||||
item=self.object, begin_time=None, person=self.request.user).exists(),
|
||||
'active_type': active_type,
|
||||
})
|
||||
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.
|
||||
"""
|
||||
slide = get_slide_from_sid(get_active_slide(only_sid=True), element=True)
|
||||
if not isinstance(slide, Item):
|
||||
return None
|
||||
active_slide = get_active_slide()
|
||||
if active_slide['callback'] == 'agenda':
|
||||
try:
|
||||
return Item.objects.get(pk=active_slide.get('pk', None))
|
||||
except Item.DoesNotExist:
|
||||
return None
|
||||
else:
|
||||
return slide
|
||||
return None
|
||||
|
||||
def get_redirect_url(self):
|
||||
"""
|
||||
@ -583,6 +604,14 @@ def get_widgets(request):
|
||||
"""
|
||||
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 [
|
||||
Widget(
|
||||
request,
|
||||
@ -590,10 +619,9 @@ def get_widgets(request):
|
||||
display_name=_('Agenda'),
|
||||
template='agenda/widget.html',
|
||||
context={
|
||||
'agenda': SLIDE['agenda'],
|
||||
'agenda_is_active': agenda_is_active,
|
||||
'items': Item.objects.all(),
|
||||
'summary': config['presentation_argument'] == 'summary',
|
||||
'speakers': config['presentation_argument'] == 'show_list_of_speakers'},
|
||||
'active_type': active_type},
|
||||
permission_required='projector.can_manage_projector',
|
||||
default_column=1,
|
||||
default_weight=20),
|
||||
|
@ -11,3 +11,4 @@
|
||||
"""
|
||||
|
||||
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.utils import html_strong
|
||||
from openslides.config.api import config
|
||||
from openslides.projector.api import register_slidemodel
|
||||
from openslides.projector.projector import SlideMixin
|
||||
from openslides.projector.models import SlideMixin
|
||||
from openslides.poll.models import (
|
||||
BasePoll, CountInvalid, CountVotesCast, BaseOption, PublishPollMixin, BaseVote)
|
||||
|
||||
@ -37,8 +36,9 @@ class AssignmentCandidate(models.Model):
|
||||
unique_together = ("assignment", "person")
|
||||
|
||||
|
||||
class Assignment(models.Model, SlideMixin):
|
||||
prefix = ugettext_noop('assignment')
|
||||
class Assignment(SlideMixin, models.Model):
|
||||
slide_callback_name = 'assignment'
|
||||
|
||||
STATUS = (
|
||||
('sea', ugettext_lazy('Searching for candidates')),
|
||||
('vot', ugettext_lazy('Voting')),
|
||||
@ -53,6 +53,27 @@ class Assignment(models.Model, SlideMixin):
|
||||
verbose_name=ugettext_lazy("Comment on the ballot paper"))
|
||||
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):
|
||||
status_dict = dict(self.STATUS)
|
||||
if status not in status_dict:
|
||||
@ -206,45 +227,6 @@ class Assignment(models.Model, SlideMixin):
|
||||
def get_agenda_title_supplement(self):
|
||||
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):
|
||||
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()
|
||||
|
||||
@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':
|
||||
return ('assignment_poll_view', [str(self.id)])
|
||||
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>
|
||||
{% for person in assignment.get_participants %}
|
||||
<li>
|
||||
<a href="{{ person|absolute_url:'detail' }}">{{ person }}</a>
|
||||
<a href="{{ person|absolute_url }}">{{ person }}</a>
|
||||
{% if perms.assignment.can_manage_assignment %}
|
||||
{% 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>
|
||||
@ -126,7 +126,7 @@
|
||||
<ul>
|
||||
{% for person in blocked_candidates %}
|
||||
<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 %}"
|
||||
rel="tooltip" data-original-title="{% trans 'Remove candidate' %}">
|
||||
<i class="icon-ban-circle"></i>
|
||||
@ -168,16 +168,16 @@
|
||||
<i class="icon-unchecked-new"></i>
|
||||
{% endif %}
|
||||
</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>
|
||||
<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>
|
||||
{% endif %}
|
||||
</th>
|
||||
{% endfor %}
|
||||
{% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
||||
<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' %}
|
||||
</a>
|
||||
</th>
|
||||
@ -200,7 +200,7 @@
|
||||
<a class="election_link" href="{% url 'assignment_user_elected' assignment.id candidate.person_id %}"></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<a href="{{ candidate|absolute_url:'detail' }}">{{ candidate }}</a>
|
||||
<a href="{{ candidate|absolute_url }}">{{ candidate }}</a>
|
||||
</td>
|
||||
{% for vote in poll_list %}
|
||||
<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%">
|
||||
{% for assignment in assignments %}
|
||||
<li class="{% if assignment.active %}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' %}">
|
||||
<li class="{% if assignment.is_active_slide %}activeline{% endif %}">
|
||||
<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>
|
||||
</a>
|
||||
<a href="{{ assignment|absolute_url:'update' }}" title="{% trans 'Edit' %}" class="btn btn-mini right">
|
||||
<i class="icon-pencil"></i>
|
||||
</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>
|
||||
</a>
|
||||
<a href="{{ assignment|absolute_url:'detail' }}">{{ assignment }}</a>
|
||||
<a href="{{ assignment|absolute_url }}">{{ assignment }}</a>
|
||||
</li>
|
||||
{% empty %}
|
||||
<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.get_filesize }}</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 %}
|
||||
<td>
|
||||
<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="{% 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:'update' }}" rel="tooltip" data-original-title="{% trans 'Edit' %}" class="btn btn-mini"><i class="icon-pencil"></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>
|
||||
</td>
|
||||
{% endif %}
|
||||
|
@ -29,7 +29,6 @@ from openslides.config.api import config
|
||||
from openslides.poll.models import (
|
||||
BaseOption, BasePoll, CountVotesCast, CountInvalid, BaseVote)
|
||||
from openslides.participant.models import User
|
||||
from openslides.projector.api import register_slidemodel
|
||||
from openslides.projector.models import SlideMixin
|
||||
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.
|
||||
"""
|
||||
|
||||
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,
|
||||
@ -193,6 +192,7 @@ class Motion(SlideMixin, models.Model):
|
||||
return reverse('motion_edit', args=[str(self.id)])
|
||||
if link == 'delete':
|
||||
return reverse('motion_delete', args=[str(self.id)])
|
||||
return super(Motion, self).get_absolute_url(link)
|
||||
|
||||
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])
|
||||
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):
|
||||
"""
|
||||
Return a title for the agenda.
|
||||
|
@ -10,7 +10,26 @@
|
||||
: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
|
||||
|
||||
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">
|
||||
<!-- 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 %}
|
||||
{% if perms.motion.can_manage_motion %}
|
||||
<!-- 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 %}
|
||||
<!-- create agenda item -->
|
||||
@ -68,14 +68,14 @@
|
||||
<p><span class="label label-warning">
|
||||
<i class="icon-warning-sign icon-white"></i> {% trans "This is not the newest version." %}
|
||||
</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>
|
||||
{% endif %}
|
||||
{% if version.version_number != active_version.version_number %}
|
||||
<p><span class="label label-warning">
|
||||
<i class="icon-warning-sign icon-white"></i> {% trans "This version is not authorized." %}
|
||||
</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>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
@ -123,11 +123,11 @@
|
||||
<input type="radio" value="{{ version.version_number }}" name="rev2">
|
||||
</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>
|
||||
</a>
|
||||
{% 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>
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -166,7 +166,7 @@
|
||||
<!-- Submitter -->
|
||||
<h5>{% trans "Submitter" %}:</h5>
|
||||
{% 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 %}
|
||||
|
||||
<!-- Supporters -->
|
||||
@ -177,7 +177,7 @@
|
||||
{% else %}
|
||||
<ol>
|
||||
{% 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 %}
|
||||
</ol>
|
||||
{% endif %}
|
||||
@ -196,8 +196,8 @@
|
||||
{% if perms.motion.can_manage_motion or poll.has_votes %}
|
||||
<li><b>{{ poll.poll_number|ordinal|safe }} {% trans "vote" %}</b>
|
||||
{% 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="{% model_url poll 'delete' %}" title="{% trans 'Delete Vote' %}"><i class="icon-remove"></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="{{ poll|absolute_url:'delete' }}" title="{% trans 'Delete Vote' %}"><i class="icon-remove"></i></a>
|
||||
{% endif %}
|
||||
<br>
|
||||
{% 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%">
|
||||
{% for motion in motions %}
|
||||
<li class="{% if motion.active %}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' %}">
|
||||
<i class="icon-facetime-video {% if motion.active %}icon-white{% endif %}"></i>
|
||||
<li class="{% if motion.is_active_slide %}activeline{% endif %}">
|
||||
<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.is_active_slide %}icon-white{% endif %}"></i>
|
||||
</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>
|
||||
</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>
|
||||
</a>
|
||||
<a href="{% model_url motion %}">
|
||||
<a href="{{ motion|absolute_url }}">
|
||||
{{ motion.identifier|add:' | '|default:'' }}
|
||||
{{ motion }}
|
||||
</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.poll.views import PollFormView
|
||||
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.agenda.views import CreateRelatedAgendaItemView as _CreateRelatedAgendaItemView
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
from django.utils.translation import ugettext_noop
|
||||
|
||||
from . import signals
|
||||
from . import slides
|
||||
|
||||
|
||||
NAME = ugettext_noop('Participant')
|
||||
|
@ -10,6 +10,7 @@
|
||||
: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.contenttypes.models import ContentType
|
||||
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.signals import receive_persons
|
||||
from openslides.config.api import config
|
||||
from openslides.projector.api import register_slidemodel
|
||||
from openslides.projector.projector import SlideMixin
|
||||
from openslides.projector.models import SlideMixin
|
||||
|
||||
|
||||
class User(PersonMixin, Person, SlideMixin, DjangoUser):
|
||||
prefix = 'user' # This is for the slides
|
||||
class User(SlideMixin, PersonMixin, Person, DjangoUser):
|
||||
slide_callback_name = 'user'
|
||||
person_prefix = 'user'
|
||||
|
||||
GENDER_CHOICES = (
|
||||
('male', ugettext_lazy('Male')),
|
||||
('female', ugettext_lazy('Female')),
|
||||
@ -55,6 +56,30 @@ class User(PersonMixin, Person, SlideMixin, DjangoUser):
|
||||
max_length=100, blank=True, default='',
|
||||
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
|
||||
def clean_name(self):
|
||||
if self.title:
|
||||
@ -86,50 +111,9 @@ class User(PersonMixin, Person, SlideMixin, DjangoUser):
|
||||
return self.first_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:
|
||||
* detail
|
||||
* 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
|
||||
class Group(SlideMixin, PersonMixin, Person, DjangoGroup):
|
||||
slide_callback_name = 'group'
|
||||
person_prefix = 'group'
|
||||
|
||||
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.'))
|
||||
description = models.TextField(blank=True, verbose_name=ugettext_lazy("Description"))
|
||||
|
||||
@models.permalink
|
||||
def get_absolute_url(self, link='detail'):
|
||||
"""
|
||||
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)])
|
||||
class Meta:
|
||||
ordering = ('name',)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.name)
|
||||
|
||||
class Meta:
|
||||
ordering = ('name',)
|
||||
|
||||
def slide(self):
|
||||
def get_absolute_url(self, link='detail'):
|
||||
"""
|
||||
Returns a map with the data for the slides.
|
||||
Return the URL to the user group.
|
||||
"""
|
||||
return {
|
||||
'group': self,
|
||||
'title': self.name,
|
||||
'template': 'projector/GroupSlide.html'}
|
||||
|
||||
register_slidemodel(Group)
|
||||
if link == 'detail' or link == 'view':
|
||||
return reverse('user_group_view', args=[str(self.id)])
|
||||
if link == 'update' or link == 'edit':
|
||||
return reverse('user_group_edit', args=[str(self.id)])
|
||||
if link == 'delete':
|
||||
return reverse('user_group_delete', args=[str(self.id)])
|
||||
return super(Group, self).get_absolute_url(link)
|
||||
|
||||
|
||||
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>
|
||||
{% 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 %}
|
||||
<p>{% trans "No members available." %}</p>
|
||||
{% endfor %}
|
||||
|
@ -40,7 +40,7 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% model_url group 'view' %}">{% trans group.name %}</a>
|
||||
<a href="{{ group|absolute_url }}">{% trans group.name %}</a>
|
||||
</td>
|
||||
<td>
|
||||
<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%">
|
||||
{% for group in groups %}
|
||||
<li class="{% if group.active %}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' %}">
|
||||
<i class="icon-facetime-video {% if group.active %}icon-white{% endif %}"></i>
|
||||
<li class="{% if group.is_active_slide %}activeline{% endif %}">
|
||||
<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.is_active_slide %}icon-white{% endif %}"></i>
|
||||
</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>
|
||||
</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>
|
||||
</a>
|
||||
<a href="{% model_url group 'view' %}">{% trans group.name %}</a>
|
||||
<a href="{{ group|absolute_url }}">{% trans group.name %}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</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 tags %}
|
||||
|
||||
<ul style="line-height: 180%">
|
||||
{% for user in users %}
|
||||
<li class="{% if user.active %}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' %}">
|
||||
<i class="icon-facetime-video {% if user.active %}icon-white{% endif %}"></i>
|
||||
<li class="{% if user.is_active_slide %}activeline{% endif %}">
|
||||
<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.is_active_slide %}icon-white{% endif %}"></i>
|
||||
</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>
|
||||
</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>
|
||||
</a>
|
||||
<a href="{% model_url user 'view' %}">{{ user }}</a>
|
||||
<a href="{{ user|absolute_url }}">{{ user }}</a>
|
||||
</li>
|
||||
{% empty %}
|
||||
<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)
|
||||
from openslides.config.api import config
|
||||
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,
|
||||
UserImportForm, GroupForm)
|
||||
from openslides.participant.models import User, Group, get_protected_perm
|
||||
from .models import User, Group, get_protected_perm
|
||||
|
||||
|
||||
class UserOverview(ListView):
|
||||
|
@ -10,4 +10,4 @@
|
||||
: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.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.utils.importlib import import_module
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
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:
|
||||
data = sid.split('-')
|
||||
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()
|
||||
return slide_callback[callback](**slide_dict)
|
||||
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,
|
||||
return None
|
||||
|
||||
if only_sid is True, returns only the id of this item. Returns None if not
|
||||
Item is active.
|
||||
Returns the HTML Code for the default slide.
|
||||
"""
|
||||
sid = config["presentation"]
|
||||
|
||||
if only_sid:
|
||||
return sid
|
||||
return get_slide_from_sid(sid)
|
||||
return render_to_string('projector/default_slide.html')
|
||||
|
||||
|
||||
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.
|
||||
|
||||
callback: The name of the slide callback.
|
||||
kwargs: Keyword arguments for the slide callback.
|
||||
"""
|
||||
config["presentation"] = sid
|
||||
config['presentation_argument'] = argument
|
||||
clear_projector_cache()
|
||||
kwargs.update(callback=callback)
|
||||
config['projector_active_slide'] = kwargs
|
||||
update_projector()
|
||||
update_projector_overlay(None)
|
||||
|
||||
|
||||
def clear_projector_cache():
|
||||
cache.delete('projector_content')
|
||||
cache.delete('projector_scrollcontent')
|
||||
cache.delete('projector_data')
|
||||
|
||||
|
||||
def register_slidemodel(model, model_name=None, control_template=None, weight=0):
|
||||
def get_active_slide():
|
||||
"""
|
||||
Register a Model as a slide.
|
||||
Returns the dictonary, witch defindes the active slide.
|
||||
"""
|
||||
# TODO: control_template should never be None
|
||||
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,)
|
||||
return config['projector_active_slide']
|
||||
|
||||
|
||||
def get_all_widgets(request, session=False):
|
||||
@ -135,9 +167,11 @@ def get_all_widgets(request, session=False):
|
||||
except ImportError:
|
||||
continue
|
||||
try:
|
||||
module_widgets = mod.get_widgets(request)
|
||||
mod_get_widgets = mod.get_widgets
|
||||
except AttributeError:
|
||||
continue
|
||||
else:
|
||||
module_widgets = mod_get_widgets(request)
|
||||
all_module_widgets.extend(module_widgets)
|
||||
all_module_widgets.sort(key=lambda widget: widget.default_weight)
|
||||
session_widgets = request.session.get('widgets', {})
|
||||
|
@ -13,34 +13,92 @@
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
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.projector.projector import SlideMixin
|
||||
from openslides.utils.utils import int_or_none
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
prefix = 'ProjectorSlide'
|
||||
slide_callback_name = 'projector_slide'
|
||||
|
||||
title = models.CharField(max_length=256, verbose_name=ugettext_lazy("Title"))
|
||||
text = models.TextField(null=True, blank=True, verbose_name=ugettext_lazy("Text"))
|
||||
weight = models.IntegerField(default=0, verbose_name=ugettext_lazy("Weight"))
|
||||
|
||||
def slide(self):
|
||||
return {
|
||||
'slide': self,
|
||||
'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)])
|
||||
def get_absolute_url(self, link='update'):
|
||||
if link == 'edit' or link == 'update':
|
||||
return reverse('customslide_edit', args=[str(self.pk)])
|
||||
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):
|
||||
return self.title
|
||||
@ -51,6 +109,3 @@ class ProjectorSlide(models.Model, SlideMixin):
|
||||
('can_see_projector', ugettext_noop("Can see the projector")),
|
||||
('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
|
||||
|
||||
|
||||
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 for a Widget for the Projector-Tab.
|
||||
@ -158,23 +67,52 @@ class Overlay(object):
|
||||
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.widget_html_callback = get_widget_html
|
||||
self.projector_html_callback = get_projector_html
|
||||
self.javascript_callback = get_javascript
|
||||
self.allways_active = allways_active
|
||||
|
||||
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):
|
||||
try:
|
||||
return self._projector_html
|
||||
except AttributeError:
|
||||
self._projector_html = self.projector_html_callback()
|
||||
return self.get_projector_html()
|
||||
return self.get_html_wrapper(self.projector_html_callback())
|
||||
|
||||
def get_javascript(self):
|
||||
if self.javascript_callback is None:
|
||||
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):
|
||||
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):
|
||||
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.template.loader import render_to_string
|
||||
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.api import ConfigVariable, ConfigPage
|
||||
|
||||
from .projector import Overlay
|
||||
from .api import clear_projector_cache
|
||||
|
||||
|
||||
projector_overlays = Signal(providing_args=['request'])
|
||||
@ -34,13 +33,13 @@ def setup_projector_config_variables(sender, **kwargs):
|
||||
config page.
|
||||
"""
|
||||
|
||||
presentation = ConfigVariable(
|
||||
name='presentation',
|
||||
default_value='')
|
||||
|
||||
presentation_argument = ConfigVariable(
|
||||
name='presentation_argument',
|
||||
default_value=None)
|
||||
projector = ConfigVariable(
|
||||
name='projector_active_slide',
|
||||
default_value={'callback': None})
|
||||
"""
|
||||
The active slide. The config-value is a dictonary with at least the entrie
|
||||
'callback'.
|
||||
"""
|
||||
|
||||
projector_message = ConfigVariable(
|
||||
name='projector_message',
|
||||
@ -76,7 +75,7 @@ def setup_projector_config_variables(sender, **kwargs):
|
||||
|
||||
return ConfigPage(
|
||||
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_state, bigger, up, projector_active_overlays))
|
||||
|
||||
@ -97,30 +96,32 @@ def countdown(sender, **kwargs):
|
||||
'countdown_time': config['countdown_time'],
|
||||
'countdown_state': config['countdown_state']}
|
||||
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():
|
||||
"""
|
||||
Returns an html-code to show on the projector.
|
||||
"""
|
||||
start = config['countdown_start_stamp']
|
||||
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 render_to_string('projector/overlay_countdown_projector.html')
|
||||
|
||||
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")
|
||||
@ -148,3 +149,26 @@ def projector_message(sender, **kwargs):
|
||||
return None
|
||||
|
||||
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
|
||||
*
|
||||
* :copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
|
||||
* :copyright: 2011–2013 by OpenSlides team, see AUTHORS.
|
||||
* :license: GNU GPL, see LICENSE for more details.
|
||||
*/
|
||||
|
||||
content_hash = null;
|
||||
$(document).ready(function() {
|
||||
if ($('#content.reload').length > 0) {
|
||||
updater.start();
|
||||
}
|
||||
});
|
||||
|
||||
function presentation_reload() {
|
||||
if ($('#config > #ajax').html() == 'on') {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/projector/',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
$('#currentTime').removeClass('ajax_error');
|
||||
var new_content_hash = data['content_hash'];
|
||||
if (new_content_hash != content_hash) {
|
||||
$('#content').html(data.content);
|
||||
content_hash = new_content_hash;
|
||||
var projector = {
|
||||
_loaded_files: {},
|
||||
|
||||
load_file: function(src) {
|
||||
if (projector._loaded_files[src] === undefined) {
|
||||
projector._loaded_files[src] = document.createElement('script');
|
||||
projector._loaded_files[src].setAttribute("type","text/javascript");
|
||||
projector._loaded_files[src].setAttribute("src", src);
|
||||
$('head').append(projector._loaded_files[src]);
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
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);
|
||||
projector.update_data(value.javascript);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 i18n %}
|
||||
{% load staticfiles %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
@ -6,47 +7,28 @@
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="{% static 'styles/projector.css' %}">
|
||||
<link rel="shortcut icon" href="{% static 'img/favicon.png' %}" type="image/png" />
|
||||
<script type="text/javascript" src="{% static 'javascript/jquery.min.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'javascript/projector.js' %}"></script>
|
||||
<title>{% block title %}{% get_config 'event_name' %}{% endblock %}</title>
|
||||
{% block header %}
|
||||
{% endblock %}
|
||||
<title>{{ 'event_name'|get_config }} – {% trans 'Projector' %}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="config" style="display:none;">
|
||||
<div id="ajax">{{ ajax }}</div>
|
||||
</div>
|
||||
<div id="ajaxswitcher"></div>
|
||||
|
||||
<div id="header">
|
||||
<div id="logo"><img src="{% static 'img/logo-projector.png' %}"></div>
|
||||
<div class="event_name">{% get_config 'event_name' %}</div>
|
||||
<div class="event_description">{% get_config 'event_description' %}</div>
|
||||
</div>
|
||||
|
||||
<div id="currentTime">
|
||||
{% now "H:i" %}
|
||||
</div>
|
||||
|
||||
<div id="overlays">
|
||||
{% for overlay in overlays %}
|
||||
<div id="overlay_{{ overlay.name }}">
|
||||
{{ overlay.html|safe }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{{ overlays }}
|
||||
</div>
|
||||
|
||||
<div id="contentwrapper">
|
||||
<div id="content">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div id="scrollcontentcontainer">
|
||||
<div id="scrollcontent">
|
||||
{% block scrollcontent %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="content" {% if reload %}class="reload"{% endif %}>
|
||||
{{ content }}
|
||||
</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>
|
||||
</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%">
|
||||
<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>
|
||||
</a>
|
||||
<a href="{% url 'projctor_preview_welcomepage' %}" title="{% trans 'Preview' %}" class="btn btn-mini right">
|
||||
@ -15,18 +15,18 @@
|
||||
<hr>
|
||||
<ul style="line-height: 180%">
|
||||
{% for slide in slides %}
|
||||
<li class="{% if slide.active %}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' %}">
|
||||
<i class="icon-facetime-video {% if slide.active %}icon-white{% endif %}"></i>
|
||||
<li class="{% if slide.is_active_slide %}activeline{% endif %}">
|
||||
<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.is_active_slide %}icon-white{% endif %}"></i>
|
||||
</a>
|
||||
<a href="{% url 'customslide_edit' slide.id %}">{{ slide }}</a>
|
||||
<a href="{% url 'customslide_delete' slide.id %}" title="{% trans 'Delete' %}" class="btn btn-mini right">
|
||||
<a href="{{ slide|absolute_url:'update' }}">{{ slide }}</a>
|
||||
<a href="{{ slide|absolute_url:'delete' }}" title="{% trans 'Delete' %}" class="btn btn-mini right">
|
||||
<i class="icon-remove"></i>
|
||||
</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>
|
||||
</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>
|
||||
</a>
|
||||
</li>
|
||||
@ -38,4 +38,4 @@
|
||||
<a href="{% url 'customslide_new' %}" class="btn btn-mini right">
|
||||
<i class="icon-plus"></i>{% trans 'New' %}
|
||||
</a>
|
||||
</p>
|
||||
</p>
|
||||
|
@ -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>
|
||||
|
||||
<div id="overlay_countdown_inner">
|
||||
{{ seconds }}
|
||||
</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('',
|
||||
url(r'^$',
|
||||
Projector.as_view(),
|
||||
{'sid': None},
|
||||
name='projector_show',
|
||||
),
|
||||
|
||||
url(r'^preview/$',
|
||||
Projector.as_view(),
|
||||
{'sid': None},
|
||||
{'callback': None},
|
||||
name='projctor_preview_welcomepage',
|
||||
),
|
||||
|
||||
url(r'^preview/(?P<sid>[^/]*)/$',
|
||||
url(r'^preview/(?P<callback>[^/]*)/$',
|
||||
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/$',
|
||||
@ -41,22 +45,6 @@ urlpatterns = patterns('',
|
||||
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/$',
|
||||
SelectWidgetsView.as_view(),
|
||||
name='projector_select_widgets',
|
||||
|
@ -28,15 +28,16 @@ from openslides.utils.views import (
|
||||
TemplateView, RedirectView, CreateView, UpdateView, DeleteView, AjaxMixin)
|
||||
from openslides.config.api import config
|
||||
from .api import (
|
||||
get_active_slide, set_active_slide, get_slide_from_sid, get_all_widgets,
|
||||
clear_projector_cache)
|
||||
get_projector_content, get_projector_overlays, get_all_widgets,
|
||||
set_active_slide, update_projector, get_active_slide, update_projector_overlay,
|
||||
get_overlays, get_projector_overlays_js)
|
||||
from .forms import SelectWidgetsForm
|
||||
from .models import ProjectorSlide
|
||||
from .projector import Widget
|
||||
from .signals import projector_overlays
|
||||
|
||||
|
||||
class DashboardView(TemplateView, AjaxMixin):
|
||||
class DashboardView(AjaxMixin, TemplateView):
|
||||
"""
|
||||
Overview over all possible slides, the overlays and a liveview.
|
||||
"""
|
||||
@ -50,96 +51,33 @@ class DashboardView(TemplateView, AjaxMixin):
|
||||
return context
|
||||
|
||||
|
||||
class Projector(TemplateView, AjaxMixin):
|
||||
class Projector(TemplateView):
|
||||
"""
|
||||
The Projector-Page.
|
||||
"""
|
||||
permission_required = 'projector.can_see_projector'
|
||||
|
||||
@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']]
|
||||
template_name = 'projector.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(Projector, self).get_context_data(**kwargs)
|
||||
context.update(self.data)
|
||||
return context
|
||||
slide_dict = dict(self.request.GET.items())
|
||||
callback = self.kwargs.get('callback', None)
|
||||
if callback:
|
||||
slide_dict['callback'] = callback
|
||||
|
||||
def get_ajax_context(self, **kwargs):
|
||||
content = cache.get('projector_content')
|
||||
if not content:
|
||||
content = render_block_to_string(
|
||||
self.get_template_names()[0],
|
||||
'content', self.data)
|
||||
cache.set('projector_content', content, 1)
|
||||
if not slide_dict:
|
||||
kwargs.update({
|
||||
'content': get_projector_content(),
|
||||
'overlays': get_projector_overlays(),
|
||||
'overlay_js': get_projector_overlays_js(),
|
||||
'reload': True})
|
||||
|
||||
scrollcontent = cache.get('projector_scrollcontent')
|
||||
if not scrollcontent:
|
||||
scrollcontent = render_block_to_string(
|
||||
self.get_template_names()[0],
|
||||
'scrollcontent', self.data)
|
||||
cache.set('projector_scrollcontent', scrollcontent, 1)
|
||||
# For the Preview
|
||||
else:
|
||||
kwargs.update({
|
||||
'content': get_projector_content(slide_dict),
|
||||
'reload': False})
|
||||
|
||||
# TODO: do not call the hole data-methode, if we only need some vars
|
||||
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)
|
||||
return super(Projector, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class ActivateView(RedirectView):
|
||||
@ -151,10 +89,7 @@ class ActivateView(RedirectView):
|
||||
allow_ajax = True
|
||||
|
||||
def pre_redirect(self, request, *args, **kwargs):
|
||||
try:
|
||||
set_active_slide(kwargs['sid'], kwargs['argument'])
|
||||
except KeyError:
|
||||
set_active_slide(kwargs['sid'])
|
||||
set_active_slide(kwargs['callback'], kwargs=dict(request.GET.items()))
|
||||
config['up'] = 0
|
||||
config['bigger'] = 100
|
||||
|
||||
@ -267,9 +202,9 @@ class CountdownEdit(RedirectView):
|
||||
pass
|
||||
except AttributeError:
|
||||
pass
|
||||
update_projector_overlay('projector_countdown')
|
||||
|
||||
def get_ajax_context(self, **kwargs):
|
||||
clear_projector_cache()
|
||||
return {
|
||||
'state': config['countdown_state'],
|
||||
'countdown_time': config['countdown_time'],
|
||||
@ -289,9 +224,9 @@ class OverlayMessageView(RedirectView):
|
||||
config['projector_message'] = request.POST['message_text']
|
||||
elif 'message-clean' in request.POST:
|
||||
config['projector_message'] = ''
|
||||
update_projector_overlay('projector_message')
|
||||
|
||||
def get_ajax_context(self, **kwargs):
|
||||
clear_projector_cache()
|
||||
return {
|
||||
'overlay_message': config['projector_message'],
|
||||
}
|
||||
@ -306,21 +241,20 @@ class ActivateOverlay(RedirectView):
|
||||
permission_required = 'projector.can_manage_projector'
|
||||
|
||||
def pre_redirect(self, request, *args, **kwargs):
|
||||
self.name = kwargs['name']
|
||||
active_overlays = config['projector_active_overlays']
|
||||
overlay = get_overlays()[kwargs['name']]
|
||||
self.name = overlay.name
|
||||
if kwargs['activate']:
|
||||
if self.name not in active_overlays:
|
||||
active_overlays.append(self.name)
|
||||
config['projector_active_overlays'] = active_overlays
|
||||
if not overlay.is_active():
|
||||
overlay.set_active(True)
|
||||
update_projector_overlay(overlay)
|
||||
self.active = True
|
||||
elif not kwargs['activate']:
|
||||
if self.name in active_overlays:
|
||||
active_overlays.remove(self.name)
|
||||
config['projector_active_overlays'] = active_overlays
|
||||
else:
|
||||
if overlay.is_active():
|
||||
overlay.set_active(False)
|
||||
update_projector_overlay(overlay)
|
||||
self.active = False
|
||||
|
||||
def get_ajax_context(self, **kwargs):
|
||||
clear_projector_cache()
|
||||
return {'active': self.active, 'name': self.name}
|
||||
|
||||
|
||||
@ -399,7 +333,8 @@ def get_widgets(request):
|
||||
# Overlay widget
|
||||
overlays = []
|
||||
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.update(csrf(request))
|
||||
widgets.append(Widget(
|
||||
@ -413,6 +348,7 @@ def get_widgets(request):
|
||||
context=context))
|
||||
|
||||
# Custom slide widget
|
||||
welcomepage_is_active = get_active_slide().get('callback', 'default') == 'default'
|
||||
widgets.append(Widget(
|
||||
request,
|
||||
name='custom_slide',
|
||||
@ -420,7 +356,7 @@ def get_widgets(request):
|
||||
template='projector/custom_slide_widget.html',
|
||||
context={
|
||||
'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',
|
||||
default_column=2,
|
||||
default_weight=30))
|
||||
|
@ -8,15 +8,15 @@
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import posixpath
|
||||
from urllib import unquote
|
||||
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.web import FallbackHandler, Application, StaticFileHandler
|
||||
from tornado.websocket import WebSocketHandler
|
||||
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.conf import settings
|
||||
@ -38,6 +38,25 @@ class DjangoStaticFileHandler(StaticFileHandler):
|
||||
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):
|
||||
# Don't try to read the command line args from openslides
|
||||
parse_command_line(args=[])
|
||||
@ -54,7 +73,9 @@ def run_tornado(addr, port, reload=False):
|
||||
tornado_app = Application([
|
||||
(r"%s(.*)" % settings.STATIC_URL, DjangoStaticFileHandler),
|
||||
(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.listen(port=port,
|
||||
|
@ -141,3 +141,10 @@ def htmldiff(text1, text2):
|
||||
|
||||
diff = difflib.HtmlDiff(wrapcolumn=60)
|
||||
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']
|
||||
else:
|
||||
extra_stylefiles = []
|
||||
# TODO: Do not go over the filesystem by any request
|
||||
for app in settings.INSTALLED_APPS:
|
||||
try:
|
||||
mod = import_module(app + '.views')
|
||||
|
@ -1,16 +1,11 @@
|
||||
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')
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Releated Item CHFNGEJ5634DJ34F'
|
||||
verbose_name = 'Related Item CHFNGEJ5634DJ34F'
|
||||
|
||||
def get_agenda_title(self):
|
||||
return self.name
|
||||
@ -22,11 +17,5 @@ class ReleatedItem(SlideMixin, models.Model):
|
||||
return '/absolute-url-here/'
|
||||
|
||||
|
||||
class BadReleatedItem(SlideMixin, models.Model):
|
||||
prefix = 'badreleateditem'
|
||||
|
||||
class BadRelatedItem(models.Model):
|
||||
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.agenda.models import Item, Speaker
|
||||
from openslides.config.api import config
|
||||
from openslides.projector.api import set_active_slide
|
||||
|
||||
|
||||
class ListOfSpeakerModelTests(TestCase):
|
||||
@ -214,36 +215,30 @@ class SpeakerListOpenView(SpeakerViewTestCase):
|
||||
|
||||
class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
||||
def test_global_redirect_url(self):
|
||||
self.assertFalse(config['presentation'])
|
||||
response = self.speaker1_client.get('/agenda/list_of_speakers/')
|
||||
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.')
|
||||
|
||||
response = self.admin_client.get('/projector/activate/item-1/')
|
||||
self.assertEqual(config['presentation'], 'item-1')
|
||||
set_active_slide('agenda', {'pk': 1})
|
||||
response = self.speaker1_client.get('/agenda/list_of_speakers/')
|
||||
self.assertRedirects(response, '/agenda/1/')
|
||||
|
||||
def test_global_add_url(self):
|
||||
self.assertFalse(config['presentation'])
|
||||
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
|
||||
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.')
|
||||
|
||||
response = self.admin_client.get('/projector/activate/item-1/')
|
||||
self.assertEqual(config['presentation'], 'item-1')
|
||||
set_active_slide('agenda', {'pk': 1})
|
||||
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
|
||||
self.assertRedirects(response, '/agenda/1/')
|
||||
self.assertEqual(Speaker.objects.get(item__pk='1').person, self.speaker1)
|
||||
|
||||
def test_global_next_speaker_url(self):
|
||||
self.assertFalse(config['presentation'])
|
||||
response = self.admin_client.get('/agenda/list_of_speakers/next/')
|
||||
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.')
|
||||
|
||||
response = self.admin_client.get('/projector/activate/item-1/')
|
||||
self.assertEqual(config['presentation'], 'item-1')
|
||||
set_active_slide('agenda', {'pk': 1})
|
||||
response = self.admin_client.get('/agenda/list_of_speakers/next/')
|
||||
self.assertRedirects(response, '/projector/dashboard/')
|
||||
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)
|
||||
|
||||
def test_global_end_speach_url(self):
|
||||
self.assertFalse(config['presentation'])
|
||||
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
|
||||
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.')
|
||||
|
||||
response = self.admin_client.get('/projector/activate/item-1/')
|
||||
self.assertEqual(config['presentation'], 'item-1')
|
||||
set_active_slide('agenda', {'pk': 1})
|
||||
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
|
||||
self.assertRedirects(response, '/projector/dashboard/')
|
||||
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 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.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):
|
||||
@ -28,8 +28,8 @@ class ItemTest(TestCase):
|
||||
self.item2 = Item.objects.create(title='item2')
|
||||
self.item3 = Item.objects.create(title='item1A', parent=self.item1)
|
||||
self.item4 = Item.objects.create(title='item1Aa', parent=self.item3)
|
||||
self.releated = ReleatedItem.objects.create(name='ekdfjen458gj1siek45nv')
|
||||
self.item5 = Item.objects.create(title='item5', content_object=self.releated)
|
||||
self.related = RelatedItem.objects.create(name='ekdfjen458gj1siek45nv')
|
||||
self.item5 = Item.objects.create(title='item5', content_object=self.related)
|
||||
|
||||
def testClosed(self):
|
||||
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('delete'), '/agenda/1/del/')
|
||||
|
||||
def test_agenda_slide(self):
|
||||
data = agenda_show()
|
||||
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)
|
||||
def test_related_item(self):
|
||||
self.assertEqual(self.item5.get_title(), self.related.name)
|
||||
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):
|
||||
self.releated.delete()
|
||||
self.assertFalse(ReleatedItem.objects.all().exists())
|
||||
def test_deleted_related_item(self):
|
||||
self.related.delete()
|
||||
self.assertFalse(RelatedItem.objects.all().exists())
|
||||
self.assertEqual(Item.objects.get(pk=self.item5.pk).title, '< Item for deleted slide (ekdfjen458gj1siek45nv) >')
|
||||
|
||||
def test_bad_releated_item(self):
|
||||
bad = BadReleatedItem.objects.create(name='dhfne94irkgl2047fzvb')
|
||||
def test_bad_related_item(self):
|
||||
bad = BadRelatedItem.objects.create(name='dhfne94irkgl2047fzvb')
|
||||
item = Item.objects.create(title='item_jghfndzrh46w738kdmc', content_object=bad)
|
||||
self.assertRaisesMessage(
|
||||
NotImplementedError,
|
||||
@ -138,20 +132,6 @@ class ViewTest(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
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):
|
||||
c = self.adminClient
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user