from collections import Counter from django import forms from django.core.exceptions import ValidationError from django.forms.utils import ErrorList from django.utils.translation import ugettext_lazy as _ from django.utils.text import mark_safe from wagtail.wagtailcore.blocks import StaticBlock from opentech.apply.stream_forms.blocks import FormFieldsBlock, FormFieldBlock from opentech.apply.categories.blocks import CategoryQuestionBlock class CustomFormFieldsBlock(FormFieldsBlock): category = CategoryQuestionBlock(group=_('Custom')) def __init__(self, *args, **kwargs): child_blocks = [(block.name, block(group=_('Required'))) for block in MustIncludeFieldBlock.__subclasses__()] super().__init__(child_blocks, *args, **kwargs) def clean(self, value): try: value = super().clean(value) except ValidationError as e: error_dict = e.params else: error_dict = dict() block_types = [block.block_type for block in value] missing = set(REQUIRED_BLOCK_NAMES) - set(block_types) counted_types = Counter(block_types) duplicates = [ name for name, count in counted_types.items() if name in REQUIRED_BLOCK_NAMES and count > 1 ] all_errors = list() if missing: all_errors.append( 'You are missing the following required fields: {}'.format(', '.join(missing).title()) ) if duplicates: all_errors.append( 'You have duplicates of the following required fields: {}'.format(', '.join(duplicates).title()) ) for name in duplicates: for i, block_name in enumerate(block_types): if block_name == name: try: error_dict[i].data[0].params['info'] = ErrorList(['Duplicate']) except KeyError: error_dict[i] = ErrorList( [ValidationError('Error', params={'info': ErrorList(['Duplicate'])})] ) if all_errors or error_dict: error_dict['__all__'] = all_errors raise ValidationError('Error', params=error_dict) return value class MustIncludeStatic(StaticBlock): def __init__(self, *args, description='', **kwargs): self.description = description super().__init__(*args, **kwargs) class Meta: admin_text = 'Much be included in the form only once.' def render_form(self, *args, **kwargs): errors = kwargs.pop('errors') if errors: # Pretend the error is a readonly input so that we get nice formatting # Issue discussed here: https://github.com/wagtail/wagtail/issues/4122 error_message = '<div class="error"><input readonly placeholder="{}"></div>'.format(errors[0]) else: error_message = '' form = super().render_form(*args, **kwargs) form = '<br>'.join([self.description, form]) + error_message return mark_safe(form) def deconstruct(self): return ('wagtail.wagtailcore.blocks.static_block.StaticBlock', (), {}) class MustIncludeFieldBlock(FormFieldBlock): def __init__(self, *args, **kwargs): info_name = f'{self.name.title()} Field' child_blocks = [('info', MustIncludeStatic(label=info_name, description=self.description))] super().__init__(child_blocks, *args, **kwargs) def get_field_kwargs(self, struct_value): kwargs = super().get_field_kwargs(struct_value) kwargs['required'] = True return kwargs class TitleBlock(MustIncludeFieldBlock): name = 'title' description = 'The title of the project' class ValueBlock(MustIncludeFieldBlock): name = 'value' description = 'The value of the project' widget = forms.NumberInput REQUIRED_BLOCK_NAMES = [block.name for block in MustIncludeFieldBlock.__subclasses__()]