Moved config app into the core app

This commit is contained in:
Oskar Hahn 2015-06-29 12:08:15 +02:00
parent bcfe72fa8c
commit e042c668d5
50 changed files with 335 additions and 346 deletions

View File

@ -37,7 +37,7 @@ Other:
- Refactored projector API using metaclasses now. - Refactored projector API using metaclasses now.
- Renamed SignalConnectMetaClass classmethod get_all_objects to get_all - Renamed SignalConnectMetaClass classmethod get_all_objects to get_all
(private API). (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. - Removed old style personal info page, main menu entries and widget API.
- Used AngularJS with additional libraries for single page frontend. - Used AngularJS with additional libraries for single page frontend.
- Removed use of 'django.views.i18n.javascript_catalog'. Used angular-gettext - Removed use of 'django.views.i18n.javascript_catalog'. Used angular-gettext

View File

@ -12,7 +12,7 @@ class AgendaAppConfig(AppConfig):
# Import all required stuff. # Import all required stuff.
from django.db.models.signals import pre_delete 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 openslides.utils.rest_api import router
from .signals import setup_agenda_config, listen_to_related_object_delete_signal from .signals import setup_agenda_config, listen_to_related_object_delete_signal
from .views import ItemViewSet from .views import ItemViewSet

View File

@ -9,12 +9,12 @@ from django.db import models
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy, ugettext_noop from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.config.api import config from openslides.core.config import config
from openslides.core.models import Tag from openslides.core.models import Tag
from openslides.projector.models import SlideMixin from openslides.projector.models import SlideMixin
from openslides.users.models import User from openslides.users.models import User
from openslides.utils.exceptions import OpenSlidesError 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 from openslides.utils.utils import to_roman

View File

@ -6,7 +6,7 @@ from django.core.validators import MaxLengthValidator, MinValueValidator
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy from django.utils.translation import ugettext_lazy
from openslides.config.api import ConfigVariable from openslides.core.config import ConfigVariable
from .models import Item 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 Receiver function to setup all agenda config variables. They are not
grouped. This function connected to the signal 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. # TODO: Use an input type with generic datetime support.
yield ConfigVariable( yield ConfigVariable(

View File

@ -11,7 +11,7 @@ class AssignmentAppConfig(AppConfig):
from . import projector # noqa from . import projector # noqa
# Import all required stuff. # 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 openslides.utils.rest_api import router
from .signals import setup_assignment_config from .signals import setup_assignment_config
from .views import AssignmentViewSet, AssignmentPollViewSet from .views import AssignmentViewSet, AssignmentPollViewSet

View File

@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy, ugettext_noop from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.agenda.models import Item, Speaker 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.core.models import Tag
from openslides.poll.models import ( from openslides.poll.models import (
BaseOption, BaseOption,
@ -17,7 +17,7 @@ from openslides.poll.models import (
from openslides.projector.models import SlideMixin from openslides.projector.models import SlideMixin
from openslides.users.models import User from openslides.users.models import User
from openslides.utils.exceptions import OpenSlidesError from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.rest_api import RESTModelMixin from openslides.utils.models import RESTModelMixin
class AssignmentRelatedUser(RESTModelMixin, models.Model): class AssignmentRelatedUser(RESTModelMixin, models.Model):

View File

@ -2,7 +2,7 @@ from django.core.validators import MinValueValidator
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy 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 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 Receiver function to setup all assignment config variables. They are
grouped in 'Ballot and ballot papers' and 'PDF'. This function is 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. app loading.
""" """
# Ballot and ballot papers # Ballot and ballot papers

View File

@ -15,7 +15,7 @@ from reportlab.platypus import (
TableStyle, 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.users.models import Group, User # TODO: remove this
from openslides.utils.pdf import stylesheet from openslides.utils.pdf import stylesheet
from openslides.utils.rest_api import ( from openslides.utils.rest_api import (

View File

@ -1 +0,0 @@
default_app_config = 'openslides.config.apps.ConfigAppConfig'

View File

@ -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')

View File

@ -1,9 +0,0 @@
from openslides.utils.exceptions import OpenSlidesError
class ConfigError(OpenSlidesError):
pass
class ConfigNotFound(ConfigError):
pass

View File

@ -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')),)

View File

@ -1,4 +0,0 @@
from django.dispatch import Signal
config_signal = Signal(providing_args=[])
"""Signal to get all config tabs from all apps."""

View File

@ -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})

View File

@ -12,11 +12,16 @@ class CoreAppConfig(AppConfig):
# Import all required stuff. # Import all required stuff.
from django.db.models import signals 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.autoupdate import inform_changed_data_receiver
from openslides.utils.rest_api import router from openslides.utils.rest_api import router
from .signals import setup_general_config from .signals import setup_general_config
from .views import CustomSlideViewSet, ProjectorViewSet, TagViewSet from .views import (
ConfigViewSet,
CustomSlideViewSet,
ProjectorViewSet,
TagViewSet,
)
# Connect signals. # Connect signals.
config_signal.connect(setup_general_config, dispatch_uid='setup_general_config') 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/projector', ProjectorViewSet)
router.register('core/customslide', CustomSlideViewSet) router.register('core/customslide', CustomSlideViewSet)
router.register('core/tag', TagViewSet) router.register('core/tag', TagViewSet)
router.register('core/config', ConfigViewSet, 'config')
# Update data when any model of any installed app is saved or deleted. # Update data when any model of any installed app is saved or deleted.
# TODO: Test if the m2m_changed signal is also needed. # TODO: Test if the m2m_changed signal is also needed.

View File

@ -3,7 +3,6 @@ from django.utils.translation import ugettext as _
from .exceptions import ConfigError, ConfigNotFound from .exceptions import ConfigError, ConfigNotFound
from .models import ConfigStore from .models import ConfigStore
from .signals import config_signal
INPUT_TYPE_MAPPING = { INPUT_TYPE_MAPPING = {
'string': str, 'string': str,
@ -97,6 +96,10 @@ class ConfigHandler:
Returns a dictionary with all ConfigVariable instances of all Returns a dictionary with all ConfigVariable instances of all
signal receivers. The key is the name of the config variable. 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 = {} result = {}
for receiver, config_collection in config_signal.send(sender='get_config_variables'): for receiver, config_collection in config_signal.send(sender='get_config_variables'):
for config_variable in config_collection: for config_variable in config_collection:

View File

@ -7,3 +7,11 @@ class ProjectorException(OpenSlidesError):
class TagException(OpenSlidesError): class TagException(OpenSlidesError):
pass pass
class ConfigError(OpenSlidesError):
pass
class ConfigNotFound(ConfigError):
pass

View File

@ -1,4 +1,4 @@
from openslides.config.api import config from .config import config
class ConfigCacheMiddleware(object): class ConfigCacheMiddleware(object):

View File

@ -2,7 +2,6 @@ import jsonfield.fields
from django.db import migrations, models from django.db import migrations, models
import openslides.utils.models import openslides.utils.models
import openslides.utils.rest_api
def add_default_projector(apps, schema_editor): def add_default_projector(apps, schema_editor):
@ -32,20 +31,20 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='CustomSlide', name='CustomSlide',
fields=[ 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)),
('title', models.CharField(verbose_name='Title', max_length=256)), ('title', models.CharField(max_length=256, verbose_name='Title')),
('text', models.TextField(verbose_name='Text', blank=True)), ('text', models.TextField(blank=True, verbose_name='Text')),
('weight', models.IntegerField(verbose_name='Weight', default=0)), ('weight', models.IntegerField(verbose_name='Weight', default=0)),
], ],
options={ options={
'ordering': ('weight', 'title'), 'ordering': ('weight', 'title'),
}, },
bases=(openslides.utils.rest_api.RESTModelMixin, models.Model), bases=(openslides.utils.models.RESTModelMixin, models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Projector', name='Projector',
fields=[ 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()), ('config', jsonfield.fields.JSONField()),
], ],
options={ options={
@ -55,21 +54,35 @@ class Migration(migrations.Migration):
('can_see_dashboard', 'Can see the dashboard'), ('can_see_dashboard', 'Can see the dashboard'),
('can_use_chat', 'Can use the chat')), ('can_use_chat', 'Can use the chat')),
}, },
bases=(openslides.utils.rest_api.RESTModelMixin, models.Model), bases=(openslides.utils.models.RESTModelMixin, models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Tag', name='Tag',
fields=[ 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)),
('name', models.CharField(verbose_name='Tag', unique=True, max_length=255)), ('name', models.CharField(max_length=255, verbose_name='Tag', unique=True)),
], ],
options={ options={
'permissions': (('can_manage_tags', 'Can manage tags'),),
'ordering': ('name',), '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( migrations.RunPython(
add_default_projector, code=add_default_projector,
reverse_code=None,
atomic=True,
), ),
] ]

