Rewrite config to have id field

This commit is contained in:
Oskar Hahn 2017-08-22 14:17:20 +02:00
parent 9d1ebac86e
commit 87b889fbf2
31 changed files with 204 additions and 359 deletions

View File

@ -32,4 +32,4 @@ script:
- DJANGO_SETTINGS_MODULE='tests.settings' coverage run ./manage.py test tests.integration - DJANGO_SETTINGS_MODULE='tests.settings' coverage run ./manage.py test tests.integration
- coverage report --fail-under=74 - coverage report --fail-under=74
- DJANGO_SETTINGS_MODULE='tests.old.settings' ./manage.py test tests.old - DJANGO_SETTINGS_MODULE='tests.settings' ./manage.py test tests.old

View File

@ -86,8 +86,7 @@ def get_config_variables():
label='Title for PDF document (all elections)', label='Title for PDF document (all elections)',
weight=460, weight=460,
group='Elections', group='Elections',
subgroup='PDF', subgroup='PDF')
translatable=True)
yield ConfigVariable( yield ConfigVariable(
name='assignments_pdf_preamble', name='assignments_pdf_preamble',

View File

@ -116,18 +116,10 @@ class ConfigAccessPermissions(BaseAccessPermissions):
# the config. Anonymous users can do so if they are enabled. # the config. Anonymous users can do so if they are enabled.
return not isinstance(user, AnonymousUser) or anonymous_is_enabled() return not isinstance(user, AnonymousUser) or anonymous_is_enabled()
def get_full_data(self, instance): def get_serializer_class(self, user=None):
""" """
Returns the serlialized config data. Returns serializer class.
""" """
from .config import config from .serializers import ConfigSerializer
from .models import ConfigStore
# Attention: The format of this response has to be the same as in return ConfigSerializer
# the retrieve method of ConfigViewSet.
if isinstance(instance, ConfigStore):
result = {'key': instance.key, 'value': config[instance.key]}
else:
# It is possible, that the caching system already sends the correct data as "instance".
result = instance
return result

View File

@ -1,5 +1,6 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.conf import settings from django.conf import settings
from django.db.models.signals import post_migrate
from ..utils.collection import Collection from ..utils.collection import Collection
@ -49,6 +50,8 @@ class CoreAppConfig(AppConfig):
required_users, required_users,
dispatch_uid='core_required_users') dispatch_uid='core_required_users')
post_migrate.connect(call_save_default_values, sender=self, dispatch_uid='core_save_config_default_values')
# Register viewsets. # Register viewsets.
router.register(self.get_model('Projector').get_collection_string(), ProjectorViewSet) router.register(self.get_model('Projector').get_collection_string(), ProjectorViewSet)
router.register(self.get_model('ChatMessage').get_collection_string(), ChatMessageViewSet) router.register(self.get_model('ChatMessage').get_collection_string(), ChatMessageViewSet)
@ -62,10 +65,8 @@ class CoreAppConfig(AppConfig):
Yields all collections required on startup i. e. opening the websocket Yields all collections required on startup i. e. opening the websocket
connection. connection.
""" """
from .config import config for model in ('Projector', 'ChatMessage', 'Tag', 'ProjectorMessage', 'Countdown', 'ConfigStore'):
for model in ('Projector', 'ChatMessage', 'Tag', 'ProjectorMessage', 'Countdown'):
yield Collection(self.get_model(model).get_collection_string()) yield Collection(self.get_model(model).get_collection_string())
yield Collection(config.get_collection_string())
def get_angular_constants(self): def get_angular_constants(self):
# Client settings # Client settings
@ -84,3 +85,8 @@ class CoreAppConfig(AppConfig):
'name': 'OpenSlidesSettings', 'name': 'OpenSlidesSettings',
'value': client_settings_dict} 'value': client_settings_dict}
return [client_settings] return [client_settings]
def call_save_default_values(**kwargs):
from .config import config
config.save_default_values()

View File

