clean up the config app. Use jsonfield to save the config values.

Syncdb necessary.
This commit is contained in:
Oskar Hahn 2012-07-07 14:48:21 +02:00
parent 15621b7702
commit e01d456e7b
11 changed files with 321 additions and 90 deletions

View File

@ -10,19 +10,59 @@
:license: GNU GPL, see LICENSE for more details. :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 utils.forms import CssClassMixin
from models import config from models import config
from utils.translation_ext import ugettext as _ from utils.translation_ext import ugettext as _
class GeneralConfigForm(Form, CssClassMixin): class GeneralConfigForm(forms.Form, CssClassMixin):
event_name = CharField(widget=TextInput(),label=_("Event name"), max_length=30) event_name = forms.CharField(
event_description = CharField(widget=TextInput(),label=_("Short description of event"), max_length=100, required=False) widget=forms.TextInput(),
event_date = CharField(widget=TextInput(), required=False, label=_("Event date")) label=_("Event name"),
event_location = CharField(widget=TextInput(), required=False, label=_("Event location")) max_length=30,
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") ) event_description = forms.CharField(
frontpage_welcometext = CharField(widget=Textarea(), required=False, label=_("Welcome text") ) 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,
)

View File

@ -9,21 +9,26 @@
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :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.db import models
from django.dispatch import receiver 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 from openslides.config.signals import default_config_value
import settings
class ConfigStore(models.Model): class ConfigStore(models.Model):
"""
Stores the config values.
"""
key = models.CharField(max_length=100, primary_key=True) key = models.CharField(max_length=100, primary_key=True)
value = models.TextField() value = JSONField()
def __unicode__(self): def __unicode__(self):
return self.key return self.key
@ -31,41 +36,23 @@ class ConfigStore(models.Model):
class Meta: class Meta:
verbose_name = 'config' verbose_name = 'config'
permissions = ( 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): class Config(object):
def load_config(self): """
self.config = {} Access the config values via config[...]
for key, value in ConfigStore.objects.all().values_list(): """
self.config[key] = loads(base64.decodestring(str(value)))
def __getitem__(self, key): 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: try:
return loads(base64.decodestring(str(ConfigStore.objects.get(key=key).value))) return ConfigStore.objects.get(key=key).value
except ConfigStore.DoesNotExist: except ConfigStore.DoesNotExist:
pass 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 value is not None:
# if settings.DEBUG:
# print 'Using default for %s' % key
return value return value
if settings.DEBUG: if settings.DEBUG:
print "No default value for: %s" % key print "No default value for: %s" % key
@ -76,22 +63,25 @@ class Config(object):
c = ConfigStore.objects.get(pk=key) c = ConfigStore.objects.get(pk=key)
except ConfigStore.DoesNotExist: except ConfigStore.DoesNotExist:
c = ConfigStore(pk=key) c = ConfigStore(pk=key)
c.value = base64.encodestring(dumps(value)) c.value = value
c.save() c.save()
try:
self.config[key] = value def __contains__(self, item):
except AttributeError: return ConfigStore.objects.filter(key=item).exists()
self.load_config()
self.config[key] = value
config = Config() config = Config()
@receiver(default_config_value, dispatch_uid="config_default_config") @receiver(default_config_value, dispatch_uid="config_default_config")
def default_config(sender, key, **kwargs): def default_config(sender, key, **kwargs):
"""
Global default values.
"""
return { return {
'event_name': 'OpenSlides', 'event_name': 'OpenSlides',
'event_description': _('Presentation system for agenda, applications and elections'), 'event_description':
_('Presentation system for agenda, applications and elections'),
'event_date': '', 'event_date': '',
'event_location': '', 'event_location': '',
'event_organizer': '', 'event_organizer': '',
@ -99,25 +89,23 @@ def default_config(sender, key, **kwargs):
'frontpage_title': _('Welcome'), 'frontpage_title': _('Welcome'),
'frontpage_welcometext': _('Welcome to OpenSlides!'), 'frontpage_welcometext': _('Welcome to OpenSlides!'),
'show_help_text': True, '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, 'system_enable_anonymous': False,
}.get(key) }.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") @receiver(template_manipulation, dispatch_uid="config_submenu")
def set_submenu(sender, request, context, **kwargs): def set_submenu(sender, request, context, **kwargs):
"""
Submenu for the config tab.
"""
if not request.path.startswith('/config/'): if not request.path.startswith('/config/'):
return None return None
menu_links = [ 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: for app in settings.INSTALLED_APPS:
@ -139,7 +127,8 @@ def set_submenu(sender, request, context, **kwargs):
) )
menu_links.append ( menu_links.append (
(reverse('config_version'), _('Version'), request.path == reverse('config_version') ) (reverse('config_version'), _('Version'),
request.path == reverse('config_version'))
) )
context.update({ context.update({

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
openslides.config.signals openslides.config.signals
~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~
Defines Signals for the config. Defines Signals for the config.

View File

@ -10,14 +10,13 @@
:license: GNU GPL, see LICENSE for more details. :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 from django.utils.importlib import import_module
import settings from openslides.config.views import GeneralConfig, VersionConfig
from views import GeneralConfig, VersionConfig urlpatterns = patterns('',
urlpatterns = patterns('config.views',
url(r'^general/$', url(r'^general/$',
GeneralConfig.as_view(), GeneralConfig.as_view(),
name='config_general', name='config_general',

View File

@ -10,27 +10,26 @@
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.shortcuts import redirect from django.conf import settings
from django.core.urlresolvers import reverse
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import Group, Permission from django.contrib.auth.models import Group, Permission
from django.utils.translation import ugettext as _ from django.core.urlresolvers import reverse
from django.template.loader import render_to_string
from django.utils.importlib import import_module from django.utils.importlib import import_module
import settings from django.utils.translation import ugettext as _
from openslides import get_version from openslides import get_version
from utils.utils import template, permission_required from openslides.utils.template import Tab
from utils.views import FormView, TemplateView from openslides.utils.views import FormView, TemplateView
from utils.template import Tab
from forms import GeneralConfigForm from openslides.config.forms import GeneralConfigForm
from openslides.config.models import config
from models import config
class GeneralConfig(FormView): class GeneralConfig(FormView):
"""
Gereral config values.
"""
permission_required = 'config.can_manage_config' permission_required = 'config.can_manage_config'
form_class = GeneralConfigForm form_class = GeneralConfigForm
template_name = 'config/general.html' template_name = 'config/general.html'
@ -57,7 +56,8 @@ class GeneralConfig(FormView):
# frontpage # frontpage
config['frontpage_title'] = form.cleaned_data['frontpage_title'] 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 # system
if form.cleaned_data['system_enable_anonymous']: if form.cleaned_data['system_enable_anonymous']:
@ -66,21 +66,29 @@ class GeneralConfig(FormView):
try: try:
anonymous = Group.objects.get(name='Anonymous') anonymous = Group.objects.get(name='Anonymous')
except Group.DoesNotExist: 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 = Group()
anonymous.name = 'Anonymous' anonymous.name = 'Anonymous'
anonymous.save() anonymous.save()
anonymous.permissions = Permission.objects.filter(codename__in=default_perms) anonymous.permissions = Permission.objects.filter(
codename__in=default_perms)
anonymous.save() 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: else:
config['system_enable_anonymous'] = False 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) return super(GeneralConfig, self).form_valid(form)
class VersionConfig(TemplateView): class VersionConfig(TemplateView):
"""
Show version infos.
"""
permission_required = 'config.can_manage_config' permission_required = 'config.can_manage_config'
template_name = 'config/version.html' template_name = 'config/version.html'
@ -99,12 +107,14 @@ class VersionConfig(TemplateView):
plugin_name = mod.__name__.split('.')[0] plugin_name = mod.__name__.split('.')[0]
context['versions'].append((plugin_name, plugin_version)) context['versions'].append((plugin_name, plugin_version))
return context return context
def register_tab(request): def register_tab(request):
selected = True if request.path.startswith('/config/') else False """
Register the config tab.
"""
selected = request.path.startswith('/config/')
return Tab( return Tab(
title=_('Configuration'), title=_('Configuration'),
url=reverse('config_general'), url=reverse('config_general'),

View File

@ -186,13 +186,8 @@ def set_system_url(url):
from openslides.config.models import config from openslides.config.models import config
key = "participant_pdf_system_url" key = "participant_pdf_system_url"
try: if key in config:
if key in config.config: return
return
except AttributeError:
config.load_config()
if key in config.config:
return
config[key] = url config[key] = url

View 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()

View File

@ -0,0 +1 @@
from fields import JSONField

View 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

View File

@ -0,0 +1 @@
# Django needs this to see it as a project

View 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)