diff --git a/openslides/agenda/__init__.py b/openslides/agenda/__init__.py index 43b3a78d3..a9676dd1a 100644 --- a/openslides/agenda/__init__.py +++ b/openslides/agenda/__init__.py @@ -10,4 +10,4 @@ :license: GNU GPL, see LICENSE for more details. """ -import openslides.agenda.signals +from . import signals diff --git a/openslides/agenda/forms.py b/openslides/agenda/forms.py index 098e949de..42752f91b 100644 --- a/openslides/agenda/forms.py +++ b/openslides/agenda/forms.py @@ -13,7 +13,7 @@ import re 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 openslides.utils.forms import CssClassMixin @@ -25,14 +25,14 @@ class ItemForm(forms.ModelForm, CssClassMixin): Form to create of update an item. """ 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( 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, required=False, - label=_("Duration (hh:mm)")) + label=ugettext_lazy("Duration (hh:mm)")) class Meta: model = Item @@ -46,7 +46,7 @@ def gen_weight_choices(): 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. """ @@ -57,10 +57,3 @@ class ItemOrderForm(forms.Form, CssClassMixin): widget=forms.HiddenInput(attrs={'class': 'menu-mlid'})) parent = forms.IntegerField( 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")) diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index a7c27c616..6a994e3d4 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -16,7 +16,7 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext 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.api import ( register_slidemodel, get_slide_from_sid, register_slidefunc) diff --git a/openslides/agenda/signals.py b/openslides/agenda/signals.py index fd43e6d9e..718028269 100644 --- a/openslides/agenda/signals.py +++ b/openslides/agenda/signals.py @@ -6,17 +6,44 @@ Signals for the agenda app. - :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :copyright: (c) 2011–2013 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 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") -def default_config(sender, key, **kwargs): - """Return the default config values for the agenda app.""" - return { - 'agenda_start_event_date_time': ''}.get(key) +# TODO: Reinsert the datepicker scripts in the template + +@receiver(config_signal, dispatch_uid='setup_agenda_config_page') +def setup_agenda_config_page(sender, **kwargs): + """ + 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}) diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index 98fbea02a..cb471119e 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -20,8 +20,7 @@ from django.db.models import Model from django.utils.translation import ugettext as _, ugettext_lazy from django.views.generic.detail import SingleObjectMixin -from openslides.config.models import config -from openslides.agenda.forms import ConfigForm +from openslides.config.api import config from openslides.utils.pdf import stylesheet from openslides.utils.views import ( TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView, @@ -225,29 +224,9 @@ class AgendaPDF(PDFView): 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): """ - register the agenda tab. + Registers the agenda tab. """ selected = request.path.startswith('/agenda/') return Tab( @@ -261,7 +240,7 @@ def register_tab(request): def get_widgets(request): """ - return the agenda widget for the projector-tab. + Returns the agenda widget for the projector tab. """ return [Widget( name='agenda', diff --git a/openslides/assignment/__init__.py b/openslides/assignment/__init__.py index e69de29bb..5d5108c21 100644 --- a/openslides/assignment/__init__.py +++ b/openslides/assignment/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.assignment + ~~~~~~~~~~~~~~~~~~~~~ + + The OpenSlides assignment app. + + :copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS. + :license: GNU GPL, see LICENSE for more details. +""" + +from . import signals diff --git a/openslides/assignment/forms.py b/openslides/assignment/forms.py index 9adf16dbd..a0a0f5818 100644 --- a/openslides/assignment/forms.py +++ b/openslides/assignment/forms.py @@ -6,7 +6,7 @@ Forms for the assignment app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ @@ -33,39 +33,3 @@ class AssignmentRunForm(forms.Form, CssClassMixin): widget=forms.Select(attrs={'class': 'medium-input'}), 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.")))) diff --git a/openslides/assignment/models.py b/openslides/assignment/models.py index 56269b601..0f89d61a6 100644 --- a/openslides/assignment/models.py +++ b/openslides/assignment/models.py @@ -6,18 +6,16 @@ Models for the assignment app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ from django.core.urlresolvers import reverse from django.db import models -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 PersonField -from openslides.config.models import config -from openslides.config.signals import default_config_value +from openslides.config.api import config from openslides.projector.api import register_slidemodel from openslides.projector.projector import SlideMixin from openslides.poll.models import ( @@ -308,15 +306,3 @@ class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin): def __unicode__(self): 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) diff --git a/openslides/assignment/signals.py b/openslides/assignment/signals.py new file mode 100644 index 000000000..9f2490d82 --- /dev/null +++ b/openslides/assignment/signals.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.assignment.signals + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Signals for the assignment app. + + :copyright: (c) 2011–2013 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)) diff --git a/openslides/assignment/templates/assignment/config.html b/openslides/assignment/templates/assignment/config.html deleted file mode 100644 index 2de60af06..000000000 --- a/openslides/assignment/templates/assignment/config.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "config/base_config.html" %} - -{% load i18n %} - -{% block title %}{{ block.super }} – {% trans "Election settings" %}{% endblock %} - -{% block content %} -

- {% trans "Configuration" %} - {% trans "Elections" %} - {% block config_submenu %}{{ block.super }}{% endblock %} -

-
{% csrf_token %} - {% include "form.html" %} -

- {% include "formbuttons_save.html" %} - - {% trans 'Cancel' %} - -

- * {% trans "required" %} -
-{% endblock %} diff --git a/openslides/assignment/urls.py b/openslides/assignment/urls.py index 42e55b638..55ef169cb 100644 --- a/openslides/assignment/urls.py +++ b/openslides/assignment/urls.py @@ -6,7 +6,7 @@ URL list for the assignment app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ diff --git a/openslides/assignment/views.py b/openslides/assignment/views.py index c60f0f8d7..c8582bca7 100644 --- a/openslides/assignment/views.py +++ b/openslides/assignment/views.py @@ -6,7 +6,7 @@ Views for the assignment app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :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) from openslides.utils.views import FormView, DeleteView, PDFView, RedirectView 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.projector.projector import Widget from openslides.poll.views import PollFormView from openslides.agenda.models import Item from openslides.assignment.models import Assignment, AssignmentPoll -from openslides.assignment.forms import ( - AssignmentForm, AssignmentRunForm, ConfigForm) +from openslides.assignment.forms import AssignmentForm, AssignmentRunForm @permission_required('assignment.can_see_assignment') @@ -633,45 +632,6 @@ class AssignmentPollPDF(PDFView): 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): selected = request.path.startswith('/assignment/') return Tab( diff --git a/openslides/config/api.py b/openslides/config/api.py new file mode 100644 index 000000000..fdef1aa67 --- /dev/null +++ b/openslides/config/api.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.config.api + ~~~~~~~~~~~~~~~~~~~~~ + + Api for the config app. + + :copyright: 2011–2013 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 diff --git a/openslides/config/exceptions.py b/openslides/config/exceptions.py new file mode 100644 index 000000000..cee473431 --- /dev/null +++ b/openslides/config/exceptions.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.config.exceptions + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Exceptions for the config app. + + :copyright: 2011–2013 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 diff --git a/openslides/config/forms.py b/openslides/config/forms.py deleted file mode 100644 index 5e7f001d2..000000000 --- a/openslides/config/forms.py +++ /dev/null @@ -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, - ) diff --git a/openslides/config/middleware.py b/openslides/config/middleware.py new file mode 100644 index 000000000..fa3fcafd9 --- /dev/null +++ b/openslides/config/middleware.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.config.middleware + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Middleware for the config app. + + :copyright: 2011–2013 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() diff --git a/openslides/config/models.py b/openslides/config/models.py index 01ac2fd1d..23cd9ecca 100644 --- a/openslides/config/models.py +++ b/openslides/config/models.py @@ -6,125 +6,26 @@ Models for the config app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :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.dispatch import receiver -from django.utils.importlib import import_module -from django.utils.translation import ugettext_lazy as _, ugettext_noop +from django.utils.translation import ugettext_noop 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): """ - 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): - return self.key + key = models.CharField(max_length=255, primary_key=True) + """A string, the key of the config variable.""" + + value = JSONField() + """The value of the config variable. """ class Meta: - verbose_name = 'config' - 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}) + permissions = (('can_manage', ugettext_noop('Can manage configuration')),) diff --git a/openslides/config/signals.py b/openslides/config/signals.py index 7cdc7c7e9..6fbe53c69 100644 --- a/openslides/config/signals.py +++ b/openslides/config/signals.py @@ -4,12 +4,14 @@ openslides.config.signals ~~~~~~~~~~~~~~~~~~~~~~~~~ - Defines Signals for the config. + Signals for the config app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ 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.""" diff --git a/openslides/config/templates/config/base_config.html b/openslides/config/templates/config/base_config.html deleted file mode 100644 index a1f99418e..000000000 --- a/openslides/config/templates/config/base_config.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "base.html" %} - -{% load i18n %} - -{% block config_submenu %} - -
-
- {% for menu_link in menu_links %} - {{ menu_link.1 }} - {% endfor %} -
-
-
-{% endblock %} diff --git a/openslides/agenda/templates/agenda/config.html b/openslides/config/templates/config/config_form.html similarity index 57% rename from openslides/agenda/templates/agenda/config.html rename to openslides/config/templates/config/config_form.html index c4f2f6f6c..33341e580 100644 --- a/openslides/agenda/templates/agenda/config.html +++ b/openslides/config/templates/config/config_form.html @@ -1,19 +1,12 @@ -{% extends "config/base_config.html" %} +{% extends 'base.html' %} {% load i18n %} -{% get_current_language as LANGUAGE_CODE %} +{% get_current_language as LANGUAGE_CODE %} {% load staticfiles %} -{% block header %} - - -{% endblock %} - {% block javascript %} - - - + {{ block.super }} {% endblock %} -{% block title %}{{ block.super }} – {% trans "Agenda settings" %}{% endblock %} - - {% block content %} -

- {% trans "Configuration" %} - {% trans "Agenda" %} - {% block config_submenu %}{{ block.super }}{% endblock %} -

-
{% csrf_token %} - {% include "form.html" %} -

- {% include "formbuttons_save.html" %} - - {% trans 'Cancel' %} - -

- * {% trans "required" %} -
-{% endblock %} \ No newline at end of file +

+ {% trans 'Configuration' %} + {% trans active_config_page.title %} + +
+
+ {% for config_page_dict in config_pages_list %} + {{ config_page_dict.config_page.title }} + {% endfor %} + {% trans 'Version' %} +
+
+
+

+
{% csrf_token %} + {% for group in groups %} +
+ {{ group.title }} + {% for field in form %} + {% if field.name in group.get_field_names %} +
+ + {{ field }} + {% if field.errors %} + {{ field.errors }} + {% endif %} + {% if field.help_text %} + {{ field.help_text }} + {% endif %} +
+ {% endif %} + {% endfor %} +
+ {% empty %} + {% include 'form.html' %} + {% endfor %} +

+ {% include 'formbuttons_save.html' %} + {% trans 'Cancel' %} +

+ * {% trans 'required' %} +
+{% endblock %} diff --git a/openslides/config/templates/config/general.html b/openslides/config/templates/config/general.html deleted file mode 100644 index f5ef585d8..000000000 --- a/openslides/config/templates/config/general.html +++ /dev/null @@ -1,75 +0,0 @@ -{% extends "config/base_config.html" %} - -{% load i18n %} - -{% block title %}{{ block.super }} – {% trans "General settings" %}{% endblock %} - -{% block content %} -

- {% trans "Configuration" %} - {% trans "General" %} - {% block config_submenu %}{{ block.super }}{% endblock %} -

-
{% csrf_token %} -
- {% trans "Event" %} - {% for field in form %} - {% if "id_event" in field.label_tag %} -
- - {{ field }} - {% if field.errors %} - {{ field.errors }} - {% endif %} - {% if field.help_text %} - {{ field.help_text }} - {% endif %} -
- {% endif %} - {% endfor %} -
-

-
- {% trans "Welcome Widget" %} - {% for field in form %} - {% if "id_welcome" in field.label_tag %} -
- - {{ field }} - {% if field.errors %} - {{ field.errors }} - {% endif %} - {% if field.help_text %} - {{ field.help_text }} - {% endif %} -
- {% endif %} - {% endfor %} -
-

-
- {% trans "System" %} - {% for field in form %} - {% if "id_system" in field.label_tag %} -
- - {{ field }} - {% if field.errors %} - {{ field.errors }} - {% endif %} - {% if field.help_text %} - {{ field.help_text }} - {% endif %} -
- {% endif %} - {% endfor %} -
-

- {% include "formbuttons_save.html" %} - - {% trans 'Cancel' %} - -

- * {% trans "required" %} -
-{% endblock %} diff --git a/openslides/config/templates/config/version.html b/openslides/config/templates/config/version.html deleted file mode 100644 index a846675ad..000000000 --- a/openslides/config/templates/config/version.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "config/base_config.html" %} - -{% load i18n %} - -{% block title %}{{ block.super }} – {% trans "Version" %}{% endblock %} - -{% block content %} -

{% trans "Version" %} - {% block config_submenu %}{{ block.super }}{% endblock %} -

- - {% for version in versions %} -

{{ version.0 }} {% trans "Version" %}: {{ version.1 }}

- {% endfor %} -{% endblock %} diff --git a/openslides/config/urls.py b/openslides/config/urls.py index 84c1e93e9..6aa2d1265 100644 --- a/openslides/config/urls.py +++ b/openslides/config/urls.py @@ -4,42 +4,28 @@ openslides.config.urls ~~~~~~~~~~~~~~~~~~~~~~ - URL list for the config app. + Url patterns for the config app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ -from django.conf import settings 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('', - url(r'^general/$', - GeneralConfig.as_view(), - name='config_general', - ), - - url(r'^version/$', - VersionConfig.as_view(), - name='config_version', + url(r'^$', + RedirectView.as_view(url_name='config_general'), + name='config_first_config_page', ), ) -for app in settings.INSTALLED_APPS: - try: - mod = import_module(app + '.views') - except ImportError: - continue - appname = mod.__name__.split('.')[-2] - try: - urlpatterns += patterns('', url( - r'^%s/$' % appname, - mod.Config.as_view(), - name='config_%s' % appname, - )) - except AttributeError: - continue - +for receiver, config_page in config_signal.send(sender='config_urls'): + if config_page.is_shown(): + urlpatterns += patterns('', url(r'^%s/$' % config_page.url, + ConfigView.as_view(config_page=config_page), + name='config_%s' % config_page.url)) diff --git a/openslides/config/views.py b/openslides/config/views.py index 09f0a6968..3b9782a15 100644 --- a/openslides/config/views.py +++ b/openslides/config/views.py @@ -6,107 +6,126 @@ Views for the config app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ -from django.conf import settings -from django.contrib import messages +from django import forms 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 openslides import get_version, get_git_commit_id, RELEASE +from openslides.utils.views import FormView from openslides.utils.template import Tab -from openslides.utils.views import FormView, TemplateView -from .forms import GeneralConfigForm -from .models import config +from .api import config +from .signals import config_signal -class GeneralConfig(FormView): +class ConfigView(FormView): """ - Gereral config values. + The view for a config page. """ - permission_required = 'config.can_manage_config' - form_class = GeneralConfigForm - template_name = 'config/general.html' - success_url_name = 'config_general' + template_name = 'config/config_form.html' + config_page = None + form_class = forms.Form + + 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): - 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. - """ - permission_required = 'config.can_manage_config' - template_name = 'config/version.html' + """ + Returns a dictonary with the actual values of the config variables + as intial value for the form. + """ + initial = super(ConfigView, self).get_initial() + for variable in self.config_page.variables: + initial.update({variable.name: config[variable.name]}) + return initial 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. - openslides_version_string = get_version() - if not RELEASE: - openslides_version_string += ' Commit: %s' % get_git_commit_id() - context['versions'] = [('OpenSlides', openslides_version_string)] + context['active_config_page'] = self.config_page + + config_pages_list = [] + for receiver, config_page in config_signal.send(sender=self): + 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 + 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): """ - Register the config tab. + Registers the tab for this app in the main menu. """ - selected = request.path.startswith('/config/') return Tab( title=_('Configuration'), app='config', - url=reverse('config_general'), - permission=request.user.has_perm('config.can_manage_config'), - selected=selected, - ) + url=reverse('config_first_config_page'), + permission=request.user.has_perm('config.can_manage'), + selected=request.path.startswith('/config/')) diff --git a/openslides/core/__init__.py b/openslides/core/__init__.py index e69de29bb..b31692336 100644 --- a/openslides/core/__init__.py +++ b/openslides/core/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.core + ~~~~~~~~~~~~~~~ + + The core app. + + :copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS. + :license: GNU GPL, see LICENSE for more details. +""" + +from . import signals diff --git a/openslides/core/signals.py b/openslides/core/signals.py index 9d253035a..4f7da8405 100644 --- a/openslides/core/signals.py +++ b/openslides/core/signals.py @@ -2,15 +2,110 @@ # -*- coding: utf-8 -*- """ openslides.core.signals - ~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~ - Core Signals. + Signals for the core app. - :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS. :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() + + +@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)) diff --git a/openslides/core/templates/core/version.html b/openslides/core/templates/core/version.html new file mode 100644 index 000000000..5cb717756 --- /dev/null +++ b/openslides/core/templates/core/version.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% load i18n %} + +{% block title %}{{ block.super }} – {% trans 'Version' %}{% endblock %} + +{% block content %} +