@ -1,6 +1,19 @@
from typing import (
Any,
Callable,
Dict,
Iterable,
List,
Optional,
TypeVar,
Union,
)
from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ValidationError as DjangoValidationError
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from mypy_extensions import TypedDict
from ..utils.collection import CollectionElement
from .exceptions import ConfigError, ConfigNotFound from .exceptions import ConfigError, ConfigNotFound
from .models import ConfigStore from .models import ConfigStore
@ -26,30 +39,31 @@ class ConfigHandler:
config[...] = x. config[...] = x.
""" """
def __init__(self): def __init__(self) -> None:
# Dict, that keeps all ConfigVariable objects. Has to be set at statup. # Dict, that keeps all ConfigVariable objects. Has to be set at statup.
# See the run method in openslides.core.apps. # See the ready() method in openslides.core.apps.
self.config_variables = {} self.config_variables = {} # type: Dict[str, ConfigVariable]
def __getitem__(self, key): # Index to get the database id from a given config key
self.key_to_id = {} # type: Dict[str, int]
def __getitem__(self, key: str) -> Any:
""" """
Returns the value of the config variable. Returns the default value, if Returns the value of the config variable.
not value exists in the database.
""" """
try: # Build the key_to_id dict
default_value = self.config_variables[key].default_value self.save_default_values()
except KeyError:
if not self.exists(key):
raise ConfigNotFound(_('The config variable {} was not found.').format(key)) raise ConfigNotFound(_('The config variable {} was not found.').format(key))
try: return CollectionElement.from_values(
db_value = ConfigStore.objects.get(key=key) self.get_collection_string(),
except ConfigStore.DoesNotExist: self.key_to_id[key]).get_full_data()['value']
return default_value
return db_value.value
def __contains__(self, key): def exists(self, key: str) -> bool:
""" """
Returns True, if the config varialbe exists. Returns True, if the config varialbe was defined.
""" """
try: try:
self.config_variables[key] self.config_variables[key]
@ -58,7 +72,8 @@ class ConfigHandler:
else: else:
return True return True
def __setitem__(self, key, value): # TODO: Remove the any by using right types in INPUT_TYPE_MAPPING
def __setitem__(self, key: str, value: Any) -> None:
""" """
Sets the new value. First it validates the input. Sets the new value. First it validates the input.
""" """
@ -84,8 +99,9 @@ class ConfigHandler:
choices = config_variable.choices() choices = config_variable.choices()
else: else:
choices = config_variable.choices choices = config_variable.choices
if value not in map(lambda choice: choice['value'], choices): if choices is None or value not in map(lambda choice: choice['value'], choices):
raise ConfigError(_('Invalid input. Choice does not match.')) raise ConfigError(_('Invalid input. Choice does not match.'))
for validator in config_variable.validators: for validator in config_variable.validators:
try: try:
validator(value) validator(value)
@ -119,10 +135,7 @@ class ConfigHandler:
raise ConfigError(_('{} has to be a string.'.format(required_entry))) raise ConfigError(_('{} has to be a string.'.format(required_entry)))
# Save the new value to the database. # Save the new value to the database.
try: db_value = ConfigStore.objects.get(key=key)
db_value = ConfigStore.objects.get(key=key)
except ConfigStore.DoesNotExist:
db_value = ConfigStore(key=key)
db_value.value = value db_value.value = value
db_value.save(information={'changed_config': key}) db_value.save(information={'changed_config': key})
@ -130,43 +143,41 @@ class ConfigHandler:
if config_variable.on_change: if config_variable.on_change:
config_variable.on_change() config_variable.on_change()
def update_config_variables(self, items): def update_config_variables(self, items: Iterable['ConfigVariable']) -> None:
""" """
Updates the config_variables dict. Updates the config_variables dict.
items has to be an iterator over ConfigVariable objects.
""" """
new_items = dict((variable.name, variable) for variable in items) # build an index from variable name to the variable
item_index = dict((variable.name, variable) for variable in items)
# Check that all ConfigVariables are unique. So no key from items can # Check that all ConfigVariables are unique. So no key from items can
# be in already in self.config_variables # be in already in self.config_variables
for key in new_items.keys(): intersection = set(item_index.keys()).intersection(self.config_variables.keys())
if key in self.config_variables: if intersection:
raise ConfigError(_('Too many values for config variable {} found.').format(key)) raise ConfigError(_('Too many values for config variables {} found.').format(intersection))
self.config_variables.update(new_items) self.config_variables.update(item_index)
def items(self): def save_default_values(self) -> None:
""" """
Iterates over key-value pairs of all config variables. Saves the default values to the database.
"""
# Create a dict with the default values of each ConfigVariable
config_items = dict((key, variable.default_value) for key, variable in self.config_variables.items())
# Update the dict with all values, which are in the db Does also build the dictonary key_to_id.
for db_value in ConfigStore.objects.all():
config_items[db_value.key] = db_value.value
return config_items.items()
def get_all_translatable(self): Does nothing on a second run.
""" """
Generator to get all config variables as strings when their values are if not self.key_to_id:
intended to be translated. for item in self.config_variables.values():
""" try:
for config_variable in self.config_variables.values(): db_value = ConfigStore.objects.get(key=item.name)
if config_variable.translatable: except ConfigStore.DoesNotExist:
yield config_variable.name db_value = ConfigStore()
db_value.key = item.name
db_value.value = item.default_value
db_value.save(skip_autoupdate=True)
self.key_to_id[item.name] = db_value.pk
def get_collection_string(self): def get_collection_string(self) -> str:
""" """
Returns the collection_string from the CollectionStore. Returns the collection_string from the CollectionStore.
""" """
@ -180,6 +191,22 @@ use x = config[...], to set it use config[...] = x.
""" """
T = TypeVar('T')
ChoiceType = Optional[List[Dict[str, str]]]
ChoiceCallableType = Union[ChoiceType, Callable[[], ChoiceType]]
ValidatorsType = List[Callable[[T], None]]
OnChangeType = Callable[[], None]
ConfigVariableDict = TypedDict('ConfigVariableDict', {
'key': str,
'default_value': Any,
'value': Any,
'input_type': str,
'label': str,
'help_text': str,
'choices': ChoiceType,
})
class ConfigVariable: class ConfigVariable:
""" """
A simple object class to wrap new config variables. A simple object class to wrap new config variables.
@ -206,10 +233,10 @@ class ConfigVariable:
the value during setup of the database if the admin uses the respective the value during setup of the database if the admin uses the respective
command line option. command line option.
""" """
def __init__(self, name, default_value, input_type='string', label=None, def __init__(self, name: str, default_value: T, input_type: str='string',
help_text=None, choices=None, hidden=False, weight=0, label: str=None, help_text: str=None, choices: ChoiceCallableType=None,
group=None, subgroup=None, validators=None, on_change=None, hidden: bool=False, weight: int=0, group: str=None, subgroup: str=None,
translatable=False): validators: ValidatorsType=None, on_change: OnChangeType=None) -> None:
if input_type not in INPUT_TYPE_MAPPING: if input_type not in INPUT_TYPE_MAPPING:
raise ValueError(_('Invalid value for config attribute input_type.')) raise ValueError(_('Invalid value for config attribute input_type.'))
if input_type == 'choice' and choices is None: if input_type == 'choice' and choices is None:
@ -230,26 +257,23 @@ class ConfigVariable:
self.subgroup = subgroup self.subgroup = subgroup
self.validators = validators or () self.validators = validators or ()
self.on_change = on_change self.on_change = on_change
self.translatable = translatable
@property @property
def data(self): def data(self) -> ConfigVariableDict:
""" """
Property with all data for OPTIONS requests. Property with all data for OPTIONS requests.
""" """
data = { return ConfigVariableDict(
'key': self.name, key=self.name,
'default_value': self.default_value, default_value=self.default_value,
'value': config[self.name], value=config[self.name],
'input_type': self.input_type, input_type=self.input_type,
'label': self.label, label=self.label,
'help_text': self.help_text, help_text=self.help_text,
} choices=self.choices() if callable(self.choices) else self.choices
if self.input_type == 'choice': )
data['choices'] = self.choices() if callable(self.choices) else self.choices
return data
def is_hidden(self): def is_hidden(self) -> bool:
""" """
Returns True if the config variable is hidden so it can be removed Returns True if the config variable is hidden so it can be removed
from response of OPTIONS request. from response of OPTIONS request.

