Merge pull request #1549 from normanjaeckel/ConfigRefactoring
Refactored config API. Removed form_fields. Added extra fields for HT…
This commit is contained in:
commit
a0f4506c35
@ -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(
|
|
||||||
validators=[validate_start_time, ],
|
|
||||||
widget=forms.DateTimeInput(format='%d.%m.%Y %H:%M'),
|
|
||||||
required=False,
|
|
||||||
label=ugettext_lazy('Begin of event'),
|
label=ugettext_lazy('Begin of event'),
|
||||||
help_text=ugettext_lazy('Input format: DD.MM.YYYY HH:MM')))
|
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',
|
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'),
|
||||||
max_length=20,
|
weight=240,
|
||||||
required=False))
|
group=ugettext_lazy('Agenda'),
|
||||||
|
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=(
|
||||||
('arabic', ugettext_lazy('Arabic')),
|
{'value': 'arabic', 'display_name': ugettext_lazy('Arabic')},
|
||||||
('roman', ugettext_lazy('Roman'))),
|
{'value': 'roman', 'display_name': ugettext_lazy('Roman')}),
|
||||||
required=False))
|
weight=250,
|
||||||
|
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(),
|
|
||||||
required=False,
|
|
||||||
label=ugettext_lazy('Election method'),
|
label=ugettext_lazy('Election method'),
|
||||||
choices=(
|
choices=(
|
||||||
('auto', ugettext_lazy('Automatic assign of method')),
|
{'value': 'auto', 'display_name': ugettext_lazy('Automatic assign of method')},
|
||||||
('votes', ugettext_lazy('Always one option per candidate')),
|
{'value': 'votes', 'display_name': ugettext_lazy('Always one option per candidate')},
|
||||||
('yesnoabstain', ugettext_lazy('Always Yes-No-Abstain per candidate')))))
|
{'value': 'yesnoabstain', 'display_name': ugettext_lazy('Always Yes-No-Abstain per candidate')}),
|
||||||
assignments_poll_100_percent_base = ConfigVariable(
|
weight=410,
|
||||||
|
group=ugettext_lazy('Elections'),
|
||||||
|
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(),
|
|
||||||
required=False,
|
|
||||||
label=ugettext_lazy('The 100 % base of an election result consists of'),
|
label=ugettext_lazy('The 100 % base of an election result consists of'),
|
||||||
choices=PERCENT_BASE_CHOICES))
|
choices=PERCENT_BASE_CHOICES,
|
||||||
assignments_pdf_ballot_papers_selection = ConfigVariable(
|
weight=420,
|
||||||
|
group=ugettext_lazy('Elections'),
|
||||||
|
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(),
|
|
||||||
required=False,
|
|
||||||
label=ugettext_lazy('Number of ballot papers (selection)'),
|
label=ugettext_lazy('Number of ballot papers (selection)'),
|
||||||
choices=(
|
choices=(
|
||||||
('NUMBER_OF_DELEGATES', ugettext_lazy('Number of all delegates')),
|
{'value': 'NUMBER_OF_DELEGATES', 'display_name': ugettext_lazy('Number of all delegates')},
|
||||||
('NUMBER_OF_ALL_PARTICIPANTS', ugettext_lazy('Number of all participants')),
|
{'value': 'NUMBER_OF_ALL_PARTICIPANTS', 'display_name': ugettext_lazy('Number of all participants')},
|
||||||
('CUSTOM_NUMBER', ugettext_lazy('Use the following custom number')))))
|
{'value': 'CUSTOM_NUMBER', 'display_name': ugettext_lazy('Use the following custom number')}),
|
||||||
assignments_pdf_ballot_papers_number = ConfigVariable(
|
weight=430,
|
||||||
|
group=ugettext_lazy('Elections'),
|
||||||
|
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)'),
|
||||||
group_ballot = ConfigGroup(
|
weight=450,
|
||||||
title=ugettext_lazy('Ballot and ballot papers'),
|
group=ugettext_lazy('Elections'),
|
||||||
variables=(assignments_poll_vote_values,
|
subgroup=ugettext_lazy('Ballot and ballot papers'))
|
||||||
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.
|
||||||
|
try:
|
||||||
config[key] = value
|
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(
|
|
||||||
widget=forms.TextInput(),
|
|
||||||
label=ugettext_lazy('Event name'),
|
label=ugettext_lazy('Event name'),
|
||||||
max_length=50))
|
weight=110,
|
||||||
|
group=ugettext_lazy('General'),
|
||||||
|
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,
|
|
||||||
form_field=forms.CharField(
|
|
||||||
widget=forms.TextInput(),
|
|
||||||
label=ugettext_lazy('Short description of event'),
|
label=ugettext_lazy('Short description of event'),
|
||||||
required=False,
|
weight=115,
|
||||||
max_length=100))
|
group=ugettext_lazy('General'),
|
||||||
|
subgroup=ugettext_lazy('Event'),
|
||||||
|
validators=(MaxLengthValidator(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(
|
|
||||||
widget=forms.TextInput(),
|
|
||||||
label=ugettext_lazy('Event date'),
|
label=ugettext_lazy('Event date'),
|
||||||
required=False))
|
weight=120,
|
||||||
|
group=ugettext_lazy('General'),
|
||||||
|
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(
|
|
||||||
widget=forms.TextInput(),
|
|
||||||
label=ugettext_lazy('Event location'),
|
label=ugettext_lazy('Event location'),
|
||||||
required=False))
|
weight=125,
|
||||||
|
group=ugettext_lazy('General'),
|
||||||
|
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(
|
|
||||||
widget=forms.TextInput(),
|
|
||||||
label=ugettext_lazy('Event organizer'),
|
label=ugettext_lazy('Event organizer'),
|
||||||
required=False))
|
weight=130,
|
||||||
|
group=ugettext_lazy('General'),
|
||||||
|
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(
|
|
||||||
widget=forms.TextInput(),
|
|
||||||
label=ugettext_lazy('Background color of projector header'),
|
label=ugettext_lazy('Background color of projector header'),
|
||||||
help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'),
|
help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'),
|
||||||
required=True))
|
weight=160,
|
||||||
|
group=ugettext_lazy('Projector'))
|
||||||
|
|
||||||
projector_backgroundcolor2 = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='projector_backgroundcolor2',
|
name='projector_backgroundcolor2',
|
||||||
default_value='#222222',
|
default_value='#222222',
|
||||||
form_field=forms.CharField(
|
|
||||||
widget=forms.TextInput(),
|
|
||||||
label=ugettext_lazy('Second (optional) background color for linear color gradient'),
|
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".'),
|
help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'),
|
||||||
required=False))
|
weight=165,
|
||||||
|
group=ugettext_lazy('Projector'))
|
||||||
|
|
||||||
projector_fontcolor = ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='projector_fontcolor',
|
name='projector_fontcolor',
|
||||||
default_value='#F5F5F5',
|
default_value='#F5F5F5',
|
||||||
form_field=forms.CharField(
|
|
||||||
widget=forms.TextInput(),
|
|
||||||
label=ugettext_lazy('Font color of projector header'),
|
label=ugettext_lazy('Font color of projector header'),
|
||||||
help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'),
|
help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'),
|
||||||
required=True))
|
weight=170,
|
||||||
|
group=ugettext_lazy('Projector'))
|
||||||
|
|
||||||
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,
|
|
||||||
form_field=forms.CharField(
|
|
||||||
widget=forms.TextInput(),
|
|
||||||
label=ugettext_lazy('Title'),
|
label=ugettext_lazy('Title'),
|
||||||
help_text=ugettext_lazy('Also used for the default welcome slide.'),
|
help_text=ugettext_lazy('Also used for the default welcome slide.'),
|
||||||
required=False))
|
weight=175,
|
||||||
|
group=ugettext_lazy('Projector'),
|
||||||
|
translatable=True)
|
||||||
|
|
||||||
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,
|
|
||||||
form_field=forms.CharField(
|
|
||||||
widget=forms.Textarea(),
|
|
||||||
label=ugettext_lazy('Welcome text'),
|
label=ugettext_lazy('Welcome text'),
|
||||||
required=False))
|
weight=180,
|
||||||
|
group=ugettext_lazy('Projector'),
|
||||||
group_event = ConfigGroup(
|
translatable=True)
|
||||||
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'),
|
||||||
required=True,
|
choices=({'value': str(workflow.pk), 'display_name': ugettext_lazy(workflow.name)} for workflow in Workflow.objects.all()),
|
||||||
choices=[(str(workflow.pk), ugettext_lazy(workflow.name)) for workflow in Workflow.objects.all()]))
|
weight=310,
|
||||||
motions_identifier = ConfigVariable(
|
group=ugettext_lazy('Motion'),
|
||||||
|
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(),
|
|
||||||
required=True,
|
|
||||||
label=ugettext_lazy('Identifier'),
|
label=ugettext_lazy('Identifier'),
|
||||||
choices=[
|
choices=(
|
||||||
('per_category', ugettext_lazy('Numbered per category')),
|
{'value': 'per_category', 'display_name': ugettext_lazy('Numbered per category')},
|
||||||
('serially_numbered', ugettext_lazy('Serially numbered')),
|
{'value': 'serially_numbered', 'display_name': ugettext_lazy('Serially numbered')},
|
||||||
('manually', ugettext_lazy('Set it manually'))]))
|
{'value': 'manually', 'display_name': ugettext_lazy('Set it manually')}),
|
||||||
motions_preamble = ConfigVariable(
|
weight=315,
|
||||||
|
group=ugettext_lazy('Motion'),
|
||||||
|
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'),
|
||||||
min_value=0,
|
help_text=ugettext_lazy('Choose 0 to disable the supporting system.'),
|
||||||
help_text=ugettext_lazy('Choose 0 to disable the supporting system.')))
|
weight=345,
|
||||||
motions_remove_supporters = ConfigVariable(
|
group=ugettext_lazy('Motion'),
|
||||||
|
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(),
|
|
||||||
required=False,
|
|
||||||
label=ugettext_lazy('The 100 % base of a voting result consists of'),
|
label=ugettext_lazy('The 100 % base of a voting result consists of'),
|
||||||
choices=PERCENT_BASE_CHOICES))
|
choices=PERCENT_BASE_CHOICES,
|
||||||
motions_pdf_ballot_papers_selection = ConfigVariable(
|
weight=355,
|
||||||
|
group=ugettext_lazy('Motion'),
|
||||||
|
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(),
|
|
||||||
required=False,
|
|
||||||
label=ugettext_lazy('Number of ballot papers (selection)'),
|
label=ugettext_lazy('Number of ballot papers (selection)'),
|
||||||
choices=[
|
choices=(
|
||||||
('NUMBER_OF_DELEGATES', ugettext_lazy('Number of all delegates')),
|
{'value': 'NUMBER_OF_DELEGATES', 'display_name': ugettext_lazy('Number of all delegates')},
|
||||||
('NUMBER_OF_ALL_PARTICIPANTS', ugettext_lazy('Number of all participants')),
|
{'value': 'NUMBER_OF_ALL_PARTICIPANTS', 'display_name': ugettext_lazy('Number of all participants')},
|
||||||
('CUSTOM_NUMBER', ugettext_lazy("Use the following custom number"))]))
|
{'value': 'CUSTOM_NUMBER', 'display_name': ugettext_lazy('Use the following custom number')}),
|
||||||
motions_pdf_ballot_papers_number = ConfigVariable(
|
weight=360,
|
||||||
|
group=ugettext_lazy('Motion'),
|
||||||
|
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)'),
|
||||||
required=False))
|
weight=380,
|
||||||
group_pdf = ConfigGroup(
|
group=ugettext_lazy('Motion'),
|
||||||
title=ugettext_lazy('PDF'),
|
subgroup=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_general = ConfigGroup(
|
group=ugettext_lazy('Users'),
|
||||||
title=ugettext_lazy('Sorting'),
|
subgroup=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(
|
|
||||||
widget=forms.TextInput(),
|
|
||||||
required=False,
|
|
||||||
label=ugettext_lazy('System URL'),
|
label=ugettext_lazy('System URL'),
|
||||||
help_text=ugettext_lazy('Used for QRCode in PDF of access data.')))
|
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',
|
name='users_pdf_wlan_ssid',
|
||||||
default_value='',
|
default_value='',
|
||||||
form_field=forms.CharField(
|
|
||||||
widget=forms.TextInput(),
|
|
||||||
required=False,
|
|
||||||
label=ugettext_lazy('WLAN name (SSID)'),
|
label=ugettext_lazy('WLAN name (SSID)'),
|
||||||
help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.')))
|
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',
|
name='users_pdf_wlan_password',
|
||||||
default_value='',
|
default_value='',
|
||||||
form_field=forms.CharField(
|
|
||||||
widget=forms.TextInput(),
|
|
||||||
required=False,
|
|
||||||
label=ugettext_lazy('WLAN password'),
|
label=ugettext_lazy('WLAN password'),
|
||||||
help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.')))
|
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',
|
name='users_pdf_wlan_encryption',
|
||||||
default_value='',
|
default_value='',
|
||||||
form_field=forms.ChoiceField(
|
input_type='choice',
|
||||||
widget=forms.Select(),
|
|
||||||
required=False,
|
|
||||||
label=ugettext_lazy('WLAN encryption'),
|
label=ugettext_lazy('WLAN encryption'),
|
||||||
help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.'),
|
help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.'),
|
||||||
choices=(
|
choices=(
|
||||||
('', '---------'),
|
{'value': '', 'display_name': '---------'},
|
||||||
('WEP', 'WEP'),
|
{'value': 'WEP', 'display_name': ugettext_lazy('WEP')},
|
||||||
('WPA', 'WPA/WPA2'),
|
{'value': 'WPA', 'display_name': ugettext_lazy('WPA/WPA2')},
|
||||||
('nopass', ugettext_lazy('No encryption')))))
|
{'value': 'nopass', 'display_name': ugettext_lazy('No encryption')}),
|
||||||
|
weight=570,
|
||||||
group_pdf = ConfigGroup(
|
group=ugettext_lazy('Users'),
|
||||||
title=ugettext_lazy('PDF'),
|
subgroup=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',
|
|
||||||
|
yield ConfigVariable(
|
||||||
|
name='test_var_ohhii4iavoh5Phoh5ahg',
|
||||||
default_value=0,
|
default_value=0,
|
||||||
form_field=forms.IntegerField())))
|
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',
|
|
||||||
url='testsimplepage4',
|
|
||||||
variables=(ConfigVariable(
|
|
||||||
name='var_with_callback_ghvnfjd5768gdfkwg0hm2',
|
name='var_with_callback_ghvnfjd5768gdfkwg0hm2',
|
||||||
default_value='',
|
default_value='',
|
||||||
on_change=callback),))
|
on_change=callback)
|
||||||
|
Loading…
Reference in New Issue
Block a user