-
-
Notifications
You must be signed in to change notification settings - Fork 311
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix constance management command without admin installed (#506)
* refactor out ConstanceForm and get_values into forms.py and utils.py respectively * fix tests and documentation * correct mock import * fix merge
- Loading branch information
Showing
9 changed files
with
194 additions
and
178 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import hashlib | ||
from datetime import date, datetime, time, timedelta | ||
from decimal import Decimal | ||
|
||
from django import conf, forms | ||
from django.contrib import messages | ||
from django.contrib.admin import widgets | ||
from django.core.exceptions import ImproperlyConfigured | ||
from django.core.files.storage import default_storage | ||
from django.forms import fields | ||
from django.utils import timezone | ||
from django.utils.encoding import smart_bytes | ||
from django.utils.module_loading import import_string | ||
from django.utils.text import normalize_newlines | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
from . import LazyConfig, settings | ||
from .checks import get_inconsistent_fieldnames | ||
|
||
config = LazyConfig() | ||
|
||
NUMERIC_WIDGET = forms.TextInput(attrs={'size': 10}) | ||
|
||
INTEGER_LIKE = (fields.IntegerField, {'widget': NUMERIC_WIDGET}) | ||
STRING_LIKE = (fields.CharField, { | ||
'widget': forms.Textarea(attrs={'rows': 3}), | ||
'required': False, | ||
}) | ||
|
||
FIELDS = { | ||
bool: (fields.BooleanField, {'required': False}), | ||
int: INTEGER_LIKE, | ||
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}), | ||
float: (fields.FloatField, {'widget': NUMERIC_WIDGET}), | ||
} | ||
|
||
def parse_additional_fields(fields): | ||
for key in fields: | ||
field = list(fields[key]) | ||
|
||
if len(field) == 1: | ||
field.append({}) | ||
|
||
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 {}) | ||
) | ||
|
||
if 'widget_kwargs' in field[1]: | ||
del field[1]['widget_kwargs'] | ||
|
||
fields[key] = field | ||
|
||
return fields | ||
|
||
|
||
FIELDS.update(parse_additional_fields(settings.ADDITIONAL_FIELDS)) | ||
|
||
|
||
|
||
class ConstanceForm(forms.Form): | ||
version = forms.CharField(widget=forms.HiddenInput) | ||
|
||
def __init__(self, initial, request=None, *args, **kwargs): | ||
super().__init__(*args, initial=initial, **kwargs) | ||
version_hash = hashlib.sha256() | ||
|
||
only_view = request and not request.user.has_perm('constance.change_config') | ||
if only_view: | ||
messages.warning( | ||
request, | ||
_("You don't have permission to change these values"), | ||
) | ||
|
||
for name, options in settings.CONFIG.items(): | ||
default = options[0] | ||
if len(options) == 3: | ||
config_type = options[2] | ||
if config_type not in settings.ADDITIONAL_FIELDS and not isinstance(default, config_type): | ||
raise ImproperlyConfigured(_("Default value type must be " | ||
"equal to declared config " | ||
"parameter type. Please fix " | ||
"the default value of " | ||
"'%(name)s'.") | ||
% {'name': name}) | ||
else: | ||
config_type = type(default) | ||
|
||
if config_type not in FIELDS: | ||
raise ImproperlyConfigured(_("Constance doesn't support " | ||
"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] | ||
if only_view: | ||
kwargs['disabled'] = True | ||
self.fields[name] = field_class(label=name, **kwargs) | ||
|
||
version_hash.update(smart_bytes(initial.get(name, ''))) | ||
self.initial['version'] = version_hash.hexdigest() | ||
|
||
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 isinstance(new, str): | ||
new = normalize_newlines(new) | ||
|
||
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) | ||
|
||
def clean_version(self): | ||
value = self.cleaned_data['version'] | ||
|
||
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 | ||
|
||
missing_keys, extra_keys = get_inconsistent_fieldnames() | ||
if missing_keys or extra_keys: | ||
raise forms.ValidationError(_('CONSTANCE_CONFIG_FIELDSETS is missing ' | ||
'field(s) that exists in CONSTANCE_CONFIG.')) | ||
|
||
return cleaned_data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,23 @@ | ||
from importlib import import_module | ||
|
||
from . import LazyConfig, settings | ||
|
||
config = LazyConfig() | ||
|
||
def import_module_attr(path): | ||
package, module = path.rsplit('.', 1) | ||
return getattr(import_module(package), module) | ||
|
||
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 |
Oops, something went wrong.