from django import forms from django.db.models import BLANK_CHOICE_DASH from django.utils.dateparse import parse_datetime from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ from anyascii import anyascii from wagtail.blocks import ( StructBlock, TextBlock, CharBlock, BooleanBlock, ListBlock, StreamBlock, DateBlock, TimeBlock, DateTimeBlock, ChoiceBlock, RichTextBlock, ) class FormFieldBlock(StructBlock): field_label = CharBlock(label=_('Label')) help_text = TextBlock(required=False, label=_('Help text')) field_class = forms.CharField widget = None def get_slug(self, struct_value): return slugify(anyascii(struct_value['field_label'])) def get_field_class(self, struct_value): return self.field_class def get_widget(self, struct_value): return self.widget def get_field_kwargs(self, struct_value): kwargs = {'label': struct_value['field_label'], 'help_text': struct_value['help_text'], 'required': struct_value.get('required', False)} if 'default_value' in struct_value: kwargs['initial'] = struct_value['default_value'] form_widget = self.get_widget(struct_value) if form_widget is not None: kwargs['widget'] = form_widget return kwargs def get_field(self, struct_value): return self.get_field_class(struct_value)( **self.get_field_kwargs(struct_value)) class OptionalFormFieldBlock(FormFieldBlock): required = BooleanBlock(label=_('Required'), required=False) CHARFIELD_FORMATS = [ ('email', _('Email')), ('url', _('URL')), ] try: from phonenumber_field.formfields import PhoneNumberField except ImportError: pass else: CHARFIELD_FORMATS.append(('phone', _('Phone'))) class CharFieldBlock(OptionalFormFieldBlock): format = ChoiceBlock(choices=CHARFIELD_FORMATS, required=False, label=_('Format')) default_value = CharBlock(required=False, label=_('Default value')) class Meta: label = _('Text field (single line)') def get_field_class(self, struct_value): text_format = struct_value['format'] if text_format == 'url': return forms.URLField if text_format == 'email': return forms.EmailField if text_format == 'phone': return PhoneNumberField return super().get_field_class(struct_value) class TextFieldBlock(OptionalFormFieldBlock): default_value = TextBlock(required=False, label=_('Default value')) widget = forms.Textarea(attrs={'rows': 5}) class Meta: label = _('Text field (multi line)') class NumberFieldBlock(OptionalFormFieldBlock): default_value = CharBlock(required=False, label=_('Default value')) widget = forms.NumberInput class Meta: label = _('Number field') class CheckboxFieldBlock(FormFieldBlock): default_value = BooleanBlock(required=False) field_class = forms.BooleanField class Meta: label = _('Checkbox field') icon = 'tick-inverse' class RadioButtonsFieldBlock(OptionalFormFieldBlock): choices = ListBlock(CharBlock(label=_('Choice'))) field_class = forms.ChoiceField widget = forms.RadioSelect class Meta: label = _('Radio buttons') icon = 'radio-empty' def get_field_kwargs(self, struct_value): kwargs = super().get_field_kwargs(struct_value) kwargs['choices'] = [(choice, choice) for choice in struct_value['choices']] return kwargs class DropdownFieldBlock(RadioButtonsFieldBlock): widget = forms.Select class Meta: label = _('Dropdown field') icon = 'arrow-down-big' def get_field_kwargs(self, struct_value): kwargs = super(DropdownFieldBlock, self).get_field_kwargs(struct_value) kwargs['choices'].insert(0, BLANK_CHOICE_DASH[0]) return kwargs class CheckboxesFieldBlock(OptionalFormFieldBlock): checkboxes = ListBlock(CharBlock(label=_('Checkbox'))) field_class = forms.MultipleChoiceField widget = forms.CheckboxSelectMultiple class Meta: label = _('Multiple checkboxes field') icon = 'list-ul' def get_field_kwargs(self, struct_value): kwargs = super(CheckboxesFieldBlock, self).get_field_kwargs(struct_value) kwargs['choices'] = [(choice, choice) for choice in struct_value['checkboxes']] return kwargs class DatePickerInput(forms.DateInput): def __init__(self, *args, **kwargs): attrs = kwargs.get('attrs') if attrs is None: attrs = {} attrs.update({ 'data-provide': 'datepicker', 'data-date-format': 'yyyy-mm-dd', }) kwargs['attrs'] = attrs super().__init__(*args, **kwargs) class DateFieldBlock(OptionalFormFieldBlock): default_value = DateBlock(required=False) field_class = forms.DateField widget = DatePickerInput class Meta: label = _('Date field') icon = 'date' class HTML5TimeInput(forms.TimeInput): input_type = 'time' class TimeFieldBlock(OptionalFormFieldBlock): default_value = TimeBlock(required=False) field_class = forms.TimeField widget = HTML5TimeInput class Meta: label = _('Time field') icon = 'time' class DateTimePickerInput(forms.SplitDateTimeWidget): def __init__(self, attrs=None, date_format=None, time_format=None): super().__init__(attrs=attrs, date_format=date_format, time_format=time_format) self.widgets = ( DatePickerInput(attrs=attrs, format=date_format), HTML5TimeInput(attrs=attrs, format=time_format), ) def decompress(self, value): if isinstance(value, str): value = parse_datetime(value) return super().decompress(value) class DateTimeFieldBlock(OptionalFormFieldBlock): default_value = DateTimeBlock(required=False) field_class = forms.SplitDateTimeField widget = DateTimePickerInput class Meta: label = _('Date+time field') icon = 'date' class ImageFieldBlock(OptionalFormFieldBlock): field_class = forms.ImageField class Meta: label = _('Image field') icon = 'image' class FileFieldBlock(OptionalFormFieldBlock): field_class = forms.FileField class Meta: label = _('File field') icon = 'download' class FormFieldsBlock(StreamBlock): char = CharFieldBlock(group=_('Fields')) text = TextFieldBlock(group=_('Fields')) number = NumberFieldBlock(group=_('Fields')) checkbox = CheckboxFieldBlock(group=_('Fields')) radios = RadioButtonsFieldBlock(group=_('Fields')) dropdown = DropdownFieldBlock(group=_('Fields')) checkboxes = CheckboxesFieldBlock(group=_('Fields')) date = DateFieldBlock(group=_('Fields')) time = TimeFieldBlock(group=_('Fields')) datetime = DateTimeFieldBlock(group=_('Fields')) image = ImageFieldBlock(group=_('Fields')) file = FileFieldBlock(group=_('Fields')) text_markup = RichTextBlock(group=_('Other')) class Meta: label = _('Form fields') class FormStepBlock(StructBlock): name = CharBlock(label=_('Name'), required=False) form_fields = FormFieldsBlock() class Meta: label = _('Form step') class FormStepsBlock(StreamBlock): step = FormStepBlock() class Meta: label = _('Form steps')