View File

@ -3,8 +3,8 @@ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy, ugettext_noop from django.utils.translation import ugettext_lazy, ugettext_noop
from jsonfield import JSONField from jsonfield import JSONField
from openslides.utils.models import RESTModelMixin
from openslides.utils.projector import ProjectorElement from openslides.utils.projector import ProjectorElement
from openslides.utils.rest_api import RESTModelMixin
from .exceptions import ProjectorException from .exceptions import ProjectorException
@ -120,3 +120,18 @@ class Tag(RESTModelMixin, models.Model):
def __str__(self): def __str__(self):
return self.name 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')),)

View File

@ -3,7 +3,7 @@ from django.dispatch import Signal
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy 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 # This signal is sent when the migrate command is done. That means it is sent
# after post_migrate sending and creating all Permission objects. Don't use it # after post_migrate sending and creating all Permission objects. Don't use it
@ -16,7 +16,7 @@ def setup_general_config(sender, **kwargs):
Receiver function to setup general config variables for OpenSlides. Receiver function to setup general config variables for OpenSlides.
There are two main groups: 'General' and 'Projector'. The group There are two main groups: 'General' and 'Projector'. The group
'General' has subgroups. This function is connected to the signal '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 # General Event
@ -134,3 +134,7 @@ def setup_general_config(sender, **kwargs):
weight=180, weight=180,
group=ugettext_lazy('Projector'), group=ugettext_lazy('Projector'),
translatable=True) translatable=True)
config_signal = Signal(providing_args=[])
"""Signal to get all config tabs from all apps."""

