Remove old thinks not needed for the 2.0 release:
* django templates * widgets * views * mppt * main_menu * projector 1.x api Sorted all imports Add a ending slash to each url with a redirect view
This commit is contained in:
parent
1ccd2ab91c
commit
fbf7d0e43d
@ -11,7 +11,7 @@ Version 2.0.0 (unreleased)
|
|||||||
Agenda:
|
Agenda:
|
||||||
- Updated the tests and changed only small internal parts of method of the
|
- Updated the tests and changed only small internal parts of method of the
|
||||||
agenda model. No API changes.
|
agenda model. No API changes.
|
||||||
- Deprecated mptt.
|
- Removed mptt.
|
||||||
Assignments:
|
Assignments:
|
||||||
- Renamed app from assignment to assignments.
|
- Renamed app from assignment to assignments.
|
||||||
- Massive refactoring and cleanup of the app.
|
- Massive refactoring and cleanup of the app.
|
||||||
@ -48,6 +48,7 @@ Other:
|
|||||||
Sheets libraries.
|
Sheets libraries.
|
||||||
- Used setup.cfg for development tools.
|
- Used setup.cfg for development tools.
|
||||||
- Fixed bug in LocalizedModelMultipleChoiceField.
|
- Fixed bug in LocalizedModelMultipleChoiceField.
|
||||||
|
- Removed the django error pages
|
||||||
|
|
||||||
|
|
||||||
Version 1.7 (2015-02-16)
|
Version 1.7 (2015-02-16)
|
||||||
|
@ -7,14 +7,15 @@ from django.core.management import execute_from_command_line
|
|||||||
|
|
||||||
from openslides import __version__ as openslides_version
|
from openslides import __version__ as openslides_version
|
||||||
from openslides.utils.main import (
|
from openslides.utils.main import (
|
||||||
get_default_settings_path,
|
|
||||||
setup_django_settings_module,
|
|
||||||
write_settings,
|
|
||||||
UnknownCommand,
|
|
||||||
ExceptionArgumentParser,
|
ExceptionArgumentParser,
|
||||||
|
UnknownCommand,
|
||||||
|
get_default_settings_path,
|
||||||
get_development_settings_path,
|
get_development_settings_path,
|
||||||
|
is_development,
|
||||||
|
setup_django_settings_module,
|
||||||
start_browser,
|
start_browser,
|
||||||
is_development)
|
write_settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -1 +0,0 @@
|
|||||||
default_app_config = 'openslides.account.apps.AccountAppConfig'
|
|
@ -1,10 +0,0 @@
|
|||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class AccountAppConfig(AppConfig):
|
|
||||||
name = 'openslides.account'
|
|
||||||
verbose_name = 'OpenSlides Account'
|
|
||||||
|
|
||||||
def ready(self):
|
|
||||||
# Load widget.
|
|
||||||
from . import widgets # noqa
|
|
@ -1,22 +0,0 @@
|
|||||||
{% extends 'core/widget.html' %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load tags %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% for infoblock in infoblocks %}
|
|
||||||
{% if infoblock.is_active %}
|
|
||||||
<ul style="line-height: 180%">
|
|
||||||
{{ infoblock.headline }}:
|
|
||||||
{% for object in infoblock.get_queryset %}
|
|
||||||
<li><a href="{{ object|absolute_url }}">{{ object }}</a></li>
|
|
||||||
{% empty %}
|
|
||||||
<li><i>{% trans 'None' %}</i></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% if not forloop.last %}
|
|
||||||
<hr />
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock %}
|
|
@ -1,42 +0,0 @@
|
|||||||
from django.contrib.auth.models import AnonymousUser
|
|
||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
from openslides.utils.personal_info import PersonalInfo
|
|
||||||
from openslides.utils.widgets import Widget
|
|
||||||
|
|
||||||
|
|
||||||
class PersonalInfoWidget(Widget):
|
|
||||||
"""
|
|
||||||
Provides a widget for personal info. It shows all info block given by the
|
|
||||||
personal info api. See openslides.utils.personal_info.PersonalInfo.
|
|
||||||
"""
|
|
||||||
name = 'personal_info'
|
|
||||||
verbose_name = ugettext_lazy('My personal info')
|
|
||||||
default_column = 1
|
|
||||||
default_weight = 80
|
|
||||||
template_name = 'account/widget_personal_info.html'
|
|
||||||
icon_css_class = 'icon-flag'
|
|
||||||
|
|
||||||
def check_permission(self):
|
|
||||||
"""
|
|
||||||
The widget is disabled for anonymous users.
|
|
||||||
"""
|
|
||||||
return not isinstance(self.request.user, AnonymousUser)
|
|
||||||
|
|
||||||
def is_active(self):
|
|
||||||
"""
|
|
||||||
The widget is disabled if there are no info blocks at the moment.
|
|
||||||
"""
|
|
||||||
for infoblock in PersonalInfo.get_all(self.request):
|
|
||||||
if infoblock.is_active():
|
|
||||||
active = super(PersonalInfoWidget, self).is_active()
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
active = False
|
|
||||||
return active
|
|
||||||
|
|
||||||
def get_context_data(self, **context):
|
|
||||||
"""
|
|
||||||
Adds the context to the widget.
|
|
||||||
"""
|
|
||||||
return super(PersonalInfoWidget, self).get_context_data(infoblocks=PersonalInfo.get_all(self.request), **context)
|
|
@ -6,28 +6,16 @@ class AgendaAppConfig(AppConfig):
|
|||||||
verbose_name = 'OpenSlides Agenda'
|
verbose_name = 'OpenSlides Agenda'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
# Load main menu entry, personal info and widgets.
|
|
||||||
# Do this by just importing all from these files.
|
|
||||||
from . import main_menu, personal_info, widgets # noqa
|
|
||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from django.db.models.signals import pre_delete
|
from django.db.models.signals import pre_delete
|
||||||
from openslides.config.signals import config_signal
|
from openslides.config.signals import config_signal
|
||||||
from openslides.projector.api import register_slide
|
|
||||||
from openslides.projector.signals import projector_overlays
|
|
||||||
from openslides.utils.rest_api import router
|
from openslides.utils.rest_api import router
|
||||||
from .signals import agenda_list_of_speakers, setup_agenda_config, listen_to_related_object_delete_signal
|
from .signals import setup_agenda_config, listen_to_related_object_delete_signal
|
||||||
from .slides import agenda_slide
|
|
||||||
from .views import ItemViewSet
|
from .views import ItemViewSet
|
||||||
|
|
||||||
# Connect signals.
|
# Connect signals.
|
||||||
config_signal.connect(setup_agenda_config, dispatch_uid='setup_agenda_config')
|
config_signal.connect(setup_agenda_config, dispatch_uid='setup_agenda_config')
|
||||||
projector_overlays.connect(agenda_list_of_speakers, dispatch_uid='agenda_list_of_speakers')
|
|
||||||
pre_delete.connect(listen_to_related_object_delete_signal, dispatch_uid='agenda_listen_to_related_object_delete_signal')
|
pre_delete.connect(listen_to_related_object_delete_signal, dispatch_uid='agenda_listen_to_related_object_delete_signal')
|
||||||
|
|
||||||
# Register slides.
|
|
||||||
Item = self.get_model('Item')
|
|
||||||
register_slide('agenda', agenda_slide, Item)
|
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register('agenda/item', ItemViewSet)
|
router.register('agenda/item', ItemViewSet)
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
import csv
|
|
||||||
import re
|
|
||||||
|
|
||||||
from django.db import transaction
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
|
|
||||||
from openslides.utils import csv_ext
|
|
||||||
|
|
||||||
from .models import Item
|
|
||||||
|
|
||||||
|
|
||||||
def import_agenda_items(csvfile):
|
|
||||||
"""
|
|
||||||
Performs the import of agenda items form a csv file.
|
|
||||||
"""
|
|
||||||
# Check encoding
|
|
||||||
try:
|
|
||||||
csvfile.read().decode('utf8')
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
return_value = '', '', _('Import file has wrong character encoding, only UTF-8 is supported!')
|
|
||||||
else:
|
|
||||||
csvfile.seek(0)
|
|
||||||
# Check dialect
|
|
||||||
dialect = csv.Sniffer().sniff(csvfile.readline().decode('utf8'))
|
|
||||||
dialect = csv_ext.patchup(dialect)
|
|
||||||
csvfile.seek(0)
|
|
||||||
# Parse CSV file
|
|
||||||
with transaction.atomic():
|
|
||||||
success_lines = []
|
|
||||||
error_lines = []
|
|
||||||
for (line_no, line) in enumerate(csv.reader(
|
|
||||||
(line.decode('utf8') for line in csvfile.readlines()), dialect=dialect)):
|
|
||||||
if line_no == 0:
|
|
||||||
# Do not read the header line
|
|
||||||
continue
|
|
||||||
# Check format
|
|
||||||
try:
|
|
||||||
title, text, duration = line[:3]
|
|
||||||
except ValueError:
|
|
||||||
error_lines.append(line_no + 1)
|
|
||||||
continue
|
|
||||||
if duration and re.match('^(?:[0-9]{1,2}:[0-5][0-9]|[0-9]+)$', duration) is None:
|
|
||||||
error_lines.append(line_no + 1)
|
|
||||||
continue
|
|
||||||
Item.objects.create(title=title, text=text, duration=duration)
|
|
||||||
success_lines.append(line_no + 1)
|
|
||||||
success = _('%d items successfully imported.') % len(success_lines)
|
|
||||||
if error_lines:
|
|
||||||
error = _('Error in the following lines: %s.') % ', '.join(str(number) for number in error_lines)
|
|
||||||
else:
|
|
||||||
error = ''
|
|
||||||
return_value = success, '', error
|
|
||||||
return return_value
|
|
@ -1,80 +0,0 @@
|
|||||||
import re
|
|
||||||
|
|
||||||
from ckeditor.widgets import CKEditorWidget
|
|
||||||
from django import forms
|
|
||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
from mptt.forms import TreeNodeChoiceField
|
|
||||||
|
|
||||||
from openslides.utils.forms import CssClassMixin, CleanHtmlFormMixin
|
|
||||||
from openslides.users.models import User
|
|
||||||
|
|
||||||
from .models import Item, Speaker
|
|
||||||
|
|
||||||
|
|
||||||
class ItemForm(CleanHtmlFormMixin, CssClassMixin, forms.ModelForm):
|
|
||||||
"""
|
|
||||||
Form to create of update an item.
|
|
||||||
"""
|
|
||||||
clean_html_fields = ('text', )
|
|
||||||
|
|
||||||
parent = TreeNodeChoiceField(
|
|
||||||
queryset=Item.objects.all(), label=ugettext_lazy("Parent item"), required=False)
|
|
||||||
|
|
||||||
duration = forms.RegexField(
|
|
||||||
regex=re.compile('^(?:[0-9]{1,2}:[0-5][0-9]|[0-9]+)$'),
|
|
||||||
error_message=ugettext_lazy("Invalid format. Hours from 0 to 99 and minutes from 00 to 59"),
|
|
||||||
max_length=5,
|
|
||||||
required=False,
|
|
||||||
label=ugettext_lazy("Duration"),
|
|
||||||
help_text=ugettext_lazy('Input format: HH:MM or M or MM or MMM'))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Item
|
|
||||||
fields = ('item_number', 'title', 'text', 'comment', 'tags', 'type', 'duration', 'parent', 'speaker_list_closed')
|
|
||||||
widgets = {'text': CKEditorWidget(config_name='images')}
|
|
||||||
|
|
||||||
|
|
||||||
class RelatedItemForm(ItemForm):
|
|
||||||
"""
|
|
||||||
Form to update an related item.
|
|
||||||
"""
|
|
||||||
class Meta:
|
|
||||||
model = Item
|
|
||||||
fields = ('comment', 'duration', 'parent', 'speaker_list_closed')
|
|
||||||
|
|
||||||
|
|
||||||
class ItemOrderForm(CssClassMixin, forms.Form):
|
|
||||||
"""
|
|
||||||
Form to change the order of the items.
|
|
||||||
"""
|
|
||||||
weight = forms.IntegerField(
|
|
||||||
widget=forms.HiddenInput(attrs={'class': 'menu-weight'}))
|
|
||||||
self = forms.IntegerField(
|
|
||||||
widget=forms.HiddenInput(attrs={'class': 'menu-mlid'}))
|
|
||||||
parent = forms.IntegerField(
|
|
||||||
widget=forms.HiddenInput(attrs={'class': 'menu-plid'}))
|
|
||||||
|
|
||||||
|
|
||||||
class AppendSpeakerForm(CssClassMixin, forms.Form):
|
|
||||||
"""
|
|
||||||
Form to set an user to a list of speakers.
|
|
||||||
"""
|
|
||||||
speaker = forms.ModelChoiceField(
|
|
||||||
User.objects.all(),
|
|
||||||
widget=forms.Select(attrs={'class': 'medium-input'}),
|
|
||||||
label=ugettext_lazy("Add participant"))
|
|
||||||
|
|
||||||
def __init__(self, item, *args, **kwargs):
|
|
||||||
self.item = item
|
|
||||||
return super(AppendSpeakerForm, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def clean_speaker(self):
|
|
||||||
"""
|
|
||||||
Checks, that the user is not already on the list.
|
|
||||||
"""
|
|
||||||
speaker = self.cleaned_data['speaker']
|
|
||||||
if Speaker.objects.filter(user=speaker, item=self.item, begin_time=None).exists():
|
|
||||||
raise forms.ValidationError(ugettext_lazy(
|
|
||||||
'%s is already on the list of speakers.'
|
|
||||||
% str(speaker)))
|
|
||||||
return speaker
|
|
@ -1,14 +0,0 @@
|
|||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
from openslides.utils.main_menu import MainMenuEntry
|
|
||||||
|
|
||||||
|
|
||||||
class AgendaMainMenuEntry(MainMenuEntry):
|
|
||||||
"""
|
|
||||||
Main menu entry for the agenda app.
|
|
||||||
"""
|
|
||||||
verbose_name = ugettext_lazy('Agenda')
|
|
||||||
required_permission = 'agenda.can_see'
|
|
||||||
default_weight = 20
|
|
||||||
pattern_name = '/agenda' # TODO: use generic solution, see issue #1469
|
|
||||||
icon_css_class = 'glyphicon-calendar'
|
|
@ -4,30 +4,22 @@ from django.contrib.auth.models import AnonymousUser
|
|||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||||
from mptt.models import MPTTModel, TreeForeignKey
|
|
||||||
|
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.core.models import Tag
|
from openslides.core.models import Tag
|
||||||
from openslides.projector.api import (reset_countdown,
|
|
||||||
start_countdown, stop_countdown)
|
|
||||||
from openslides.projector.models import SlideMixin
|
from openslides.projector.models import SlideMixin
|
||||||
|
from openslides.users.models import User
|
||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
from openslides.utils.models import AbsoluteUrlMixin
|
|
||||||
from openslides.utils.rest_api import RESTModelMixin
|
from openslides.utils.rest_api import RESTModelMixin
|
||||||
from openslides.utils.utils import to_roman
|
from openslides.utils.utils import to_roman
|
||||||
from openslides.users.models import User
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: remove mptt after removing the django views and forms
|
class Item(RESTModelMixin, SlideMixin, models.Model):
|
||||||
class Item(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
|
||||||
"""
|
"""
|
||||||
An Agenda Item
|
An Agenda Item
|
||||||
|
|
||||||
MPTT-model. See http://django-mptt.github.com/django-mptt/
|
|
||||||
"""
|
"""
|
||||||
slide_callback_name = 'agenda'
|
slide_callback_name = 'agenda'
|
||||||
|
|
||||||
@ -76,8 +68,7 @@ class Item(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
|||||||
The intended duration for the topic.
|
The intended duration for the topic.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
parent = TreeForeignKey('self', null=True, blank=True,
|
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
|
||||||
related_name='children')
|
|
||||||
"""
|
"""
|
||||||
The parent item in the agenda tree.
|
The parent item in the agenda tree.
|
||||||
"""
|
"""
|
||||||
@ -119,44 +110,35 @@ class Item(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
|||||||
('can_manage', ugettext_noop("Can manage agenda")),
|
('can_manage', ugettext_noop("Can manage agenda")),
|
||||||
('can_see_orga_items', ugettext_noop("Can see orga items and time scheduling of agenda")))
|
('can_see_orga_items', ugettext_noop("Can see orga items and time scheduling of agenda")))
|
||||||
|
|
||||||
class MPTTMeta:
|
def __str__(self):
|
||||||
order_insertion_by = ['weight']
|
return self.get_title()
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""
|
"""
|
||||||
Ensures that the children of orga items are only orga items.
|
Ensures that the children of orga items are only orga items.
|
||||||
"""
|
"""
|
||||||
if self.type == self.AGENDA_ITEM and self.parent is not None and self.parent.type == self.ORGANIZATIONAL_ITEM:
|
if (self.type == self.AGENDA_ITEM and
|
||||||
|
self.parent is not None and
|
||||||
|
self.parent.type == self.ORGANIZATIONAL_ITEM):
|
||||||
raise ValidationError(_('Agenda items can not be child elements of an organizational item.'))
|
raise ValidationError(_('Agenda items can not be child elements of an organizational item.'))
|
||||||
if self.type == self.ORGANIZATIONAL_ITEM and self.get_descendants().filter(type=self.AGENDA_ITEM).exists():
|
if (self.type == self.ORGANIZATIONAL_ITEM and
|
||||||
|
self.children.filter(type=self.AGENDA_ITEM).exists()):
|
||||||
raise ValidationError(_('Organizational items can not have agenda items as child elements.'))
|
raise ValidationError(_('Organizational items can not have agenda items as child elements.'))
|
||||||
return super().clean()
|
return super().clean()
|
||||||
|
|
||||||
def __str__(self):
|
def delete(self, with_children=False):
|
||||||
return self.get_title()
|
|
||||||
|
|
||||||
def get_absolute_url(self, link='detail'):
|
|
||||||
"""
|
"""
|
||||||
Return the URL to this item.
|
Delete the Item.
|
||||||
|
|
||||||
The link can be detail, update or delete.
|
If with_children is True, all children of the item will be deleted as
|
||||||
|
well. If with_children is False, all children will be children of the
|
||||||
|
parent of the item.
|
||||||
"""
|
"""
|
||||||
if link == 'detail':
|
if not with_children:
|
||||||
url = reverse('item_view', args=[str(self.id)])
|
for child in self.children.all():
|
||||||
elif link == 'update':
|
child.parent = self.parent
|
||||||
url = reverse('item_edit', args=[str(self.id)])
|
child.save()
|
||||||
elif link == 'delete':
|
super().delete()
|
||||||
url = reverse('item_delete', args=[str(self.id)])
|
|
||||||
elif link == 'projector_list_of_speakers':
|
|
||||||
url = '%s&type=list_of_speakers' % super().get_absolute_url('projector')
|
|
||||||
elif link == 'projector_summary':
|
|
||||||
url = '%s&type=summary' % super().get_absolute_url('projector')
|
|
||||||
elif (link in ('projector', 'projector_preview') and
|
|
||||||
self.content_object and isinstance(self.content_object, SlideMixin)):
|
|
||||||
url = self.content_object.get_absolute_url(link)
|
|
||||||
else:
|
|
||||||
url = super().get_absolute_url(link)
|
|
||||||
return url
|
|
||||||
|
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
"""
|
"""
|
||||||
@ -183,39 +165,6 @@ class Item(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise NotImplementedError('You have to provide a get_agenda_title_supplement method on your related model.')
|
raise NotImplementedError('You have to provide a get_agenda_title_supplement method on your related model.')
|
||||||
|
|
||||||
@property
|
|
||||||
def weight_form(self):
|
|
||||||
"""
|
|
||||||
Return the WeightForm for this item.
|
|
||||||
"""
|
|
||||||
from openslides.agenda.forms import ItemOrderForm
|
|
||||||
try:
|
|
||||||
parent = self.parent.id
|
|
||||||
except AttributeError:
|
|
||||||
parent = 0
|
|
||||||
initial = {
|
|
||||||
'weight': self.weight,
|
|
||||||
'self': self.id,
|
|
||||||
'parent': parent,
|
|
||||||
}
|
|
||||||
return ItemOrderForm(initial=initial, prefix="i%d" % self.id)
|
|
||||||
|
|
||||||
def delete(self, with_children=False):
|
|
||||||
"""
|
|
||||||
Delete the Item.
|
|
||||||
|
|
||||||
If with_children is True, all children of the item will be deleted as
|
|
||||||
well. If with_children is False, all children will be children of the
|
|
||||||
parent of the item.
|
|
||||||
"""
|
|
||||||
if not with_children:
|
|
||||||
for child in self.get_children():
|
|
||||||
child.move_to(self.parent)
|
|
||||||
child.save()
|
|
||||||
super().delete()
|
|
||||||
# TODO: Try to remove the rebuild call
|
|
||||||
Item.objects.rebuild()
|
|
||||||
|
|
||||||
def get_list_of_speakers(self, old_speakers_count=None, coming_speakers_count=None):
|
def get_list_of_speakers(self, old_speakers_count=None, coming_speakers_count=None):
|
||||||
"""
|
"""
|
||||||
Returns the list of speakers as a list of dictionaries. Each
|
Returns the list of speakers as a list of dictionaries. Each
|
||||||
@ -316,27 +265,28 @@ class Item(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
|||||||
Returns the number of this agenda item.
|
Returns the number of this agenda item.
|
||||||
"""
|
"""
|
||||||
if self.type == self.AGENDA_ITEM:
|
if self.type == self.AGENDA_ITEM:
|
||||||
if self.is_root_node():
|
if self.parent is None:
|
||||||
|
sibling_no = self.sibling_no()
|
||||||
if config['agenda_numeral_system'] == 'arabic':
|
if config['agenda_numeral_system'] == 'arabic':
|
||||||
return str(self._calc_sibling_no())
|
return str(sibling_no)
|
||||||
else: # config['agenda_numeral_system'] == 'roman'
|
else: # config['agenda_numeral_system'] == 'roman'
|
||||||
return to_roman(self._calc_sibling_no())
|
return to_roman(sibling_no)
|
||||||
else:
|
else:
|
||||||
return '%s.%s' % (self.parent.calc_item_no(), self._calc_sibling_no())
|
return '%s.%s' % (self.parent.calc_item_no(), self.sibling_no())
|
||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def _calc_sibling_no(self):
|
def sibling_no(self):
|
||||||
"""
|
"""
|
||||||
Counts all siblings on the same level which are AGENDA_ITEMs.
|
Counts how many AGENDA_ITEMS with the same parent (siblings) have a
|
||||||
|
smaller weight then this item.
|
||||||
|
|
||||||
|
Returns this number + 1 or 0 when self is not an AGENDA_ITEM.
|
||||||
"""
|
"""
|
||||||
sibling_no = 0
|
return Item.objects.filter(
|
||||||
prev_sibling = self.get_previous_sibling()
|
parent=self.parent,
|
||||||
while prev_sibling is not None:
|
type=self.AGENDA_ITEM,
|
||||||
if prev_sibling.type == self.AGENDA_ITEM:
|
weight__lte=self.weight).count()
|
||||||
sibling_no += 1
|
|
||||||
prev_sibling = prev_sibling.get_previous_sibling()
|
|
||||||
return sibling_no + 1
|
|
||||||
|
|
||||||
|
|
||||||
class SpeakerManager(models.Manager):
|
class SpeakerManager(models.Manager):
|
||||||
@ -353,7 +303,7 @@ class SpeakerManager(models.Manager):
|
|||||||
return self.create(item=item, user=user, weight=weight + 1)
|
return self.create(item=item, user=user, weight=weight + 1)
|
||||||
|
|
||||||
|
|
||||||
class Speaker(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
class Speaker(RESTModelMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
Model for the Speaker list.
|
Model for the Speaker list.
|
||||||
"""
|
"""
|
||||||
@ -393,16 +343,6 @@ class Speaker(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.user)
|
return str(self.user)
|
||||||
|
|
||||||
def get_absolute_url(self, link='detail'):
|
|
||||||
if link == 'detail':
|
|
||||||
url = self.user.get_absolute_url('detail')
|
|
||||||
elif link == 'delete':
|
|
||||||
url = reverse('agenda_speaker_delete',
|
|
||||||
args=[self.item.pk, self.pk])
|
|
||||||
else:
|
|
||||||
url = super(Speaker, self).get_absolute_url(link)
|
|
||||||
return url
|
|
||||||
|
|
||||||
def begin_speach(self):
|
def begin_speach(self):
|
||||||
"""
|
"""
|
||||||
Let the user speak.
|
Let the user speak.
|
||||||
@ -421,8 +361,10 @@ class Speaker(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
self.save()
|
self.save()
|
||||||
# start countdown
|
# start countdown
|
||||||
if config['agenda_couple_countdown_and_speakers']:
|
if config['agenda_couple_countdown_and_speakers']:
|
||||||
reset_countdown()
|
# TODO: Fix me with the new countdown api
|
||||||
start_countdown()
|
# reset_countdown()
|
||||||
|
# start_countdown()
|
||||||
|
pass
|
||||||
|
|
||||||
def end_speach(self):
|
def end_speach(self):
|
||||||
"""
|
"""
|
||||||
@ -432,7 +374,9 @@ class Speaker(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
self.save()
|
self.save()
|
||||||
# stop countdown
|
# stop countdown
|
||||||
if config['agenda_couple_countdown_and_speakers']:
|
if config['agenda_couple_countdown_and_speakers']:
|
||||||
stop_countdown()
|
# TODO: Fix me with the new countdown api
|
||||||
|
# stop_countdown()
|
||||||
|
pass
|
||||||
|
|
||||||
def get_root_rest_element(self):
|
def get_root_rest_element(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
from openslides.utils.personal_info import PersonalInfo
|
|
||||||
|
|
||||||
from .models import Item
|
|
||||||
|
|
||||||
|
|
||||||
class AgendaPersonalInfo(PersonalInfo):
|
|
||||||
"""
|
|
||||||
Class for user info block for the agenda app.
|
|
||||||
"""
|
|
||||||
headline = ugettext_lazy('I am on the list of speakers of the following items')
|
|
||||||
default_weight = 10
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return Item.objects.filter(
|
|
||||||
speaker__user=self.request.user,
|
|
||||||
speaker__begin_time=None)
|
|
@ -1,4 +1,5 @@
|
|||||||
from haystack import indexes
|
from haystack import indexes
|
||||||
|
|
||||||
from .models import Item
|
from .models import Item
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
from openslides.utils.rest_api import CharField, ModelSerializer, RelatedField, get_collection_and_id_from_url
|
from openslides.utils.rest_api import (
|
||||||
|
CharField,
|
||||||
|
ModelSerializer,
|
||||||
|
RelatedField,
|
||||||
|
get_collection_and_id_from_url,
|
||||||
|
)
|
||||||
|
|
||||||
from .models import Item, Speaker
|
from .models import Item, Speaker
|
||||||
|
|
||||||
|
@ -3,13 +3,10 @@ from datetime import datetime
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.template.loader import render_to_string
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||||
|
|
||||||
from openslides.config.api import config, ConfigCollection, ConfigVariable
|
from openslides.config.api import ConfigCollection, ConfigVariable
|
||||||
from openslides.projector.api import get_active_slide, get_active_object
|
|
||||||
from openslides.projector.projector import Overlay
|
|
||||||
|
|
||||||
from .models import Item
|
from .models import Item
|
||||||
|
|
||||||
@ -90,54 +87,6 @@ def setup_agenda_config(sender, **kwargs):
|
|||||||
'extra_javascript': extra_javascript})
|
'extra_javascript': extra_javascript})
|
||||||
|
|
||||||
|
|
||||||
def agenda_list_of_speakers(sender, **kwargs):
|
|
||||||
"""
|
|
||||||
Receiver function to setup the list of speaker overlay. It is connected
|
|
||||||
to the signal openslides.projector.signals.projector_overlays during
|
|
||||||
app loading.
|
|
||||||
"""
|
|
||||||
name = 'agenda_speaker'
|
|
||||||
|
|
||||||
def get_widget_html():
|
|
||||||
"""
|
|
||||||
Returns the the html-code to show in the overly-widget.
|
|
||||||
"""
|
|
||||||
return render_to_string('agenda/overlay_speaker_widget.html')
|
|
||||||
|
|
||||||
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_active_object()
|
|
||||||
if slide is None or isinstance(slide, Item):
|
|
||||||
item = slide
|
|
||||||
else:
|
|
||||||
# TODO: If there is more than one item, use the first one in the
|
|
||||||
# mptt tree that is not closed.
|
|
||||||
try:
|
|
||||||
item = Item.objects.filter(
|
|
||||||
content_type=ContentType.objects.get_for_model(slide),
|
|
||||||
object_id=slide.pk)[0]
|
|
||||||
except IndexError:
|
|
||||||
item = None
|
|
||||||
if item and get_active_slide().get('type', None) != 'list_of_speakers':
|
|
||||||
list_of_speakers = item.get_list_of_speakers(
|
|
||||||
old_speakers_count=config['agenda_show_last_speakers'],
|
|
||||||
coming_speakers_count=5)
|
|
||||||
|
|
||||||
value = render_to_string('agenda/overlay_speaker_projector.html', {
|
|
||||||
'list_of_speakers': list_of_speakers,
|
|
||||||
'closed': item.speaker_list_closed})
|
|
||||||
else:
|
|
||||||
value = None
|
|
||||||
return value
|
|
||||||
|
|
||||||
return Overlay(name, get_widget_html, get_projector_html)
|
|
||||||
|
|
||||||
|
|
||||||
def listen_to_related_object_delete_signal(sender, instance, **kwargs):
|
def listen_to_related_object_delete_signal(sender, instance, **kwargs):
|
||||||
"""
|
"""
|
||||||
Receiver function to changed agenda items of a related items that is to
|
Receiver function to changed agenda items of a related items that is to
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
from django.template.loader import render_to_string
|
|
||||||
|
|
||||||
from openslides.config.api import config
|
|
||||||
from openslides.projector.api import get_projector_content
|
|
||||||
|
|
||||||
from .models import Item
|
|
||||||
|
|
||||||
|
|
||||||
def agenda_slide(**kwargs):
|
|
||||||
"""
|
|
||||||
Return the html code for all slides of the agenda app.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
The function is registered during app loading.
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
slide = 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}
|
|
||||||
slide = 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}
|
|
||||||
slide = get_projector_content(slide_dict)
|
|
||||||
|
|
||||||
else:
|
|
||||||
context = {'item': item}
|
|
||||||
slide = render_to_string('agenda/item_slide.html', context)
|
|
||||||
return slide
|
|
@ -1,4 +0,0 @@
|
|||||||
{% extends 'projector.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{% trans 'List of speakers' %} – {{ block.super }}{% endblock %}
|
|
@ -1,12 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
<link type="text/css" rel="stylesheet" media="all" href="{% static 'css/agenda_projector.css' %}" />
|
|
||||||
|
|
||||||
<h1>
|
|
||||||
{{ item }}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
{% if item.text %}
|
|
||||||
{{ item.text|safe }}
|
|
||||||
{% endif %}
|
|
@ -1,27 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
<link type="text/css" rel="stylesheet" media="all" href="{% static 'css/agenda_projector.css' %}" />
|
|
||||||
|
|
||||||
<h1>
|
|
||||||
{{ title }}
|
|
||||||
<small>
|
|
||||||
{% trans 'List of speakers' %}
|
|
||||||
{% if item.speaker_list_closed %}(<span class="closed">{% trans 'closed' %}</span>){% endif %}
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
{% 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 %}
|
|
@ -1,17 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
<link type="text/css" rel="stylesheet" media="all" href="{% static 'css/agenda_projector.css' %}" />
|
|
||||||
|
|
||||||
<h1>
|
|
||||||
{% if title %}{{ title }}{% else %}{% trans "Agenda" %}{% endif %}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<ul class="itemlist">
|
|
||||||
{% for item in items %}
|
|
||||||
<li{% if item.closed %} class="closed" {% endif %}>
|
|
||||||
{{ item }}
|
|
||||||
<small>{{ item.get_title_supplement|safe }}</small>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
@ -1,22 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
<link type="text/css" rel="stylesheet" media="all" href="{% static 'css/agenda_projector.css' %}" />
|
|
||||||
|
|
||||||
<div id="overlay_list_of_speaker_box">
|
|
||||||
<h3>{% trans "List of speakers" %} {% if closed %}(<span class="closed">{% trans 'closed' %}</span>){% endif %}</h3>
|
|
||||||
{% 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 %}
|
|
||||||
</div>
|
|
@ -1,6 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<span>
|
|
||||||
{% trans 'List of speakers' %}
|
|
||||||
<small class="grey">({% trans 'This overlay only appears on agenda slides if it is activated.' %})</small>
|
|
||||||
</span>
|
|
@ -4,14 +4,7 @@ from . import views
|
|||||||
|
|
||||||
urlpatterns = patterns(
|
urlpatterns = patterns(
|
||||||
'',
|
'',
|
||||||
|
|
||||||
# PDF
|
|
||||||
url(r'^print/$',
|
url(r'^print/$',
|
||||||
views.AgendaPDF.as_view(),
|
views.AgendaPDF.as_view(),
|
||||||
name='agenda_pdf'),
|
name='agenda_pdf'),
|
||||||
|
|
||||||
# TODO: remove it after implement projector rest api
|
|
||||||
url(r'^list_of_speakers/projector/$',
|
|
||||||
views.CurrentListOfSpeakersProjectorView.as_view(),
|
|
||||||
name='agenda_current_list_of_speakers_projector'),
|
|
||||||
)
|
)
|
||||||
|
@ -1,24 +1,11 @@
|
|||||||
# TODO: Rename all views and template names
|
|
||||||
|
|
||||||
from cgi import escape
|
from cgi import escape
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from json import dumps
|
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.contrib.staticfiles.templatetags.staticfiles import static
|
|
||||||
from django.template.loader import render_to_string
|
|
||||||
from django.utils.datastructures import SortedDict
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import ugettext_lazy
|
||||||
from reportlab.platypus import Paragraph
|
from reportlab.platypus import Paragraph
|
||||||
|
|
||||||
from openslides.config.api import config
|
|
||||||
from openslides.projector.api import (
|
|
||||||
get_active_object,
|
|
||||||
get_projector_overlays_js,
|
|
||||||
get_overlays)
|
|
||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
from openslides.utils.pdf import stylesheet
|
from openslides.utils.pdf import stylesheet
|
||||||
from openslides.utils.rest_api import (
|
from openslides.utils.rest_api import (
|
||||||
@ -28,35 +15,12 @@ from openslides.utils.rest_api import (
|
|||||||
detail_route,
|
detail_route,
|
||||||
list_route,
|
list_route,
|
||||||
)
|
)
|
||||||
from openslides.utils.views import (
|
from openslides.utils.views import PDFView
|
||||||
AjaxMixin,
|
|
||||||
PDFView,
|
|
||||||
RedirectView,
|
|
||||||
SingleObjectMixin,
|
|
||||||
TemplateView)
|
|
||||||
|
|
||||||
from .models import Item, Speaker
|
from .models import Item, Speaker
|
||||||
from .serializers import ItemSerializer
|
from .serializers import ItemSerializer
|
||||||
|
|
||||||
|
|
||||||
class CreateRelatedAgendaItemView(SingleObjectMixin, RedirectView):
|
|
||||||
"""
|
|
||||||
View to create and agenda item for a related object.
|
|
||||||
|
|
||||||
This view is only for subclassing in views of related apps. You
|
|
||||||
have to define 'model = ....'
|
|
||||||
"""
|
|
||||||
required_permission = 'agenda.can_manage'
|
|
||||||
url_name = 'item_overview'
|
|
||||||
url_name_args = []
|
|
||||||
|
|
||||||
def pre_redirect(self, request, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Create the agenda item.
|
|
||||||
"""
|
|
||||||
self.item = Item.objects.create(content_object=self.get_object())
|
|
||||||
|
|
||||||
|
|
||||||
class AgendaPDF(PDFView):
|
class AgendaPDF(PDFView):
|
||||||
"""
|
"""
|
||||||
Create a full agenda-PDF.
|
Create a full agenda-PDF.
|
||||||
@ -77,108 +41,6 @@ class AgendaPDF(PDFView):
|
|||||||
story.append(Paragraph(escape(item.get_title()), stylesheet['Item']))
|
story.append(Paragraph(escape(item.get_title()), stylesheet['Item']))
|
||||||
|
|
||||||
|
|
||||||
class CurrentListOfSpeakersProjectorView(AjaxMixin, TemplateView):
|
|
||||||
"""
|
|
||||||
View with the current list of speakers depending on the active slide.
|
|
||||||
Usefule for the projector.
|
|
||||||
"""
|
|
||||||
template_name = 'agenda/current_list_of_speakers_projector.html'
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Returns response object depending on request type (ajax or normal).
|
|
||||||
"""
|
|
||||||
if request.is_ajax():
|
|
||||||
value = self.ajax_get(request, *args, **kwargs)
|
|
||||||
else:
|
|
||||||
value = super(CurrentListOfSpeakersProjectorView, self).get(request, *args, **kwargs)
|
|
||||||
return value
|
|
||||||
|
|
||||||
def get_item(self):
|
|
||||||
"""
|
|
||||||
Returns the item of the current slide is an agenda item slide or a
|
|
||||||
slide of a related model else returns None.
|
|
||||||
"""
|
|
||||||
slide_object = get_active_object()
|
|
||||||
if slide_object is None or isinstance(slide_object, Item):
|
|
||||||
item = slide_object
|
|
||||||
else:
|
|
||||||
# TODO: If there is more than one item, use the first one in the
|
|
||||||
# mptt tree that is not closed.
|
|
||||||
try:
|
|
||||||
item = Item.objects.filter(
|
|
||||||
content_type=ContentType.objects.get_for_model(slide_object),
|
|
||||||
object_id=slide_object.pk)[0]
|
|
||||||
except IndexError:
|
|
||||||
item = None
|
|
||||||
return item
|
|
||||||
|
|
||||||
def get_content(self):
|
|
||||||
"""
|
|
||||||
Returns the content of this slide.
|
|
||||||
"""
|
|
||||||
item = self.get_item()
|
|
||||||
if item is None:
|
|
||||||
content = mark_safe('<h1>%s</h1><i>%s</i>\n' % (_('List of speakers'), _('Not available.')))
|
|
||||||
else:
|
|
||||||
content_dict = {
|
|
||||||
'title': item.get_title(),
|
|
||||||
'item': item,
|
|
||||||
'list_of_speakers': item.get_list_of_speakers(
|
|
||||||
old_speakers_count=config['agenda_show_last_speakers'])}
|
|
||||||
content = render_to_string('agenda/item_slide_list_of_speaker.html', content_dict)
|
|
||||||
return content
|
|
||||||
|
|
||||||
def get_overlays_and_overlay_js(self):
|
|
||||||
"""
|
|
||||||
Returns the overlays and their JavaScript for this slide as a
|
|
||||||
two-tuple. The overlay 'agenda_speaker' is always excluded.
|
|
||||||
|
|
||||||
The required JavaScript fot this view is inserted.
|
|
||||||
"""
|
|
||||||
overlays = get_overlays(only_active=True)
|
|
||||||
overlays.pop('agenda_speaker', None)
|
|
||||||
overlay_js = get_projector_overlays_js(as_json=True)
|
|
||||||
# Note: The JavaScript content of overlay 'agenda_speaker' is not
|
|
||||||
# excluded because this overlay has no such content at the moment.
|
|
||||||
extra_js = SortedDict()
|
|
||||||
extra_js['load_file'] = static('js/agenda_current_list_of_speakers_projector.js')
|
|
||||||
extra_js['call'] = 'reloadListOfSpeakers();'
|
|
||||||
extra_js = dumps(extra_js)
|
|
||||||
overlay_js.append(extra_js)
|
|
||||||
return overlays, overlay_js
|
|
||||||
|
|
||||||
def get_context_data(self, **context):
|
|
||||||
"""
|
|
||||||
Returns the context for the projector template. Contains the content
|
|
||||||
of this slide.
|
|
||||||
"""
|
|
||||||
overlays, overlay_js = self.get_overlays_and_overlay_js()
|
|
||||||
return super(CurrentListOfSpeakersProjectorView, self).get_context_data(
|
|
||||||
content=self.get_content(),
|
|
||||||
overlays=overlays,
|
|
||||||
overlay_js=overlay_js,
|
|
||||||
**context)
|
|
||||||
|
|
||||||
def get_ajax_context(self, **context):
|
|
||||||
"""
|
|
||||||
Returns the context including the slide content for ajax response. The
|
|
||||||
overlay 'agenda_speaker' is always excluded.
|
|
||||||
"""
|
|
||||||
overlay_dict = {}
|
|
||||||
for overlay in get_overlays().values():
|
|
||||||
if overlay.is_active() and overlay.name != 'agenda_speaker':
|
|
||||||
overlay_dict[overlay.name] = {
|
|
||||||
'html': overlay.get_projector_html(),
|
|
||||||
'javascript': overlay.get_javascript()}
|
|
||||||
else:
|
|
||||||
overlay_dict[overlay.name] = None
|
|
||||||
return super(CurrentListOfSpeakersProjectorView, self).get_ajax_context(
|
|
||||||
content=self.get_content(),
|
|
||||||
overlays=overlay_dict,
|
|
||||||
**context)
|
|
||||||
|
|
||||||
|
|
||||||
class ItemViewSet(ModelViewSet):
|
class ItemViewSet(ModelViewSet):
|
||||||
"""
|
"""
|
||||||
API endpoint to list, retrieve, create, update and destroy agenda items.
|
API endpoint to list, retrieve, create, update and destroy agenda items.
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
from openslides.utils.widgets import Widget
|
|
||||||
from openslides.projector.api import get_active_slide
|
|
||||||
|
|
||||||
from .models import Item
|
|
||||||
|
|
||||||
|
|
||||||
class AgendaWidget(Widget):
|
|
||||||
"""
|
|
||||||
Agenda widget.
|
|
||||||
"""
|
|
||||||
name = 'agenda'
|
|
||||||
verbose_name = ugettext_lazy('Agenda')
|
|
||||||
required_permission = 'core.can_manage_projector'
|
|
||||||
default_column = 1
|
|
||||||
default_weight = 20
|
|
||||||
template_name = 'agenda/widget_item.html'
|
|
||||||
icon_css_class = 'icon-calendar'
|
|
||||||
more_link_pattern_name = 'item_overview'
|
|
||||||
|
|
||||||
def get_context_data(self, **context):
|
|
||||||
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
|
|
||||||
context.update({
|
|
||||||
'agenda_is_active': agenda_is_active,
|
|
||||||
'items': Item.objects.all(),
|
|
||||||
'active_type': active_type})
|
|
||||||
return super(AgendaWidget, self).get_context_data(**context)
|
|
||||||
|
|
||||||
|
|
||||||
class ListOfSpeakersWidget(Widget):
|
|
||||||
"""
|
|
||||||
Widget to control the list of speakers.
|
|
||||||
"""
|
|
||||||
name = 'append_to_list_of_speakers'
|
|
||||||
verbose_name = ugettext_lazy('List of speakers')
|
|
||||||
default_column = 1
|
|
||||||
default_weight = 30
|
|
||||||
template_name = 'agenda/widget_list_of_speakers.html'
|
|
||||||
icon_css_class = 'icon-bell'
|
|
||||||
|
|
||||||
def check_permission(self):
|
|
||||||
return (self.request.user.has_perm('agenda.can_manage') or
|
|
||||||
self.request.user.has_perm('agenda.can_be_speaker'))
|
|
@ -6,31 +6,15 @@ class AssignmentAppConfig(AppConfig):
|
|||||||
verbose_name = 'OpenSlides Assignments'
|
verbose_name = 'OpenSlides Assignments'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
# Load main menu entry, personal info and widgets.
|
|
||||||
# Do this by just importing all from these files.
|
|
||||||
from . import main_menu, personal_info, widgets # noqa
|
|
||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from openslides.config.signals import config_signal
|
from openslides.config.signals import config_signal
|
||||||
from openslides.projector.api import register_slide_model
|
|
||||||
from openslides.utils.rest_api import router
|
from openslides.utils.rest_api import router
|
||||||
from openslides.utils.signals import template_manipulation
|
|
||||||
from .signals import setup_assignment_config
|
from .signals import setup_assignment_config
|
||||||
from .template import add_assignment_stylesheets
|
|
||||||
from .views import AssignmentViewSet, AssignmentPollViewSet
|
from .views import AssignmentViewSet, AssignmentPollViewSet
|
||||||
|
|
||||||
# Connect signals.
|
# Connect signals.
|
||||||
config_signal.connect(setup_assignment_config, dispatch_uid='setup_assignment_config')
|
config_signal.connect(setup_assignment_config, dispatch_uid='setup_assignment_config')
|
||||||
|
|
||||||
# Connect template signal.
|
|
||||||
template_manipulation.connect(add_assignment_stylesheets, dispatch_uid='add_assignment_stylesheets')
|
|
||||||
|
|
||||||
# Register slides.
|
|
||||||
Assignment = self.get_model('Assignment')
|
|
||||||
AssignmentPoll = self.get_model('AssignmentPoll')
|
|
||||||
register_slide_model(Assignment, 'assignments/slide.html')
|
|
||||||
register_slide_model(AssignmentPoll, 'assignments/assignmentpoll_slide.html')
|
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register('assignments/assignment', AssignmentViewSet)
|
router.register('assignments/assignment', AssignmentViewSet)
|
||||||
router.register('assignments/assignmentpoll', AssignmentPollViewSet)
|
router.register('assignments/assignmentpoll', AssignmentPollViewSet)
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
from django import forms
|
|
||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
from openslides.users.models import User
|
|
||||||
from openslides.utils.forms import CssClassMixin
|
|
||||||
|
|
||||||
from .models import Assignment
|
|
||||||
|
|
||||||
|
|
||||||
class AssignmentForm(CssClassMixin, forms.ModelForm):
|
|
||||||
open_posts = forms.IntegerField(
|
|
||||||
min_value=1, initial=1, label=ugettext_lazy("Number of available posts"))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Assignment
|
|
||||||
fields = ('title', 'description', 'open_posts', 'poll_description_default')
|
|
||||||
|
|
||||||
|
|
||||||
class AssignmentRunForm(CssClassMixin, forms.Form):
|
|
||||||
candidate = forms.ModelChoiceField(
|
|
||||||
queryset=User.objects.all(),
|
|
||||||
widget=forms.Select(attrs={'class': 'medium-input'}),
|
|
||||||
label=ugettext_lazy("Nominate a participant"))
|
|
@ -1,14 +0,0 @@
|
|||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
from openslides.utils.main_menu import MainMenuEntry
|
|
||||||
|
|
||||||
|
|
||||||
class AssignmentMainMenuEntry(MainMenuEntry):
|
|
||||||
"""
|
|
||||||
Main menu entry for the assignment app.
|
|
||||||
"""
|
|
||||||
verbose_name = ugettext_lazy('Elections')
|
|
||||||
required_permission = 'assignments.can_see'
|
|
||||||
default_weight = 40
|
|
||||||
pattern_name = '/assignments' # TODO: use generic solution, see issue #1469
|
|
||||||
icon_css_class = 'icon-assignment'
|
|
@ -1,21 +1,23 @@
|
|||||||
from django.contrib.contenttypes.fields import GenericRelation
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||||
|
|
||||||
from openslides.agenda.models import Item, Speaker
|
from openslides.agenda.models import Item, Speaker
|
||||||
from openslides.core.models import Tag
|
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.poll.models import (BaseOption, BasePoll, BaseVote,
|
from openslides.core.models import Tag
|
||||||
|
from openslides.poll.models import (
|
||||||
|
BaseOption,
|
||||||
|
BasePoll,
|
||||||
|
BaseVote,
|
||||||
CollectDefaultVotesMixin,
|
CollectDefaultVotesMixin,
|
||||||
PublishPollMixin)
|
PublishPollMixin,
|
||||||
|
)
|
||||||
from openslides.projector.models import SlideMixin
|
from openslides.projector.models import SlideMixin
|
||||||
from openslides.utils.exceptions import OpenSlidesError
|
|
||||||
from openslides.utils.models import AbsoluteUrlMixin
|
|
||||||
from openslides.utils.rest_api import RESTModelMixin
|
|
||||||
from openslides.users.models import User
|
from openslides.users.models import User
|
||||||
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
|
from openslides.utils.rest_api import RESTModelMixin
|
||||||
|
|
||||||
|
|
||||||
class AssignmentRelatedUser(RESTModelMixin, models.Model):
|
class AssignmentRelatedUser(RESTModelMixin, models.Model):
|
||||||
@ -53,7 +55,7 @@ class AssignmentRelatedUser(RESTModelMixin, models.Model):
|
|||||||
return self.assignment
|
return self.assignment
|
||||||
|
|
||||||
|
|
||||||
class Assignment(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
class Assignment(RESTModelMixin, SlideMixin, models.Model):
|
||||||
slide_callback_name = 'assignment'
|
slide_callback_name = 'assignment'
|
||||||
|
|
||||||
PHASE_SEARCH = 0
|
PHASE_SEARCH = 0
|
||||||
@ -133,20 +135,6 @@ class Assignment(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
def get_absolute_url(self, link='detail'):
|
|
||||||
"""
|
|
||||||
Returns absolute url to the assignment instance.
|
|
||||||
"""
|
|
||||||
if link == 'detail':
|
|
||||||
url = reverse('assignment_detail', args=[str(self.pk)])
|
|
||||||
elif link == 'update':
|
|
||||||
url = reverse('assignment_update', args=[str(self.pk)])
|
|
||||||
elif link == 'delete':
|
|
||||||
url = reverse('assignment_delete', args=[str(self.pk)])
|
|
||||||
else:
|
|
||||||
url = super().get_absolute_url(link)
|
|
||||||
return url
|
|
||||||
|
|
||||||
def get_slide_context(self, **context):
|
def get_slide_context(self, **context):
|
||||||
"""
|
"""
|
||||||
Retuns the context to generate the assignment slide.
|
Retuns the context to generate the assignment slide.
|
||||||
@ -351,7 +339,7 @@ class AssignmentOption(RESTModelMixin, BaseOption):
|
|||||||
|
|
||||||
|
|
||||||
class AssignmentPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
|
class AssignmentPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
|
||||||
PublishPollMixin, AbsoluteUrlMixin, BasePoll):
|
PublishPollMixin, BasePoll):
|
||||||
slide_callback_name = 'assignmentpoll'
|
slide_callback_name = 'assignmentpoll'
|
||||||
option_class = AssignmentOption
|
option_class = AssignmentOption
|
||||||
|
|
||||||
@ -365,20 +353,6 @@ class AssignmentPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _("Ballot %d") % self.get_ballot()
|
return _("Ballot %d") % self.get_ballot()
|
||||||
|
|
||||||
def get_absolute_url(self, link='update'):
|
|
||||||
"""
|
|
||||||
Return an URL for the poll.
|
|
||||||
|
|
||||||
The keyargument 'link' can be 'update' or 'delete'.
|
|
||||||
"""
|
|
||||||
if link == 'update':
|
|
||||||
url = reverse('assignmentpoll_update', args=[str(self.pk)])
|
|
||||||
elif link == 'delete':
|
|
||||||
url = reverse('assignmentpoll_delete', args=[str(self.pk)])
|
|
||||||
else:
|
|
||||||
url = super().get_absolute_url(link)
|
|
||||||
return url
|
|
||||||
|
|
||||||
def get_assignment(self):
|
def get_assignment(self):
|
||||||
return self.assignment
|
return self.assignment
|
||||||
|
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
from openslides.utils.personal_info import PersonalInfo
|
|
||||||
|
|
||||||
from .models import Assignment, AssignmentRelatedUser
|
|
||||||
|
|
||||||
|
|
||||||
class AssignmentPersonalInfo(PersonalInfo):
|
|
||||||
"""
|
|
||||||
Class for personal info block for the assignment app.
|
|
||||||
"""
|
|
||||||
headline = ugettext_lazy('I am candidate for the following elections')
|
|
||||||
default_weight = 40
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return (Assignment.objects.filter(assignment_related_users__user=self.request.user)
|
|
||||||
.exclude(assignment_related_users__status=AssignmentRelatedUser.STATUS_BLOCKED))
|
|
@ -1,4 +1,5 @@
|
|||||||
from haystack import indexes
|
from haystack import indexes
|
||||||
|
|
||||||
from .models import Assignment
|
from .models import Assignment
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,15 +7,17 @@ from openslides.utils.rest_api import (
|
|||||||
ListField,
|
ListField,
|
||||||
ListSerializer,
|
ListSerializer,
|
||||||
ModelSerializer,
|
ModelSerializer,
|
||||||
ValidationError)
|
ValidationError,
|
||||||
|
)
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
models,
|
|
||||||
Assignment,
|
Assignment,
|
||||||
AssignmentRelatedUser,
|
|
||||||
AssignmentOption,
|
AssignmentOption,
|
||||||
AssignmentPoll,
|
AssignmentPoll,
|
||||||
AssignmentVote)
|
AssignmentRelatedUser,
|
||||||
|
AssignmentVote,
|
||||||
|
models,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AssignmentRelatedUserSerializer(ModelSerializer):
|
class AssignmentRelatedUserSerializer(ModelSerializer):
|
||||||
|
@ -2,7 +2,11 @@ from django import forms
|
|||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||||
|
|
||||||
from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
|
from openslides.config.api import (
|
||||||
|
ConfigGroup,
|
||||||
|
ConfigGroupedCollection,
|
||||||
|
ConfigVariable,
|
||||||
|
)
|
||||||
from openslides.poll.models import PERCENT_BASE_CHOICES
|
from openslides.poll.models import PERCENT_BASE_CHOICES
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
def add_assignment_stylesheets(sender, request, context, **kwargs):
|
|
||||||
"""
|
|
||||||
Receiver function to add the assignment.css to the context. It is
|
|
||||||
connected to the signal openslides.utils.signals.template_manipulation
|
|
||||||
during app loading.
|
|
||||||
"""
|
|
||||||
context['extra_stylefiles'].append('css/assignment.css')
|
|
@ -1,356 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
{% load tags %}
|
|
||||||
{% load humanize %}
|
|
||||||
|
|
||||||
{% block title %}{% trans "Election" %} "{{ assignment }}" – {{ block.super }}{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block javascript %}
|
|
||||||
<script type="text/javascript" src="{% static 'js/assignment.js' %}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>
|
|
||||||
{{ assignment }}
|
|
||||||
<br>
|
|
||||||
<small>{% trans "Election" %}</small>
|
|
||||||
<small class="pull-right">
|
|
||||||
<a href="{% url 'assignment_list' %}" class="btn btn-default btn-sm">
|
|
||||||
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
|
|
||||||
<span class="optional-small"> {% trans "Back to overview" %}</span>
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'assignments_single_pdf' assignment.id %}" class="btn btn-default btn-sm"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Print election as PDF' %}" target="_blank">
|
|
||||||
<span class="glyphicon glyphicon-print" aria-hidden="true"></span>
|
|
||||||
<span class="optional-small"> PDF</span>
|
|
||||||
</a>
|
|
||||||
<!-- activate projector -->
|
|
||||||
{% if perms.core.can_manage_projector %}
|
|
||||||
<a href="{{ assignment|absolute_url:'projector' }}"
|
|
||||||
class="activate_link btn btn-default {% if assignment.is_active_slide %}btn-primary{% endif %} btn-sm"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Show election' %}">
|
|
||||||
<span class="glyphicon glyphicon-facetime-video" aria-hidden="true"></span>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.assignments.can_manage or perms.agenda.can_manage %}
|
|
||||||
<div class="btn-group">
|
|
||||||
<a data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">
|
|
||||||
<span class="optional-small">{% trans 'More actions' %}</span> <span class="caret"></span>
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu pull-right">
|
|
||||||
{% if perms.assignments.can_manage %}
|
|
||||||
<!-- edit -->
|
|
||||||
<li>
|
|
||||||
<a href="{{ assignment|absolute_url:'update' }}">
|
|
||||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
|
||||||
{% trans 'Edit election' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<!-- delete -->
|
|
||||||
<li>
|
|
||||||
<a href="{{ assignment|absolute_url:'delete' }}">
|
|
||||||
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
|
|
||||||
{% trans 'Delete election' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
<!-- create agenda item -->
|
|
||||||
{% if perms.agenda.can_manage %}
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'assignment_create_agenda' assignment.id %}">
|
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
|
||||||
{% trans 'New agenda item' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-9">
|
|
||||||
<!-- Tags -->
|
|
||||||
{% for tag in assignment.tags.all %}
|
|
||||||
<span class="optional label">{{ tag }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<!-- Description -->
|
|
||||||
<h4>{% trans "Description" %}</h4>
|
|
||||||
{% if assignment.description %}
|
|
||||||
{{ assignment.description|linebreaks }}
|
|
||||||
{% else %}
|
|
||||||
–
|
|
||||||
{% endif %}
|
|
||||||
<br>
|
|
||||||
<!-- Candidates -->
|
|
||||||
{% if assignment.phase != assignment.PHASE_FINISHED %}
|
|
||||||
<h4>{% trans "Candidates" %}</h4>
|
|
||||||
<ol>
|
|
||||||
{% for person in assignment.candidates %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ person|absolute_url }}">{{ person }}</a>
|
|
||||||
{% if perms.assignments.can_manage %}
|
|
||||||
<a href="{% url 'assignment_del_candidate_other' assignment.id person.pk %}"
|
|
||||||
class="btn btn-default btn-xs"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Remove candidate' %}">
|
|
||||||
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if person in assignment.elected %}
|
|
||||||
| <b>{% trans "elected" %}</b>
|
|
||||||
{% if perms.assignments.can_manage %}
|
|
||||||
<a href="{% url 'assignment_user_not_elected' assignment.id person.pk %}"
|
|
||||||
class="btn btn-default btn-xs"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Mark candidate as not elected' %}">
|
|
||||||
<span class="glyphicon glyphicon-ban-circle" aria-hidden="true"></span>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</li>
|
|
||||||
{% empty %}
|
|
||||||
<li style="list-style: none outside none; margin-left: -25px;">
|
|
||||||
<i>{% trans "No candidates available." %}</i>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ol>
|
|
||||||
{% if assignment.phase == assignment.PHASE_SEARCH or perms.assignments.can_manage and assignment.phase == assignment.PHASE_VOTING %}
|
|
||||||
{% if perms.assignments.can_nominate_self or perms.assignments.can_nominate_other %}
|
|
||||||
<form action="" method="post">{% csrf_token %}
|
|
||||||
{% if perms.assignments.can_nominate_self %}
|
|
||||||
<p>
|
|
||||||
{% if user_is_candidate %}
|
|
||||||
<a href='{% url 'assignment_del_candidate' assignment.id %}' class="btn btn-default btn-sm">
|
|
||||||
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
|
|
||||||
{% trans 'Withdraw self candidature' %}
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
<a href='{% url 'assignment_candidate' assignment.id %}' class="btn btn-default btn-sm">
|
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
|
||||||
{% trans 'Self candidature' %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.assignments.can_nominate_other %}
|
|
||||||
{% for field in form %}
|
|
||||||
<label>{{ field.label }}:</label>
|
|
||||||
{{ field }}
|
|
||||||
<button class="btn btn-primary btn-sm tooltip-bottom" type="submit"
|
|
||||||
data-original-title="{% trans 'Apply' %}">
|
|
||||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
|
|
||||||
</button>
|
|
||||||
{% if perms.users.can_see and perms.users.can_manage %}
|
|
||||||
<a href="{% url 'user_create' %}" target="_blank" class="btn btn-default"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Add new participant' %}">
|
|
||||||
<span class="glyphicon glyphicon-user" aria-hidden="true"></span>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if perms.assignments.can_manage and blocked_candidates and assignment.phase != assignment.PHASE_FINISHED %}
|
|
||||||
<h4>{% trans "Blocked Candidates" %}</h4>
|
|
||||||
<ul>
|
|
||||||
{% for person in blocked_candidates %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ person|absolute_url }}">{{ person }}</a>
|
|
||||||
<a class="btn btn-mini" href="{% url 'assignment_del_candidate_other' assignment.id person.pk %}"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Remove candidate' %}">
|
|
||||||
<i class="icon-ban-circle"></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% empty %}
|
|
||||||
<li>{% trans "No blocked candidates available." %}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Election result -->
|
|
||||||
{% if assignment.phase != assignment.PHASE_SEARCH or polls.exists %}
|
|
||||||
<h4>{% trans "Election result" %}</h4>
|
|
||||||
{% if polls.exists %}
|
|
||||||
<table id="election-result-table" class="table table-striped table-bordered">
|
|
||||||
<tr>
|
|
||||||
<th>{% trans "Candidates" %}</th>
|
|
||||||
{% for poll in polls %}
|
|
||||||
<th style="white-space: nowrap;" class="col-sm-1">
|
|
||||||
{% if perms.assignments.can_manage %}<p class="text-center">{% endif %}
|
|
||||||
{{ poll.get_ballot|ordinal|safe }} {% trans 'ballot' %}
|
|
||||||
{% if perms.assignments.can_manage %}
|
|
||||||
<a class="publish_link btn btn-sm btn-danger {% if poll.published %}btn-primary{% endif %}"
|
|
||||||
href="{% url 'assignmentpoll_publish_poll' poll.id %}"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Publish result' %}">
|
|
||||||
{% if poll.published %}
|
|
||||||
<i class="icon-checked-new_white"></i>
|
|
||||||
{% else %}
|
|
||||||
<i class="icon-unchecked-new"></i>
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p class="text-center">
|
|
||||||
<a href="{{ poll|absolute_url:'projector' }}"
|
|
||||||
class="btn btn-default btn-sm activate_link {% if poll.is_active_slide %}btn-primary{% endif %}"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Show election result' %}">
|
|
||||||
<span class="glyphicon glyphicon-facetime-video" aria-hidden="true"></span>
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'assignmentpoll_pdf' poll.id %}" class="btn btn-mini" target="_blank"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Ballot paper as PDF' %}"><i class="icon-print"></i></a>
|
|
||||||
<a href="{% url 'assignmentpoll_update' poll.id %}" class="btn btn-mini"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Edit' %}"><i class="icon-pencil"></i></a>
|
|
||||||
<a href="{% url 'assignmentpoll_delete' poll.id %}" class="btn btn-mini"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Delete' %}"><i class="icon-remove"></i></a>
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
</th>
|
|
||||||
{% endfor %}
|
|
||||||
{% if assignment.candidates and perms.assignments.can_manage and assignment.phase == assignment.PHASE_VOTING %}
|
|
||||||
<th class="col-sm-1 nobr">
|
|
||||||
<a href="{% url 'assignmentpoll_create' assignment.pk %}" class="btn btn-default btn-sm">
|
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
|
||||||
{% trans 'New ballot' %}
|
|
||||||
</a>
|
|
||||||
</th>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
{% for candidate, poll_list in vote_results.items %}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{% if candidate in assignment.elected %}
|
|
||||||
{% if perms.assignments.can_manage %}
|
|
||||||
<a class="election_link elected tooltip-bottom" href="{% url 'assignment_user_not_elected' assignment.id candidate.pk %}"
|
|
||||||
data-original-title="{% trans 'Mark candidate as elected' %}"></a>
|
|
||||||
{% else %}
|
|
||||||
<a class="elected">
|
|
||||||
<img src="{% static 'img/voting-yes.png' %}" class="tooltip-bottom" data-original-title="{% trans 'Candidate is elected' %}">
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
{% if perms.assignments.can_manage %}
|
|
||||||
<a class="election_link tooltip-bottom" href="{% url 'assignment_user_elected' assignment.id candidate.pk %}"
|
|
||||||
data-original-title="{% trans 'Mark candidate as elected' %}"></a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
<a href="{{ candidate|absolute_url }}">{{ candidate }}</a>
|
|
||||||
</td>
|
|
||||||
{% for vote in poll_list %}
|
|
||||||
<td style="white-space:nowrap;">
|
|
||||||
{% if 'Yes' in vote and 'No' in vote and 'Abstain' in vote %}
|
|
||||||
<img src="{% static 'img/voting-yes.png' %}" class="tooltip-left" data-original-title="{% trans 'Yes' %}"> {{ vote.Yes }}<br>
|
|
||||||
<img src="{% static 'img/voting-no.png' %}" class="tooltip-left" data-original-title="{% trans 'No' %}"> {{ vote.No }}<br>
|
|
||||||
<img src="{% static 'img/voting-abstention.png' %}" class="tooltip-left" data-original-title="{% trans 'Abstention' %}"> {{ vote.Abstain }}<br>
|
|
||||||
{% elif 'Votes' in vote %}
|
|
||||||
<img src="{% static 'img/voting-yes.png' %}" class="tooltip-left" data-original-title="{% trans 'Yes' %}"> {{ vote.Votes }}
|
|
||||||
{% elif vote == None %}
|
|
||||||
{% trans 'was not a <br> candidate'%}
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
{% endfor %}
|
|
||||||
{% if assignment.candidates and perms.assignments.can_manage and assignment.phase == assignment.PHASE_VOTING %}
|
|
||||||
<td></td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
<tr>
|
|
||||||
<td>{% trans 'Valid votes' %}</td>
|
|
||||||
{% for poll in polls %}
|
|
||||||
{% if poll.published or perms.assignments.can_manage %}
|
|
||||||
<td style="white-space:nowrap;">
|
|
||||||
{% if poll.has_votes %}
|
|
||||||
<img src="{% static 'img/voting-yes-grey.png' %}" class="tooltip-left" data-original-title="{% trans 'Valid votes' %}">
|
|
||||||
{{ poll.print_votesvalid }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% if assignment.candidates and perms.assignments.can_manage and assignment.phase == assignment.PHASE_VOTING %}
|
|
||||||
<td></td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{% trans 'Invalid votes' %}</td>
|
|
||||||
{% for poll in polls %}
|
|
||||||
{% if poll.published or perms.assignments.can_manage %}
|
|
||||||
<td style="white-space:nowrap;">
|
|
||||||
{% if poll.has_votes %}
|
|
||||||
<img src="{% static 'img/voting-invalid.png' %}" class="tooltip-left" data-original-title="{% trans 'Invalid votes' %}">
|
|
||||||
{{ poll.print_votesinvalid }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% if assignment.candidates and perms.assignments.can_manage and assignment.phase == assignment.PHASE_VOTING %}
|
|
||||||
<td></td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
<tr class="info total">
|
|
||||||
<td>{% trans 'Votes cast' %}</td>
|
|
||||||
{% for poll in polls %}
|
|
||||||
{% if poll.published or perms.assignments.can_manage %}
|
|
||||||
<td style="white-space:nowrap;">
|
|
||||||
{% if poll.has_votes %}
|
|
||||||
<img src="{% static 'img/voting-total.png' %}" class="tooltip-left" data-original-title="{% trans 'Votes cast' %}">
|
|
||||||
{{ poll.print_votescast }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% if assignment.candidates and perms.assignments.can_manage and assignment.phase == assignment.PHASE_VOTING %}
|
|
||||||
<td></td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
{% else %}
|
|
||||||
<i>{% trans "No ballots available." %}</i>
|
|
||||||
{% if assignment.candidates and perms.assignments.can_manage and assignment.phase == assignment.PHASE_VOTING %}
|
|
||||||
<p>
|
|
||||||
<a href='{% url 'assignmentpoll_create' assignment.id %}' class="btn btn-default">
|
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
|
||||||
{% trans 'New ballot' %}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<div class="well">
|
|
||||||
<!-- Text -->
|
|
||||||
<h5>{% trans "Phases" %}:</h5>
|
|
||||||
{% trans assignment.get_phase_display %}
|
|
||||||
<!-- Posts -->
|
|
||||||
<h5>{% trans "Number of available posts" %}:</h5>
|
|
||||||
{{ assignment.posts }}
|
|
||||||
</div> <!--/well-->
|
|
||||||
|
|
||||||
{% if perms.assignments.can_manage %}
|
|
||||||
<div class="well">
|
|
||||||
<h4>{% trans "Change phase" %}:</h4>
|
|
||||||
<div class="btn-group btn-group-vertical" data-toggle="buttons-radio">
|
|
||||||
<a href="{% url 'assignment_set_phase' assignment.id assignment.PHASE_SEARCH %}"
|
|
||||||
class="btn btn-default btn-sm {% if assignment.phase == assignment.PHASE_SEARCH %}active{% endif %}">
|
|
||||||
{% trans 'Searching for candidates' %}</a>
|
|
||||||
<a href="{% url 'assignment_set_phase' assignment.id assignment.PHASE_VOTING %}"
|
|
||||||
class="btn btn-default btn-sm {% if assignment.phase == assignment.PHASE_VOTING %}active{% endif %}">
|
|
||||||
{% trans 'Voting' %}</a>
|
|
||||||
<a href="{% url 'assignment_set_phase' assignment.id assignment.PHASE_FINISHED %}"
|
|
||||||
class="btn btn-default btn-sm {% if assignment.phase == assignment.PHASE_FINISHED %}active{% endif %}">
|
|
||||||
{% trans 'Finished' %}</a>
|
|
||||||
</div>
|
|
||||||
</div> <!--/well-->
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div> <!--/row-->
|
|
||||||
{% endblock %}
|
|
@ -1,112 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load humanize %}
|
|
||||||
{% load tags %}
|
|
||||||
|
|
||||||
{% block title %}{% trans "Election" %} "{{ assignment }}", {{ ballotnumber }}. {% trans "ballot" %} – {{ block.super }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>
|
|
||||||
{{ assignment }}
|
|
||||||
<small>
|
|
||||||
{{ ballotnumber|ordinal|safe }} {% trans "ballot" %}
|
|
||||||
</small>
|
|
||||||
<small class="pull-right">
|
|
||||||
<a href="{{ assignment|absolute_url:'detail' }}" class="btn btn-mini"><i class="icon-chevron-left"></i><span class="optional-small"> {% trans "Back to election" %}</span></a>
|
|
||||||
<!-- activate projector -->
|
|
||||||
{% if perms.core.can_manage_projector %}
|
|
||||||
<a href="{{ assignment|absolute_url:'projector' }}"
|
|
||||||
class="activate_link btn {% if assignment.is_active_slide %}btn-primary{% endif %} btn-mini"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Show election' %}">
|
|
||||||
<i class="icon-facetime-video {% if assignment.is_active_slide %}icon-white{% endif %}"></i>
|
|
||||||
</a>
|
|
||||||
<a class="btn btn-mini activate_link {% if poll.is_active_slide %}btn-primary{% endif %}" href="{{ poll|absolute_url:'projector' }}"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Show election result' %}">
|
|
||||||
<i class="icon-facetime-video {% if poll.is_active_slide %}icon-white{% endif %}"></i> {% trans "Election result" %}</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.motions.can_manage %}
|
|
||||||
<a class="btn btn-mini" href="{{ poll|absolute_url:'delete' }}"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Delete ballot' %}"><i class="icon-remove"></i></a>
|
|
||||||
{% endif %}
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<form action="" method="post" class="small-form">{% csrf_token %}
|
|
||||||
<p>
|
|
||||||
{% trans "Special values" %}:
|
|
||||||
<span class="badge badge-success">-1</span> = {% trans 'majority' %}|
|
|
||||||
<span class="badge">-2</span> = {% trans 'undocumented' %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<table class="table table-striped table-bordered" style="width: auto;">
|
|
||||||
<tr>
|
|
||||||
<th>{% trans "Candidates" %}</th>
|
|
||||||
{% for value in poll.get_vote_values %}
|
|
||||||
<th>{% trans value %}</th>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
{% for form in forms %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ form.option }}</td>
|
|
||||||
{% for value in form %}
|
|
||||||
<td>
|
|
||||||
{{ value.errors }}
|
|
||||||
{{ value }}
|
|
||||||
</td>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
<tr class="total warning">
|
|
||||||
<td>{% trans "Valid votes" %}</td>
|
|
||||||
{% for value in poll.get_vote_values %}
|
|
||||||
{% if forloop.first %}
|
|
||||||
<td>{{ pollform.votesvalid.errors }}{{ pollform.votesvalid }}</td>
|
|
||||||
{% else %}
|
|
||||||
<td></td>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{% trans "Invalid votes" %}</td>
|
|
||||||
{% for value in poll.get_vote_values %}
|
|
||||||
{% if forloop.first %}
|
|
||||||
<td>{{ pollform.votesinvalid.errors }}{{ pollform.votesinvalid }}</td>
|
|
||||||
{% else %}
|
|
||||||
<td></td>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
<tr class="total warning">
|
|
||||||
<td>{% trans "Votes cast" %}</td>
|
|
||||||
{% for value in poll.get_vote_values %}
|
|
||||||
{% if forloop.first %}
|
|
||||||
<td>{{ pollform.votescast.errors }}{{ pollform.votescast }}</td>
|
|
||||||
{% else %}
|
|
||||||
<td></td>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<p><strong>{% trans "Short description (for ballot paper)" %}:</strong></p>
|
|
||||||
<p class="normal-form">{{ pollform.description }}</p>
|
|
||||||
<p>
|
|
||||||
<a href="{% url 'assignmentpoll_pdf' poll.id %}" class="btn" target="_blank">
|
|
||||||
<i class="icon-print"></i> {% trans 'Ballot paper as PDF' %}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<!-- Control buttons -->
|
|
||||||
<div class="control-group">
|
|
||||||
<button type="submit" class="btn btn-primary">
|
|
||||||
{% trans 'Save' %}
|
|
||||||
</button>
|
|
||||||
<button type="submit" name="apply" class="btn">
|
|
||||||
{% trans 'Apply' %}
|
|
||||||
</button>
|
|
||||||
<a href="{{ poll.assignment|absolute_url:'detail' }}" class="btn">
|
|
||||||
{% trans 'Cancel' %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
@ -1,59 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
{% load humanize %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
{% load tags %}
|
|
||||||
|
|
||||||
<h1>
|
|
||||||
{{ poll.assignment }}
|
|
||||||
<br>
|
|
||||||
<small>
|
|
||||||
{% trans "Election" %}
|
|
||||||
{% if poll.get_ballot > 1 %}| {{ poll.get_ballot|ordinal|safe }} {% trans "ballot" %}{% endif %}
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
{% if poll.has_votes and poll.published %}
|
|
||||||
<table class="result big nobr">
|
|
||||||
{% for option in poll.get_options %}
|
|
||||||
<tr {% if option.candidate in poll.assignment.elected %}class="elected"{% endif %}>
|
|
||||||
<td>{{ option }}</td>
|
|
||||||
<td class="bold">
|
|
||||||
{% if not "assignment_publish_winner_results_only"|get_config or option.candidate in poll.assignment.elected %}
|
|
||||||
{% if poll.yesnoabstain %}
|
|
||||||
<img src="{% static 'img/voting-yes.png' %}"> {% trans 'Yes' %}:
|
|
||||||
{{ option.Yes }}<br>
|
|
||||||
<img src="{% static 'img/voting-no.png' %}"> {% trans 'No' %}:
|
|
||||||
{{ option.No }}<br>
|
|
||||||
<img src="{% static 'img/voting-abstention.png' %}"> {% trans 'Abstention' %}:
|
|
||||||
{{ option.Abstain }}
|
|
||||||
{% else %}
|
|
||||||
{{ option.Votes }}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
{% if poll.votesvalid != None %}
|
|
||||||
<tr class="total">
|
|
||||||
<td><img src="{% static 'img/voting-yes-grey.png' %}"> {% trans 'Valid votes' %}:</td>
|
|
||||||
<td class="bold">{{ poll.print_votesvalid }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% if poll.votesinvalid != None %}
|
|
||||||
<tr class="total">
|
|
||||||
<td><img src="{% static 'img/voting-invalid.png' %}"> {% trans 'Invalid votes' %}:</td>
|
|
||||||
<td class="bold">{{ poll.print_votesinvalid }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% if poll.votescast != None %}
|
|
||||||
<tr class="total">
|
|
||||||
<td><img src="{% static 'img/voting-total.png' %}"> {% trans 'Votes cast' %}:</td>
|
|
||||||
<td class="bold">{{ poll.print_votescast }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
</table>
|
|
||||||
{% else %}
|
|
||||||
<i>{% trans "No result available." %}</i>
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
@ -1,53 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
{% load tags %}
|
|
||||||
{% load humanize %}
|
|
||||||
|
|
||||||
<div id="sidebar">
|
|
||||||
<div class="well">
|
|
||||||
<!-- Status -->
|
|
||||||
<h4 class="first">{% trans "Status" %}:</h4>
|
|
||||||
{% trans assignment.get_status_display %}</p>
|
|
||||||
<h4>{% trans "Number of available posts" %}:</h4>
|
|
||||||
{{ assignment.posts }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1>{{ assignment }}
|
|
||||||
<small>
|
|
||||||
{% trans "Election" %}
|
|
||||||
</small>
|
|
||||||
</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 assignment.status == "fin" %}
|
|
||||||
<h3>{% trans "Elected candidates" %}</h3>
|
|
||||||
<ol>
|
|
||||||
{% for person in assignment.elected %}
|
|
||||||
<li>{{ person }}</li>
|
|
||||||
{% empty %}
|
|
||||||
<li style="list-style: none outside none;">
|
|
||||||
<i>{% trans "No candidates elected." %}</i>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ol>
|
|
||||||
{% endif %}
|
|
@ -4,8 +4,6 @@ from . import views
|
|||||||
|
|
||||||
urlpatterns = patterns(
|
urlpatterns = patterns(
|
||||||
'',
|
'',
|
||||||
|
|
||||||
# PDF
|
|
||||||
url(r'^print/$',
|
url(r'^print/$',
|
||||||
views.AssignmentPDF.as_view(),
|
views.AssignmentPDF.as_view(),
|
||||||
name='assignments_pdf'),
|
name='assignments_pdf'),
|
||||||
|
@ -5,8 +5,15 @@ from django.utils.translation import ugettext as _
|
|||||||
from django.utils.translation import ungettext
|
from django.utils.translation import ungettext
|
||||||
from reportlab.lib import colors
|
from reportlab.lib import colors
|
||||||
from reportlab.lib.units import cm
|
from reportlab.lib.units import cm
|
||||||
from reportlab.platypus import (PageBreak, Paragraph, SimpleDocTemplate, Spacer,
|
from reportlab.platypus import (
|
||||||
LongTable, Table, TableStyle)
|
LongTable,
|
||||||
|
PageBreak,
|
||||||
|
Paragraph,
|
||||||
|
SimpleDocTemplate,
|
||||||
|
Spacer,
|
||||||
|
Table,
|
||||||
|
TableStyle,
|
||||||
|
)
|
||||||
|
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.users.models import Group, User # TODO: remove this
|
from openslides.users.models import Group, User # TODO: remove this
|
||||||
@ -18,14 +25,15 @@ from openslides.utils.rest_api import (
|
|||||||
Response,
|
Response,
|
||||||
UpdateModelMixin,
|
UpdateModelMixin,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
detail_route)
|
detail_route,
|
||||||
|
)
|
||||||
from openslides.utils.views import PDFView
|
from openslides.utils.views import PDFView
|
||||||
|
|
||||||
from .models import Assignment, AssignmentPoll
|
from .models import Assignment, AssignmentPoll
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
AssignmentAllPollSerializer,
|
AssignmentAllPollSerializer,
|
||||||
AssignmentFullSerializer,
|
AssignmentFullSerializer,
|
||||||
AssignmentShortSerializer
|
AssignmentShortSerializer,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
from openslides.utils.widgets import Widget
|
|
||||||
|
|
||||||
from .models import Assignment
|
|
||||||
|
|
||||||
|
|
||||||
class AssignmentWidget(Widget):
|
|
||||||
"""
|
|
||||||
Assignment widget.
|
|
||||||
"""
|
|
||||||
name = 'assignment'
|
|
||||||
verbose_name = ugettext_lazy('Elections')
|
|
||||||
required_permission = 'core.can_manage_projector'
|
|
||||||
default_column = 1
|
|
||||||
default_weight = 50
|
|
||||||
template_name = 'assignments/widget_assignment.html'
|
|
||||||
more_link_pattern_name = 'assignment_list'
|
|
||||||
|
|
||||||
def get_context_data(self, **context):
|
|
||||||
return super(AssignmentWidget, self).get_context_data(
|
|
||||||
assignments=Assignment.objects.all(),
|
|
||||||
**context)
|
|
@ -6,10 +6,6 @@ class ConfigAppConfig(AppConfig):
|
|||||||
verbose_name = 'OpenSlides Config'
|
verbose_name = 'OpenSlides Config'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
# Load main menu entry.
|
|
||||||
# Do this by just importing all from this file.
|
|
||||||
from . import main_menu # noqa
|
|
||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from openslides.utils.rest_api import router
|
from openslides.utils.rest_api import router
|
||||||
from .views import ConfigViewSet
|
from .views import ConfigViewSet
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
from openslides.utils.main_menu import MainMenuEntry
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigMainMenuEntry(MainMenuEntry):
|
|
||||||
"""
|
|
||||||
Main menu entry for the config app.
|
|
||||||
"""
|
|
||||||
verbose_name = ugettext_lazy('Configuration')
|
|
||||||
required_permission = 'config.can_manage'
|
|
||||||
default_weight = 70
|
|
||||||
pattern_name = 'config_first_config_collection_view'
|
|
||||||
icon_css_class = 'glyphicon-cog'
|
|
@ -1,6 +1,5 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_noop
|
from django.utils.translation import ugettext_noop
|
||||||
|
|
||||||
from jsonfield import JSONField
|
from jsonfield import JSONField
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{% trans "Configuration" %} – {{ block.super }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>
|
|
||||||
{% trans 'Configuration' %}
|
|
||||||
<small>{% trans active_config_collection_view.title %}</small>
|
|
||||||
<small class="pull-right">
|
|
||||||
<div class="btn-toolbar">
|
|
||||||
<div class="btn-group">
|
|
||||||
{% for config_collection_dict in config_collection_list %}
|
|
||||||
<a href="/config/{{ config_collection_dict.config_collection.url }}/" class="btn btn-mini {% if config_collection_dict.active %}active{% endif %}">
|
|
||||||
{% trans config_collection_dict.config_collection.title %}
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
<form action="" method="post">{% csrf_token %}
|
|
||||||
{% for group in groups %}
|
|
||||||
<fieldset>
|
|
||||||
<legend>{{ group.title }}</legend>
|
|
||||||
{% for field in form %}
|
|
||||||
{% if field.name in group.get_field_names %}
|
|
||||||
<div class="control-group {% if field.errors %}error{% endif%}">
|
|
||||||
<label for="id_{{ field.name }}">{{ field.label }}{% if field.field.required %}<span class="required">*</span>{% endif %}:</label>
|
|
||||||
{{ field }}
|
|
||||||
{% if field.errors %}
|
|
||||||
<span class="help-inline">{{ field.errors }}</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if field.help_text %}
|
|
||||||
<span class="help-inline">{{ field.help_text }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</fieldset>
|
|
||||||
{% empty %}
|
|
||||||
{% include 'form.html' %}
|
|
||||||
{% endfor %}
|
|
||||||
<p>
|
|
||||||
{% include 'formbuttons_save.html' %}
|
|
||||||
<a href="/config/{{ active_config_collection_view.url }}/" class="btn">{% trans 'Cancel' %}</a>
|
|
||||||
</p>
|
|
||||||
<small>* {% trans 'required' %}</small>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
@ -1,19 +0,0 @@
|
|||||||
from django.conf.urls import patterns, url
|
|
||||||
|
|
||||||
from openslides.utils.views import RedirectView
|
|
||||||
|
|
||||||
from .signals import config_signal
|
|
||||||
from .views import ConfigView
|
|
||||||
|
|
||||||
urlpatterns = patterns(
|
|
||||||
'',
|
|
||||||
url(r'^$',
|
|
||||||
RedirectView.as_view(url_name='config_general'),
|
|
||||||
name='config_first_config_collection_view')
|
|
||||||
)
|
|
||||||
|
|
||||||
for receiver, config_collection in config_signal.send(sender='config_urls'):
|
|
||||||
if config_collection.is_shown():
|
|
||||||
urlpatterns += patterns('', url(r'^%s/$' % config_collection.url,
|
|
||||||
ConfigView.as_view(config_collection=config_collection),
|
|
||||||
name='config_%s' % config_collection.url))
|
|
@ -1,109 +1,10 @@
|
|||||||
from django import forms
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
|
|
||||||
from openslides.utils.rest_api import Response, ValidationError, ViewSet
|
from openslides.utils.rest_api import Response, ValidationError, ViewSet
|
||||||
from openslides.utils.views import FormView
|
|
||||||
|
|
||||||
from .api import config
|
from .api import config
|
||||||
from .exceptions import ConfigNotFound
|
from .exceptions import ConfigNotFound
|
||||||
from .signals import config_signal
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigView(FormView):
|
|
||||||
"""
|
|
||||||
The view for a config collection.
|
|
||||||
"""
|
|
||||||
required_permission = 'config.can_manage'
|
|
||||||
template_name = 'config/config_form.html'
|
|
||||||
config_collection = None
|
|
||||||
form_class = forms.Form
|
|
||||||
|
|
||||||
def get_form(self, *args):
|
|
||||||
"""
|
|
||||||
Gets the form for the view. Includes all form fields given by the
|
|
||||||
config collection.
|
|
||||||
"""
|
|
||||||
form = super(ConfigView, self).get_form(*args)
|
|
||||||
for name, field in self.generate_form_fields_from_config_collection():
|
|
||||||
form.fields[name] = field
|
|
||||||
return form
|
|
||||||
|
|
||||||
def generate_form_fields_from_config_collection(self):
|
|
||||||
"""
|
|
||||||
Generates the fields for the get_form function.
|
|
||||||
"""
|
|
||||||
for variable in self.config_collection.variables:
|
|
||||||
if variable.form_field is not None:
|
|
||||||
yield (variable.name, variable.form_field)
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
"""
|
|
||||||
Returns a dictonary with the actual values of the config variables
|
|
||||||
as intial value for the form.
|
|
||||||
"""
|
|
||||||
initial = super(ConfigView, self).get_initial()
|
|
||||||
for variable in self.config_collection.variables:
|
|
||||||
initial.update({variable.name: config[variable.name]})
|
|
||||||
return initial
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
"""
|
|
||||||
Adds to the context the active config view, a list of dictionaries
|
|
||||||
containing all config collections each with a flag which is true if its
|
|
||||||
view is the active one and adds a flag whether the config collection
|
|
||||||
has groups. Adds also extra_stylefiles and extra_javascript.
|
|
||||||
"""
|
|
||||||
context = super(ConfigView, self).get_context_data(**kwargs)
|
|
||||||
|
|
||||||
context['active_config_collection_view'] = self.config_collection
|
|
||||||
|
|
||||||
config_collection_list = []
|
|
||||||
for receiver, config_collection in config_signal.send(sender=self):
|
|
||||||
if config_collection.is_shown():
|
|
||||||
config_collection_list.append({
|
|
||||||
'config_collection': config_collection,
|
|
||||||
'active': self.request.path == reverse('config_%s' % config_collection.url)})
|
|
||||||
context['config_collection_list'] = sorted(
|
|
||||||
config_collection_list, key=lambda config_collection_dict: config_collection_dict['config_collection'].weight)
|
|
||||||
|
|
||||||
if hasattr(self.config_collection, 'groups'):
|
|
||||||
context['groups'] = self.config_collection.groups
|
|
||||||
else:
|
|
||||||
context['groups'] = None
|
|
||||||
|
|
||||||
if 'extra_stylefiles' in self.config_collection.extra_context:
|
|
||||||
if 'extra_stylefiles' in context:
|
|
||||||
context['extra_stylefiles'].extend(self.config_collection.extra_context['extra_stylefiles'])
|
|
||||||
else:
|
|
||||||
context['extra_stylefiles'] = self.config_collection.extra_context['extra_stylefiles']
|
|
||||||
|
|
||||||
if 'extra_javascript' in self.config_collection.extra_context:
|
|
||||||
if 'extra_javascript' in context:
|
|
||||||
context['extra_javascript'].extend(self.config_collection.extra_context['extra_javascript'])
|
|
||||||
else:
|
|
||||||
context['extra_javascript'] = self.config_collection.extra_context['extra_javascript']
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
"""
|
|
||||||
Returns the success url when changes are saved. Here it is the same
|
|
||||||
url as the main menu entry.
|
|
||||||
"""
|
|
||||||
return reverse('config_%s' % self.config_collection.url)
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
"""
|
|
||||||
Saves all data of a valid form.
|
|
||||||
"""
|
|
||||||
for key in form.cleaned_data:
|
|
||||||
config[key] = form.cleaned_data[key]
|
|
||||||
messages.success(self.request, _('%s settings successfully saved.') % _(self.config_collection.title))
|
|
||||||
return super(ConfigView, self).form_valid(form)
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigViewSet(ViewSet):
|
class ConfigViewSet(ViewSet):
|
||||||
|
@ -6,9 +6,9 @@ class CoreAppConfig(AppConfig):
|
|||||||
verbose_name = 'OpenSlides Core'
|
verbose_name = 'OpenSlides Core'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
# Load main menu entry, projector elements and widgets.
|
# Load projector elements.
|
||||||
# Do this by just importing all from these files.
|
# Do this by just importing all from these files.
|
||||||
from . import main_menu, projector, widgets # noqa
|
from . import projector # noqa
|
||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
|
@ -3,7 +3,6 @@ from datetime import datetime
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.html import urlize
|
from django.utils.html import urlize
|
||||||
from django.utils.importlib import import_module
|
from django.utils.importlib import import_module
|
||||||
|
|
||||||
from sockjs.tornado import SockJSConnection
|
from sockjs.tornado import SockJSConnection
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
from django import forms
|
|
||||||
|
|
||||||
from openslides.utils.forms import CssClassMixin
|
|
||||||
|
|
||||||
|
|
||||||
class SelectWidgetsForm(CssClassMixin, forms.Form):
|
|
||||||
"""
|
|
||||||
Form to select the widgets.
|
|
||||||
"""
|
|
||||||
widget = forms.BooleanField(required=False)
|
|
@ -1,14 +0,0 @@
|
|||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
from openslides.utils.main_menu import MainMenuEntry
|
|
||||||
|
|
||||||
|
|
||||||
class DashboardMainMenuEntry(MainMenuEntry):
|
|
||||||
"""
|
|
||||||
Main menu entry to the dashboard.
|
|
||||||
"""
|
|
||||||
verbose_name = ugettext_lazy('Dashboard')
|
|
||||||
required_permission = 'core.can_see_dashboard'
|
|
||||||
default_weight = 10
|
|
||||||
icon_css_class = 'glyphicon-home'
|
|
||||||
pattern_name = '/' # TODO: use generic solution, see issue #1469
|
|
@ -1,7 +1,7 @@
|
|||||||
from optparse import make_option # TODO: Use argpase in Django 1.8
|
|
||||||
import shutil
|
import shutil
|
||||||
|
from optparse import make_option # TODO: Use argpase in Django 1.8
|
||||||
|
|
||||||
from django.core.management.base import NoArgsCommand, CommandError
|
from django.core.management.base import CommandError, NoArgsCommand
|
||||||
from django.db import connection, transaction
|
from django.db import connection, transaction
|
||||||
|
|
||||||
from openslides.utils.main import get_database_path_from_settings
|
from openslides.utils.main import get_database_path_from_settings
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
from datetime import datetime
|
|
||||||
import sys
|
|
||||||
import socket
|
|
||||||
import errno
|
import errno
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from django.core.management.commands.runserver import Command as _Command
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.core.management.commands.runserver import Command as _Command
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class Migration(migrations.Migration):
|
|||||||
options={
|
options={
|
||||||
'ordering': ('weight', 'title'),
|
'ordering': ('weight', 'title'),
|
||||||
},
|
},
|
||||||
bases=(openslides.utils.rest_api.RESTModelMixin, openslides.utils.models.AbsoluteUrlMixin, models.Model),
|
bases=(openslides.utils.rest_api.RESTModelMixin, models.Model),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Projector',
|
name='Projector',
|
||||||
@ -67,7 +67,7 @@ class Migration(migrations.Migration):
|
|||||||
'permissions': (('can_manage_tags', 'Can manage tags'),),
|
'permissions': (('can_manage_tags', 'Can manage tags'),),
|
||||||
'ordering': ('name',),
|
'ordering': ('name',),
|
||||||
},
|
},
|
||||||
bases=(openslides.utils.rest_api.RESTModelMixin, openslides.utils.models.AbsoluteUrlMixin, models.Model),
|
bases=(openslides.utils.rest_api.RESTModelMixin, models.Model),
|
||||||
),
|
),
|
||||||
migrations.RunPython(
|
migrations.RunPython(
|
||||||
add_default_projector,
|
add_default_projector,
|
||||||
|
@ -3,7 +3,6 @@ from django.utils.translation import ugettext as _
|
|||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||||
from jsonfield import JSONField
|
from jsonfield import JSONField
|
||||||
|
|
||||||
from openslides.utils.models import AbsoluteUrlMixin
|
|
||||||
from openslides.utils.projector import ProjectorElement
|
from openslides.utils.projector import ProjectorElement
|
||||||
from openslides.utils.rest_api import RESTModelMixin
|
from openslides.utils.rest_api import RESTModelMixin
|
||||||
|
|
||||||
@ -83,7 +82,7 @@ class Projector(RESTModelMixin, models.Model):
|
|||||||
yield requirement
|
yield requirement
|
||||||
|
|
||||||
|
|
||||||
class CustomSlide(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
class CustomSlide(RESTModelMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
Model for slides with custom content.
|
Model for slides with custom content.
|
||||||
"""
|
"""
|
||||||
@ -104,7 +103,7 @@ class CustomSlide(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
class Tag(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
class Tag(RESTModelMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
Model for tags. This tags can be used for other models like agenda items,
|
Model for tags. This tags can be used for other models like agenda items,
|
||||||
motions or assignments.
|
motions or assignments.
|
||||||
|
@ -3,7 +3,11 @@ from django.dispatch import Signal
|
|||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||||
|
|
||||||
from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
|
from openslides.config.api import (
|
||||||
|
ConfigGroup,
|
||||||
|
ConfigGroupedCollection,
|
||||||
|
ConfigVariable,
|
||||||
|
)
|
||||||
|
|
||||||
# This signal is sent when the migrate command is done. That means it is sent
|
# This signal is sent when the migrate command is done. That means it is sent
|
||||||
# after post_migrate sending and creating all Permission objects. Don't use it
|
# after post_migrate sending and creating all Permission objects. Don't use it
|
||||||
|
@ -1,163 +0,0 @@
|
|||||||
{% load tags %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="{{ LANGUAGE_CODE }}">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>{% block title %}{% get_config 'event_name' %}{% endblock %}</title>
|
|
||||||
<!-- styles -->
|
|
||||||
<link href="{% static 'css/openslides-libs.css' %}" type="text/css" rel="stylesheet" />
|
|
||||||
<link href="{% static 'css/jquery-ui/jquery-ui.custom.min.css'%}" type="text/css" rel="stylesheet" />
|
|
||||||
<link href="{% static 'css/base.css' %}" type="text/css" rel="stylesheet" />
|
|
||||||
<link href="{% static 'css/chatbox.css' %}" type="text/css" rel="stylesheet" />
|
|
||||||
<link href="{% static 'img/favicon.png' %}" type="image/png" rel="shortcut icon" />
|
|
||||||
<link href="{% static 'css/jquery.bsmselect.css' %}" type="text/css" rel="stylesheet" />
|
|
||||||
{% for stylefile in extra_stylefiles %}
|
|
||||||
<link href="{% static stylefile %}" type="text/css" rel="stylesheet" />
|
|
||||||
{% endfor %}
|
|
||||||
{% block header %}{% endblock %}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<!-- Navbar -->
|
|
||||||
<nav id="header" class="navbar">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="navbar-header">
|
|
||||||
<a href="/" class="logo" title="{% trans 'Home' %}"><img src="{% static 'img/logo.png' %}" alt="{% trans 'Logo' %}" /></a>
|
|
||||||
<span class="title optional">{% get_config 'event_name' %}</span>
|
|
||||||
</div>
|
|
||||||
{% block loginbutton %}
|
|
||||||
<div class="navbar-right">
|
|
||||||
<!-- Chatbox button -->
|
|
||||||
{% if chat_messages != None %}
|
|
||||||
<button class="btn btn-default" id="chatboxbutton" data-toggle="button">
|
|
||||||
<span class="glyphicon glyphicon-comment" aria-hidden="true"></span>
|
|
||||||
<span class="optional-small">{% trans 'Chat' %}</span>
|
|
||||||
<span id="newchatmessage"></span>
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- login/logout button -->
|
|
||||||
<div class="btn-group">
|
|
||||||
{% if user.is_authenticated %}
|
|
||||||
<a href="#" data-toggle="dropdown" class="btn btn-default dropdown-toggle">
|
|
||||||
<span class="glyphicon glyphicon-user" aria-hidden="true"></span>
|
|
||||||
<span class="optional-small">{{ user.username }}</span>
|
|
||||||
<span class="caret"></span>
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu pull-right">
|
|
||||||
<li><a href="{% url 'user_settings' %}">
|
|
||||||
<span class="glyphicon glyphicon-cog" aria-hidden="true"></span>
|
|
||||||
{% trans "Edit profile" %}</a></li>
|
|
||||||
<li><a href="{% url 'password_change' %}">
|
|
||||||
<span class="glyphicon glyphicon-lock" aria-hidden="true"></span>
|
|
||||||
{% trans "Change password" %}</a></li>
|
|
||||||
<li class="divider"></li>
|
|
||||||
<li><a href="{% url 'user_logout' %}">
|
|
||||||
<span class="glyphicon glyphicon-log-out" aria-hidden="true"></span>
|
|
||||||
{% trans "Logout" %}</a></li>
|
|
||||||
</ul>
|
|
||||||
{% else %}
|
|
||||||
<a href="" class="btn btn-default">
|
|
||||||
<span class="glyphicon glyphicon-log-in" aria-hidden="true"></span>
|
|
||||||
{% trans "Login" %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Search field -->
|
|
||||||
<form id="searchform" class="navbar-form navbar-right" action="{% url 'core_search' %}" method="get">
|
|
||||||
<div class="form-group input-group">
|
|
||||||
<input type="text" id="id_q" name="q" class="form-control" placeholder="{% trans 'Search' %}">
|
|
||||||
<span class="input-group-btn">
|
|
||||||
<button type="submit" class="btn btn-default">
|
|
||||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
<!-- Container -->
|
|
||||||
<div class="container-fluid" id="container">
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<!-- Sidebar navigation (main menu)
|
|
||||||
TODO: this menu is a temporary solution only! We will use angular base template
|
|
||||||
for building main menu, see issue #1469 -->
|
|
||||||
<div class="col-md-2 leftmenu lefticon">
|
|
||||||
<ul>
|
|
||||||
{% for entry in main_menu_entries %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ entry.pattern_name }}" class="tooltip-right">
|
|
||||||
<span class="glyphicon {{ entry.get_icon_css_class }}" aria-hidden="true"></span>
|
|
||||||
<span class="text">{{ entry }}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Content -->
|
|
||||||
<div id="content" class="col-md-10">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div id="notifications">
|
|
||||||
<div id="dummy-notification" class="notification" style="display:none">
|
|
||||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
|
||||||
</div>
|
|
||||||
{% for message in messages %}
|
|
||||||
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}">
|
|
||||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
|
||||||
{{ message|safe }}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<footer>
|
|
||||||
<small>
|
|
||||||
© Copyright 2011–2015 | Powered by <a href="http://openslides.org" target="_blank">OpenSlides</a> | <a href="{% url 'core_version' %}">Version</a>
|
|
||||||
</small>
|
|
||||||
</footer>
|
|
||||||
</div><!--/#content-->
|
|
||||||
|
|
||||||
</div><!--/.row-->
|
|
||||||
</div><!--/#container-->
|
|
||||||
{% endblock %}<!--/body-->
|
|
||||||
|
|
||||||
{% include 'core/chatbox.html' %}
|
|
||||||
|
|
||||||
<!-- JavaScript (Placed at the end of the document so the pages load faster) -->
|
|
||||||
<script src="{% static 'js/openslides-libs.js' %}" type="text/javascript"></script>
|
|
||||||
<script src="{% static 'js/jquery/jquery-ui.custom.min.js' %}" type="text/javascript"></script>
|
|
||||||
<script src="{% static 'js/sockjs-0.3.min.js' %}" type="text/javascript"></script>
|
|
||||||
<script src="{% static 'js/utils.js' %}" type="text/javascript"></script>
|
|
||||||
<script src="{% static 'js/chatbox.js' %}" type="text/javascript"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'js/jquery/jquery.bsmselect.js' %}"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
// use jquery-bsmselect for all <select multiple> form elements
|
|
||||||
$("select[multiple]:not(.dont_use_bsmselect)").bsmSelect({
|
|
||||||
removeLabel: '<sup><b>X</b></sup>',
|
|
||||||
containerClass: 'bsmContainer',
|
|
||||||
listClass: 'bsmList-custom',
|
|
||||||
listItemClass: 'bsmListItem-custom',
|
|
||||||
listItemLabelClass: 'bsmListItemLabel-custom',
|
|
||||||
removeClass: 'bsmListItemRemove-custom'
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% for javascript in extra_javascript %}
|
|
||||||
<script src="{% static javascript %}" type="text/javascript"></script>
|
|
||||||
{% endfor %}
|
|
||||||
{% block javascript %}{% endblock %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,22 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% if chat_messages != None %}
|
|
||||||
<div class="hidden well" id="chatbox">
|
|
||||||
<div id="chatbox-header">
|
|
||||||
<h1 id="chatbox-title"><i class="icon-comments icon-white"></i> Chat
|
|
||||||
<button class="btn btn-mini right" id="close-chatbox"><i class="icon-remove"></i></button>
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<div id="chatbox-text">
|
|
||||||
{% for message in chat_messages %}
|
|
||||||
<br>{{ message.html_time|safe }} {{ message.html_person|safe }} {{ message.message|urlize }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<form class="form-inline" id="chatbox-form">
|
|
||||||
<div class="input-append" style="width: calc(100% - 25px);">
|
|
||||||
<input id="chatbox-form-input" type="text" />
|
|
||||||
<button type="submit" class="btn"><i class="icon-comments"></i></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
@ -1,9 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<h1 {% if not slide.text %}class="title_only"{% endif %}>
|
|
||||||
{{ slide.title }}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
{% if slide.text %}
|
|
||||||
<span>{{ slide.text|safe|linebreaks }}</span>
|
|
||||||
{% endif %}
|
|
@ -1,20 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{% trans "Custom slide" %} – {{ block.super }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{% trans 'Custom slide' %}
|
|
||||||
<small class="pull-right">
|
|
||||||
<a href="{% url 'core_dashboard' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
<form action="" method="post">{% csrf_token %}
|
|
||||||
{% include "form.html" %}
|
|
||||||
<p>
|
|
||||||
{% include "formbuttons_saveapply.html" %}
|
|
||||||
</p>
|
|
||||||
<small>* {% trans "required" %}</small>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
@ -1,42 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
{% block header %}
|
|
||||||
<link type="text/css" rel="stylesheet" media="all" href="{% static 'css/dashboard.css' %}" />
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block javascript %}
|
|
||||||
<script type="text/javascript" src="{% static 'js/jquery/jquery.form.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'js/dashboard.js' %}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block title %}{% trans 'Dashboard' %} – {{ block.super }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{% trans 'Dashboard' %}
|
|
||||||
<small class="pull-right">
|
|
||||||
<a href="{% url 'core_select_widgets' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Manage widgets' %}">
|
|
||||||
<i class="icon-th-large"></i>
|
|
||||||
{% trans 'Widgets' %}
|
|
||||||
</a>
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
<div class="row-fluid">
|
|
||||||
<div class="span6 column" id="col1">
|
|
||||||
{% for widget in widgets %}
|
|
||||||
{% if widget.default_column == 1 %}
|
|
||||||
{{ widget.get_html }}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<div class="span6 column" id="col2">
|
|
||||||
{% for widget in widgets %}
|
|
||||||
{% if widget.default_column == 2 %}
|
|
||||||
{{ widget.get_html }}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -1,8 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block title %}{{ http_error.status_code }} {{ http_error.name }} – {{ block.super }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{{ http_error.name }}</h1>
|
|
||||||
<p>{{ http_error.description }}</p>
|
|
||||||
{% endblock %}
|
|
@ -1,58 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{{ block.super }} – {% trans "Search" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h2>{% trans 'Search results' %}</h2>
|
|
||||||
<form class="form-search well" action="" method="get">
|
|
||||||
{% for field in form %}
|
|
||||||
<div class="control-group">
|
|
||||||
{% if field.name == "q" %}
|
|
||||||
<label for="id_q">{% trans 'Search' %}:</label>
|
|
||||||
<div class="input-append">
|
|
||||||
<input type="text" id="id_q" name="q" value="{{ field.value }}">
|
|
||||||
<button type="submit" class="btn">{% trans "Search" %}</button>
|
|
||||||
</div>
|
|
||||||
{% elif field.name == "models" %}
|
|
||||||
{% trans 'Filter' %}:
|
|
||||||
<div class="control-group">
|
|
||||||
{% for model in models %}
|
|
||||||
<label class="checkbox inline">
|
|
||||||
<input type="checkbox" value="{{ model.1 }}" name="models"
|
|
||||||
{% if model.1 in get_values %}checked{% endif %}> {% trans model.0 %}
|
|
||||||
</label>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{% if query %}
|
|
||||||
{% for result in page.object_list %}
|
|
||||||
{% if forloop.first %}
|
|
||||||
<ol class="searchresults">
|
|
||||||
{% endif %}
|
|
||||||
{% if result.app_label %}
|
|
||||||
{% with result_template=result.app_label|add:"-results.html" %}
|
|
||||||
{% include "search/"|add:result_template %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
|
||||||
{% if forloop.last %}
|
|
||||||
</ol>
|
|
||||||
{% endif %}
|
|
||||||
{% empty %}
|
|
||||||
<p><i>{% trans "No results found." %}</i></p>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if page.has_previous or page.has_next %}
|
|
||||||
<div>
|
|
||||||
{% if page.has_previous %}<a href="?q={{ query }}&page={{ page.previous_page_number }}">{% endif %}« Previous{% if page.has_previous %}</a>{% endif %}
|
|
||||||
|
|
|
||||||
{% if page.has_next %}<a href="?q={{ query }}&page={{ page.next_page_number }}">{% endif %}Next »{% if page.has_next %}</a>{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@ -1,32 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{% trans 'Select widgets' %} – {{ block.super }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{% trans 'Select widgets' %}
|
|
||||||
<small class="pull-right">
|
|
||||||
<a href="{% url 'core_dashboard' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans 'Back to overview' %}</a>
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<form action="" method="post">{% csrf_token %}
|
|
||||||
<ul class="unstyled">
|
|
||||||
{% for widget in widgets %}
|
|
||||||
<li>
|
|
||||||
<label class="checkbox">
|
|
||||||
{{ widget.form.widget }} {{ widget }}
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
{% empty %}
|
|
||||||
<li>{% trans 'No widgets available' %}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
<button class="btn btn-primary" type="submit">
|
|
||||||
{% trans 'Save' %}
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
@ -1,61 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
{% block title %}{% trans "Tags" %} – {{ block.super }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{% trans "Tags" %}
|
|
||||||
<small class="pull-right">
|
|
||||||
<a href="javascript:window.history.back()" class="btn btn-mini">
|
|
||||||
<i class="icon-chevron-left"></i><span class="optional-small"> {% trans "Back to overview" %}</span>
|
|
||||||
</a>
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<label for="tag-edit">{% trans 'Enter new tag name' %}:</label>
|
|
||||||
<input id="tag-edit" name="new">
|
|
||||||
<a href="#" id="tag-save" class="btn btn-primary">{% trans 'Save' %}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table id="tag-table" class="table table-striped table-bordered">
|
|
||||||
<tr>
|
|
||||||
<th>{% trans "Tag" %}</th>
|
|
||||||
<th class="mini_width">{% trans "Actions" %}</th>
|
|
||||||
</tr>
|
|
||||||
<tr id="dummy-tag" class="tag-row" style="display:none">
|
|
||||||
<td class="tag-name"></td>
|
|
||||||
<td>
|
|
||||||
<span style="width: 1px; white-space: nowrap;">
|
|
||||||
<a href="#" rel="tooltip" data-original-title="{% trans 'Edit' %}" class="btn btn-mini tag-edit">
|
|
||||||
<i class="icon-pencil "></i>
|
|
||||||
</a>
|
|
||||||
<a href="#" rel="tooltip" data-original-title="{% trans 'Delete' %}" class="btn btn-mini tag-del">
|
|
||||||
<i class="icon-remove"></i>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% for tag in tag_list %}
|
|
||||||
<tr id="tag-{{ tag.pk }}" class="tag-row">
|
|
||||||
<td class="tag-name">{{ tag }}</td>
|
|
||||||
<td>
|
|
||||||
<span style="width: 1px; white-space: nowrap;">
|
|
||||||
<a href="#" rel="tooltip" data-original-title="{% trans 'Edit' %}" class="btn btn-mini tag-edit">
|
|
||||||
<i class="icon-pencil "></i>
|
|
||||||
</a>
|
|
||||||
<a href="#" rel="tooltip" data-original-title="{% trans 'Delete' %}" class="btn btn-mini tag-del">
|
|
||||||
<i class="icon-remove"></i>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
<p>{% trans "You can use these tags for agenda items, motions and elections." %}</p>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block javascript %}
|
|
||||||
<script src="{% static 'js/config_tags.js' %}" type="text/javascript"></script>
|
|
||||||
{% endblock %}
|
|
@ -1,20 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{% trans 'Version' %} – {{ block.super }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{% trans 'Version' %}</h1>
|
|
||||||
<ul>
|
|
||||||
{% for module in modules %}
|
|
||||||
<li>
|
|
||||||
{{ module.verbose_name }}
|
|
||||||
{% if module.description %}
|
|
||||||
({{ module.description }})
|
|
||||||
{% endif %}
|
|
||||||
– {% trans 'Version' %} {{ module.version }}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
@ -1,40 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<div class="widget" id="widget_{{ widget.name }}">
|
|
||||||
<div class="widget-header">
|
|
||||||
{% block header %}
|
|
||||||
<h3>
|
|
||||||
<i class="{{ widget.get_icon_css_class }}"></i>
|
|
||||||
<div class="collapsebutton btn-group right" data-toggle="buttons-checkbox">
|
|
||||||
<button type="button" class="btn btn-mini"
|
|
||||||
data-toggle="collapse" data-target="#widgetcontent_{{ widget.name }}"
|
|
||||||
title="{% trans 'Collapse widget content' %}">
|
|
||||||
_</button>
|
|
||||||
</div>
|
|
||||||
<div class="fixbutton btn-group right" data-toggle="buttons-checkbox">
|
|
||||||
<button type="button" class="btn btn-mini custom-btn-mini"
|
|
||||||
title="{% trans 'Fix widget position' %}">
|
|
||||||
<i class="icon-pushpin"></i></button>
|
|
||||||
</div>
|
|
||||||
{{ widget.get_verbose_name }}
|
|
||||||
</h3>
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
<div class="widget-content collapse in" id="widgetcontent_{{ widget.name }}">
|
|
||||||
{% block content-wrapper %}
|
|
||||||
<div class="widget-content-inner">
|
|
||||||
{% block content %}
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
<div> <!-- widget-content-footer -->
|
|
||||||
{% block footer %}
|
|
||||||
{% if widget.get_url_for_more %}
|
|
||||||
<small>
|
|
||||||
<p class="text-right"><a href="{{ widget.get_url_for_more }}">{% trans 'More ...' %}</a></p>
|
|
||||||
</small>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,49 +0,0 @@
|
|||||||
{% extends 'core/widget.html' %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load tags %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<ul style="line-height: 180%">
|
|
||||||
<li class="{% if welcomepage_is_active %}activeline{% endif %}">
|
|
||||||
<a href="{% url 'projector_activate_slide' 'default' %}" class="activate_link btn {% if welcomepage_is_active %}btn-primary{% endif %} btn-mini"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Show' %}">
|
|
||||||
<i class="icon-facetime-video {% if welcomepage_is_active %}icon-white{% endif %}"></i>
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'projector_preview' callback='default' %}"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Preview' %}" class="btn btn-mini right">
|
|
||||||
<i class="icon-search"></i>
|
|
||||||
</a>
|
|
||||||
{{ 'welcome_title'|get_config }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<hr>
|
|
||||||
<ul style="line-height: 180%">
|
|
||||||
{% for slide in slides %}
|
|
||||||
<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"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Show' %}">
|
|
||||||
<i class="icon-facetime-video {% if slide.is_active_slide %}icon-white{% endif %}"></i>
|
|
||||||
</a>
|
|
||||||
<a href="{{ slide|absolute_url:'update' }}">{{ slide }}</a>
|
|
||||||
<a href="{{ slide|absolute_url:'delete' }}"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Delete' %}" class="btn btn-mini right">
|
|
||||||
<i class="icon-remove"></i>
|
|
||||||
</a>
|
|
||||||
<a href="{{ slide|absolute_url:'update' }}"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Edit' %}" class="btn btn-mini right">
|
|
||||||
<i class="icon-pencil"></i>
|
|
||||||
</a>
|
|
||||||
<a href="{{ slide|absolute_url:'projector_preview' }}"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Preview' %}" class="btn btn-mini right">
|
|
||||||
<i class="icon-search"></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% empty %}
|
|
||||||
<li>{% trans 'No items available.' %}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<a href="{% url 'customslide_create' %}" class="btn btn-mini right" style="margin: 10px 0;">
|
|
||||||
<i class="icon-plus"></i>{% trans 'New' %}
|
|
||||||
</a>
|
|
||||||
{% endblock %}
|
|
@ -1,12 +0,0 @@
|
|||||||
{% extends 'core/widget.html' %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load tags %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% with 'welcome_text'|get_config as welcometext %}
|
|
||||||
{% if welcometext %}
|
|
||||||
<p>{{ welcometext|safe|linebreaks }}</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock %}
|
|
@ -1,22 +0,0 @@
|
|||||||
{{ form.media }}
|
|
||||||
{% if form.non_field_errors %}
|
|
||||||
<div class="alert alert-danger" role="alert">
|
|
||||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
|
||||||
{% for msg in form.non_field_errors %}
|
|
||||||
{{ msg }}
|
|
||||||
{% if not forloop.last %}<br />{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% for field in form %}
|
|
||||||
<div class="form-group {% if field.errors %}error{% endif%}">
|
|
||||||
<label for="id_{{ field.name }}">{{ field.label }}{% if field.field.required %}<span class="required">*</span>{% endif %}:</label>
|
|
||||||
{{ field }}
|
|
||||||
{% if field.errors %}
|
|
||||||
<span class="help-inline">{{ field.errors }}</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if field.help_text %}
|
|
||||||
<span class="help-inline">{{ field.help_text }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
@ -1,5 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<button class="btn btn-primary" type="submit">
|
|
||||||
{% trans 'Save' %}
|
|
||||||
</button>
|
|
@ -1,8 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<button class="btn btn-primary" type="submit">
|
|
||||||
{% trans 'Save' %}
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-default" type="submit" name="apply">
|
|
||||||
{% trans 'Apply' %}
|
|
||||||
</button>
|
|
@ -1,53 +0,0 @@
|
|||||||
from django import template
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
|
|
||||||
from openslides.config.api import config
|
|
||||||
|
|
||||||
register = template.Library()
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: remove the tag get_config
|
|
||||||
@register.simple_tag
|
|
||||||
def get_config(key):
|
|
||||||
return config[key]
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter # noqa
|
|
||||||
def get_config(key):
|
|
||||||
return config[key]
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
|
||||||
def trans(value):
|
|
||||||
return _(value)
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
|
||||||
def absolute_url(model, link=None):
|
|
||||||
"""
|
|
||||||
Returns the absolute_url to a model. The 'link' argument decides which url
|
|
||||||
will be returned. See get_absolute_url() in the model.
|
|
||||||
|
|
||||||
Example: {{ motion|absolute_url:'delete' }}
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if link is None:
|
|
||||||
url = model.get_absolute_url()
|
|
||||||
else:
|
|
||||||
url = model.get_absolute_url(link)
|
|
||||||
except ValueError:
|
|
||||||
url = ''
|
|
||||||
return url
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
|
||||||
def first_line(text):
|
|
||||||
try:
|
|
||||||
lines = text.split('\n')
|
|
||||||
except AttributeError:
|
|
||||||
return ''
|
|
||||||
if len(lines) > 1 or len(lines[0]) > 30:
|
|
||||||
s = "%s ..."
|
|
||||||
else:
|
|
||||||
s = "%s"
|
|
||||||
return s % lines[0][:30]
|
|
@ -1,46 +1,9 @@
|
|||||||
from django.conf.urls import patterns, url
|
from django.conf.urls import patterns, url
|
||||||
|
|
||||||
from openslides.utils.views import RedirectView
|
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = patterns(
|
urlpatterns = patterns(
|
||||||
'',
|
'',
|
||||||
# Redirect to dashboard URL
|
|
||||||
url(r'^$',
|
|
||||||
RedirectView.as_view(url_name='core_dashboard'),
|
|
||||||
name='home',),
|
|
||||||
|
|
||||||
url(r'^dashboard/$',
|
|
||||||
views.DashboardView.as_view(),
|
|
||||||
name='core_dashboard'),
|
|
||||||
|
|
||||||
url(r'^dashboard/select_widgets/$',
|
|
||||||
views.SelectWidgetsView.as_view(),
|
|
||||||
name='core_select_widgets'),
|
|
||||||
|
|
||||||
url(r'^version/$',
|
|
||||||
views.VersionView.as_view(),
|
|
||||||
name='core_version',),
|
|
||||||
|
|
||||||
url(r'^search/$',
|
|
||||||
views.SearchView(),
|
|
||||||
name='core_search',),
|
|
||||||
|
|
||||||
# CustomSlide urls
|
|
||||||
url(r'^customslide/new/$',
|
|
||||||
views.CustomSlideCreateView.as_view(),
|
|
||||||
name='customslide_create'),
|
|
||||||
|
|
||||||
url(r'^customslide/(?P<pk>\d+)/edit/$',
|
|
||||||
views.CustomSlideUpdateView.as_view(),
|
|
||||||
name='customslide_update'),
|
|
||||||
|
|
||||||
url(r'^customslide/(?P<pk>\d+)/del/$',
|
|
||||||
views.CustomSlideDeleteView.as_view(),
|
|
||||||
name='customslide_delete'),
|
|
||||||
|
|
||||||
# Ajax Urls
|
|
||||||
url(r'^core/url_patterns/$',
|
url(r'^core/url_patterns/$',
|
||||||
views.UrlPatternsView.as_view(),
|
views.UrlPatternsView.as_view(),
|
||||||
name='core_url_patterns'),
|
name='core_url_patterns'),
|
||||||
|
@ -1,38 +1,18 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.contrib.staticfiles import finders
|
from django.contrib.staticfiles import finders
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.urlresolvers import get_resolver
|
||||||
from django.core.urlresolvers import get_resolver, reverse
|
|
||||||
from django.db import IntegrityError
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import redirect, render_to_response
|
|
||||||
from django.template import RequestContext
|
|
||||||
from django.utils.importlib import import_module
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from haystack.views import SearchView as _SearchView
|
|
||||||
|
|
||||||
from openslides import __version__ as openslides_version
|
|
||||||
from openslides.config.api import config
|
|
||||||
from openslides.utils import views as utils_views
|
from openslides.utils import views as utils_views
|
||||||
from openslides.utils.plugins import (
|
|
||||||
get_plugin_description,
|
|
||||||
get_plugin_verbose_name,
|
|
||||||
get_plugin_version,
|
|
||||||
)
|
|
||||||
from openslides.utils.rest_api import (
|
from openslides.utils.rest_api import (
|
||||||
ModelViewSet,
|
ModelViewSet,
|
||||||
ReadOnlyModelViewSet,
|
ReadOnlyModelViewSet,
|
||||||
Response,
|
Response,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
detail_route
|
detail_route,
|
||||||
)
|
)
|
||||||
from openslides.utils.signals import template_manipulation
|
|
||||||
from openslides.utils.widgets import Widget
|
|
||||||
|
|
||||||
from .exceptions import TagException
|
|
||||||
from .forms import SelectWidgetsForm
|
|
||||||
from .models import CustomSlide, Projector, Tag
|
from .models import CustomSlide, Projector, Tag
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
CustomSlideSerializer,
|
CustomSlideSerializer,
|
||||||
@ -211,272 +191,3 @@ class UrlPatternsView(utils_views.APIView):
|
|||||||
url, url_kwargs = normalized_regex_bits[0]
|
url, url_kwargs = normalized_regex_bits[0]
|
||||||
result[pattern_name] = self.URL_KWARGS_REGEX.sub(r':\1', url)
|
result[pattern_name] = self.URL_KWARGS_REGEX.sub(r':\1', url)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class ErrorView(utils_views.View):
|
|
||||||
"""
|
|
||||||
View for Http 403, 404 and 500 error pages.
|
|
||||||
"""
|
|
||||||
status_code = None
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
|
||||||
http_error_strings = {
|
|
||||||
403: {'name': _('Forbidden'),
|
|
||||||
'description': _('Sorry, you have no permission to see this page.'),
|
|
||||||
'status_code': '403'},
|
|
||||||
404: {'name': _('Not Found'),
|
|
||||||
'description': _('Sorry, the requested page could not be found.'),
|
|
||||||
'status_code': '404'},
|
|
||||||
500: {'name': _('Internal Server Error'),
|
|
||||||
'description': _('Sorry, there was an unknown error. Please contact the event manager.'),
|
|
||||||
'status_code': '500'}}
|
|
||||||
context = {}
|
|
||||||
context['http_error'] = http_error_strings[self.status_code]
|
|
||||||
template_manipulation.send(sender=self.__class__, request=request, context=context)
|
|
||||||
response = render_to_response(
|
|
||||||
'core/error.html',
|
|
||||||
context_instance=RequestContext(request, context))
|
|
||||||
response.status_code = self.status_code
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: Remove the following classes one by one.
|
|
||||||
|
|
||||||
class DashboardView(utils_views.AjaxMixin, utils_views.TemplateView):
|
|
||||||
"""
|
|
||||||
Overview over all possible slides, the overlays and a live view: the
|
|
||||||
Dashboard of OpenSlides. This main view uses the widget api to collect all
|
|
||||||
widgets from all apps. See openslides.utils.widgets.Widget for more details.
|
|
||||||
"""
|
|
||||||
required_permission = 'core.can_see_dashboard'
|
|
||||||
template_name = 'core/dashboard.html'
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
widgets = []
|
|
||||||
for widget in Widget.get_all(self.request):
|
|
||||||
if widget.is_active():
|
|
||||||
widgets.append(widget)
|
|
||||||
context['extra_stylefiles'].extend(widget.get_stylesheets())
|
|
||||||
context['extra_javascript'].extend(widget.get_javascript_files())
|
|
||||||
context['widgets'] = widgets
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class SelectWidgetsView(utils_views.TemplateView):
|
|
||||||
"""
|
|
||||||
Shows a form to select which widgets should be displayed on the own
|
|
||||||
dashboard. The setting is saved in the session.
|
|
||||||
"""
|
|
||||||
# TODO: Use another base view class here, e. g. a FormView
|
|
||||||
required_permission = 'core.can_see_dashboard'
|
|
||||||
template_name = 'core/select_widgets.html'
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
widgets = Widget.get_all(self.request)
|
|
||||||
for widget in widgets:
|
|
||||||
initial = {'widget': widget.is_active()}
|
|
||||||
prefix = widget.name
|
|
||||||
if self.request.method == 'POST':
|
|
||||||
widget.form = SelectWidgetsForm(
|
|
||||||
self.request.POST,
|
|
||||||
prefix=prefix,
|
|
||||||
initial=initial)
|
|
||||||
else:
|
|
||||||
widget.form = SelectWidgetsForm(prefix=prefix, initial=initial)
|
|
||||||
context['widgets'] = widgets
|
|
||||||
return context
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Activates or deactivates the widgets in a post request.
|
|
||||||
"""
|
|
||||||
context = self.get_context_data(**kwargs)
|
|
||||||
session_widgets = self.request.session.get('widgets', {})
|
|
||||||
for widget in context['widgets']:
|
|
||||||
if widget.form.is_valid():
|
|
||||||
session_widgets[widget.name] = widget.form.cleaned_data['widget']
|
|
||||||
else:
|
|
||||||
messages.error(request, _('There are errors in the form.'))
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
self.request.session['widgets'] = session_widgets
|
|
||||||
return redirect(reverse('core_dashboard'))
|
|
||||||
|
|
||||||
|
|
||||||
class VersionView(utils_views.TemplateView):
|
|
||||||
"""
|
|
||||||
Shows version infos.
|
|
||||||
"""
|
|
||||||
template_name = 'core/version.html'
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
"""
|
|
||||||
Adds version strings to the context.
|
|
||||||
"""
|
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
context['modules'] = [{'verbose_name': 'OpenSlides',
|
|
||||||
'description': '',
|
|
||||||
'version': openslides_version}]
|
|
||||||
# Versions of plugins.
|
|
||||||
for plugin in settings.INSTALLED_PLUGINS:
|
|
||||||
context['modules'].append({'verbose_name': get_plugin_verbose_name(plugin),
|
|
||||||
'description': get_plugin_description(plugin),
|
|
||||||
'version': get_plugin_version(plugin)})
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class SearchView(_SearchView):
|
|
||||||
"""
|
|
||||||
Shows search result page.
|
|
||||||
"""
|
|
||||||
template = 'core/search.html'
|
|
||||||
|
|
||||||
def __call__(self, request):
|
|
||||||
if not request.user.is_authenticated() and not config['system_enable_anonymous']:
|
|
||||||
raise PermissionDenied
|
|
||||||
return super().__call__(request)
|
|
||||||
|
|
||||||
def extra_context(self):
|
|
||||||
"""
|
|
||||||
Adds extra context variables to set navigation and search filter.
|
|
||||||
|
|
||||||
Returns a context dictionary.
|
|
||||||
"""
|
|
||||||
context = {}
|
|
||||||
template_manipulation.send(
|
|
||||||
sender=self.__class__, request=self.request, context=context)
|
|
||||||
context['models'] = self.get_indexed_searchmodels()
|
|
||||||
context['get_values'] = self.request.GET.getlist('models')
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_indexed_searchmodels(self):
|
|
||||||
"""
|
|
||||||
Iterate over all INSTALLED_APPS and return a list of models which are
|
|
||||||
indexed by haystack/whoosh for using in customized model search filter
|
|
||||||
in search template search.html. Each list entry contains a verbose name
|
|
||||||
of the model and a special form field value for haystack (app_name.model_name),
|
|
||||||
e.g. ['Agenda', 'agenda.item'].
|
|
||||||
"""
|
|
||||||
models = []
|
|
||||||
# TODO: cache this query!
|
|
||||||
for app in settings.INSTALLED_APPS:
|
|
||||||
try:
|
|
||||||
module = import_module(app + '.search_indexes')
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
models.append([module.Index.modelfilter_name, module.Index.modelfilter_value])
|
|
||||||
return models
|
|
||||||
|
|
||||||
|
|
||||||
class CustomSlideViewMixin(object):
|
|
||||||
"""
|
|
||||||
Mixin for for CustomSlide Views.
|
|
||||||
"""
|
|
||||||
fields = ('title', 'text', 'weight',)
|
|
||||||
required_permission = 'core.can_manage_projector'
|
|
||||||
template_name = 'core/customslide_update.html'
|
|
||||||
model = CustomSlide
|
|
||||||
success_url_name = 'core_dashboard'
|
|
||||||
url_name_args = []
|
|
||||||
|
|
||||||
|
|
||||||
class CustomSlideCreateView(CustomSlideViewMixin, utils_views.CreateView):
|
|
||||||
"""
|
|
||||||
Create a custom slide.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CustomSlideUpdateView(CustomSlideViewMixin, utils_views.UpdateView):
|
|
||||||
"""
|
|
||||||
Update a custom slide.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CustomSlideDeleteView(CustomSlideViewMixin, utils_views.DeleteView):
|
|
||||||
"""
|
|
||||||
Delete a custom slide.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class TagListView(utils_views.AjaxMixin, utils_views.ListView):
|
|
||||||
"""
|
|
||||||
View to list and manipulate tags.
|
|
||||||
|
|
||||||
Shows all tags when requested via a GET-request. Manipulates tags with
|
|
||||||
POST-requests.
|
|
||||||
"""
|
|
||||||
|
|
||||||
model = Tag
|
|
||||||
required_permission = 'core.can_manage_tags'
|
|
||||||
|
|
||||||
def post(self, *args, **kwargs):
|
|
||||||
return self.ajax_get(*args, **kwargs)
|
|
||||||
|
|
||||||
def ajax_get(self, request, *args, **kwargs):
|
|
||||||
name, value = request.POST['name'], request.POST.get('value', None)
|
|
||||||
|
|
||||||
# Create a new tag
|
|
||||||
if name == 'new':
|
|
||||||
try:
|
|
||||||
tag = Tag.objects.create(name=value)
|
|
||||||
except IntegrityError:
|
|
||||||
# The name of the tag is already taken. It must be unique.
|
|
||||||
self.error = 'Tag name is already taken'
|
|
||||||
else:
|
|
||||||
self.pk = tag.pk
|
|
||||||
self.action = 'created'
|
|
||||||
|
|
||||||
# Update an existing tag
|
|
||||||
elif name.startswith('edit-tag-'):
|
|
||||||
try:
|
|
||||||
self.get_tag_queryset(name, 9).update(name=value)
|
|
||||||
except TagException as error:
|
|
||||||
self.error = str(error)
|
|
||||||
except IntegrityError:
|
|
||||||
self.error = 'Tag name is already taken'
|
|
||||||
except Tag.DoesNotExist:
|
|
||||||
self.error = 'Tag does not exist'
|
|
||||||
else:
|
|
||||||
self.action = 'updated'
|
|
||||||
|
|
||||||
# Delete a tag
|
|
||||||
elif name.startswith('delete-tag-'):
|
|
||||||
try:
|
|
||||||
self.get_tag_queryset(name, 11).delete()
|
|
||||||
except TagException as error:
|
|
||||||
self.error = str(error)
|
|
||||||
except Tag.DoesNotExist:
|
|
||||||
self.error = 'Tag does not exist'
|
|
||||||
else:
|
|
||||||
self.action = 'deleted'
|
|
||||||
return super().ajax_get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_tag_queryset(self, name, place_in_str):
|
|
||||||
"""
|
|
||||||
Get a django-tag-queryset from a string.
|
|
||||||
|
|
||||||
'name' is the string in which the pk is (at the end).
|
|
||||||
|
|
||||||
'place_in_str' is the place where to look for the pk. It has to be an int.
|
|
||||||
|
|
||||||
Returns a Tag QuerySet or raises TagException.
|
|
||||||
Also sets self.pk to the pk inside the name.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
self.pk = int(name[place_in_str:])
|
|
||||||
except ValueError:
|
|
||||||
raise TagException('Invalid name in request')
|
|
||||||
return Tag.objects.filter(pk=self.pk)
|
|
||||||
|
|
||||||
def get_ajax_context(self, **context):
|
|
||||||
return super().get_ajax_context(
|
|
||||||
pk=getattr(self, 'pk', None),
|
|
||||||
action=getattr(self, 'action', None),
|
|
||||||
error=getattr(self, 'error', None),
|
|
||||||
**context)
|
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
from openslides.config.api import config
|
|
||||||
from openslides.projector.api import get_active_slide
|
|
||||||
from openslides.utils.widgets import Widget
|
|
||||||
|
|
||||||
from .models import CustomSlide
|
|
||||||
|
|
||||||
|
|
||||||
class WelcomeWidget(Widget):
|
|
||||||
"""
|
|
||||||
Welcome widget with static info for all users.
|
|
||||||
"""
|
|
||||||
name = 'welcome'
|
|
||||||
required_permission = 'core.can_see_dashboard'
|
|
||||||
default_column = 1
|
|
||||||
default_weight = 10
|
|
||||||
template_name = 'core/widget_welcome.html'
|
|
||||||
icon_css_class = 'icon-home'
|
|
||||||
|
|
||||||
def get_verbose_name(self):
|
|
||||||
return config['welcome_title']
|
|
||||||
|
|
||||||
|
|
||||||
class CustonSlideWidget(Widget):
|
|
||||||
"""
|
|
||||||
Widget to control custom slides.
|
|
||||||
"""
|
|
||||||
name = 'custom_slide'
|
|
||||||
verbose_name = ugettext_lazy('Custom Slides')
|
|
||||||
required_permission = 'core.can_manage_projector'
|
|
||||||
default_column = 2
|
|
||||||
default_weight = 30
|
|
||||||
template_name = 'core/widget_customslide.html'
|
|
||||||
context = None
|
|
||||||
icon_css_class = 'icon-star'
|
|
||||||
|
|
||||||
def get_context_data(self, **context):
|
|
||||||
"""
|
|
||||||
Adds custom slides and a flag whether the welcome page is active to
|
|
||||||
the context.
|
|
||||||
"""
|
|
||||||
context['slides'] = CustomSlide.objects.all().order_by('weight')
|
|
||||||
context['welcomepage_is_active'] = (
|
|
||||||
get_active_slide().get('callback', 'default') == 'default')
|
|
||||||
return super(CustonSlideWidget, self).get_context_data(**context)
|
|
@ -1,5 +1,5 @@
|
|||||||
import os
|
|
||||||
import copy
|
import copy
|
||||||
|
import os
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import ugettext_lazy
|
||||||
|
|
||||||
@ -11,9 +11,6 @@ AUTH_USER_MODEL = 'users.User'
|
|||||||
|
|
||||||
AUTHENTICATION_BACKENDS = ('openslides.users.auth.CustomizedModelBackend',)
|
AUTHENTICATION_BACKENDS = ('openslides.users.auth.CustomizedModelBackend',)
|
||||||
|
|
||||||
LOGIN_URL = '/users/'
|
|
||||||
LOGIN_REDIRECT_URL = '/'
|
|
||||||
|
|
||||||
SESSION_COOKIE_NAME = 'OpenSlidesSessionID'
|
SESSION_COOKIE_NAME = 'OpenSlidesSessionID'
|
||||||
|
|
||||||
LANGUAGES = (
|
LANGUAGES = (
|
||||||
@ -89,9 +86,7 @@ INSTALLED_APPS = (
|
|||||||
'haystack', # full-text-search
|
'haystack', # full-text-search
|
||||||
'ckeditor',
|
'ckeditor',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'openslides.poll',
|
'openslides.poll', # TODO: try to remove this line
|
||||||
'openslides.account',
|
|
||||||
'openslides.projector',
|
|
||||||
'openslides.agenda',
|
'openslides.agenda',
|
||||||
'openslides.motions',
|
'openslides.motions',
|
||||||
'openslides.assignments',
|
'openslides.assignments',
|
||||||
|
@ -6,24 +6,9 @@ class MediafileAppConfig(AppConfig):
|
|||||||
verbose_name = 'OpenSlides Mediafiles'
|
verbose_name = 'OpenSlides Mediafiles'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
# Load main menu entry and widgets.
|
|
||||||
# Do this by just importing all from these files.
|
|
||||||
from . import main_menu, widgets # noqa
|
|
||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from openslides.projector.api import register_slide
|
|
||||||
from openslides.utils.rest_api import router
|
from openslides.utils.rest_api import router
|
||||||
from openslides.utils.signals import template_manipulation
|
|
||||||
from .slides import mediafile_presentation_as_slide
|
|
||||||
from .template import add_mediafile_stylesheets
|
|
||||||
from .views import MediafileViewSet
|
from .views import MediafileViewSet
|
||||||
|
|
||||||
# Connect template signal.
|
|
||||||
template_manipulation.connect(add_mediafile_stylesheets, dispatch_uid='add_mediafile_stylesheets')
|
|
||||||
|
|
||||||
# Register slides.
|
|
||||||
Mediafile = self.get_model('Mediafile')
|
|
||||||
register_slide('mediafile', mediafile_presentation_as_slide, Mediafile)
|
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register('mediafiles/mediafile', MediafileViewSet)
|
router.register('mediafiles/mediafile', MediafileViewSet)
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
from django.forms import ModelForm
|
|
||||||
|
|
||||||
from openslides.utils.forms import CssClassMixin
|
|
||||||
|
|
||||||
from .models import Mediafile
|
|
||||||
|
|
||||||
|
|
||||||
class MediafileFormMixin(object):
|
|
||||||
"""
|
|
||||||
Mixin for mediafile forms. It is used to delete old files.
|
|
||||||
"""
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Method to save the form. Here the override is to delete old files.
|
|
||||||
"""
|
|
||||||
if self.instance.pk is not None:
|
|
||||||
old_file = Mediafile.objects.get(pk=self.instance.pk).mediafile
|
|
||||||
if not old_file == self.instance.mediafile:
|
|
||||||
old_file.delete()
|
|
||||||
return super(MediafileFormMixin, self).save(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class MediafileNormalUserForm(MediafileFormMixin, CssClassMixin, ModelForm):
|
|
||||||
"""
|
|
||||||
This form is only used by normal users, not by managers.
|
|
||||||
"""
|
|
||||||
class Meta:
|
|
||||||
model = Mediafile
|
|
||||||
fields = ('mediafile', 'title', 'is_presentable')
|
|
||||||
|
|
||||||
|
|
||||||
class MediafileManagerForm(MediafileFormMixin, CssClassMixin, ModelForm):
|
|
||||||
"""
|
|
||||||
This form is only used be managers, not by normal users.
|
|
||||||
"""
|
|
||||||
class Meta:
|
|
||||||
model = Mediafile
|
|
||||||
fields = ('mediafile', 'title', 'uploader', 'is_presentable')
|
|
@ -1,19 +0,0 @@
|
|||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
from openslides.utils.main_menu import MainMenuEntry
|
|
||||||
|
|
||||||
|
|
||||||
class MediafileMainMenuEntry(MainMenuEntry):
|
|
||||||
"""
|
|
||||||
Main menu entry for the mediafile app.
|
|
||||||
"""
|
|
||||||
verbose_name = ugettext_lazy('Files')
|
|
||||||
default_weight = 60
|
|
||||||
pattern_name = '/mediafiles'
|
|
||||||
icon_css_class = 'glyphicon-paperclip'
|
|
||||||
|
|
||||||
def check_permission(self):
|
|
||||||
return (
|
|
||||||
self.request.user.has_perm('mediafiles.can_see') or
|
|
||||||
self.request.user.has_perm('mediafiles.can_upload') or
|
|
||||||
self.request.user.has_perm('mediafiles.can_manage'))
|
|
@ -1,17 +1,15 @@
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||||
|
|
||||||
from openslides.projector.models import SlideMixin
|
from openslides.projector.models import SlideMixin
|
||||||
from openslides.utils.models import AbsoluteUrlMixin
|
|
||||||
from openslides.utils.rest_api import RESTModelMixin
|
|
||||||
from openslides.users.models import User
|
from openslides.users.models import User
|
||||||
|
from openslides.utils.rest_api import RESTModelMixin
|
||||||
|
|
||||||
|
|
||||||
class Mediafile(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
class Mediafile(RESTModelMixin, SlideMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
Class for uploaded files which can be delivered under a certain url.
|
Class for uploaded files which can be delivered under a certain url.
|
||||||
"""
|
"""
|
||||||
@ -68,19 +66,6 @@ class Mediafile(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
self.filetype = ugettext_noop('unknown')
|
self.filetype = ugettext_noop('unknown')
|
||||||
return super(Mediafile, self).save(*args, **kwargs)
|
return super(Mediafile, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def get_absolute_url(self, link='update'):
|
|
||||||
"""
|
|
||||||
Returns the URL to a mediafile. The link can be 'projector',
|
|
||||||
'update' or 'delete'.
|
|
||||||
"""
|
|
||||||
if link == 'update':
|
|
||||||
url = reverse('mediafile_update', kwargs={'pk': str(self.pk)})
|
|
||||||
elif link == 'delete':
|
|
||||||
url = reverse('mediafile_delete', kwargs={'pk': str(self.pk)})
|
|
||||||
else:
|
|
||||||
url = super(Mediafile, self).get_absolute_url(link)
|
|
||||||
return url
|
|
||||||
|
|
||||||
def get_filesize(self):
|
def get_filesize(self):
|
||||||
"""
|
"""
|
||||||
Transforms bytes to kilobytes or megabytes. Returns the size as string.
|
Transforms bytes to kilobytes or megabytes. Returns the size as string.
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from haystack import indexes
|
from haystack import indexes
|
||||||
|
|
||||||
from .models import Mediafile
|
from .models import Mediafile
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
from django.template.loader import render_to_string
|
|
||||||
|
|
||||||
from openslides.config.api import config
|
|
||||||
from openslides.projector.api import SlideError
|
|
||||||
|
|
||||||
from .models import Mediafile
|
|
||||||
|
|
||||||
|
|
||||||
def mediafile_presentation_as_slide(**kwargs):
|
|
||||||
"""
|
|
||||||
Return the html code for a presentation of a Mediafile.
|
|
||||||
|
|
||||||
At the moment, only the presentation of pdfs is supported.
|
|
||||||
|
|
||||||
The function is registered during app loading.
|
|
||||||
"""
|
|
||||||
file_pk = kwargs.get('pk', None)
|
|
||||||
page_num = kwargs.get('page_num', 1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
pdf = Mediafile.objects.get(
|
|
||||||
pk=file_pk,
|
|
||||||
filetype__in=Mediafile.PRESENTABLE_FILE_TYPES,
|
|
||||||
is_presentable=True)
|
|
||||||
except Mediafile.DoesNotExist:
|
|
||||||
raise SlideError
|
|
||||||
context = {'pdf': pdf, 'page_num': page_num,
|
|
||||||
'fullscreen': config['pdf_fullscreen']}
|
|
||||||
return render_to_string('mediafiles/presentation_slide.html', context)
|
|
@ -1,7 +0,0 @@
|
|||||||
def add_mediafile_stylesheets(sender, request, context, **kwargs):
|
|
||||||
"""
|
|
||||||
Receiver function to add the mediafile.css to the context. It is
|
|
||||||
connected to the signal openslides.utils.signals.template_manipulation
|
|
||||||
during app loading.
|
|
||||||
"""
|
|
||||||
context['extra_stylefiles'].append('css/mediafile.css')
|
|
@ -1,37 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
{% if mediafile %}
|
|
||||||
{% trans "Edit file" %}
|
|
||||||
{% else %}
|
|
||||||
{% trans "New file" %}
|
|
||||||
{% endif %}
|
|
||||||
– {{ block.super }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>
|
|
||||||
{% if mediafile %}
|
|
||||||
{% trans "Edit file" %}
|
|
||||||
{% else %}
|
|
||||||
{% trans "New file" %}
|
|
||||||
{% endif %}
|
|
||||||
<small class="pull-right">
|
|
||||||
<a href="{% url 'mediafile_list' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
<form action="" method="post" enctype="multipart/form-data">{% csrf_token %}
|
|
||||||
{% include "form.html" %}
|
|
||||||
<p>
|
|
||||||
{% if perms.mediafile.can_manage %}
|
|
||||||
{% include "formbuttons_saveapply.html" %}
|
|
||||||
{% else %}
|
|
||||||
{% include "formbuttons_save.html" %}
|
|
||||||
{% endif %}
|
|
||||||
<a href="{% url 'mediafile_list' %}" class="btn">{% trans 'Cancel' %}</a>
|
|
||||||
</p>
|
|
||||||
<small>* {% trans "required" %}</small>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
@ -1,59 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load tags %}
|
|
||||||
|
|
||||||
{% block title %}{% trans 'Files' %} – {{ block.super }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{% trans 'Files' %}
|
|
||||||
<small class="pull-right">
|
|
||||||
{% if perms.mediafiles.can_upload %}
|
|
||||||
<a href="{% url 'mediafile_create' %}" class="btn btn-mini btn-primary" rel="tooltip" data-original-title="{% trans 'New file' %}"><i class="icon-plus icon-white"></i> {% trans "New" %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
<table class="table table-striped table-bordered">
|
|
||||||
<tr>
|
|
||||||
<th>{% trans 'Title' %}</th>
|
|
||||||
<th class="optional">{% trans 'Type' %}</th>
|
|
||||||
<th class="optional-small">{% trans 'Size' %}</th>
|
|
||||||
<th class="optional">{% trans 'Upload time' %}</th>
|
|
||||||
<th class="optional">{% trans 'Uploaded by' %}</th>
|
|
||||||
{% if perms.mediafiles.can_manage or perms.mediafiles.can_upload %}
|
|
||||||
<th class="mini_width">{% trans "Actions" %}</th>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
{% for mediafile in mediafile_list %}
|
|
||||||
<tr class="{% if mediafile.is_active_slide %}activeline{% endif %}">
|
|
||||||
<td><a href="{{ mediafile.mediafile.url }}">{{ mediafile }}</a></td>
|
|
||||||
<td class="optional">{% trans mediafile.filetype %}</td>
|
|
||||||
<td class="optional-small">{{ mediafile.get_filesize }}</td>
|
|
||||||
<td class="optional">{{ mediafile.timestamp }}</td>
|
|
||||||
<td class="optional"><a href="{{ mediafile.uploader|absolute_url }}">{{ mediafile.uploader }}</a></td>
|
|
||||||
{% if perms.mediafiles.can_manage or perms.mediafiles.can_upload %}
|
|
||||||
<td>
|
|
||||||
{% if mediafile.with_action_buttons %}
|
|
||||||
<span style="width: 1px; white-space: nowrap;">
|
|
||||||
<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>
|
|
||||||
{% if perms.mediafiles.can_manage and mediafile.is_presentable %}{% if mediafile.filetype in mediafile.PRESENTABLE_FILE_TYPES %}
|
|
||||||
<a href="{{ mediafile|absolute_url:'projector' }}" class="activate_link choose-pdf btn {% if mediafile.is_active_slide %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
|
||||||
<i class="icon-facetime-video {% if mediafile.is_active_slide %}icon-white{% endif %}">
|
|
||||||
</i>
|
|
||||||
</a>
|
|
||||||
{% endif %}{% endif %}
|
|
||||||
</span>
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
{% empty %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="6"><i>{% trans 'No files available.' %}</i></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
{% endblock %}
|
|
@ -1,17 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
<script src="{% static 'js/pdf.js' %}" type="text/javascript"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
projector['pdf_url'] = '{{ pdf.mediafile.url }}';
|
|
||||||
projector['pdf_page_num'] = {{ page_num }};
|
|
||||||
projector['pdf_fullscreen'] = {% if fullscreen %}true{% else %}false{% endif %};
|
|
||||||
PDFJS.workerSrc = "{% static 'js/pdf.worker.js' %}";
|
|
||||||
</script>
|
|
||||||
<script src="{% static 'js/pdf_presenter.js' %}" type="text/javascript"></script>
|
|
||||||
|
|
||||||
<link href="{% static 'css/mediafile_projector.css' %}" type="text/css" rel="stylesheet">
|
|
||||||
|
|
||||||
<div class="canvas-container">
|
|
||||||
<canvas id="presentation" class="{% if fullscreen %}fullscreen{% endif %}"></canvas>
|
|
||||||
</div>
|
|
@ -1,50 +0,0 @@
|
|||||||
{% extends 'core/widget.html' %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load tags %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<form action="{% url 'mediafiles_target_pdf_page' %}" method="GET" class="set-page-form">
|
|
||||||
<div class="input-prepend" style="margin-bottom:0;">
|
|
||||||
<a class="btn go-first-page"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'First page' %}">
|
|
||||||
<i class="icon-fast-backward"></i>
|
|
||||||
</a>
|
|
||||||
<a class="btn pdf-page-ctl" href="{% url 'mediafiles_prev_pdf_page' %}"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Previous page' %}">
|
|
||||||
<i class="icon-backward"></i>
|
|
||||||
</a>
|
|
||||||
<a class="btn pdf-page-ctl" href="{% url 'mediafiles_next_pdf_page' %}"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Next page' %}">
|
|
||||||
<i class="icon-forward"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="input-append" style="margin-bottom:0;">
|
|
||||||
<a class="btn pdf-toggle-fullscreen {% if 'pdf_fullscreen'|get_config %}btn-primary{% endif %}"
|
|
||||||
href="{% url 'mediafiles_toggle_fullscreen' %}"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Fullscreen' %}">
|
|
||||||
<i class="icon-fullscreen {% if 'pdf_fullscreen'|get_config %}icon-white{% endif %}"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="input-append input-prepend" style="margin-bottom:0;">
|
|
||||||
<span class="add-on">{% trans "Page" %}:</span>
|
|
||||||
<input id="page_num" name="page_num" type="number" style="width: 22px;" value="{{ current_page }}">
|
|
||||||
<button type="submit" id="go_to_page" class="btn tooltip-bottom"
|
|
||||||
rel="tooltip" data-original-title="{% trans 'Apply' %}">
|
|
||||||
<i class="icon-refresh"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<ul style="line-height: 180%">
|
|
||||||
{% for pdf in pdfs %}
|
|
||||||
<li class="{% if pdf.is_active_slide %}activeline{% endif %}">
|
|
||||||
<a href="{{ pdf|absolute_url:'projector' }}" class="activate_link choose-pdf btn {% if pdf.is_active_slide %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
|
||||||
<i class="icon-facetime-video {% if pdf.is_active_slide %}icon-white{% endif %}"></i>
|
|
||||||
</a> {{ pdf }}
|
|
||||||
</li>
|
|
||||||
{% empty %}
|
|
||||||
<li>{% trans 'No PDFs available.' %}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
@ -1,19 +0,0 @@
|
|||||||
from django.conf.urls import patterns, url
|
|
||||||
|
|
||||||
from . import views
|
|
||||||
|
|
||||||
urlpatterns = patterns(
|
|
||||||
'',
|
|
||||||
url(r'^pdf/next/$',
|
|
||||||
views.PdfNextView.as_view(),
|
|
||||||
name='mediafiles_next_pdf_page'),
|
|
||||||
url(r'^pdf/prev/$',
|
|
||||||
views.PdfPreviousView.as_view(),
|
|
||||||
name='mediafiles_prev_pdf_page'),
|
|
||||||
url(r'^pdf/target_page/$',
|
|
||||||
views.PdfGoToPageView.as_view(),
|
|
||||||
name='mediafiles_target_pdf_page'),
|
|
||||||
url(r'^pdf/toggle_fullscreen/$',
|
|
||||||
views.PdfToggleFullscreenView.as_view(),
|
|
||||||
name='mediafiles_toggle_fullscreen')
|
|
||||||
)
|
|
@ -1,108 +1,9 @@
|
|||||||
from django.http import HttpResponse
|
|
||||||
|
|
||||||
from openslides.config.api import config
|
|
||||||
from openslides.projector.api import get_active_slide
|
|
||||||
from openslides.utils.rest_api import ModelViewSet
|
from openslides.utils.rest_api import ModelViewSet
|
||||||
from openslides.utils.views import (AjaxView, RedirectView)
|
|
||||||
|
|
||||||
from .models import Mediafile
|
from .models import Mediafile
|
||||||
from .serializers import MediafileSerializer
|
from .serializers import MediafileSerializer
|
||||||
|
|
||||||
|
|
||||||
class PdfNavBaseView(AjaxView):
|
|
||||||
"""
|
|
||||||
BaseView for the Pdf Ajax Navigation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_ajax_context(self, *args, **kwargs):
|
|
||||||
return {'current_page': self.active_slide['page_num']}
|
|
||||||
|
|
||||||
def load_other_page(self, active_slide):
|
|
||||||
"""
|
|
||||||
Tell connected clients to load an other pdf page.
|
|
||||||
"""
|
|
||||||
config['projector_active_slide'] = active_slide
|
|
||||||
|
|
||||||
|
|
||||||
class PdfNextView(PdfNavBaseView):
|
|
||||||
"""
|
|
||||||
Activate the next Page of a pdf and return the number of the current page.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Increment the page number by 1.
|
|
||||||
|
|
||||||
If the page number is set in the active slide, we are the value is
|
|
||||||
incremented by 1. Otherwise, it is the first page and it is set to 2.
|
|
||||||
"""
|
|
||||||
self.active_slide = get_active_slide()
|
|
||||||
if self.active_slide['callback'] == 'mediafile':
|
|
||||||
if 'page_num' not in self.active_slide:
|
|
||||||
self.active_slide['page_num'] = 2
|
|
||||||
else:
|
|
||||||
self.active_slide['page_num'] += 1
|
|
||||||
self.load_other_page(self.active_slide)
|
|
||||||
response = super(PdfNextView, self).get(self, request, *args, **kwargs)
|
|
||||||
else:
|
|
||||||
# no Mediafile is active and the JavaScript should not do anything.
|
|
||||||
response = HttpResponse()
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
class PdfPreviousView(PdfNavBaseView):
|
|
||||||
"""
|
|
||||||
Activate the previous Page of a pdf and return the number of the current page.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Decrement the page number by 1.
|
|
||||||
|
|
||||||
If the page number is set and it is greater than 1, it is decremented
|
|
||||||
by 1. Otherwise, it is the first page and nothing happens.
|
|
||||||
"""
|
|
||||||
self.active_slide = get_active_slide()
|
|
||||||
response = None
|
|
||||||
if self.active_slide['callback'] == 'mediafile':
|
|
||||||
if 'page_num' in self.active_slide and self.active_slide['page_num'] > 1:
|
|
||||||
self.active_slide['page_num'] -= 1
|
|
||||||
self.load_other_page(self.active_slide)
|
|
||||||
response = super(PdfPreviousView, self).get(self, request, *args, **kwargs)
|
|
||||||
if not response:
|
|
||||||
response = HttpResponse()
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
class PdfGoToPageView(PdfNavBaseView):
|
|
||||||
"""
|
|
||||||
Activate the page set in the textfield.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
target_page = int(request.GET.get('page_num'))
|
|
||||||
self.active_slide = get_active_slide()
|
|
||||||
if target_page:
|
|
||||||
self.active_slide['page_num'] = target_page
|
|
||||||
self.load_other_page(self.active_slide)
|
|
||||||
response = super(PdfGoToPageView, self).get(self, request, *args, **kwargs)
|
|
||||||
else:
|
|
||||||
response = HttpResponse()
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
class PdfToggleFullscreenView(RedirectView):
|
|
||||||
"""
|
|
||||||
Toggle fullscreen mode for pdf presentations.
|
|
||||||
"""
|
|
||||||
allow_ajax = True
|
|
||||||
url_name = 'core_dashboard'
|
|
||||||
|
|
||||||
def get_ajax_context(self, *args, **kwargs):
|
|
||||||
config['pdf_fullscreen'] = not config['pdf_fullscreen']
|
|
||||||
return {'fullscreen': config['pdf_fullscreen']}
|
|
||||||
|
|
||||||
|
|
||||||
class MediafileViewSet(ModelViewSet):
|
class MediafileViewSet(ModelViewSet):
|
||||||
"""
|
"""
|
||||||
API endpoint to list, retrieve, create, update and destroy mediafile
|
API endpoint to list, retrieve, create, update and destroy mediafile
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
from openslides.utils.widgets import Widget
|
|
||||||
from openslides.projector.api import get_active_slide
|
|
||||||
|
|
||||||
from .models import Mediafile
|
|
||||||
|
|
||||||
|
|
||||||
class PDFPresentationWidget(Widget):
|
|
||||||
"""
|
|
||||||
Widget for presentable PDF files.
|
|
||||||
"""
|
|
||||||
name = 'presentations'
|
|
||||||
verbose_name = ugettext_lazy('Presentations')
|
|
||||||
required_permission = 'core.can_manage_projector'
|
|
||||||
default_column = 1
|
|
||||||
default_weight = 75
|
|
||||||
template_name = 'mediafiles/widget_pdfpresentation.html'
|
|
||||||
icon_css_class = 'icon-align-left'
|
|
||||||
# javascript_files = None # TODO: Add pdf.js stuff here.
|
|
||||||
|
|
||||||
def get_context_data(self, **context):
|
|
||||||
pdfs = Mediafile.objects.filter(
|
|
||||||
filetype__in=Mediafile.PRESENTABLE_FILE_TYPES,
|
|
||||||
is_presentable=True)
|
|
||||||
current_page = get_active_slide().get('page_num', 1)
|
|
||||||
return super(PDFPresentationWidget, self).get_context_data(
|
|
||||||
pdfs=pdfs,
|
|
||||||
current_page=current_page,
|
|
||||||
**context)
|
|
@ -7,14 +7,9 @@ class MotionAppConfig(AppConfig):
|
|||||||
verbose_name = 'OpenSlides Motion'
|
verbose_name = 'OpenSlides Motion'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
# Load main menu entry, personal info and widgets.
|
|
||||||
# Do this by just importing all from these files.
|
|
||||||
from . import main_menu, personal_info, widgets # noqa
|
|
||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from openslides.config.signals import config_signal
|
from openslides.config.signals import config_signal
|
||||||
from openslides.utils.rest_api import router
|
from openslides.utils.rest_api import router
|
||||||
from openslides.projector.api import register_slide_model
|
|
||||||
from .signals import create_builtin_workflows, setup_motion_config
|
from .signals import create_builtin_workflows, setup_motion_config
|
||||||
from .views import CategoryViewSet, MotionViewSet, WorkflowViewSet
|
from .views import CategoryViewSet, MotionViewSet, WorkflowViewSet
|
||||||
|
|
||||||
@ -22,12 +17,6 @@ class MotionAppConfig(AppConfig):
|
|||||||
config_signal.connect(setup_motion_config, dispatch_uid='setup_motion_config')
|
config_signal.connect(setup_motion_config, dispatch_uid='setup_motion_config')
|
||||||
post_migrate.connect(create_builtin_workflows, dispatch_uid='motion_create_builtin_workflows')
|
post_migrate.connect(create_builtin_workflows, dispatch_uid='motion_create_builtin_workflows')
|
||||||
|
|
||||||
# Register slides.
|
|
||||||
Motion = self.get_model('Motion')
|
|
||||||
MotionPoll = self.get_model('MotionPoll')
|
|
||||||
register_slide_model(Motion, 'motions/slide.html')
|
|
||||||
register_slide_model(MotionPoll, 'motions/motionpoll_slide.html')
|
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register('motions/category', CategoryViewSet)
|
router.register('motions/category', CategoryViewSet)
|
||||||
router.register('motions/motion', MotionViewSet)
|
router.register('motions/motion', MotionViewSet)
|
||||||
|
@ -1,206 +0,0 @@
|
|||||||
import collections
|
|
||||||
|
|
||||||
from django import forms
|
|
||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
from openslides.mediafiles.models import Mediafile
|
|
||||||
from openslides.utils.forms import (CleanHtmlFormMixin, CssClassMixin,
|
|
||||||
CSVImportForm, LocalizedModelChoiceField)
|
|
||||||
from openslides.users.models import User
|
|
||||||
|
|
||||||
from ckeditor.widgets import CKEditorWidget
|
|
||||||
|
|
||||||
from .models import Category, Motion, Workflow, Tag
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMotionForm(CleanHtmlFormMixin, CssClassMixin, forms.ModelForm):
|
|
||||||
"""
|
|
||||||
Base FormClass for a Motion.
|
|
||||||
|
|
||||||
For it's own, it append the version data to the fields.
|
|
||||||
|
|
||||||
The class can be mixed with the following mixins to add fields for the
|
|
||||||
submitter, supporters etc.
|
|
||||||
"""
|
|
||||||
clean_html_fields = ('text', 'reason')
|
|
||||||
|
|
||||||
title = forms.CharField(widget=forms.TextInput(), label=ugettext_lazy("Title"))
|
|
||||||
"""
|
|
||||||
Title of the motion. Will be saved in a MotionVersion object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
text = forms.CharField(widget=CKEditorWidget(), label=ugettext_lazy("Text"))
|
|
||||||
"""
|
|
||||||
Text of the motion. Will be saved in a MotionVersion object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
reason = forms.CharField(
|
|
||||||
widget=CKEditorWidget(), required=False, label=ugettext_lazy("Reason"))
|
|
||||||
"""
|
|
||||||
Reason of the motion. will be saved in a MotionVersion object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
attachments = forms.ModelMultipleChoiceField(
|
|
||||||
queryset=Mediafile.objects.all(),
|
|
||||||
required=False,
|
|
||||||
label=ugettext_lazy('Attachments'))
|
|
||||||
"""
|
|
||||||
Attachments of the motion.
|
|
||||||
"""
|
|
||||||
|
|
||||||
tags = forms.ModelMultipleChoiceField(
|
|
||||||
queryset=Tag.objects.all(),
|
|
||||||
required=False,
|
|
||||||
label=ugettext_lazy('Tags'))
|
|
||||||
|
|
||||||
key_order = ('identifier',
|
|
||||||
'title',
|
|
||||||
'text',
|
|
||||||
'reason',
|
|
||||||
'submitter',
|
|
||||||
'supporter',
|
|
||||||
'category',
|
|
||||||
'tags',
|
|
||||||
'attachments',
|
|
||||||
'workflow',
|
|
||||||
'disable_versioning',)
|
|
||||||
"""
|
|
||||||
Order of fields, including optional fields from mixins (for example MotionSupporterMixin)
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Motion
|
|
||||||
fields = ()
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Fill the FormFields related to the version data with initial data.
|
|
||||||
Fill also the initial data for attachments and tags.
|
|
||||||
"""
|
|
||||||
self.motion = kwargs.get('instance', None)
|
|
||||||
self.initial = kwargs.setdefault('initial', {})
|
|
||||||
if self.motion is not None:
|
|
||||||
last_version = self.motion.get_last_version()
|
|
||||||
self.initial['title'] = last_version.title
|
|
||||||
self.initial['text'] = last_version.text
|
|
||||||
self.initial['reason'] = last_version.reason
|
|
||||||
self.initial['attachments'] = self.motion.attachments.all()
|
|
||||||
self.initial['tags'] = self.motion.tags.all()
|
|
||||||
super(BaseMotionForm, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
keys = self.fields.keys()
|
|
||||||
keys_order = [key for key in self.key_order if key in keys]
|
|
||||||
keys_order.extend(set(keys) - set(keys_order))
|
|
||||||
self.fields = collections.OrderedDict([(key, self.fields[key]) for key in keys_order])
|
|
||||||
|
|
||||||
|
|
||||||
class MotionSubmitterMixin(forms.ModelForm):
|
|
||||||
"""Mixin to append the submitter field to a MotionForm."""
|
|
||||||
|
|
||||||
submitter = forms.ModelMultipleChoiceField(
|
|
||||||
User.objects, label=ugettext_lazy("Submitter"), required=False)
|
|
||||||
"""Submitter of the motion. Can be one or more persons."""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
"""Fill in the submitter of the motion as default value."""
|
|
||||||
if self.motion is not None:
|
|
||||||
submitter = self.motion.submitters.all()
|
|
||||||
self.initial['submitter'] = submitter
|
|
||||||
super(MotionSubmitterMixin, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class MotionSupporterMixin(forms.ModelForm):
|
|
||||||
"""Mixin to append the supporter field to a Motionform."""
|
|
||||||
|
|
||||||
supporter = forms.ModelMultipleChoiceField(
|
|
||||||
User.objects, required=False, label=ugettext_lazy("Supporters"))
|
|
||||||
"""Supporter of the motion. Can be one or more persons."""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
"""Fill in the supporter of the motions as default value."""
|
|
||||||
if self.motion is not None:
|
|
||||||
supporter = self.motion.supporters.all()
|
|
||||||
self.initial['supporter'] = supporter
|
|
||||||
super(MotionSupporterMixin, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class MotionDisableVersioningMixin(forms.ModelForm):
|
|
||||||
"""Mixin to add the option to the form to choose to disable versioning."""
|
|
||||||
|
|
||||||
disable_versioning = forms.BooleanField(
|
|
||||||
required=False, label=ugettext_lazy("Don't create a new version"),
|
|
||||||
help_text=ugettext_lazy("Don't create a new version. Useful e.g. for trivial changes."))
|
|
||||||
"""BooleanField to decide, if a new version will be created, or the
|
|
||||||
last_version will be used."""
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: Add category and identifier to the form as normal fields (the django way),
|
|
||||||
# not as 'new' field from 'new' forms.
|
|
||||||
|
|
||||||
class MotionCategoryMixin(forms.ModelForm):
|
|
||||||
"""
|
|
||||||
Mixin to let the user choose the category for the motion.
|
|
||||||
"""
|
|
||||||
|
|
||||||
category = forms.ModelChoiceField(queryset=Category.objects.all(), required=False, label=ugettext_lazy("Category"))
|
|
||||||
"""
|
|
||||||
Category of the motion.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Fill in the category of the motion as default value.
|
|
||||||
"""
|
|
||||||
if self.motion is not None:
|
|
||||||
category = self.motion.category
|
|
||||||
self.initial['category'] = category
|
|
||||||
super(MotionCategoryMixin, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class MotionIdentifierMixin(forms.ModelForm):
|
|
||||||
"""
|
|
||||||
Mixin to let the user choose the identifier for the motion.
|
|
||||||
"""
|
|
||||||
|
|
||||||
identifier = forms.CharField(required=False, label=ugettext_lazy('Identifier'))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Motion
|
|
||||||
fields = ('identifier',)
|
|
||||||
|
|
||||||
|
|
||||||
class MotionWorkflowMixin(forms.ModelForm):
|
|
||||||
"""
|
|
||||||
Mixin to let the user change the workflow of the motion.
|
|
||||||
"""
|
|
||||||
|
|
||||||
workflow = LocalizedModelChoiceField(
|
|
||||||
queryset=Workflow.objects.all(),
|
|
||||||
empty_label=None,
|
|
||||||
label=ugettext_lazy('Workflow'),
|
|
||||||
help_text=ugettext_lazy('Set a specific workflow to switch to it. '
|
|
||||||
'If you do so, the state of the motion will be reset.'))
|
|
||||||
|
|
||||||
|
|
||||||
class MotionCSVImportForm(CSVImportForm):
|
|
||||||
"""
|
|
||||||
Form for motion import via csv file.
|
|
||||||
"""
|
|
||||||
override = forms.BooleanField(
|
|
||||||
required=False,
|
|
||||||
label=ugettext_lazy('Override existing motions with the same identifier'),
|
|
||||||
help_text=ugettext_lazy('If this is active, every motion with the same identifier as in your csv file will be overridden.'))
|
|
||||||
"""
|
|
||||||
Flag to decide whether existing motions (according to the identifier)
|
|
||||||
should be overridden.
|
|
||||||
"""
|
|
||||||
|
|
||||||
default_submitter = forms.ModelChoiceField(
|
|
||||||
User.objects.all(),
|
|
||||||
required=True,
|
|
||||||
label=ugettext_lazy('Default submitter'),
|
|
||||||
help_text=ugettext_lazy('This person is used as submitter for any line of your csv file which does not contain valid submitter data.'))
|
|
||||||
"""
|
|
||||||
Person which is used as submitter, if the line of the csv file does
|
|
||||||
not contain valid submitter data.
|
|
||||||
"""
|
|
@ -1,14 +0,0 @@
|
|||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
from openslides.utils.main_menu import MainMenuEntry
|
|
||||||
|
|
||||||
|
|
||||||
class MotionMainMenuEntry(MainMenuEntry):
|
|
||||||
"""
|
|
||||||
Main menu entry for the motion app.
|
|
||||||
"""
|
|
||||||
verbose_name = ugettext_lazy('Motions')
|
|
||||||
required_permission = 'motions.can_see'
|
|
||||||
default_weight = 30
|
|
||||||
pattern_name = '/motions' # TODO: use generic solution, see issue #1469
|
|
||||||
icon_css_class = 'glyphicon-file'
|
|
@ -1,25 +1,28 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Max
|
from django.db.models import Max
|
||||||
from django.utils import formats
|
from django.utils import formats
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||||
|
from jsonfield import JSONField
|
||||||
|
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.core.models import Tag
|
from openslides.core.models import Tag
|
||||||
from openslides.mediafiles.models import Mediafile
|
from openslides.mediafiles.models import Mediafile
|
||||||
from openslides.poll.models import (BaseOption, BasePoll, BaseVote, CollectDefaultVotesMixin)
|
from openslides.poll.models import (
|
||||||
|
BaseOption,
|
||||||
|
BasePoll,
|
||||||
|
BaseVote,
|
||||||
|
CollectDefaultVotesMixin,
|
||||||
|
)
|
||||||
from openslides.projector.models import SlideMixin
|
from openslides.projector.models import SlideMixin
|
||||||
from jsonfield import JSONField
|
|
||||||
from openslides.utils.models import AbsoluteUrlMixin
|
|
||||||
from openslides.utils.rest_api import RESTModelMixin
|
|
||||||
from openslides.users.models import User
|
from openslides.users.models import User
|
||||||
|
from openslides.utils.rest_api import RESTModelMixin
|
||||||
|
|
||||||
from .exceptions import WorkflowError
|
from .exceptions import WorkflowError
|
||||||
|
|
||||||
|
|
||||||
class Motion(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
class Motion(RESTModelMixin, SlideMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
The Motion Class.
|
The Motion Class.
|
||||||
|
|
||||||
@ -191,22 +194,6 @@ class Motion(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
self.active_version = use_version
|
self.active_version = use_version
|
||||||
self.save(update_fields=['active_version'])
|
self.save(update_fields=['active_version'])
|
||||||
|
|
||||||
def get_absolute_url(self, link='detail'):
|
|
||||||
"""
|
|
||||||
Return an URL for this version.
|
|
||||||
|
|
||||||
The keyword argument 'link' can be 'detail', 'update' or 'delete'.
|
|
||||||
"""
|
|
||||||
if link == 'detail':
|
|
||||||
url = reverse('motion_detail', args=[str(self.pk)])
|
|
||||||
elif link == 'update':
|
|
||||||
url = reverse('motion_update', args=[str(self.pk)])
|
|
||||||
elif link == 'delete':
|
|
||||||
url = reverse('motion_delete', args=[str(self.pk)])
|
|
||||||
else:
|
|
||||||
url = super(Motion, self).get_absolute_url(link)
|
|
||||||
return url
|
|
||||||
|
|
||||||
def version_data_changed(self, version):
|
def version_data_changed(self, version):
|
||||||
"""
|
"""
|
||||||
Compare the version with the last version of the motion.
|
Compare the version with the last version of the motion.
|
||||||
@ -535,7 +522,7 @@ class Motion(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
return config['motion_amendments_enabled'] and self.parent is not None
|
return config['motion_amendments_enabled'] and self.parent is not None
|
||||||
|
|
||||||
|
|
||||||
class MotionVersion(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
class MotionVersion(RESTModelMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
A MotionVersion object saves some date of the motion.
|
A MotionVersion object saves some date of the motion.
|
||||||
"""
|
"""
|
||||||
@ -572,22 +559,6 @@ class MotionVersion(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
counter = self.version_number or ugettext_lazy('new')
|
counter = self.version_number or ugettext_lazy('new')
|
||||||
return "Motion %s, Version %s" % (self.motion_id, counter)
|
return "Motion %s, Version %s" % (self.motion_id, counter)
|
||||||
|
|
||||||
def get_absolute_url(self, link='detail'):
|
|
||||||
"""
|
|
||||||
Return the URL of this Version.
|
|
||||||
|
|
||||||
The keyargument link can be 'detail' or 'delete'.
|
|
||||||
"""
|
|
||||||
if link == 'detail':
|
|
||||||
url = reverse('motion_version_detail', args=[str(self.motion.pk),
|
|
||||||
str(self.version_number)])
|
|
||||||
elif link == 'delete':
|
|
||||||
url = reverse('motion_version_delete', args=[str(self.motion.pk),
|
|
||||||
str(self.version_number)])
|
|
||||||
else:
|
|
||||||
url = super(MotionVersion, self).get_absolute_url(link)
|
|
||||||
return url
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def active(self):
|
def active(self):
|
||||||
"""Return True, if the version is the active version of a motion. Else: False."""
|
"""Return True, if the version is the active version of a motion. Else: False."""
|
||||||
@ -600,7 +571,7 @@ class MotionVersion(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
return self.motion
|
return self.motion
|
||||||
|
|
||||||
|
|
||||||
class Category(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
class Category(RESTModelMixin, models.Model):
|
||||||
name = models.CharField(max_length=255, verbose_name=ugettext_lazy("Category name"))
|
name = models.CharField(max_length=255, verbose_name=ugettext_lazy("Category name"))
|
||||||
"""Name of the category."""
|
"""Name of the category."""
|
||||||
|
|
||||||
@ -613,15 +584,6 @@ class Category(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def get_absolute_url(self, link='update'):
|
|
||||||
if link == 'update':
|
|
||||||
url = reverse('motion_category_update', args=[str(self.pk)])
|
|
||||||
elif link == 'delete':
|
|
||||||
url = reverse('motion_category_delete', args=[str(self.pk)])
|
|
||||||
else:
|
|
||||||
url = super(Category, self).get_absolute_url(link)
|
|
||||||
return url
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['prefix']
|
ordering = ['prefix']
|
||||||
|
|
||||||
@ -699,7 +661,7 @@ class MotionOption(RESTModelMixin, BaseOption):
|
|||||||
|
|
||||||
|
|
||||||
class MotionPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
|
class MotionPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
|
||||||
AbsoluteUrlMixin, BasePoll):
|
BasePoll):
|
||||||
"""The Class to saves the vote result for a motion poll."""
|
"""The Class to saves the vote result for a motion poll."""
|
||||||
|
|
||||||
slide_callback_name = 'motionpoll'
|
slide_callback_name = 'motionpoll'
|
||||||
@ -728,22 +690,6 @@ class MotionPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
|
|||||||
"""Return a string, representing the poll."""
|
"""Return a string, representing the poll."""
|
||||||
return _('Vote %d') % self.poll_number
|
return _('Vote %d') % self.poll_number
|
||||||
|
|
||||||
def get_absolute_url(self, link='update'):
|
|
||||||
"""
|
|
||||||
Return an URL for the poll.
|
|
||||||
|
|
||||||
The keyargument 'link' can be 'update' or 'delete'.
|
|
||||||
"""
|
|
||||||
if link == 'update':
|
|
||||||
url = reverse('motionpoll_update', args=[str(self.motion.pk),
|
|
||||||
str(self.poll_number)])
|
|
||||||
elif link == 'delete':
|
|
||||||
url = reverse('motionpoll_delete', args=[str(self.motion.pk),
|
|
||||||
str(self.poll_number)])
|
|
||||||
else:
|
|
||||||
url = super(MotionPoll, self).get_absolute_url(link)
|
|
||||||
return url
|
|
||||||
|
|
||||||
def set_options(self):
|
def set_options(self):
|
||||||
"""Create the option class for this poll."""
|
"""Create the option class for this poll."""
|
||||||
# TODO: maybe it is possible with .create() to call this without poll=self
|
# TODO: maybe it is possible with .create() to call this without poll=self
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from cgi import escape
|
|
||||||
|
|
||||||
from operator import attrgetter
|
|
||||||
import random
|
import random
|
||||||
|
from cgi import escape
|
||||||
|
from operator import attrgetter
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
@ -16,9 +15,6 @@ from openslides.utils.pdf import stylesheet
|
|||||||
|
|
||||||
from .models import Category
|
from .models import Category
|
||||||
|
|
||||||
# Needed to count the delegates
|
|
||||||
# TODO: find another way to do this.
|
|
||||||
|
|
||||||
|
|
||||||
def motions_to_pdf(pdf, motions):
|
def motions_to_pdf(pdf, motions):
|
||||||
"""
|
"""
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user