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)