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
class Meta:
label = _('Rich text field')
icon = 'form'
def get_searchable_content(self, value, data):
return bleach.clean(data or '', tags=[], strip=True)
def no_response(self):
return '<p>No response</p>'
class CustomFormFieldsBlock(StreamBlock):
rich_text = RichTextFieldBlock(group=_('Fields'))
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
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):
"""
This allows historic data to still be accessible even
if a custom field type is removed from the code in the future.
"""
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
147
148
149
150
151
152
# 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