{% trans 'Versions' %}

+ {% for version in versions %} +

{{ version.0 }} {% trans "Version" %}: {{ version.1 }}

+ {% endfor %} +{% endblock %} diff --git a/openslides/core/urls.py b/openslides/core/urls.py new file mode 100644 index 000000000..8059680ed --- /dev/null +++ b/openslides/core/urls.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.core.urls + ~~~~~~~~~~~~~~~~~~~~ + + Url patterns for the core app. + + :copyright: 2011–2013 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',), +) diff --git a/openslides/core/views.py b/openslides/core/views.py new file mode 100644 index 000000000..8d0384deb --- /dev/null +++ b/openslides/core/views.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.core.views + ~~~~~~~~~~~~~~~~~~~~~ + + Views for the core app. + + :copyright: 2011–2013 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 diff --git a/openslides/global_settings.py b/openslides/global_settings.py index c74bbd4ce..60f6876ae 100644 --- a/openslides/global_settings.py +++ b/openslides/global_settings.py @@ -89,6 +89,7 @@ MIDDLEWARE_CLASSES = ( 'django.middleware.csrf.CsrfViewMiddleware', 'openslides.participant.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', + 'openslides.config.middleware.ConfigCacheMiddleware', ) ROOT_URLCONF = 'openslides.urls' @@ -110,6 +111,7 @@ INSTALLED_APPS = ( 'mptt', 'openslides.utils', 'openslides.poll', + 'openslides.core', 'openslides.projector', 'openslides.agenda', 'openslides.motion', diff --git a/openslides/main.py b/openslides/main.py index 4477b6a51..bade20440 100644 --- a/openslides/main.py +++ b/openslides/main.py @@ -284,12 +284,10 @@ def run_syncdb(): def set_system_url(url): # can't be imported in global scope as it already requires # the settings module during import - from openslides.config.models import config + from openslides.config.api import config - key = "participant_pdf_system_url" - if key in config: - return - config[key] = url + if config['participant_pdf_system_url'] == 'http://example.com:8000': + config['participant_pdf_system_url'] = url def create_or_reset_admin_user(): diff --git a/openslides/motion/__init__.py b/openslides/motion/__init__.py index e296be3ce..6d62a31c2 100644 --- a/openslides/motion/__init__.py +++ b/openslides/motion/__init__.py @@ -7,9 +7,8 @@ The OpenSlides motion app appends the functionality to OpenSlides to manage motions. - :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ -import openslides.motion.signals -import openslides.motion.slides +from . import signals, slides diff --git a/openslides/motion/exceptions.py b/openslides/motion/exceptions.py index f0e482804..4dfdbd57f 100644 --- a/openslides/motion/exceptions.py +++ b/openslides/motion/exceptions.py @@ -6,7 +6,7 @@ Exceptions for the motion app. - :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ diff --git a/openslides/motion/forms.py b/openslides/motion/forms.py index 7b81d6524..2ecb30258 100644 --- a/openslides/motion/forms.py +++ b/openslides/motion/forms.py @@ -15,7 +15,7 @@ from django.utils.translation import ugettext as _ from openslides.utils.forms import CssClassMixin from openslides.utils.person import PersonFormField, MultiplePersonFormField -from .models import Motion, Workflow, Category +from .models import Motion, Category class BaseMotionForm(forms.ModelForm, CssClassMixin): @@ -100,55 +100,3 @@ class MotionIdentifierMixin(forms.ModelForm): """Mixin to let the user choose the identifier for the motion.""" 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()]) diff --git a/openslides/motion/models.py b/openslides/motion/models.py index 17f81f238..148425d99 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -9,7 +9,7 @@ To use a motion object, you only have to import the Motion class. Any functionality can be reached from a motion object. - :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS. :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 openslides.utils.person import PersonField -from openslides.config.models import config -from openslides.config.signals import default_config_value +from openslides.config.api import config from openslides.poll.models import ( BaseOption, BasePoll, CountVotesCast, CountInvalid, BaseVote) from openslides.participant.models import User diff --git a/openslides/motion/pdf.py b/openslides/motion/pdf.py index 686e8fd03..7f48ec7dd 100644 --- a/openslides/motion/pdf.py +++ b/openslides/motion/pdf.py @@ -6,7 +6,7 @@ Functions to generate the PDFs for the motion app. - :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ @@ -16,9 +16,8 @@ from reportlab.platypus import ( SimpleDocTemplate, PageBreak, Paragraph, Spacer, Table, TableStyle) 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 .models import Motion diff --git a/openslides/motion/signals.py b/openslides/motion/signals.py index ff662ddf1..8946201fd 100644 --- a/openslides/motion/signals.py +++ b/openslides/motion/signals.py @@ -11,26 +11,110 @@ """ 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 .models import Workflow, State -@receiver(default_config_value, dispatch_uid="motion_default_config") -def default_config(sender, key, **kwargs): - """Return the default config values for the motion app.""" - return { - 'motion_min_supporters': 0, - 'motion_preamble': _('The assembly may decide,'), - 'motion_pdf_ballot_papers_selection': 'CUSTOM_NUMBER', - 'motion_pdf_ballot_papers_number': '8', - 'motion_pdf_title': _('Motions'), - 'motion_pdf_preamble': '', - 'motion_allow_disable_versioning': False, - 'motion_workflow': 1}.get(key) +@receiver(config_signal, dispatch_uid='setup_motion_config_page') +def setup_motion_config_page(sender, **kwargs): + """ + Motion config variables. + """ + motion_min_supporters = ConfigVariable( + name='motion_min_supporters', + default_value=0, + form_field=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 = 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') diff --git a/openslides/motion/slides.py b/openslides/motion/slides.py index 47212d107..6b65f2567 100644 --- a/openslides/motion/slides.py +++ b/openslides/motion/slides.py @@ -6,7 +6,7 @@ Defines the slides for the motion app. - :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ diff --git a/openslides/motion/templates/motion/config.html b/openslides/motion/templates/motion/config.html deleted file mode 100644 index b36f60a68..000000000 --- a/openslides/motion/templates/motion/config.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "config/base_config.html" %} - -{% load i18n %} - -{% block title %}{{ block.super }} – {% trans "Motion settings" %}{% endblock %} - -{% block content %} -

