django-constance/constance/admin.py

334 lines
12 KiB
Python
Raw Normal View History

from collections import OrderedDict
from datetime import datetime, date, time, timedelta
from decimal import Decimal
2010-08-23 15:23:38 +00:00
from operator import itemgetter
import hashlib
2010-08-23 14:18:44 +00:00
from django import forms, VERSION, conf
from django.apps import apps
from django.conf.urls import url
from django.contrib import admin, messages
2010-11-10 03:07:53 +00:00
from django.contrib.admin import widgets
from django.contrib.admin.options import csrf_protect_m
from django.core.exceptions import PermissionDenied, ImproperlyConfigured
from django.core.files.storage import default_storage
2010-08-23 14:18:44 +00:00
from django.forms import fields
2010-11-10 03:07:53 +00:00
from django.http import HttpResponseRedirect
2015-09-24 09:56:19 +00:00
from django.template.response import TemplateResponse
2019-09-16 08:51:18 +00:00
from django.utils import timezone
from django.utils.encoding import smart_bytes
2010-11-10 03:07:53 +00:00
from django.utils.formats import localize
2015-11-02 14:52:55 +00:00
from django.utils.module_loading import import_string
from django.utils.translation import ugettext_lazy as _
from . import LazyConfig, settings
from .checks import get_inconsistent_fieldnames
2014-07-08 15:06:40 +00:00
config = LazyConfig()
2010-08-23 12:49:54 +00:00
2010-09-03 14:18:17 +00:00
NUMERIC_WIDGET = forms.TextInput(attrs={'size': 10})
2010-08-23 12:49:54 +00:00
INTEGER_LIKE = (fields.IntegerField, {'widget': NUMERIC_WIDGET})
2013-04-12 15:39:10 +00:00
STRING_LIKE = (fields.CharField, {
'widget': forms.Textarea(attrs={'rows': 3}),
'required': False,
})
2010-08-23 14:18:44 +00:00
FIELDS = {
bool: (fields.BooleanField, {'required': False}),
int: INTEGER_LIKE,
2010-09-03 14:18:17 +00:00
Decimal: (fields.DecimalField, {'widget': NUMERIC_WIDGET}),
str: STRING_LIKE,
datetime: (
fields.SplitDateTimeField, {'widget': widgets.AdminSplitDateTime}
),
timedelta: (
fields.DurationField, {'widget': widgets.AdminTextInputWidget}
),
date: (fields.DateField, {'widget': widgets.AdminDateWidget}),
time: (fields.TimeField, {'widget': widgets.AdminTimeWidget}),
2010-09-03 14:18:17 +00:00
float: (fields.FloatField, {'widget': NUMERIC_WIDGET}),
2010-08-23 14:18:44 +00:00
}
2015-11-02 14:52:55 +00:00
def parse_additional_fields(fields):
2015-11-02 14:52:55 +00:00
for key in fields:
field = list(fields[key])
if len(field) == 1:
field.append({})
2015-11-02 14:52:55 +00:00
field[0] = import_string(field[0])
if 'widget' in field[1]:
klass = import_string(field[1]['widget'])
field[1]['widget'] = klass(
**(field[1].get('widget_kwargs', {}) or {})
)
2015-11-02 14:52:55 +00:00
if 'widget_kwargs' in field[1]:
del field[1]['widget_kwargs']
fields[key] = field
2015-11-02 14:52:55 +00:00
return fields
FIELDS.update(parse_additional_fields(settings.ADDITIONAL_FIELDS))
2010-08-23 14:18:44 +00:00
def get_values():
"""
Get dictionary of values from the backend
:return:
"""
# First load a mapping between config name and default value
default_initial = ((name, options[0])
for name, options in settings.CONFIG.items())
# Then update the mapping with actually values from the backend
initial = dict(default_initial, **dict(config._backend.mget(settings.CONFIG)))
return initial
2010-08-23 14:18:44 +00:00
class ConstanceForm(forms.Form):
version = forms.CharField(widget=forms.HiddenInput)
def __init__(self, initial, *args, **kwargs):
super().__init__(*args, initial=initial, **kwargs)
version_hash = hashlib.md5()
for name, options in settings.CONFIG.items():
2017-05-25 12:02:52 +00:00
default = options[0]
if len(options) == 3:
2016-08-21 21:45:57 +00:00
config_type = options[2]
2017-05-25 12:02:52 +00:00
if config_type not in settings.ADDITIONAL_FIELDS and not isinstance(default, config_type):
2016-10-12 21:09:18 +00:00
raise ImproperlyConfigured(_("Default value type must be "
"equal to declared config "
"parameter type. Please fix "
"the default value of "
"'%(name)s'.")
% {'name': name})
else:
2016-08-21 21:45:57 +00:00
config_type = type(default)
if config_type not in FIELDS:
raise ImproperlyConfigured(_("Constance doesn't support "
2014-11-27 18:05:42 +00:00
"config values of the type "
"%(config_type)s. Please fix "
"the value of '%(name)s'.")
% {'config_type': config_type,
'name': name})
field_class, kwargs = FIELDS[config_type]
self.fields[name] = field_class(label=name, **kwargs)
2010-08-23 14:18:44 +00:00
version_hash.update(smart_bytes(initial.get(name, '')))
self.initial['version'] = version_hash.hexdigest()
2010-08-23 14:40:01 +00:00
def save(self):
for file_field in self.files:
file = self.cleaned_data[file_field]
self.cleaned_data[file_field] = default_storage.save(file.name, file)
for name in settings.CONFIG:
current = getattr(config, name)
new = self.cleaned_data[name]
if conf.settings.USE_TZ and isinstance(current, datetime) and not timezone.is_aware(current):
current = timezone.make_aware(current)
if current != new:
setattr(config, name, new)
2010-08-23 14:40:01 +00:00
def clean_version(self):
value = self.cleaned_data['version']
2016-04-08 21:18:31 +00:00
if settings.IGNORE_ADMIN_VERSION_CHECK:
return value
if value != self.initial['version']:
raise forms.ValidationError(_('The settings have been modified '
'by someone else. Please reload the '
'form and resubmit your changes.'))
return value
def clean(self):
cleaned_data = super().clean()
if not settings.CONFIG_FIELDSETS:
return cleaned_data
if get_inconsistent_fieldnames():
raise forms.ValidationError(_('CONSTANCE_CONFIG_FIELDSETS is missing '
'field(s) that exists in CONSTANCE_CONFIG.'))
return cleaned_data
2010-08-23 14:18:44 +00:00
2010-08-23 12:49:54 +00:00
class ConstanceAdmin(admin.ModelAdmin):
2015-09-24 09:56:19 +00:00
change_list_template = 'admin/constance/change_list.html'
change_list_form = ConstanceForm
2010-08-23 12:49:54 +00:00
2010-11-10 03:10:49 +00:00
def get_urls(self):
2010-08-23 12:49:54 +00:00
info = self.model._meta.app_label, self.model._meta.module_name
return [
2010-08-23 12:49:54 +00:00
url(r'^$',
2010-11-10 03:10:49 +00:00
self.admin_site.admin_view(self.changelist_view),
2013-04-12 15:25:11 +00:00
name='%s_%s_changelist' % info),
2011-09-21 14:24:15 +00:00
url(r'^$',
self.admin_site.admin_view(self.changelist_view),
2013-04-12 15:25:11 +00:00
name='%s_%s_add' % info),
]
2010-08-23 12:49:54 +00:00
2016-08-21 21:45:57 +00:00
def get_config_value(self, name, options, form, initial):
default, help_text = options[0], options[1]
# First try to load the value from the actual backend
value = initial.get(name)
# Then if the returned value is None, get the default
if value is None:
value = getattr(config, name)
config_value = {
'name': name,
'default': localize(default),
'raw_default': default,
2016-08-21 21:45:57 +00:00
'help_text': _(help_text),
'value': localize(value),
'modified': localize(value) != localize(default),
2016-08-21 21:45:57 +00:00
'form_field': form[name],
2018-04-16 18:41:18 +00:00
'is_date': isinstance(default, date),
'is_datetime': isinstance(default, datetime),
2017-11-08 13:42:34 +00:00
'is_checkbox': isinstance(form[name].field.widget, forms.CheckboxInput),
'is_file': isinstance(form[name].field.widget, forms.FileInput),
2016-08-21 21:45:57 +00:00
}
return config_value
def get_changelist_form(self, request):
"""
Returns a Form class for use in the changelist_view.
"""
# Defaults to self.change_list_form in order to preserve backward
# compatibility
return self.change_list_form
2010-11-10 03:10:49 +00:00
@csrf_protect_m
def changelist_view(self, request, extra_context=None):
if not self.has_change_permission(request, None):
raise PermissionDenied
initial = get_values()
form_cls = self.get_changelist_form(request)
form = form_cls(initial=initial)
2010-08-23 14:40:01 +00:00
if request.method == 'POST':
form = form_cls(
data=request.POST, files=request.FILES, initial=initial
)
2010-08-23 14:40:01 +00:00
if form.is_valid():
form.save()
messages.add_message(
request,
messages.SUCCESS,
_('Live settings updated successfully.'),
)
return HttpResponseRedirect('.')
context = dict(
self.admin_site.each_context(request),
config_values=[],
title=self.model._meta.app_config.verbose_name,
app_label='constance',
opts=self.model._meta,
form=form,
media=self.media + form.media,
icon_type='gif' if VERSION < (1, 9) else 'svg',
)
for name, options in settings.CONFIG.items():
context['config_values'].append(
self.get_config_value(name, options, form, initial)
)
2016-08-21 21:45:57 +00:00
if settings.CONFIG_FIELDSETS:
context['fieldsets'] = []
for fieldset_title, fieldset_data in settings.CONFIG_FIELDSETS.items():
if type(fieldset_data) == dict:
fields_list = fieldset_data['fields']
collapse = fieldset_data.get('collapse', False)
else:
fields_list = fieldset_data
collapse = False
absent_fields = [field for field in fields_list
if field not in settings.CONFIG]
assert not any(absent_fields), (
"CONSTANCE_CONFIG_FIELDSETS contains field(s) that does "
"not exist: %s" % ', '.join(absent_fields))
2016-08-21 21:45:57 +00:00
config_values = []
for name in fields_list:
options = settings.CONFIG.get(name)
if options:
config_values.append(
self.get_config_value(name, options, form, initial)
)
fieldset_context = {
2016-08-21 21:45:57 +00:00
'title': fieldset_title,
'config_values': config_values
}
if collapse:
fieldset_context['collapse'] = True
context['fieldsets'].append(fieldset_context)
2016-10-01 11:07:40 +00:00
if not isinstance(settings.CONFIG_FIELDSETS, OrderedDict):
context['fieldsets'].sort(key=itemgetter('title'))
2016-08-21 21:45:57 +00:00
2016-10-01 11:07:40 +00:00
if not isinstance(settings.CONFIG, OrderedDict):
context['config_values'].sort(key=itemgetter('name'))
2015-09-24 09:56:19 +00:00
request.current_app = self.admin_site.name
return TemplateResponse(request, self.change_list_template, context)
2010-08-23 12:49:54 +00:00
def has_add_permission(self, *args, **kwargs):
return False
def has_delete_permission(self, *args, **kwargs):
return False
def has_change_permission(self, request, obj=None):
if settings.SUPERUSER_ONLY:
return request.user.is_superuser
return super().has_change_permission(request, obj)
2010-08-23 12:49:54 +00:00
class Config:
class Meta:
2010-08-23 12:49:54 +00:00
app_label = 'constance'
object_name = 'Config'
model_name = module_name = 'config'
verbose_name_plural = _('config')
2011-02-23 15:39:58 +00:00
abstract = False
2013-01-19 16:27:35 +00:00
swapped = False
2015-09-24 09:56:19 +00:00
def get_ordered_objects(self):
return False
def get_change_permission(self):
return 'change_%s' % self.model_name
@property
def app_config(self):
return apps.get_app_config(self.app_label)
2018-03-06 10:16:44 +00:00
@property
def label(self):
return '%s.%s' % (self.app_label, self.object_name)
@property
def label_lower(self):
return '%s.%s' % (self.app_label, self.model_name)
2010-08-23 12:49:54 +00:00
_meta = Meta()
2010-08-23 12:49:54 +00:00
admin.site.register([Config], ConstanceAdmin)