From c5fbe2e9ee900bae225951584520f5e1314bfedd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Wed, 17 Jun 2015 18:32:05 +0200 Subject: [PATCH] Refactored config API. Removed form_field attributes. Added extra fields for HTML rendering like label and help text. Added fields for sorting and grouping. Removed old collection system. Added config groups to config view via OPTIONS requests. Regrouped all variables. Added validation. Changed internal handling. --- openslides/agenda/signals.py | 99 ++++----- openslides/assignments/signals.py | 134 ++++++------- openslides/config/api.py | 266 ++++++++++++------------- openslides/config/views.py | 75 ++++--- openslides/core/signals.py | 196 ++++++++---------- openslides/motions/signals.py | 245 +++++++++++------------ openslides/poll/models.py | 6 +- openslides/users/signals.py | 132 ++++++------ openslides/utils/rest_api.py | 1 + tests/integration/config/test_views.py | 87 ++++++-- tests/old/config/test_config.py | 127 ++++-------- 11 files changed, 663 insertions(+), 705 deletions(-) diff --git a/openslides/agenda/signals.py b/openslides/agenda/signals.py index f48a914bb..1dd9f8b98 100644 --- a/openslides/agenda/signals.py +++ b/openslides/agenda/signals.py @@ -1,12 +1,12 @@ from datetime import datetime -from django import forms from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import ValidationError +from django.core.exceptions import ValidationError as DjangoValidationError +from django.core.validators import MaxLengthValidator, MinValueValidator from django.utils.translation import ugettext as _ -from django.utils.translation import ugettext_lazy, ugettext_noop +from django.utils.translation import ugettext_lazy -from openslides.config.api import ConfigCollection, ConfigVariable +from openslides.config.api import ConfigVariable from .models import Item @@ -15,87 +15,72 @@ def validate_start_time(value): try: datetime.strptime(value, '%d.%m.%Y %H:%M') except ValueError: - raise ValidationError(_('Invalid input.')) + raise DjangoValidationError(_('Invalid input.')) -# TODO: Reinsert the datepicker scripts in the template - def setup_agenda_config(sender, **kwargs): """ - Receiver function to setup all agenda config variables. It is connected to - the signal openslides.config.signals.config_signal during app loading. + Receiver function to setup all agenda config variables. They are not + grouped. This function connected to the signal + openslides.config.signals.config_signal during app loading. """ - # TODO: Insert validator for the format or use other field carefully. - agenda_start_event_date_time = ConfigVariable( + # TODO: Use an input type with generic datetime support. + yield ConfigVariable( name='agenda_start_event_date_time', default_value='', - form_field=forms.CharField( - validators=[validate_start_time, ], - widget=forms.DateTimeInput(format='%d.%m.%Y %H:%M'), - required=False, - label=ugettext_lazy('Begin of event'), - help_text=ugettext_lazy('Input format: DD.MM.YYYY HH:MM'))) + label=ugettext_lazy('Begin of event'), + help_text=ugettext_lazy('Input format: DD.MM.YYYY HH:MM'), + weight=210, + group=ugettext_lazy('Agenda'), + validators=(validate_start_time,)) - agenda_show_last_speakers = ConfigVariable( + yield ConfigVariable( name='agenda_show_last_speakers', default_value=1, - form_field=forms.IntegerField( - min_value=0, - label=ugettext_lazy('Number of last speakers to be shown on the projector'))) + input_type='integer', + label=ugettext_lazy('Number of last speakers to be shown on the projector'), + weight=220, + group=ugettext_lazy('Agenda'), + validators=(MinValueValidator(0),)) - agenda_couple_countdown_and_speakers = ConfigVariable( + yield ConfigVariable( name='agenda_couple_countdown_and_speakers', default_value=False, - form_field=forms.BooleanField( - label=ugettext_lazy('Couple countdown with the list of speakers'), - help_text=ugettext_lazy('[Begin speach] starts the countdown, [End speach] stops the countdown.'), - required=False)) + input_type='boolean', + label=ugettext_lazy('Couple countdown with the list of speakers'), + help_text=ugettext_lazy('[Begin speach] starts the countdown, [End speach] stops the countdown.'), + weight=230, + group=ugettext_lazy('Agenda')) - agenda_number_prefix = ConfigVariable( + yield ConfigVariable( name='agenda_number_prefix', default_value='', - form_field=forms.CharField( - label=ugettext_lazy('Numbering prefix for agenda items'), - max_length=20, - required=False)) + label=ugettext_lazy('Numbering prefix for agenda items'), + weight=240, + group=ugettext_lazy('Agenda'), + validators=(MaxLengthValidator(20),)) - agenda_numeral_system = ConfigVariable( + yield ConfigVariable( name='agenda_numeral_system', default_value='arabic', - form_field=forms.ChoiceField( - label=ugettext_lazy('Numeral system for agenda items'), - widget=forms.Select(), - choices=( - ('arabic', ugettext_lazy('Arabic')), - ('roman', ugettext_lazy('Roman'))), - required=False)) - - extra_stylefiles = ['css/jquery-ui-timepicker.css'] - extra_javascript = ['js/jquery/jquery-ui-timepicker-addon.min.js', - 'js/jquery/jquery-ui-sliderAccess.min.js', - 'js/jquery/datepicker-config.js'] - - return ConfigCollection(title=ugettext_noop('Agenda'), - url='agenda', - weight=20, - variables=(agenda_start_event_date_time, - agenda_show_last_speakers, - agenda_couple_countdown_and_speakers, - agenda_number_prefix, - agenda_numeral_system), - extra_context={'extra_stylefiles': extra_stylefiles, - 'extra_javascript': extra_javascript}) + input_type='choice', + label=ugettext_lazy('Numeral system for agenda items'), + choices=( + {'value': 'arabic', 'display_name': ugettext_lazy('Arabic')}, + {'value': 'roman', 'display_name': ugettext_lazy('Roman')}), + weight=250, + group=ugettext_lazy('Agenda')) def listen_to_related_object_delete_signal(sender, instance, **kwargs): """ - Receiver function to changed agenda items of a related items that is to + Receiver function to change agenda items of a related item that is to be deleted. It is connected to the signal django.db.models.signals.pre_delete during app loading. """ if hasattr(instance, 'get_agenda_title'): for item in Item.objects.filter(content_type=ContentType.objects.get_for_model(sender), object_id=instance.pk): - item.title = '< Item for deleted slide (%s) >' % instance.get_agenda_title() + item.title = '< Item for deleted (%s) >' % instance.get_agenda_title() item.content_type = None item.object_id = None item.save() diff --git a/openslides/assignments/signals.py b/openslides/assignments/signals.py index 71d5973ad..bd74bc997 100644 --- a/openslides/assignments/signals.py +++ b/openslides/assignments/signals.py @@ -1,97 +1,91 @@ -from django import forms +from django.core.validators import MinValueValidator from django.utils.translation import ugettext as _ -from django.utils.translation import ugettext_lazy, ugettext_noop +from django.utils.translation import ugettext_lazy -from openslides.config.api import ( - ConfigGroup, - ConfigGroupedCollection, - ConfigVariable, -) +from openslides.config.api import ConfigVariable from openslides.poll.models import PERCENT_BASE_CHOICES def setup_assignment_config(sender, **kwargs): """ - Receiver function to setup all assignment config variables. It is + Receiver function to setup all assignment config variables. They are + grouped in 'Ballot and ballot papers' and 'PDF'. This function is connected to the signal openslides.config.signals.config_signal during app loading. """ # Ballot and ballot papers - assignments_poll_vote_values = ConfigVariable( + + yield ConfigVariable( name='assignments_poll_vote_values', default_value='auto', - form_field=forms.ChoiceField( - widget=forms.Select(), - required=False, - label=ugettext_lazy('Election method'), - choices=( - ('auto', ugettext_lazy('Automatic assign of method')), - ('votes', ugettext_lazy('Always one option per candidate')), - ('yesnoabstain', ugettext_lazy('Always Yes-No-Abstain per candidate'))))) - assignments_poll_100_percent_base = ConfigVariable( + input_type='choice', + label=ugettext_lazy('Election method'), + choices=( + {'value': 'auto', 'display_name': ugettext_lazy('Automatic assign of method')}, + {'value': 'votes', 'display_name': ugettext_lazy('Always one option per candidate')}, + {'value': 'yesnoabstain', 'display_name': ugettext_lazy('Always Yes-No-Abstain per candidate')}), + weight=410, + group=ugettext_lazy('Elections'), + subgroup=ugettext_lazy('Ballot and ballot papers')) + + yield ConfigVariable( name='assignments_poll_100_percent_base', default_value='WITHOUT_INVALID', - form_field=forms.ChoiceField( - widget=forms.Select(), - required=False, - label=ugettext_lazy('The 100 % base of an election result consists of'), - choices=PERCENT_BASE_CHOICES)) - assignments_pdf_ballot_papers_selection = ConfigVariable( + input_type='choice', + label=ugettext_lazy('The 100 % base of an election result consists of'), + choices=PERCENT_BASE_CHOICES, + weight=420, + group=ugettext_lazy('Elections'), + subgroup=ugettext_lazy('Ballot and ballot papers')) + + yield ConfigVariable( name='assignments_pdf_ballot_papers_selection', default_value='CUSTOM_NUMBER', - form_field=forms.ChoiceField( - widget=forms.Select(), - required=False, - label=ugettext_lazy('Number of ballot papers (selection)'), - choices=( - ('NUMBER_OF_DELEGATES', ugettext_lazy('Number of all delegates')), - ('NUMBER_OF_ALL_PARTICIPANTS', ugettext_lazy('Number of all participants')), - ('CUSTOM_NUMBER', ugettext_lazy('Use the following custom number'))))) - assignments_pdf_ballot_papers_number = ConfigVariable( + input_type='choice', + label=ugettext_lazy('Number of ballot papers (selection)'), + choices=( + {'value': 'NUMBER_OF_DELEGATES', 'display_name': ugettext_lazy('Number of all delegates')}, + {'value': 'NUMBER_OF_ALL_PARTICIPANTS', 'display_name': ugettext_lazy('Number of all participants')}, + {'value': 'CUSTOM_NUMBER', 'display_name': ugettext_lazy('Use the following custom number')}), + weight=430, + group=ugettext_lazy('Elections'), + subgroup=ugettext_lazy('Ballot and ballot papers')) + + yield ConfigVariable( name='assignments_pdf_ballot_papers_number', default_value=8, - form_field=forms.IntegerField( - widget=forms.TextInput(attrs={'class': 'small-input'}), - required=False, - min_value=1, - label=ugettext_lazy('Custom number of ballot papers'))) - assignments_publish_winner_results_only = ConfigVariable( + input_type='integer', + label=ugettext_lazy('Custom number of ballot papers'), + weight=440, + group=ugettext_lazy('Elections'), + subgroup=ugettext_lazy('Ballot and ballot papers'), + validators=(MinValueValidator(1),)) + + yield ConfigVariable( name='assignments_publish_winner_results_only', default_value=False, - form_field=forms.BooleanField( - required=False, - label=ugettext_lazy('Publish election result for elected candidates only ' - '(projector view)'))) - group_ballot = ConfigGroup( - title=ugettext_lazy('Ballot and ballot papers'), - variables=(assignments_poll_vote_values, - assignments_poll_100_percent_base, - assignments_pdf_ballot_papers_selection, - assignments_pdf_ballot_papers_number, - assignments_publish_winner_results_only)) + input_type='boolean', + label=ugettext_lazy('Publish election result for elected candidates only ' + '(projector view)'), + weight=450, + group=ugettext_lazy('Elections'), + subgroup=ugettext_lazy('Ballot and ballot papers')) # PDF - assignments_pdf_title = ConfigVariable( + + yield ConfigVariable( name='assignments_pdf_title', default_value=_('Elections'), - translatable=True, - form_field=forms.CharField( - widget=forms.TextInput(), - required=False, - label=ugettext_lazy('Title for PDF document (all elections)'))) - assignments_pdf_preamble = ConfigVariable( + label=ugettext_lazy('Title for PDF document (all elections)'), + weight=460, + group=ugettext_lazy('Elections'), + subgroup=ugettext_lazy('PDF'), + translatable=True) + + yield ConfigVariable( name='assignments_pdf_preamble', default_value='', - form_field=forms.CharField( - widget=forms.Textarea(), - required=False, - label=ugettext_lazy('Preamble text for PDF document (all elections)'))) - group_pdf = ConfigGroup( - title=ugettext_lazy('PDF'), - variables=(assignments_pdf_title, assignments_pdf_preamble)) - - return ConfigGroupedCollection( - title=ugettext_noop('Elections'), - url='assignment', - weight=40, - groups=(group_ballot, group_pdf)) + label=ugettext_lazy('Preamble text for PDF document (all elections)'), + weight=470, + group=ugettext_lazy('Elections'), + subgroup=ugettext_lazy('PDF')) diff --git a/openslides/config/api.py b/openslides/config/api.py index 1498ec0ef..07148c78d 100644 --- a/openslides/config/api.py +++ b/openslides/config/api.py @@ -1,74 +1,39 @@ +from django.core.exceptions import ValidationError as DjangoValidationError +from django.utils.translation import ugettext as _ + from .exceptions import ConfigError, ConfigNotFound from .models import ConfigStore from .signals import config_signal +INPUT_TYPE_MAPPING = { + 'string': str, + 'integer': int, + 'boolean': bool, + 'choice': str} -class ConfigHandler(object): + +class ConfigHandler: """ - An simple object class to wrap the config variables. It is a container + A 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): + """ + Returns the value of the config variable. Builds the cache if it + does not exist. + """ try: return self._cache[key] except KeyError: - raise ConfigNotFound('The config variable %s was not found.' % key) + raise ConfigNotFound(_('The config variable %s was not found.') % key) except AttributeError: self.setup_cache() return self[key] - def __setitem__(self, key, value): - # Check if the variable is defined. - if key not in self: - raise ConfigNotFound('The config variable %s was not found.' % key) - - # Save the new value to the database. - updated_rows = ConfigStore.objects.filter(key=key).update(value=value) - if not updated_rows: - ConfigStore.objects.create(key=key, value=value) - - # Update cache. - self._cache[key] = value - - # Call on_change callback. - if self.get_config_variables()[key].on_change: - self.get_config_variables()[key].on_change() - - def items(self): - """ - Returns key-value pairs of all config variables. - """ - if not hasattr(self, '_cache'): - self.setup_cache() - return self._cache.items() - - def get_config_variables(self): - """ - Returns a dictionary with all ConfigVariable instances of all - collections. The key is the name of the config variables. - """ - result = {} - for receiver, config_collection in config_signal.send(sender='get_config_variables'): - for config_variable in config_collection.variables: - if config_variable.name in result: - raise ConfigError('Too many values for config variable %s found.' % config_variable.name) - result[config_variable.name] = config_variable - return result - - def get_default(self, key): - """ - Returns the default value for 'key'. - """ - try: - return self.get_config_variables()[key].default_value - except KeyError: - raise ConfigNotFound('The config variable %s was not found.' % key) - def setup_cache(self): """ - Loads all config variables from the database by sending a signal to - save the default to the cache. + Creates a cache of all config variables with their current value. """ self._cache = {} for key, config_variable in self.get_config_variables().items(): @@ -84,6 +49,62 @@ class ConfigHandler(object): else: return True + def __setitem__(self, key, value): + """ + Sets the new value. First it validates the input. + """ + # Check if the variable is defined. + try: + config_variable = config.get_config_variables()[key] + except KeyError: + raise ConfigNotFound(_('The config variable %s was not found.') % key) + + # Validate datatype and run validators. + expected_type = INPUT_TYPE_MAPPING[config_variable.input_type] + if not isinstance(value, expected_type): + raise ConfigError(_('Wrong datatype. Expected %s, got %s.') % (expected_type, type(value))) + if config_variable.input_type == 'choice' and value not in map(lambda choice: choice['value'], config_variable.choices): + raise ConfigError(_('Invalid input. Choice does not match.')) + for validator in config_variable.validators: + try: + validator(value) + except DjangoValidationError as e: + raise ConfigError(e.messages[0]) + + # Save the new value to the database. + updated_rows = ConfigStore.objects.filter(key=key).update(value=value) + if not updated_rows: + ConfigStore.objects.create(key=key, value=value) + + # Update cache. + if hasattr(self, '_cache'): + self._cache[key] = value + + # Call on_change callback. + if config_variable.on_change: + config_variable.on_change() + + def items(self): + """ + Returns key-value pairs of all config variables. + """ + if not hasattr(self, '_cache'): + self.setup_cache() + return self._cache.items() + + def get_config_variables(self): + """ + Returns a dictionary with all ConfigVariable instances of all + signal receivers. The key is the name of the config variable. + """ + result = {} + for receiver, config_collection in config_signal.send(sender='get_config_variables'): + for config_variable in config_collection: + if config_variable.name in result: + raise ConfigError(_('Too many values for config variable %s found.') % config_variable.name) + result[config_variable.name] = config_variable + return result + def get_all_translatable(self): """ Generator to get all config variables as strings when their values are @@ -100,92 +121,67 @@ use x = config[...], to set it use config[...] = x. """ -class ConfigBaseCollection(object): +class ConfigVariable: """ - An abstract base class for simple and grouped config collections. The - attributes title and url are required for collections that should be - shown as a view. 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. + A simple object class to wrap new config variables. + + The keyword arguments 'name' and 'default_value' are required. + + The keyword arguments 'input_type', 'label' and 'help_text' are for + rendering a HTML form element. If you set 'input_type' to 'choice' you + have to provide 'choices', which is a list of dictionaries containing a + value and a display_name of every possible choice. + + The keyword arguments 'weight', 'group' and 'subgroup' are for sorting + and grouping. + + The keyword argument validators expects an interable of validator + functions. Such a function gets the value and raises Django's + ValidationError if the value is invalid. + + The keyword argument 'on_change' can be a callback which is called + every time, the variable is changed. + + If the argument 'translatable' is set, OpenSlides is able to translate + the value during setup of the database if the admin uses the respective + command line option. """ - def __init__(self, title=None, url=None, weight=0, extra_context={}): - self.title = title - self.url = url - self.weight = weight - self.extra_context = extra_context - - def is_shown(self): - """ - Returns True if at least one variable of the collection has a form field. - """ - for variable in self.variables: - if variable.form_field is not None: - is_shown = True - break - else: - is_shown = False - if is_shown and (self.title is None or self.url is None): - raise ConfigError('The config collection %s must have a title and an url attribute.' % self) - return is_shown - - -class ConfigGroupedCollection(ConfigBaseCollection): - """ - A simple object class for a grouped config collection. Developers have to - set the groups attribute (tuple). The config variables are available - via the variables attribute. The collection is shown as a view via the config - main menu entry if there is at least one variable with a form field. - """ - def __init__(self, groups, **kwargs): - self.groups = groups - super(ConfigGroupedCollection, self).__init__(**kwargs) - - @property - def variables(self): - for group in self.groups: - for variable in group.variables: - yield variable - - -class ConfigCollection(ConfigBaseCollection): - """ - A simple object class for a ungrouped config collection. Developers have - to set the variables (tuple) directly. The collection is shown as a view via - the config main menu entry if there is at least one variable with a - form field. - """ - def __init__(self, variables, **kwargs): - self.variables = variables - super(ConfigCollection, 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. The argument 'on_change' can get a callback - which is called every time, the variable is changed. If the argument - 'translatable' is set, OpenSlides is able to translate the value during - setup of the database if the admin uses the respective command line option. - """ - def __init__(self, name, default_value, form_field=None, on_change=None, translatable=False): + def __init__(self, name, default_value, input_type='string', label=None, + help_text=None, choices=None, weight=0, group=None, + subgroup=None, validators=None, on_change=None, translatable=False): + if input_type not in INPUT_TYPE_MAPPING: + raise ValueError(_('Invalid value for config attribute input_type.')) + if input_type == 'choice' and choices is None: + raise ConfigError(_("Either config attribute 'choices' must not be None or " + "'input_type' must not be 'choice'.")) + elif input_type != 'choice' and choices is not None: + raise ConfigError(_("Either config attribute 'choices' must be None or " + "'input_type' must be 'choice'.")) self.name = name self.default_value = default_value - self.form_field = form_field + self.input_type = input_type + self.label = label or name + self.help_text = help_text or '' + self.choices = choices + self.weight = weight + self.group = group or _('General') + self.subgroup = subgroup + self.validators = validators or () self.on_change = on_change self.translatable = translatable + + @property + def data(self): + """ + Property with all data for OPTIONS requests. + """ + data = { + 'key': self.name, + 'value': config[self.name], + 'input_type': self.input_type, + 'label': self.label, + 'help_text': self.help_text + } + if self.input_type == 'choice': + data['choices'] = self.choices + return data diff --git a/openslides/config/views.py b/openslides/config/views.py index 7ebc7f5a0..bfd2768be 100644 --- a/openslides/config/views.py +++ b/openslides/config/views.py @@ -1,39 +1,72 @@ -from django.core.exceptions import ValidationError as DjangoValidationError +from collections import OrderedDict +from operator import attrgetter + from django.http import Http404 -from openslides.utils.rest_api import Response, ValidationError, ViewSet +from openslides.utils.rest_api import ( + Response, + SimpleMetadata, + ValidationError, + ViewSet, +) from .api import config -from .exceptions import ConfigNotFound +from .exceptions import ConfigError, ConfigNotFound + + +class ConfigMetadata(SimpleMetadata): + """ + Custom metadata class to add config info to responses on OPTIONS requests. + """ + def determine_metadata(self, request, view): + # Sort config variables by weight. + config_variables = sorted(config.get_config_variables().values(), key=attrgetter('weight')) + + # Build tree. + config_groups = [] + for config_variable in config_variables: + if not config_groups or config_groups[-1]['name'] != config_variable.group: + config_groups.append(OrderedDict( + name=config_variable.group, + subgroups=[])) + if not config_groups[-1]['subgroups'] or config_groups[-1]['subgroups'][-1]['name'] != config_variable.subgroup: + config_groups[-1]['subgroups'].append(OrderedDict( + name=config_variable.subgroup, + items=[])) + config_groups[-1]['subgroups'][-1]['items'].append(config_variable.data) + + # Add tree to metadata. + metadata = super().determine_metadata(request, view) + metadata['config_groups'] = config_groups + return metadata class ConfigViewSet(ViewSet): """ API endpoint to list, retrieve and update the config. """ + metadata_class = ConfigMetadata + def list(self, request): """ Lists all config variables. Everybody can see them. """ - # TODO: Check if we need permission check here. - data = ({'key': key, 'value': value} for key, value in config.items()) - return Response(data) + return Response([{'key': key, 'value': value} for key, value in config.items()]) def retrieve(self, request, *args, **kwargs): """ - Retrieves one config variable. Everybody can see it. + Retrieves a config variable. Everybody can see it. """ - # TODO: Check if we need permission check here. key = kwargs['pk'] try: - data = {'key': key, 'value': config[key]} + value = config[key] except ConfigNotFound: raise Http404 - return Response(data) + return Response({'key': key, 'value': value}) def update(self, request, *args, **kwargs): """ - Updates one config variable. Only managers can do this. + Updates a config variable. Only managers can do this. Example: {"value": 42} """ @@ -41,22 +74,16 @@ class ConfigViewSet(ViewSet): if not request.user.has_perm('config.can_manage'): self.permission_denied(request) - # Check if pk is a valid config variable key. key = kwargs['pk'] - if key not in config: - raise Http404 - - # Validate value. - form_field = config.get_config_variables()[key].form_field value = request.data['value'] - if form_field: - try: - form_field.clean(value) - except DjangoValidationError as e: - raise ValidationError({'detail': e.messages[0]}) - # Change value. - config[key] = value + # Validate and change value. + try: + config[key] = value + except ConfigNotFound: + raise Http404 + except ConfigError as e: + raise ValidationError({'detail': e}) # Return response. return Response({'key': key, 'value': value}) diff --git a/openslides/core/signals.py b/openslides/core/signals.py index e073839dc..b57c74a30 100644 --- a/openslides/core/signals.py +++ b/openslides/core/signals.py @@ -1,13 +1,9 @@ -from django import forms +from django.core.validators import MaxLengthValidator from django.dispatch import Signal from django.utils.translation import ugettext as _ -from django.utils.translation import ugettext_lazy, ugettext_noop +from django.utils.translation import ugettext_lazy -from openslides.config.api import ( - ConfigGroup, - ConfigGroupedCollection, - ConfigVariable, -) +from openslides.config.api import ConfigVariable # This signal is sent when the migrate command is done. That means it is sent # after post_migrate sending and creating all Permission objects. Don't use it @@ -18,147 +14,123 @@ post_permission_creation = Signal() def setup_general_config(sender, **kwargs): """ Receiver function to setup general config variables for OpenSlides. - They are grouped in 'Event', 'Projector' and 'System'. This function is - connected to the signal openslides.config.signals.config_signal during - app loading. + There are two main groups: 'General' and 'Projector'. The group + 'General' has subgroups. This function is connected to the signal + openslides.config.signals.config_signal during app loading. """ - general_event_name = ConfigVariable( + # General Event + + yield ConfigVariable( name='general_event_name', default_value='OpenSlides', - form_field=forms.CharField( - widget=forms.TextInput(), - label=ugettext_lazy('Event name'), - max_length=50)) + label=ugettext_lazy('Event name'), + weight=110, + group=ugettext_lazy('General'), + subgroup=ugettext_lazy('Event'), + validators=(MaxLengthValidator(50),)) - general_event_description = ConfigVariable( + yield ConfigVariable( name='general_event_description', default_value=_('Presentation and assembly system'), - translatable=True, - form_field=forms.CharField( - widget=forms.TextInput(), - label=ugettext_lazy('Short description of event'), - required=False, - max_length=100)) + label=ugettext_lazy('Short description of event'), + weight=115, + group=ugettext_lazy('General'), + subgroup=ugettext_lazy('Event'), + validators=(MaxLengthValidator(100),), + translatable=True) - general_event_date = ConfigVariable( + yield ConfigVariable( name='general_event_date', default_value='', - form_field=forms.CharField( - widget=forms.TextInput(), - label=ugettext_lazy('Event date'), - required=False)) + label=ugettext_lazy('Event date'), + weight=120, + group=ugettext_lazy('General'), + subgroup=ugettext_lazy('Event')) - general_event_location = ConfigVariable( + yield ConfigVariable( name='general_event_location', default_value='', - form_field=forms.CharField( - widget=forms.TextInput(), - label=ugettext_lazy('Event location'), - required=False)) + label=ugettext_lazy('Event location'), + weight=125, + group=ugettext_lazy('General'), + subgroup=ugettext_lazy('Event')) # TODO: Check whether this variable is ever used. - general_event_organizer = ConfigVariable( + yield ConfigVariable( name='general_event_organizer', default_value='', - form_field=forms.CharField( - widget=forms.TextInput(), - label=ugettext_lazy('Event organizer'), - required=False)) + label=ugettext_lazy('Event organizer'), + weight=130, + group=ugettext_lazy('General'), + subgroup=ugettext_lazy('Event')) - general_system_enable_anonymous = ConfigVariable( + # General System + + yield ConfigVariable( name='general_system_enable_anonymous', default_value=False, - form_field=forms.BooleanField( - label=ugettext_lazy('Allow access for anonymous guest users'), - required=False)) + input_type='boolean', + label=ugettext_lazy('Allow access for anonymous guest users'), + weight=135, + group=ugettext_lazy('General'), + subgroup=ugettext_lazy('System')) - projector_enable_logo = ConfigVariable( + # Projector + + yield ConfigVariable( name='projector_enable_logo', default_value=True, - form_field=forms.BooleanField( - label=ugettext_lazy('Show logo on projector'), - help_text=ugettext_lazy('You can find and replace the logo under "openslides/projector/static/img/logo-projector.png".'), - required=False)) + input_type='boolean', + label=ugettext_lazy('Show logo on projector'), + help_text=ugettext_lazy('You can find and replace the logo under "openslides/core/static/...".'), # TODO: Update path. + weight=150, + group=ugettext_lazy('Projector')) - projector_enable_title = ConfigVariable( + yield ConfigVariable( name='projector_enable_title', default_value=True, - form_field=forms.BooleanField( - label=ugettext_lazy('Show title and description of event on projector'), - required=False)) + input_type='boolean', + label=ugettext_lazy('Show title and description of event on projector'), + weight=155, + group=ugettext_lazy('Projector')) - projector_backgroundcolor1 = ConfigVariable( + yield ConfigVariable( name='projector_backgroundcolor1', default_value='#444444', - form_field=forms.CharField( - widget=forms.TextInput(), - label=ugettext_lazy('Background color of projector header'), - help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'), - required=True)) + label=ugettext_lazy('Background color of projector header'), + help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'), + weight=160, + group=ugettext_lazy('Projector')) - projector_backgroundcolor2 = ConfigVariable( + yield ConfigVariable( name='projector_backgroundcolor2', default_value='#222222', - form_field=forms.CharField( - widget=forms.TextInput(), - label=ugettext_lazy('Second (optional) background color for linear color gradient'), - help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'), - required=False)) + label=ugettext_lazy('Second (optional) background color for linear color gradient'), + help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'), + weight=165, + group=ugettext_lazy('Projector')) - projector_fontcolor = ConfigVariable( + yield ConfigVariable( name='projector_fontcolor', default_value='#F5F5F5', - form_field=forms.CharField( - widget=forms.TextInput(), - label=ugettext_lazy('Font color of projector header'), - help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'), - required=True)) + label=ugettext_lazy('Font color of projector header'), + help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'), + weight=170, + group=ugettext_lazy('Projector')) - projector_welcome_title = ConfigVariable( + yield ConfigVariable( name='projector_welcome_title', default_value=_('Welcome to OpenSlides'), - translatable=True, - form_field=forms.CharField( - widget=forms.TextInput(), - label=ugettext_lazy('Title'), - help_text=ugettext_lazy('Also used for the default welcome slide.'), - required=False)) + label=ugettext_lazy('Title'), + help_text=ugettext_lazy('Also used for the default welcome slide.'), + weight=175, + group=ugettext_lazy('Projector'), + translatable=True) - projector_welcome_text = ConfigVariable( + yield ConfigVariable( name='projector_welcome_text', - default_value=_('[Place for your welcome text.]'), - translatable=True, - form_field=forms.CharField( - widget=forms.Textarea(), - label=ugettext_lazy('Welcome text'), - required=False)) - - group_event = ConfigGroup( - title=ugettext_lazy('Event'), - variables=( - general_event_name, - general_event_description, - general_event_date, - general_event_location, - general_event_organizer)) - - group_system = ConfigGroup( - title=ugettext_lazy('System'), - variables=(general_system_enable_anonymous,)) - - group_projector = ConfigGroup( - title=ugettext_lazy('Projector'), - variables=( - projector_enable_logo, - projector_enable_title, - projector_backgroundcolor1, - projector_backgroundcolor2, - projector_fontcolor, - projector_welcome_title, - projector_welcome_text)) - - return ConfigGroupedCollection( - title=ugettext_noop('General'), - url='general', - weight=10, - groups=(group_event, group_system, group_projector)) + default_value=_('[Space for your welcome text.]'), + label=ugettext_lazy('Welcome text'), + weight=180, + group=ugettext_lazy('Projector'), + translatable=True) diff --git a/openslides/motions/signals.py b/openslides/motions/signals.py index 20eaec464..3d3edb21b 100644 --- a/openslides/motions/signals.py +++ b/openslides/motions/signals.py @@ -1,12 +1,8 @@ -from django import forms +from django.core.validators import MinValueValidator from django.utils.translation import ugettext as _ from django.utils.translation import pgettext, ugettext_lazy, ugettext_noop -from openslides.config.api import ( - ConfigGroup, - ConfigGroupedCollection, - ConfigVariable, -) +from openslides.config.api import ConfigVariable from openslides.poll.models import PERCENT_BASE_CHOICES from .models import State, Workflow @@ -14,166 +10,165 @@ from .models import State, Workflow def setup_motion_config(sender, **kwargs): """ - Receiver function to setup all motion config variables. It is connected to - the signal openslides.config.signals.config_signal during app loading. + Receiver function to setup all motion config variables. They are + grouped in 'General', 'Amendments', 'Supporters', 'Voting and ballot + papers' and 'PDF'. This function connected to the signal + openslides.config.signals.config_signal during app loading. """ # General - motions_workflow = ConfigVariable( + + yield ConfigVariable( name='motions_workflow', default_value='1', - form_field=forms.ChoiceField( - widget=forms.Select(), - label=ugettext_lazy('Workflow of new motions'), - required=True, - choices=[(str(workflow.pk), ugettext_lazy(workflow.name)) for workflow in Workflow.objects.all()])) - motions_identifier = ConfigVariable( + input_type='choice', + label=ugettext_lazy('Workflow of new motions'), + choices=({'value': str(workflow.pk), 'display_name': ugettext_lazy(workflow.name)} for workflow in Workflow.objects.all()), + weight=310, + group=ugettext_lazy('Motion'), + subgroup=ugettext_lazy('General')) + + yield ConfigVariable( name='motions_identifier', default_value='per_category', - form_field=forms.ChoiceField( - widget=forms.Select(), - required=True, - label=ugettext_lazy('Identifier'), - choices=[ - ('per_category', ugettext_lazy('Numbered per category')), - ('serially_numbered', ugettext_lazy('Serially numbered')), - ('manually', ugettext_lazy('Set it manually'))])) - motions_preamble = ConfigVariable( + input_type='choice', + label=ugettext_lazy('Identifier'), + choices=( + {'value': 'per_category', 'display_name': ugettext_lazy('Numbered per category')}, + {'value': 'serially_numbered', 'display_name': ugettext_lazy('Serially numbered')}, + {'value': 'manually', 'display_name': ugettext_lazy('Set it manually')}), + weight=315, + group=ugettext_lazy('Motion'), + subgroup=ugettext_lazy('General')) + + yield ConfigVariable( name='motions_preamble', default_value=_('The assembly may decide,'), - translatable=True, - form_field=forms.CharField( - widget=forms.TextInput(), - required=False, - label=ugettext_lazy('Motion preamble'))) - motions_stop_submitting = ConfigVariable( + label=ugettext_lazy('Motion preamble'), + weight=320, + group=ugettext_lazy('Motion'), + subgroup=ugettext_lazy('General'), + translatable=True) + + yield ConfigVariable( name='motions_stop_submitting', default_value=False, - form_field=forms.BooleanField( - label=ugettext_lazy('Stop submitting new motions by non-staff users'), - required=False)) - motions_allow_disable_versioning = ConfigVariable( + input_type='boolean', + label=ugettext_lazy('Stop submitting new motions by non-staff users'), + weight=325, + group=ugettext_lazy('Motion'), + subgroup=ugettext_lazy('General')) + + yield ConfigVariable( name='motions_allow_disable_versioning', default_value=False, - form_field=forms.BooleanField( - label=ugettext_lazy('Allow to disable versioning'), - required=False)) - group_general = ConfigGroup( - title=ugettext_lazy('General'), - variables=( - motions_workflow, - motions_identifier, - motions_preamble, - motions_stop_submitting, - motions_allow_disable_versioning)) + input_type='boolean', + label=ugettext_lazy('Allow to disable versioning'), + weight=330, + group=ugettext_lazy('Motion'), + subgroup=ugettext_lazy('General')) # Amendments - motions_amendments_enabled = ConfigVariable( + + yield ConfigVariable( name='motions_amendments_enabled', default_value=False, - form_field=forms.BooleanField( - label=ugettext_lazy('Activate amendments'), - required=False)) + input_type='boolean', + label=ugettext_lazy('Activate amendments'), + weight=335, + group=ugettext_lazy('Motion'), + subgroup=ugettext_lazy('Amendments')) - motions_amendments_prefix = ConfigVariable( + yield ConfigVariable( name='motions_amendments_prefix', default_value=pgettext('Prefix for the identifier for amendments', 'A'), - form_field=forms.CharField( - required=False, - label=ugettext_lazy('Prefix for the identifier for amendments'))) - - group_amendments = ConfigGroup( - title=ugettext_lazy('Amendments'), - variables=(motions_amendments_enabled, motions_amendments_prefix)) + label=ugettext_lazy('Prefix for the identifier for amendments'), + weight=340, + group=ugettext_lazy('Motion'), + subgroup=ugettext_lazy('Amendments')) # Supporters - motions_min_supporters = ConfigVariable( + + yield ConfigVariable( name='motions_min_supporters', default_value=0, - form_field=forms.IntegerField( - widget=forms.TextInput(attrs={'class': 'small-input'}), - label=ugettext_lazy('Number of (minimum) required supporters for a motion'), - min_value=0, - help_text=ugettext_lazy('Choose 0 to disable the supporting system.'))) - motions_remove_supporters = ConfigVariable( + input_type='integer', + label=ugettext_lazy('Number of (minimum) required supporters for a motion'), + help_text=ugettext_lazy('Choose 0 to disable the supporting system.'), + weight=345, + group=ugettext_lazy('Motion'), + subgroup=ugettext_lazy('Supporters'), + validators=(MinValueValidator(0),)) + + yield ConfigVariable( name='motions_remove_supporters', default_value=False, - form_field=forms.BooleanField( - label=ugettext_lazy('Remove all supporters of a motion if a submitter edits his motion in early state'), - required=False)) - group_supporters = ConfigGroup( - title=ugettext_lazy('Supporters'), - variables=(motions_min_supporters, motions_remove_supporters)) + input_type='boolean', + label=ugettext_lazy('Remove all supporters of a motion if a submitter edits his motion in early state'), + weight=350, + group=ugettext_lazy('Motion'), + subgroup=ugettext_lazy('Supporters')) # Voting and ballot papers - motions_poll_100_percent_base = ConfigVariable( + + yield ConfigVariable( name='motions_poll_100_percent_base', default_value='WITHOUT_INVALID', - form_field=forms.ChoiceField( - widget=forms.Select(), - required=False, - label=ugettext_lazy('The 100 % base of a voting result consists of'), - choices=PERCENT_BASE_CHOICES)) - motions_pdf_ballot_papers_selection = ConfigVariable( + input_type='choice', + label=ugettext_lazy('The 100 % base of a voting result consists of'), + choices=PERCENT_BASE_CHOICES, + weight=355, + group=ugettext_lazy('Motion'), + subgroup=ugettext_lazy('Voting and ballot papers')) + + yield ConfigVariable( name='motions_pdf_ballot_papers_selection', default_value='CUSTOM_NUMBER', - form_field=forms.ChoiceField( - widget=forms.Select(), - required=False, - label=ugettext_lazy('Number of ballot papers (selection)'), - choices=[ - ('NUMBER_OF_DELEGATES', ugettext_lazy('Number of all delegates')), - ('NUMBER_OF_ALL_PARTICIPANTS', ugettext_lazy('Number of all participants')), - ('CUSTOM_NUMBER', ugettext_lazy("Use the following custom number"))])) - motions_pdf_ballot_papers_number = ConfigVariable( + input_type='choice', + label=ugettext_lazy('Number of ballot papers (selection)'), + choices=( + {'value': 'NUMBER_OF_DELEGATES', 'display_name': ugettext_lazy('Number of all delegates')}, + {'value': 'NUMBER_OF_ALL_PARTICIPANTS', 'display_name': ugettext_lazy('Number of all participants')}, + {'value': 'CUSTOM_NUMBER', 'display_name': ugettext_lazy('Use the following custom number')}), + weight=360, + group=ugettext_lazy('Motion'), + subgroup=ugettext_lazy('Voting and ballot papers')) + + yield ConfigVariable( name='motions_pdf_ballot_papers_number', default_value=8, - form_field=forms.IntegerField( - widget=forms.TextInput(attrs={'class': 'small-input'}), - required=False, - min_value=1, - label=ugettext_lazy('Custom number of ballot papers'))) - group_ballot_papers = ConfigGroup( - title=ugettext_lazy('Voting and ballot papers'), - variables=( - motions_poll_100_percent_base, - motions_pdf_ballot_papers_selection, - motions_pdf_ballot_papers_number)) + input_type='integer', + label=ugettext_lazy('Custom number of ballot papers'), + weight=365, + group=ugettext_lazy('Motion'), + subgroup=ugettext_lazy('Voting and ballot papers'), + validators=(MinValueValidator(1),)) # PDF - motions_pdf_title = ConfigVariable( + + yield ConfigVariable( name='motions_pdf_title', default_value=_('Motions'), - translatable=True, - form_field=forms.CharField( - widget=forms.TextInput(), - required=False, - label=ugettext_lazy('Title for PDF document (all motions)'))) - motions_pdf_preamble = ConfigVariable( + label=ugettext_lazy('Title for PDF document (all motions)'), + weight=370, + group=ugettext_lazy('Motion'), + subgroup=ugettext_lazy('PDF'), + translatable=True) + + yield ConfigVariable( name='motions_pdf_preamble', default_value='', - form_field=forms.CharField( - widget=forms.Textarea(), - required=False, - label=ugettext_lazy('Preamble text for PDF document (all motions)'))) - motions_pdf_paragraph_numbering = ConfigVariable( + label=ugettext_lazy('Preamble text for PDF document (all motions)'), + weight=375, + group=ugettext_lazy('Motion'), + subgroup=ugettext_lazy('PDF')) + + yield ConfigVariable( name='motions_pdf_paragraph_numbering', default_value=False, - form_field=forms.BooleanField( - label=ugettext_lazy('Show paragraph numbering (only in PDF)'), - required=False)) - group_pdf = ConfigGroup( - title=ugettext_lazy('PDF'), - variables=( - motions_pdf_title, - motions_pdf_preamble, - motions_pdf_paragraph_numbering)) - - return ConfigGroupedCollection( - title=ugettext_noop('Motion'), - url='motion', - weight=30, - groups=(group_general, group_amendments, group_supporters, - group_ballot_papers, group_pdf)) + label=ugettext_lazy('Show paragraph numbering (only in PDF)'), + weight=380, + group=ugettext_lazy('Motion'), + subgroup=ugettext_lazy('PDF')) def create_builtin_workflows(sender, **kwargs): diff --git a/openslides/poll/models.py b/openslides/poll/models.py index ca4d8b1fd..3ddc0c267 100644 --- a/openslides/poll/models.py +++ b/openslides/poll/models.py @@ -68,9 +68,9 @@ class BaseVote(models.Model): PERCENT_BASE_CHOICES = ( - ('WITHOUT_INVALID', ugettext_lazy('Only all valid votes')), - ('WITH_INVALID', ugettext_lazy('All votes cast (including invalid votes)')), - ('DISABLED', ugettext_lazy('Disabled (no percents)'))) + {'value': 'WITHOUT_INVALID', 'display_name': ugettext_lazy('Only all valid votes')}, + {'value': 'WITH_INVALID', 'display_name': ugettext_lazy('All votes cast (including invalid votes)')}, + {'value': 'DISABLED', 'display_name': ugettext_lazy('Disabled (no percents)')}) class CollectDefaultVotesMixin(models.Model): diff --git a/openslides/users/signals.py b/openslides/users/signals.py index 05231043a..973c97a27 100644 --- a/openslides/users/signals.py +++ b/openslides/users/signals.py @@ -1,109 +1,93 @@ -from django import forms from django.db.models import Q from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy, ugettext_noop -from openslides.config.api import ( - ConfigGroup, - ConfigGroupedCollection, - ConfigVariable, -) +from openslides.config.api import ConfigVariable from .models import Group, Permission, User def setup_users_config(sender, **kwargs): """ - Receiver function to setup all users config variables. It is connected - to the signal openslides.config.signals.config_signal during app loading. + Receiver function to setup all users config variables. They are grouped + in 'Sorting' and 'PDF'. This function is connected to the signal + openslides.config.signals.config_signal during app loading. """ - # General - users_sort_users_by_first_name = ConfigVariable( + + # Sorting + + yield ConfigVariable( name='users_sort_users_by_first_name', default_value=False, - form_field=forms.BooleanField( - required=False, - label=ugettext_lazy('Sort users by first name'), - help_text=ugettext_lazy('Disable for sorting by last name'))) - - group_general = ConfigGroup( - title=ugettext_lazy('Sorting'), - variables=(users_sort_users_by_first_name,)) + input_type='boolean', + label=ugettext_lazy('Sort users by first name'), + help_text=ugettext_lazy('Disable for sorting by last name'), + weight=510, + group=ugettext_lazy('Users'), + subgroup=ugettext_lazy('Sorting')) # PDF - users_pdf_welcometitle = ConfigVariable( + + yield ConfigVariable( name='users_pdf_welcometitle', default_value=_('Welcome to OpenSlides!'), - translatable=True, - form_field=forms.CharField( - widget=forms.Textarea(), - required=False, - label=ugettext_lazy('Title for access data and welcome PDF'))) + label=ugettext_lazy('Title for access data and welcome PDF'), + weight=520, + group=ugettext_lazy('Users'), + subgroup=ugettext_lazy('PDF'), + translatable=True) - users_pdf_welcometext = ConfigVariable( + yield ConfigVariable( name='users_pdf_welcometext', default_value=_('[Place for your welcome and help text.]'), - translatable=True, - form_field=forms.CharField( - widget=forms.Textarea(), - required=False, - label=ugettext_lazy('Help text for access data and welcome PDF'))) + label=ugettext_lazy('Help text for access data and welcome PDF'), + weight=530, + group=ugettext_lazy('Users'), + subgroup=ugettext_lazy('PDF'), + translatable=True) - users_pdf_url = ConfigVariable( + # TODO: Use Django's URLValidator here. + yield ConfigVariable( name='users_pdf_url', default_value='http://example.com:8000', - form_field=forms.CharField( - widget=forms.TextInput(), - required=False, - label=ugettext_lazy('System URL'), - help_text=ugettext_lazy('Used for QRCode in PDF of access data.'))) + label=ugettext_lazy('System URL'), + help_text=ugettext_lazy('Used for QRCode in PDF of access data.'), + weight=540, + group=ugettext_lazy('Users'), + subgroup=ugettext_lazy('PDF')) - users_pdf_wlan_ssid = ConfigVariable( + yield ConfigVariable( name='users_pdf_wlan_ssid', default_value='', - form_field=forms.CharField( - widget=forms.TextInput(), - required=False, - label=ugettext_lazy('WLAN name (SSID)'), - help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.'))) + label=ugettext_lazy('WLAN name (SSID)'), + help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.'), + weight=550, + group=ugettext_lazy('Users'), + subgroup=ugettext_lazy('PDF')) - users_pdf_wlan_password = ConfigVariable( + yield ConfigVariable( name='users_pdf_wlan_password', default_value='', - form_field=forms.CharField( - widget=forms.TextInput(), - required=False, - label=ugettext_lazy('WLAN password'), - help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.'))) + label=ugettext_lazy('WLAN password'), + help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.'), + weight=560, + group=ugettext_lazy('Users'), + subgroup=ugettext_lazy('PDF')) - users_pdf_wlan_encryption = ConfigVariable( + yield ConfigVariable( name='users_pdf_wlan_encryption', default_value='', - form_field=forms.ChoiceField( - widget=forms.Select(), - required=False, - label=ugettext_lazy('WLAN encryption'), - help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.'), - choices=( - ('', '---------'), - ('WEP', 'WEP'), - ('WPA', 'WPA/WPA2'), - ('nopass', ugettext_lazy('No encryption'))))) - - group_pdf = ConfigGroup( - title=ugettext_lazy('PDF'), - variables=(users_pdf_welcometitle, - users_pdf_welcometext, - users_pdf_url, - users_pdf_wlan_ssid, - users_pdf_wlan_password, - users_pdf_wlan_encryption)) - - return ConfigGroupedCollection( - title=ugettext_noop('Users'), - url='users', - weight=50, - groups=(group_general, group_pdf)) + input_type='choice', + label=ugettext_lazy('WLAN encryption'), + help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.'), + choices=( + {'value': '', 'display_name': '---------'}, + {'value': 'WEP', 'display_name': ugettext_lazy('WEP')}, + {'value': 'WPA', 'display_name': ugettext_lazy('WPA/WPA2')}, + {'value': 'nopass', 'display_name': ugettext_lazy('No encryption')}), + weight=570, + group=ugettext_lazy('Users'), + subgroup=ugettext_lazy('PDF')) def create_builtin_groups_and_admin(**kwargs): diff --git a/openslides/utils/rest_api.py b/openslides/utils/rest_api.py index c57f18724..5de4a2ce3 100644 --- a/openslides/utils/rest_api.py +++ b/openslides/utils/rest_api.py @@ -4,6 +4,7 @@ from urllib.parse import urlparse from django.core.urlresolvers import reverse from rest_framework.decorators import detail_route # noqa from rest_framework.decorators import list_route # noqa +from rest_framework.metadata import SimpleMetadata # noqa from rest_framework.mixins import DestroyModelMixin, UpdateModelMixin # noqa from rest_framework.response import Response # noqa from rest_framework.routers import DefaultRouter diff --git a/tests/integration/config/test_views.py b/tests/integration/config/test_views.py index f72bad8a8..cb66fc40a 100644 --- a/tests/integration/config/test_views.py +++ b/tests/integration/config/test_views.py @@ -1,11 +1,11 @@ -from django import forms from django.core.urlresolvers import reverse from django.dispatch import receiver from rest_framework import status from rest_framework.test import APIClient -from openslides.config.api import ConfigCollection, ConfigVariable, config +from openslides.config.api import ConfigVariable, config from openslides.config.signals import config_signal +from openslides.utils.rest_api import ValidationError from openslides.utils.test import TestCase @@ -38,7 +38,51 @@ class ConfigViewSet(TestCase): reverse('config-detail', args=['test_var_ohhii4iavoh5Phoh5ahg']), {'value': 'test_value_string'}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data, {'detail': 'Enter a whole number.'}) + self.assertEqual(response.data, {'detail': "Wrong datatype. Expected , got ."}) + + def test_update_good_choice(self): + self.client = APIClient() + self.client.login(username='admin', password='admin') + response = self.client.put( + reverse('config-detail', args=['test_var_wei0Rei9ahzooSohK1ph']), + {'value': 'key_2_yahb2ain1aeZ1lea1Pei'}) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(config['test_var_wei0Rei9ahzooSohK1ph'], 'key_2_yahb2ain1aeZ1lea1Pei') + + def test_update_bad_choice(self): + self.client = APIClient() + self.client.login(username='admin', password='admin') + response = self.client.put( + reverse('config-detail', args=['test_var_wei0Rei9ahzooSohK1ph']), + {'value': 'test_value_bad_string'}) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data, {'detail': 'Invalid input. Choice does not match.'}) + + def test_update_validator_ok(self): + self.client = APIClient() + self.client.login(username='admin', password='admin') + response = self.client.put( + reverse('config-detail', args=['test_var_Hi7Oje8Oith7goopeeng']), + {'value': 'valid_string'}) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(config['test_var_Hi7Oje8Oith7goopeeng'], 'valid_string') + + def test_update_validator_invalid(self): + self.client = APIClient() + self.client.login(username='admin', password='admin') + response = self.client.put( + reverse('config-detail', args=['test_var_Hi7Oje8Oith7goopeeng']), + {'value': 'invalid_string'}) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data, {'detail': 'Invalid input.'}) + + +def validator_for_testing(value): + """ + Validator for testing. + """ + if value == 'invalid_string': + raise ValidationError({'detail': 'Invalid input.'}) @receiver(config_signal, dispatch_uid='set_simple_config_view_integration_config_test') @@ -47,14 +91,29 @@ def set_simple_config_view_integration_config_test(sender, **kwargs): Sets a simple config view with some config variables but without grouping. """ - return ConfigCollection( - title='Config vars for testing', - url='test_url_ieXao5Wae5Duoy6Wohtu', - variables=(ConfigVariable(name='test_var_aeW3Quahkah1phahCheo', - default_value=None), - ConfigVariable(name='test_var_Xeiizi7ooH8Thuk5aida', - default_value='', - form_field=forms.CharField()), - ConfigVariable(name='test_var_ohhii4iavoh5Phoh5ahg', - default_value=0, - form_field=forms.IntegerField()))) + yield ConfigVariable( + name='test_var_aeW3Quahkah1phahCheo', + default_value=None, + label='test_label_aeNahsheu8phahk8taYo') + + yield ConfigVariable( + name='test_var_Xeiizi7ooH8Thuk5aida', + default_value='') + + yield ConfigVariable( + name='test_var_ohhii4iavoh5Phoh5ahg', + default_value=0, + input_type='integer') + + yield ConfigVariable( + name='test_var_wei0Rei9ahzooSohK1ph', + default_value='key_1_Queit2juchoocos2Vugh', + input_type='choice', + choices=( + {'value': 'key_1_Queit2juchoocos2Vugh', 'display_name': 'label_1_Queit2juchoocos2Vugh'}, + {'value': 'key_2_yahb2ain1aeZ1lea1Pei', 'display_name': 'label_2_yahb2ain1aeZ1lea1Pei'})) + + yield ConfigVariable( + name='test_var_Hi7Oje8Oith7goopeeng', + default_value='', + validators=(validator_for_testing,)) diff --git a/tests/old/config/test_config.py b/tests/old/config/test_config.py index 33341d20d..b0997abd5 100644 --- a/tests/old/config/test_config.py +++ b/tests/old/config/test_config.py @@ -1,19 +1,8 @@ -from django import forms -from django.contrib.auth.models import Permission -from django.contrib.contenttypes.models import ContentType from django.dispatch import receiver -from django.test.client import Client -from openslides.config.api import ( - ConfigCollection, - ConfigGroup, - ConfigGroupedCollection, - ConfigVariable, - config, -) +from openslides.config.api import ConfigVariable, config from openslides.config.exceptions import ConfigError, ConfigNotFound from openslides.config.signals import config_signal -from openslides.users.models import User from openslides.utils.test import TestCase @@ -86,79 +75,47 @@ class HandleConfigTest(TestCase): value='new_string_kbmbnfhdgibkdjshg452bc') self.assertEqual(config['var_with_callback_ghvnfjd5768gdfkwg0hm2'], 'new_string_kbmbnfhdgibkdjshg452bc') - def test_get_default(self): - """ - Tests the methode 'default'. - """ - self.assertEqual(config.get_default('string_var'), 'default_string_rien4ooCZieng6ah') - self.assertRaisesMessage( - ConfigNotFound, - 'The config variable unknown_var was not found.', - config.get_default, - 'unknown_var') - - -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_user('config_test_manager', '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_views_abstract(self): - config_collection_dict = {} - for signal_receiver, config_collection in config_signal.send(sender=self): - config_collection_dict[signal_receiver.__name__] = config_collection - self.assertGreater(config_collection_dict['set_grouped_config_view'].weight, config_collection_dict['set_simple_config_view'].weight) - @receiver(config_signal, dispatch_uid='set_grouped_config_view_for_testing') def set_grouped_config_view(sender, **kwargs): """ - Sets a grouped config collection view which can be reached under the url - '/config/testgroupedpage1/'. There are some variables, one variable + Sets a grouped config collection. 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. + one with an integer as default value, one with choices and one hidden + variable. These variables are grouped in two subgroups. """ - string_var = ConfigVariable( + yield ConfigVariable( name='string_var', default_value='default_string_rien4ooCZieng6ah', - form_field=forms.CharField()) - bool_var = ConfigVariable( + group='Config vars for testing 1', + subgroup='Group 1 aiYeix2mCieQuae3') + yield ConfigVariable( name='bool_var', default_value=True, - form_field=forms.BooleanField(required=False)) - integer_var = ConfigVariable( + input_type='boolean', + group='Config vars for testing 1', + subgroup='Group 1 aiYeix2mCieQuae3') + yield 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)) + input_type='integer', + group='Config vars for testing 1', + subgroup='Group 1 aiYeix2mCieQuae3') - hidden_var = ConfigVariable( + yield ConfigVariable( name='hidden_var', - default_value='hidden_value') - choices_var = ConfigVariable( + default_value='hidden_value', + group='Config vars for testing 1', + subgroup='Group 2 Toongai7ahyahy7B') + yield 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 ConfigGroupedCollection( - title='Config vars for testing 1', - url='testgroupedpage1', - weight=10000, - groups=(group_1, group_2), - extra_context={'extra_stylefiles': ['styles/test-config-sjNN56dFGDrg2.css'], - 'extra_javascript': ['javascript/test-config-djg4dFGVslk4209f.js']}) + input_type='choice', + choices=( + {'value': '1', 'display_name': 'Choice One Ughoch4ocoche6Ee'}, + {'value': '2', 'display_name': 'Choice Two Vahnoh5yalohv5Eb'}), + group='Config vars for testing 1', + subgroup='Group 2 Toongai7ahyahy7B') @receiver(config_signal, dispatch_uid='set_simple_config_view_for_testing') @@ -167,12 +124,9 @@ def set_simple_config_view(sender, **kwargs): Sets a simple config view with some config variables but without grouping. """ - return ConfigCollection( - title='Config vars for testing 2', - url='testsimplepage1', - 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))) + yield ConfigVariable(name='additional_config_var', default_value='BaeB0ahcMae3feem') + yield ConfigVariable(name='additional_config_var_2', default_value='') + yield ConfigVariable(name='none_config_var', default_value=None) # Do not connect to the signal now but later inside the test. @@ -180,29 +134,20 @@ def set_simple_config_view_multiple_vars(sender, **kwargs): """ Sets a bad config view with some multiple config vars. """ - return ConfigCollection( - title='Config vars for testing 3', - url='testsimplepage2', - variables=(ConfigVariable(name='multiple_config_var', default_value='foobar1'), - ConfigVariable(name='multiple_config_var', default_value='foobar2'))) + yield ConfigVariable(name='multiple_config_var', default_value='foobar1') + yield ConfigVariable(name='multiple_config_var', default_value='foobar2') @receiver(config_signal, dispatch_uid='set_simple_config_collection_disabled_view_for_testing') def set_simple_config_collection_disabled_view(sender, **kwargs): - return ConfigCollection( - title='Ho5iengaoon5Hoht', - url='testsimplepage3', - variables=(ConfigVariable(name='hidden_config_var_2', default_value=''),)) + yield ConfigVariable(name='hidden_config_var_2', default_value='') @receiver(config_signal, dispatch_uid='set_simple_config_collection_with_callback_for_testing') def set_simple_config_collection_with_callback(sender, **kwargs): def callback(): raise Exception('Change callback dhcnfg34dlg06kdg successfully called.') - return ConfigCollection( - title='Hvndfhsbgkridfgdfg', - url='testsimplepage4', - variables=(ConfigVariable( - name='var_with_callback_ghvnfjd5768gdfkwg0hm2', - default_value='', - on_change=callback),)) + yield ConfigVariable( + name='var_with_callback_ghvnfjd5768gdfkwg0hm2', + default_value='', + on_change=callback)