New config app. Apps only have to define config vars once. Config pages, forms and so on are created automaticly.

Changes after some reviews are done.

Problematic is still that the JS can not be moved to an extra file because of the template tags in the code.
This commit is contained in:
Norman Jäckel 2013-03-01 17:13:12 +01:00
parent d424176def
commit e1b149cde3
65 changed files with 1265 additions and 877 deletions

View File

@ -10,4 +10,4 @@
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
import openslides.agenda.signals from . import signals

View File

@ -13,7 +13,7 @@
import re import re
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy
from mptt.forms import TreeNodeChoiceField from mptt.forms import TreeNodeChoiceField
from openslides.utils.forms import CssClassMixin from openslides.utils.forms import CssClassMixin
@ -25,14 +25,14 @@ class ItemForm(forms.ModelForm, CssClassMixin):
Form to create of update an item. Form to create of update an item.
""" """
parent = TreeNodeChoiceField( parent = TreeNodeChoiceField(
queryset=Item.objects.all(), label=_("Parent item"), required=False) queryset=Item.objects.all(), label=ugettext_lazy("Parent item"), required=False)
duration = forms.RegexField( duration = forms.RegexField(
regex=re.compile('[0-99]:[0-5][0-9]'), regex=re.compile('[0-99]:[0-5][0-9]'),
error_message=_("Invalid format. Hours from 0 to 99 and minutes from 00 to 59"), error_message=ugettext_lazy("Invalid format. Hours from 0 to 99 and minutes from 00 to 59"),
max_length=5, max_length=5,
required=False, required=False,
label=_("Duration (hh:mm)")) label=ugettext_lazy("Duration (hh:mm)"))
class Meta: class Meta:
model = Item model = Item
@ -46,7 +46,7 @@ def gen_weight_choices():
return zip(*(range(-50, 51), range(-50, 51))) return zip(*(range(-50, 51), range(-50, 51)))
class ItemOrderForm(forms.Form, CssClassMixin): class ItemOrderForm(CssClassMixin, forms.Form):
""" """
Form to change the order of the items. Form to change the order of the items.
""" """
@ -57,10 +57,3 @@ class ItemOrderForm(forms.Form, CssClassMixin):
widget=forms.HiddenInput(attrs={'class': 'menu-mlid'})) widget=forms.HiddenInput(attrs={'class': 'menu-mlid'}))
parent = forms.IntegerField( parent = forms.IntegerField(
widget=forms.HiddenInput(attrs={'class': 'menu-plid'})) widget=forms.HiddenInput(attrs={'class': 'menu-plid'}))
class ConfigForm(CssClassMixin, forms.Form):
agenda_start_event_date_time = forms.CharField(
widget=forms.DateTimeInput(format='%d.%m.%Y %H:%M'),
required=False,
label=_("Begin of event"))

View File

@ -16,7 +16,7 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext
from mptt.models import MPTTModel, TreeForeignKey from mptt.models import MPTTModel, TreeForeignKey
from openslides.config.models import config from openslides.config.api import config
from openslides.projector.projector import SlideMixin from openslides.projector.projector import SlideMixin
from openslides.projector.api import ( from openslides.projector.api import (
register_slidemodel, get_slide_from_sid, register_slidefunc) register_slidemodel, get_slide_from_sid, register_slidefunc)

View File

@ -6,17 +6,44 @@
Signals for the agenda app. Signals for the agenda app.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. :copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.dispatch import receiver from django.dispatch import receiver
from django import forms
from django.utils.translation import ugettext_lazy, ugettext_noop, ugettext as _
from openslides.config.signals import default_config_value from openslides.config.signals import config_signal
from openslides.config.api import ConfigVariable, ConfigPage
@receiver(default_config_value, dispatch_uid="agenda_default_config") # TODO: Reinsert the datepicker scripts in the template
def default_config(sender, key, **kwargs):
"""Return the default config values for the agenda app.""" @receiver(config_signal, dispatch_uid='setup_agenda_config_page')
return { def setup_agenda_config_page(sender, **kwargs):
'agenda_start_event_date_time': ''}.get(key) """
Agenda config variables.
"""
# TODO: Insert validator for the format or use other field carefully.
agenda_start_event_date_time = ConfigVariable(
name='agenda_start_event_date_time',
default_value='',
form_field=forms.CharField(
widget=forms.DateTimeInput(format='%d.%m.%Y %H:%M'),
required=False,
label=ugettext_lazy('Begin of event'),
help_text=_('Input format: DD.MM.YYYY HH:MM')))
extra_stylefiles = ['styles/timepicker.css', 'styles/jquery-ui/jquery-ui.custom.min.css']
extra_javascript = ['javascript/jquery-ui.custom.min.js',
'javascript/jquery-ui-timepicker-addon.min.js',
'javascript/jquery-ui-sliderAccess.min.js']
return ConfigPage(title=ugettext_noop('Agenda'),
url='agenda',
required_permission='config.can_manage',
weight=20,
variables=(agenda_start_event_date_time,),
extra_context={'extra_stylefiles': extra_stylefiles,
'extra_javascript': extra_javascript})

View File

