Files
notifications-admin/app/main/forms.py
T

680 lines
19 KiB
Python
Raw Normal View History

2017-02-15 15:06:47 +00:00
import re
import pytz
from flask_wtf import Form
from datetime import datetime, timedelta
from notifications_utils.recipients import (
validate_phone_number,
InvalidPhoneError
)
from notifications_utils.columns import Columns
2016-01-12 10:43:23 +00:00
from wtforms import (
2016-05-11 09:43:55 +01:00
validators,
2016-01-12 10:43:23 +00:00
StringField,
PasswordField,
ValidationError,
TextAreaField,
FileField,
BooleanField,
2016-05-11 09:43:55 +01:00
HiddenField,
IntegerField,
RadioField,
FieldList,
DateField,
SelectField)
from wtforms.fields.html5 import EmailField, TelField, SearchField
from wtforms.validators import (DataRequired, Email, Length, Regexp, Optional)
from app.main.validators import (Blacklist, CsvFileValidator, ValidGovEmail, NoCommasInPlaceHolders, OnlyGSMCharacters)
def get_time_value_and_label(future_time):
return (
future_time.replace(tzinfo=None).isoformat(),
'{} at {}'.format(
get_human_day(future_time.astimezone(pytz.timezone('Europe/London'))),
get_human_time(future_time.astimezone(pytz.timezone('Europe/London')))
)
)
def get_human_time(time):
return {
'0': 'midnight',
'12': 'midday'
}.get(
time.strftime('%-H'),
time.strftime('%-I%p').lower()
)
2016-10-11 17:59:09 +01:00
def get_human_day(time, prefix_today_with='T'):
# Add 1 hour to get midnight today instead of midnight tomorrow
time = (time - timedelta(hours=1)).strftime('%A')
if time == datetime.utcnow().strftime('%A'):
2016-10-11 17:59:09 +01:00
return '{}oday'.format(prefix_today_with)
if time == (datetime.utcnow() + timedelta(days=1)).strftime('%A'):
return 'Tomorrow'
return time
def get_furthest_possible_scheduled_time():
return (datetime.utcnow() + timedelta(days=4)).replace(hour=0)
def get_next_hours_until(until):
now = datetime.utcnow()
hours = int((until - now).total_seconds() / (60 * 60))
return [
(now + timedelta(hours=i)).replace(minute=0, second=0).replace(tzinfo=pytz.utc)
for i in range(1, hours + 1)
]
def get_next_days_until(until):
now = datetime.utcnow()
days = int((until - now).total_seconds() / (60 * 60 * 24))
return [
2016-10-11 17:59:09 +01:00
get_human_day(
(now + timedelta(days=i)).replace(tzinfo=pytz.utc),
prefix_today_with='Later t'
)
for i in range(0, days + 1)
]
def email_address(label='Email address', gov_user=True):
validators = [
2016-01-08 12:00:52 +00:00
Length(min=5, max=255),
2016-04-04 10:42:04 +01:00
DataRequired(message='Cant be empty'),
Email(message='Enter a valid email address')
]
if gov_user:
2016-10-28 10:45:05 +01:00
validators.append(ValidGovEmail())
return EmailField(label, validators)
2016-01-08 12:00:52 +00:00
2016-02-15 13:13:57 +00:00
class UKMobileNumber(TelField):
2016-01-12 18:10:16 +00:00
def pre_validate(self, form):
try:
2016-03-08 07:17:39 +00:00
validate_phone_number(self.data)
except InvalidPhoneError as e:
2016-12-20 14:38:34 +00:00
raise ValidationError(str(e))
2016-01-12 18:10:16 +00:00
class InternationalPhoneNumber(TelField):
def pre_validate(self, form):
try:
validate_phone_number(self.data, international=True)
except InvalidPhoneError as e:
raise ValidationError(str(e))
def mobile_number(label='Mobile number'):
return UKMobileNumber(label,
2016-04-04 10:42:04 +01:00
validators=[DataRequired(message='Cant be empty')])
2016-01-08 12:00:52 +00:00
def international_phone_number(label='Mobile number'):
return InternationalPhoneNumber(
label,
validators=[DataRequired(message='Cant be empty')]
)
def password(label='Password'):
return PasswordField(label,
2016-04-04 10:42:04 +01:00
validators=[DataRequired(message='Cant be empty'),
Length(8, 255, message='Must be at least 8 characters'),
Blacklist(message='Choose a password thats harder to guess')])
2016-01-08 12:00:52 +00:00
def sms_code():
verify_code = '^\d{5}$'
2016-02-02 15:41:54 +00:00
return StringField('Text message code',
2016-04-04 10:42:04 +01:00
validators=[DataRequired(message='Cant be empty'),
2016-01-08 12:00:52 +00:00
Regexp(regex=verify_code,
message='Code not found')])
2016-01-08 12:00:52 +00:00
class LoginForm(Form):
email_address = StringField('Email address', validators=[
Length(min=5, max=255),
2016-04-04 10:42:04 +01:00
DataRequired(message='Cant be empty'),
2016-01-08 12:00:52 +00:00
Email(message='Enter a valid email address')
])
2015-12-02 15:23:03 +00:00
password = PasswordField('Password', validators=[
2016-01-08 12:00:52 +00:00
DataRequired(message='Enter your password')
])
2015-12-01 13:23:54 +00:00
class RegisterUserForm(Form):
2015-12-02 15:23:03 +00:00
name = StringField('Full name',
2016-04-04 10:42:04 +01:00
validators=[DataRequired(message='Cant be empty')])
2016-01-08 12:00:52 +00:00
email_address = email_address()
mobile_number = mobile_number()
password = password()
class RegisterUserFromInviteForm(Form):
name = StringField('Full name',
2016-04-04 10:42:04 +01:00
validators=[DataRequired(message='Cant be empty')])
mobile_number = mobile_number()
password = password()
service = HiddenField('service')
email_address = HiddenField('email_address')
class PermissionsForm(Form):
send_messages = BooleanField("Send messages from existing templates")
2017-02-23 10:48:10 +00:00
manage_service = BooleanField("Modify this service, its team, and its templates")
manage_api_keys = BooleanField("Create and revoke API keys")
class InviteUserForm(PermissionsForm):
email_address = email_address(gov_user=False)
def __init__(self, invalid_email_address, *args, **kwargs):
super(InviteUserForm, self).__init__(*args, **kwargs)
self.invalid_email_address = invalid_email_address.lower()
def validate_email_address(self, field):
if field.data.lower() == self.invalid_email_address:
2016-04-04 10:42:04 +01:00
raise ValidationError("You cant send an invitation to yourself")
class TwoFactorForm(Form):
def __init__(self, validate_code_func, *args, **kwargs):
'''
Keyword arguments:
validate_code_func -- Validates the code with the API.
'''
self.validate_code_func = validate_code_func
super(TwoFactorForm, self).__init__(*args, **kwargs)
2016-01-08 12:00:52 +00:00
sms_code = sms_code()
2015-12-08 12:36:54 +00:00
def validate_sms_code(self, field):
is_valid, reason = self.validate_code_func(field.data)
if not is_valid:
raise ValidationError(reason)
class EmailNotReceivedForm(Form):
2016-01-08 12:00:52 +00:00
email_address = email_address()
class TextNotReceivedForm(Form):
2016-01-08 12:00:52 +00:00
mobile_number = mobile_number()
2015-12-14 17:12:28 +00:00
class AddServiceForm(Form):
2016-01-15 16:10:24 +00:00
def __init__(self, names_func, *args, **kwargs):
"""
Keyword arguments:
names_func -- Returns a list of unique service_names already registered
on the system.
"""
self._names_func = names_func
super(AddServiceForm, self).__init__(*args, **kwargs)
2016-01-19 09:49:01 +00:00
name = StringField(
2016-01-18 10:47:53 +00:00
'Service name',
validators=[
2016-04-04 10:42:04 +01:00
DataRequired(message='Cant be empty')
2016-01-18 10:47:53 +00:00
]
)
2016-01-18 17:35:28 +00:00
def validate_name(self, a):
from app.utils import email_safe
# make sure the email_from will be unique to all services
if email_safe(a.data) in self._names_func():
2016-02-26 10:46:49 +00:00
raise ValidationError('This service name is already in use')
2016-01-04 14:00:39 +00:00
2016-01-11 13:15:10 +00:00
class ServiceNameForm(Form):
def __init__(self, names_func, *args, **kwargs):
"""
Keyword arguments:
names_func -- Returns a list of unique service_names already registered
on the system.
"""
self._names_func = names_func
super(ServiceNameForm, self).__init__(*args, **kwargs)
name = StringField(
2016-06-20 13:33:29 +01:00
u'Service name',
validators=[
2016-04-04 10:42:04 +01:00
DataRequired(message='Cant be empty')
])
def validate_name(self, a):
from app.utils import email_safe
# make sure the email_from will be unique to all services
if email_safe(a.data) in self._names_func():
raise ValidationError('This service name is already in use')
2016-01-11 13:15:10 +00:00
class ConfirmPasswordForm(Form):
def __init__(self, validate_password_func, *args, **kwargs):
self.validate_password_func = validate_password_func
super(ConfirmPasswordForm, self).__init__(*args, **kwargs)
2016-01-11 13:15:10 +00:00
password = PasswordField(u'Enter password')
def validate_password(self, field):
if not self.validate_password_func(field.data):
raise ValidationError('Invalid password')
2016-01-11 13:15:10 +00:00
2017-02-15 15:06:47 +00:00
class BaseTemplateForm(Form):
name = StringField(
u'Template name',
2016-04-04 10:42:04 +01:00
validators=[DataRequired(message="Cant be empty")])
2016-01-22 12:15:47 +00:00
template_content = TextAreaField(
u'Message',
2016-04-07 16:02:06 +01:00
validators=[
DataRequired(message="Cant be empty"),
2017-02-15 15:06:47 +00:00
NoCommasInPlaceHolders()
2016-04-07 16:02:06 +01:00
]
)
process_type = RadioField(
'Use priority queue?',
choices=[
('priority', 'Yes'),
('normal', 'No'),
],
validators=[DataRequired()],
default='normal'
)
2016-01-11 13:15:10 +00:00
2017-02-15 15:06:47 +00:00
class SMSTemplateForm(BaseTemplateForm):
def validate_template_content(self, field):
OnlyGSMCharacters()(None, field)
class EmailTemplateForm(BaseTemplateForm):
2016-04-14 14:04:41 +01:00
subject = TextAreaField(
u'Subject',
2016-04-04 10:42:04 +01:00
validators=[DataRequired(message="Cant be empty")])
2016-11-08 13:12:07 +00:00
class LetterTemplateForm(EmailTemplateForm):
subject = TextAreaField(
2017-05-10 11:27:45 +01:00
u'Main heading',
validators=[DataRequired(message="Cant be empty")])
template_content = TextAreaField(
u'Body',
validators=[
DataRequired(message="Cant be empty"),
NoCommasInPlaceHolders()
]
)
2016-11-08 13:12:07 +00:00
2016-01-05 17:52:09 +00:00
class ForgotPasswordForm(Form):
email_address = email_address(gov_user=False)
2016-01-04 14:00:39 +00:00
2016-01-06 17:37:07 +00:00
class NewPasswordForm(Form):
2016-01-08 12:00:52 +00:00
new_password = password()
class ChangePasswordForm(Form):
def __init__(self, validate_password_func, *args, **kwargs):
self.validate_password_func = validate_password_func
super(ChangePasswordForm, self).__init__(*args, **kwargs)
old_password = password('Current password')
new_password = password('New password')
def validate_old_password(self, field):
if not self.validate_password_func(field.data):
raise ValidationError('Invalid password')
class CsvUploadForm(Form):
2016-02-04 12:20:24 +00:00
file = FileField('Add recipients', validators=[DataRequired(
2016-05-11 09:43:55 +01:00
message='Please pick a file'), CsvFileValidator()])
class ChangeNameForm(Form):
new_name = StringField(u'Your name')
class ChangeEmailForm(Form):
def __init__(self, validate_email_func, *args, **kwargs):
self.validate_email_func = validate_email_func
super(ChangeEmailForm, self).__init__(*args, **kwargs)
email_address = email_address()
def validate_email_address(self, field):
is_valid = self.validate_email_func(field.data)
if not is_valid:
raise ValidationError("The email address is already in use")
class ChangeMobileNumberForm(Form):
mobile_number = mobile_number()
class ConfirmMobileNumberForm(Form):
def __init__(self, validate_code_func, *args, **kwargs):
self.validate_code_func = validate_code_func
super(ConfirmMobileNumberForm, self).__init__(*args, **kwargs)
sms_code = sms_code()
2016-01-19 09:55:13 +00:00
def validate_sms_code(self, field):
is_valid, msg = self.validate_code_func(field.data)
if not is_valid:
raise ValidationError(msg)
2016-01-19 09:55:13 +00:00
class ChooseTimeForm(Form):
def __init__(self, *args, **kwargs):
super(ChooseTimeForm, self).__init__(*args, **kwargs)
self.scheduled_for.choices = [('', 'Now')] + [
get_time_value_and_label(hour) for hour in get_next_hours_until(
get_furthest_possible_scheduled_time()
)
]
2016-10-11 14:17:29 +01:00
self.scheduled_for.categories = get_next_days_until(get_furthest_possible_scheduled_time())
scheduled_for = RadioField(
'When should Notify send these messages?',
default='',
validators=[
DataRequired()
]
)
2016-01-19 09:55:13 +00:00
class CreateKeyForm(Form):
def __init__(self, existing_key_names=[], *args, **kwargs):
2016-01-21 16:52:01 +00:00
self.existing_key_names = [x.lower() for x in existing_key_names]
super(CreateKeyForm, self).__init__(*args, **kwargs)
key_type = RadioField(
'Type of key',
validators=[
DataRequired()
]
)
key_name = StringField(u'Description of key', validators=[
DataRequired(message='You need to give the key a name')
])
def validate_key_name(self, key_name):
2016-01-21 16:52:01 +00:00
if key_name.data.lower() in self.existing_key_names:
raise ValidationError('A key with this name already exists')
2016-12-12 11:25:43 +00:00
class SupportType(Form):
support_type = RadioField(
'How can we help you?',
choices=[
('problem', 'Report a problem'),
('question', 'Ask a question or give feedback'),
],
validators=[DataRequired()]
)
class Feedback(Form):
name = StringField('Name')
email_address = StringField('Email address')
2016-12-12 11:25:43 +00:00
feedback = TextAreaField('Your message', validators=[DataRequired(message="Cant be empty")])
2016-04-26 13:31:57 +01:00
class Problem(Feedback):
email_address = email_address(label='Email address', gov_user=False)
class Triage(Form):
severe = RadioField(
'Is it an emergency?',
choices=[
('yes', 'Yes'),
('no', 'No'),
],
validators=[DataRequired()]
)
2016-04-26 13:31:57 +01:00
class RequestToGoLiveForm(Form):
mou = RadioField(
(
'Has your organisation accepted the GOV.UK Notify data sharing and financial '
'agreement (Memorandum of Understanding)?'
),
choices=[
('yes', 'Yes'),
('no', 'No'),
('dont know', 'I dont know')
],
validators=[DataRequired()]
)
channel = RadioField(
'What kind of messages will you be sending?',
choices=[
('emails', 'Emails'),
('text messages', 'Text messages'),
('emails and text messages', 'Both')
],
validators=[DataRequired()]
)
start_date = StringField(
2016-10-24 16:10:41 +01:00
'When will you be ready to start sending messages?',
validators=[DataRequired(message='Cant be empty')]
)
2016-10-24 13:38:55 +01:00
start_volume = StringField(
2016-11-01 11:30:03 +00:00
'How many messages do you expect to send to start with?',
2016-10-24 16:10:41 +01:00
validators=[DataRequired(message='Cant be empty')]
2016-10-24 13:38:55 +01:00
)
peak_volume = StringField(
2016-11-01 11:30:03 +00:00
'Will the number of messages increase and when will that start?',
2016-10-24 16:10:41 +01:00
validators=[DataRequired(message='Cant be empty')]
2016-10-24 13:38:55 +01:00
)
upload_or_api = RadioField(
'How are you going to send messages?',
choices=[
('File upload', 'Upload a spreadsheet of recipients'),
('API', 'Integrate with the GOV.UK Notify API'),
('API and file upload', 'Both')
],
validators=[DataRequired()]
2016-10-24 13:38:55 +01:00
)
2016-05-11 09:43:55 +01:00
class ProviderForm(Form):
priority = IntegerField('Priority', [validators.NumberRange(min=1, max=100, message="Must be between 1 and 100")])
class ServiceReplyToEmailFrom(Form):
email_address = email_address(label='Email reply to address')
class ServiceSmsSender(Form):
2016-08-22 16:10:57 +01:00
sms_sender = StringField(
'Text message sender',
validators=[
2017-05-19 17:12:44 +01:00
DataRequired(message="Cant be empty"),
2017-02-27 15:49:42 +00:00
Length(max=11, message="Enter 11 characters or fewer")
2016-08-22 16:10:57 +01:00
]
)
def validate_sms_sender(form, field):
2017-03-02 15:56:28 +00:00
if field.data and not re.match(r'^[a-zA-Z0-9\s]+$', field.data):
2016-08-22 16:10:57 +01:00
raise ValidationError('Use letters and numbers only')
2017-03-02 15:56:28 +00:00
class ServiceLetterContactBlock(Form):
2017-05-17 15:35:21 +01:00
letter_contact_block = TextAreaField(
validators=[
NoCommasInPlaceHolders()
]
)
2017-03-02 15:56:28 +00:00
def validate_letter_contact_block(form, field):
line_count = field.data.strip().count('\n')
if line_count >= 10:
raise ValidationError(
'Contains {} lines, maximum is 10'.format(line_count + 1)
)
2017-03-02 15:56:28 +00:00
class ServiceBrandingOrg(Form):
def __init__(self, organisations=[], *args, **kwargs):
self.organisation.choices = organisations
super(ServiceBrandingOrg, self).__init__(*args, **kwargs)
branding_type = RadioField(
'Branding',
choices=[
('govuk', 'GOV.UK only'),
('both', 'GOV.UK and organisation'),
('org', 'Organisation only')
],
validators=[
DataRequired()
]
)
organisation = RadioField(
'Organisation',
validators=[
DataRequired()
]
)
class LetterBranding(Form):
def __init__(self, choices=[], *args, **kwargs):
super().__init__(*args, **kwargs)
self.dvla_org_id.choices = choices
dvla_org_id = RadioField(
'Which logo should this services letter have?',
validators=[
DataRequired()
]
)
class Whitelist(Form):
def populate(self, email_addresses, phone_numbers):
for form_field, existing_whitelist in (
(self.email_addresses, email_addresses),
(self.phone_numbers, phone_numbers)
):
for index, value in enumerate(existing_whitelist):
form_field[index].data = value
email_addresses = FieldList(
EmailField(
'',
validators=[
Optional(),
Email(message='Enter valid email addresses')
],
default=''
),
min_entries=5,
max_entries=5,
label="Email addresses"
)
phone_numbers = FieldList(
UKMobileNumber(
'',
validators=[
Optional()
],
default=''
),
min_entries=5,
max_entries=5,
label="Mobile numbers"
)
2017-01-03 10:45:06 +00:00
2017-01-03 16:14:25 +00:00
2017-01-03 10:45:06 +00:00
class DateFilterForm(Form):
start_date = DateField("Start Date", [validators.optional()])
2017-01-03 16:14:25 +00:00
end_date = DateField("End Date", [validators.optional()])
include_from_test_key = BooleanField("Include test keys", default="checked", false_values={"N"})
class ChooseTemplateType(Form):
template_type = RadioField(
'What kind of template do you want to add?',
validators=[
DataRequired()
]
)
def __init__(self, include_letters=False, *args, **kwargs):
super().__init__(*args, **kwargs)
self.template_type.choices = filter(None, [
('email', 'Email'),
('sms', 'Text message'),
('letter', 'Letter') if include_letters else None
])
class SearchTemplatesForm(Form):
search = SearchField('Search by name')
class SearchNotificationsForm(Form):
to = SearchField('Search by phone number or email address')
class PlaceholderForm(Form):
pass
def get_placeholder_form_instance(
placeholder_name,
dict_to_populate_from,
optional_placeholder=False,
allow_international_phone_numbers=False,
):
if Columns.make_key(placeholder_name) == 'emailaddress':
field = email_address(label=placeholder_name, gov_user=False)
elif Columns.make_key(placeholder_name) == 'phonenumber':
if allow_international_phone_numbers:
field = international_phone_number(label=placeholder_name)
else:
field = mobile_number(label=placeholder_name)
elif optional_placeholder:
field = StringField(placeholder_name)
else:
field = StringField(placeholder_name, validators=[
DataRequired(message='Cant be empty')
])
PlaceholderForm.placeholder_value = field
return PlaceholderForm(
placeholder_value=dict_to_populate_from.get(placeholder_name, '')
)