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.
This commit is contained in:
parent
4506d787ee
commit
c5fbe2e9ee
@ -1,12 +1,12 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from django import forms
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
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 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
|
from .models import Item
|
||||||
|
|
||||||
@ -15,87 +15,72 @@ def validate_start_time(value):
|
|||||||
try:
|
try:
|
||||||
datetime.strptime(value, '%d.%m.%Y %H:%M')
|
datetime.strptime(value, '%d.%m.%Y %H:%M')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValidationError(_('Invalid input.'))
|
raise DjangoValidationError(_('Invalid input.'))
|
||||||
|
|
||||||
|
|
||||||
# TODO: Reinsert the datepicker scripts in the template
|
|
||||||
|
|
||||||
def setup_agenda_config(sender, **kwargs):
|
def setup_agenda_config(sender, **kwargs):
|
||||||
"""
|
"""
|
||||||
Receiver function to setup all agenda config variables. It is connected to
|
Receiver function to setup all agenda config variables. They are not
|
||||||
the signal openslides.config.signals.config_signal during app loading.
|
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.
|
# TODO: Use an input type with generic datetime support.
|
||||||
agenda_start_event_date_time = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='agenda_start_event_date_time',
|
name='agenda_start_event_date_time',
|
||||||
default_value='',
|
default_value='',
|
||||||
form_field=forms.CharField(
|
label=ugettext_lazy('Begin of event'),
|
||||||
validators=[validate_start_time, ],
|
help_text=ugettext_lazy('Input format: DD.MM.YYYY HH:MM'),
|
||||||
widget=forms.DateTimeInput(format='%d.%m.%Y %H:%M'),
|
weight=210,
|
||||||
required=False,
|
group=ugettext_lazy('Agenda'),
|
||||||
label=ugettext_lazy('Begin of event'),
|
validators=(validate_start_time,))
|
||||||
help_text=ugettext_lazy('Input format: DD.MM.YYYY HH:MM')))
|
|
||||||
|
|
||||||
agenda_show_last_speakers = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='agenda_show_last_speakers',
|
name='agenda_show_last_speakers',
|
||||||
default_value=1,
|
default_value=1,
|
||||||
form_field=forms.IntegerField(
|
input_type='integer',
|
||||||
min_value=0,
|
label=ugettext_lazy('Number of last speakers to be shown on the projector'),
|
||||||
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',
|
name='agenda_couple_countdown_and_speakers',
|
||||||
default_value=False,
|
default_value=False,
|
||||||
form_field=forms.BooleanField(
|
input_type='boolean',
|
||||||
label=ugettext_lazy('Couple countdown with the list of speakers'),
|
label=ugettext_lazy('Couple countdown with the list of speakers'),
|
||||||
help_text=ugettext_lazy('[Begin speach] starts the countdown, [End speach] stops the countdown.'),
|
help_text=ugettext_lazy('[Begin speach] starts the countdown, [End speach] stops the countdown.'),
|
||||||
required=False))
|
weight=230,
|
||||||
|
group=ugettext_lazy('Agenda'))
|
||||||
|
|
||||||
agenda_number_prefix = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='agenda_number_prefix',
|
name='agenda_number_prefix',
|
||||||
default_value='',
|
default_value='',
|
||||||
form_field=forms.CharField(
|
label=ugettext_lazy('Numbering prefix for agenda items'),
|
||||||
label=ugettext_lazy('Numbering prefix for agenda items'),
|
weight=240,
|
||||||
max_length=20,
|
group=ugettext_lazy('Agenda'),
|
||||||
required=False))
|
validators=(MaxLengthValidator(20),))
|
||||||
|
|
||||||
agenda_numeral_system = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='agenda_numeral_system',
|
name='agenda_numeral_system',
|
||||||
default_value='arabic',
|
default_value='arabic',
|
||||||
form_field=forms.ChoiceField(
|
input_type='choice',
|
||||||
label=ugettext_lazy('Numeral system for agenda items'),
|
label=ugettext_lazy('Numeral system for agenda items'),
|
||||||
widget=forms.Select(),
|
choices=(
|
||||||
choices=(
|
{'value': 'arabic', 'display_name': ugettext_lazy('Arabic')},
|
||||||
('arabic', ugettext_lazy('Arabic')),
|
{'value': 'roman', 'display_name': ugettext_lazy('Roman')}),
|
||||||
('roman', ugettext_lazy('Roman'))),
|
weight=250,
|
||||||
required=False))
|
group=ugettext_lazy('Agenda'))
|
||||||
|
|
||||||
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})
|
|
||||||
|
|
||||||
|
|
||||||
def listen_to_related_object_delete_signal(sender, instance, **kwargs):
|
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
|
be deleted. It is connected to the signal
|
||||||
django.db.models.signals.pre_delete during app loading.
|
django.db.models.signals.pre_delete during app loading.
|
||||||
"""
|
"""
|
||||||
if hasattr(instance, 'get_agenda_title'):
|
if hasattr(instance, 'get_agenda_title'):
|
||||||
for item in Item.objects.filter(content_type=ContentType.objects.get_for_model(sender), object_id=instance.pk):
|
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.content_type = None
|
||||||
item.object_id = None
|
item.object_id = None
|
||||||
item.save()
|
item.save()
|
||||||
|
@ -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 as _
|
||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy
|
||||||
|
|
||||||
from openslides.config.api import (
|
from openslides.config.api import ConfigVariable
|
||||||
ConfigGroup,
|
|
||||||
ConfigGroupedCollection,
|
|
||||||
ConfigVariable,
|
|
||||||
)
|
|
||||||
from openslides.poll.models import PERCENT_BASE_CHOICES
|
from openslides.poll.models import PERCENT_BASE_CHOICES
|
||||||
|
|
||||||
|
|
||||||
def setup_assignment_config(sender, **kwargs):
|
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
|
connected to the signal openslides.config.signals.config_signal during
|
||||||
app loading.
|
app loading.
|
||||||
"""
|
"""
|
||||||
# Ballot and ballot papers
|
# Ballot and ballot papers
|
||||||
assignments_poll_vote_values = ConfigVariable(
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='assignments_poll_vote_values',
|
name='assignments_poll_vote_values',
|
||||||
default_value='auto',
|
default_value='auto',
|
||||||
form_field=forms.ChoiceField(
|
input_type='choice',
|
||||||
widget=forms.Select(),
|
label=ugettext_lazy('Election method'),
|
||||||
required=False,
|
choices=(
|
||||||
label=ugettext_lazy('Election method'),
|
{'value': 'auto', 'display_name': ugettext_lazy('Automatic assign of method')},
|
||||||
choices=(
|
{'value': 'votes', 'display_name': ugettext_lazy('Always one option per candidate')},
|
||||||
('auto', ugettext_lazy('Automatic assign of method')),
|
{'value': 'yesnoabstain', 'display_name': ugettext_lazy('Always Yes-No-Abstain per candidate')}),
|
||||||
('votes', ugettext_lazy('Always one option per candidate')),
|
weight=410,
|
||||||
('yesnoabstain', ugettext_lazy('Always Yes-No-Abstain per candidate')))))
|
group=ugettext_lazy('Elections'),
|
||||||
assignments_poll_100_percent_base = ConfigVariable(
|
subgroup=ugettext_lazy('Ballot and ballot papers'))
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='assignments_poll_100_percent_base',
|
name='assignments_poll_100_percent_base',
|
||||||
default_value='WITHOUT_INVALID',
|
default_value='WITHOUT_INVALID',
|
||||||
form_field=forms.ChoiceField(
|
input_type='choice',
|
||||||
widget=forms.Select(),
|
label=ugettext_lazy('The 100 % base of an election result consists of'),
|
||||||
required=False,
|
choices=PERCENT_BASE_CHOICES,
|
||||||
label=ugettext_lazy('The 100 % base of an election result consists of'),
|
weight=420,
|
||||||
choices=PERCENT_BASE_CHOICES))
|
group=ugettext_lazy('Elections'),
|
||||||
assignments_pdf_ballot_papers_selection = ConfigVariable(
|
subgroup=ugettext_lazy('Ballot and ballot papers'))
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='assignments_pdf_ballot_papers_selection',
|
name='assignments_pdf_ballot_papers_selection',
|
||||||
default_value='CUSTOM_NUMBER',
|
default_value='CUSTOM_NUMBER',
|
||||||
form_field=forms.ChoiceField(
|
input_type='choice',
|
||||||
widget=forms.Select(),
|
label=ugettext_lazy('Number of ballot papers (selection)'),
|
||||||
required=False,
|
choices=(
|
||||||
label=ugettext_lazy('Number of ballot papers (selection)'),
|
{'value': 'NUMBER_OF_DELEGATES', 'display_name': ugettext_lazy('Number of all delegates')},
|
||||||
choices=(
|
{'value': 'NUMBER_OF_ALL_PARTICIPANTS', 'display_name': ugettext_lazy('Number of all participants')},
|
||||||
('NUMBER_OF_DELEGATES', ugettext_lazy('Number of all delegates')),
|
{'value': 'CUSTOM_NUMBER', 'display_name': ugettext_lazy('Use the following custom number')}),
|
||||||
('NUMBER_OF_ALL_PARTICIPANTS', ugettext_lazy('Number of all participants')),
|
weight=430,
|
||||||
('CUSTOM_NUMBER', ugettext_lazy('Use the following custom number')))))
|
group=ugettext_lazy('Elections'),
|
||||||
assignments_pdf_ballot_papers_number = ConfigVariable(
|
subgroup=ugettext_lazy('Ballot and ballot papers'))
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='assignments_pdf_ballot_papers_number',
|
name='assignments_pdf_ballot_papers_number',
|
||||||
default_value=8,
|
default_value=8,
|
||||||
form_field=forms.IntegerField(
|
input_type='integer',
|
||||||
widget=forms.TextInput(attrs={'class': 'small-input'}),
|
label=ugettext_lazy('Custom number of ballot papers'),
|
||||||
required=False,
|
weight=440,
|
||||||
min_value=1,
|
group=ugettext_lazy('Elections'),
|
||||||
label=ugettext_lazy('Custom number of ballot papers')))
|
subgroup=ugettext_lazy('Ballot and ballot papers'),
|
||||||
assignments_publish_winner_results_only = ConfigVariable(
|
validators=(MinValueValidator(1),))
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='assignments_publish_winner_results_only',
|
name='assignments_publish_winner_results_only',
|
||||||
default_value=False,
|
default_value=False,
|
||||||
form_field=forms.BooleanField(
|
input_type='boolean',
|
||||||
required=False,
|
label=ugettext_lazy('Publish election result for elected candidates only '
|
||||||
label=ugettext_lazy('Publish election result for elected candidates only '
|
'(projector view)'),
|
||||||
'(projector view)')))
|
weight=450,
|
||||||
group_ballot = ConfigGroup(
|
group=ugettext_lazy('Elections'),
|
||||||
title=ugettext_lazy('Ballot and ballot papers'),
|
subgroup=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))
|
|
||||||
|
|
||||||
# PDF
|
# PDF
|
||||||
assignments_pdf_title = ConfigVariable(
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='assignments_pdf_title',
|
name='assignments_pdf_title',
|
||||||
default_value=_('Elections'),
|
default_value=_('Elections'),
|
||||||
translatable=True,
|
label=ugettext_lazy('Title for PDF document (all elections)'),
|
||||||
form_field=forms.CharField(
|
weight=460,
|
||||||
widget=forms.TextInput(),
|
group=ugettext_lazy('Elections'),
|
||||||
required=False,
|
subgroup=ugettext_lazy('PDF'),
|
||||||
label=ugettext_lazy('Title for PDF document (all elections)')))
|
translatable=True)
|
||||||
assignments_pdf_preamble = ConfigVariable(
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='assignments_pdf_preamble',
|
name='assignments_pdf_preamble',
|
||||||
default_value='',
|
default_value='',
|
||||||
form_field=forms.CharField(
|
label=ugettext_lazy('Preamble text for PDF document (all elections)'),
|
||||||
widget=forms.Textarea(),
|
weight=470,
|
||||||
required=False,
|
group=ugettext_lazy('Elections'),
|
||||||
label=ugettext_lazy('Preamble text for PDF document (all elections)')))
|
subgroup=ugettext_lazy('PDF'))
|
||||||
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))
|
|
||||||
|
@ -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 .exceptions import ConfigError, ConfigNotFound
|
||||||
from .models import ConfigStore
|
from .models import ConfigStore
|
||||||
from .signals import config_signal
|
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
|
object. To get a config variable use x = config[...], to set it use
|
||||||
config[...] = x.
|
config[...] = x.
|
||||||
"""
|
"""
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
"""
|
||||||
|
Returns the value of the config variable. Builds the cache if it
|
||||||
|
does not exist.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return self._cache[key]
|
return self._cache[key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ConfigNotFound('The config variable %s was not found.' % key)
|
raise ConfigNotFound(_('The config variable %s was not found.') % key)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.setup_cache()
|
self.setup_cache()
|
||||||
return self[key]
|
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):
|
def setup_cache(self):
|
||||||
"""
|
"""
|
||||||
Loads all config variables from the database by sending a signal to
|
Creates a cache of all config variables with their current value.
|
||||||
save the default to the cache.
|
|
||||||
"""
|
"""
|
||||||
self._cache = {}
|
self._cache = {}
|
||||||
for key, config_variable in self.get_config_variables().items():
|
for key, config_variable in self.get_config_variables().items():
|
||||||
@ -84,6 +49,62 @@ class ConfigHandler(object):
|
|||||||
else:
|
else:
|
||||||
return True
|
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):
|
def get_all_translatable(self):
|
||||||
"""
|
"""
|
||||||
Generator to get all config variables as strings when their values are
|
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
|
A simple object class to wrap new config variables.
|
||||||
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
|
The keyword arguments 'name' and 'default_value' are required.
|
||||||
links in the submenu of the views. The attribute extra_context can be
|
|
||||||
used to insert extra css and js files into the template.
|
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={}):
|
def __init__(self, name, default_value, input_type='string', label=None,
|
||||||
self.title = title
|
help_text=None, choices=None, weight=0, group=None,
|
||||||
self.url = url
|
subgroup=None, validators=None, on_change=None, translatable=False):
|
||||||
self.weight = weight
|
if input_type not in INPUT_TYPE_MAPPING:
|
||||||
self.extra_context = extra_context
|
raise ValueError(_('Invalid value for config attribute input_type.'))
|
||||||
|
if input_type == 'choice' and choices is None:
|
||||||
def is_shown(self):
|
raise ConfigError(_("Either config attribute 'choices' must not be None or "
|
||||||
"""
|
"'input_type' must not be 'choice'."))
|
||||||
Returns True if at least one variable of the collection has a form field.
|
elif input_type != 'choice' and choices is not None:
|
||||||
"""
|
raise ConfigError(_("Either config attribute 'choices' must be None or "
|
||||||
for variable in self.variables:
|
"'input_type' must be 'choice'."))
|
||||||
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):
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.default_value = default_value
|
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.on_change = on_change
|
||||||
self.translatable = translatable
|
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
|
||||||
|
@ -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 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 .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):
|
class ConfigViewSet(ViewSet):
|
||||||
"""
|
"""
|
||||||
API endpoint to list, retrieve and update the config.
|
API endpoint to list, retrieve and update the config.
|
||||||
"""
|
"""
|
||||||
|
metadata_class = ConfigMetadata
|
||||||
|
|
||||||
def list(self, request):
|
def list(self, request):
|
||||||
"""
|
"""
|
||||||
Lists all config variables. Everybody can see them.
|
Lists all config variables. Everybody can see them.
|
||||||
"""
|
"""
|
||||||
# TODO: Check if we need permission check here.
|
return Response([{'key': key, 'value': value} for key, value in config.items()])
|
||||||
data = ({'key': key, 'value': value} for key, value in config.items())
|
|
||||||
return Response(data)
|
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
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']
|
key = kwargs['pk']
|
||||||
try:
|
try:
|
||||||
data = {'key': key, 'value': config[key]}
|
value = config[key]
|
||||||
except ConfigNotFound:
|
except ConfigNotFound:
|
||||||
raise Http404
|
raise Http404
|
||||||
return Response(data)
|
return Response({'key': key, 'value': value})
|
||||||
|
|
||||||
def update(self, request, *args, **kwargs):
|
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}
|
Example: {"value": 42}
|
||||||
"""
|
"""
|
||||||
@ -41,22 +74,16 @@ class ConfigViewSet(ViewSet):
|
|||||||
if not request.user.has_perm('config.can_manage'):
|
if not request.user.has_perm('config.can_manage'):
|
||||||
self.permission_denied(request)
|
self.permission_denied(request)
|
||||||
|
|
||||||
# Check if pk is a valid config variable key.
|
|
||||||
key = kwargs['pk']
|
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']
|
value = request.data['value']
|
||||||
if form_field:
|
|
||||||
try:
|
|
||||||
form_field.clean(value)
|
|
||||||
except DjangoValidationError as e:
|
|
||||||
raise ValidationError({'detail': e.messages[0]})
|
|
||||||
|
|
||||||
# Change value.
|
# Validate and change value.
|
||||||
config[key] = value
|
try:
|
||||||
|
config[key] = value
|
||||||
|
except ConfigNotFound:
|
||||||
|
raise Http404
|
||||||
|
except ConfigError as e:
|
||||||
|
raise ValidationError({'detail': e})
|
||||||
|
|
||||||
# Return response.
|
# Return response.
|
||||||
return Response({'key': key, 'value': value})
|
return Response({'key': key, 'value': value})
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
from django import forms
|
from django.core.validators import MaxLengthValidator
|
||||||
from django.dispatch import Signal
|
from django.dispatch import Signal
|
||||||
from django.utils.translation import ugettext as _
|
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 (
|
from openslides.config.api import ConfigVariable
|
||||||
ConfigGroup,
|
|
||||||
ConfigGroupedCollection,
|
|
||||||
ConfigVariable,
|
|
||||||
)
|
|
||||||
|
|
||||||
# This signal is sent when the migrate command is done. That means it is sent
|
# 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
|
# 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):
|
def setup_general_config(sender, **kwargs):
|
||||||
"""
|
"""
|
||||||
Receiver function to setup general config variables for OpenSlides.
|
Receiver function to setup general config variables for OpenSlides.
|
||||||
They are grouped in 'Event', 'Projector' and 'System'. This function is
|
There are two main groups: 'General' and 'Projector'. The group
|
||||||
connected to the signal openslides.config.signals.config_signal during
|
'General' has subgroups. This function is connected to the signal
|
||||||
app loading.
|
openslides.config.signals.config_signal during app loading.
|
||||||
"""
|
"""
|
||||||
general_event_name = ConfigVariable(
|
# General Event
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='general_event_name',
|
name='general_event_name',
|
||||||
default_value='OpenSlides',
|
default_value='OpenSlides',
|
||||||
form_field=forms.CharField(
|
label=ugettext_lazy('Event name'),
|
||||||
widget=forms.TextInput(),
|
weight=110,
|
||||||
label=ugettext_lazy('Event name'),
|
group=ugettext_lazy('General'),
|
||||||
max_length=50))
|
subgroup=ugettext_lazy('Event'),
|
||||||
|
validators=(MaxLengthValidator(50),))
|
||||||
|
|
||||||
general_event_description = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='general_event_description',
|
name='general_event_description',
|
||||||
default_value=_('Presentation and assembly system'),
|
default_value=_('Presentation and assembly system'),
|
||||||
translatable=True,
|
label=ugettext_lazy('Short description of event'),
|
||||||
form_field=forms.CharField(
|
weight=115,
|
||||||
widget=forms.TextInput(),
|
group=ugettext_lazy('General'),
|
||||||
label=ugettext_lazy('Short description of event'),
|
subgroup=ugettext_lazy('Event'),
|
||||||
required=False,
|
validators=(MaxLengthValidator(100),),
|
||||||
max_length=100))
|
translatable=True)
|
||||||
|
|
||||||
general_event_date = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='general_event_date',
|
name='general_event_date',
|
||||||
default_value='',
|
default_value='',
|
||||||
form_field=forms.CharField(
|
label=ugettext_lazy('Event date'),
|
||||||
widget=forms.TextInput(),
|
weight=120,
|
||||||
label=ugettext_lazy('Event date'),
|
group=ugettext_lazy('General'),
|
||||||
required=False))
|
subgroup=ugettext_lazy('Event'))
|
||||||
|
|
||||||
general_event_location = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='general_event_location',
|
name='general_event_location',
|
||||||
default_value='',
|
default_value='',
|
||||||
form_field=forms.CharField(
|
label=ugettext_lazy('Event location'),
|
||||||
widget=forms.TextInput(),
|
weight=125,
|
||||||
label=ugettext_lazy('Event location'),
|
group=ugettext_lazy('General'),
|
||||||
required=False))
|
subgroup=ugettext_lazy('Event'))
|
||||||
|
|
||||||
# TODO: Check whether this variable is ever used.
|
# TODO: Check whether this variable is ever used.
|
||||||
general_event_organizer = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='general_event_organizer',
|
name='general_event_organizer',
|
||||||
default_value='',
|
default_value='',
|
||||||
form_field=forms.CharField(
|
label=ugettext_lazy('Event organizer'),
|
||||||
widget=forms.TextInput(),
|
weight=130,
|
||||||
label=ugettext_lazy('Event organizer'),
|
group=ugettext_lazy('General'),
|
||||||
required=False))
|
subgroup=ugettext_lazy('Event'))
|
||||||
|
|
||||||
general_system_enable_anonymous = ConfigVariable(
|
# General System
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='general_system_enable_anonymous',
|
name='general_system_enable_anonymous',
|
||||||
default_value=False,
|
default_value=False,
|
||||||
form_field=forms.BooleanField(
|
input_type='boolean',
|
||||||
label=ugettext_lazy('Allow access for anonymous guest users'),
|
label=ugettext_lazy('Allow access for anonymous guest users'),
|
||||||
required=False))
|
weight=135,
|
||||||
|
group=ugettext_lazy('General'),
|
||||||
|
subgroup=ugettext_lazy('System'))
|
||||||
|
|
||||||
projector_enable_logo = ConfigVariable(
|
# Projector
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='projector_enable_logo',
|
name='projector_enable_logo',
|
||||||
default_value=True,
|
default_value=True,
|
||||||
form_field=forms.BooleanField(
|
input_type='boolean',
|
||||||
label=ugettext_lazy('Show logo on projector'),
|
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".'),
|
help_text=ugettext_lazy('You can find and replace the logo under "openslides/core/static/...".'), # TODO: Update path.
|
||||||
required=False))
|
weight=150,
|
||||||
|
group=ugettext_lazy('Projector'))
|
||||||
|
|
||||||
projector_enable_title = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='projector_enable_title',
|
name='projector_enable_title',
|
||||||
default_value=True,
|
default_value=True,
|
||||||
form_field=forms.BooleanField(
|
input_type='boolean',
|
||||||
label=ugettext_lazy('Show title and description of event on projector'),
|
label=ugettext_lazy('Show title and description of event on projector'),
|
||||||
required=False))
|
weight=155,
|
||||||
|
group=ugettext_lazy('Projector'))
|
||||||
|
|
||||||
projector_backgroundcolor1 = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='projector_backgroundcolor1',
|
name='projector_backgroundcolor1',
|
||||||
default_value='#444444',
|
default_value='#444444',
|
||||||
form_field=forms.CharField(
|
label=ugettext_lazy('Background color of projector header'),
|
||||||
widget=forms.TextInput(),
|
help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'),
|
||||||
label=ugettext_lazy('Background color of projector header'),
|
weight=160,
|
||||||
help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'),
|
group=ugettext_lazy('Projector'))
|
||||||
required=True))
|
|
||||||
|
|
||||||
projector_backgroundcolor2 = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='projector_backgroundcolor2',
|
name='projector_backgroundcolor2',
|
||||||
default_value='#222222',
|
default_value='#222222',
|
||||||
form_field=forms.CharField(
|
label=ugettext_lazy('Second (optional) background color for linear color gradient'),
|
||||||
widget=forms.TextInput(),
|
help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'),
|
||||||
label=ugettext_lazy('Second (optional) background color for linear color gradient'),
|
weight=165,
|
||||||
help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'),
|
group=ugettext_lazy('Projector'))
|
||||||
required=False))
|
|
||||||
|
|
||||||
projector_fontcolor = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='projector_fontcolor',
|
name='projector_fontcolor',
|
||||||
default_value='#F5F5F5',
|
default_value='#F5F5F5',
|
||||||
form_field=forms.CharField(
|
label=ugettext_lazy('Font color of projector header'),
|
||||||
widget=forms.TextInput(),
|
help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'),
|
||||||
label=ugettext_lazy('Font color of projector header'),
|
weight=170,
|
||||||
help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'),
|
group=ugettext_lazy('Projector'))
|
||||||
required=True))
|
|
||||||
|
|
||||||
projector_welcome_title = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='projector_welcome_title',
|
name='projector_welcome_title',
|
||||||
default_value=_('Welcome to OpenSlides'),
|
default_value=_('Welcome to OpenSlides'),
|
||||||
translatable=True,
|
label=ugettext_lazy('Title'),
|
||||||
form_field=forms.CharField(
|
help_text=ugettext_lazy('Also used for the default welcome slide.'),
|
||||||
widget=forms.TextInput(),
|
weight=175,
|
||||||
label=ugettext_lazy('Title'),
|
group=ugettext_lazy('Projector'),
|
||||||
help_text=ugettext_lazy('Also used for the default welcome slide.'),
|
translatable=True)
|
||||||
required=False))
|
|
||||||
|
|
||||||
projector_welcome_text = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='projector_welcome_text',
|
name='projector_welcome_text',
|
||||||
default_value=_('[Place for your welcome text.]'),
|
default_value=_('[Space for your welcome text.]'),
|
||||||
translatable=True,
|
label=ugettext_lazy('Welcome text'),
|
||||||
form_field=forms.CharField(
|
weight=180,
|
||||||
widget=forms.Textarea(),
|
group=ugettext_lazy('Projector'),
|
||||||
label=ugettext_lazy('Welcome text'),
|
translatable=True)
|
||||||
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))
|
|
||||||
|
@ -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 ugettext as _
|
||||||
from django.utils.translation import pgettext, ugettext_lazy, ugettext_noop
|
from django.utils.translation import pgettext, ugettext_lazy, ugettext_noop
|
||||||
|
|
||||||
from openslides.config.api import (
|
from openslides.config.api import ConfigVariable
|
||||||
ConfigGroup,
|
|
||||||
ConfigGroupedCollection,
|
|
||||||
ConfigVariable,
|
|
||||||
)
|
|
||||||
from openslides.poll.models import PERCENT_BASE_CHOICES
|
from openslides.poll.models import PERCENT_BASE_CHOICES
|
||||||
|
|
||||||
from .models import State, Workflow
|
from .models import State, Workflow
|
||||||
@ -14,166 +10,165 @@ from .models import State, Workflow
|
|||||||
|
|
||||||
def setup_motion_config(sender, **kwargs):
|
def setup_motion_config(sender, **kwargs):
|
||||||
"""
|
"""
|
||||||
Receiver function to setup all motion config variables. It is connected to
|
Receiver function to setup all motion config variables. They are
|
||||||
the signal openslides.config.signals.config_signal during app loading.
|
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
|
# General
|
||||||
motions_workflow = ConfigVariable(
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='motions_workflow',
|
name='motions_workflow',
|
||||||
default_value='1',
|
default_value='1',
|
||||||
form_field=forms.ChoiceField(
|
input_type='choice',
|
||||||
widget=forms.Select(),
|
label=ugettext_lazy('Workflow of new motions'),
|
||||||
label=ugettext_lazy('Workflow of new motions'),
|
choices=({'value': str(workflow.pk), 'display_name': ugettext_lazy(workflow.name)} for workflow in Workflow.objects.all()),
|
||||||
required=True,
|
weight=310,
|
||||||
choices=[(str(workflow.pk), ugettext_lazy(workflow.name)) for workflow in Workflow.objects.all()]))
|
group=ugettext_lazy('Motion'),
|
||||||
motions_identifier = ConfigVariable(
|
subgroup=ugettext_lazy('General'))
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='motions_identifier',
|
name='motions_identifier',
|
||||||
default_value='per_category',
|
default_value='per_category',
|
||||||
form_field=forms.ChoiceField(
|
input_type='choice',
|
||||||
widget=forms.Select(),
|
label=ugettext_lazy('Identifier'),
|
||||||
required=True,
|
choices=(
|
||||||
label=ugettext_lazy('Identifier'),
|
{'value': 'per_category', 'display_name': ugettext_lazy('Numbered per category')},
|
||||||
choices=[
|
{'value': 'serially_numbered', 'display_name': ugettext_lazy('Serially numbered')},
|
||||||
('per_category', ugettext_lazy('Numbered per category')),
|
{'value': 'manually', 'display_name': ugettext_lazy('Set it manually')}),
|
||||||
('serially_numbered', ugettext_lazy('Serially numbered')),
|
weight=315,
|
||||||
('manually', ugettext_lazy('Set it manually'))]))
|
group=ugettext_lazy('Motion'),
|
||||||
motions_preamble = ConfigVariable(
|
subgroup=ugettext_lazy('General'))
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='motions_preamble',
|
name='motions_preamble',
|
||||||
default_value=_('The assembly may decide,'),
|
default_value=_('The assembly may decide,'),
|
||||||
translatable=True,
|
label=ugettext_lazy('Motion preamble'),
|
||||||
form_field=forms.CharField(
|
weight=320,
|
||||||
widget=forms.TextInput(),
|
group=ugettext_lazy('Motion'),
|
||||||
required=False,
|
subgroup=ugettext_lazy('General'),
|
||||||
label=ugettext_lazy('Motion preamble')))
|
translatable=True)
|
||||||
motions_stop_submitting = ConfigVariable(
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='motions_stop_submitting',
|
name='motions_stop_submitting',
|
||||||
default_value=False,
|
default_value=False,
|
||||||
form_field=forms.BooleanField(
|
input_type='boolean',
|
||||||
label=ugettext_lazy('Stop submitting new motions by non-staff users'),
|
label=ugettext_lazy('Stop submitting new motions by non-staff users'),
|
||||||
required=False))
|
weight=325,
|
||||||
motions_allow_disable_versioning = ConfigVariable(
|
group=ugettext_lazy('Motion'),
|
||||||
|
subgroup=ugettext_lazy('General'))
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='motions_allow_disable_versioning',
|
name='motions_allow_disable_versioning',
|
||||||
default_value=False,
|
default_value=False,
|
||||||
form_field=forms.BooleanField(
|
input_type='boolean',
|
||||||
label=ugettext_lazy('Allow to disable versioning'),
|
label=ugettext_lazy('Allow to disable versioning'),
|
||||||
required=False))
|
weight=330,
|
||||||
group_general = ConfigGroup(
|
group=ugettext_lazy('Motion'),
|
||||||
title=ugettext_lazy('General'),
|
subgroup=ugettext_lazy('General'))
|
||||||
variables=(
|
|
||||||
motions_workflow,
|
|
||||||
motions_identifier,
|
|
||||||
motions_preamble,
|
|
||||||
motions_stop_submitting,
|
|
||||||
motions_allow_disable_versioning))
|
|
||||||
|
|
||||||
# Amendments
|
# Amendments
|
||||||
motions_amendments_enabled = ConfigVariable(
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='motions_amendments_enabled',
|
name='motions_amendments_enabled',
|
||||||
default_value=False,
|
default_value=False,
|
||||||
form_field=forms.BooleanField(
|
input_type='boolean',
|
||||||
label=ugettext_lazy('Activate amendments'),
|
label=ugettext_lazy('Activate amendments'),
|
||||||
required=False))
|
weight=335,
|
||||||
|
group=ugettext_lazy('Motion'),
|
||||||
|
subgroup=ugettext_lazy('Amendments'))
|
||||||
|
|
||||||
motions_amendments_prefix = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='motions_amendments_prefix',
|
name='motions_amendments_prefix',
|
||||||
default_value=pgettext('Prefix for the identifier for amendments', 'A'),
|
default_value=pgettext('Prefix for the identifier for amendments', 'A'),
|
||||||
form_field=forms.CharField(
|
label=ugettext_lazy('Prefix for the identifier for amendments'),
|
||||||
required=False,
|
weight=340,
|
||||||
label=ugettext_lazy('Prefix for the identifier for amendments')))
|
group=ugettext_lazy('Motion'),
|
||||||
|
subgroup=ugettext_lazy('Amendments'))
|
||||||
group_amendments = ConfigGroup(
|
|
||||||
title=ugettext_lazy('Amendments'),
|
|
||||||
variables=(motions_amendments_enabled, motions_amendments_prefix))
|
|
||||||
|
|
||||||
# Supporters
|
# Supporters
|
||||||
motions_min_supporters = ConfigVariable(
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='motions_min_supporters',
|
name='motions_min_supporters',
|
||||||
default_value=0,
|
default_value=0,
|
||||||
form_field=forms.IntegerField(
|
input_type='integer',
|
||||||
widget=forms.TextInput(attrs={'class': 'small-input'}),
|
label=ugettext_lazy('Number of (minimum) required supporters for a motion'),
|
||||||
label=ugettext_lazy('Number of (minimum) required supporters for a motion'),
|
help_text=ugettext_lazy('Choose 0 to disable the supporting system.'),
|
||||||
min_value=0,
|
weight=345,
|
||||||
help_text=ugettext_lazy('Choose 0 to disable the supporting system.')))
|
group=ugettext_lazy('Motion'),
|
||||||
motions_remove_supporters = ConfigVariable(
|
subgroup=ugettext_lazy('Supporters'),
|
||||||
|
validators=(MinValueValidator(0),))
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='motions_remove_supporters',
|
name='motions_remove_supporters',
|
||||||
default_value=False,
|
default_value=False,
|
||||||
form_field=forms.BooleanField(
|
input_type='boolean',
|
||||||
label=ugettext_lazy('Remove all supporters of a motion if a submitter edits his motion in early state'),
|
label=ugettext_lazy('Remove all supporters of a motion if a submitter edits his motion in early state'),
|
||||||
required=False))
|
weight=350,
|
||||||
group_supporters = ConfigGroup(
|
group=ugettext_lazy('Motion'),
|
||||||
title=ugettext_lazy('Supporters'),
|
subgroup=ugettext_lazy('Supporters'))
|
||||||
variables=(motions_min_supporters, motions_remove_supporters))
|
|
||||||
|
|
||||||
# Voting and ballot papers
|
# Voting and ballot papers
|
||||||
motions_poll_100_percent_base = ConfigVariable(
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='motions_poll_100_percent_base',
|
name='motions_poll_100_percent_base',
|
||||||
default_value='WITHOUT_INVALID',
|
default_value='WITHOUT_INVALID',
|
||||||
form_field=forms.ChoiceField(
|
input_type='choice',
|
||||||
widget=forms.Select(),
|
label=ugettext_lazy('The 100 % base of a voting result consists of'),
|
||||||
required=False,
|
choices=PERCENT_BASE_CHOICES,
|
||||||
label=ugettext_lazy('The 100 % base of a voting result consists of'),
|
weight=355,
|
||||||
choices=PERCENT_BASE_CHOICES))
|
group=ugettext_lazy('Motion'),
|
||||||
motions_pdf_ballot_papers_selection = ConfigVariable(
|
subgroup=ugettext_lazy('Voting and ballot papers'))
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='motions_pdf_ballot_papers_selection',
|
name='motions_pdf_ballot_papers_selection',
|
||||||
default_value='CUSTOM_NUMBER',
|
default_value='CUSTOM_NUMBER',
|
||||||
form_field=forms.ChoiceField(
|
input_type='choice',
|
||||||
widget=forms.Select(),
|
label=ugettext_lazy('Number of ballot papers (selection)'),
|
||||||
required=False,
|
choices=(
|
||||||
label=ugettext_lazy('Number of ballot papers (selection)'),
|
{'value': 'NUMBER_OF_DELEGATES', 'display_name': ugettext_lazy('Number of all delegates')},
|
||||||
choices=[
|
{'value': 'NUMBER_OF_ALL_PARTICIPANTS', 'display_name': ugettext_lazy('Number of all participants')},
|
||||||
('NUMBER_OF_DELEGATES', ugettext_lazy('Number of all delegates')),
|
{'value': 'CUSTOM_NUMBER', 'display_name': ugettext_lazy('Use the following custom number')}),
|
||||||
('NUMBER_OF_ALL_PARTICIPANTS', ugettext_lazy('Number of all participants')),
|
weight=360,
|
||||||
('CUSTOM_NUMBER', ugettext_lazy("Use the following custom number"))]))
|
group=ugettext_lazy('Motion'),
|
||||||
motions_pdf_ballot_papers_number = ConfigVariable(
|
subgroup=ugettext_lazy('Voting and ballot papers'))
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='motions_pdf_ballot_papers_number',
|
name='motions_pdf_ballot_papers_number',
|
||||||
default_value=8,
|
default_value=8,
|
||||||
form_field=forms.IntegerField(
|
input_type='integer',
|
||||||
widget=forms.TextInput(attrs={'class': 'small-input'}),
|
label=ugettext_lazy('Custom number of ballot papers'),
|
||||||
required=False,
|
weight=365,
|
||||||
min_value=1,
|
group=ugettext_lazy('Motion'),
|
||||||
label=ugettext_lazy('Custom number of ballot papers')))
|
subgroup=ugettext_lazy('Voting and ballot papers'),
|
||||||
group_ballot_papers = ConfigGroup(
|
validators=(MinValueValidator(1),))
|
||||||
title=ugettext_lazy('Voting and ballot papers'),
|
|
||||||
variables=(
|
|
||||||
motions_poll_100_percent_base,
|
|
||||||
motions_pdf_ballot_papers_selection,
|
|
||||||
motions_pdf_ballot_papers_number))
|
|
||||||
|
|
||||||
# PDF
|
# PDF
|
||||||
motions_pdf_title = ConfigVariable(
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='motions_pdf_title',
|
name='motions_pdf_title',
|
||||||
default_value=_('Motions'),
|
default_value=_('Motions'),
|
||||||
translatable=True,
|
label=ugettext_lazy('Title for PDF document (all motions)'),
|
||||||
form_field=forms.CharField(
|
weight=370,
|
||||||
widget=forms.TextInput(),
|
group=ugettext_lazy('Motion'),
|
||||||
required=False,
|
subgroup=ugettext_lazy('PDF'),
|
||||||
label=ugettext_lazy('Title for PDF document (all motions)')))
|
translatable=True)
|
||||||
motions_pdf_preamble = ConfigVariable(
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='motions_pdf_preamble',
|
name='motions_pdf_preamble',
|
||||||
default_value='',
|
default_value='',
|
||||||
form_field=forms.CharField(
|
label=ugettext_lazy('Preamble text for PDF document (all motions)'),
|
||||||
widget=forms.Textarea(),
|
weight=375,
|
||||||
required=False,
|
group=ugettext_lazy('Motion'),
|
||||||
label=ugettext_lazy('Preamble text for PDF document (all motions)')))
|
subgroup=ugettext_lazy('PDF'))
|
||||||
motions_pdf_paragraph_numbering = ConfigVariable(
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='motions_pdf_paragraph_numbering',
|
name='motions_pdf_paragraph_numbering',
|
||||||
default_value=False,
|
default_value=False,
|
||||||
form_field=forms.BooleanField(
|
label=ugettext_lazy('Show paragraph numbering (only in PDF)'),
|
||||||
label=ugettext_lazy('Show paragraph numbering (only in PDF)'),
|
weight=380,
|
||||||
required=False))
|
group=ugettext_lazy('Motion'),
|
||||||
group_pdf = ConfigGroup(
|
subgroup=ugettext_lazy('PDF'))
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
def create_builtin_workflows(sender, **kwargs):
|
def create_builtin_workflows(sender, **kwargs):
|
||||||
|
@ -68,9 +68,9 @@ class BaseVote(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
PERCENT_BASE_CHOICES = (
|
PERCENT_BASE_CHOICES = (
|
||||||
('WITHOUT_INVALID', ugettext_lazy('Only all valid votes')),
|
{'value': 'WITHOUT_INVALID', 'display_name': ugettext_lazy('Only all valid votes')},
|
||||||
('WITH_INVALID', ugettext_lazy('All votes cast (including invalid votes)')),
|
{'value': 'WITH_INVALID', 'display_name': ugettext_lazy('All votes cast (including invalid votes)')},
|
||||||
('DISABLED', ugettext_lazy('Disabled (no percents)')))
|
{'value': 'DISABLED', 'display_name': ugettext_lazy('Disabled (no percents)')})
|
||||||
|
|
||||||
|
|
||||||
class CollectDefaultVotesMixin(models.Model):
|
class CollectDefaultVotesMixin(models.Model):
|
||||||
|
@ -1,109 +1,93 @@
|
|||||||
from django import forms
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||||
|
|
||||||
from openslides.config.api import (
|
from openslides.config.api import ConfigVariable
|
||||||
ConfigGroup,
|
|
||||||
ConfigGroupedCollection,
|
|
||||||
ConfigVariable,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .models import Group, Permission, User
|
from .models import Group, Permission, User
|
||||||
|
|
||||||
|
|
||||||
def setup_users_config(sender, **kwargs):
|
def setup_users_config(sender, **kwargs):
|
||||||
"""
|
"""
|
||||||
Receiver function to setup all users config variables. It is connected
|
Receiver function to setup all users config variables. They are grouped
|
||||||
to the signal openslides.config.signals.config_signal during app loading.
|
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',
|
name='users_sort_users_by_first_name',
|
||||||
default_value=False,
|
default_value=False,
|
||||||
form_field=forms.BooleanField(
|
input_type='boolean',
|
||||||
required=False,
|
label=ugettext_lazy('Sort users by first name'),
|
||||||
label=ugettext_lazy('Sort users by first name'),
|
help_text=ugettext_lazy('Disable for sorting by last name'),
|
||||||
help_text=ugettext_lazy('Disable for sorting by last name')))
|
weight=510,
|
||||||
|
group=ugettext_lazy('Users'),
|
||||||
group_general = ConfigGroup(
|
subgroup=ugettext_lazy('Sorting'))
|
||||||
title=ugettext_lazy('Sorting'),
|
|
||||||
variables=(users_sort_users_by_first_name,))
|
|
||||||
|
|
||||||
# PDF
|
# PDF
|
||||||
users_pdf_welcometitle = ConfigVariable(
|
|
||||||
|
yield ConfigVariable(
|
||||||
name='users_pdf_welcometitle',
|
name='users_pdf_welcometitle',
|
||||||
default_value=_('Welcome to OpenSlides!'),
|
default_value=_('Welcome to OpenSlides!'),
|
||||||
translatable=True,
|
label=ugettext_lazy('Title for access data and welcome PDF'),
|
||||||
form_field=forms.CharField(
|
weight=520,
|
||||||
widget=forms.Textarea(),
|
group=ugettext_lazy('Users'),
|
||||||
required=False,
|
subgroup=ugettext_lazy('PDF'),
|
||||||
label=ugettext_lazy('Title for access data and welcome PDF')))
|
translatable=True)
|
||||||
|
|
||||||
users_pdf_welcometext = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='users_pdf_welcometext',
|
name='users_pdf_welcometext',
|
||||||
default_value=_('[Place for your welcome and help text.]'),
|
default_value=_('[Place for your welcome and help text.]'),
|
||||||
translatable=True,
|
label=ugettext_lazy('Help text for access data and welcome PDF'),
|
||||||
form_field=forms.CharField(
|
weight=530,
|
||||||
widget=forms.Textarea(),
|
group=ugettext_lazy('Users'),
|
||||||
required=False,
|
subgroup=ugettext_lazy('PDF'),
|
||||||
label=ugettext_lazy('Help text for access data and welcome PDF')))
|
translatable=True)
|
||||||
|
|
||||||
users_pdf_url = ConfigVariable(
|
# TODO: Use Django's URLValidator here.
|
||||||
|
yield ConfigVariable(
|
||||||
name='users_pdf_url',
|
name='users_pdf_url',
|
||||||
default_value='http://example.com:8000',
|
default_value='http://example.com:8000',
|
||||||
form_field=forms.CharField(
|
label=ugettext_lazy('System URL'),
|
||||||
widget=forms.TextInput(),
|
help_text=ugettext_lazy('Used for QRCode in PDF of access data.'),
|
||||||
required=False,
|
weight=540,
|
||||||
label=ugettext_lazy('System URL'),
|
group=ugettext_lazy('Users'),
|
||||||
help_text=ugettext_lazy('Used for QRCode in PDF of access data.')))
|
subgroup=ugettext_lazy('PDF'))
|
||||||
|
|
||||||
users_pdf_wlan_ssid = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='users_pdf_wlan_ssid',
|
name='users_pdf_wlan_ssid',
|
||||||
default_value='',
|
default_value='',
|
||||||
form_field=forms.CharField(
|
label=ugettext_lazy('WLAN name (SSID)'),
|
||||||
widget=forms.TextInput(),
|
help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.'),
|
||||||
required=False,
|
weight=550,
|
||||||
label=ugettext_lazy('WLAN name (SSID)'),
|
group=ugettext_lazy('Users'),
|
||||||
help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.')))
|
subgroup=ugettext_lazy('PDF'))
|
||||||
|
|
||||||
users_pdf_wlan_password = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='users_pdf_wlan_password',
|
name='users_pdf_wlan_password',
|
||||||
default_value='',
|
default_value='',
|
||||||
form_field=forms.CharField(
|
label=ugettext_lazy('WLAN password'),
|
||||||
widget=forms.TextInput(),
|
help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.'),
|
||||||
required=False,
|
weight=560,
|
||||||
label=ugettext_lazy('WLAN password'),
|
group=ugettext_lazy('Users'),
|
||||||
help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.')))
|
subgroup=ugettext_lazy('PDF'))
|
||||||
|
|
||||||
users_pdf_wlan_encryption = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='users_pdf_wlan_encryption',
|
name='users_pdf_wlan_encryption',
|
||||||
default_value='',
|
default_value='',
|
||||||
form_field=forms.ChoiceField(
|
input_type='choice',
|
||||||
widget=forms.Select(),
|
label=ugettext_lazy('WLAN encryption'),
|
||||||
required=False,
|
help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.'),
|
||||||
label=ugettext_lazy('WLAN encryption'),
|
choices=(
|
||||||
help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.'),
|
{'value': '', 'display_name': '---------'},
|
||||||
choices=(
|
{'value': 'WEP', 'display_name': ugettext_lazy('WEP')},
|
||||||
('', '---------'),
|
{'value': 'WPA', 'display_name': ugettext_lazy('WPA/WPA2')},
|
||||||
('WEP', 'WEP'),
|
{'value': 'nopass', 'display_name': ugettext_lazy('No encryption')}),
|
||||||
('WPA', 'WPA/WPA2'),
|
weight=570,
|
||||||
('nopass', ugettext_lazy('No encryption')))))
|
group=ugettext_lazy('Users'),
|
||||||
|
subgroup=ugettext_lazy('PDF'))
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
def create_builtin_groups_and_admin(**kwargs):
|
def create_builtin_groups_and_admin(**kwargs):
|
||||||
|
@ -4,6 +4,7 @@ from urllib.parse import urlparse
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from rest_framework.decorators import detail_route # noqa
|
from rest_framework.decorators import detail_route # noqa
|
||||||
from rest_framework.decorators import list_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.mixins import DestroyModelMixin, UpdateModelMixin # noqa
|
||||||
from rest_framework.response import Response # noqa
|
from rest_framework.response import Response # noqa
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
from django import forms
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.test import APIClient
|
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.config.signals import config_signal
|
||||||
|
from openslides.utils.rest_api import ValidationError
|
||||||
from openslides.utils.test import TestCase
|
from openslides.utils.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
@ -38,7 +38,51 @@ class ConfigViewSet(TestCase):
|
|||||||
reverse('config-detail', args=['test_var_ohhii4iavoh5Phoh5ahg']),
|
reverse('config-detail', args=['test_var_ohhii4iavoh5Phoh5ahg']),
|
||||||
{'value': 'test_value_string'})
|
{'value': 'test_value_string'})
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
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 <class 'int'>, got <class 'str'>."})
|
||||||
|
|
||||||
|
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')
|
@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
|
Sets a simple config view with some config variables but without
|
||||||
grouping.
|
grouping.
|
||||||
"""
|
"""
|
||||||
return ConfigCollection(
|
yield ConfigVariable(
|
||||||
title='Config vars for testing',
|
name='test_var_aeW3Quahkah1phahCheo',
|
||||||
url='test_url_ieXao5Wae5Duoy6Wohtu',
|
default_value=None,
|
||||||
variables=(ConfigVariable(name='test_var_aeW3Quahkah1phahCheo',
|
label='test_label_aeNahsheu8phahk8taYo')
|
||||||
default_value=None),
|
|
||||||
ConfigVariable(name='test_var_Xeiizi7ooH8Thuk5aida',
|
yield ConfigVariable(
|
||||||
default_value='',
|
name='test_var_Xeiizi7ooH8Thuk5aida',
|
||||||
form_field=forms.CharField()),
|
default_value='')
|
||||||
ConfigVariable(name='test_var_ohhii4iavoh5Phoh5ahg',
|
|
||||||
default_value=0,
|
yield ConfigVariable(
|
||||||
form_field=forms.IntegerField())))
|
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,))
|
||||||
|
@ -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.dispatch import receiver
|
||||||
from django.test.client import Client
|
|
||||||
|
|
||||||
from openslides.config.api import (
|
from openslides.config.api import ConfigVariable, config
|
||||||
ConfigCollection,
|
|
||||||
ConfigGroup,
|
|
||||||
ConfigGroupedCollection,
|
|
||||||
ConfigVariable,
|
|
||||||
config,
|
|
||||||
)
|
|
||||||
from openslides.config.exceptions import ConfigError, ConfigNotFound
|
from openslides.config.exceptions import ConfigError, ConfigNotFound
|
||||||
from openslides.config.signals import config_signal
|
from openslides.config.signals import config_signal
|
||||||
from openslides.users.models import User
|
|
||||||
from openslides.utils.test import TestCase
|
from openslides.utils.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
@ -86,79 +75,47 @@ class HandleConfigTest(TestCase):
|
|||||||
value='new_string_kbmbnfhdgibkdjshg452bc')
|
value='new_string_kbmbnfhdgibkdjshg452bc')
|
||||||
self.assertEqual(config['var_with_callback_ghvnfjd5768gdfkwg0hm2'], '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')
|
@receiver(config_signal, dispatch_uid='set_grouped_config_view_for_testing')
|
||||||
def set_grouped_config_view(sender, **kwargs):
|
def set_grouped_config_view(sender, **kwargs):
|
||||||
"""
|
"""
|
||||||
Sets a grouped config collection view which can be reached under the url
|
Sets a grouped config collection. There are some variables, one variable
|
||||||
'/config/testgroupedpage1/'. There are some variables, one variable
|
|
||||||
with a string as default value, one with a boolean as default value,
|
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
|
one with an integer as default value, one with choices and one hidden
|
||||||
hidden variable. These variables are grouped in two groups.
|
variable. These variables are grouped in two subgroups.
|
||||||
"""
|
"""
|
||||||
string_var = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='string_var',
|
name='string_var',
|
||||||
default_value='default_string_rien4ooCZieng6ah',
|
default_value='default_string_rien4ooCZieng6ah',
|
||||||
form_field=forms.CharField())
|
group='Config vars for testing 1',
|
||||||
bool_var = ConfigVariable(
|
subgroup='Group 1 aiYeix2mCieQuae3')
|
||||||
|
yield ConfigVariable(
|
||||||
name='bool_var',
|
name='bool_var',
|
||||||
default_value=True,
|
default_value=True,
|
||||||
form_field=forms.BooleanField(required=False))
|
input_type='boolean',
|
||||||
integer_var = ConfigVariable(
|
group='Config vars for testing 1',
|
||||||
|
subgroup='Group 1 aiYeix2mCieQuae3')
|
||||||
|
yield ConfigVariable(
|
||||||
name='integer_var',
|
name='integer_var',
|
||||||
default_value=3,
|
default_value=3,
|
||||||
form_field=forms.IntegerField())
|
input_type='integer',
|
||||||
group_1 = ConfigGroup(title='Group 1 aiYeix2mCieQuae3', variables=(string_var, bool_var, integer_var))
|
group='Config vars for testing 1',
|
||||||
|
subgroup='Group 1 aiYeix2mCieQuae3')
|
||||||
|
|
||||||
hidden_var = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='hidden_var',
|
name='hidden_var',
|
||||||
default_value='hidden_value')
|
default_value='hidden_value',
|
||||||
choices_var = ConfigVariable(
|
group='Config vars for testing 1',
|
||||||
|
subgroup='Group 2 Toongai7ahyahy7B')
|
||||||
|
yield ConfigVariable(
|
||||||
name='choices_var',
|
name='choices_var',
|
||||||
default_value='1',
|
default_value='1',
|
||||||
form_field=forms.ChoiceField(choices=(('1', 'Choice One Ughoch4ocoche6Ee'), ('2', 'Choice Two Vahnoh5yalohv5Eb'))))
|
input_type='choice',
|
||||||
group_2 = ConfigGroup(title='Group 2 Toongai7ahyahy7B', variables=(hidden_var, choices_var))
|
choices=(
|
||||||
|
{'value': '1', 'display_name': 'Choice One Ughoch4ocoche6Ee'},
|
||||||
return ConfigGroupedCollection(
|
{'value': '2', 'display_name': 'Choice Two Vahnoh5yalohv5Eb'}),
|
||||||
title='Config vars for testing 1',
|
group='Config vars for testing 1',
|
||||||
url='testgroupedpage1',
|
subgroup='Group 2 Toongai7ahyahy7B')
|
||||||
weight=10000,
|
|
||||||
groups=(group_1, group_2),
|
|
||||||
extra_context={'extra_stylefiles': ['styles/test-config-sjNN56dFGDrg2.css'],
|
|
||||||
'extra_javascript': ['javascript/test-config-djg4dFGVslk4209f.js']})
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(config_signal, dispatch_uid='set_simple_config_view_for_testing')
|
@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
|
Sets a simple config view with some config variables but without
|
||||||
grouping.
|
grouping.
|
||||||
"""
|
"""
|
||||||
return ConfigCollection(
|
yield ConfigVariable(name='additional_config_var', default_value='BaeB0ahcMae3feem')
|
||||||
title='Config vars for testing 2',
|
yield ConfigVariable(name='additional_config_var_2', default_value='')
|
||||||
url='testsimplepage1',
|
yield ConfigVariable(name='none_config_var', default_value=None)
|
||||||
variables=(ConfigVariable(name='additional_config_var', default_value='BaeB0ahcMae3feem'),
|
|
||||||
ConfigVariable(name='additional_config_var_2', default_value='', form_field=forms.CharField()),
|
|
||||||
ConfigVariable(name='none_config_var', default_value=None)))
|
|
||||||
|
|
||||||
|
|
||||||
# Do not connect to the signal now but later inside the test.
|
# 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.
|
Sets a bad config view with some multiple config vars.
|
||||||
"""
|
"""
|
||||||
return ConfigCollection(
|
yield ConfigVariable(name='multiple_config_var', default_value='foobar1')
|
||||||
title='Config vars for testing 3',
|
yield ConfigVariable(name='multiple_config_var', default_value='foobar2')
|
||||||
url='testsimplepage2',
|
|
||||||
variables=(ConfigVariable(name='multiple_config_var', default_value='foobar1'),
|
|
||||||
ConfigVariable(name='multiple_config_var', default_value='foobar2')))
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(config_signal, dispatch_uid='set_simple_config_collection_disabled_view_for_testing')
|
@receiver(config_signal, dispatch_uid='set_simple_config_collection_disabled_view_for_testing')
|
||||||
def set_simple_config_collection_disabled_view(sender, **kwargs):
|
def set_simple_config_collection_disabled_view(sender, **kwargs):
|
||||||
return ConfigCollection(
|
yield ConfigVariable(name='hidden_config_var_2', default_value='')
|
||||||
title='Ho5iengaoon5Hoht',
|
|
||||||
url='testsimplepage3',
|
|
||||||
variables=(ConfigVariable(name='hidden_config_var_2', default_value=''),))
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(config_signal, dispatch_uid='set_simple_config_collection_with_callback_for_testing')
|
@receiver(config_signal, dispatch_uid='set_simple_config_collection_with_callback_for_testing')
|
||||||
def set_simple_config_collection_with_callback(sender, **kwargs):
|
def set_simple_config_collection_with_callback(sender, **kwargs):
|
||||||
def callback():
|
def callback():
|
||||||
raise Exception('Change callback dhcnfg34dlg06kdg successfully called.')
|
raise Exception('Change callback dhcnfg34dlg06kdg successfully called.')
|
||||||
return ConfigCollection(
|
yield ConfigVariable(
|
||||||
title='Hvndfhsbgkridfgdfg',
|
name='var_with_callback_ghvnfjd5768gdfkwg0hm2',
|
||||||
url='testsimplepage4',
|
default_value='',
|
||||||
variables=(ConfigVariable(
|
on_change=callback)
|
||||||
name='var_with_callback_ghvnfjd5768gdfkwg0hm2',
|
|
||||||
default_value='',
|
|
||||||
on_change=callback),))
|
|
||||||
|
Loading…
Reference in New Issue
Block a user