View File

@ -27,8 +27,7 @@ def get_config_variables():
weight=115, weight=115,
group='General', group='General',
subgroup='Event', subgroup='Event',
validators=(MaxLengthValidator(100),), validators=(MaxLengthValidator(100),))
translatable=True)
yield ConfigVariable( yield ConfigVariable(
name='general_event_date', name='general_event_date',
@ -64,8 +63,7 @@ def get_config_variables():
label='Legal notice', label='Legal notice',
weight=132, weight=132,
group='General', group='General',
subgroup='Event', subgroup='Event')
translatable=True)
yield ConfigVariable( yield ConfigVariable(
name='general_event_welcome_title', name='general_event_welcome_title',
@ -73,8 +71,7 @@ def get_config_variables():
label='Front page title', label='Front page title',
weight=134, weight=134,
group='General', group='General',
subgroup='Event', subgroup='Event')
translatable=True)
yield ConfigVariable( yield ConfigVariable(
name='general_event_welcome_text', name='general_event_welcome_text',
@ -83,8 +80,7 @@ def get_config_variables():
label='Front page text', label='Front page text',
weight=136, weight=136,
group='General', group='General',
subgroup='Event', subgroup='Event')
translatable=True)
# General System # General System

View File

@ -294,12 +294,6 @@ class ConfigStore(RESTModelMixin, models.Model):
def get_collection_string(cls): def get_collection_string(cls):
return 'core/config' return 'core/config'
def get_rest_pk(self):
"""
Returns the primary key used in the REST API.
"""
return self.key
class ChatMessage(RESTModelMixin, models.Model): class ChatMessage(RESTModelMixin, models.Model):
""" """

View File