View File

@ -128,9 +128,9 @@ angular.module('OpenSlidesApp.core', [])
.factory('Config', function(DS) { .factory('Config', function(DS) {
return DS.defineResource({ return DS.defineResource({
name: 'config/config', name: 'core/config',
idAttribute: 'key', idAttribute: 'key',
endpoint: '/rest/config/config/' endpoint: '/rest/core/config/'
}); });
}) })

View File

@ -1,9 +1,11 @@
import re import re
from collections import OrderedDict
from operator import attrgetter
from django.conf import settings from django.conf import settings
from django.contrib.staticfiles import finders from django.contrib.staticfiles import finders
from django.core.urlresolvers import get_resolver 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 import __version__ as version
from openslides.utils import views as utils_views from openslides.utils import views as utils_views
@ -16,10 +18,14 @@ from openslides.utils.rest_api import (
ModelViewSet, ModelViewSet,
ReadOnlyModelViewSet, ReadOnlyModelViewSet,
Response, Response,
SimpleMetadata,
ValidationError, ValidationError,
ViewSet,
detail_route, detail_route,
) )
from .config import config
from .exceptions import ConfigError, ConfigNotFound
from .models import CustomSlide, Projector, Tag from .models import CustomSlide, Projector, Tag
from .serializers import ( from .serializers import (
CustomSlideSerializer, CustomSlideSerializer,
@ -227,3 +233,78 @@ class VersionView(utils_views.APIView):
'description': get_plugin_description(plugin), 'description': get_plugin_description(plugin),
'version': get_plugin_version(plugin)}) 'version': get_plugin_version(plugin)})
return result 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})

