clean up the config app. Use jsonfield to save the config values.
Syncdb necessary.
This commit is contained in:
parent
15621b7702
commit
e01d456e7b
@ -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,
|
||||
)
|
||||
|
@ -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.") % "<a href='http://openslides.org/' target='_blank'>www.openslides.org</a>",
|
||||
'help_text': _("Get professional support for OpenSlides on %s.") %
|
||||
"<a href='http://openslides.org/' target='_blank'> \
|
||||
www.openslides.org</a>",
|
||||
'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({
|
||||
|
@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.config.signals
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Defines Signals for the config.
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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'),
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
13
openslides/utils/jsonfield/README
Normal file
13
openslides/utils/jsonfield/README
Normal file
@ -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()
|
1
openslides/utils/jsonfield/__init__.py
Normal file
1
openslides/utils/jsonfield/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from fields import JSONField
|
75
openslides/utils/jsonfield/fields.py
Normal file
75
openslides/utils/jsonfield/fields.py
Normal file
@ -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
|
1
openslides/utils/jsonfield/models.py
Normal file
1
openslides/utils/jsonfield/models.py
Normal file
@ -0,0 +1 @@
|
||||
# Django needs this to see it as a project
|
108
openslides/utils/jsonfield/tests.py
Normal file
108
openslides/utils/jsonfield/tests.py
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user