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 %} -
{{ 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 %} +{{ 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 %} -