- {% trans "Configuration" %} - {% trans "Motions" %} - {% block config_submenu %}{{ block.super }}{% endblock %} -

-
{% csrf_token %} - {% include "form.html" %} -

- {% include "formbuttons_save.html" %} - - {% trans 'Cancel' %} - -

- * {% trans "required" %} -
-{% endblock %} diff --git a/openslides/motion/urls.py b/openslides/motion/urls.py index f230a8dd9..46a50dc9b 100644 --- a/openslides/motion/urls.py +++ b/openslides/motion/urls.py @@ -6,7 +6,7 @@ Defines the URL patterns for the motion app. - :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ diff --git a/openslides/motion/views.py b/openslides/motion/views.py index 788e7c50b..608d8f5d3 100644 --- a/openslides/motion/views.py +++ b/openslides/motion/views.py @@ -29,21 +29,17 @@ from openslides.utils.utils import html_strong, htmldiff from openslides.poll.views import PollFormView from openslides.projector.api import get_active_slide from openslides.projector.projector import Widget, SLIDE -from openslides.config.models import config +from openslides.config.api import config from openslides.agenda.models import Item from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll, MotionVersion, State, WorkflowError, Category) from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin, - MotionDisableVersioningMixin, ConfigForm, MotionCategoryMixin, + MotionDisableVersioningMixin, MotionCategoryMixin, MotionIdentifierMixin) 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): """View, to list all motions.""" permission_required = 'motion.can_see_motion' @@ -590,49 +586,15 @@ class CategoryDeleteView(DeleteView): 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): """Return the motion tab.""" - # TODO: Find a bether way to set the selected var. - selected = request.path.startswith('/motion/') + # TODO: Find a better way to set the selected var. return Tab( title=_('Motions'), app='motion', url=reverse('motion_list'), permission=request.user.has_perm('motion.can_see_motion'), - selected=selected, - ) + selected=request.path.startswith('/motion/')) def get_widgets(request): diff --git a/openslides/participant/__init__.py b/openslides/participant/__init__.py index 12b9658f8..ff2de0a29 100644 --- a/openslides/participant/__init__.py +++ b/openslides/participant/__init__.py @@ -6,13 +6,13 @@ The OpenSlides participant app. - :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ from django.utils.translation import ugettext_noop -import openslides.participant.signals +from . import signals NAME = ugettext_noop('Participant') diff --git a/openslides/participant/forms.py b/openslides/participant/forms.py index 2da72bafe..64a4f2b83 100644 --- a/openslides/participant/forms.py +++ b/openslides/participant/forms.py @@ -6,13 +6,13 @@ Forms for the participant app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ from django import forms 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 openslides.utils.forms import ( @@ -112,20 +112,3 @@ class UsersettingsForm(forms.ModelForm, CssClassMixin): class UserImportForm(forms.Form, CssClassMixin): csvfile = forms.FileField(widget=forms.FileInput(attrs={'size': '50'}), 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")) diff --git a/openslides/participant/models.py b/openslides/participant/models.py index 9bb8bc0de..bbb889329 100644 --- a/openslides/participant/models.py +++ b/openslides/participant/models.py @@ -6,7 +6,7 @@ Models for the participant app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :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.models import signals 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.signals import receive_persons - -from openslides.config.models import config -from openslides.config.signals import default_config_value - +from openslides.config.api import config from openslides.projector.api import register_slidemodel from openslides.projector.projector import SlideMixin @@ -226,19 +223,6 @@ def receive_persons(sender, **kwargs): 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) def djangouser_post_save(sender, instance, signal, *args, **kwargs): try: diff --git a/openslides/participant/signals.py b/openslides/participant/signals.py index 84b8fb351..bb7e822e8 100644 --- a/openslides/participant/signals.py +++ b/openslides/participant/signals.py @@ -2,24 +2,66 @@ # -*- coding: utf-8 -*- """ openslides.participant.signals - ~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Signals for the participant app. - :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ 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.auth.models import Permission 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 +@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') 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') 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.permissions.add(perm_7, perm_9, perm_10, perm_11, perm_12, perm_13, perm_14, perm_15, perm_15a, perm_16) diff --git a/openslides/participant/templates/participant/config.html b/openslides/participant/templates/participant/config.html deleted file mode 100644 index 50258fe52..000000000 --- a/openslides/participant/templates/participant/config.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "config/base_config.html" %} - -{% load i18n %} - -{% block title %}{{ block.super }} – {% trans "Participant settings" %}{% endblock %} - -{% block content %} -