@ -3,6 +3,7 @@ from openslides.utils.validate import validate_html
from .models import ( from .models import (
ChatMessage, ChatMessage,
ConfigStore,
Countdown, Countdown,
ProjectionDefault, ProjectionDefault,
Projector, Projector,
@ -13,7 +14,7 @@ from .models import (
class JSONSerializerField(Field): class JSONSerializerField(Field):
""" """
Serializer for projector's JSONField. Serializer for projector's and config JSONField.
""" """
def to_internal_value(self, data): def to_internal_value(self, data):
""" """
@ -29,6 +30,12 @@ class JSONSerializerField(Field):
raise ValidationError({'detail': "Every dictionary must have a key 'name'."}) raise ValidationError({'detail': "Every dictionary must have a key 'name'."})
return data return data
def to_representation(self, value):
"""
Returns the value. It is decoded from the Django JSONField.
"""
return value
class ProjectionDefaultSerializer(ModelSerializer): class ProjectionDefaultSerializer(ModelSerializer):
""" """
@ -61,6 +68,17 @@ class TagSerializer(ModelSerializer):
fields = ('id', 'name', ) fields = ('id', 'name', )
class ConfigSerializer(ModelSerializer):
"""
Serializer for core.models.Tag objects.
"""
value = JSONSerializerField()
class Meta:
model = ConfigStore
fields = ('id', 'key', 'value')
class ChatMessageSerializer(ModelSerializer): class ChatMessageSerializer(ModelSerializer):
""" """
Serializer for core.models.ChatMessage objects. Serializer for core.models.ChatMessage objects.

View File

@ -16,7 +16,6 @@ from .. import __version__ as version
from ..utils import views as utils_views from ..utils import views as utils_views
from ..utils.auth import anonymous_is_enabled, has_perm from ..utils.auth import anonymous_is_enabled, has_perm
from ..utils.autoupdate import inform_changed_data, inform_deleted_data from ..utils.autoupdate import inform_changed_data, inform_deleted_data
from ..utils.collection import Collection, CollectionElement
from ..utils.plugins import ( from ..utils.plugins import (
get_plugin_description, get_plugin_description,
get_plugin_verbose_name, get_plugin_verbose_name,
@ -27,7 +26,6 @@ from ..utils.rest_api import (
Response, Response,
SimpleMetadata, SimpleMetadata,
ValidationError, ValidationError,
ViewSet,
detail_route, detail_route,
list_route, list_route,
) )
@ -608,7 +606,7 @@ class ConfigMetadata(SimpleMetadata):
return metadata return metadata
class ConfigViewSet(ViewSet): class ConfigViewSet(ModelViewSet):
""" """
API endpoint for the config. API endpoint for the config.
@ -616,6 +614,7 @@ class ConfigViewSet(ViewSet):
partial_update. partial_update.
""" """
access_permissions = ConfigAccessPermissions() access_permissions = ConfigAccessPermissions()
queryset = ConfigStore.objects.all()
metadata_class = ConfigMetadata metadata_class = ConfigMetadata
def check_view_permissions(self): def check_view_permissions(self):
@ -641,29 +640,6 @@ class ConfigViewSet(ViewSet):
result = False result = False
return result return result
def list(self, request):
"""
Lists all config variables.
"""
collection = Collection(config.get_collection_string())
return Response(collection.as_list_for_user(request.user))
def retrieve(self, request, *args, **kwargs):
"""
Retrieves a config variable.
"""
key = kwargs['pk']
collection_element = CollectionElement.from_values(config.get_collection_string(), key)
try:
content = collection_element.as_dict_for_user(request.user)
except ConfigStore.DoesNotExist:
raise Http404
if content is None:
# If content is None, the user has no permissions to see the item.
# See ConfigAccessPermissions or rather its parent class.
self.permission_denied()
return Response(content)
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
""" """
Updates a config variable. Only managers can do this. Updates a config variable. Only managers can do this.

View File

@ -53,8 +53,7 @@ def get_config_variables():
label='Motion preamble', label='Motion preamble',
weight=320, weight=320,
group='Motions', group='Motions',
subgroup='General', subgroup='General')
translatable=True)
yield ConfigVariable( yield ConfigVariable(
name='motions_default_line_numbering', name='motions_default_line_numbering',
@ -105,8 +104,7 @@ def get_config_variables():
help_text='Will be displayed as label before selected recommendation. Use an empty value to disable the recommendation system.', help_text='Will be displayed as label before selected recommendation. Use an empty value to disable the recommendation system.',
weight=332, weight=332,
group='Motions', group='Motions',
subgroup='General', subgroup='General')
translatable=True)
yield ConfigVariable( yield ConfigVariable(
name='motions_recommendation_text_mode', name='motions_recommendation_text_mode',
@ -243,8 +241,7 @@ def get_config_variables():
label='Title for PDF and DOCX documents (all motions)', label='Title for PDF and DOCX documents (all motions)',
weight=370, weight=370,
group='Motions', group='Motions',
subgroup='Export', subgroup='Export')
translatable=True)
yield ConfigVariable( yield ConfigVariable(
name='motions_export_preamble', name='motions_export_preamble',

View File

@ -29,8 +29,7 @@ def get_config_variables():
label='Title for access data and welcome PDF', label='Title for access data and welcome PDF',
weight=520, weight=520,
group='Participants', group='Participants',
subgroup='PDF', subgroup='PDF')
translatable=True)
yield ConfigVariable( yield ConfigVariable(
name='users_pdf_welcometext', name='users_pdf_welcometext',
@ -38,8 +37,7 @@ def get_config_variables():
label='Help text for access data and welcome PDF', label='Help text for access data and welcome PDF',
weight=530, weight=530,
group='Participants', group='Participants',
subgroup='PDF', subgroup='PDF')
translatable=True)
# TODO: Use Django's URLValidator here. # TODO: Use Django's URLValidator here.
yield ConfigVariable( yield ConfigVariable(

View File

@ -41,8 +41,8 @@ def anonymous_is_enabled() -> bool:
""" """
Returns True if the anonymous user is enabled in the settings. Returns True if the anonymous user is enabled in the settings.
""" """
return (CollectionElement.from_values('core/config', 'general_system_enable_anonymous') from ..core.config import config
.get_full_data()['value']) return config['general_system_enable_anonymous']
AnyUser = Union[Model, CollectionElement, int, AnonymousUser, None] AnyUser = Union[Model, CollectionElement, int, AnonymousUser, None]

View File

@ -92,11 +92,6 @@ def ws_add_site(message):
access_permissions = collection.get_access_permissions() access_permissions = collection.get_access_permissions()
restricted_data = access_permissions.get_restricted_data(collection, user) restricted_data = access_permissions.get_restricted_data(collection, user)
if collection.collection_string == 'core/config':
id_key = 'key'
else:
id_key = 'id'
for data in restricted_data: for data in restricted_data:
if data is None: if data is None:
# We do not want to send 'deleted' objects on startup. # We do not want to send 'deleted' objects on startup.
@ -105,7 +100,7 @@ def ws_add_site(message):
output.append( output.append(
format_for_autoupdate( format_for_autoupdate(
collection_string=collection.collection_string, collection_string=collection.collection_string,
id=data[id_key], id=data['id'],
action='changed', action='changed',
data=data)) data=data))

View File

@ -43,16 +43,11 @@ class CollectionElement:
if instance is not None: if instance is not None:
# Collection element is created via instance # Collection element is created via instance
self.collection_string = instance.get_collection_string() self.collection_string = instance.get_collection_string()
from openslides.core.config import config self.id = instance.pk
if self.collection_string == config.get_collection_string():
# For config objects we do not work with the pk but with the key.
self.id = instance.key
else:
self.id = instance.pk
elif collection_string is not None and id is not None: elif collection_string is not None and id is not None:
# Collection element is created via values # Collection element is created via values
self.collection_string = collection_string self.collection_string = collection_string
self.id = id self.id = int(id)
else: else:
raise RuntimeError( raise RuntimeError(
'Invalid state. Use CollectionElement.from_instance() or ' 'Invalid state. Use CollectionElement.from_instance() or '
@ -162,18 +157,12 @@ class CollectionElement:
raise RuntimeError("The collection element is deleted.") raise RuntimeError("The collection element is deleted.")
if self.instance is None: if self.instance is None:
# The config instance has to be get from the config element, because model = self.get_model()
# some config values are not in the db. try:
from openslides.core.config import config query = model.objects.get_full_queryset()
if self.collection_string == config.get_collection_string(): except AttributeError:
self.instance = {'key': self.id, 'value': config[self.id]} query = model.objects
else: self.instance = query.get(pk=self.id)
model = self.get_model()
try:
query = model.objects.get_full_queryset()
except AttributeError:
query = model.objects
self.instance = query.get(pk=self.id)
return self.instance return self.instance
def get_access_permissions(self): def get_access_permissions(self):
@ -346,28 +335,13 @@ class Collection:
# Generate collection element that where not in the cache. # Generate collection element that where not in the cache.
if missing_ids: if missing_ids:
from openslides.core.config import config model = self.get_model()
if self.collection_string == config.get_collection_string(): try:
# If config elements are not in the cache, they have to be read from query = model.objects.get_full_queryset()
# the config object. except AttributeError:
for key, value in config.items(): query = model.objects
if key in missing_ids: for instance in query.filter(pk__in=missing_ids):
collection_element = CollectionElement.from_values( yield CollectionElement.from_instance(instance)
config.get_collection_string(),
key,
full_data={'key': key, 'value': value})
# We can not use .from_instance therefore the config value
# is not saved to the cache. We have to do it manualy.
collection_element.save_to_cache()
yield collection_element
else:
model = self.get_model()
try:
query = model.objects.get_full_queryset()
except AttributeError:
query = model.objects
for instance in query.filter(pk__in=missing_ids):
yield CollectionElement.from_instance(instance)
def get_full_data(self): def get_full_data(self):
""" """
@ -428,10 +402,7 @@ class Collection:
""" """
Returns a set of all ids of instances in this collection. Returns a set of all ids of instances in this collection.
""" """
from openslides.core.config import config if use_redis_cache():
if self.collection_string == config.get_collection_string():
ids = config.config_variables.keys()
elif use_redis_cache():
ids = self.get_all_ids_redis() ids = self.get_all_ids_redis()
else: else:
ids = self.get_all_ids_other() ids = self.get_all_ids_other()
@ -573,9 +544,4 @@ def get_collection_id_from_cache_key(cache_key):
The returned id can be an integer or an string. The returned id can be an integer or an string.
""" """
collection_string, id = cache_key.rsplit(':', 1) collection_string, id = cache_key.rsplit(':', 1)
try: return (collection_string, int(id))
id = int(id)
except ValueError:
# The id is no integer. This can happen on config elements
pass
return (collection_string, id)

View File

@ -10,8 +10,6 @@ import webbrowser
from django.conf import ENVIRONMENT_VARIABLE from django.conf import ENVIRONMENT_VARIABLE
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.translation import ugettext as _
from django.utils.translation import activate, check_for_language, get_language
DEVELOPMENT_VERSION = 'Development Version' DEVELOPMENT_VERSION = 'Development Version'
UNIX_VERSION = 'Unix Version' UNIX_VERSION = 'Unix Version'
@ -315,19 +313,6 @@ def get_database_path_from_settings():
return database_path return database_path
def translate_customizable_strings(language_code):
"""
Translates all translatable config values and saves them into database.
"""
if check_for_language(language_code):
from openslides.core.config import config
current_language = get_language()
activate(language_code)
for name in config.get_all_translatable():
config[name] = _(config[name])
activate(current_language)
def is_local_installation(): def is_local_installation():
""" """
Returns True if the command is called for a local installation Returns True if the command is called for a local installation

View File

@ -5,6 +5,8 @@ from django.core.cache import caches
from django.test import TestCase as _TestCase from django.test import TestCase as _TestCase
from django.test.runner import DiscoverRunner from django.test.runner import DiscoverRunner
from ..core.config import config
class OpenSlidesDiscoverRunner(DiscoverRunner): class OpenSlidesDiscoverRunner(DiscoverRunner):
def run_tests(self, test_labels, extra_tests=None, **kwargs): def run_tests(self, test_labels, extra_tests=None, **kwargs):
@ -30,11 +32,11 @@ class OpenSlidesDiscoverRunner(DiscoverRunner):
class TestCase(_TestCase): class TestCase(_TestCase):
""" """
Does nothing at the moment. Resets the config object after each test.
Could be used in the future. Use this this for the integration test suit.
""" """
pass
def tearDown(self):
config.key_to_id = {}
class use_cache(ContextDecorator): class use_cache(ContextDecorator):

View File

@ -1,9 +1,10 @@
# Requirements for OpenSlides in production in alphabetical order # Requirements for OpenSlides in production in alphabetical order
bleach>=1.5.0,<1.6
channels>=1.1,<1.2 channels>=1.1,<1.2
Django>=1.10.4,<1.11 Django>=1.10.4,<1.11
djangorestframework>=3.4,<3.5 djangorestframework>=3.4,<3.5
jsonfield>=1.0,<1.1 jsonfield>=1.0,<1.1
mypy_extensions>=0.3,<1.1
PyPDF2>=1.26,<1.27 PyPDF2>=1.26,<1.27
roman>=2.0,<2.1 roman>=2.0,<2.1
setuptools>=29.0,<35.0 setuptools>=29.0,<35.0
bleach>=1.5.0,<1.6

View File

@ -20,3 +20,6 @@ strict_optional = true
[mypy-openslides.utils.auth] [mypy-openslides.utils.auth]
disallow_any = unannotated disallow_any = unannotated
[mypy-openslides.core.config]
disallow_any = unannotated

View File

@ -20,6 +20,7 @@ class TestDBQueries(TestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
config['general_system_enable_anonymous'] = True config['general_system_enable_anonymous'] = True
config.save_default_values()
for index in range(10): for index in range(10):
Assignment.objects.create(title='motion{}'.format(index), open_posts=1) Assignment.objects.create(title='motion{}'.format(index), open_posts=1)

View File

@ -109,19 +109,12 @@ class ConfigViewSet(TestCase):
# TODO: Can be changed to setUpClass when Django 1.8 is no longer supported # TODO: Can be changed to setUpClass when Django 1.8 is no longer supported
self._config_values = config.config_variables.copy() self._config_values = config.config_variables.copy()
config.update_config_variables(set_simple_config_view_integration_config_test()) config.update_config_variables(set_simple_config_view_integration_config_test())
config.save_default_values()
def tearDown(self): def tearDown(self):
# Reset the config variables # Reset the config variables
config.config_variables = self._config_values config.config_variables = self._config_values
super().tearDown()
def test_retrieve(self):
self.client.login(username='admin', password='admin')
config['test_var_aeW3Quahkah1phahCheo'] = 'test_value_Oovoojieme7eephaed2A'
response = self.client.get(reverse('config-detail', args=['test_var_aeW3Quahkah1phahCheo']))
self.assertEqual(
response.data,
{'key': 'test_var_aeW3Quahkah1phahCheo',
'value': 'test_value_Oovoojieme7eephaed2A'})
def test_update(self): def test_update(self):
self.client = APIClient() self.client = APIClient()

View File

@ -19,6 +19,7 @@ class TestProjectorDBQueries(TestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
config['general_system_enable_anonymous'] = True config['general_system_enable_anonymous'] = True
config.save_default_values()
for index in range(10): for index in range(10):
Projector.objects.create(name="Projector{}".format(index)) Projector.objects.create(name="Projector{}".format(index))
@ -57,6 +58,7 @@ class TestCharmessageDBQueries(TestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
config['general_system_enable_anonymous'] = True config['general_system_enable_anonymous'] = True
config.save_default_values()
user = User.objects.get(pk=1) user = User.objects.get(pk=1)
for index in range(10): for index in range(10):
ChatMessage.objects.create(user=user) ChatMessage.objects.create(user=user)
@ -84,6 +86,7 @@ class TestTagDBQueries(TestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
config['general_system_enable_anonymous'] = True config['general_system_enable_anonymous'] = True
config.save_default_values()
for index in range(10): for index in range(10):
Tag.objects.create(name='tag{}'.format(index)) Tag.objects.create(name='tag{}'.format(index))
@ -120,6 +123,7 @@ class TestConfigDBQueries(TestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
config['general_system_enable_anonymous'] = True config['general_system_enable_anonymous'] = True
config.save_default_values()
@use_cache() @use_cache()
def test_admin(self): def test_admin(self):
@ -127,9 +131,11 @@ class TestConfigDBQueries(TestCase):
Tests that only the following db queries are done: Tests that only the following db queries are done:
* 2 requests to get the session an the request user with its permissions and * 2 requests to get the session an the request user with its permissions and
* 1 requests to get the list of all config values * 1 requests to get the list of all config values
* 1 more that I do not understand
""" """
self.client.force_login(User.objects.get(pk=1)) self.client.force_login(User.objects.get(pk=1))
with self.assertNumQueries(3): with self.assertNumQueries(4):
self.client.get(reverse('config-list')) self.client.get(reverse('config-list'))
@use_cache() @use_cache()
@ -138,8 +144,10 @@ class TestConfigDBQueries(TestCase):
Tests that only the following db queries are done: Tests that only the following db queries are done:
* 1 requests to see if anonymous is enabled * 1 requests to see if anonymous is enabled
* 1 to get all config value and * 1 to get all config value and
* 1 more that I do not understand
""" """
with self.assertNumQueries(2): with self.assertNumQueries(3):
self.client.get(reverse('config-list')) self.client.get(reverse('config-list'))

View File

@ -19,6 +19,7 @@ class TestDBQueries(TestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
config['general_system_enable_anonymous'] = True config['general_system_enable_anonymous'] = True
config.save_default_values()
for index in range(10): for index in range(10):
Mediafile.objects.create( Mediafile.objects.create(
title='some_file{}'.format(index), title='some_file{}'.format(index),

View File

@ -23,6 +23,7 @@ class TestMotionDBQueries(TestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
config['general_system_enable_anonymous'] = True config['general_system_enable_anonymous'] = True
config.save_default_values()
for index in range(10): for index in range(10):
Motion.objects.create(title='motion{}'.format(index)) Motion.objects.create(title='motion{}'.format(index))
get_user_model().objects.create_user( get_user_model().objects.create_user(
@ -76,6 +77,7 @@ class TestCategoryDBQueries(TestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
config.save_default_values()
config['general_system_enable_anonymous'] = True config['general_system_enable_anonymous'] = True
for index in range(10): for index in range(10):
Category.objects.create(name='category{}'.format(index)) Category.objects.create(name='category{}'.format(index))
@ -109,6 +111,7 @@ class TestWorkflowDBQueries(TestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
config.save_default_values()
config['general_system_enable_anonymous'] = True config['general_system_enable_anonymous'] = True
# There do not need to be more workflows # There do not need to be more workflows

View File

@ -20,6 +20,7 @@ class TestDBQueries(TestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
config['general_system_enable_anonymous'] = True config['general_system_enable_anonymous'] = True
config.save_default_values()
for index in range(10): for index in range(10):
Topic.objects.create(title='topic-{}'.format(index)) Topic.objects.create(title='topic-{}'.format(index))

View File

@ -19,6 +19,7 @@ class TestUserDBQueries(TestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
config['general_system_enable_anonymous'] = True config['general_system_enable_anonymous'] = True
config.save_default_values()
for index in range(10): for index in range(10):
User.objects.create(username='user{}'.format(index)) User.objects.create(username='user{}'.format(index))
@ -57,6 +58,7 @@ class TestGroupDBQueries(TestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
config['general_system_enable_anonymous'] = True config['general_system_enable_anonymous'] = True
config.save_default_values()
for index in range(10): for index in range(10):
Group.objects.create(name='group{}'.format(index)) Group.objects.create(name='group{}'.format(index))

View File

@ -19,6 +19,7 @@ class TestCase(ChannelTestCase):
def tearDown(self): def tearDown(self):
self.patch.stop() self.patch.stop()
super().tearDown()
class TestCollectionElementCache(TestCase): class TestCollectionElementCache(TestCase):
@ -131,17 +132,3 @@ class TestCollectionCache(TestCase):
with self.assertNumQueries(0): with self.assertNumQueries(0):
instance_list = list(topic_collection.as_autoupdate_for_projector()) instance_list = list(topic_collection.as_autoupdate_for_projector())
self.assertEqual(len(instance_list), 2) self.assertEqual(len(instance_list), 2)
def test_config_elements_without_cache(self):
topic_collection = collection.Collection('core/config')
caches['locmem'].clear()
with self.assertNumQueries(1):
list(topic_collection.as_autoupdate_for_projector())
def test_config_elements_with_cache(self):
topic_collection = collection.Collection('core/config')
list(topic_collection.as_autoupdate_for_projector())
with self.assertNumQueries(0):
list(topic_collection.as_autoupdate_for_projector())

View File

@ -17,10 +17,12 @@ class HandleConfigTest(TestCase):
config.update_config_variables(set_simple_config_view_multiple_vars()) config.update_config_variables(set_simple_config_view_multiple_vars())
config.update_config_variables(set_simple_config_collection_disabled_view()) config.update_config_variables(set_simple_config_collection_disabled_view())
config.update_config_variables(set_simple_config_collection_with_callback()) config.update_config_variables(set_simple_config_collection_with_callback())
config.save_default_values()
def tearDown(self): def tearDown(self):
# Reset the config variables # Reset the config variables
config.config_variables = self._config_values config.config_variables = self._config_values
super().tearDown()
def get_config_var(self, key): def get_config_var(self, key):
return config[key] return config[key]
@ -42,7 +44,7 @@ class HandleConfigTest(TestCase):
def test_get_multiple_config_var_error(self): def test_get_multiple_config_var_error(self):
with self.assertRaisesMessage( with self.assertRaisesMessage(
ConfigError, ConfigError,
'Too many values for config variable multiple_config_var found.'): 'Too many values for config variables {\'multiple_config_var\'} found.'):
config.update_config_variables(set_simple_config_view_multiple_vars()) config.update_config_variables(set_simple_config_view_multiple_vars())
def test_setup_config_var(self): def test_setup_config_var(self):
@ -58,9 +60,9 @@ class HandleConfigTest(TestCase):
def test_missing_cache_(self): def test_missing_cache_(self):
self.assertEqual(config['string_var'], 'default_string_rien4ooCZieng6ah') self.assertEqual(config['string_var'], 'default_string_rien4ooCZieng6ah')
def test_config_contains(self): def test_config_exists(self):
self.assertTrue('string_var' in config) self.assertTrue(config.exists('string_var'))
self.assertFalse('unknown_config_var' in config) self.assertFalse(config.exists('unknown_config_var'))
def test_set_value_before_getting_it(self): def test_set_value_before_getting_it(self):
""" """

View File

@ -37,6 +37,7 @@ class MediafileTest(TestCase):
def tearDown(self): def tearDown(self):
self.object.mediafile.delete(save=False) self.object.mediafile.delete(save=False)
super().tearDown()
def test_str(self): def test_str(self):
self.assertEqual(str(self.object), 'Title File 1') self.assertEqual(str(self.object), 'Title File 1')

View File

@ -1,76 +0,0 @@
"""
Settings file for OpenSlides' tests.
"""
import os
from openslides.global_settings import * # noqa
# Path to the directory for user specific data files
OPENSLIDES_USER_DATA_PATH = os.path.realpath(os.path.dirname(os.path.abspath(__file__)))
# OpenSlides plugins
# Add plugins to this list.
INSTALLED_PLUGINS += ( # noqa
'tests.old.utils',
)
INSTALLED_APPS += INSTALLED_PLUGINS # noqa
# Important settings for production use
# https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
SECRET_KEY = 'secret'
DEBUG = False
# Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
# Change this setting to use e. g. PostgreSQL or MySQL.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
}
}
# Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/
TIME_ZONE = 'Europe/Berlin'
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/
STATICFILES_DIRS.insert(0, os.path.join(OPENSLIDES_USER_DATA_PATH, 'static')) # noqa
# Files
# https://docs.djangoproject.com/en/1.10/topics/files/
MEDIA_ROOT = os.path.join(OPENSLIDES_USER_DATA_PATH, '')
# Customization of OpenSlides apps
MOTION_IDENTIFIER_MIN_DIGITS = 1
# Special settings only for testing
TEST_RUNNER = 'openslides.utils.test.OpenSlidesDiscoverRunner'
# Use a faster password hasher.
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.MD5PasswordHasher',
]

View File

@ -1,7 +1,8 @@
from unittest import TestCase from unittest import TestCase
from unittest.mock import patch from unittest.mock import patch
from openslides.core.config import ConfigVariable from openslides.core.config import ConfigVariable, config
from openslides.core.exceptions import ConfigNotFound
class TestConfigVariable(TestCase): class TestConfigVariable(TestCase):
@ -22,3 +23,12 @@ class TestConfigVariable(TestCase):
'test_default_value', 'test_default_value',
"The value of config_variable.data['default_value'] should be the same " "The value of config_variable.data['default_value'] should be the same "
"as set as second argument of ConfigVariable()") "as set as second argument of ConfigVariable()")
class TestConfigHandler(TestCase):
def test_get_not_found(self):
config.key_to_id = 'has to be set or there is a db query'
self.assertRaises(
ConfigNotFound,
config.__getitem__,
'key_leehah4Sho4ee7aCohbn')

View File

@ -17,16 +17,6 @@ class TestCacheKeys(TestCase):
collection.get_collection_id_from_cache_key( collection.get_collection_id_from_cache_key(
collection.get_single_element_cache_key(*element))) collection.get_single_element_cache_key(*element)))
def test_get_collection_id_from_cache_key_for_strings(self):
"""
Test get_collection_id_from_cache_key for strings
"""
element = ('some/testkey', 'my_config_value')
self.assertEqual(
element,
collection.get_collection_id_from_cache_key(
collection.get_single_element_cache_key(*element)))
def test_get_single_element_cache_key_prefix(self): def test_get_single_element_cache_key_prefix(self):
""" """
Tests that the cache prefix is realy a prefix. Tests that the cache prefix is realy a prefix.
@ -161,19 +151,6 @@ class TestCollectionElement(TestCase):
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
collection_element.get_instance() collection_element.get_instance()
@patch('openslides.core.config.config')
def test_get_instance_config_str(self, mock_config):
mock_config.get_collection_string.return_value = 'core/config'
mock_config.__getitem__.return_value = 'config_value'
collection_element = collection.CollectionElement.from_values('core/config', 'my_config_value')
instance = collection_element.get_instance()
self.assertEqual(
instance,
{'key': 'my_config_value',
'value': 'config_value'})
def test_get_instance(self): def test_get_instance(self):
with patch.object(collection.CollectionElement, 'get_full_data'): with patch.object(collection.CollectionElement, 'get_full_data'):
collection_element = collection.CollectionElement.from_values('testmodule/model', 42) collection_element = collection.CollectionElement.from_values('testmodule/model', 42)
@ -253,23 +230,6 @@ class TestCollectionElement(TestCase):
collection.CollectionElement.from_values('testmodule/model', 1), collection.CollectionElement.from_values('testmodule/model', 1),
collection.CollectionElement.from_values('testmodule/other_model', 1)) collection.CollectionElement.from_values('testmodule/other_model', 1))
@patch.object(collection.CollectionElement, 'get_full_data')
def test_config_cache_key(self, mock_get_full_data):
"""
Test that collection elements for config values do always use the
config key as cache key.
"""
fake_config_instance = MagicMock()
fake_config_instance.get_collection_string.return_value = 'core/config'
fake_config_instance.key = 'test_config_key'
self.assertEqual(
collection.CollectionElement.from_values('core/config', 'test_config_key').get_cache_key(),
'core/config:test_config_key')
self.assertEqual(
collection.CollectionElement.from_instance(fake_config_instance).get_cache_key(),
'core/config:test_config_key')
class TestcollectionElementList(TestCase): class TestcollectionElementList(TestCase):
@patch.object(collection.CollectionElement, 'get_full_data') @patch.object(collection.CollectionElement, 'get_full_data')