Newer
Older
from collections import Counter
import bleach
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.core.blocks import StaticBlock, StreamValue, StreamBlock
from opentech.apply.stream_forms.blocks import FormFieldBlock, TextFieldBlock
from opentech.apply.utils.options import RICH_TEXT_WIDGET
def find_duplicates(items):
counted = Counter(items)
duplicates = [
name for name, count in counted.items() if count > 1
]
return duplicates
def prettify_names(sequence):
return [nice_field_name(item) for item in sequence]
def nice_field_name(name):
return name.title().replace('_', ' ')
class RichTextFieldBlock(TextFieldBlock):
widget = RICH_TEXT_WIDGET
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
class Meta:
label = _('Rich text field')
icon = 'form'
def get_searchable_content(self, value, data):
return bleach.clean(data, tags=[], strip=True)
class CustomFormFieldsBlock(StreamBlock):
rich_text = RichTextFieldBlock(group=_('Fields'))
required_blocks = None
required_block_names = None
def __init__(self, *args, **kwargs):
child_blocks = [(block.name, block(group=_('Required'))) for block in self.required_blocks]
self.required_block_names = [block.name for block in self.required_blocks]
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(self.required_block_names) - set(block_types)
duplicates = [
name for name in find_duplicates(block_types)
if name in self.required_block_names
]
all_errors = list()
if missing:
all_errors.append(
'You are missing the following required fields: {}'.format(', '.join(prettify_names(missing)))
)
if duplicates:
all_errors.append(
'You have duplicates of the following required fields: {}'.format(', '.join(prettify_names(duplicates)))
)
for i, block_name in enumerate(block_types):
if block_name in duplicates:
self.add_error_to_child(error_dict, i, 'info', 'Duplicate field')
if all_errors or error_dict:
error_dict['__all__'] = all_errors
raise ValidationError('Error', params=error_dict)
return value
def add_error_to_child(self, errors, child_number, field, message):
new_error = ErrorList([message])
try:
errors[child_number].data[0].params[field] = new_error
except KeyError:
errors[child_number] = ErrorList(
[ValidationError('Error', params={field: new_error})]
)
def to_python(self, value):
# If the data type is missing, fallback to a CharField
for child_data in value:
if child_data['type'] not in self.child_blocks:
child_data['type'] = 'char'
return StreamValue(self, value, is_lazy=True)
class MustIncludeStatic(StaticBlock):
"""Helper block which displays additional information about the must include block and
helps display the error in a noticeable way.
"""
def __init__(self, *args, description='', **kwargs):
self.description = description
super().__init__(*args, **kwargs)
class Meta:
admin_text = 'Must 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.core.blocks.static_block.StaticBlock', (), {})
class MustIncludeFieldBlock(FormFieldBlock):
"""Any block inheriting from this will need to be included in the application forms
This data will also be available to query on the submission object
"""
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