- {% trans "Configuration" %} - {% trans "Participants" %} - {% block config_submenu %}{{ block.super }}{% endblock %} -

-
{% csrf_token %} - {% include "form.html" %} -

- {% include "formbuttons_save.html" %} - - {% trans 'Cancel' %} - -

- * {% trans "required" %} -
-{% endblock %} diff --git a/openslides/participant/urls.py b/openslides/participant/urls.py index 8fa6b9ca9..4e99df142 100644 --- a/openslides/participant/urls.py +++ b/openslides/participant/urls.py @@ -6,7 +6,7 @@ URL list for the participant app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ diff --git a/openslides/participant/views.py b/openslides/participant/views.py index 3e05009a1..c1988b309 100644 --- a/openslides/participant/views.py +++ b/openslides/participant/views.py @@ -6,7 +6,7 @@ Views for the participant app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ @@ -41,14 +41,14 @@ from openslides.utils.utils import ( from openslides.utils.views import ( FormView, PDFView, CreateView, UpdateView, DeleteView, PermissionMixin, 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.motion.models import Motion from openslides.assignment.models import Assignment from openslides.participant.api import gen_username, gen_password, import_users from openslides.participant.forms import ( UserCreateForm, UserUpdateForm, UsersettingsForm, - UserImportForm, GroupForm, ConfigForm) + UserImportForm, GroupForm) from openslides.participant.models import User, Group @@ -388,34 +388,6 @@ class GroupDeleteView(DeleteView): 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): extra_content = {} try: @@ -485,7 +457,7 @@ def user_settings_password(request): def register_tab(request): """ - Register the participant tab. + Registers the participant tab. """ selected = request.path.startswith('/participant/') return Tab( diff --git a/openslides/projector/__init__.py b/openslides/projector/__init__.py index e69de29bb..39748e2c6 100644 --- a/openslides/projector/__init__.py +++ b/openslides/projector/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.projector + ~~~~~~~~~~~~~~~~~~~~ + + The projector app. + + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. + :license: GNU GPL, see LICENSE for more details. +""" + +from . import signals diff --git a/openslides/projector/api.py b/openslides/projector/api.py index 7cbd5da02..13035d916 100644 --- a/openslides/projector/api.py +++ b/openslides/projector/api.py @@ -15,7 +15,7 @@ from django.core.cache import cache from django.utils.datastructures import SortedDict 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 diff --git a/openslides/projector/models.py b/openslides/projector/models.py index c03c426cf..e2260cf2a 100644 --- a/openslides/projector/models.py +++ b/openslides/projector/models.py @@ -6,7 +6,7 @@ Models for the projector app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ @@ -14,8 +14,6 @@ from django.db import models from django.dispatch import receiver 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.projector import SlideMixin @@ -70,16 +68,3 @@ class ProjectorOverlay(models.Model): if self.sid: return "%s on %s" % (self.def_name, self.sid) 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) diff --git a/openslides/projector/projector.py b/openslides/projector/projector.py index 6e3129f4b..ba6b930ea 100644 --- a/openslides/projector/projector.py +++ b/openslides/projector/projector.py @@ -15,10 +15,10 @@ from time import time from django.dispatch import receiver 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 + SLIDE = {} diff --git a/openslides/projector/signals.py b/openslides/projector/signals.py index f034ccd06..af38e4596 100644 --- a/openslides/projector/signals.py +++ b/openslides/projector/signals.py @@ -4,12 +4,68 @@ openslides.projector.signals ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Defines Signals for the projector. + Signals for the projector app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :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']) + + +@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)) diff --git a/openslides/projector/views.py b/openslides/projector/views.py index b70cd504d..13f7c9faa 100644 --- a/openslides/projector/views.py +++ b/openslides/projector/views.py @@ -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.views import ( TemplateView, RedirectView, CreateView, UpdateView, DeleteView, AjaxMixin) -from openslides.config.models import config +from openslides.config.api import config from .api import ( get_active_slide, set_active_slide, projector_message_set, projector_message_delete, get_slide_from_sid, get_all_widgets, @@ -224,8 +224,8 @@ class ProjectorEdit(RedirectView): if config['up'] < 0: config['up'] = int(config['up']) + 10 elif direction == 'clean': - config['up'] = 0 - config['bigger'] = 100 + config['up'] = 0 # TODO: Use default value from the signal instead of fix value here + config['bigger'] = 100 # TODO: Use default value from the signal instead of fix value here class CountdownEdit(RedirectView): diff --git a/openslides/templates/base.html b/openslides/templates/base.html index 83466239f..6b0eedaa5 100644 --- a/openslides/templates/base.html +++ b/openslides/templates/base.html @@ -98,7 +98,7 @@
@@ -112,6 +112,9 @@ + {% for javascript in extra_javascript %} + + {% endfor %} {% block javascript %}{% endblock %} diff --git a/openslides/urls.py b/openslides/urls.py index 467addb38..463f81f73 100644 --- a/openslides/urls.py +++ b/openslides/urls.py @@ -14,15 +14,10 @@ from django.conf import settings from django.conf.urls import patterns, url, include from django.utils.importlib import import_module -from openslides.utils.views import RedirectView - handler500 = 'openslides.utils.views.server_error' urlpatterns = patterns('', - # Redirect to dashboard URL - url(r'^$', RedirectView.as_view(url='projector/dashboard'), name='home',), - (r'^agenda/', include('openslides.agenda.urls')), (r'^motion/', include('openslides.motion.urls')), (r'^assignment/', include('openslides.assignment.urls')), @@ -69,3 +64,7 @@ urlpatterns += patterns('', name='password_change', ), ) + +urlpatterns += patterns('', + (r'^', include('openslides.core.urls')), +) diff --git a/openslides/utils/auth/AnonymousAuth.py b/openslides/utils/auth/AnonymousAuth.py index e6c4b27fe..f2d24303d 100644 --- a/openslides/utils/auth/AnonymousAuth.py +++ b/openslides/utils/auth/AnonymousAuth.py @@ -11,7 +11,7 @@ """ from django.contrib.auth.models import Permission -from openslides.config.models import config +from openslides.config.api import config class AnonymousAuth(object): diff --git a/openslides/utils/pdf.py b/openslides/utils/pdf.py index 772641b16..10cad58c0 100755 --- a/openslides/utils/pdf.py +++ b/openslides/utils/pdf.py @@ -23,7 +23,7 @@ from django.conf import settings from django.utils import formats from django.utils.translation import ugettext as _ -from openslides.config.models import config +from openslides.config.api import config # register new truetype fonts diff --git a/openslides/utils/templatetags/tags.py b/openslides/utils/templatetags/tags.py index e8cc0d5c5..1119b9cb7 100644 --- a/openslides/utils/templatetags/tags.py +++ b/openslides/utils/templatetags/tags.py @@ -11,7 +11,8 @@ """ from django import template -from openslides.config.models import config +from openslides.config.api import config + register = template.Library() diff --git a/openslides/utils/test.py b/openslides/utils/test.py index 9d7ca3df9..263aea32e 100644 --- a/openslides/utils/test.py +++ b/openslides/utils/test.py @@ -14,14 +14,16 @@ from django.test import TestCase as _TestCase from openslides.core.signals import post_database_setup +from openslides.config.api import config class TestCase(_TestCase): """ 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): return_value = super(TestCase, self)._pre_setup(*args, **kwargs) post_database_setup.send(sender=self) + config.setup_cache() return return_value diff --git a/openslides/utils/views.py b/openslides/utils/views.py index 598332f3d..47bcd1157 100644 --- a/openslides/utils/views.py +++ b/openslides/utils/views.py @@ -391,7 +391,10 @@ def send_register_tab(sender, request, context, **kwargs): Inserts the tab objects and also the extra_stylefiles to the context. """ tabs = [] - extra_stylefiles = [] + if 'extra_stylefiles' in context: + extra_stylefiles = context['extra_stylefiles'] + else: + extra_stylefiles = [] for app in settings.INSTALLED_APPS: try: mod = import_module(app + '.views') @@ -401,8 +404,6 @@ def send_register_tab(sender, request, context, **kwargs): extra_stylefiles.append(tab.stylefile) except (ImportError, AttributeError): continue - context.update({ 'tabs': tabs, - 'extra_stylefiles': extra_stylefiles, - }) + 'extra_stylefiles': extra_stylefiles}) diff --git a/tests/agenda/tests.py b/tests/agenda/tests.py index d1a76cf6f..28d1f4391 100644 --- a/tests/agenda/tests.py +++ b/tests/agenda/tests.py @@ -191,3 +191,18 @@ class ViewTest(TestCase): self.assertEqual(response.status_code, 200) self.refreshItems() 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) diff --git a/tests/config/__init__.py b/tests/config/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/config/test_config.py b/tests/config/test_config.py new file mode 100644 index 000000000..ccdade5ca --- /dev/null +++ b/tests/config/test_config.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + Tests for openslides.config + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: 2011–2013 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('Config vars for testing 1', response.content) + m2 = re.search('Config vars for testing 2', 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=''),)) diff --git a/tests/motion/test_models.py b/tests/motion/test_models.py index 8e7ddcd4d..475afd7dc 100644 --- a/tests/motion/test_models.py +++ b/tests/motion/test_models.py @@ -10,7 +10,7 @@ from openslides.utils.test import TestCase 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.exceptions import WorkflowError