input letter address data in a single block

rather than in multiple placeholders - this is the first step towards
making postcodes non-required, which is the first step towards
international letters.

they still populate address_line_# and postcode fields under the hood -
to keep validation working the same, the last line always goes into
`postcode`.

the form normalises whitespace, removes extra new lines, and enforces
that you have between three and seven lines.

if the letter repeats address placeholders further down (eg "Dear
((address_line_1))"), then it'll fill those in as well. It'll still
prompt you to fill them in, but they'll be pre-filled.
This commit is contained in:
Leo Hemsted
2020-03-10 15:12:19 +00:00
parent df51bf6f5f
commit c4d839d4f5
7 changed files with 411 additions and 13 deletions

View File

@@ -1,3 +1,4 @@
import re
import weakref
from datetime import datetime, timedelta
from itertools import chain
@@ -9,12 +10,17 @@ from flask_wtf import FlaskForm as Form
from flask_wtf.file import FileAllowed
from flask_wtf.file import FileField as FileField_wtf
from notifications_utils.columns import Columns
from notifications_utils.formatters import strip_whitespace
from notifications_utils.formatters import (
normalise_whitespace_and_newlines,
remove_whitespace_before_punctuation,
strip_whitespace,
)
from notifications_utils.recipients import (
InvalidPhoneError,
normalise_phone_number,
validate_phone_number,
)
from notifications_utils.take import Take
from wtforms import (
BooleanField,
DateField,
@@ -362,6 +368,23 @@ class StripWhitespaceStringField(StringField):
super(StringField, self).__init__(label, **kwargs)
class StripWhitespaceTextAreaField(TextAreaField):
def process_formdata(self, valuelist):
if valuelist:
self.data = Take(
valuelist[0]
).then(
remove_whitespace_before_punctuation
).then(
normalise_whitespace_and_newlines
).then(
# similar to normalise_multiple_newlines but taking everything down to one `\n` instead of two
lambda value: re.compile(r'\n{2,}').sub('\n', value)
).then(
str.strip
)
class OnOffField(RadioField):
def __init__(self, label, choices=None, *args, **kwargs):
@@ -741,6 +764,43 @@ class SMSTemplateForm(BaseTemplateForm):
OnlySMSCharacters()(None, field)
class LetterAddressForm(StripWhitespaceForm):
MIN_ADDRESS_LINES = 3
MAX_ADDRESS_LINES = 7
address = StripWhitespaceTextAreaField(
'Address',
validators=[DataRequired(message="Cannot be empty")]
)
def validate_address(self, field):
lines = field.data.splitlines()
if len(lines) < self.MIN_ADDRESS_LINES:
raise ValidationError('Address must be at least 3 lines long')
if len(lines) > self.MAX_ADDRESS_LINES:
raise ValidationError('Address must be no more than 7 lines long')
@property
def as_address_lines_1_to_7_with_postcode(self):
lines = self.address.data.splitlines()
placeholders = {}
# set all placeholders to empty strings, or all_placeholders_in_session will always return false.
# note that it must be `address line #` with spaces, not underscores or dashes
for i in range(1, 7):
placeholders[f'address line {i}'] = ''
# unroll the address into lines, and place into the session in the underlying placeholder names
# postcode is required so make sure we put the last value in that
# TODO: When postcode is no longer a required field, remove this special case and just use `address line #`
address_lines, last_address_line = lines[:-1], lines[-1]
for i, line in enumerate(address_lines, start=1):
placeholders[f'address line {i}'] = line
placeholders['postcode'] = last_address_line
return placeholders
class EmailTemplateForm(BaseTemplateForm):
subject = TextAreaField(
u'Subject',