diff --git a/CHANGELOG b/CHANGELOG index 8c5e5a6ff..9cb47a72a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -37,7 +37,7 @@ Other: - Refactored projector API using metaclasses now. - Renamed SignalConnectMetaClass classmethod get_all_objects to get_all (private API). -- Refactored config API. +- Refactored config API and moved it into the core app. - Removed old style personal info page, main menu entries and widget API. - Used AngularJS with additional libraries for single page frontend. - Removed use of 'django.views.i18n.javascript_catalog'. Used angular-gettext diff --git a/openslides/agenda/apps.py b/openslides/agenda/apps.py index 8b6efe4fa..f6a935eb6 100644 --- a/openslides/agenda/apps.py +++ b/openslides/agenda/apps.py @@ -12,7 +12,7 @@ class AgendaAppConfig(AppConfig): # Import all required stuff. from django.db.models.signals import pre_delete - from openslides.config.signals import config_signal + from openslides.core.signals import config_signal from openslides.utils.rest_api import router from .signals import setup_agenda_config, listen_to_related_object_delete_signal from .views import ItemViewSet diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index 1e93d86fb..c8098803a 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -9,12 +9,12 @@ from django.db import models from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy, ugettext_noop -from openslides.config.api import config +from openslides.core.config import config from openslides.core.models import Tag from openslides.projector.models import SlideMixin from openslides.users.models import User from openslides.utils.exceptions import OpenSlidesError -from openslides.utils.rest_api import RESTModelMixin +from openslides.utils.models import RESTModelMixin from openslides.utils.utils import to_roman diff --git a/openslides/agenda/signals.py b/openslides/agenda/signals.py index 1dd9f8b98..b22f62da8 100644 --- a/openslides/agenda/signals.py +++ b/openslides/agenda/signals.py @@ -6,7 +6,7 @@ from django.core.validators import MaxLengthValidator, MinValueValidator from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy -from openslides.config.api import ConfigVariable +from openslides.core.config import ConfigVariable from .models import Item @@ -22,7 +22,7 @@ def setup_agenda_config(sender, **kwargs): """ Receiver function to setup all agenda config variables. They are not grouped. This function connected to the signal - openslides.config.signals.config_signal during app loading. + openslides.core.signals.config_signal during app loading. """ # TODO: Use an input type with generic datetime support. yield ConfigVariable( diff --git a/openslides/assignments/apps.py b/openslides/assignments/apps.py index 2fb5d888b..2cc34758d 100644 --- a/openslides/assignments/apps.py +++ b/openslides/assignments/apps.py @@ -11,7 +11,7 @@ class AssignmentAppConfig(AppConfig): from . import projector # noqa # Import all required stuff. - from openslides.config.signals import config_signal + from openslides.core.signals import config_signal from openslides.utils.rest_api import router from .signals import setup_assignment_config from .views import AssignmentViewSet, AssignmentPollViewSet diff --git a/openslides/assignments/models.py b/openslides/assignments/models.py index f41113a58..d07465168 100644 --- a/openslides/assignments/models.py +++ b/openslides/assignments/models.py @@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy, ugettext_noop from openslides.agenda.models import Item, Speaker -from openslides.config.api import config +from openslides.core.config import config from openslides.core.models import Tag from openslides.poll.models import ( BaseOption, @@ -17,7 +17,7 @@ from openslides.poll.models import ( from openslides.projector.models import SlideMixin from openslides.users.models import User from openslides.utils.exceptions import OpenSlidesError -from openslides.utils.rest_api import RESTModelMixin +from openslides.utils.models import RESTModelMixin class AssignmentRelatedUser(RESTModelMixin, models.Model): diff --git a/openslides/assignments/signals.py b/openslides/assignments/signals.py index bd74bc997..17f920948 100644 --- a/openslides/assignments/signals.py +++ b/openslides/assignments/signals.py @@ -2,7 +2,7 @@ from django.core.validators import MinValueValidator from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy -from openslides.config.api import ConfigVariable +from openslides.core.config import ConfigVariable from openslides.poll.models import PERCENT_BASE_CHOICES @@ -10,7 +10,7 @@ def setup_assignment_config(sender, **kwargs): """ 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.core.signals.config_signal during app loading. """ # Ballot and ballot papers diff --git a/openslides/assignments/views.py b/openslides/assignments/views.py index f249df58e..dabbac719 100644 --- a/openslides/assignments/views.py +++ b/openslides/assignments/views.py @@ -15,7 +15,7 @@ from reportlab.platypus import ( TableStyle, ) -from openslides.config.api import config +from openslides.core.config import config from openslides.users.models import Group, User # TODO: remove this from openslides.utils.pdf import stylesheet from openslides.utils.rest_api import ( diff --git a/openslides/config/__init__.py b/openslides/config/__init__.py deleted file mode 100644 index 2c5bdbda5..000000000 --- a/openslides/config/__init__.py +++ /dev/null @@ -1 +0,0 @@ -default_app_config = 'openslides.config.apps.ConfigAppConfig' diff --git a/openslides/config/apps.py b/openslides/config/apps.py deleted file mode 100644 index 5629cf41f..000000000 --- a/openslides/config/apps.py +++ /dev/null @@ -1,14 +0,0 @@ -from django.apps import AppConfig - - -class ConfigAppConfig(AppConfig): - name = 'openslides.config' - verbose_name = 'OpenSlides Config' - - def ready(self): - # Import all required stuff. - from openslides.utils.rest_api import router - from .views import ConfigViewSet - - # Register viewsets. - router.register('config/config', ConfigViewSet, 'config') diff --git a/openslides/config/exceptions.py b/openslides/config/exceptions.py deleted file mode 100644 index 5c8ee69e2..000000000 --- a/openslides/config/exceptions.py +++ /dev/null @@ -1,9 +0,0 @@ -from openslides.utils.exceptions import OpenSlidesError - - -class ConfigError(OpenSlidesError): - pass - - -class ConfigNotFound(ConfigError): - pass diff --git a/openslides/config/models.py b/openslides/config/models.py deleted file mode 100644 index 6b739811d..000000000 --- a/openslides/config/models.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.db import models -from django.utils.translation import ugettext_noop -from jsonfield import JSONField - - -class ConfigStore(models.Model): - """ - A model class to store all config variables in the database. - """ - - key = models.CharField(max_length=255, unique=True, db_index=True) - """A string, the key of the config variable.""" - - value = JSONField() - """The value of the config variable. """ - - class Meta: - permissions = (('can_manage', ugettext_noop('Can manage configuration')),) diff --git a/openslides/config/signals.py b/openslides/config/signals.py deleted file mode 100644 index 87232c5c0..000000000 --- a/openslides/config/signals.py +++ /dev/null @@ -1,4 +0,0 @@ -from django.dispatch import Signal - -config_signal = Signal(providing_args=[]) -"""Signal to get all config tabs from all apps.""" diff --git a/openslides/config/views.py b/openslides/config/views.py deleted file mode 100644 index bfd2768be..000000000 --- a/openslides/config/views.py +++ /dev/null @@ -1,89 +0,0 @@ -from collections import OrderedDict -from operator import attrgetter - -from django.http import Http404 - -from openslides.utils.rest_api import ( - Response, - SimpleMetadata, - ValidationError, - ViewSet, -) - -from .api import config -from .exceptions import ConfigError, ConfigNotFound - - -class ConfigMetadata(SimpleMetadata): - """ - Custom metadata class to add config info to responses on OPTIONS requests. - """ - def determine_metadata(self, request, view): - # Sort config variables by weight. - config_variables = sorted(config.get_config_variables().values(), key=attrgetter('weight')) - - # Build tree. - config_groups = [] - for config_variable in config_variables: - if not config_groups or config_groups[-1]['name'] != config_variable.group: - config_groups.append(OrderedDict( - name=config_variable.group, - subgroups=[])) - if not config_groups[-1]['subgroups'] or config_groups[-1]['subgroups'][-1]['name'] != config_variable.subgroup: - config_groups[-1]['subgroups'].append(OrderedDict( - name=config_variable.subgroup, - items=[])) - config_groups[-1]['subgroups'][-1]['items'].append(config_variable.data) - - # Add tree to metadata. - metadata = super().determine_metadata(request, view) - metadata['config_groups'] = config_groups - return metadata - - -class ConfigViewSet(ViewSet): - """ - API endpoint to list, retrieve and update the config. - """ - metadata_class = ConfigMetadata - - def list(self, request): - """ - Lists all config variables. Everybody can see them. - """ - return Response([{'key': key, 'value': value} for key, value in config.items()]) - - def retrieve(self, request, *args, **kwargs): - """ - Retrieves a config variable. Everybody can see it. - """ - key = kwargs['pk'] - try: - value = config[key] - except ConfigNotFound: - raise Http404 - return Response({'key': key, 'value': value}) - - def update(self, request, *args, **kwargs): - """ - Updates a config variable. Only managers can do this. - - Example: {"value": 42} - """ - # Check permission. - if not request.user.has_perm('config.can_manage'): - self.permission_denied(request) - - key = kwargs['pk'] - value = request.data['value'] - - # Validate and change value. - try: - config[key] = value - except ConfigNotFound: - raise Http404 - except ConfigError as e: - raise ValidationError({'detail': e}) - - # Return response. - return Response({'key': key, 'value': value}) diff --git a/openslides/core/apps.py b/openslides/core/apps.py index e46a65943..13861e295 100644 --- a/openslides/core/apps.py +++ b/openslides/core/apps.py @@ -12,11 +12,16 @@ class CoreAppConfig(AppConfig): # Import all required stuff. from django.db.models import signals - from openslides.config.signals import config_signal + from openslides.core.signals import config_signal from openslides.utils.autoupdate import inform_changed_data_receiver from openslides.utils.rest_api import router from .signals import setup_general_config - from .views import CustomSlideViewSet, ProjectorViewSet, TagViewSet + from .views import ( + ConfigViewSet, + CustomSlideViewSet, + ProjectorViewSet, + TagViewSet, + ) # Connect signals. config_signal.connect(setup_general_config, dispatch_uid='setup_general_config') @@ -25,6 +30,7 @@ class CoreAppConfig(AppConfig): router.register('core/projector', ProjectorViewSet) router.register('core/customslide', CustomSlideViewSet) router.register('core/tag', TagViewSet) + router.register('core/config', ConfigViewSet, 'config') # Update data when any model of any installed app is saved or deleted. # TODO: Test if the m2m_changed signal is also needed. diff --git a/openslides/config/api.py b/openslides/core/config.py similarity index 97% rename from openslides/config/api.py rename to openslides/core/config.py index 676158e6f..be055244b 100644 --- a/openslides/config/api.py +++ b/openslides/core/config.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext as _ from .exceptions import ConfigError, ConfigNotFound from .models import ConfigStore -from .signals import config_signal INPUT_TYPE_MAPPING = { 'string': str, @@ -97,6 +96,10 @@ class ConfigHandler: Returns a dictionary with all ConfigVariable instances of all signal receivers. The key is the name of the config variable. """ + # config_signal can not be imported at global space, because + # core.signals imports this file + from .signals import config_signal + result = {} for receiver, config_collection in config_signal.send(sender='get_config_variables'): for config_variable in config_collection: diff --git a/openslides/core/exceptions.py b/openslides/core/exceptions.py index 01660ab6f..f01a9f275 100644 --- a/openslides/core/exceptions.py +++ b/openslides/core/exceptions.py @@ -7,3 +7,11 @@ class ProjectorException(OpenSlidesError): class TagException(OpenSlidesError): pass + + +class ConfigError(OpenSlidesError): + pass + + +class ConfigNotFound(ConfigError): + pass diff --git a/openslides/config/middleware.py b/openslides/core/middleware.py similarity index 82% rename from openslides/config/middleware.py rename to openslides/core/middleware.py index 05275637b..d4bc03a21 100644 --- a/openslides/config/middleware.py +++ b/openslides/core/middleware.py @@ -1,4 +1,4 @@ -from openslides.config.api import config +from .config import config class ConfigCacheMiddleware(object): diff --git a/openslides/core/migrations/0001_initial.py b/openslides/core/migrations/0001_initial.py index ff25b5cae..bd925cfcb 100644 --- a/openslides/core/migrations/0001_initial.py +++ b/openslides/core/migrations/0001_initial.py @@ -2,7 +2,6 @@ import jsonfield.fields from django.db import migrations, models import openslides.utils.models -import openslides.utils.rest_api def add_default_projector(apps, schema_editor): @@ -32,20 +31,20 @@ class Migration(migrations.Migration): migrations.CreateModel( name='CustomSlide', fields=[ - ('id', models.AutoField(serialize=False, primary_key=True, auto_created=True, verbose_name='ID')), - ('title', models.CharField(verbose_name='Title', max_length=256)), - ('text', models.TextField(verbose_name='Text', blank=True)), + ('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)), + ('title', models.CharField(max_length=256, verbose_name='Title')), + ('text', models.TextField(blank=True, verbose_name='Text')), ('weight', models.IntegerField(verbose_name='Weight', default=0)), ], options={ 'ordering': ('weight', 'title'), }, - bases=(openslides.utils.rest_api.RESTModelMixin, models.Model), + bases=(openslides.utils.models.RESTModelMixin, models.Model), ), migrations.CreateModel( name='Projector', fields=[ - ('id', models.AutoField(serialize=False, primary_key=True, auto_created=True, verbose_name='ID')), + ('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)), ('config', jsonfield.fields.JSONField()), ], options={ @@ -55,21 +54,35 @@ class Migration(migrations.Migration): ('can_see_dashboard', 'Can see the dashboard'), ('can_use_chat', 'Can use the chat')), }, - bases=(openslides.utils.rest_api.RESTModelMixin, models.Model), + bases=(openslides.utils.models.RESTModelMixin, models.Model), ), migrations.CreateModel( name='Tag', fields=[ - ('id', models.AutoField(serialize=False, primary_key=True, auto_created=True, verbose_name='ID')), - ('name', models.CharField(verbose_name='Tag', unique=True, max_length=255)), + ('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)), + ('name', models.CharField(max_length=255, verbose_name='Tag', unique=True)), ], options={ - 'permissions': (('can_manage_tags', 'Can manage tags'),), 'ordering': ('name',), + 'permissions': (('can_manage_tags', 'Can manage tags'),), }, - bases=(openslides.utils.rest_api.RESTModelMixin, models.Model), + bases=(openslides.utils.models.RESTModelMixin, models.Model), + ), + migrations.CreateModel( + name='ConfigStore', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)), + ('key', models.CharField(max_length=255, db_index=True, unique=True)), + ('value', jsonfield.fields.JSONField()), + ], + options={ + 'permissions': (('can_manage_config', 'Can manage configuration'),), + }, + bases=(models.Model,), ), migrations.RunPython( - add_default_projector, + code=add_default_projector, + reverse_code=None, + atomic=True, ), ] diff --git a/openslides/core/models.py b/openslides/core/models.py index ac203d4fa..ea5d18952 100644 --- a/openslides/core/models.py +++ b/openslides/core/models.py @@ -3,8 +3,8 @@ from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy, ugettext_noop from jsonfield import JSONField +from openslides.utils.models import RESTModelMixin from openslides.utils.projector import ProjectorElement -from openslides.utils.rest_api import RESTModelMixin from .exceptions import ProjectorException @@ -120,3 +120,18 @@ class Tag(RESTModelMixin, models.Model): def __str__(self): return self.name + + +class ConfigStore(models.Model): + """ + A model class to store all config variables in the database. + """ + + key = models.CharField(max_length=255, unique=True, db_index=True) + """A string, the key of the config variable.""" + + value = JSONField() + """The value of the config variable. """ + + class Meta: + permissions = (('can_manage_config', ugettext_noop('Can manage configuration')),) diff --git a/openslides/core/signals.py b/openslides/core/signals.py index b57c74a30..c8641de41 100644 --- a/openslides/core/signals.py +++ b/openslides/core/signals.py @@ -3,7 +3,7 @@ from django.dispatch import Signal from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy -from openslides.config.api import ConfigVariable +from openslides.core.config import ConfigVariable # This signal is sent when the migrate command is done. That means it is sent # after post_migrate sending and creating all Permission objects. Don't use it @@ -16,7 +16,7 @@ def setup_general_config(sender, **kwargs): Receiver function to setup general config variables for OpenSlides. There are two main groups: 'General' and 'Projector'. The group 'General' has subgroups. This function is connected to the signal - openslides.config.signals.config_signal during app loading. + openslides.core.signals.config_signal during app loading. """ # General Event @@ -134,3 +134,7 @@ def setup_general_config(sender, **kwargs): weight=180, group=ugettext_lazy('Projector'), translatable=True) + + +config_signal = Signal(providing_args=[]) +"""Signal to get all config tabs from all apps.""" diff --git a/openslides/core/static/js/core.js b/openslides/core/static/js/core.js index 7740c95f7..547656cfb 100644 --- a/openslides/core/static/js/core.js +++ b/openslides/core/static/js/core.js @@ -128,9 +128,9 @@ angular.module('OpenSlidesApp.core', []) .factory('Config', function(DS) { return DS.defineResource({ - name: 'config/config', + name: 'core/config', idAttribute: 'key', - endpoint: '/rest/config/config/' + endpoint: '/rest/core/config/' }); }) diff --git a/openslides/core/views.py b/openslides/core/views.py index c2839b651..0c4875e2e 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -1,9 +1,11 @@ import re +from collections import OrderedDict +from operator import attrgetter from django.conf import settings from django.contrib.staticfiles import finders from django.core.urlresolvers import get_resolver -from django.http import HttpResponse +from django.http import Http404, HttpResponse from openslides import __version__ as version from openslides.utils import views as utils_views @@ -16,10 +18,14 @@ from openslides.utils.rest_api import ( ModelViewSet, ReadOnlyModelViewSet, Response, + SimpleMetadata, ValidationError, + ViewSet, detail_route, ) +from .config import config +from .exceptions import ConfigError, ConfigNotFound from .models import CustomSlide, Projector, Tag from .serializers import ( CustomSlideSerializer, @@ -227,3 +233,78 @@ class VersionView(utils_views.APIView): 'description': get_plugin_description(plugin), 'version': get_plugin_version(plugin)}) return result + + +class ConfigMetadata(SimpleMetadata): + """ + Custom metadata class to add config info to responses on OPTIONS requests. + """ + def determine_metadata(self, request, view): + # Sort config variables by weight. + config_variables = sorted(config.get_config_variables().values(), key=attrgetter('weight')) + + # Build tree. + config_groups = [] + for config_variable in config_variables: + if not config_groups or config_groups[-1]['name'] != config_variable.group: + config_groups.append(OrderedDict( + name=config_variable.group, + subgroups=[])) + if not config_groups[-1]['subgroups'] or config_groups[-1]['subgroups'][-1]['name'] != config_variable.subgroup: + config_groups[-1]['subgroups'].append(OrderedDict( + name=config_variable.subgroup, + items=[])) + config_groups[-1]['subgroups'][-1]['items'].append(config_variable.data) + + # Add tree to metadata. + metadata = super().determine_metadata(request, view) + metadata['config_groups'] = config_groups + return metadata + + +class ConfigViewSet(ViewSet): + """ + API endpoint to list, retrieve and update the config. + """ + metadata_class = ConfigMetadata + + def list(self, request): + """ + Lists all config variables. Everybody can see them. + """ + return Response([{'key': key, 'value': value} for key, value in config.items()]) + + def retrieve(self, request, *args, **kwargs): + """ + Retrieves a config variable. Everybody can see it. + """ + key = kwargs['pk'] + try: + value = config[key] + except ConfigNotFound: + raise Http404 + return Response({'key': key, 'value': value}) + + def update(self, request, *args, **kwargs): + """ + Updates a config variable. Only managers can do this. + + Example: {"value": 42} + """ + # Check permission. + if not request.user.has_perm('core.can_manage_config'): + self.permission_denied(request) + + key = kwargs['pk'] + value = request.data['value'] + + # Validate and change value. + try: + config[key] = value + except ConfigNotFound: + raise Http404 + except ConfigError as e: + raise ValidationError({'detail': e}) + + # Return response. + return Response({'key': key, 'value': value}) diff --git a/openslides/global_settings.py b/openslides/global_settings.py index 327640cd4..7a1ba0a40 100644 --- a/openslides/global_settings.py +++ b/openslides/global_settings.py @@ -68,7 +68,7 @@ MIDDLEWARE_CLASSES = ( 'django.middleware.csrf.CsrfViewMiddleware', 'openslides.users.auth.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', - 'openslides.config.middleware.ConfigCacheMiddleware', + 'openslides.core.middleware.ConfigCacheMiddleware', ) ROOT_URLCONF = 'openslides.urls' @@ -90,7 +90,6 @@ INSTALLED_APPS = ( 'openslides.motions', 'openslides.assignments', 'openslides.mediafiles', - 'openslides.config', ) TEMPLATE_CONTEXT_PROCESSORS = ( diff --git a/openslides/mediafiles/models.py b/openslides/mediafiles/models.py index 749ee138f..810b27921 100644 --- a/openslides/mediafiles/models.py +++ b/openslides/mediafiles/models.py @@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy, ugettext_noop from openslides.projector.models import SlideMixin from openslides.users.models import User -from openslides.utils.rest_api import RESTModelMixin +from openslides.utils.models import RESTModelMixin class Mediafile(RESTModelMixin, SlideMixin, models.Model): diff --git a/openslides/motions/apps.py b/openslides/motions/apps.py index 7771a8d14..07ceb3817 100644 --- a/openslides/motions/apps.py +++ b/openslides/motions/apps.py @@ -12,7 +12,7 @@ class MotionAppConfig(AppConfig): from . import projector # noqa # Import all required stuff. - from openslides.config.signals import config_signal + from openslides.core.signals import config_signal from openslides.utils.rest_api import router from .signals import create_builtin_workflows, setup_motion_config from .views import CategoryViewSet, MotionViewSet, WorkflowViewSet diff --git a/openslides/motions/models.py b/openslides/motions/models.py index 709209eb8..2919d2adc 100644 --- a/openslides/motions/models.py +++ b/openslides/motions/models.py @@ -6,7 +6,7 @@ from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy, ugettext_noop from jsonfield import JSONField -from openslides.config.api import config +from openslides.core.config import config from openslides.core.models import Tag from openslides.mediafiles.models import Mediafile from openslides.poll.models import ( @@ -17,7 +17,7 @@ from openslides.poll.models import ( ) from openslides.projector.models import SlideMixin from openslides.users.models import User -from openslides.utils.rest_api import RESTModelMixin +from openslides.utils.models import RESTModelMixin from .exceptions import WorkflowError diff --git a/openslides/motions/pdf.py b/openslides/motions/pdf.py index 4509fba6d..8c23db29d 100644 --- a/openslides/motions/pdf.py +++ b/openslides/motions/pdf.py @@ -9,7 +9,7 @@ from reportlab.lib import colors from reportlab.lib.units import cm from reportlab.platypus import PageBreak, Paragraph, Spacer, Table, TableStyle -from openslides.config.api import config +from openslides.core.config import config from openslides.users.models import Group, User # TODO: remove this line from openslides.utils.pdf import stylesheet diff --git a/openslides/motions/serializers.py b/openslides/motions/serializers.py index e5ff3ccd5..8ecbd9d0e 100644 --- a/openslides/motions/serializers.py +++ b/openslides/motions/serializers.py @@ -1,7 +1,7 @@ from django.db import transaction from django.utils.translation import ugettext as _ -from openslides.config.api import config +from openslides.core.config import config from openslides.utils.rest_api import ( CharField, IntegerField, diff --git a/openslides/motions/signals.py b/openslides/motions/signals.py index 3d3edb21b..5a61a2c4b 100644 --- a/openslides/motions/signals.py +++ b/openslides/motions/signals.py @@ -2,7 +2,7 @@ from django.core.validators import MinValueValidator from django.utils.translation import ugettext as _ from django.utils.translation import pgettext, ugettext_lazy, ugettext_noop -from openslides.config.api import ConfigVariable +from openslides.core.config import ConfigVariable from openslides.poll.models import PERCENT_BASE_CHOICES from .models import State, Workflow @@ -13,7 +13,7 @@ def setup_motion_config(sender, **kwargs): Receiver function to setup all motion config variables. They are grouped in 'General', 'Amendments', 'Supporters', 'Voting and ballot papers' and 'PDF'. This function connected to the signal - openslides.config.signals.config_signal during app loading. + openslides.core.signals.config_signal during app loading. """ # General diff --git a/openslides/motions/views.py b/openslides/motions/views.py index 8c8e06128..218ab74b6 100644 --- a/openslides/motions/views.py +++ b/openslides/motions/views.py @@ -6,7 +6,7 @@ from django.utils.translation import ugettext_noop from reportlab.platypus import SimpleDocTemplate from rest_framework import status -from openslides.config.api import config +from openslides.core.config import config from openslides.utils.rest_api import ( ModelViewSet, Response, diff --git a/openslides/users/apps.py b/openslides/users/apps.py index 8d81339b6..84113829f 100644 --- a/openslides/users/apps.py +++ b/openslides/users/apps.py @@ -11,8 +11,7 @@ class UsersAppConfig(AppConfig): from . import projector # noqa # Import all required stuff. - from openslides.config.signals import config_signal - from openslides.core.signals import post_permission_creation + from openslides.core.signals import config_signal, post_permission_creation from openslides.utils.rest_api import router from .signals import create_builtin_groups_and_admin, setup_users_config from .views import GroupViewSet, UserViewSet diff --git a/openslides/users/auth.py b/openslides/users/auth.py index a24582a88..fbc1e73c1 100644 --- a/openslides/users/auth.py +++ b/openslides/users/auth.py @@ -8,7 +8,7 @@ from django.db.models import Q from django.utils.functional import SimpleLazyObject from rest_framework.authentication import BaseAuthentication -from openslides.config.api import config +from openslides.core.config import config class AnonymousUser(DjangoAnonymousUser): diff --git a/openslides/users/models.py b/openslides/users/models.py index 3d2f041a8..d33cbd272 100644 --- a/openslides/users/models.py +++ b/openslides/users/models.py @@ -13,9 +13,9 @@ from django.contrib.auth.models import ( # noqa from django.db import models from django.utils.translation import ugettext_lazy, ugettext_noop -from openslides.config.api import config +from openslides.core.config import config from openslides.projector.models import SlideMixin -from openslides.utils.rest_api import RESTModelMixin +from openslides.utils.models import RESTModelMixin from .exceptions import UserError diff --git a/openslides/users/pdf.py b/openslides/users/pdf.py index eae78e16f..daad36d72 100644 --- a/openslides/users/pdf.py +++ b/openslides/users/pdf.py @@ -14,7 +14,7 @@ from reportlab.platypus import ( TableStyle, ) -from openslides.config.api import config +from openslides.core.config import config from openslides.utils.pdf import stylesheet from .models import User diff --git a/openslides/users/signals.py b/openslides/users/signals.py index 973c97a27..5a5222099 100644 --- a/openslides/users/signals.py +++ b/openslides/users/signals.py @@ -2,7 +2,7 @@ from django.db.models import Q from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy, ugettext_noop -from openslides.config.api import ConfigVariable +from openslides.core.config import ConfigVariable from .models import Group, Permission, User @@ -11,7 +11,7 @@ def setup_users_config(sender, **kwargs): """ Receiver function to setup all users config variables. They are grouped in 'Sorting' and 'PDF'. This function is connected to the signal - openslides.config.signals.config_signal during app loading. + openslides.core.signals.config_signal during app loading. """ # Sorting @@ -110,7 +110,7 @@ def create_builtin_groups_and_admin(**kwargs): 'assignments.can_nominate_other', 'assignments.can_nominate_self', 'assignments.can_see', - 'config.can_manage', + 'core.can_manage_config', 'core.can_manage_projector', 'core.can_manage_tags', 'core.can_see_dashboard', @@ -171,7 +171,7 @@ def create_builtin_groups_and_admin(**kwargs): permission_dict['assignments.can_manage'], permission_dict['assignments.can_nominate_other'], permission_dict['assignments.can_nominate_self'], - permission_dict['config.can_manage'], + permission_dict['core.can_manage_config'], permission_dict['core.can_manage_projector'], permission_dict['core.can_manage_tags'], permission_dict['core.can_use_chat'], diff --git a/openslides/utils/main.py b/openslides/utils/main.py index 6eb418c00..b0a3b9893 100644 --- a/openslides/utils/main.py +++ b/openslides/utils/main.py @@ -299,7 +299,7 @@ def translate_customizable_strings(language_code): Translates all translatable config values and saves them into database. """ if check_for_language(language_code): - from openslides.config.api import config + from openslides.core.config import config current_language = get_language() activate(language_code) for name in config.get_all_translatable(): diff --git a/openslides/utils/models.py b/openslides/utils/models.py index 9744332b1..3626d34fc 100644 --- a/openslides/utils/models.py +++ b/openslides/utils/models.py @@ -1,3 +1,4 @@ +from django.core.urlresolvers import reverse from django.db import models @@ -14,3 +15,27 @@ class MinMaxIntegerField(models.IntegerField): defaults = {'min_value': self.min_value, 'max_value': self.max_value} defaults.update(kwargs) return super(MinMaxIntegerField, self).formfield(**defaults) + + +class RESTModelMixin: + """ + Mixin for django models which are used in our rest api. + """ + + def get_root_rest_element(self): + """ + Returns the root rest instance. + + Uses self as default. + """ + return self + + def get_root_rest_url(self): + """ + Returns the detail url of the root model of this object. + """ + # Gets the default url-name in the same way as django rest framework + # does in relations.HyperlinkedModelSerializer + root_instance = self.get_root_rest_element() + rest_url = '%s-detail' % type(root_instance)._meta.object_name.lower() + return reverse(rest_url, args=[str(root_instance.pk)]) diff --git a/openslides/utils/pdf.py b/openslides/utils/pdf.py index 1d0aeb5ab..8bb030e1a 100644 --- a/openslides/utils/pdf.py +++ b/openslides/utils/pdf.py @@ -11,7 +11,7 @@ from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from reportlab.rl_config import defaultPageSize -from openslides.config.api import config +from openslides.core.config import config # register new truetype fonts pdfmetrics.registerFont(TTFont( diff --git a/openslides/utils/rest_api.py b/openslides/utils/rest_api.py index 5de4a2ce3..5bc7f643c 100644 --- a/openslides/utils/rest_api.py +++ b/openslides/utils/rest_api.py @@ -1,7 +1,6 @@ import re from urllib.parse import urlparse -from django.core.urlresolvers import reverse from rest_framework.decorators import detail_route # noqa from rest_framework.decorators import list_route # noqa from rest_framework.metadata import SimpleMetadata # noqa @@ -33,30 +32,6 @@ from .exceptions import OpenSlidesError router = DefaultRouter() -class RESTModelMixin: - """ - Mixin for django models which are used in our rest api. - """ - - def get_root_rest_element(self): - """ - Returns the root rest instance. - - Uses self as default. - """ - return self - - def get_root_rest_url(self): - """ - Returns the detail url of the root model of this object. - """ - # Gets the default url-name in the same way as django rest framework - # does in relations.HyperlinkedModelSerializer - root_instance = self.get_root_rest_element() - rest_url = '%s-detail' % type(root_instance)._meta.object_name.lower() - return reverse(rest_url, args=[str(root_instance.pk)]) - - class ModelViewSet(_ModelViewSet): """ Viewset for models. Before the method check_permission is called we diff --git a/openslides/utils/test.py b/openslides/utils/test.py index bab27fe48..712ecbbce 100644 --- a/openslides/utils/test.py +++ b/openslides/utils/test.py @@ -2,7 +2,7 @@ from django.core.management import call_command from django.test import TestCase as _TestCase from django.test.runner import DiscoverRunner -from openslides.config.api import config +from openslides.core.config import config class OpenSlidesDiscoverRunner(DiscoverRunner): diff --git a/tests/integration/config/__init__.py b/tests/integration/config/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/integration/config/test_views.py b/tests/integration/config/test_views.py deleted file mode 100644 index cb66fc40a..000000000 --- a/tests/integration/config/test_views.py +++ /dev/null @@ -1,119 +0,0 @@ -from django.core.urlresolvers import reverse -from django.dispatch import receiver -from rest_framework import status -from rest_framework.test import APIClient - -from openslides.config.api import ConfigVariable, config -from openslides.config.signals import config_signal -from openslides.utils.rest_api import ValidationError -from openslides.utils.test import TestCase - - -class ConfigViewSet(TestCase): - """ - Tests requests to deal with config variables. - """ - 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): - self.client = APIClient() - self.client.login(username='admin', password='admin') - response = self.client.put( - reverse('config-detail', args=['test_var_Xeiizi7ooH8Thuk5aida']), - {'value': 'test_value_Phohx3oopeichaiTheiw'}) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(config['test_var_Xeiizi7ooH8Thuk5aida'], 'test_value_Phohx3oopeichaiTheiw') - - def test_update_wrong_datatype(self): - self.client = APIClient() - self.client.login(username='admin', password='admin') - response = self.client.put( - reverse('config-detail', args=['test_var_ohhii4iavoh5Phoh5ahg']), - {'value': 'test_value_string'}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data, {'detail': "Wrong datatype. Expected , got ."}) - - def test_update_good_choice(self): - self.client = APIClient() - self.client.login(username='admin', password='admin') - response = self.client.put( - reverse('config-detail', args=['test_var_wei0Rei9ahzooSohK1ph']), - {'value': 'key_2_yahb2ain1aeZ1lea1Pei'}) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(config['test_var_wei0Rei9ahzooSohK1ph'], 'key_2_yahb2ain1aeZ1lea1Pei') - - def test_update_bad_choice(self): - self.client = APIClient() - self.client.login(username='admin', password='admin') - response = self.client.put( - reverse('config-detail', args=['test_var_wei0Rei9ahzooSohK1ph']), - {'value': 'test_value_bad_string'}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data, {'detail': 'Invalid input. Choice does not match.'}) - - def test_update_validator_ok(self): - self.client = APIClient() - self.client.login(username='admin', password='admin') - response = self.client.put( - reverse('config-detail', args=['test_var_Hi7Oje8Oith7goopeeng']), - {'value': 'valid_string'}) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(config['test_var_Hi7Oje8Oith7goopeeng'], 'valid_string') - - def test_update_validator_invalid(self): - self.client = APIClient() - self.client.login(username='admin', password='admin') - response = self.client.put( - reverse('config-detail', args=['test_var_Hi7Oje8Oith7goopeeng']), - {'value': 'invalid_string'}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data, {'detail': 'Invalid input.'}) - - -def validator_for_testing(value): - """ - Validator for testing. - """ - if value == 'invalid_string': - raise ValidationError({'detail': 'Invalid input.'}) - - -@receiver(config_signal, dispatch_uid='set_simple_config_view_integration_config_test') -def set_simple_config_view_integration_config_test(sender, **kwargs): - """ - Sets a simple config view with some config variables but without - grouping. - """ - yield ConfigVariable( - name='test_var_aeW3Quahkah1phahCheo', - default_value=None, - label='test_label_aeNahsheu8phahk8taYo') - - yield ConfigVariable( - name='test_var_Xeiizi7ooH8Thuk5aida', - default_value='') - - yield ConfigVariable( - name='test_var_ohhii4iavoh5Phoh5ahg', - default_value=0, - input_type='integer') - - yield ConfigVariable( - name='test_var_wei0Rei9ahzooSohK1ph', - default_value='key_1_Queit2juchoocos2Vugh', - input_type='choice', - choices=( - {'value': 'key_1_Queit2juchoocos2Vugh', 'display_name': 'label_1_Queit2juchoocos2Vugh'}, - {'value': 'key_2_yahb2ain1aeZ1lea1Pei', 'display_name': 'label_2_yahb2ain1aeZ1lea1Pei'})) - - yield ConfigVariable( - name='test_var_Hi7Oje8Oith7goopeeng', - default_value='', - validators=(validator_for_testing,)) diff --git a/tests/integration/core/test_views.py b/tests/integration/core/test_views.py index 061963d9e..53efa77f0 100644 --- a/tests/integration/core/test_views.py +++ b/tests/integration/core/test_views.py @@ -1,10 +1,15 @@ import json from django.core.urlresolvers import reverse +from django.dispatch import receiver from rest_framework import status +from rest_framework.test import APIClient from openslides import __version__ as version +from openslides.core.config import ConfigVariable, config from openslides.core.models import CustomSlide, Projector +from openslides.core.signals import config_signal +from openslides.utils.rest_api import ValidationError from openslides.utils.test import TestCase @@ -59,3 +64,113 @@ class VersionView(TestCase): {'verbose_name': 'Plugin tests.old.utils', 'description': 'Description of plugin tests.old.utils', 'version': 'unknown'}]}) + + +class ConfigViewSet(TestCase): + """ + Tests requests to deal with config variables. + """ + 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): + self.client = APIClient() + self.client.login(username='admin', password='admin') + response = self.client.put( + reverse('config-detail', args=['test_var_Xeiizi7ooH8Thuk5aida']), + {'value': 'test_value_Phohx3oopeichaiTheiw'}) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(config['test_var_Xeiizi7ooH8Thuk5aida'], 'test_value_Phohx3oopeichaiTheiw') + + def test_update_wrong_datatype(self): + self.client = APIClient() + self.client.login(username='admin', password='admin') + response = self.client.put( + reverse('config-detail', args=['test_var_ohhii4iavoh5Phoh5ahg']), + {'value': 'test_value_string'}) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data, {'detail': "Wrong datatype. Expected , got ."}) + + def test_update_good_choice(self): + self.client = APIClient() + self.client.login(username='admin', password='admin') + response = self.client.put( + reverse('config-detail', args=['test_var_wei0Rei9ahzooSohK1ph']), + {'value': 'key_2_yahb2ain1aeZ1lea1Pei'}) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(config['test_var_wei0Rei9ahzooSohK1ph'], 'key_2_yahb2ain1aeZ1lea1Pei') + + def test_update_bad_choice(self): + self.client = APIClient() + self.client.login(username='admin', password='admin') + response = self.client.put( + reverse('config-detail', args=['test_var_wei0Rei9ahzooSohK1ph']), + {'value': 'test_value_bad_string'}) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data, {'detail': 'Invalid input. Choice does not match.'}) + + def test_update_validator_ok(self): + self.client = APIClient() + self.client.login(username='admin', password='admin') + response = self.client.put( + reverse('config-detail', args=['test_var_Hi7Oje8Oith7goopeeng']), + {'value': 'valid_string'}) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(config['test_var_Hi7Oje8Oith7goopeeng'], 'valid_string') + + def test_update_validator_invalid(self): + self.client = APIClient() + self.client.login(username='admin', password='admin') + response = self.client.put( + reverse('config-detail', args=['test_var_Hi7Oje8Oith7goopeeng']), + {'value': 'invalid_string'}) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data, {'detail': 'Invalid input.'}) + + +def validator_for_testing(value): + """ + Validator for testing. + """ + if value == 'invalid_string': + raise ValidationError({'detail': 'Invalid input.'}) + + +@receiver(config_signal, dispatch_uid='set_simple_config_view_integration_config_test') +def set_simple_config_view_integration_config_test(sender, **kwargs): + """ + Sets a simple config view with some config variables but without + grouping. + """ + yield ConfigVariable( + name='test_var_aeW3Quahkah1phahCheo', + default_value=None, + label='test_label_aeNahsheu8phahk8taYo') + + yield ConfigVariable( + name='test_var_Xeiizi7ooH8Thuk5aida', + default_value='') + + yield ConfigVariable( + name='test_var_ohhii4iavoh5Phoh5ahg', + default_value=0, + input_type='integer') + + yield ConfigVariable( + name='test_var_wei0Rei9ahzooSohK1ph', + default_value='key_1_Queit2juchoocos2Vugh', + input_type='choice', + choices=( + {'value': 'key_1_Queit2juchoocos2Vugh', 'display_name': 'label_1_Queit2juchoocos2Vugh'}, + {'value': 'key_2_yahb2ain1aeZ1lea1Pei', 'display_name': 'label_2_yahb2ain1aeZ1lea1Pei'})) + + yield ConfigVariable( + name='test_var_Hi7Oje8Oith7goopeeng', + default_value='', + validators=(validator_for_testing,)) diff --git a/tests/integration/motions/test_views.py b/tests/integration/motions/test_views.py index e1be0cc0a..d3a434279 100644 --- a/tests/integration/motions/test_views.py +++ b/tests/integration/motions/test_views.py @@ -1,6 +1,6 @@ from django.test.client import Client -from openslides.config.api import config +from openslides.core.config import config from openslides.motions.models import Motion from openslides.utils.test import TestCase diff --git a/tests/integration/motions/test_viewset.py b/tests/integration/motions/test_viewset.py index d3d791539..1add5159b 100644 --- a/tests/integration/motions/test_viewset.py +++ b/tests/integration/motions/test_viewset.py @@ -3,7 +3,7 @@ from django.core.urlresolvers import reverse from rest_framework import status from rest_framework.test import APIClient -from openslides.config.api import config +from openslides.core.config import config from openslides.core.models import Tag from openslides.motions.models import Category, Motion from openslides.utils.test import TestCase diff --git a/tests/old/config/test_config.py b/tests/old/config/test_config.py index b0997abd5..1ca93863b 100644 --- a/tests/old/config/test_config.py +++ b/tests/old/config/test_config.py @@ -1,8 +1,8 @@ from django.dispatch import receiver -from openslides.config.api import ConfigVariable, config -from openslides.config.exceptions import ConfigError, ConfigNotFound -from openslides.config.signals import config_signal +from openslides.core.config import ConfigVariable, config +from openslides.core.exceptions import ConfigError, ConfigNotFound +from openslides.core.signals import config_signal from openslides.utils.test import TestCase diff --git a/tests/old/motions/test_models.py b/tests/old/motions/test_models.py index 560612fd0..2eaee8c31 100644 --- a/tests/old/motions/test_models.py +++ b/tests/old/motions/test_models.py @@ -1,6 +1,6 @@ from unittest import skip -from openslides.config.api import config +from openslides.core.config import config from openslides.motions.exceptions import WorkflowError from openslides.motions.models import Motion, State, Workflow from openslides.users.models import User diff --git a/tests/old/utils/test_main.py b/tests/old/utils/test_main.py index 786f13abf..cc3b78a67 100644 --- a/tests/old/utils/test_main.py +++ b/tests/old/utils/test_main.py @@ -2,7 +2,7 @@ import os import sys from unittest.mock import MagicMock, patch -from openslides.config.api import config +from openslides.core.config import config from openslides.utils import main from openslides.utils.test import TestCase diff --git a/tests/unit/config/test_api.py b/tests/unit/config/test_api.py index 4685ec005..53b8c063e 100644 --- a/tests/unit/config/test_api.py +++ b/tests/unit/config/test_api.py @@ -1,11 +1,11 @@ from unittest import TestCase from unittest.mock import patch -from openslides.config.api import ConfigVariable +from openslides.core.config import ConfigVariable class TestConfigVariable(TestCase): - @patch('openslides.config.api.config', {'test_variable': None}) + @patch('openslides.core.config.config', {'test_variable': None}) def test_default_value_in_data(self): """ Tests, that the default_value attribute is in the 'data' property of