View File

@ -68,7 +68,7 @@ MIDDLEWARE_CLASSES = (
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'openslides.users.auth.AuthenticationMiddleware', 'openslides.users.auth.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'openslides.config.middleware.ConfigCacheMiddleware', 'openslides.core.middleware.ConfigCacheMiddleware',
) )
ROOT_URLCONF = 'openslides.urls' ROOT_URLCONF = 'openslides.urls'
@ -90,7 +90,6 @@ INSTALLED_APPS = (
'openslides.motions', 'openslides.motions',
'openslides.assignments', 'openslides.assignments',
'openslides.mediafiles', 'openslides.mediafiles',
'openslides.config',
) )
TEMPLATE_CONTEXT_PROCESSORS = ( TEMPLATE_CONTEXT_PROCESSORS = (

View File

@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.projector.models import SlideMixin from openslides.projector.models import SlideMixin
from openslides.users.models import User 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): class Mediafile(RESTModelMixin, SlideMixin, models.Model):

View File

@ -12,7 +12,7 @@ class MotionAppConfig(AppConfig):
from . import projector # noqa from . import projector # noqa
# Import all required stuff. # 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 openslides.utils.rest_api import router
from .signals import create_builtin_workflows, setup_motion_config from .signals import create_builtin_workflows, setup_motion_config
from .views import CategoryViewSet, MotionViewSet, WorkflowViewSet from .views import CategoryViewSet, MotionViewSet, WorkflowViewSet

View File

@ -6,7 +6,7 @@ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy, ugettext_noop from django.utils.translation import ugettext_lazy, ugettext_noop
from jsonfield import JSONField from jsonfield import JSONField
from openslides.config.api import config from openslides.core.config import config
from openslides.core.models import Tag from openslides.core.models import Tag
from openslides.mediafiles.models import Mediafile from openslides.mediafiles.models import Mediafile
from openslides.poll.models import ( from openslides.poll.models import (
@ -17,7 +17,7 @@ from openslides.poll.models import (
) )
from openslides.projector.models import SlideMixin from openslides.projector.models import SlideMixin
from openslides.users.models import User from openslides.users.models import User
from openslides.utils.rest_api import RESTModelMixin from openslides.utils.models import RESTModelMixin
from .exceptions import WorkflowError from .exceptions import WorkflowError

View File

@ -9,7 +9,7 @@ from reportlab.lib import colors
from reportlab.lib.units import cm from reportlab.lib.units import cm
from reportlab.platypus import PageBreak, Paragraph, Spacer, Table, TableStyle 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.users.models import Group, User # TODO: remove this line
from openslides.utils.pdf import stylesheet from openslides.utils.pdf import stylesheet

View File

@ -1,7 +1,7 @@
from django.db import transaction from django.db import transaction
from django.utils.translation import ugettext as _ 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 ( from openslides.utils.rest_api import (
CharField, CharField,
IntegerField, IntegerField,

View File

@ -2,7 +2,7 @@ from django.core.validators import MinValueValidator
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import pgettext, ugettext_lazy, ugettext_noop from django.utils.translation import pgettext, ugettext_lazy, ugettext_noop
from openslides.config.api import ConfigVariable from openslides.core.config import ConfigVariable
from openslides.poll.models import PERCENT_BASE_CHOICES from openslides.poll.models import PERCENT_BASE_CHOICES
from .models import State, Workflow from .models import State, Workflow
@ -13,7 +13,7 @@ def setup_motion_config(sender, **kwargs):
Receiver function to setup all motion config variables. They are Receiver function to setup all motion config variables. They are
grouped in 'General', 'Amendments', 'Supporters', 'Voting and ballot grouped in 'General', 'Amendments', 'Supporters', 'Voting and ballot
papers' and 'PDF'. This function connected to the signal 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 # General

View File

@ -6,7 +6,7 @@ from django.utils.translation import ugettext_noop
from reportlab.platypus import SimpleDocTemplate from reportlab.platypus import SimpleDocTemplate
from rest_framework import status from rest_framework import status
from openslides.config.api import config from openslides.core.config import config
from openslides.utils.rest_api import ( from openslides.utils.rest_api import (
ModelViewSet, ModelViewSet,
Response, Response,

View File

@ -11,8 +11,7 @@ class UsersAppConfig(AppConfig):
from . import projector # noqa from . import projector # noqa
# Import all required stuff. # Import all required stuff.
from openslides.config.signals import config_signal from openslides.core.signals import config_signal, post_permission_creation
from openslides.core.signals import post_permission_creation
from openslides.utils.rest_api import router from openslides.utils.rest_api import router
from .signals import create_builtin_groups_and_admin, setup_users_config from .signals import create_builtin_groups_and_admin, setup_users_config
from .views import GroupViewSet, UserViewSet from .views import GroupViewSet, UserViewSet

View File

@ -8,7 +8,7 @@ from django.db.models import Q
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
from rest_framework.authentication import BaseAuthentication from rest_framework.authentication import BaseAuthentication
from openslides.config.api import config from openslides.core.config import config
class AnonymousUser(DjangoAnonymousUser): class AnonymousUser(DjangoAnonymousUser):

View File

@ -13,9 +13,9 @@ from django.contrib.auth.models import ( # noqa
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy, ugettext_noop 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.projector.models import SlideMixin
from openslides.utils.rest_api import RESTModelMixin from openslides.utils.models import RESTModelMixin
from .exceptions import UserError from .exceptions import UserError

View File

@ -14,7 +14,7 @@ from reportlab.platypus import (
TableStyle, TableStyle,
) )
from openslides.config.api import config from openslides.core.config import config
from openslides.utils.pdf import stylesheet from openslides.utils.pdf import stylesheet
from .models import User from .models import User

View File

@ -2,7 +2,7 @@ from django.db.models import Q
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy, ugettext_noop from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.config.api import ConfigVariable from openslides.core.config import ConfigVariable
from .models import Group, Permission, User 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 Receiver function to setup all users config variables. They are grouped
in 'Sorting' and 'PDF'. This function is connected to the signal 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 # Sorting
@ -110,7 +110,7 @@ def create_builtin_groups_and_admin(**kwargs):
'assignments.can_nominate_other', 'assignments.can_nominate_other',
'assignments.can_nominate_self', 'assignments.can_nominate_self',
'assignments.can_see', 'assignments.can_see',
'config.can_manage', 'core.can_manage_config',
'core.can_manage_projector', 'core.can_manage_projector',
'core.can_manage_tags', 'core.can_manage_tags',
'core.can_see_dashboard', 'core.can_see_dashboard',
@ -171,7 +171,7 @@ def create_builtin_groups_and_admin(**kwargs):
permission_dict['assignments.can_manage'], permission_dict['assignments.can_manage'],
permission_dict['assignments.can_nominate_other'], permission_dict['assignments.can_nominate_other'],
permission_dict['assignments.can_nominate_self'], 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_projector'],
permission_dict['core.can_manage_tags'], permission_dict['core.can_manage_tags'],
permission_dict['core.can_use_chat'], permission_dict['core.can_use_chat'],

View File

@ -299,7 +299,7 @@ def translate_customizable_strings(language_code):
Translates all translatable config values and saves them into database. Translates all translatable config values and saves them into database.
""" """
if check_for_language(language_code): if check_for_language(language_code):
from openslides.config.api import config from openslides.core.config import config
current_language = get_language() current_language = get_language()
activate(language_code) activate(language_code)
for name in config.get_all_translatable(): for name in config.get_all_translatable():

View File

@ -1,3 +1,4 @@
from django.core.urlresolvers import reverse
from django.db import models 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 = {'min_value': self.min_value, 'max_value': self.max_value}
defaults.update(kwargs) defaults.update(kwargs)
return super(MinMaxIntegerField, self).formfield(**defaults) 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)])

View File

@ -11,7 +11,7 @@ from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfbase.ttfonts import TTFont
from reportlab.rl_config import defaultPageSize from reportlab.rl_config import defaultPageSize
from openslides.config.api import config from openslides.core.config import config
# register new truetype fonts # register new truetype fonts
pdfmetrics.registerFont(TTFont( pdfmetrics.registerFont(TTFont(

View File

@ -1,7 +1,6 @@
import re import re
from urllib.parse import urlparse from urllib.parse import urlparse
from django.core.urlresolvers import reverse
from rest_framework.decorators import detail_route # noqa from rest_framework.decorators import detail_route # noqa
from rest_framework.decorators import list_route # noqa from rest_framework.decorators import list_route # noqa
from rest_framework.metadata import SimpleMetadata # noqa from rest_framework.metadata import SimpleMetadata # noqa
@ -33,30 +32,6 @@ from .exceptions import OpenSlidesError
router = DefaultRouter() 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): class ModelViewSet(_ModelViewSet):
""" """
Viewset for models. Before the method check_permission is called we Viewset for models. Before the method check_permission is called we

View File

@ -2,7 +2,7 @@ from django.core.management import call_command
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 openslides.config.api import config from openslides.core.config import config
class OpenSlidesDiscoverRunner(DiscoverRunner): class OpenSlidesDiscoverRunner(DiscoverRunner):

View File

@ -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 <class 'int'>, got <class 'str'>."})
def test_update_good_choice(self):
self.client = APIClient()
self.client.login(username='admin', password='admin')
response = self.client.put(
reverse('config-detail', args=['test_var_wei0Rei9ahzooSohK1ph']),
{'value': 'key_2_yahb2ain1aeZ1lea1Pei'})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(config['test_var_wei0Rei9ahzooSohK1ph'], 'key_2_yahb2ain1aeZ1lea1Pei')
def test_update_bad_choice(self):
self.client = APIClient()
self.client.login(username='admin', password='admin')
response = self.client.put(
reverse('config-detail', args=['test_var_wei0Rei9ahzooSohK1ph']),
{'value': 'test_value_bad_string'})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data, {'detail': 'Invalid input. Choice does not match.'})
def test_update_validator_ok(self):
self.client = APIClient()
self.client.login(username='admin', password='admin')
response = self.client.put(
reverse('config-detail', args=['test_var_Hi7Oje8Oith7goopeeng']),
{'value': 'valid_string'})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(config['test_var_Hi7Oje8Oith7goopeeng'], 'valid_string')
def test_update_validator_invalid(self):
self.client = APIClient()
self.client.login(username='admin', password='admin')
response = self.client.put(
reverse('config-detail', args=['test_var_Hi7Oje8Oith7goopeeng']),
{'value': 'invalid_string'})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data, {'detail': 'Invalid input.'})
def validator_for_testing(value):
"""
Validator for testing.
"""
if value == 'invalid_string':
raise ValidationError({'detail': 'Invalid input.'})
@receiver(config_signal, dispatch_uid='set_simple_config_view_integration_config_test')
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,))

View File

@ -1,10 +1,15 @@
import json import json
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.dispatch import receiver
from rest_framework import status from rest_framework import status
from rest_framework.test import APIClient
from openslides import __version__ as version from openslides import __version__ as version
from openslides.core.config import ConfigVariable, config
from openslides.core.models import CustomSlide, Projector 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 from openslides.utils.test import TestCase
@ -59,3 +64,113 @@ class VersionView(TestCase):
{'verbose_name': 'Plugin tests.old.utils', {'verbose_name': 'Plugin tests.old.utils',
'description': 'Description of plugin tests.old.utils', 'description': 'Description of plugin tests.old.utils',
'version': 'unknown'}]}) '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 <class 'int'>, got <class 'str'>."})
def test_update_good_choice(self):
self.client = APIClient()
self.client.login(username='admin', password='admin')
response = self.client.put(
reverse('config-detail', args=['test_var_wei0Rei9ahzooSohK1ph']),
{'value': 'key_2_yahb2ain1aeZ1lea1Pei'})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(config['test_var_wei0Rei9ahzooSohK1ph'], 'key_2_yahb2ain1aeZ1lea1Pei')
def test_update_bad_choice(self):
self.client = APIClient()
self.client.login(username='admin', password='admin')
response = self.client.put(
reverse('config-detail', args=['test_var_wei0Rei9ahzooSohK1ph']),
{'value': 'test_value_bad_string'})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data, {'detail': 'Invalid input. Choice does not match.'})
def test_update_validator_ok(self):
self.client = APIClient()
self.client.login(username='admin', password='admin')
response = self.client.put(
reverse('config-detail', args=['test_var_Hi7Oje8Oith7goopeeng']),
{'value': 'valid_string'})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(config['test_var_Hi7Oje8Oith7goopeeng'], 'valid_string')
def test_update_validator_invalid(self):
self.client = APIClient()
self.client.login(username='admin', password='admin')
response = self.client.put(
reverse('config-detail', args=['test_var_Hi7Oje8Oith7goopeeng']),
{'value': 'invalid_string'})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data, {'detail': 'Invalid input.'})
def validator_for_testing(value):
"""
Validator for testing.
"""
if value == 'invalid_string':
raise ValidationError({'detail': 'Invalid input.'})
@receiver(config_signal, dispatch_uid='set_simple_config_view_integration_config_test')
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,))