@ -20,8 +20,7 @@ from django.db.models import Model
from django.utils.translation import ugettext as _, ugettext_lazy from django.utils.translation import ugettext as _, ugettext_lazy
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from openslides.config.models import config from openslides.config.api import config
from openslides.agenda.forms import ConfigForm
from openslides.utils.pdf import stylesheet from openslides.utils.pdf import stylesheet
from openslides.utils.views import ( from openslides.utils.views import (
TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView, TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView,
@ -225,29 +224,9 @@ class AgendaPDF(PDFView):
story.append(Paragraph(item.get_title(), stylesheet['Item'])) story.append(Paragraph(item.get_title(), stylesheet['Item']))
class Config(FormView):
"""
Config page for the agenda app.
"""
permission_required = 'config.can_manage_config'
form_class = ConfigForm
template_name = 'agenda/config.html'
success_url_name = 'config_agenda'
def get_initial(self):
return {
'agenda_start_event_date_time': config['agenda_start_event_date_time'],
}
def form_valid(self, form):
config['agenda_start_event_date_time'] = form.cleaned_data['agenda_start_event_date_time']
messages.success(self.request, _('Agenda settings successfully saved.'))
return super(Config, self).form_valid(form)
def register_tab(request): def register_tab(request):
""" """
register the agenda tab. Registers the agenda tab.
""" """
selected = request.path.startswith('/agenda/') selected = request.path.startswith('/agenda/')
return Tab( return Tab(
@ -261,7 +240,7 @@ def register_tab(request):
def get_widgets(request): def get_widgets(request):
""" """
return the agenda widget for the projector-tab. Returns the agenda widget for the projector tab.
""" """
return [Widget( return [Widget(
name='agenda', name='agenda',

View File

@ -0,0 +1,13 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.assignment
~~~~~~~~~~~~~~~~~~~~~
The OpenSlides assignment app.
:copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from . import signals

View File

@ -6,7 +6,7 @@
Forms for the assignment app. Forms for the assignment app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
@ -33,39 +33,3 @@ class AssignmentRunForm(forms.Form, CssClassMixin):
widget=forms.Select(attrs={'class': 'medium-input'}), widget=forms.Select(attrs={'class': 'medium-input'}),
label=_("Nominate a participant"), label=_("Nominate a participant"),
) )
class ConfigForm(forms.Form, CssClassMixin):
assignment_publish_winner_results_only = forms.BooleanField(
required=False,
label=_("Only publish voting results for selected winners "
"(Projector view only)"))
assignment_pdf_ballot_papers_selection = forms.ChoiceField(
widget=forms.Select(),
required=False,
label=_("Number of ballot papers (selection)"),
choices=(
("NUMBER_OF_DELEGATES", _("Number of all delegates")),
("NUMBER_OF_ALL_PARTICIPANTS", _("Number of all participants")),
("CUSTOM_NUMBER", _("Use the following custom number"))))
assignment_pdf_ballot_papers_number = forms.IntegerField(
widget=forms.TextInput(attrs={'class': 'small-input'}),
required=False,
min_value=1,
label=_("Custom number of ballot papers"))
assignment_pdf_title = forms.CharField(
widget=forms.TextInput(),
required=False,
label=_("Title for PDF document (all elections)"))
assignment_pdf_preamble = forms.CharField(
widget=forms.Textarea(),
required=False,
label=_("Preamble text for PDF document (all elections)"))
assignment_poll_vote_values = forms.ChoiceField(
widget=forms.Select(),
required=False,
label=_("Election method"),
choices=(
("auto", _("Automatic assign of method.")),
("votes", _("Always one option per candidate.")),
("yesnoabstain", _("Always Yes-No-Abstain per candidate."))))

View File

@ -6,18 +6,16 @@
Models for the assignment app. Models for the assignment app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import models from django.db import models
from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _, ugettext_noop # TODO Change this in the code
from django.utils.translation import ugettext_lazy as _, ugettext_noop
from openslides.utils.person import PersonField from openslides.utils.person import PersonField
from openslides.config.models import config from openslides.config.api import config
from openslides.config.signals import default_config_value
from openslides.projector.api import register_slidemodel from openslides.projector.api import register_slidemodel
from openslides.projector.projector import SlideMixin from openslides.projector.projector import SlideMixin
from openslides.poll.models import ( from openslides.poll.models import (
@ -308,15 +306,3 @@ class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin):
def __unicode__(self): def __unicode__(self):
return _("Ballot %d") % self.get_ballot() return _("Ballot %d") % self.get_ballot()
@receiver(default_config_value, dispatch_uid="assignment_default_config")
def default_config(sender, key, **kwargs):
return {
'assignment_publish_winner_results_only': False,
'assignment_pdf_ballot_papers_selection': 'CUSTOM_NUMBER',
'assignment_pdf_ballot_papers_number': '8',
'assignment_pdf_title': _('Elections'),
'assignment_pdf_preamble': '',
'assignment_poll_vote_values': 'auto',
}.get(key)

View File

@ -0,0 +1,87 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.assignment.signals
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Signals for the assignment app.
:copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.dispatch import receiver
from django import forms
from django.utils.translation import ugettext_lazy, ugettext_noop, ugettext as _
from openslides.config.signals import config_signal
from openslides.config.api import ConfigVariable, ConfigPage
@receiver(config_signal, dispatch_uid='setup_assignment_config_page')
def setup_assignment_config_page(sender, **kwargs):
"""
Assignment config variables.
"""
assignment_publish_winner_results_only = ConfigVariable(
name='assignment_publish_winner_results_only',
default_value=False,
form_field=forms.BooleanField(
required=False,
label=_('Only publish voting results for selected winners '
'(Projector view only)')))
assignment_pdf_ballot_papers_selection = ConfigVariable(
name='assignment_pdf_ballot_papers_selection',
default_value='CUSTOM_NUMBER',
form_field=forms.ChoiceField(
widget=forms.Select(),
required=False,
label=_('Number of ballot papers (selection)'),
choices=(
('NUMBER_OF_DELEGATES', _('Number of all delegates')),
('NUMBER_OF_ALL_PARTICIPANTS', _('Number of all participants')),
('CUSTOM_NUMBER', _('Use the following custom number')))))
assignment_pdf_ballot_papers_number = ConfigVariable(
name='assignment_pdf_ballot_papers_number',
default_value=8,
form_field=forms.IntegerField(
widget=forms.TextInput(attrs={'class': 'small-input'}),
required=False,
min_value=1,
label=_('Custom number of ballot papers')))
assignment_pdf_title = ConfigVariable(
name='assignment_pdf_title',
default_value=_('Elections'),
form_field=forms.CharField(
widget=forms.TextInput(),
required=False,
label=_('Title for PDF document (all elections)')))
assignment_pdf_preamble = ConfigVariable(
name='assignment_pdf_preamble',
default_value='',
form_field=forms.CharField(
widget=forms.Textarea(),
required=False,
label=_('Preamble text for PDF document (all elections)')))
assignment_poll_vote_values = ConfigVariable(
name='assignment_poll_vote_values',
default_value='auto',
form_field=forms.ChoiceField(
widget=forms.Select(),
required=False,
label=_('Election method'),
choices=(
('auto', _('Automatic assign of method.')),
('votes', _('Always one option per candidate.')),
('yesnoabstain', _('Always Yes-No-Abstain per candidate.')))))
return ConfigPage(title=ugettext_noop('Elections'),
url='assignment',
required_permission='config.can_manage',
weight=40,
variables=(assignment_publish_winner_results_only,
assignment_pdf_ballot_papers_selection,
assignment_pdf_ballot_papers_number,
assignment_pdf_title,
assignment_pdf_preamble,
assignment_poll_vote_values))

View File

@ -1,23 +0,0 @@
{% extends "config/base_config.html" %}
{% load i18n %}
{% block title %}{{ block.super }} {% trans "Election settings" %}{% endblock %}
{% block content %}
<h1>
{% trans "Configuration" %}
<small>{% trans "Elections" %}</small>
{% block config_submenu %}{{ block.super }}{% endblock %}
</h1>
<form action="" method="post">{% csrf_token %}
{% include "form.html" %}
<p>
{% include "formbuttons_save.html" %}
<a href="{% url 'config_assignment' %}" class="btn">
{% trans 'Cancel' %}
</a>
</p>
<small>* {% trans "required" %}</small>
</form>
{% endblock %}

View File

@ -6,7 +6,7 @@
URL list for the assignment app. URL list for the assignment app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """

View File

@ -6,7 +6,7 @@
Views for the assignment app. Views for the assignment app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
@ -30,14 +30,13 @@ from openslides.utils.utils import (
template, permission_required, gen_confirm_form, del_confirm_form, ajax_request) template, permission_required, gen_confirm_form, del_confirm_form, ajax_request)
from openslides.utils.views import FormView, DeleteView, PDFView, RedirectView from openslides.utils.views import FormView, DeleteView, PDFView, RedirectView
from openslides.utils.person import get_person from openslides.utils.person import get_person
from openslides.config.models import config from openslides.config.api import config
from openslides.participant.models import User from openslides.participant.models import User
from openslides.projector.projector import Widget from openslides.projector.projector import Widget
from openslides.poll.views import PollFormView from openslides.poll.views import PollFormView
from openslides.agenda.models import Item from openslides.agenda.models import Item
from openslides.assignment.models import Assignment, AssignmentPoll from openslides.assignment.models import Assignment, AssignmentPoll
from openslides.assignment.forms import ( from openslides.assignment.forms import AssignmentForm, AssignmentRunForm
AssignmentForm, AssignmentRunForm, ConfigForm)
@permission_required('assignment.can_see_assignment') @permission_required('assignment.can_see_assignment')
@ -633,45 +632,6 @@ class AssignmentPollPDF(PDFView):
story.append(t) story.append(t)
class Config(FormView):
permission_required = 'config.can_manage_config'
form_class = ConfigForm
template_name = 'assignment/config.html'
success_url_name = 'config_assignment'
def get_initial(self):
return {
'assignment_publish_winner_results_only':
config['assignment_publish_winner_results_only'],
'assignment_pdf_ballot_papers_selection':
config['assignment_pdf_ballot_papers_selection'],
'assignment_pdf_ballot_papers_number':
config['assignment_pdf_ballot_papers_number'],
'assignment_pdf_title': config['assignment_pdf_title'],
'assignment_pdf_preamble': config['assignment_pdf_preamble'],
'assignment_poll_vote_values':
config['assignment_poll_vote_values']}
def form_valid(self, form):
if form.cleaned_data['assignment_publish_winner_results_only']:
config['assignment_publish_winner_results_only'] = True
else:
config['assignment_publish_winner_results_only'] = False
config['assignment_pdf_ballot_papers_selection'] = \
form.cleaned_data['assignment_pdf_ballot_papers_selection']
config['assignment_pdf_ballot_papers_number'] = \
form.cleaned_data['assignment_pdf_ballot_papers_number']
config['assignment_pdf_title'] = \
form.cleaned_data['assignment_pdf_title']
config['assignment_pdf_preamble'] = \
form.cleaned_data['assignment_pdf_preamble']
config['assignment_poll_vote_values'] = \
form.cleaned_data['assignment_poll_vote_values']
messages.success(
self.request, _('Election settings successfully saved.'))
return super(Config, self).form_valid(form)
def register_tab(request): def register_tab(request):
selected = request.path.startswith('/assignment/') selected = request.path.startswith('/assignment/')
return Tab( return Tab(

147
openslides/config/api.py Normal file
View File

@ -0,0 +1,147 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.config.api
~~~~~~~~~~~~~~~~~~~~~
Api for the config app.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from .models import ConfigStore
from .exceptions import ConfigError, ConfigNotFound
from .signals import config_signal
class ConfigHandler(object):
"""
An simple object class to wrap the config variables. It is a container
object. To get a config variable use x = config[...], to set it use
config[...] = x.
"""
def __getitem__(self, key):
try:
return self._cache[key]
except KeyError:
raise ConfigNotFound('The config variable %s was not found.' % key)
except AttributeError:
self.setup_cache()
return self[key]
def __setitem__(self, key, value):
updated_rows = ConfigStore.objects.filter(key=key).update(value=value)
if not updated_rows:
ConfigStore.objects.create(key=key, value=value)
self._cache[key] = value
def setup_cache(self):
"""
Loads all config variables from the database and by sending a
signal to get the default into the cache.
"""
self._cache = {}
for receiver, config_page in config_signal.send(sender=self):
for config_variable in config_page.variables:
if config_variable.name in self._cache:
raise ConfigError('Too many values for config variable %s found.' % config_variable.name)
self._cache[config_variable.name] = config_variable.default_value
for config_object in ConfigStore.objects.all():
self._cache[config_object.key] = config_object.value
def __contains__(self, key):
try:
config[key]
except ConfigNotFound:
return False
else:
return True
config = ConfigHandler()
"""
Final entry point to get an set config variables. To get a config variable
use x = config[...], to set it use config[...] = x.
"""
class ConfigBasePage(object):
"""
An abstract base class for simple and grouped config pages. The
attributes title and url are required. The attribute weight is used
for the order of the links in the submenu of the views. The attribute
extra_context can be used to insert extra css and js files into the
template.
"""
def __init__(self, title, url, required_permission=None, weight=0, extra_context={}):
self.title = title
self.url = url
self.required_permission = required_permission
self.weight = weight
self.extra_context = extra_context
def is_shown(self):
"""
Returns True if at least one variable of the page has a form field.
"""
for variable in self.variables:
if variable.form_field is not None:
return True
else:
return False
class ConfigGroupedPage(ConfigBasePage):
"""
A simple object class for a grouped config page. Developers have to
set the groups attribute (tuple). The config variables are available
via the variables attribute. The page is shown as view in the config
tab, if there is at least one variable with a form field.
"""
def __init__(self, groups, **kwargs):
self.groups = groups
super(ConfigGroupedPage, self).__init__(**kwargs)
@property
def variables(self):
for group in self.groups:
for variable in group.variables:
yield variable
class ConfigPage(ConfigBasePage):
"""
A simple object class for a ungrouped config page. Developers have
to set the variables (tuple) directly. The page is shown as view in
the config tab, if there is at least one variable with a form field.
"""
def __init__(self, variables, **kwargs):
self.variables = variables
super(ConfigPage, self).__init__(**kwargs)
class ConfigGroup(object):
"""
A simple object class representing a group of variables (tuple) with
a special title.
"""
def __init__(self, title, variables):
self.title = title
self.variables = variables
def get_field_names(self):
return [variable.name for variable in self.variables if variable.form_field is not None]
class ConfigVariable(object):
"""
A simple object class to wrap new config variables. The keyword
arguments 'name' and 'default_value' are required. The keyword
argument 'form_field' has to be set, if the variable should appear
on the ConfigView.
"""
def __init__(self, name, default_value, form_field=None):
self.name = name
self.default_value = default_value
self.form_field = form_field

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.config.exceptions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Exceptions for the config app.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from openslides.utils.exceptions import OpenSlidesError
class ConfigError(OpenSlidesError):
pass
class ConfigNotFound(ConfigError):
pass

View File

@ -1,67 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.config.forms
~~~~~~~~~~~~~~~~~~~~~~~
Forms for the config app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django import forms
from django.utils.translation import ugettext_lazy as _
from openslides.utils.forms import CssClassMixin
class GeneralConfigForm(forms.Form, CssClassMixin):
event_name = forms.CharField(
widget=forms.TextInput(),
label=_("Event name"),
max_length=30,
)
event_description = forms.CharField(
widget=forms.TextInput(),
label=_("Short description of event"),
required=False,
max_length=100,
)
event_date = forms.CharField(
widget=forms.TextInput(),
label=_("Event date"),
required=False,
)
event_location = forms.CharField(
widget=forms.TextInput(),
label=_("Event location"),
required=False,
)
event_organizer = forms.CharField(
widget=forms.TextInput(),
label=_("Event organizer"),
required=False,
)
system_enable_anonymous = forms.BooleanField(
label=_("Allow access for anonymous guest users"),
required=False,
)
welcome_title = forms.CharField(
widget=forms.TextInput(),
label=_("Title"),
required=False,
)
welcome_text = forms.CharField(
widget=forms.Textarea(),
label=_("Welcome text"),
required=False,
)

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.config.middleware
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Middleware for the config app.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from openslides.config.api import config
class ConfigCacheMiddleware(object):
"""
Middleware to refresh the config cache before processing any view.
"""
def process_request(self, request):
config.setup_cache()

View File

@ -6,125 +6,26 @@
Models for the config app. Models for the config app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import models from django.db import models
from django.dispatch import receiver from django.utils.translation import ugettext_noop
from django.utils.importlib import import_module
from django.utils.translation import ugettext_lazy as _, ugettext_noop
from openslides.utils.jsonfield import JSONField from openslides.utils.jsonfield import JSONField
from openslides.utils.signals import template_manipulation
from openslides.config.signals import default_config_value
class ConfigStore(models.Model): class ConfigStore(models.Model):
""" """
Stores the config values. A model class to store all config variables in the database.
""" """
key = models.CharField(max_length=100, primary_key=True)
value = JSONField()
def __unicode__(self): key = models.CharField(max_length=255, primary_key=True)
return self.key """A string, the key of the config variable."""
value = JSONField()
"""The value of the config variable. """
class Meta: class Meta:
verbose_name = 'config' permissions = (('can_manage', ugettext_noop('Can manage configuration')),)
permissions = (
('can_manage_config', ugettext_noop("Can manage configuration")),
)
class Config(object):
"""
Access the config values via config[...]
"""
def __getitem__(self, key):
try:
return ConfigStore.objects.get(key=key).value
except ConfigStore.DoesNotExist:
pass
for receiver, value in default_config_value.send(sender='config',
key=key):
if value is not None:
return value
if settings.DEBUG:
print "No default value for: %s" % key
return None
def __setitem__(self, key, value):
try:
c = ConfigStore.objects.get(pk=key)
except ConfigStore.DoesNotExist:
c = ConfigStore(pk=key)
c.value = value
c.save()
def __contains__(self, item):
return ConfigStore.objects.filter(key=item).exists()
config = Config()
@receiver(default_config_value, dispatch_uid="config_default_config")
def default_config(sender, key, **kwargs):
"""
Global default values.
"""
return {
'event_name': 'OpenSlides',
'event_description':
_('Presentation and assembly system'),
'event_date': '',
'event_location': '',
'event_organizer': '',
'presentation': '',
'welcome_title': _('Welcome to OpenSlides'),
'welcome_text': _('[Place for your welcome text.]'),
'system_enable_anonymous': False,
}.get(key)
@receiver(template_manipulation, dispatch_uid="config_submenu")
def set_submenu(sender, request, context, **kwargs):
"""
Submenu for the config tab.
"""
if not request.path.startswith('/config/'):
return None
menu_links = [
(reverse('config_general'), _('General'),
request.path == reverse('config_general')),
]
for app in settings.INSTALLED_APPS:
try:
mod = import_module(app)
views = mod.views
views.Config
except (ImportError, AttributeError):
continue
appname = mod.__name__.split('.')[-1]
selected = reverse('config_%s' % appname) == request.path
try:
title = mod.NAME
except AttributeError:
title = appname.title()
menu_links.append(
(reverse('config_%s' % appname), _(title), selected)
)
menu_links.append((
reverse('config_version'), _('Version'),
request.path == reverse('config_version')))
context.update({
'menu_links': menu_links})

View File

@ -4,12 +4,14 @@
openslides.config.signals openslides.config.signals
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~
Defines Signals for the config. Signals for the config app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.dispatch import Signal from django.dispatch import Signal
default_config_value = Signal(providing_args=['key'])
config_signal = Signal(providing_args=[])
"""Signal to get all config tabs from all apps."""

View File

@ -1,15 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% block config_submenu %}
<small class="pull-right">
<div class="btn-toolbar">
<div class="btn-group">
{% for menu_link in menu_links %}
<a href="{{ menu_link.0 }}" class="btn btn-mini {% if menu_link.2 %}active{% endif %}">{{ menu_link.1 }}</a>
{% endfor %}
</div>
</div>
</small>
{% endblock %}

View File

@ -1,19 +1,12 @@
{% extends "config/base_config.html" %} {% extends 'base.html' %}
{% load i18n %} {% load i18n %}
{% get_current_language as LANGUAGE_CODE %}
{% get_current_language as LANGUAGE_CODE %}
{% load staticfiles %} {% load staticfiles %}
{% block header %}
<link type="text/css" rel="stylesheet" media="all" href="{% static 'styles/jquery-ui/jquery-ui.custom.min.css' %}" />
<link type="text/css" rel="stylesheet" media="all" href="{% static 'styles/timepicker.css' %}" />
{% endblock %}
{% block javascript %} {% block javascript %}
<script type="text/javascript" src="{% static 'javascript/jquery-ui.custom.min.js' %}"></script> {{ block.super }}
<script type="text/javascript" src="{% static 'javascript/jquery-ui-timepicker-addon.min.js' %}"></script>
<script type="text/javascript" src="{% static 'javascript/jquery-ui-sliderAccess.min.js' %}"></script>
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {
$.datepicker.regional['{{ LANGUAGE_CODE }}'] = { $.datepicker.regional['{{ LANGUAGE_CODE }}'] = {
@ -63,23 +56,47 @@
</script> </script>
{% endblock %} {% endblock %}
{% block title %}{{ block.super }} {% trans "Agenda settings" %}{% endblock %}
{% block content %} {% block content %}
<h1> <h1>
{% trans "Configuration" %} {% trans 'Configuration' %}
<small>{% trans "Agenda" %}</small> <small>{% trans active_config_page.title %}</small>
{% block config_submenu %}{{ block.super }}{% endblock %} <small class="pull-right">
</h1> <div class="btn-toolbar">
<form action="" method="post">{% csrf_token %} <div class="btn-group">
{% include "form.html" %} {% for config_page_dict in config_pages_list %}
<a href="/config/{{ config_page_dict.config_page.url }}/" class="btn btn-mini {% if config_page_dict.active %}active{% endif %}">{{ config_page_dict.config_page.title }}</a>
{% endfor %}
<a href="{% url 'core_version' %}" class="btn btn-mini">{% trans 'Version' %}</a>
</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> <p>
{% include "formbuttons_save.html" %} {% include 'formbuttons_save.html' %}
<a href="{% url 'config_agenda' %}" class="btn"> <a href="/config/{{ active_config_page.url }}/" class="btn">{% trans 'Cancel' %}</a>
{% trans 'Cancel' %}
</a>
</p> </p>
<small>* {% trans "required" %}</small> <small>* {% trans 'required' %}</small>
</form> </form>
{% endblock %} {% endblock %}

View File

@ -1,75 +0,0 @@
{% extends "config/base_config.html" %}
{% load i18n %}
{% block title %}{{ block.super }} {% trans "General settings" %}{% endblock %}
{% block content %}
<h1>
{% trans "Configuration" %}
<small>{% trans "General" %}</small>
{% block config_submenu %}{{ block.super }}{% endblock %}
</h1>
<form action="" method="post">{% csrf_token %}
<fieldset>
<legend>{% trans "Event" %}</legend>
{% for field in form %}
{% if "id_event" in field.label_tag %}
<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>
<p></p>
<fieldset>
<legend>{% trans "Welcome Widget" %}</legend>
{% for field in form %}
{% if "id_welcome" in field.label_tag %}
<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>
<p></p>
<fieldset style="width: 410px;">
<legend>{% trans "System" %}</legend>
{% for field in form %}
{% if "id_system" in field.label_tag %}
<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>
<p>
{% include "formbuttons_save.html" %}
<a href='{% url 'config_general' %}' class="btn">
{% trans 'Cancel' %}
</a>
</p>
<small>* {% trans "required" %}</small>
</form>
{% endblock %}

View File

@ -1,15 +0,0 @@
{% extends "config/base_config.html" %}
{% load i18n %}
{% block title %}{{ block.super }} {% trans "Version" %}{% endblock %}
{% block content %}
<h1>{% trans "Version" %}
{% block config_submenu %}{{ block.super }}{% endblock %}
</h1>
{% for version in versions %}
<p>{{ version.0 }} {% trans "Version" %}: {{ version.1 }}</p>
{% endfor %}
{% endblock %}

View File

@ -4,42 +4,28 @@
openslides.config.urls openslides.config.urls
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
URL list for the config app. Url patterns for the config app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.conf import settings
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
from django.utils.importlib import import_module
from openslides.config.views import GeneralConfig, VersionConfig from openslides.utils.views import RedirectView
from .signals import config_signal
from .views import ConfigView
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^general/$', url(r'^$',
GeneralConfig.as_view(), RedirectView.as_view(url_name='config_general'),
name='config_general', name='config_first_config_page',
),
url(r'^version/$',
VersionConfig.as_view(),
name='config_version',
), ),
) )
for app in settings.INSTALLED_APPS: for receiver, config_page in config_signal.send(sender='config_urls'):
try: if config_page.is_shown():
mod = import_module(app + '.views') urlpatterns += patterns('', url(r'^%s/$' % config_page.url,
except ImportError: ConfigView.as_view(config_page=config_page),
continue name='config_%s' % config_page.url))
appname = mod.__name__.split('.')[-2]
try:
urlpatterns += patterns('', url(
r'^%s/$' % appname,
mod.Config.as_view(),
name='config_%s' % appname,
))
except AttributeError:
continue

View File

@ -6,107 +6,126 @@
Views for the config app. Views for the config app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.conf import settings from django import forms
from django.contrib import messages
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.importlib import import_module from django.contrib import messages
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from openslides import get_version, get_git_commit_id, RELEASE from openslides.utils.views import FormView
from openslides.utils.template import Tab from openslides.utils.template import Tab
from openslides.utils.views import FormView, TemplateView from .api import config
from .forms import GeneralConfigForm from .signals import config_signal
from .models import config
class GeneralConfig(FormView): class ConfigView(FormView):
""" """
Gereral config values. The view for a config page.
""" """
permission_required = 'config.can_manage_config' template_name = 'config/config_form.html'
form_class = GeneralConfigForm config_page = None
template_name = 'config/general.html' form_class = forms.Form
success_url_name = 'config_general'
def has_permission(self, *args, **kwargs):
"""
Ensures that only users with tab's permission can see this view.
"""
self.permission_required = self.config_page.required_permission
return super(ConfigView, self).has_permission(*args, **kwargs)
def get_form(self, *args):
"""
Gets the form for the view. Includes all form fields given by the
tab's config objects.
"""
form = super(ConfigView, self).get_form(*args)
for name, field in self.generate_form_fields_from_config_page():
form.fields[name] = field
return form
def generate_form_fields_from_config_page(self):
"""
Generates the fields for the get_form function.
"""
for variable in self.config_page.variables:
if variable.form_field is not None:
yield (variable.name, variable.form_field)
def get_initial(self): def get_initial(self):
return {
'event_name': config['event_name'],
'event_description': config['event_description'],
'event_date': config['event_date'],
'event_location': config['event_location'],
'event_organizer': config['event_organizer'],
'welcome_title': config['welcome_title'],
'welcome_text': config['welcome_text'],
'system_enable_anonymous': config['system_enable_anonymous'],
}
def form_valid(self, form):
# event
config['event_name'] = form.cleaned_data['event_name']
config['event_description'] = form.cleaned_data['event_description']
config['event_date'] = form.cleaned_data['event_date']
config['event_location'] = form.cleaned_data['event_location']
config['event_organizer'] = form.cleaned_data['event_organizer']
# welcome widget
config['welcome_title'] = form.cleaned_data['welcome_title']
config['welcome_text'] = form.cleaned_data['welcome_text']
# system
if form.cleaned_data['system_enable_anonymous']:
config['system_enable_anonymous'] = True
else:
config['system_enable_anonymous'] = False
messages.success(
self.request, _('General settings successfully saved.'))
return super(GeneralConfig, self).form_valid(form)
class VersionConfig(TemplateView):
""" """
Show version infos. Returns a dictonary with the actual values of the config variables
as intial value for the form.
""" """
permission_required = 'config.can_manage_config' initial = super(ConfigView, self).get_initial()
template_name = 'config/version.html' for variable in self.config_page.variables:
initial.update({variable.name: config[variable.name]})
return initial
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(VersionConfig, self).get_context_data(**kwargs) """
Adds to the context the active config tab, a list of dictionaries
containing all config tabs each with a flag which is true if the
tab is the active one and adds a flag whether the config page has
groups. Adds also extra_stylefiles and extra_javascript.
"""
context = super(ConfigView, self).get_context_data(**kwargs)
# OpenSlides version. During development the git commit id is added. context['active_config_page'] = self.config_page
openslides_version_string = get_version()
if not RELEASE: config_pages_list = []
openslides_version_string += ' Commit: %s' % get_git_commit_id() for receiver, config_page in config_signal.send(sender=self):
context['versions'] = [('OpenSlides', openslides_version_string)] if config_page.is_shown():
config_pages_list.append({
'config_page': config_page,
'active': self.request.path == reverse('config_%s' % config_page.url)})
context['config_pages_list'] = sorted(config_pages_list, key=lambda config_page_dict: config_page_dict['config_page'].weight)
if hasattr(self.config_page, 'groups'):
context['groups'] = self.config_page.groups
else:
context['groups'] = None
if 'extra_stylefiles' in self.config_page.extra_context:
if 'extra_stylefiles' in context:
context['extra_stylefiles'].extend(self.config_page.extra_context['extra_stylefiles'])
else:
context['extra_stylefiles'] = self.config_page.extra_context['extra_stylefiles']
if 'extra_javascript' in self.config_page.extra_context:
if 'extra_javascript' in context:
context['extra_javascript'].extend(self.config_page.extra_context['extra_javascript'])
else:
context['extra_javascript'] = self.config_page.extra_context['extra_javascript']
# Version of plugins.
for plugin in settings.INSTALLED_PLUGINS:
try:
mod = import_module(plugin)
plugin_version = get_version(mod.VERSION)
except (ImportError, AttributeError, AssertionError):
continue
try:
plugin_name = mod.NAME
except AttributeError:
plugin_name = mod.__name__.split('.')[0]
context['versions'].append((plugin_name, plugin_version))
return context return context
def get_success_url(self):
"""
Returns the success url when changes are saved. Here it is the same
url as the tab.
"""
return reverse('config_%s' % self.config_page.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_page.title))
return super(ConfigView, self).form_valid(form)
def register_tab(request): def register_tab(request):
""" """
Register the config tab. Registers the tab for this app in the main menu.
""" """
selected = request.path.startswith('/config/')
return Tab( return Tab(
title=_('Configuration'), title=_('Configuration'),
app='config', app='config',
url=reverse('config_general'), url=reverse('config_first_config_page'),
permission=request.user.has_perm('config.can_manage_config'), permission=request.user.has_perm('config.can_manage'),
selected=selected, selected=request.path.startswith('/config/'))
)

View File

@ -0,0 +1,13 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.core
~~~~~~~~~~~~~~~
The core app.
:copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from . import signals

View File

@ -2,15 +2,110 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
openslides.core.signals openslides.core.signals
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
Core Signals. Signals for the core app.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. :copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.dispatch import Signal from django.dispatch import Signal, receiver
from django import forms
from django.utils.translation import ugettext_lazy, ugettext_noop, ugettext as _
from openslides.config.signals import config_signal
from openslides.config.api import ConfigVariable, ConfigGroup, ConfigGroupedPage
post_database_setup = Signal() post_database_setup = Signal()
@receiver(config_signal, dispatch_uid='setup_general_config_page')
def setup_general_config_page(sender, **kwargs):
"""
General config variables for OpenSlides. They are grouped in 'Event',
'Welcome Widget' and 'System'.
"""
event_name = ConfigVariable(
name='event_name',
default_value='OpenSlides',
form_field=forms.CharField(
widget=forms.TextInput(),
label=ugettext_lazy('Event name'),
max_length=30))
event_description = ConfigVariable(
name='event_description',
default_value=_('Presentation and assembly system'),
form_field=forms.CharField(
widget=forms.TextInput(),
label=ugettext_lazy('Short description of event'),
required=False,
max_length=100))
event_date = ConfigVariable(
name='event_date',
default_value='',
form_field=forms.CharField(
widget=forms.TextInput(),
label=ugettext_lazy('Event date'),
required=False))
event_location = ConfigVariable(
name='event_location',
default_value='',
form_field=forms.CharField(
widget=forms.TextInput(),
label=ugettext_lazy('Event location'),
required=False))
event_organizer = ConfigVariable(
name='event_organizer',
default_value='',
form_field=forms.CharField(
widget=forms.TextInput(),
label=ugettext_lazy('Event organizer'),
required=False))
welcome_title = ConfigVariable(
name='welcome_title',
default_value=_('Welcome to OpenSlides'),
form_field=forms.CharField(
widget=forms.TextInput(),
label=ugettext_lazy('Title'),
required=False))
welcome_text = ConfigVariable(
name='welcome_text',
default_value=_('[Place for your welcome text.]'),
form_field=forms.CharField(
widget=forms.Textarea(),
label=ugettext_lazy('Welcome text'),
required=False))
system_enable_anonymous = ConfigVariable(
name='system_enable_anonymous',
default_value=False,
form_field=forms.BooleanField(
label=ugettext_lazy('Allow access for anonymous guest users'),
required=False))
group_event = ConfigGroup(
title=ugettext_lazy('Event'),
variables=(event_name, event_description, event_date, event_location, event_organizer))
group_welcome_widget = ConfigGroup(
title=ugettext_lazy('Welcome Widget'),
variables=(welcome_title, welcome_text))
group_system = ConfigGroup(
title=ugettext_lazy('System'),
variables=(system_enable_anonymous,))
return ConfigGroupedPage(
title=ugettext_noop('General'),
url='general',
required_permission='config.can_manage',
weight=10,
groups=(group_event, group_welcome_widget, group_system))

View File

@ -0,0 +1,12 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{{ block.super }} {% trans 'Version' %}{% endblock %}
{% block content %}
<h1>{% trans 'Versions' %}</h1>
{% for version in versions %}
<p>{{ version.0 }} {% trans "Version" %}: {{ version.1 }}</p>
{% endfor %}
{% endblock %}

29
openslides/core/urls.py Normal file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.core.urls
~~~~~~~~~~~~~~~~~~~~
Url patterns for the core app.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.conf.urls import patterns, url
from openslides.utils.views import RedirectView
from .views import VersionView
urlpatterns = patterns('',
# Redirect to dashboard URL
url(r'^$',
RedirectView.as_view(url='projector/dashboard/'),
name='home',),
url(r'^version/$',
VersionView.as_view(),
name='core_version',),
)

51
openslides/core/views.py Normal file
View File

@ -0,0 +1,51 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.core.views
~~~~~~~~~~~~~~~~~~~~~
Views for the core app.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.conf import settings
from django.utils.importlib import import_module
from openslides import get_version, get_git_commit_id, RELEASE
from openslides.utils.views import TemplateView
class VersionView(TemplateView):
"""
Show version infos.
"""
template_name = 'core/version.html'
def get_context_data(self, **kwargs):
"""
Adds version strings to the context.
"""
context = super(VersionView, self).get_context_data(**kwargs)
# OpenSlides version. During development the git commit id is added.
openslides_version_string = get_version()
if not RELEASE:
openslides_version_string += ' Commit: %s' % get_git_commit_id()
context['versions'] = [('OpenSlides', openslides_version_string)]
# Versions of plugins.
for plugin in settings.INSTALLED_PLUGINS:
try:
mod = import_module(plugin)
plugin_version = get_version(mod.VERSION)
except (ImportError, AttributeError, AssertionError):
continue
try:
plugin_name = mod.NAME
except AttributeError:
plugin_name = mod.__name__.split('.')[0]
context['versions'].append((plugin_name, plugin_version))
return context

View File

@ -89,6 +89,7 @@ MIDDLEWARE_CLASSES = (
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'openslides.participant.middleware.AuthenticationMiddleware', 'openslides.participant.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'openslides.config.middleware.ConfigCacheMiddleware',
) )
ROOT_URLCONF = 'openslides.urls' ROOT_URLCONF = 'openslides.urls'
@ -110,6 +111,7 @@ INSTALLED_APPS = (
'mptt', 'mptt',
'openslides.utils', 'openslides.utils',
'openslides.poll', 'openslides.poll',
'openslides.core',
'openslides.projector', 'openslides.projector',
'openslides.agenda', 'openslides.agenda',
'openslides.motion', 'openslides.motion',

View File

@ -284,12 +284,10 @@ def run_syncdb():
def set_system_url(url): def set_system_url(url):
# can't be imported in global scope as it already requires # can't be imported in global scope as it already requires
# the settings module during import # the settings module during import
from openslides.config.models import config from openslides.config.api import config
key = "participant_pdf_system_url" if config['participant_pdf_system_url'] == 'http://example.com:8000':
if key in config: config['participant_pdf_system_url'] = url
return
config[key] = url
def create_or_reset_admin_user(): def create_or_reset_admin_user():

View File

@ -7,9 +7,8 @@
The OpenSlides motion app appends the functionality to OpenSlides to The OpenSlides motion app appends the functionality to OpenSlides to
manage motions. manage motions.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. :copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
import openslides.motion.signals from . import signals, slides
import openslides.motion.slides

View File

@ -6,7 +6,7 @@
Exceptions for the motion app. Exceptions for the motion app.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. :copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """

View File

@ -15,7 +15,7 @@ from django.utils.translation import ugettext as _
from openslides.utils.forms import CssClassMixin from openslides.utils.forms import CssClassMixin
from openslides.utils.person import PersonFormField, MultiplePersonFormField from openslides.utils.person import PersonFormField, MultiplePersonFormField
from .models import Motion, Workflow, Category from .models import Motion, Category
class BaseMotionForm(forms.ModelForm, CssClassMixin): class BaseMotionForm(forms.ModelForm, CssClassMixin):
@ -100,55 +100,3 @@ class MotionIdentifierMixin(forms.ModelForm):
"""Mixin to let the user choose the identifier for the motion.""" """Mixin to let the user choose the identifier for the motion."""
identifier = forms.CharField(required=False) identifier = forms.CharField(required=False)
class ConfigForm(CssClassMixin, forms.Form):
"""Form for the configuration tab of OpenSlides."""
motion_min_supporters = forms.IntegerField(
widget=forms.TextInput(attrs={'class': 'small-input'}),
label=_("Number of (minimum) required supporters for a motion"),
initial=4, min_value=0, max_value=8,
help_text=_("Choose 0 to disable the supporting system"),
)
motion_preamble = forms.CharField(
widget=forms.TextInput(),
required=False,
label=_("Motion preamble")
)
motion_pdf_ballot_papers_selection = forms.ChoiceField(
widget=forms.Select(),
required=False,
label=_("Number of ballot papers (selection)"),
choices=[
("NUMBER_OF_DELEGATES", _("Number of all delegates")),
("NUMBER_OF_ALL_PARTICIPANTS", _("Number of all participants")),
("CUSTOM_NUMBER", _("Use the following custom number")),
]
)
motion_pdf_ballot_papers_number = forms.IntegerField(
widget=forms.TextInput(attrs={'class': 'small-input'}),
required=False,
min_value=1,
label=_("Custom number of ballot papers")
)
motion_pdf_title = forms.CharField(
widget=forms.TextInput(),
required=False,
label=_("Title for PDF document (all motions)")
)
motion_pdf_preamble = forms.CharField(
widget=forms.Textarea(),
required=False,
label=_("Preamble text for PDF document (all motions)")
)
motion_allow_disable_versioning = forms.BooleanField(
label=_("Allow to disable versioning"),
required=False,
)
motion_workflow = forms.ChoiceField(
widget=forms.Select(),
label=_("Workflow of new motions"),
required=True,
choices=[(workflow.pk, workflow.name) for workflow in Workflow.objects.all()])

View File

@ -9,7 +9,7 @@
To use a motion object, you only have to import the Motion class. Any To use a motion object, you only have to import the Motion class. Any
functionality can be reached from a motion object. functionality can be reached from a motion object.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. :copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
@ -24,8 +24,7 @@ from django.utils.translation import pgettext
from django.utils.translation import ugettext_lazy, ugettext_noop, ugettext as _ from django.utils.translation import ugettext_lazy, ugettext_noop, ugettext as _
from openslides.utils.person import PersonField from openslides.utils.person import PersonField
from openslides.config.models import config from openslides.config.api import config
from openslides.config.signals import default_config_value
from openslides.poll.models import ( from openslides.poll.models import (
BaseOption, BasePoll, CountVotesCast, CountInvalid, BaseVote) BaseOption, BasePoll, CountVotesCast, CountInvalid, BaseVote)
from openslides.participant.models import User from openslides.participant.models import User

View File

@ -6,7 +6,7 @@
Functions to generate the PDFs for the motion app. Functions to generate the PDFs for the motion app.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. :copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
@ -16,9 +16,8 @@ from reportlab.platypus import (
SimpleDocTemplate, PageBreak, Paragraph, Spacer, Table, TableStyle) SimpleDocTemplate, PageBreak, Paragraph, Spacer, Table, TableStyle)
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from openslides.config.models import config from openslides.config.api import config
from openslides.utils.pdf import stylesheet from openslides.utils.pdf import stylesheet
from .models import Motion from .models import Motion

View File

@ -11,26 +11,110 @@
""" """
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import ugettext as _, ugettext_noop from django import forms
from django.utils.translation import ugettext as _, ugettext_lazy, ugettext_noop
from openslides.config.signals import default_config_value from openslides.config.signals import config_signal
from openslides.config.api import ConfigVariable, ConfigPage
from openslides.core.signals import post_database_setup from openslides.core.signals import post_database_setup
from .models import Workflow, State from .models import Workflow, State
@receiver(default_config_value, dispatch_uid="motion_default_config") @receiver(config_signal, dispatch_uid='setup_motion_config_page')
def default_config(sender, key, **kwargs): def setup_motion_config_page(sender, **kwargs):
"""Return the default config values for the motion app.""" """
return { Motion config variables.
'motion_min_supporters': 0, """
'motion_preamble': _('The assembly may decide,'), motion_min_supporters = ConfigVariable(
'motion_pdf_ballot_papers_selection': 'CUSTOM_NUMBER', name='motion_min_supporters',
'motion_pdf_ballot_papers_number': '8', default_value=0,
'motion_pdf_title': _('Motions'), form_field=forms.IntegerField(
'motion_pdf_preamble': '', widget=forms.TextInput(attrs={'class': 'small-input'}),
'motion_allow_disable_versioning': False, label=_('Number of (minimum) required supporters for a motion'),
'motion_workflow': 1}.get(key) initial=4,
min_value=0,
max_value=8,
help_text=_('Choose 0 to disable the supporting system')))
motion_preamble = ConfigVariable(
name='motion_preamble',
default_value=_('The assembly may decide,'),
form_field=forms.CharField(
widget=forms.TextInput(),
required=False,
label=_('Motion preamble')))
motion_pdf_ballot_papers_selection = ConfigVariable(
name='motion_pdf_ballot_papers_selection',
default_value='CUSTOM_NUMBER',
form_field=forms.ChoiceField(
widget=forms.Select(),
required=False,
label=_('Number of ballot papers (selection)'),
choices=[
('NUMBER_OF_DELEGATES', _('Number of all delegates')),
('NUMBER_OF_ALL_PARTICIPANTS', _('Number of all participants')),
('CUSTOM_NUMBER', _("Use the following custom number"))]))
motion_pdf_ballot_papers_number = ConfigVariable(
name='motion_pdf_ballot_papers_number',
default_value=8,
form_field=forms.IntegerField(
widget=forms.TextInput(attrs={'class': 'small-input'}),
required=False,
min_value=1,
label=_('Custom number of ballot papers')))
motion_pdf_title = ConfigVariable(
name='motion_pdf_title',
default_value=_('Motions'),
form_field=forms.CharField(
widget=forms.TextInput(),
required=False,
label=_('Title for PDF document (all motions)')))
motion_pdf_preamble = ConfigVariable(
name='motion_pdf_preamble',
default_value='',
form_field=forms.CharField(
widget=forms.Textarea(),
required=False,
label=_('Preamble text for PDF document (all motions)')))
motion_allow_disable_versioning = ConfigVariable(
name='motion_allow_disable_versioning',
default_value=False,
form_field=forms.BooleanField(
label=_('Allow to disable versioning'),
required=False))
motion_workflow = ConfigVariable(
name='motion_workflow',
default_value=1,
form_field=forms.ChoiceField(
widget=forms.Select(),
label=_('Workflow of new motions'),
required=True,
choices=[(workflow.pk, workflow.name) for workflow in Workflow.objects.all()]))
motion_identifier = ConfigVariable(
name='motion_identifier',
default_value='manually',
form_field=forms.ChoiceField(
widget=forms.Select(),
required=False,
label=_('Identifier'),
choices=[
('manually', _('Set it manually')),
('per_category', _('Numbered per category')),
('serially_numbered', _('Serially numbered'))]))
return ConfigPage(title=ugettext_noop('Motion'),
url='motion',
required_permission='config.can_manage',
weight=30,
variables=(motion_min_supporters,
motion_preamble,
motion_pdf_ballot_papers_selection,
motion_pdf_ballot_papers_number,
motion_pdf_title,
motion_pdf_preamble,
motion_allow_disable_versioning,
motion_workflow,
motion_identifier))
@receiver(post_database_setup, dispatch_uid='motion_create_builtin_workflows') @receiver(post_database_setup, dispatch_uid='motion_create_builtin_workflows')

View File

@ -6,7 +6,7 @@
Defines the slides for the motion app. Defines the slides for the motion app.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. :copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """

View File

@ -1,23 +0,0 @@
{% extends "config/base_config.html" %}
{% load i18n %}
{% block title %}{{ block.super }} {% trans "Motion settings" %}{% endblock %}
{% block content %}
<h1>
{% trans "Configuration" %}
<small>{% trans "Motions" %}</small>
{% block config_submenu %}{{ block.super }}{% endblock %}
</h1>
<form action="" method="post">{% csrf_token %}
{% include "form.html" %}
<p>
{% include "formbuttons_save.html" %}
<a href="{% url 'config_motion' %}" class="btn">
{% trans 'Cancel' %}
</a>
</p>
<small>* {% trans "required" %}</small>
</form>
{% endblock %}

View File

@ -6,7 +6,7 @@
Defines the URL patterns for the motion app. Defines the URL patterns for the motion app.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. :copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """

View File

@ -29,21 +29,17 @@ from openslides.utils.utils import html_strong, htmldiff
from openslides.poll.views import PollFormView from openslides.poll.views import PollFormView
from openslides.projector.api import get_active_slide from openslides.projector.api import get_active_slide
from openslides.projector.projector import Widget, SLIDE from openslides.projector.projector import Widget, SLIDE
from openslides.config.models import config from openslides.config.api import config
from openslides.agenda.models import Item from openslides.agenda.models import Item
from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll, from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll,
MotionVersion, State, WorkflowError, Category) MotionVersion, State, WorkflowError, Category)
from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin, from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin,
MotionDisableVersioningMixin, ConfigForm, MotionCategoryMixin, MotionDisableVersioningMixin, MotionCategoryMixin,
MotionIdentifierMixin) MotionIdentifierMixin)
from .pdf import motions_to_pdf, motion_to_pdf from .pdf import motions_to_pdf, motion_to_pdf
# TODO: into the config-tab
config['motion_identifier'] = ('manually', 'per_category', 'serially_numbered')[2]
class MotionListView(ListView): class MotionListView(ListView):
"""View, to list all motions.""" """View, to list all motions."""
permission_required = 'motion.can_see_motion' permission_required = 'motion.can_see_motion'
@ -590,49 +586,15 @@ class CategoryDeleteView(DeleteView):
category_delete = CategoryDeleteView.as_view() category_delete = CategoryDeleteView.as_view()
class Config(FormView):
"""The View for the config tab."""
permission_required = 'config.can_manage_config'
form_class = ConfigForm
template_name = 'motion/config.html'
success_url_name = 'config_motion'
def get_initial(self):
return {
'motion_min_supporters': config['motion_min_supporters'],
'motion_preamble': config['motion_preamble'],
'motion_pdf_ballot_papers_selection': config['motion_pdf_ballot_papers_selection'],
'motion_pdf_ballot_papers_number': config['motion_pdf_ballot_papers_number'],
'motion_pdf_title': config['motion_pdf_title'],
'motion_pdf_preamble': config['motion_pdf_preamble'],
'motion_allow_disable_versioning': config['motion_allow_disable_versioning'],
'motion_workflow': config['motion_workflow'],
}
def form_valid(self, form):
config['motion_min_supporters'] = form.cleaned_data['motion_min_supporters']
config['motion_preamble'] = form.cleaned_data['motion_preamble']
config['motion_pdf_ballot_papers_selection'] = form.cleaned_data['motion_pdf_ballot_papers_selection']
config['motion_pdf_ballot_papers_number'] = form.cleaned_data['motion_pdf_ballot_papers_number']
config['motion_pdf_title'] = form.cleaned_data['motion_pdf_title']
config['motion_pdf_preamble'] = form.cleaned_data['motion_pdf_preamble']
config['motion_allow_disable_versioning'] = form.cleaned_data['motion_allow_disable_versioning']
config['motion_workflow'] = form.cleaned_data['motion_workflow']
messages.success(self.request, _('Motion settings successfully saved.'))
return super(Config, self).form_valid(form)
def register_tab(request): def register_tab(request):
"""Return the motion tab.""" """Return the motion tab."""
# TODO: Find a bether way to set the selected var. # TODO: Find a better way to set the selected var.
selected = request.path.startswith('/motion/')
return Tab( return Tab(
title=_('Motions'), title=_('Motions'),
app='motion', app='motion',
url=reverse('motion_list'), url=reverse('motion_list'),
permission=request.user.has_perm('motion.can_see_motion'), permission=request.user.has_perm('motion.can_see_motion'),
selected=selected, selected=request.path.startswith('/motion/'))
)
def get_widgets(request): def get_widgets(request):

View File

@ -6,13 +6,13 @@
The OpenSlides participant app. The OpenSlides participant app.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. :copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.utils.translation import ugettext_noop from django.utils.translation import ugettext_noop
import openslides.participant.signals from . import signals
NAME = ugettext_noop('Participant') NAME = ugettext_noop('Participant')

View File

@ -6,13 +6,13 @@
Forms for the participant app. Forms for the participant app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django import forms from django import forms
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _ # TODO: Change this in the code
from django.conf import settings from django.conf import settings
from openslides.utils.forms import ( from openslides.utils.forms import (
@ -112,20 +112,3 @@ class UsersettingsForm(forms.ModelForm, CssClassMixin):
class UserImportForm(forms.Form, CssClassMixin): class UserImportForm(forms.Form, CssClassMixin):
csvfile = forms.FileField(widget=forms.FileInput(attrs={'size': '50'}), csvfile = forms.FileField(widget=forms.FileInput(attrs={'size': '50'}),
label=_("CSV File")) label=_("CSV File"))
class ConfigForm(forms.Form, CssClassMixin):
participant_pdf_system_url = forms.CharField(
widget=forms.TextInput(),
required=False,
label=_("System URL"),
help_text=_("Printed in PDF of first time passwords only."))
participant_pdf_welcometext = forms.CharField(
widget=forms.Textarea(),
required=False,
label=_("Welcome text"),
help_text=_("Printed in PDF of first time passwords only."))
participant_sort_users_by_first_name = forms.BooleanField(
required=False,
label=_("Sort participants by first name"),
help_text=_("Disable for sorting by last name"))

View File

@ -6,7 +6,7 @@
Models for the participant app. Models for the participant app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
@ -14,14 +14,11 @@ from django.contrib.auth.models import User as DjangoUser, Group as DjangoGroup
from django.db import models from django.db import models
from django.db.models import signals from django.db.models import signals
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.utils.translation import ugettext_lazy as _, ugettext_noop # TODO: Change this in the code
from openslides.utils.person import PersonMixin, Person from openslides.utils.person import PersonMixin, Person
from openslides.utils.person.signals import receive_persons from openslides.utils.person.signals import receive_persons
from openslides.config.api import config
from openslides.config.models import config
from openslides.config.signals import default_config_value
from openslides.projector.api import register_slidemodel from openslides.projector.api import register_slidemodel
from openslides.projector.projector import SlideMixin from openslides.projector.projector import SlideMixin
@ -226,19 +223,6 @@ def receive_persons(sender, **kwargs):
id_filter=kwargs['id_filter']) id_filter=kwargs['id_filter'])
@receiver(default_config_value, dispatch_uid="participant_default_config")
def default_config(sender, key, **kwargs):
"""
Default values for the participant app.
"""
# TODO: Rename config-vars
return {
'participant_pdf_system_url': 'http://example.com:8000',
'participant_pdf_welcometext': _('Welcome to OpenSlides!'),
'participant_sort_users_by_first_name': False,
}.get(key)
@receiver(signals.post_save, sender=DjangoUser) @receiver(signals.post_save, sender=DjangoUser)
def djangouser_post_save(sender, instance, signal, *args, **kwargs): def djangouser_post_save(sender, instance, signal, *args, **kwargs):
try: try:

View File

@ -2,24 +2,66 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
openslides.participant.signals openslides.participant.signals
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Signals for the participant app. Signals for the participant app.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. :copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import ugettext_noop from django import forms
from django.utils.translation import ugettext_noop, ugettext_lazy, ugettext as _
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from openslides.core.signals import post_database_setup from openslides.core.signals import post_database_setup
from openslides.config.signals import config_signal
from openslides.config.api import ConfigVariable, ConfigPage
from .models import Group from .models import Group
@receiver(config_signal, dispatch_uid='setup_participant_config_page')
def setup_participant_config_page(sender, **kwargs):
"""
Participant config variables.
"""
# TODO: Rename config-vars
participant_pdf_system_url = ConfigVariable(
name='participant_pdf_system_url',
default_value='http://example.com:8000',
form_field=forms.CharField(
widget=forms.TextInput(),
required=False,
label=_('System URL'),
help_text=_('Printed in PDF of first time passwords only.')))
participant_pdf_welcometext = ConfigVariable(
name='participant_pdf_welcometext',
default_value=_('Welcome to OpenSlides!'),
form_field=forms.CharField(
widget=forms.Textarea(),
required=False,
label=_('Welcome text'),
help_text=_('Printed in PDF of first time passwords only.')))
participant_sort_users_by_first_name = ConfigVariable(
name='participant_sort_users_by_first_name',
default_value=False,
form_field=forms.BooleanField(
required=False,
label=_('Sort participants by first name'),
help_text=_('Disable for sorting by last name')))
return ConfigPage(title=ugettext_noop('Participant'),
url='participant',
required_permission='config.can_manage',
weight=50,
variables=(participant_pdf_system_url,
participant_pdf_welcometext,
participant_sort_users_by_first_name))
@receiver(post_database_setup, dispatch_uid='participant_create_builtin_groups') @receiver(post_database_setup, dispatch_uid='participant_create_builtin_groups')
def create_builtin_groups(sender, **kwargs): def create_builtin_groups(sender, **kwargs):
""" """
@ -69,7 +111,7 @@ def create_builtin_groups(sender, **kwargs):
perm_15a = Permission.objects.get(content_type=ct_mediafile, codename='can_manage') perm_15a = Permission.objects.get(content_type=ct_mediafile, codename='can_manage')
ct_config = ContentType.objects.get(app_label='config', model='configstore') ct_config = ContentType.objects.get(app_label='config', model='configstore')
perm_16 = Permission.objects.get(content_type=ct_config, codename='can_manage_config') perm_16 = Permission.objects.get(content_type=ct_config, codename='can_manage')
group_staff = Group.objects.create(name=ugettext_noop('Staff')) group_staff = Group.objects.create(name=ugettext_noop('Staff'))
group_staff.permissions.add(perm_7, perm_9, perm_10, perm_11, perm_12, perm_13, perm_14, perm_15, perm_15a, perm_16) group_staff.permissions.add(perm_7, perm_9, perm_10, perm_11, perm_12, perm_13, perm_14, perm_15, perm_15a, perm_16)

View File

@ -1,23 +0,0 @@
{% extends "config/base_config.html" %}
{% load i18n %}
{% block title %}{{ block.super }} {% trans "Participant settings" %}{% endblock %}
{% block content %}
<h1>
{% trans "Configuration" %}
<small>{% trans "Participants" %}</small>
{% block config_submenu %}{{ block.super }}{% endblock %}
</h1>
<form action="" method="post">{% csrf_token %}
{% include "form.html" %}
<p>
{% include "formbuttons_save.html" %}
<a href="{% url 'config_participant' %}" class="btn">
{% trans 'Cancel' %}
</a>
</p>
<small>* {% trans "required" %}</small>
</form>
{% endblock %}

View File

@ -6,7 +6,7 @@
URL list for the participant app. URL list for the participant app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """

View File

@ -6,7 +6,7 @@
Views for the participant app. Views for the participant app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
@ -41,14 +41,14 @@ from openslides.utils.utils import (
from openslides.utils.views import ( from openslides.utils.views import (
FormView, PDFView, CreateView, UpdateView, DeleteView, PermissionMixin, FormView, PDFView, CreateView, UpdateView, DeleteView, PermissionMixin,
RedirectView, SingleObjectMixin, ListView, QuestionMixin, DetailView) RedirectView, SingleObjectMixin, ListView, QuestionMixin, DetailView)
from openslides.config.models import config from openslides.config.api import config
from openslides.projector.projector import Widget from openslides.projector.projector import Widget
from openslides.motion.models import Motion from openslides.motion.models import Motion
from openslides.assignment.models import Assignment from openslides.assignment.models import Assignment
from openslides.participant.api import gen_username, gen_password, import_users from openslides.participant.api import gen_username, gen_password, import_users
from openslides.participant.forms import ( from openslides.participant.forms import (
UserCreateForm, UserUpdateForm, UsersettingsForm, UserCreateForm, UserUpdateForm, UsersettingsForm,
UserImportForm, GroupForm, ConfigForm) UserImportForm, GroupForm)
from openslides.participant.models import User, Group from openslides.participant.models import User, Group
@ -388,34 +388,6 @@ class GroupDeleteView(DeleteView):
super(GroupDeleteView, self).pre_redirect(request, *args, **kwargs) super(GroupDeleteView, self).pre_redirect(request, *args, **kwargs)
class Config(FormView):
"""
Config page for the participant app.
"""
permission_required = 'config.can_manage_config'
form_class = ConfigForm
template_name = 'participant/config.html'
success_url_name = 'config_participant'
def get_initial(self):
return {
'participant_pdf_system_url': config['participant_pdf_system_url'],
'participant_pdf_welcometext': config['participant_pdf_welcometext'],
'participant_sort_users_by_first_name': config['participant_sort_users_by_first_name']}
def form_valid(self, form):
config['participant_pdf_system_url'] = (
form.cleaned_data['participant_pdf_system_url'])
config['participant_pdf_welcometext'] = (
form.cleaned_data['participant_pdf_welcometext'])
config['participant_sort_users_by_first_name'] = (
form.cleaned_data['participant_sort_users_by_first_name'])
messages.success(
self.request,
_('Participants settings successfully saved.'))
return super(Config, self).form_valid(form)
def login(request): def login(request):
extra_content = {} extra_content = {}
try: try:
@ -485,7 +457,7 @@ def user_settings_password(request):
def register_tab(request): def register_tab(request):
""" """
Register the participant tab. Registers the participant tab.
""" """
selected = request.path.startswith('/participant/') selected = request.path.startswith('/participant/')
return Tab( return Tab(

View File

@ -0,0 +1,13 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.projector
~~~~~~~~~~~~~~~~~~~~
The projector app.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from . import signals

View File

@ -15,7 +15,7 @@ from django.core.cache import cache
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from django.utils.importlib import import_module from django.utils.importlib import import_module
from openslides.config.models import config from openslides.config.api import config
from openslides.projector.projector import SLIDE, Slide from openslides.projector.projector import SLIDE, Slide

View File

@ -6,7 +6,7 @@
Models for the projector app. Models for the projector app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
@ -14,8 +14,6 @@ from django.db import models
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.utils.translation import ugettext_lazy as _, ugettext_noop
from openslides.config.signals import default_config_value
from openslides.projector.api import register_slidemodel from openslides.projector.api import register_slidemodel
from openslides.projector.projector import SlideMixin from openslides.projector.projector import SlideMixin
@ -70,16 +68,3 @@ class ProjectorOverlay(models.Model):
if self.sid: if self.sid:
return "%s on %s" % (self.def_name, self.sid) return "%s on %s" % (self.def_name, self.sid)
return self.def_name return self.def_name
@receiver(default_config_value, dispatch_uid="projector_default_config")
def default_config(sender, key, **kwargs):
return {
'projector_message': '',
'countdown_time': 60,
'countdown_start_stamp': 0,
'countdown_pause_stamp': 0,
'countdown_state': 'inactive',
'bigger': 100,
'up': 0,
}.get(key)

View File

@ -15,10 +15,10 @@ from time import time
from django.dispatch import receiver from django.dispatch import receiver
from django.template.loader import render_to_string from django.template.loader import render_to_string
from openslides.config.models import config from openslides.config.api import config
from openslides.projector.signals import projector_overlays from openslides.projector.signals import projector_overlays
SLIDE = {} SLIDE = {}

View File

@ -4,12 +4,68 @@
openslides.projector.signals openslides.projector.signals
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Defines Signals for the projector. Signals for the projector app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.dispatch import Signal from django.dispatch import Signal, receiver
from django import forms
from django.utils.translation import ugettext_lazy, ugettext as _
from openslides.config.signals import config_signal
from openslides.config.api import ConfigVariable, ConfigPage
projector_overlays = Signal(providing_args=['register', 'call']) projector_overlays = Signal(providing_args=['register', 'call'])
@receiver(config_signal, dispatch_uid='setup_projector_config_variables')
def setup_projector_config_variables(sender, **kwargs):
"""
Projector config variables for OpenSlides. They are not shown on a
config page.
"""
presentation = ConfigVariable(
name='presentation',
default_value='')
presentation_argument = ConfigVariable(
name='presentation_argument',
default_value=None)
projector_message = ConfigVariable(
name='projector_message',
default_value='')
countdown_time = ConfigVariable(
name='countdown_time',
default_value=60)
countdown_start_stamp = ConfigVariable(
name='countdown_start_stamp',
default_value=0)
countdown_pause_stamp = ConfigVariable(
name='countdown_pause_stamp',
default_value=0)
countdown_state = ConfigVariable(
name='countdown_state',
default_value='inactive')
bigger = ConfigVariable(
name='bigger',
default_value=100)
up = ConfigVariable(
name='up',
default_value=0)
return ConfigPage(title='No title here',
url='bar',
required_permission=None,
variables=(presentation, presentation_argument, projector_message, countdown_time,
countdown_start_stamp, countdown_pause_stamp, countdown_state, bigger, up))

View File

@ -26,7 +26,7 @@ from django.utils.translation import ugettext_lazy as _
from openslides.utils.template import render_block_to_string, Tab from openslides.utils.template import render_block_to_string, Tab
from openslides.utils.views import ( from openslides.utils.views import (
TemplateView, RedirectView, CreateView, UpdateView, DeleteView, AjaxMixin) TemplateView, RedirectView, CreateView, UpdateView, DeleteView, AjaxMixin)
from openslides.config.models import config from openslides.config.api import config
from .api import ( from .api import (
get_active_slide, set_active_slide, projector_message_set, get_active_slide, set_active_slide, projector_message_set,
projector_message_delete, get_slide_from_sid, get_all_widgets, projector_message_delete, get_slide_from_sid, get_all_widgets,
@ -224,8 +224,8 @@ class ProjectorEdit(RedirectView):
if config['up'] < 0: if config['up'] < 0:
config['up'] = int(config['up']) + 10 config['up'] = int(config['up']) + 10
elif direction == 'clean': elif direction == 'clean':
config['up'] = 0 config['up'] = 0 # TODO: Use default value from the signal instead of fix value here
config['bigger'] = 100 config['bigger'] = 100 # TODO: Use default value from the signal instead of fix value here
class CountdownEdit(RedirectView): class CountdownEdit(RedirectView):

View File

@ -98,7 +98,7 @@
<hr /> <hr />
<footer> <footer>
<small> <small>
&copy; Copyright 2011-2013 | Powered by <a href="http://openslides.org" target="_blank">OpenSlides</a> &copy; Copyright 20112013 | Powered by <a href="http://openslides.org" target="_blank">OpenSlides</a>
</small> </small>
</footer> </footer>
</div><!--/content--> </div><!--/content-->
@ -112,6 +112,9 @@
<script src="{% static 'javascript/bootstrap.min.js' %}" type="text/javascript"></script> <script src="{% static 'javascript/bootstrap.min.js' %}" type="text/javascript"></script>
<script src="{% static 'javascript/utils.js' %}" type="text/javascript"></script> <script src="{% static 'javascript/utils.js' %}" type="text/javascript"></script>
<script src="{% url 'django.views.i18n.javascript_catalog' %}" type="text/javascript"></script> <script src="{% url 'django.views.i18n.javascript_catalog' %}" type="text/javascript"></script>
{% for javascript in extra_javascript %}
<script src="{% static javascript %}" type="text/javascript"></script>
{% endfor %}
{% block javascript %}{% endblock %} {% block javascript %}{% endblock %}
</body> </body>
</html> </html>

View File

@ -14,15 +14,10 @@ from django.conf import settings
from django.conf.urls import patterns, url, include from django.conf.urls import patterns, url, include
from django.utils.importlib import import_module from django.utils.importlib import import_module
from openslides.utils.views import RedirectView
handler500 = 'openslides.utils.views.server_error' handler500 = 'openslides.utils.views.server_error'
urlpatterns = patterns('', urlpatterns = patterns('',
# Redirect to dashboard URL
url(r'^$', RedirectView.as_view(url='projector/dashboard'), name='home',),
(r'^agenda/', include('openslides.agenda.urls')), (r'^agenda/', include('openslides.agenda.urls')),
(r'^motion/', include('openslides.motion.urls')), (r'^motion/', include('openslides.motion.urls')),
(r'^assignment/', include('openslides.assignment.urls')), (r'^assignment/', include('openslides.assignment.urls')),
@ -69,3 +64,7 @@ urlpatterns += patterns('',
name='password_change', name='password_change',
), ),
) )
urlpatterns += patterns('',
(r'^', include('openslides.core.urls')),
)

View File

@ -11,7 +11,7 @@
""" """
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from openslides.config.models import config from openslides.config.api import config
class AnonymousAuth(object): class AnonymousAuth(object):

View File

@ -23,7 +23,7 @@ from django.conf import settings
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 openslides.config.models import config from openslides.config.api import config
# register new truetype fonts # register new truetype fonts

View File

@ -11,7 +11,8 @@
""" """
from django import template from django import template
from openslides.config.models import config from openslides.config.api import config
register = template.Library() register = template.Library()

View File

@ -14,14 +14,16 @@
from django.test import TestCase as _TestCase from django.test import TestCase as _TestCase
from openslides.core.signals import post_database_setup from openslides.core.signals import post_database_setup
from openslides.config.api import config
class TestCase(_TestCase): class TestCase(_TestCase):
""" """
Overwrites Django's TestCase class to call the post_database_setup Overwrites Django's TestCase class to call the post_database_setup
signal after the preparation of every test. signal after the preparation of every test. Also refreshs the config cache.
""" """
def _pre_setup(self, *args, **kwargs): def _pre_setup(self, *args, **kwargs):
return_value = super(TestCase, self)._pre_setup(*args, **kwargs) return_value = super(TestCase, self)._pre_setup(*args, **kwargs)
post_database_setup.send(sender=self) post_database_setup.send(sender=self)
config.setup_cache()
return return_value return return_value

View File

@ -391,6 +391,9 @@ def send_register_tab(sender, request, context, **kwargs):
Inserts the tab objects and also the extra_stylefiles to the context. Inserts the tab objects and also the extra_stylefiles to the context.
""" """
tabs = [] tabs = []
if 'extra_stylefiles' in context:
extra_stylefiles = context['extra_stylefiles']
else:
extra_stylefiles = [] extra_stylefiles = []
for app in settings.INSTALLED_APPS: for app in settings.INSTALLED_APPS:
try: try:
@ -401,8 +404,6 @@ def send_register_tab(sender, request, context, **kwargs):
extra_stylefiles.append(tab.stylefile) extra_stylefiles.append(tab.stylefile)
except (ImportError, AttributeError): except (ImportError, AttributeError):
continue continue
context.update({ context.update({
'tabs': tabs, 'tabs': tabs,
'extra_stylefiles': extra_stylefiles, 'extra_stylefiles': extra_stylefiles})
})

View File

@ -191,3 +191,18 @@ class ViewTest(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.refreshItems() self.refreshItems()
self.assertEqual(self.item1.title, 'newitem1') self.assertEqual(self.item1.title, 'newitem1')
class ConfigTest(TestCase):
def setUp(self):
self.admin = User.objects.create(username='config_test_admin')
self.admin.reset_password('default')
self.admin.is_superuser = True
self.admin.save()
self.client = Client()
self.client.login(username='config_test_admin', password='default')
def test_config_page_css_javascript(self):
response = self.client.get('/config/agenda/')
self.assertContains(response, 'timepicker.css', status_code=200)
self.assertContains(response, 'jquery-ui-timepicker-addon.min.js', status_code=200)

0
tests/config/__init__.py Normal file
View File

259
tests/config/test_config.py Normal file
View File

@ -0,0 +1,259 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Tests for openslides.config
~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.test.client import Client
from django.dispatch import receiver
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Permission
from django import forms
from openslides.utils.test import TestCase
from openslides.participant.models import User
from openslides.config.api import config, ConfigGroupedPage, ConfigPage, ConfigGroup, ConfigVariable
from openslides.config.signals import config_signal
from openslides.config.exceptions import ConfigError, ConfigNotFound
class HandleConfigTest(TestCase):
def get_config_var(self, key):
return config[key]
def test_get_config_default_value(self):
self.assertEqual(config['string_var'], 'default_string_rien4ooCZieng6ah')
self.assertTrue(config['bool_var'])
self.assertEqual(config['integer_var'], 3)
self.assertEqual(config['choices_var'], 1)
self.assertEqual(config['none_config_var'], None)
self.assertRaisesMessage(expected_exception=ConfigNotFound,
expected_message='The config variable unknown_config_var was not found.',
callable_obj=self.get_config_var, key='unknown_config_var')
def test_get_multiple_config_var_error(self):
config_signal.connect(set_simple_config_page_multiple_vars, dispatch_uid='set_simple_config_page_multiple_vars_for_testing')
self.assertRaisesMessage(expected_exception=ConfigError,
expected_message='Too many values for config variable multiple_config_var found.',
callable_obj=config.setup_cache)
config_signal.disconnect(set_simple_config_page_multiple_vars, dispatch_uid='set_simple_config_page_multiple_vars_for_testing')
def test_database_queries(self):
self.assertNumQueries(0, self.get_config_var, key='string_var')
def test_setup_config_var(self):
self.assertRaises(TypeError, ConfigVariable)
self.assertRaises(TypeError, ConfigVariable, name='foo')
self.assertRaises(TypeError, ConfigVariable, default_value='foo')
def test_change_config_value(self):
self.assertEqual(config['string_var'], 'default_string_rien4ooCZieng6ah')
config['string_var'] = 'other_special_unique_string dauTex9eAiy7jeen'
self.assertEqual(config['string_var'], 'other_special_unique_string dauTex9eAiy7jeen')
class ConfigFormTest(TestCase):
def setUp(self):
# Setup the permission
ct = ContentType.objects.get(app_label='config', model='configstore')
perm = Permission.objects.get(content_type=ct, codename='can_manage')
# Setup two users
self.manager = User.objects.create(username='config_test_manager')
self.manager.reset_password('default')
self.manager.user_permissions.add(perm)
self.normal_user = User.objects.create(username='config_test_normal_user')
self.normal_user.reset_password('default')
# Login
self.client_manager = Client()
self.client_manager.login(username='config_test_manager', password='default')
self.client_normal_user = Client()
self.client_normal_user.login(username='config_test_normal_user', password='default')
def test_get_config_form_overview(self):
response = self.client_manager.get('/config/')
self.assertRedirects(response=response, expected_url='/config/general/',
status_code=302, target_status_code=200)
bad_client = Client()
response = bad_client.get('/config/', follow=True)
self.assertRedirects(response=response, expected_url='/login/?next=/config/general/',
status_code=302, target_status_code=200)
def test_get_config_form_testgroupedpage1_manager_client(self):
response = self.client_manager.get('/config/testgroupedpage1/')
self.assertContains(response=response, text='default_string_rien4ooCZieng6ah', status_code=200)
self.assertTemplateUsed(response=response, template_name='base.html')
self.assertTemplateUsed(response=response, template_name='config/config_form.html')
self.assertTemplateNotUsed(response=response, template_name='form.html')
self.assertTemplateUsed(response=response, template_name='formbuttons_save.html')
def test_get_config_form_testgroupedpage1_grouping(self):
response = self.client_manager.get('/config/testgroupedpage1/')
self.assertContains(response=response, text='Group 1 aiYeix2mCieQuae3', status_code=200)
self.assertContains(response=response, text='Group 2 Toongai7ahyahy7B', status_code=200)
def test_get_config_form_testgroupedpage1_other_clients(self):
response = self.client_normal_user.get('/config/testgroupedpage1/')
self.assertEqual(response.status_code, 403)
bad_client = Client()
response = bad_client.get('/config/testgroupedpage1/')
self.assertRedirects(response=response, expected_url='/login/?next=/config/testgroupedpage1/',
status_code=302, target_status_code=200)
def test_get_config_form_testsimplepage1_other_clients(self):
response = self.client_normal_user.get('/config/testsimplepage1/')
self.assertNotContains(response=response, text='BaeB0ahcMae3feem', status_code=200)
self.assertTemplateUsed(response=response, template_name='base.html')
self.assertTemplateUsed(response=response, template_name='config/config_form.html')
self.assertTemplateUsed(response=response, template_name='form.html')
self.assertTemplateUsed(response=response, template_name='formbuttons_save.html')
bad_client = Client()
response = bad_client.get('/config/testsimplepage1/')
self.assertEqual(response.status_code, 200)
self.assertNotContains(response=response, text='BaeB0ahcMae3feem', status_code=200)
def test_get_config_form_testgroupedpage1_initial(self):
config['string_var'] = 'something unique AChie6eeiDie3Ieciy1bah4I'
response = self.client_manager.get('/config/testgroupedpage1/')
self.assertContains(response=response, text='AChie6eeiDie3Ieciy1bah4I', status_code=200)
def test_get_config_form_testgroupedpage1_choices(self):
response = self.client_manager.get('/config/testgroupedpage1/')
self.assertContains(response=response, text='Ughoch4ocoche6Ee', status_code=200)
self.assertContains(response=response, text='Vahnoh5yalohv5Eb', status_code=200)
def test_post_config_form_configtest1(self):
response = self.client_manager.post(
'/config/testgroupedpage1/',
{'string_var': 'other_special_unique_string faiPaid4utie6eeL',
'integer_var': 3,
'choices_var': 2})
self.assertRedirects(response=response, expected_url='/config/testgroupedpage1/',
status_code=302, target_status_code=200)
self.assertEqual(config['string_var'], 'other_special_unique_string faiPaid4utie6eeL')
self.assertFalse(config['bool_var'])
self.assertEqual(config['integer_var'], 3)
self.assertEqual(config['choices_var'], 2)
def test_post_config_form_error(self):
response = self.client_manager.post(
'/config/testgroupedpage1/',
{'integer_var': 'bad_string_value'})
self.assertContains(response=response, text='errorlist', status_code=200)
def test_disabled_config_page(self):
response = self.client_manager.get('/config/testsimplepage3/')
self.assertEqual(response.status_code, 404)
response = self.client_manager.get('/config/testgroupedpage1/')
self.assertNotContains(response=response, text='Ho5iengaoon5Hoht', status_code=200)
class ConfigWeightTest(TestCase):
def setUp(self):
# Setup the permission
ct = ContentType.objects.get(app_label='config', model='configstore')
perm = Permission.objects.get(content_type=ct, codename='can_manage')
# Setup two users
self.manager = User.objects.create(username='config_test_manager')
self.manager.reset_password('default')
self.manager.user_permissions.add(perm)
# Login
self.client_manager = Client()
self.client_manager.login(username='config_test_manager', password='default')
def test_order_of_config_pages_abstract(self):
config_page_dict = {}
for receiver, config_page in config_signal.send(sender=self):
config_page_dict[receiver.__name__] = config_page
self.assertGreater(config_page_dict['set_grouped_config_page'].weight, config_page_dict['set_simple_config_page'].weight)
def test_order_of_config_pages_on_view(self):
response = self.client_manager.get('/config/testgroupedpage1/')
import re
m1 = re.search('<a href="/config/testgroupedpage1/" class="btn btn-mini active">Config vars for testing 1</a>', response.content)
m2 = re.search('<a href="/config/testsimplepage1/" class="btn btn-mini ">Config vars for testing 2</a>', response.content)
self.assertGreater(m1.start(), m2.start())
@receiver(config_signal, dispatch_uid='set_grouped_config_page_for_testing')
def set_grouped_config_page(sender, **kwargs):
"""
Sets a grouped config page which can be reached under the url
'/config/testgroupedpage1/'. There are some variables, one variable
with a string as default value, one with a boolean as default value,
one with an integer as default value, one with choices and one
hidden variable. These variables are grouped in two groups.
"""
string_var = ConfigVariable(
name='string_var',
default_value='default_string_rien4ooCZieng6ah',
form_field=forms.CharField())
bool_var = ConfigVariable(
name='bool_var',
default_value=True,
form_field=forms.BooleanField(required=False))
integer_var = ConfigVariable(
name='integer_var',
default_value=3,
form_field=forms.IntegerField())
group_1 = ConfigGroup(title='Group 1 aiYeix2mCieQuae3', variables=(string_var, bool_var, integer_var))
hidden_var = ConfigVariable(
name='hidden_var',
default_value='hidden_value')
choices_var = ConfigVariable(
name='choices_var',
default_value=1,
form_field=forms.ChoiceField(choices=((1, 'Choice One Ughoch4ocoche6Ee'), (2, 'Choice Two Vahnoh5yalohv5Eb'))))
group_2 = ConfigGroup(title='Group 2 Toongai7ahyahy7B', variables=(hidden_var, choices_var))
return ConfigGroupedPage(title='Config vars for testing 1',
url='testgroupedpage1',
required_permission='config.can_manage',
weight=10000,
groups=(group_1, group_2))
@receiver(config_signal, dispatch_uid='set_simple_config_page_for_testing')
def set_simple_config_page(sender, **kwargs):
"""
Sets a simple config page with some config variables but without
grouping.
"""
return ConfigPage(title='Config vars for testing 2',
url='testsimplepage1',
required_permission='No permission required',
variables=(ConfigVariable(name='additional_config_var', default_value='BaeB0ahcMae3feem'),
ConfigVariable(name='additional_config_var_2', default_value='', form_field=forms.CharField()),
ConfigVariable(name='none_config_var', default_value=None)))
# Do not connect to the signal now but later inside the test.
def set_simple_config_page_multiple_vars(sender, **kwargs):
"""
Sets a bad config page with some multiple config vars.
"""
return ConfigPage(title='Config vars for testing 3',
url='testsimplepage2',
required_permission='No permission required',
variables=(ConfigVariable(name='multiple_config_var', default_value='foobar1'),
ConfigVariable(name='multiple_config_var', default_value='foobar2')))
@receiver(config_signal, dispatch_uid='set_simple_config_page_disabled_page_for_testing')
def set_simple_config_page_disabled_page(sender, **kwargs):
return ConfigPage(title='Ho5iengaoon5Hoht',
url='testsimplepage3',
required_permission='No permission required',
variables=(ConfigVariable(name='hidden_config_var_2', default_value=''),))

View File

@ -10,7 +10,7 @@
from openslides.utils.test import TestCase from openslides.utils.test import TestCase
from openslides.participant.models import User from openslides.participant.models import User
from openslides.config.models import config from openslides.config.api import config
from openslides.motion.models import Motion, Workflow, State from openslides.motion.models import Motion, Workflow, State
from openslides.motion.exceptions import WorkflowError from openslides.motion.exceptions import WorkflowError