import math from copy import deepcopy from functools import partial from django import forms from django.core.exceptions import ValidationError from django.forms.formsets import formset_factory from django.utils.functional import Promise from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ from orchestra.forms import ReadOnlyFormMixin, widgets from orchestra.utils.python import format_exception from . import parser class SettingForm(ReadOnlyFormMixin, forms.Form): TEXTAREA = partial( forms.CharField, widget=forms.Textarea(attrs={ 'cols': 65, 'rows': 2, 'style': 'font-family: monospace', })) CHARFIELD = partial( forms.CharField, widget=forms.TextInput(attrs={ 'size': 65, 'style': 'font-family: monospace', })) NON_EDITABLE = partial(forms.CharField, widget=widgets.SpanWidget, required=False) FORMFIELD_FOR_SETTING_TYPE = { bool: partial(forms.BooleanField, required=False), int: forms.IntegerField, float: forms.FloatField, tuple: TEXTAREA, list: TEXTAREA, dict: TEXTAREA, Promise: CHARFIELD, str: CHARFIELD, } name = forms.CharField(label=_("name")) default = forms.CharField(label=_("default")) class Meta: readonly_fields = ('name', 'default') def __init__(self, *args, **kwargs): initial = kwargs.get('initial') if initial: self.setting_type = initial['type'] self.setting = initial['setting'] setting = self.setting if setting.serializable: serialized_value = parser.serialize(initial['value']) serialized_default = parser.serialize(initial['default']) else: serialized_value = parser.NotSupported() serialized_default = parser.NotSupported() if not setting.editable or isinstance(serialized_value, parser.NotSupported): field = self.NON_EDITABLE else: choices = setting.choices field = forms.ChoiceField multiple = setting.multiple if multiple: field = partial( forms.MultipleChoiceField, widget=forms.CheckboxSelectMultiple) if choices: # Lazy loading if callable(choices): choices = choices() if not multiple: choices = tuple((parser.serialize(val), verb) for val, verb in choices) field = partial(field, choices=choices) else: field = self.FORMFIELD_FOR_SETTING_TYPE.get( self.setting_type, self.NON_EDITABLE) field = deepcopy(field) real_field = field while isinstance(real_field, partial): real_field = real_field.func # Do not serialize following form types value = initial['value'] default = initial['default'] self.changed = bool(value != default) if real_field not in (forms.MultipleChoiceField, forms.BooleanField): value = serialized_value default = serialized_default initial['value'] = value initial['default'] = default super(SettingForm, self).__init__(*args, **kwargs) if initial: self.fields['value'] = field(label=_("value")) if isinstance(self.fields['value'].widget, forms.Textarea): rows = math.ceil(len(value)/65) self.fields['value'].widget.attrs['rows'] = rows self.fields['name'].help_text = mark_safe(setting.help_text) self.fields['name'].widget.attrs['readonly'] = True self.app = initial['app'] def clean_value(self): value = self.cleaned_data['value'] if not value: return parser.NotSupported() if not isinstance(value, str): value = parser.serialize(value) try: value = eval(value, parser.get_eval_context()) except Exception as exc: raise ValidationError(format_exception(exc)) self.setting.validate_value(value) if not isinstance(value, self.setting_type): if self.setting_type in (tuple, list) and isinstance(value, (tuple, list)): value = self.setting_type(value) return value SettingFormSet = formset_factory(SettingForm, extra=0)