From e1b149cde3b46efee6cc497ced4929b7eba54bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Fri, 1 Mar 2013 17:13:12 +0100 Subject: [PATCH] New config app. Apps only have to define config vars once. Config pages, forms and so on are created automaticly. Changes after some reviews are done. Problematic is still that the JS can not be moved to an extra file because of the template tags in the code. --- openslides/agenda/__init__.py | 2 +- openslides/agenda/forms.py | 17 +- openslides/agenda/models.py | 2 +- openslides/agenda/signals.py | 41 ++- openslides/agenda/views.py | 27 +- openslides/assignment/__init__.py | 13 + openslides/assignment/forms.py | 38 +-- openslides/assignment/models.py | 20 +- openslides/assignment/signals.py | 87 ++++++ .../templates/assignment/config.html | 23 -- openslides/assignment/urls.py | 2 +- openslides/assignment/views.py | 46 +--- openslides/config/api.py | 147 ++++++++++ openslides/config/exceptions.py | 21 ++ openslides/config/forms.py | 67 ----- openslides/config/middleware.py | 21 ++ openslides/config/models.py | 117 +------- openslides/config/signals.py | 8 +- .../config/templates/config/base_config.html | 15 - .../templates/config/config_form.html} | 75 +++-- .../config/templates/config/general.html | 75 ----- .../config/templates/config/version.html | 15 - openslides/config/urls.py | 42 +-- openslides/config/views.py | 175 ++++++------ openslides/core/__init__.py | 13 + openslides/core/signals.py | 103 ++++++- openslides/core/templates/core/version.html | 12 + openslides/core/urls.py | 29 ++ openslides/core/views.py | 51 ++++ openslides/global_settings.py | 2 + openslides/main.py | 8 +- openslides/motion/__init__.py | 5 +- openslides/motion/exceptions.py | 2 +- openslides/motion/forms.py | 54 +--- openslides/motion/models.py | 5 +- openslides/motion/pdf.py | 5 +- openslides/motion/signals.py | 112 +++++++- openslides/motion/slides.py | 2 +- .../motion/templates/motion/config.html | 23 -- openslides/motion/urls.py | 2 +- openslides/motion/views.py | 46 +--- openslides/participant/__init__.py | 4 +- openslides/participant/forms.py | 21 +- openslides/participant/models.py | 22 +- openslides/participant/signals.py | 50 +++- .../templates/participant/config.html | 23 -- openslides/participant/urls.py | 2 +- openslides/participant/views.py | 36 +-- openslides/projector/__init__.py | 13 + openslides/projector/api.py | 2 +- openslides/projector/models.py | 17 +- openslides/projector/projector.py | 4 +- openslides/projector/signals.py | 62 ++++- openslides/projector/views.py | 6 +- openslides/templates/base.html | 5 +- openslides/urls.py | 9 +- openslides/utils/auth/AnonymousAuth.py | 2 +- openslides/utils/pdf.py | 2 +- openslides/utils/templatetags/tags.py | 3 +- openslides/utils/test.py | 4 +- openslides/utils/views.py | 9 +- tests/agenda/tests.py | 15 + tests/config/__init__.py | 0 tests/config/test_config.py | 259 ++++++++++++++++++ tests/motion/test_models.py | 2 +- 65 files changed, 1265 insertions(+), 877 deletions(-) create mode 100644 openslides/assignment/signals.py delete mode 100644 openslides/assignment/templates/assignment/config.html create mode 100644 openslides/config/api.py create mode 100644 openslides/config/exceptions.py delete mode 100644 openslides/config/forms.py create mode 100644 openslides/config/middleware.py delete mode 100644 openslides/config/templates/config/base_config.html rename openslides/{agenda/templates/agenda/config.html => config/templates/config/config_form.html} (57%) delete mode 100644 openslides/config/templates/config/general.html delete mode 100644 openslides/config/templates/config/version.html create mode 100644 openslides/core/templates/core/version.html create mode 100644 openslides/core/urls.py create mode 100644 openslides/core/views.py delete mode 100644 openslides/motion/templates/motion/config.html delete mode 100644 openslides/participant/templates/participant/config.html create mode 100644 tests/config/__init__.py create mode 100644 tests/config/test_config.py 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