diff --git a/openslides/config/forms.py b/openslides/config/forms.py
index bc46e6412..54e8c7187 100644
--- a/openslides/config/forms.py
+++ b/openslides/config/forms.py
@@ -10,19 +10,59 @@
:license: GNU GPL, see LICENSE for more details.
"""
-from django.forms import Form, CharField, TextInput, BooleanField, IntegerField, ChoiceField, Textarea, Select
+from django import forms
from utils.forms import CssClassMixin
from models import config
from utils.translation_ext import ugettext as _
-class GeneralConfigForm(Form, CssClassMixin):
- event_name = CharField(widget=TextInput(),label=_("Event name"), max_length=30)
- event_description = CharField(widget=TextInput(),label=_("Short description of event"), max_length=100, required=False)
- event_date = CharField(widget=TextInput(), required=False, label=_("Event date"))
- event_location = CharField(widget=TextInput(), required=False, label=_("Event location"))
- event_organizer = CharField(widget=TextInput(), required=False, label=_("Event organizer"))
- system_enable_anonymous = BooleanField(required=False, label=_("Allow access for anonymous guest users") )
- frontpage_title = CharField(widget=TextInput(), required=False, label=_("Title") )
- frontpage_welcometext = CharField(widget=Textarea(), required=False, label=_("Welcome text") )
+class GeneralConfigForm(forms.Form, CssClassMixin):
+ event_name = forms.CharField(
+ widget=forms.TextInput(),
+ label=_("Event name"),
+ max_length=30,
+ )
+
+ event_description = forms.CharField(
+ widget=forms.TextInput(),
+ label=_("Short description of event"),
+ required=False,
+ max_length=100,
+
+ )
+
+ event_date = forms.CharField(
+ widget=forms.TextInput(),
+ label=_("Event date"),
+ required=False,
+ )
+
+ event_location = forms.CharField(
+ widget=forms.TextInput(),
+ label=_("Event location"),
+ required=False,
+ )
+
+ event_organizer = forms.CharField(
+ widget=forms.TextInput(),
+ label=_("Event organizer"),
+ required=False,
+ )
+
+ system_enable_anonymous = forms.BooleanField(
+ label=_("Allow access for anonymous guest users"),
+ required=False,
+ )
+
+ frontpage_title = forms.CharField(
+ widget=forms.TextInput(),
+ label=_("Title"),
+ required=False,
+ )
+
+ frontpage_welcometext = forms.CharField(
+ widget=forms.Textarea(),
+ label=_("Welcome text"),
+ required=False,
+ )
diff --git a/openslides/config/models.py b/openslides/config/models.py
index 85f620f70..726bc1191 100644
--- a/openslides/config/models.py
+++ b/openslides/config/models.py
@@ -9,21 +9,26 @@
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
-from pickle import dumps, loads
-import base64
+from django.conf import settings
+from django.core.urlresolvers import reverse
from django.db import models
from django.dispatch import receiver
+from django.utils.importlib import import_module
+from django.utils.translation import ugettext as _, ugettext_noop
-from utils.translation_ext import ugettext as _
+from openslides.utils.jsonfield import JSONField
+from openslides.utils.signals import template_manipulation
from openslides.config.signals import default_config_value
-import settings
class ConfigStore(models.Model):
+ """
+ Stores the config values.
+ """
key = models.CharField(max_length=100, primary_key=True)
- value = models.TextField()
+ value = JSONField()
def __unicode__(self):
return self.key
@@ -31,41 +36,23 @@ class ConfigStore(models.Model):
class Meta:
verbose_name = 'config'
permissions = (
- ('can_manage_config', _("Can manage configuration", fixstr=True)),
+ ('can_manage_config', ugettext_noop("Can manage configuration")),
)
-# TODO:
-# I used base64 to save pickled Data, there has to be another way see:
-# http://stackoverflow.com/questions/2524970/djangounicodedecodeerror-while-storing-pickled-data
-
class Config(object):
- def load_config(self):
- self.config = {}
- for key, value in ConfigStore.objects.all().values_list():
- self.config[key] = loads(base64.decodestring(str(value)))
-
+ """
+ Access the config values via config[...]
+ """
def __getitem__(self, key):
- # Had to be deactivated, because in more than one thread the values have
- # to be loaded on each request.
- ## try:
- ## self.config
- ## except AttributeError:
- ## self.load_config()
- ## try:
- ## return self.config[key]
- ## except KeyError:
- ## pass
-
try:
- return loads(base64.decodestring(str(ConfigStore.objects.get(key=key).value)))
+ return ConfigStore.objects.get(key=key).value
except ConfigStore.DoesNotExist:
pass
- for receiver, value in default_config_value.send(sender='config', key=key):
+ for receiver, value in default_config_value.send(sender='config',
+ key=key):
if value is not None:
-# if settings.DEBUG:
-# print 'Using default for %s' % key
return value
if settings.DEBUG:
print "No default value for: %s" % key
@@ -76,22 +63,25 @@ class Config(object):
c = ConfigStore.objects.get(pk=key)
except ConfigStore.DoesNotExist:
c = ConfigStore(pk=key)
- c.value = base64.encodestring(dumps(value))
+ c.value = value
c.save()
- try:
- self.config[key] = value
- except AttributeError:
- self.load_config()
- self.config[key] = value
+
+ def __contains__(self, item):
+ return ConfigStore.objects.filter(key=item).exists()
+
config = Config()
@receiver(default_config_value, dispatch_uid="config_default_config")
def default_config(sender, key, **kwargs):
+ """
+ Global default values.
+ """
return {
'event_name': 'OpenSlides',
- 'event_description': _('Presentation system for agenda, applications and elections'),
+ 'event_description':
+ _('Presentation system for agenda, applications and elections'),
'event_date': '',
'event_location': '',
'event_organizer': '',
@@ -99,25 +89,23 @@ def default_config(sender, key, **kwargs):
'frontpage_title': _('Welcome'),
'frontpage_welcometext': _('Welcome to OpenSlides!'),
'show_help_text': True,
- 'help_text': _("Get professional support for OpenSlides on %s.") % "www.openslides.org",
+ 'help_text': _("Get professional support for OpenSlides on %s.") %
+ " \
+ www.openslides.org",
'system_enable_anonymous': False,
}.get(key)
-from django.dispatch import receiver
-from django.core.urlresolvers import reverse
-from django.utils.importlib import import_module
-import settings
-
-from openslides.utils.signals import template_manipulation
-
-
@receiver(template_manipulation, dispatch_uid="config_submenu")
def set_submenu(sender, request, context, **kwargs):
+ """
+ Submenu for the config tab.
+ """
if not request.path.startswith('/config/'):
return None
menu_links = [
- (reverse('config_general'), _('General'), request.path == reverse('config_general') ),
+ (reverse('config_general'), _('General'),
+ request.path == reverse('config_general')),
]
for app in settings.INSTALLED_APPS:
@@ -139,7 +127,8 @@ def set_submenu(sender, request, context, **kwargs):
)
menu_links.append (
- (reverse('config_version'), _('Version'), request.path == reverse('config_version') )
+ (reverse('config_version'), _('Version'),
+ request.path == reverse('config_version'))
)
context.update({
diff --git a/openslides/config/signals.py b/openslides/config/signals.py
index c0418c290..7cdc7c7e9 100644
--- a/openslides/config/signals.py
+++ b/openslides/config/signals.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
"""
openslides.config.signals
- ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
Defines Signals for the config.
diff --git a/openslides/config/urls.py b/openslides/config/urls.py
index e873bc9f6..727fafadb 100644
--- a/openslides/config/urls.py
+++ b/openslides/config/urls.py
@@ -10,14 +10,13 @@
:license: GNU GPL, see LICENSE for more details.
"""
-from django.conf.urls.defaults import *
+from django.conf import settings
+from django.conf.urls.defaults import patterns, url
from django.utils.importlib import import_module
-import settings
+from openslides.config.views import GeneralConfig, VersionConfig
-from views import GeneralConfig, VersionConfig
-
-urlpatterns = patterns('config.views',
+urlpatterns = patterns('',
url(r'^general/$',
GeneralConfig.as_view(),
name='config_general',
diff --git a/openslides/config/views.py b/openslides/config/views.py
index 3daf008cc..749d7dae0 100644
--- a/openslides/config/views.py
+++ b/openslides/config/views.py
@@ -10,27 +10,26 @@
:license: GNU GPL, see LICENSE for more details.
"""
-from django.shortcuts import redirect
-from django.core.urlresolvers import reverse
+from django.conf import settings
from django.contrib import messages
from django.contrib.auth.models import Group, Permission
-from django.utils.translation import ugettext as _
-from django.template.loader import render_to_string
+from django.core.urlresolvers import reverse
from django.utils.importlib import import_module
-import settings
+from django.utils.translation import ugettext as _
from openslides import get_version
-from utils.utils import template, permission_required
-from utils.views import FormView, TemplateView
-from utils.template import Tab
+from openslides.utils.template import Tab
+from openslides.utils.views import FormView, TemplateView
-from forms import GeneralConfigForm
-
-from models import config
+from openslides.config.forms import GeneralConfigForm
+from openslides.config.models import config
class GeneralConfig(FormView):
+ """
+ Gereral config values.
+ """
permission_required = 'config.can_manage_config'
form_class = GeneralConfigForm
template_name = 'config/general.html'
@@ -57,7 +56,8 @@ class GeneralConfig(FormView):
# frontpage
config['frontpage_title'] = form.cleaned_data['frontpage_title']
- config['frontpage_welcometext'] = form.cleaned_data['frontpage_welcometext']
+ config['frontpage_welcometext'] = \
+ form.cleaned_data['frontpage_welcometext']
# system
if form.cleaned_data['system_enable_anonymous']:
@@ -66,21 +66,29 @@ class GeneralConfig(FormView):
try:
anonymous = Group.objects.get(name='Anonymous')
except Group.DoesNotExist:
- default_perms = [u'can_see_agenda', u'can_see_projector', u'can_see_application']
+ default_perms = [u'can_see_agenda', u'can_see_projector',
+ u'can_see_application']
anonymous = Group()
anonymous.name = 'Anonymous'
anonymous.save()
- anonymous.permissions = Permission.objects.filter(codename__in=default_perms)
+ anonymous.permissions = Permission.objects.filter(
+ codename__in=default_perms)
anonymous.save()
- messages.success(self.request, _('Anonymous access enabled. Please modify the "Anonymous" group to fit your required permissions.'))
+ messages.success(self.request,
+ _('Anonymous access enabled. Please modify the "Anonymous" \
+ group to fit your required permissions.'))
else:
config['system_enable_anonymous'] = False
- messages.success(self.request, _('General settings successfully saved.'))
+ messages.success(self.request,
+ _('General settings successfully saved.'))
return super(GeneralConfig, self).form_valid(form)
class VersionConfig(TemplateView):
+ """
+ Show version infos.
+ """
permission_required = 'config.can_manage_config'
template_name = 'config/version.html'
@@ -99,12 +107,14 @@ class VersionConfig(TemplateView):
plugin_name = mod.__name__.split('.')[0]
context['versions'].append((plugin_name, plugin_version))
-
return context
def register_tab(request):
- selected = True if request.path.startswith('/config/') else False
+ """
+ Register the config tab.
+ """
+ selected = request.path.startswith('/config/')
return Tab(
title=_('Configuration'),
url=reverse('config_general'),
diff --git a/openslides/main.py b/openslides/main.py
index aafe02c7e..192d826fd 100644
--- a/openslides/main.py
+++ b/openslides/main.py
@@ -61,7 +61,7 @@ def main(argv = None):
if opts.reset_admin:
create_or_reset_admin_user()
- # NOTE: --insecure is needed so static files will be served if
+ # NOTE: --insecure is needed so static files will be served if
# DEBUG is set to False
argv = ["", "runserver", "--noreload", "--insecure"]
if opts.nothread:
@@ -186,13 +186,8 @@ def set_system_url(url):
from openslides.config.models import config
key = "participant_pdf_system_url"
- try:
- if key in config.config:
- return
- except AttributeError:
- config.load_config()
- if key in config.config:
- return
+ if key in config:
+ return
config[key] = url
diff --git a/openslides/utils/jsonfield/README b/openslides/utils/jsonfield/README
new file mode 100644
index 000000000..74113188c
--- /dev/null
+++ b/openslides/utils/jsonfield/README
@@ -0,0 +1,13 @@
+https://github.com/bradjasper/django-jsonfield
+
+django-jsonfield is a reusable django field that allows you to store validated JSON in your model.
+
+It silently takes care of serialization. To use, simple add the field to one of your models.
+
+===
+
+from django.db import models
+from jsonfield import JSONField
+
+class MyModel(models.Model):
+json = JSONField()
diff --git a/openslides/utils/jsonfield/__init__.py b/openslides/utils/jsonfield/__init__.py
new file mode 100644
index 000000000..b8d87c831
--- /dev/null
+++ b/openslides/utils/jsonfield/__init__.py
@@ -0,0 +1 @@
+from fields import JSONField
\ No newline at end of file
diff --git a/openslides/utils/jsonfield/fields.py b/openslides/utils/jsonfield/fields.py
new file mode 100644
index 000000000..029ca74db
--- /dev/null
+++ b/openslides/utils/jsonfield/fields.py
@@ -0,0 +1,75 @@
+from django.db import models
+from django.core.serializers.json import DjangoJSONEncoder
+from django.utils import simplejson as json
+from django.utils.translation import ugettext_lazy as _
+
+from django.forms.fields import Field
+from django.forms.util import ValidationError as FormValidationError
+
+class JSONFormField(Field):
+ def clean(self, value):
+
+ if not value and not self.required:
+ return None
+
+ value = super(JSONFormField, self).clean(value)
+
+ if isinstance(value, basestring):
+ try:
+ json.loads(value)
+ except ValueError:
+ raise FormValidationError(_("Enter valid JSON"))
+ return value
+
+class JSONField(models.TextField):
+ """JSONField is a generic textfield that serializes/unserializes JSON objects"""
+
+ # Used so to_python() is called
+ __metaclass__ = models.SubfieldBase
+
+ def __init__(self, *args, **kwargs):
+ self.dump_kwargs = kwargs.pop('dump_kwargs', {'cls': DjangoJSONEncoder})
+ self.load_kwargs = kwargs.pop('load_kwargs', {})
+
+ super(JSONField, self).__init__(*args, **kwargs)
+
+ def to_python(self, value):
+ """Convert string value to JSON"""
+ if isinstance(value, basestring):
+ try:
+ return json.loads(value, **self.load_kwargs)
+ except ValueError:
+ pass
+ return value
+
+ def get_db_prep_value(self, value, connection, prepared=False):
+ """Convert JSON object to a string"""
+
+ if isinstance(value, basestring):
+ return value
+ return json.dumps(value, **self.dump_kwargs)
+
+ def value_to_string(self, obj):
+ value = self._get_val_from_obj(obj)
+ return self.get_prep_value(value)
+
+ def value_from_object(self, obj):
+ return json.dumps(super(JSONField, self).value_from_object(obj))
+
+ def formfield(self, **kwargs):
+
+ if "form_class" not in kwargs:
+ kwargs["form_class"] = JSONFormField
+
+ field = super(JSONField, self).formfield(**kwargs)
+
+ if not field.help_text:
+ field.help_text = "Enter valid JSON"
+
+ return field
+
+try:
+ from south.modelsinspector import add_introspection_rules
+ add_introspection_rules([], ["^jsonfield\.fields\.JSONField"])
+except ImportError:
+ pass
diff --git a/openslides/utils/jsonfield/models.py b/openslides/utils/jsonfield/models.py
new file mode 100644
index 000000000..e5faf1b16
--- /dev/null
+++ b/openslides/utils/jsonfield/models.py
@@ -0,0 +1 @@
+# Django needs this to see it as a project
diff --git a/openslides/utils/jsonfield/tests.py b/openslides/utils/jsonfield/tests.py
new file mode 100644
index 000000000..2eb87ec37
--- /dev/null
+++ b/openslides/utils/jsonfield/tests.py
@@ -0,0 +1,108 @@
+from django.db import models
+from django.test import TestCase
+from django.utils import simplejson as json
+
+from fields import JSONField
+
+
+class JsonModel(models.Model):
+ json = JSONField()
+
+
+class ComplexEncoder(json.JSONEncoder):
+ def default(self, obj):
+ if isinstance(obj, complex):
+ return {
+ '__complex__': True,
+ 'real': obj.real,
+ 'imag': obj.imag,
+ }
+
+ return json.JSONEncoder.default(self, obj)
+
+
+def as_complex(dct):
+ if '__complex__' in dct:
+ return complex(dct['real'], dct['imag'])
+ return dct
+
+
+class JSONModelCustomEncoders(models.Model):
+ # A JSON field that can store complex numbers
+ json = JSONField(
+ dump_kwargs={'cls': ComplexEncoder},
+ load_kwargs={'object_hook': as_complex},
+ )
+
+
+class JSONFieldTest(TestCase):
+ """JSONField Wrapper Tests"""
+
+ def test_json_field_create(self):
+ """Test saving a JSON object in our JSONField"""
+
+ json_obj = {
+ "item_1": "this is a json blah",
+ "blergh": "hey, hey, hey"}
+
+ obj = JsonModel.objects.create(json=json_obj)
+ new_obj = JsonModel.objects.get(id=obj.id)
+
+ self.failUnlessEqual(new_obj.json, json_obj)
+
+ def test_json_field_modify(self):
+ """Test modifying a JSON object in our JSONField"""
+
+ json_obj_1 = {'a': 1, 'b': 2}
+ json_obj_2 = {'a': 3, 'b': 4}
+
+ obj = JsonModel.objects.create(json=json_obj_1)
+
+ self.failUnlessEqual(obj.json, json_obj_1)
+
+ obj.json = json_obj_2
+
+ self.failUnlessEqual(obj.json, json_obj_2)
+
+ obj.save()
+
+ self.failUnlessEqual(obj.json, json_obj_2)
+
+ self.assert_(obj)
+
+ def test_json_field_load(self):
+ """Test loading a JSON object from the DB"""
+
+ json_obj_1 = {'a': 1, 'b': 2}
+
+ obj = JsonModel.objects.create(json=json_obj_1)
+
+ new_obj = JsonModel.objects.get(id=obj.id)
+
+ self.failUnlessEqual(new_obj.json, json_obj_1)
+
+ def test_json_list(self):
+ """Test storing a JSON list"""
+
+ json_obj = ["my", "list", "of", 1, "objs", {"hello": "there"}]
+
+ obj = JsonModel.objects.create(json=json_obj)
+ new_obj = JsonModel.objects.get(id=obj.id)
+ self.failUnlessEqual(new_obj.json, json_obj)
+
+ def test_empty_objects(self):
+ """Test storing empty objects"""
+
+ for json_obj in [{}, [], 0, '', False]:
+ obj = JsonModel.objects.create(json=json_obj)
+ new_obj = JsonModel.objects.get(id=obj.id)
+ self.failUnlessEqual(json_obj, obj.json)
+ self.failUnlessEqual(json_obj, new_obj.json)
+
+ def test_custom_encoder(self):
+ """Test encoder_cls and object_hook"""
+ value = 1 + 3j # A complex number
+
+ obj = JSONModelCustomEncoders.objects.create(json=value)
+ new_obj = JSONModelCustomEncoders.objects.get(pk=obj.pk)
+ self.failUnlessEqual(value, new_obj.json)