View File

@ -1,6 +1,6 @@
from django.test.client import Client 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.motions.models import Motion
from openslides.utils.test import TestCase from openslides.utils.test import TestCase

View File

@ -3,7 +3,7 @@ from django.core.urlresolvers import reverse
from rest_framework import status from rest_framework import status
from rest_framework.test import APIClient from rest_framework.test import APIClient
from openslides.config.api import config from openslides.core.config import config
from openslides.core.models import Tag from openslides.core.models import Tag
from openslides.motions.models import Category, Motion from openslides.motions.models import Category, Motion
from openslides.utils.test import TestCase from openslides.utils.test import TestCase

View File

@ -1,8 +1,8 @@
from django.dispatch import receiver from django.dispatch import receiver
from openslides.config.api import ConfigVariable, config from openslides.core.config import ConfigVariable, config
from openslides.config.exceptions import ConfigError, ConfigNotFound from openslides.core.exceptions import ConfigError, ConfigNotFound
from openslides.config.signals import config_signal from openslides.core.signals import config_signal
from openslides.utils.test import TestCase from openslides.utils.test import TestCase

View File

@ -1,6 +1,6 @@
from unittest import skip 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.exceptions import WorkflowError
from openslides.motions.models import Motion, State, Workflow from openslides.motions.models import Motion, State, Workflow
from openslides.users.models import User from openslides.users.models import User

View File

@ -2,7 +2,7 @@ import os
import sys import sys
from unittest.mock import MagicMock, patch 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 import main
from openslides.utils.test import TestCase from openslides.utils.test import TestCase

View File

@ -1,11 +1,11 @@
from unittest import TestCase from unittest import TestCase
from unittest.mock import patch from unittest.mock import patch
from openslides.config.api import ConfigVariable from openslides.core.config import ConfigVariable
class TestConfigVariable(TestCase): 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): def test_default_value_in_data(self):
""" """
Tests, that the default_value attribute is in the 'data' property of Tests, that the default_value attribute is in the 'data' property of