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:
Oskar Hahn 2015-06-16 10:37:23 +02:00
parent 1ccd2ab91c
commit fbf7d0e43d
218 changed files with 236 additions and 10486 deletions

View File

@ -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)

View File

@ -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():

View File

@ -1 +0,0 @@
default_app_config = 'openslides.account.apps.AccountAppConfig'

View File

@ -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

View File

@ -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 %}

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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):
""" """

View File

@ -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)

View File

@ -1,4 +1,5 @@
from haystack import indexes from haystack import indexes
from .models import Item from .models import Item

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,4 +0,0 @@
{% extends 'projector.html' %}
{% load i18n %}
{% block title %}{% trans 'List of speakers' %} {{ block.super }}{% endblock %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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'),
) )

View File

@ -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.

View File

@ -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'))

View File

@ -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)

View File

@ -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"))

View File

@ -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'

View File

@ -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

View File

@ -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))

View File

@ -1,4 +1,5 @@
from haystack import indexes from haystack import indexes
from .models import Assignment from .models import Assignment

View File

@ -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):

View File

@ -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

View File

@ -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')

View File

@ -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 %}
&nbsp;
{% 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 %}

View File

@ -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 %}

View File

@ -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>

View File

@ -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 %}

View File

@ -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'),

View File

@ -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,
) )

View File

@ -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)

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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 %}

View File

@ -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))

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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.

View File

@ -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

View File

@ -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>
&copy; Copyright 20112015 | 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>

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 }}&amp;page={{ page.previous_page_number }}">{% endif %}&laquo; Previous{% if page.has_previous %}</a>{% endif %}
|
{% if page.has_next %}<a href="?q={{ query }}&amp;page={{ page.next_page_number }}">{% endif %}Next &raquo;{% if page.has_next %}</a>{% endif %}
</div>
{% endif %}
{% endif %}
{% endblock %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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>

View File

@ -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>&nbsp;
<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>&nbsp;
<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 %}

View File

@ -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 %}

View File

@ -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">&times;</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 %}

View File

@ -1,5 +0,0 @@
{% load i18n %}
<button class="btn btn-primary" type="submit">
{% trans 'Save' %}
</button>

View File

@ -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>

View File

@ -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]

View File

@ -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'),

View File

@ -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)

View File

@ -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)

View File

@ -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',

View File

@ -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)

View File

@ -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')

View File

@ -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'))

View File

@ -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.

View File

@ -1,4 +1,5 @@
from haystack import indexes from haystack import indexes
from .models import Mediafile from .models import Mediafile

View File

@ -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)

View File

@ -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')

View File

@ -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 %}

View File

@ -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 %}
&nbsp;
{% endif %}
</td>
{% endif %}
</tr>
{% empty %}
<tr>
<td colspan="6"><i>{% trans 'No files available.' %}</i></td>
</tr>
{% endfor %}
</table>
{% endblock %}

View File

@ -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>

View File

@ -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 %}

View File

@ -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')
)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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.
"""

View File

@ -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'

View 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

View File

@ -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