2017-12-11 16:02:12 +00:00
|
|
|
|
import weakref
|
2016-08-07 09:17:49 +01:00
|
|
|
|
from datetime import datetime, timedelta
|
2017-12-11 16:22:37 +00:00
|
|
|
|
from itertools import chain
|
2017-08-07 11:30:25 +01:00
|
|
|
|
|
2018-02-20 11:22:17 +00:00
|
|
|
|
import pytz
|
2020-04-02 18:06:01 +01:00
|
|
|
|
from flask import Markup, render_template, request
|
2020-03-06 11:53:55 +00:00
|
|
|
|
from flask_login import current_user
|
2018-02-20 11:22:17 +00:00
|
|
|
|
from flask_wtf import FlaskForm as Form
|
|
|
|
|
|
from flask_wtf.file import FileAllowed
|
|
|
|
|
|
from flask_wtf.file import FileField as FileField_wtf
|
2017-05-25 09:40:37 +01:00
|
|
|
|
from notifications_utils.columns import Columns
|
2020-05-20 11:12:33 +01:00
|
|
|
|
from notifications_utils.countries.data import Postage
|
2020-04-02 17:21:27 +01:00
|
|
|
|
from notifications_utils.formatters import strip_whitespace
|
|
|
|
|
|
from notifications_utils.postal_address import PostalAddress
|
2018-02-20 11:22:17 +00:00
|
|
|
|
from notifications_utils.recipients import (
|
|
|
|
|
|
InvalidPhoneError,
|
2018-08-09 11:59:05 +01:00
|
|
|
|
normalise_phone_number,
|
2018-02-20 11:22:17 +00:00
|
|
|
|
validate_phone_number,
|
|
|
|
|
|
)
|
2020-04-23 12:15:24 +01:00
|
|
|
|
from werkzeug.utils import cached_property
|
2016-01-12 10:43:23 +00:00
|
|
|
|
from wtforms import (
|
2016-03-02 15:25:04 +00:00
|
|
|
|
BooleanField,
|
2018-02-20 11:22:17 +00:00
|
|
|
|
DateField,
|
|
|
|
|
|
FieldList,
|
|
|
|
|
|
FileField,
|
2016-05-11 09:43:55 +01:00
|
|
|
|
HiddenField,
|
2016-06-29 17:10:49 +01:00
|
|
|
|
IntegerField,
|
2018-02-20 11:22:17 +00:00
|
|
|
|
PasswordField,
|
2020-04-22 17:49:03 +01:00
|
|
|
|
)
|
|
|
|
|
|
from wtforms import RadioField as WTFormsRadioField
|
|
|
|
|
|
from wtforms import (
|
2018-11-08 11:56:29 +00:00
|
|
|
|
SelectMultipleField,
|
2018-02-20 11:22:17 +00:00
|
|
|
|
StringField,
|
|
|
|
|
|
TextAreaField,
|
|
|
|
|
|
ValidationError,
|
|
|
|
|
|
validators,
|
2017-10-18 14:51:26 +01:00
|
|
|
|
)
|
2018-02-20 11:22:17 +00:00
|
|
|
|
from wtforms.fields.html5 import EmailField, SearchField, TelField
|
2018-06-05 10:37:41 +01:00
|
|
|
|
from wtforms.validators import URL, DataRequired, Length, Optional, Regexp
|
2016-01-11 15:00:51 +00:00
|
|
|
|
|
2019-07-08 14:09:29 +01:00
|
|
|
|
from app import format_thousands
|
2018-01-31 10:26:37 +00:00
|
|
|
|
from app.main.validators import (
|
2020-06-16 18:04:29 +01:00
|
|
|
|
CommonlyUsedPassword,
|
2018-01-31 10:26:37 +00:00
|
|
|
|
CsvFileValidator,
|
2018-02-28 11:50:41 +00:00
|
|
|
|
DoesNotStartWithDoubleZero,
|
2020-04-02 15:57:46 +01:00
|
|
|
|
LettersNumbersFullStopsAndUnderscoresOnly,
|
2019-11-08 17:12:32 +00:00
|
|
|
|
MustContainAlphanumericCharacters,
|
2018-01-31 10:26:37 +00:00
|
|
|
|
NoCommasInPlaceHolders,
|
2020-03-04 14:25:14 +00:00
|
|
|
|
NoEmbeddedImagesInSVG,
|
2019-05-03 15:13:39 +01:00
|
|
|
|
OnlySMSCharacters,
|
2018-02-20 11:22:17 +00:00
|
|
|
|
ValidEmail,
|
|
|
|
|
|
ValidGovEmail,
|
2018-01-31 10:26:37 +00:00
|
|
|
|
)
|
2020-03-24 15:36:39 +00:00
|
|
|
|
from app.models.feedback import PROBLEM_TICKET_TYPE, QUESTION_TICKET_TYPE
|
2019-08-27 15:52:42 +01:00
|
|
|
|
from app.models.organisation import Organisation
|
2020-07-13 11:10:34 +01:00
|
|
|
|
from app.models.roles_and_permissions import (
|
|
|
|
|
|
broadcast_permissions,
|
|
|
|
|
|
permissions,
|
|
|
|
|
|
roles,
|
|
|
|
|
|
)
|
2020-08-13 12:38:12 +01:00
|
|
|
|
from app.utils import guess_name_from_email_address, merge_jsonlike
|
2016-02-01 16:57:40 +00:00
|
|
|
|
|
2015-11-27 09:47:29 +00:00
|
|
|
|
|
2016-08-07 09:17:49 +01:00
|
|
|
|
def get_time_value_and_label(future_time):
|
|
|
|
|
|
return (
|
|
|
|
|
|
future_time.replace(tzinfo=None).isoformat(),
|
2016-10-11 14:11:10 +01:00
|
|
|
|
'{} at {}'.format(
|
|
|
|
|
|
get_human_day(future_time.astimezone(pytz.timezone('Europe/London'))),
|
|
|
|
|
|
get_human_time(future_time.astimezone(pytz.timezone('Europe/London')))
|
|
|
|
|
|
)
|
2016-08-07 09:17:49 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_human_time(time):
|
|
|
|
|
|
return {
|
2016-10-11 14:11:10 +01:00
|
|
|
|
'0': 'midnight',
|
|
|
|
|
|
'12': 'midday'
|
2016-08-07 09:17:49 +01:00
|
|
|
|
}.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'):
|
2016-10-11 14:11:10 +01:00
|
|
|
|
# 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)
|
2016-10-11 14:11:10 +01:00
|
|
|
|
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))
|
2016-08-07 09:17:49 +01:00
|
|
|
|
return [
|
2020-07-16 10:49:21 +01:00
|
|
|
|
(now + timedelta(hours=i)).replace(minute=0, second=0, microsecond=0).replace(tzinfo=pytz.utc)
|
2016-08-07 09:17:49 +01:00
|
|
|
|
for i in range(1, hours + 1)
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-10-11 14:11:10 +01:00
|
|
|
|
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'
|
|
|
|
|
|
)
|
2016-10-11 14:11:10 +01:00
|
|
|
|
for i in range(0, days + 1)
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-04-22 17:49:03 +01:00
|
|
|
|
class RadioField(WTFormsRadioField):
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
|
self,
|
|
|
|
|
|
*args,
|
|
|
|
|
|
thing='an option',
|
|
|
|
|
|
**kwargs
|
|
|
|
|
|
):
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
self.thing = thing
|
|
|
|
|
|
self.validate_choice = False
|
|
|
|
|
|
|
|
|
|
|
|
def pre_validate(self, form):
|
|
|
|
|
|
super().pre_validate(form)
|
|
|
|
|
|
if self.data not in dict(self.choices).keys():
|
|
|
|
|
|
raise ValidationError(f'Select {self.thing}')
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-12-07 12:23:12 +00:00
|
|
|
|
def email_address(label='Email address', gov_user=True, required=True):
|
|
|
|
|
|
|
2016-10-26 14:01:01 +01:00
|
|
|
|
validators = [
|
2018-12-07 12:23:12 +00:00
|
|
|
|
ValidEmail(),
|
2016-10-26 14:01:01 +01:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
if gov_user:
|
2016-10-28 10:45:05 +01:00
|
|
|
|
validators.append(ValidGovEmail())
|
2018-12-07 12:23:12 +00:00
|
|
|
|
|
|
|
|
|
|
if required:
|
2019-09-12 16:42:33 +01:00
|
|
|
|
validators.append(DataRequired(message='Cannot be empty'))
|
2018-12-07 12:23:12 +00:00
|
|
|
|
|
2020-08-13 10:53:28 +01:00
|
|
|
|
return GovukEmailField(label, validators)
|
2016-01-08 12:00:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
2016-02-15 13:13:57 +00:00
|
|
|
|
class UKMobileNumber(TelField):
|
2020-08-07 13:46:15 +01:00
|
|
|
|
def __init__(self, label='', validators=None, param_extensions=None, **kwargs):
|
|
|
|
|
|
super(UKMobileNumber, self).__init__(label, validators, **kwargs)
|
|
|
|
|
|
self.param_extensions = param_extensions
|
|
|
|
|
|
|
|
|
|
|
|
# self.__call__ renders the HTML for the field by:
|
|
|
|
|
|
# 1. delegating to self.meta.render_field which
|
|
|
|
|
|
# 2. calls field.widget
|
|
|
|
|
|
# this bypasses that by making self.widget a method with the same interface as widget.__call__
|
|
|
|
|
|
def widget(self, field, param_extensions=None, **kwargs):
|
|
|
|
|
|
return govuk_field_widget(self, field, type="tel", param_extensions=param_extensions, **kwargs)
|
|
|
|
|
|
|
2016-01-12 18:10:16 +00:00
|
|
|
|
def pre_validate(self, form):
|
2016-02-01 16:57:40 +00:00
|
|
|
|
try:
|
2016-03-08 07:17:39 +00:00
|
|
|
|
validate_phone_number(self.data)
|
2016-02-01 16:57:40 +00:00
|
|
|
|
except InvalidPhoneError as e:
|
2016-12-20 14:38:34 +00:00
|
|
|
|
raise ValidationError(str(e))
|
2016-01-12 18:10:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-05-25 09:40:37 +01:00
|
|
|
|
class InternationalPhoneNumber(TelField):
|
2020-08-07 13:46:15 +01:00
|
|
|
|
def __init__(self, label='', validators=None, param_extensions=None, **kwargs):
|
|
|
|
|
|
super(InternationalPhoneNumber, self).__init__(label, validators=validators, **kwargs)
|
|
|
|
|
|
self.param_extensions = param_extensions
|
|
|
|
|
|
|
|
|
|
|
|
# self.__call__ renders the HTML for the field by:
|
|
|
|
|
|
# 1. delegating to self.meta.render_field which
|
|
|
|
|
|
# 2. calls field.widget
|
|
|
|
|
|
# this bypasses that by making self.widget a method with the same interface as widget.__call__
|
|
|
|
|
|
def widget(self, field, param_extensions=None, **kwargs):
|
|
|
|
|
|
return govuk_field_widget(self, field, type="tel", param_extensions=param_extensions, **kwargs)
|
|
|
|
|
|
|
2017-05-25 09:40:37 +01:00
|
|
|
|
def pre_validate(self, form):
|
|
|
|
|
|
try:
|
2017-11-10 12:35:21 +00:00
|
|
|
|
if self.data:
|
|
|
|
|
|
validate_phone_number(self.data, international=True)
|
2017-05-25 09:40:37 +01:00
|
|
|
|
except InvalidPhoneError as e:
|
|
|
|
|
|
raise ValidationError(str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-08-29 14:52:24 +01:00
|
|
|
|
def uk_mobile_number(label='Mobile number'):
|
2017-05-25 09:40:37 +01:00
|
|
|
|
return UKMobileNumber(label,
|
2019-09-12 16:42:33 +01:00
|
|
|
|
validators=[DataRequired(message='Cannot be empty')])
|
2016-01-08 12:00:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-05-25 09:40:37 +01:00
|
|
|
|
def international_phone_number(label='Mobile number'):
|
|
|
|
|
|
return InternationalPhoneNumber(
|
|
|
|
|
|
label,
|
2019-09-12 16:42:33 +01:00
|
|
|
|
validators=[DataRequired(message='Cannot be empty')]
|
2017-05-25 09:40:37 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-07 14:12:40 +01:00
|
|
|
|
def password(label='Password'):
|
2020-08-07 09:53:09 +01:00
|
|
|
|
return GovukPasswordField(
|
|
|
|
|
|
label,
|
|
|
|
|
|
validators=[
|
|
|
|
|
|
DataRequired(message='Cannot be empty'),
|
|
|
|
|
|
Length(8, 255, message='Must be at least 8 characters'),
|
|
|
|
|
|
CommonlyUsedPassword(message='Choose a password that’s harder to guess')
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
2016-01-08 12:00:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
2020-07-22 16:29:11 +01:00
|
|
|
|
def govuk_field_widget(self, field, type=None, param_extensions=None, **kwargs):
|
2020-08-07 11:36:45 +01:00
|
|
|
|
value = kwargs["value"] if kwargs.get("value") else field.data
|
|
|
|
|
|
|
2020-07-22 16:29:11 +01:00
|
|
|
|
# error messages
|
|
|
|
|
|
error_message = None
|
|
|
|
|
|
if field.errors:
|
2020-08-07 15:12:10 +01:00
|
|
|
|
error_message_format = "html" if kwargs.get("error_message_with_html") else "text"
|
2020-07-28 11:44:53 +01:00
|
|
|
|
error_message = {
|
|
|
|
|
|
"attributes": {
|
|
|
|
|
|
"data-module": "track-error",
|
|
|
|
|
|
"data-error-type": field.errors[0],
|
|
|
|
|
|
"data-error-label": field.name
|
|
|
|
|
|
},
|
|
|
|
|
|
error_message_format: " ".join(field.errors).strip()
|
|
|
|
|
|
}
|
2020-07-22 16:29:11 +01:00
|
|
|
|
|
|
|
|
|
|
# convert to parameters that govuk understands
|
|
|
|
|
|
params = {
|
|
|
|
|
|
"classes": "govuk-!-width-two-thirds",
|
|
|
|
|
|
"errorMessage": error_message,
|
|
|
|
|
|
"id": field.id,
|
|
|
|
|
|
"label": {"text": field.label.text},
|
|
|
|
|
|
"name": field.name,
|
2020-08-07 11:36:45 +01:00
|
|
|
|
"value": value
|
2020-07-22 16:29:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if type:
|
|
|
|
|
|
params["type"] = type
|
|
|
|
|
|
|
|
|
|
|
|
# extend default params with any sent in
|
2020-08-13 12:38:12 +01:00
|
|
|
|
merge_jsonlike(params, self.param_extensions)
|
|
|
|
|
|
# add any sent in through use in templates
|
|
|
|
|
|
merge_jsonlike(params, param_extensions)
|
2020-07-22 16:29:11 +01:00
|
|
|
|
|
|
|
|
|
|
return Markup(
|
|
|
|
|
|
render_template('vendor/govuk-frontend/components/input/template.njk', params=params))
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-07-14 16:24:40 +01:00
|
|
|
|
class GovukTextInputField(StringField):
|
|
|
|
|
|
def __init__(self, label='', validators=None, param_extensions=None, **kwargs):
|
|
|
|
|
|
super(GovukTextInputField, self).__init__(label, validators, **kwargs)
|
|
|
|
|
|
self.param_extensions = param_extensions
|
|
|
|
|
|
|
|
|
|
|
|
# self.__call__ renders the HTML for the field by:
|
|
|
|
|
|
# 1. delegating to self.meta.render_field which
|
|
|
|
|
|
# 2. calls field.widget
|
|
|
|
|
|
# this bypasses that by making self.widget a method with the same interface as widget.__call__
|
|
|
|
|
|
def widget(self, field, **kwargs):
|
2020-07-22 16:29:11 +01:00
|
|
|
|
return govuk_field_widget(self, field, **kwargs)
|
2020-07-14 16:24:40 +01:00
|
|
|
|
|
|
|
|
|
|
|
2020-08-07 09:49:07 +01:00
|
|
|
|
class GovukPasswordField(PasswordField):
|
|
|
|
|
|
def __init__(self, label='', validators=None, param_extensions=None, **kwargs):
|
|
|
|
|
|
super(GovukPasswordField, self).__init__(label, validators, **kwargs)
|
|
|
|
|
|
self.param_extensions = param_extensions
|
|
|
|
|
|
|
|
|
|
|
|
# self.__call__ renders the HTML for the field by:
|
|
|
|
|
|
# 1. delegating to self.meta.render_field which
|
|
|
|
|
|
# 2. calls field.widget
|
|
|
|
|
|
# this bypasses that by making self.widget a method with the same interface as widget.__call__
|
|
|
|
|
|
def widget(self, field, param_extensions=None, **kwargs):
|
2020-07-22 16:29:11 +01:00
|
|
|
|
return govuk_field_widget(self, field, type="password", param_extensions=param_extensions, **kwargs)
|
2020-08-07 09:49:07 +01:00
|
|
|
|
|
|
|
|
|
|
|
2020-08-07 10:07:52 +01:00
|
|
|
|
class GovukEmailField(EmailField):
|
|
|
|
|
|
def __init__(self, label='', validators=None, param_extensions=None, **kwargs):
|
|
|
|
|
|
super(GovukEmailField, self).__init__(label, validators=validators, **kwargs)
|
|
|
|
|
|
self.param_extensions = param_extensions
|
|
|
|
|
|
|
|
|
|
|
|
# self.__call__ renders the HTML for the field by:
|
|
|
|
|
|
# 1. delegating to self.meta.render_field which
|
|
|
|
|
|
# 2. calls field.widget
|
|
|
|
|
|
# this bypasses that by making self.widget a method with the same interface as widget.__call__
|
|
|
|
|
|
def widget(self, field, param_extensions=None, **kwargs):
|
|
|
|
|
|
|
|
|
|
|
|
params = {"attributes": {"spellcheck": "false"}} # email addresses don't need to be spellchecked
|
2020-08-13 12:38:12 +01:00
|
|
|
|
merge_jsonlike(params, param_extensions)
|
|
|
|
|
|
|
2020-08-07 10:07:52 +01:00
|
|
|
|
return govuk_field_widget(self, field, type="email", param_extensions=params, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-08-07 10:15:49 +01:00
|
|
|
|
class GovukSearchField(SearchField):
|
|
|
|
|
|
def __init__(self, label='', validators=None, param_extensions=None, **kwargs):
|
|
|
|
|
|
super(GovukSearchField, self).__init__(label, validators, **kwargs)
|
|
|
|
|
|
self.param_extensions = param_extensions
|
|
|
|
|
|
|
|
|
|
|
|
# self.__call__ renders the HTML for the field by:
|
|
|
|
|
|
# 1. delegating to self.meta.render_field which
|
|
|
|
|
|
# 2. calls field.widget
|
|
|
|
|
|
# this bypasses that by making self.widget a method with the same interface as widget.__call__
|
|
|
|
|
|
def widget(self, field, param_extensions=None, **kwargs):
|
|
|
|
|
|
|
|
|
|
|
|
params = {"classes": "govuk-!-width-full"} # email addresses don't need to be spellchecked
|
2020-08-13 12:38:12 +01:00
|
|
|
|
merge_jsonlike(params, param_extensions)
|
|
|
|
|
|
|
2020-08-07 10:15:49 +01:00
|
|
|
|
return govuk_field_widget(self, field, type="search", param_extensions=params, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-08-07 10:24:34 +01:00
|
|
|
|
class GovukDateField(DateField):
|
|
|
|
|
|
def __init__(self, label='', validators=None, param_extensions=None, **kwargs):
|
|
|
|
|
|
super(GovukDateField, self).__init__(label, validators, **kwargs)
|
|
|
|
|
|
self.param_extensions = param_extensions
|
|
|
|
|
|
|
|
|
|
|
|
# self.__call__ renders the HTML for the field by:
|
|
|
|
|
|
# 1. delegating to self.meta.render_field which
|
|
|
|
|
|
# 2. calls field.widget
|
|
|
|
|
|
# this bypasses that by making self.widget a method with the same interface as widget.__call__
|
|
|
|
|
|
def widget(self, field, param_extensions=None, **kwargs):
|
|
|
|
|
|
return govuk_field_widget(self, field, param_extensions=param_extensions, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-08-07 10:30:46 +01:00
|
|
|
|
class GovukIntegerField(IntegerField):
|
|
|
|
|
|
def __init__(self, label='', validators=None, param_extensions=None, **kwargs):
|
|
|
|
|
|
super(GovukIntegerField, self).__init__(label, validators, **kwargs)
|
|
|
|
|
|
self.param_extensions = param_extensions
|
|
|
|
|
|
|
|
|
|
|
|
# self.__call__ renders the HTML for the field by:
|
|
|
|
|
|
# 1. delegating to self.meta.render_field which
|
|
|
|
|
|
# 2. calls field.widget
|
|
|
|
|
|
# this bypasses that by making self.widget a method with the same interface as widget.__call__
|
|
|
|
|
|
def widget(self, field, param_extensions=None, **kwargs):
|
|
|
|
|
|
return govuk_field_widget(self, field, param_extensions=param_extensions, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-08-03 18:12:52 +01:00
|
|
|
|
class SMSCode(GovukTextInputField):
|
2018-05-07 22:57:18 +01:00
|
|
|
|
validators = [
|
2019-09-12 16:42:33 +01:00
|
|
|
|
DataRequired(message='Cannot be empty'),
|
2018-11-13 10:49:35 +00:00
|
|
|
|
Regexp(regex=r'^\d+$', message='Numbers only'),
|
2018-05-07 21:24:23 +01:00
|
|
|
|
Length(min=5, message='Not enough numbers'),
|
|
|
|
|
|
Length(max=5, message='Too many numbers'),
|
2018-05-07 22:57:18 +01:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def __call__(self, **kwargs):
|
2020-08-03 18:12:52 +01:00
|
|
|
|
params = {"attributes": {"pattern": "[0-9]*"}}
|
|
|
|
|
|
if "param_extensions" in kwargs:
|
2020-08-13 12:38:12 +01:00
|
|
|
|
merge_jsonlike(kwargs["param_extensions"], params)
|
|
|
|
|
|
|
2020-08-03 18:12:52 +01:00
|
|
|
|
return super().__call__(type='tel', **kwargs)
|
2016-01-08 12:00:52 +00:00
|
|
|
|
|
2020-04-17 16:16:52 +01:00
|
|
|
|
def process_formdata(self, valuelist):
|
|
|
|
|
|
if valuelist:
|
|
|
|
|
|
self.data = Columns.make_key(valuelist[0])
|
|
|
|
|
|
|
2016-01-08 12:00:52 +00:00
|
|
|
|
|
2020-08-07 11:36:45 +01:00
|
|
|
|
class ForgivingIntegerField(GovukTextInputField):
|
2019-02-15 13:34:29 +00:00
|
|
|
|
|
2019-02-27 13:02:55 +00:00
|
|
|
|
# Actual value is 2147483647 but this is a scary looking arbitrary number
|
|
|
|
|
|
POSTGRES_MAX_INT = 2000000000
|
2019-02-15 13:34:29 +00:00
|
|
|
|
|
2019-02-27 15:10:34 +00:00
|
|
|
|
def __init__(
|
|
|
|
|
|
self,
|
|
|
|
|
|
label=None,
|
|
|
|
|
|
things='items',
|
|
|
|
|
|
format_error_suffix='',
|
|
|
|
|
|
**kwargs
|
|
|
|
|
|
):
|
2019-02-27 13:02:37 +00:00
|
|
|
|
self.things = things
|
2019-02-27 15:10:34 +00:00
|
|
|
|
self.format_error_suffix = format_error_suffix
|
2019-02-27 13:02:37 +00:00
|
|
|
|
super().__init__(label, **kwargs)
|
|
|
|
|
|
|
2019-02-15 13:34:29 +00:00
|
|
|
|
def process_formdata(self, valuelist):
|
|
|
|
|
|
|
|
|
|
|
|
if valuelist:
|
|
|
|
|
|
|
2019-02-27 12:05:28 +00:00
|
|
|
|
value = valuelist[0].replace(',', '').replace(' ', '')
|
2019-02-15 13:34:29 +00:00
|
|
|
|
|
|
|
|
|
|
try:
|
2019-02-27 12:05:28 +00:00
|
|
|
|
value = int(value)
|
2019-02-15 13:34:29 +00:00
|
|
|
|
except ValueError:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2019-02-27 12:05:28 +00:00
|
|
|
|
if value == '':
|
|
|
|
|
|
value = 0
|
2019-02-15 13:34:29 +00:00
|
|
|
|
|
2019-02-27 12:05:28 +00:00
|
|
|
|
return super().process_formdata([value])
|
2019-02-15 13:34:29 +00:00
|
|
|
|
|
|
|
|
|
|
def pre_validate(self, form):
|
|
|
|
|
|
|
|
|
|
|
|
if self.data:
|
|
|
|
|
|
error = None
|
|
|
|
|
|
try:
|
|
|
|
|
|
if int(self.data) > self.POSTGRES_MAX_INT:
|
2019-07-08 14:09:29 +01:00
|
|
|
|
error = 'Number of {} must be {} or less'.format(
|
2019-02-27 13:02:37 +00:00
|
|
|
|
self.things,
|
2019-07-08 14:09:29 +01:00
|
|
|
|
format_thousands(self.POSTGRES_MAX_INT),
|
2019-02-27 13:02:37 +00:00
|
|
|
|
)
|
2019-02-15 13:34:29 +00:00
|
|
|
|
except ValueError:
|
2019-02-27 15:10:34 +00:00
|
|
|
|
error = 'Enter the number of {} {}'.format(
|
|
|
|
|
|
self.things,
|
|
|
|
|
|
self.format_error_suffix,
|
|
|
|
|
|
)
|
2019-02-15 13:34:29 +00:00
|
|
|
|
|
|
|
|
|
|
if error:
|
|
|
|
|
|
raise ValidationError(error)
|
|
|
|
|
|
|
|
|
|
|
|
return super().pre_validate(form)
|
|
|
|
|
|
|
|
|
|
|
|
def __call__(self, **kwargs):
|
|
|
|
|
|
|
2019-02-27 12:05:28 +00:00
|
|
|
|
if self.get_form().is_submitted() and not self.get_form().validate():
|
|
|
|
|
|
return super().__call__(
|
|
|
|
|
|
value=(self.raw_data or [None])[0],
|
|
|
|
|
|
**kwargs
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2019-02-15 13:34:29 +00:00
|
|
|
|
try:
|
|
|
|
|
|
value = int(self.data)
|
2019-07-08 14:09:29 +01:00
|
|
|
|
value = format_thousands(value)
|
2019-02-15 13:34:29 +00:00
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
|
|
value = self.data if self.data is not None else ''
|
|
|
|
|
|
|
|
|
|
|
|
return super().__call__(value=value, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-08-27 15:52:42 +01:00
|
|
|
|
class OrganisationTypeField(RadioField):
|
|
|
|
|
|
def __init__(
|
|
|
|
|
|
self,
|
|
|
|
|
|
*args,
|
|
|
|
|
|
include_only=None,
|
|
|
|
|
|
validators=None,
|
|
|
|
|
|
**kwargs
|
|
|
|
|
|
):
|
|
|
|
|
|
super().__init__(
|
|
|
|
|
|
*args,
|
|
|
|
|
|
choices=[
|
|
|
|
|
|
(value, label) for value, label in Organisation.TYPES
|
|
|
|
|
|
if not include_only or value in include_only
|
|
|
|
|
|
],
|
2020-04-22 17:49:03 +01:00
|
|
|
|
thing='the type of organisation',
|
|
|
|
|
|
validators=validators or [],
|
2019-08-27 15:52:42 +01:00
|
|
|
|
**kwargs
|
|
|
|
|
|
)
|
2017-10-05 12:06:40 +01:00
|
|
|
|
|
|
|
|
|
|
|
2019-02-25 16:17:48 +00:00
|
|
|
|
class FieldWithNoneOption():
|
|
|
|
|
|
|
|
|
|
|
|
# This is a special value that is specific to our forms. This is
|
|
|
|
|
|
# more expicit than casting `None` to a string `'None'` which can
|
|
|
|
|
|
# have unexpected edge cases
|
|
|
|
|
|
NONE_OPTION_VALUE = '__NONE__'
|
|
|
|
|
|
|
|
|
|
|
|
# When receiving Python data, eg when instantiating the form object
|
|
|
|
|
|
# we want to convert that data to our special value, so that it gets
|
|
|
|
|
|
# recognised as being one of the valid choices
|
|
|
|
|
|
def process_data(self, value):
|
|
|
|
|
|
self.data = self.NONE_OPTION_VALUE if value is None else value
|
|
|
|
|
|
|
|
|
|
|
|
# After validation we want to convert it back to a Python `None` for
|
|
|
|
|
|
# use elsewhere, eg posting to the API
|
|
|
|
|
|
def post_validate(self, form, validation_stopped):
|
|
|
|
|
|
if self.data == self.NONE_OPTION_VALUE and not validation_stopped:
|
|
|
|
|
|
self.data = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RadioFieldWithNoneOption(FieldWithNoneOption, RadioField):
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NestedFieldMixin:
|
2020-04-23 12:15:24 +01:00
|
|
|
|
|
2019-02-25 16:17:48 +00:00
|
|
|
|
def children(self):
|
2020-04-23 12:15:24 +01:00
|
|
|
|
|
2019-02-25 16:17:48 +00:00
|
|
|
|
# start map with root option as a single child entry
|
|
|
|
|
|
child_map = {None: [option for option in self
|
|
|
|
|
|
if option.data == self.NONE_OPTION_VALUE]}
|
|
|
|
|
|
|
|
|
|
|
|
# add entries for all other children
|
|
|
|
|
|
for option in self:
|
2020-04-23 12:15:24 +01:00
|
|
|
|
# assign all options with a NONE_OPTION_VALUE (not always None) to the None key
|
2019-02-25 16:17:48 +00:00
|
|
|
|
if option.data == self.NONE_OPTION_VALUE:
|
|
|
|
|
|
child_ids = [
|
|
|
|
|
|
folder['id'] for folder in self.all_template_folders
|
|
|
|
|
|
if folder['parent_id'] is None]
|
|
|
|
|
|
key = self.NONE_OPTION_VALUE
|
|
|
|
|
|
else:
|
|
|
|
|
|
child_ids = [
|
|
|
|
|
|
folder['id'] for folder in self.all_template_folders
|
|
|
|
|
|
if folder['parent_id'] == option.data]
|
|
|
|
|
|
key = option.data
|
|
|
|
|
|
|
|
|
|
|
|
child_map[key] = [option for option in self if option.data in child_ids]
|
|
|
|
|
|
|
|
|
|
|
|
return child_map
|
|
|
|
|
|
|
2020-04-23 12:15:24 +01:00
|
|
|
|
# to be used as the only version of .children once radios are converted
|
|
|
|
|
|
@cached_property
|
|
|
|
|
|
def _children(self):
|
|
|
|
|
|
return self.children()
|
|
|
|
|
|
|
|
|
|
|
|
def get_items_from_options(self, field):
|
|
|
|
|
|
items = []
|
|
|
|
|
|
|
|
|
|
|
|
for option in self._children[None]:
|
|
|
|
|
|
item = self.get_item_from_option(option)
|
|
|
|
|
|
if option.data in self._children:
|
|
|
|
|
|
item['children'] = self.render_children(field.name, option.label.text, self._children[option.data])
|
|
|
|
|
|
items.append(item)
|
|
|
|
|
|
|
|
|
|
|
|
return items
|
|
|
|
|
|
|
|
|
|
|
|
def render_children(self, name, label, options):
|
|
|
|
|
|
params = {
|
|
|
|
|
|
"name": name,
|
|
|
|
|
|
"fieldset": {
|
|
|
|
|
|
"legend": {
|
|
|
|
|
|
"text": label,
|
|
|
|
|
|
"classes": "govuk-visually-hidden"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"formGroup": {
|
|
|
|
|
|
"classes": "govuk-form-group--nested"
|
|
|
|
|
|
},
|
|
|
|
|
|
"asList": True,
|
|
|
|
|
|
"items": []
|
|
|
|
|
|
}
|
|
|
|
|
|
for option in options:
|
|
|
|
|
|
item = self.get_item_from_option(option)
|
|
|
|
|
|
|
|
|
|
|
|
if len(self._children[option.data]):
|
|
|
|
|
|
item['children'] = self.render_children(name, option.label.text, self._children[option.data])
|
|
|
|
|
|
|
|
|
|
|
|
params['items'].append(item)
|
|
|
|
|
|
|
|
|
|
|
|
return render_template('forms/fields/checkboxes/template.njk', params=params)
|
|
|
|
|
|
|
2019-02-25 16:17:48 +00:00
|
|
|
|
|
|
|
|
|
|
class NestedRadioField(RadioFieldWithNoneOption, NestedFieldMixin):
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NestedCheckboxesField(SelectMultipleField, NestedFieldMixin):
|
|
|
|
|
|
NONE_OPTION_VALUE = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class HiddenFieldWithNoneOption(FieldWithNoneOption, HiddenField):
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RadioFieldWithRequiredMessage(RadioField):
|
|
|
|
|
|
def __init__(self, *args, required_message='Not a valid choice', **kwargs):
|
|
|
|
|
|
self.required_message = required_message
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
def pre_validate(self, form):
|
|
|
|
|
|
try:
|
|
|
|
|
|
return super().pre_validate(form)
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
raise ValueError(self.required_message)
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class StripWhitespaceForm(Form):
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
def bind_field(self, form, unbound_field, options):
|
|
|
|
|
|
# FieldList simply doesn't support filters.
|
|
|
|
|
|
# @see: https://github.com/wtforms/wtforms/issues/148
|
2020-08-07 09:49:07 +01:00
|
|
|
|
no_filter_fields = (FieldList, PasswordField, GovukPasswordField)
|
2017-12-11 16:02:12 +00:00
|
|
|
|
filters = [strip_whitespace] if not issubclass(unbound_field.field_class, no_filter_fields) else []
|
|
|
|
|
|
filters += unbound_field.kwargs.get('filters', [])
|
|
|
|
|
|
bound = unbound_field.bind(form=form, filters=filters, **options)
|
|
|
|
|
|
bound.get_form = weakref.ref(form) # GC won't collect the form if we don't use a weakref
|
|
|
|
|
|
return bound
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-08-07 10:48:21 +01:00
|
|
|
|
class StripWhitespaceStringField(GovukTextInputField):
|
|
|
|
|
|
def __init__(self, label=None, param_extensions=None, **kwargs):
|
2017-12-11 16:22:37 +00:00
|
|
|
|
kwargs['filters'] = tuple(chain(
|
|
|
|
|
|
kwargs.get('filters', ()),
|
|
|
|
|
|
(
|
|
|
|
|
|
strip_whitespace,
|
|
|
|
|
|
),
|
|
|
|
|
|
))
|
2020-08-07 10:48:21 +01:00
|
|
|
|
super(GovukTextInputField, self).__init__(label, **kwargs)
|
|
|
|
|
|
self.param_extensions = param_extensions
|
2017-12-11 16:22:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
2020-04-02 17:21:27 +01:00
|
|
|
|
class PostalAddressField(TextAreaField):
|
2020-03-10 15:12:19 +00:00
|
|
|
|
def process_formdata(self, valuelist):
|
|
|
|
|
|
if valuelist:
|
2020-04-02 17:21:27 +01:00
|
|
|
|
self.data = PostalAddress(valuelist[0]).normalised
|
2020-03-10 15:12:19 +00:00
|
|
|
|
|
|
|
|
|
|
|
2019-09-05 12:10:24 +01:00
|
|
|
|
class OnOffField(RadioField):
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, label, choices=None, *args, **kwargs):
|
2020-04-22 17:49:03 +01:00
|
|
|
|
choices = choices or [
|
2019-09-05 12:10:24 +01:00
|
|
|
|
(True, 'On'),
|
|
|
|
|
|
(False, 'Off'),
|
2020-04-22 17:49:03 +01:00
|
|
|
|
]
|
|
|
|
|
|
super().__init__(
|
|
|
|
|
|
label,
|
|
|
|
|
|
choices=choices,
|
|
|
|
|
|
thing=f'{choices[0][1].lower()} or {choices[1][1].lower()}',
|
|
|
|
|
|
*args,
|
|
|
|
|
|
**kwargs,
|
|
|
|
|
|
)
|
2019-09-05 12:10:24 +01:00
|
|
|
|
|
|
|
|
|
|
def process_formdata(self, valuelist):
|
|
|
|
|
|
if valuelist:
|
|
|
|
|
|
value = valuelist[0]
|
|
|
|
|
|
self.data = (value == 'True') if value in ['True', 'False'] else value
|
|
|
|
|
|
|
|
|
|
|
|
def iter_choices(self):
|
|
|
|
|
|
for value, label in self.choices:
|
|
|
|
|
|
# This overrides WTForms default behaviour which is to check
|
|
|
|
|
|
# self.coerce(value) == self.data
|
|
|
|
|
|
# where self.coerce returns a string for a boolean input
|
|
|
|
|
|
yield (
|
|
|
|
|
|
value,
|
|
|
|
|
|
label,
|
|
|
|
|
|
(self.data in {value, self.coerce(value)})
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class LoginForm(StripWhitespaceForm):
|
2020-08-07 15:16:04 +01:00
|
|
|
|
email_address = GovukEmailField('Email address', validators=[
|
2015-11-27 16:25:56 +00:00
|
|
|
|
Length(min=5, max=255),
|
2019-09-12 16:42:33 +01:00
|
|
|
|
DataRequired(message='Cannot be empty'),
|
2018-02-14 14:35:16 +00:00
|
|
|
|
ValidEmail()
|
2015-11-27 09:47:29 +00:00
|
|
|
|
])
|
2020-08-07 09:53:09 +01:00
|
|
|
|
password = GovukPasswordField('Password', validators=[
|
2016-01-08 12:00:52 +00:00
|
|
|
|
DataRequired(message='Enter your password')
|
2015-11-27 09:47:29 +00:00
|
|
|
|
])
|
2015-12-01 13:23:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class RegisterUserForm(StripWhitespaceForm):
|
2020-08-07 15:16:04 +01:00
|
|
|
|
name = GovukTextInputField(
|
|
|
|
|
|
'Full name',
|
|
|
|
|
|
validators=[DataRequired(message='Cannot be empty')]
|
|
|
|
|
|
)
|
2016-01-08 12:00:52 +00:00
|
|
|
|
email_address = email_address()
|
2017-08-29 14:52:24 +01:00
|
|
|
|
mobile_number = international_phone_number()
|
2016-01-08 12:00:52 +00:00
|
|
|
|
password = password()
|
2017-11-13 13:39:31 +00:00
|
|
|
|
# always register as sms type
|
|
|
|
|
|
auth_type = HiddenField('auth_type', default='sms_auth')
|
2015-12-04 16:21:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
2018-07-02 09:08:21 +01:00
|
|
|
|
class RegisterUserFromInviteForm(RegisterUserForm):
|
2017-11-14 15:53:38 +00:00
|
|
|
|
def __init__(self, invited_user):
|
|
|
|
|
|
super().__init__(
|
2019-05-23 15:27:35 +01:00
|
|
|
|
service=invited_user.service,
|
|
|
|
|
|
email_address=invited_user.email_address,
|
|
|
|
|
|
auth_type=invited_user.auth_type,
|
2018-07-02 09:08:21 +01:00
|
|
|
|
name=guess_name_from_email_address(
|
2019-05-23 15:27:35 +01:00
|
|
|
|
invited_user.email_address
|
2018-07-02 09:08:21 +01:00
|
|
|
|
),
|
2017-11-14 15:53:38 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2017-11-10 12:35:21 +00:00
|
|
|
|
mobile_number = InternationalPhoneNumber('Mobile number', validators=[])
|
2016-03-02 15:25:04 +00:00
|
|
|
|
service = HiddenField('service')
|
2016-03-08 16:29:05 +00:00
|
|
|
|
email_address = HiddenField('email_address')
|
2017-11-13 13:39:31 +00:00
|
|
|
|
auth_type = HiddenField('auth_type', validators=[DataRequired()])
|
2016-03-02 15:25:04 +00:00
|
|
|
|
|
2017-11-10 12:35:21 +00:00
|
|
|
|
def validate_mobile_number(self, field):
|
2017-11-13 13:39:31 +00:00
|
|
|
|
if self.auth_type.data == 'sms_auth' and not field.data:
|
2019-09-12 16:42:33 +01:00
|
|
|
|
raise ValidationError('Cannot be empty')
|
2017-11-10 12:35:21 +00:00
|
|
|
|
|
2016-03-02 15:25:04 +00:00
|
|
|
|
|
2018-02-19 16:53:29 +00:00
|
|
|
|
class RegisterUserFromOrgInviteForm(StripWhitespaceForm):
|
|
|
|
|
|
def __init__(self, invited_org_user):
|
|
|
|
|
|
super().__init__(
|
2019-05-23 15:27:35 +01:00
|
|
|
|
organisation=invited_org_user.organisation,
|
|
|
|
|
|
email_address=invited_org_user.email_address,
|
2018-02-19 16:53:29 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2020-08-07 09:53:09 +01:00
|
|
|
|
name = GovukTextInputField(
|
2018-02-19 16:53:29 +00:00
|
|
|
|
'Full name',
|
2019-09-12 16:42:33 +01:00
|
|
|
|
validators=[DataRequired(message='Cannot be empty')]
|
2018-02-19 16:53:29 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2019-09-12 16:42:33 +01:00
|
|
|
|
mobile_number = InternationalPhoneNumber('Mobile number', validators=[DataRequired(message='Cannot be empty')])
|
2018-02-19 16:53:29 +00:00
|
|
|
|
password = password()
|
|
|
|
|
|
organisation = HiddenField('organisation')
|
|
|
|
|
|
email_address = HiddenField('email_address')
|
|
|
|
|
|
auth_type = HiddenField('auth_type', validators=[DataRequired()])
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-05-07 15:44:13 +01:00
|
|
|
|
class govukCheckboxesMixin:
|
2020-05-07 15:30:20 +01:00
|
|
|
|
|
|
|
|
|
|
def extend_params(self, params, extensions):
|
|
|
|
|
|
items = None
|
|
|
|
|
|
param_items = len(params['items']) if 'items' in params else 0
|
|
|
|
|
|
|
|
|
|
|
|
# split items off from params to make it a pure dict
|
|
|
|
|
|
if 'items' in extensions:
|
|
|
|
|
|
items = extensions['items']
|
|
|
|
|
|
del extensions['items']
|
|
|
|
|
|
|
|
|
|
|
|
# merge dicts
|
2020-08-13 12:38:12 +01:00
|
|
|
|
merge_jsonlike(params, extensions)
|
2020-05-07 15:30:20 +01:00
|
|
|
|
|
|
|
|
|
|
# merge items
|
|
|
|
|
|
if items:
|
|
|
|
|
|
if 'items' not in params:
|
|
|
|
|
|
params['items'] = items
|
|
|
|
|
|
else:
|
|
|
|
|
|
for idx, _item in enumerate(items):
|
|
|
|
|
|
if idx >= param_items:
|
|
|
|
|
|
params['items'].append(items[idx])
|
|
|
|
|
|
else:
|
|
|
|
|
|
params['items'][idx].update(items[idx])
|
|
|
|
|
|
|
2020-05-07 15:44:13 +01:00
|
|
|
|
|
|
|
|
|
|
class govukCheckboxField(govukCheckboxesMixin, BooleanField):
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, label='', validators=None, param_extensions=None, **kwargs):
|
|
|
|
|
|
super(govukCheckboxField, self).__init__(label, validators, false_values=None, **kwargs)
|
|
|
|
|
|
self.param_extensions = param_extensions
|
|
|
|
|
|
|
2020-05-07 15:30:20 +01:00
|
|
|
|
# self.__call__ renders the HTML for the field by:
|
|
|
|
|
|
# 1. delegating to self.meta.render_field which
|
|
|
|
|
|
# 2. calls field.widget
|
|
|
|
|
|
# this bypasses that by making self.widget a method with the same interface as widget.__call__
|
|
|
|
|
|
def widget(self, field, param_extensions=None, **kwargs):
|
|
|
|
|
|
|
|
|
|
|
|
# error messages
|
|
|
|
|
|
error_message = None
|
|
|
|
|
|
if field.errors:
|
2020-07-14 10:34:53 +01:00
|
|
|
|
error_message = {
|
|
|
|
|
|
"attributes": {
|
|
|
|
|
|
"data-module": "track-error",
|
|
|
|
|
|
"data-error-type": field.errors[0],
|
|
|
|
|
|
"data-error-label": field.name
|
|
|
|
|
|
},
|
|
|
|
|
|
"text": " ".join(field.errors).strip()
|
|
|
|
|
|
}
|
2020-05-07 15:30:20 +01:00
|
|
|
|
|
|
|
|
|
|
params = {
|
|
|
|
|
|
'name': field.name,
|
|
|
|
|
|
'errorMessage': error_message,
|
|
|
|
|
|
'items': [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": field.name,
|
|
|
|
|
|
"id": field.id,
|
|
|
|
|
|
"text": field.label.text,
|
|
|
|
|
|
"value": 'y',
|
|
|
|
|
|
"checked": field.data
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# extend default params with any sent in during instantiation
|
|
|
|
|
|
if self.param_extensions:
|
|
|
|
|
|
self.extend_params(params, self.param_extensions)
|
|
|
|
|
|
|
|
|
|
|
|
# add any sent in though use in templates
|
|
|
|
|
|
if param_extensions:
|
|
|
|
|
|
self.extend_params(params, param_extensions)
|
|
|
|
|
|
|
|
|
|
|
|
return Markup(
|
|
|
|
|
|
render_template('forms/fields/checkboxes/macro.njk', params=params))
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-04-02 18:06:01 +01:00
|
|
|
|
# based on work done by @richardjpope: https://github.com/richardjpope/recourse/blob/master/recourse/forms.py#L6
|
2020-05-07 15:44:13 +01:00
|
|
|
|
class govukCheckboxesField(govukCheckboxesMixin, SelectMultipleField):
|
2020-04-02 18:06:01 +01:00
|
|
|
|
|
2020-04-23 12:15:24 +01:00
|
|
|
|
render_as_list = False
|
|
|
|
|
|
|
2020-04-02 18:06:01 +01:00
|
|
|
|
def __init__(self, label='', validators=None, param_extensions=None, **kwargs):
|
|
|
|
|
|
super(govukCheckboxesField, self).__init__(label, validators, **kwargs)
|
|
|
|
|
|
self.param_extensions = param_extensions
|
|
|
|
|
|
|
2020-04-23 12:15:24 +01:00
|
|
|
|
def get_item_from_option(self, option):
|
|
|
|
|
|
return {
|
|
|
|
|
|
"name": option.name,
|
|
|
|
|
|
"id": option.id,
|
|
|
|
|
|
"text": option.label.text,
|
|
|
|
|
|
"value": str(option.data), # to protect against non-string types like uuids
|
|
|
|
|
|
"checked": option.checked
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def get_items_from_options(self, field):
|
|
|
|
|
|
return [self.get_item_from_option(option) for option in field]
|
|
|
|
|
|
|
2020-04-02 18:06:01 +01:00
|
|
|
|
# self.__call__ renders the HTML for the field by:
|
|
|
|
|
|
# 1. delegating to self.meta.render_field which
|
|
|
|
|
|
# 2. calls field.widget
|
|
|
|
|
|
# this bypasses that by making self.widget a method with the same interface as widget.__call__
|
2020-04-09 11:51:11 +01:00
|
|
|
|
def widget(self, field, param_extensions=None, **kwargs):
|
2020-04-02 18:06:01 +01:00
|
|
|
|
|
|
|
|
|
|
# error messages
|
|
|
|
|
|
error_message = None
|
|
|
|
|
|
if field.errors:
|
2020-07-14 10:34:53 +01:00
|
|
|
|
error_message = {
|
|
|
|
|
|
"attributes": {
|
|
|
|
|
|
"data-module": "track-error",
|
|
|
|
|
|
"data-error-type": field.errors[0],
|
|
|
|
|
|
"data-error-label": field.name
|
|
|
|
|
|
},
|
|
|
|
|
|
"text": " ".join(field.errors).strip()
|
|
|
|
|
|
}
|
2020-04-02 18:06:01 +01:00
|
|
|
|
|
2020-04-23 12:15:24 +01:00
|
|
|
|
# returns either a list or a hierarchy of lists
|
|
|
|
|
|
# depending on how get_items_from_options is implemented
|
|
|
|
|
|
items = self.get_items_from_options(field)
|
2020-04-02 18:06:01 +01:00
|
|
|
|
|
|
|
|
|
|
params = {
|
|
|
|
|
|
'name': field.name,
|
2020-05-07 15:30:20 +01:00
|
|
|
|
"fieldset": {
|
|
|
|
|
|
"attributes": {"id": field.name},
|
|
|
|
|
|
"legend": {
|
|
|
|
|
|
"text": field.label.text,
|
|
|
|
|
|
"classes": "govuk-fieldset__legend--s"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"asList": self.render_as_list,
|
2020-04-02 18:06:01 +01:00
|
|
|
|
'errorMessage': error_message,
|
|
|
|
|
|
'items': items
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-05-07 15:30:20 +01:00
|
|
|
|
# extend default params with any sent in during instantiation
|
|
|
|
|
|
if self.param_extensions:
|
|
|
|
|
|
self.extend_params(params, self.param_extensions)
|
2020-04-02 18:06:01 +01:00
|
|
|
|
|
2020-05-07 15:30:20 +01:00
|
|
|
|
# add any sent in though use in templates
|
|
|
|
|
|
if param_extensions:
|
|
|
|
|
|
self.extend_params(params, param_extensions)
|
2020-04-02 18:06:01 +01:00
|
|
|
|
|
|
|
|
|
|
return Markup(
|
2020-04-23 12:15:24 +01:00
|
|
|
|
render_template('forms/fields/checkboxes/macro.njk', params=params))
|
2020-04-02 18:06:01 +01:00
|
|
|
|
|
|
|
|
|
|
|
2020-04-23 12:12:55 +01:00
|
|
|
|
# Extends fields using the govukCheckboxesField interface to wrap their render in HTML needed by the collapsible JS
|
|
|
|
|
|
class govukCollapsibleCheckboxesMixin:
|
|
|
|
|
|
def __init__(self, label='', validators=None, field_label='', param_extensions=None, **kwargs):
|
|
|
|
|
|
|
2020-04-23 12:15:24 +01:00
|
|
|
|
super(govukCollapsibleCheckboxesMixin, self).__init__(label, validators, param_extensions, **kwargs)
|
2020-04-23 12:12:55 +01:00
|
|
|
|
self.field_label = field_label
|
|
|
|
|
|
|
|
|
|
|
|
def widget(self, field, **kwargs):
|
|
|
|
|
|
|
|
|
|
|
|
# add a blank hint to act as an ARIA live-region
|
|
|
|
|
|
if self.param_extensions is not None:
|
|
|
|
|
|
self.param_extensions.update(
|
|
|
|
|
|
{"hint": {"html": "<div class=\"selection-summary\" role=\"region\" aria-live=\"polite\"></div>"}})
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.param_extensions = \
|
|
|
|
|
|
{"hint": {"html": "<div class=\"selection-summary\" role=\"region\" aria-live=\"polite\"></div>"}}
|
|
|
|
|
|
|
|
|
|
|
|
# wrap the checkboxes HTML in the HTML needed by the collapisble JS
|
|
|
|
|
|
return Markup(
|
|
|
|
|
|
f'<div class="selection-wrapper"'
|
|
|
|
|
|
f' data-module="collapsible-checkboxes"'
|
|
|
|
|
|
f' data-field-label="{self.field_label}">'
|
|
|
|
|
|
f' {super(govukCollapsibleCheckboxesMixin, self).widget(field, **kwargs)}'
|
|
|
|
|
|
f'</div>'
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class govukCollapsibleCheckboxesField(govukCollapsibleCheckboxesMixin, govukCheckboxesField):
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-04-23 12:15:24 +01:00
|
|
|
|
# govukCollapsibleCheckboxesMixin adds an ARIA live-region to the hint and wraps the render in HTML needed by the
|
|
|
|
|
|
# collapsible JS
|
|
|
|
|
|
# NestedFieldMixin puts the items into a tree hierarchy, pre-rendering the sub-trees of the top-level items
|
|
|
|
|
|
class govukCollapsibleNestedCheckboxesField(govukCollapsibleCheckboxesMixin, NestedFieldMixin, govukCheckboxesField):
|
|
|
|
|
|
NONE_OPTION_VALUE = None
|
|
|
|
|
|
render_as_list = True
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-04-02 18:08:50 +01:00
|
|
|
|
# guard against data entries that aren't a role in permissions
|
|
|
|
|
|
def filter_by_permissions(valuelist):
|
|
|
|
|
|
if valuelist is None:
|
|
|
|
|
|
return None
|
|
|
|
|
|
else:
|
|
|
|
|
|
return [entry for entry in valuelist if any(entry in role for role in permissions)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# guard against data entries that aren't a role in broadcast_permissions
|
|
|
|
|
|
def filter_by_broadcast_permissions(valuelist):
|
|
|
|
|
|
if valuelist is None:
|
|
|
|
|
|
return None
|
|
|
|
|
|
else:
|
|
|
|
|
|
return [entry for entry in valuelist if any(entry in role for role in broadcast_permissions)]
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-07-13 11:10:34 +01:00
|
|
|
|
class BasePermissionsForm(StripWhitespaceForm):
|
2019-02-25 16:23:28 +00:00
|
|
|
|
def __init__(self, all_template_folders=None, *args, **kwargs):
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
2019-05-17 11:03:41 +01:00
|
|
|
|
self.folder_permissions.choices = []
|
2019-02-27 15:45:18 +00:00
|
|
|
|
if all_template_folders is not None:
|
2019-02-25 16:23:28 +00:00
|
|
|
|
self.folder_permissions.all_template_folders = all_template_folders
|
|
|
|
|
|
self.folder_permissions.choices = [
|
|
|
|
|
|
(item['id'], item['name']) for item in ([{'name': 'Templates', 'id': None}] + all_template_folders)
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2020-04-02 18:08:50 +01:00
|
|
|
|
folder_permissions = govukCollapsibleNestedCheckboxesField(
|
|
|
|
|
|
'Folders this team member can see',
|
|
|
|
|
|
field_label='folder')
|
2018-06-13 12:07:08 +01:00
|
|
|
|
|
2017-11-01 15:36:27 +00:00
|
|
|
|
login_authentication = RadioField(
|
|
|
|
|
|
'Sign in using',
|
|
|
|
|
|
choices=[
|
|
|
|
|
|
('sms_auth', 'Text message code'),
|
|
|
|
|
|
('email_auth', 'Email link'),
|
|
|
|
|
|
],
|
2020-04-22 17:49:03 +01:00
|
|
|
|
thing='how this team member should sign in',
|
2017-11-01 15:36:27 +00:00
|
|
|
|
validators=[DataRequired()]
|
|
|
|
|
|
)
|
2016-03-09 17:42:47 +00:00
|
|
|
|
|
2020-04-02 18:08:50 +01:00
|
|
|
|
permissions_field = govukCheckboxesField(
|
2020-08-04 18:17:38 +01:00
|
|
|
|
'Permissions',
|
2020-04-02 18:08:50 +01:00
|
|
|
|
filters=[filter_by_permissions],
|
|
|
|
|
|
choices=[
|
|
|
|
|
|
(value, label) for value, label in permissions
|
|
|
|
|
|
],
|
|
|
|
|
|
param_extensions={
|
|
|
|
|
|
"hint": {"text": "All team members can see sent messages."}
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
2020-05-14 16:59:34 +01:00
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def permissions(self):
|
2020-07-31 15:16:56 +01:00
|
|
|
|
return set(self.permissions_field.data)
|
2018-08-08 08:45:58 +01:00
|
|
|
|
|
2018-08-06 11:10:37 +01:00
|
|
|
|
@classmethod
|
2019-02-25 16:23:28 +00:00
|
|
|
|
def from_user(cls, user, service_id, **kwargs):
|
2018-08-06 11:10:37 +01:00
|
|
|
|
return cls(
|
2019-02-25 16:23:28 +00:00
|
|
|
|
**kwargs,
|
2018-08-06 11:10:37 +01:00
|
|
|
|
**{
|
2020-04-02 18:08:50 +01:00
|
|
|
|
"permissions_field": [
|
|
|
|
|
|
role for role in roles.keys() if user.has_permission_for_service(service_id, role)]
|
2018-08-06 11:10:37 +01:00
|
|
|
|
},
|
|
|
|
|
|
login_authentication=user.auth_type
|
|
|
|
|
|
)
|
2018-06-12 11:51:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
2020-07-31 15:16:56 +01:00
|
|
|
|
class PermissionsForm(BasePermissionsForm):
|
2020-07-13 11:10:34 +01:00
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-07-31 15:16:56 +01:00
|
|
|
|
class BroadcastPermissionsForm(BasePermissionsForm):
|
2020-07-13 11:10:34 +01:00
|
|
|
|
|
2020-04-02 18:08:50 +01:00
|
|
|
|
permissions_field = govukCheckboxesField(
|
2020-08-04 18:17:38 +01:00
|
|
|
|
'Permissions',
|
2020-04-02 18:08:50 +01:00
|
|
|
|
choices=[
|
|
|
|
|
|
(value, label) for value, label in broadcast_permissions
|
|
|
|
|
|
],
|
|
|
|
|
|
filters=[filter_by_broadcast_permissions],
|
|
|
|
|
|
param_extensions={
|
|
|
|
|
|
"hint": {"text": "All team members can see sent messages."}
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
2020-07-13 11:10:34 +01:00
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def permissions(self):
|
|
|
|
|
|
return {'view_activity'} | super().permissions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BaseInviteUserForm():
|
2016-10-26 14:01:01 +01:00
|
|
|
|
email_address = email_address(gov_user=False)
|
2016-03-09 13:00:52 +00:00
|
|
|
|
|
|
|
|
|
|
def __init__(self, invalid_email_address, *args, **kwargs):
|
2018-06-12 11:51:37 +01:00
|
|
|
|
super().__init__(*args, **kwargs)
|
2016-03-09 13:00:52 +00:00
|
|
|
|
self.invalid_email_address = invalid_email_address.lower()
|
|
|
|
|
|
|
|
|
|
|
|
def validate_email_address(self, field):
|
2020-03-06 11:53:55 +00:00
|
|
|
|
if field.data.lower() == self.invalid_email_address and not current_user.platform_admin:
|
2019-09-12 16:42:33 +01:00
|
|
|
|
raise ValidationError("You cannot send an invitation to yourself")
|
2016-03-09 13:00:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
2020-07-13 11:10:34 +01:00
|
|
|
|
class InviteUserForm(BaseInviteUserForm, PermissionsForm):
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BroadcastInviteUserForm(BaseInviteUserForm, BroadcastPermissionsForm):
|
2020-08-27 18:17:17 +01:00
|
|
|
|
email_address = email_address(gov_user=True)
|
2020-07-13 11:10:34 +01:00
|
|
|
|
|
|
|
|
|
|
|
2018-02-19 16:53:29 +00:00
|
|
|
|
class InviteOrgUserForm(StripWhitespaceForm):
|
|
|
|
|
|
email_address = email_address(gov_user=False)
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, invalid_email_address, *args, **kwargs):
|
|
|
|
|
|
super(InviteOrgUserForm, self).__init__(*args, **kwargs)
|
|
|
|
|
|
self.invalid_email_address = invalid_email_address.lower()
|
|
|
|
|
|
|
|
|
|
|
|
def validate_email_address(self, field):
|
2020-03-06 11:53:55 +00:00
|
|
|
|
if field.data.lower() == self.invalid_email_address and not current_user.platform_admin:
|
2019-09-12 16:42:33 +01:00
|
|
|
|
raise ValidationError("You cannot send an invitation to yourself")
|
2018-02-19 16:53:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class TwoFactorForm(StripWhitespaceForm):
|
2016-01-27 12:22:32 +00:00
|
|
|
|
def __init__(self, validate_code_func, *args, **kwargs):
|
2016-01-07 12:43:10 +00:00
|
|
|
|
'''
|
|
|
|
|
|
Keyword arguments:
|
2016-01-27 12:22:32 +00:00
|
|
|
|
validate_code_func -- Validates the code with the API.
|
2016-01-07 12:43:10 +00:00
|
|
|
|
'''
|
2016-01-27 12:22:32 +00:00
|
|
|
|
self.validate_code_func = validate_code_func
|
2016-01-07 12:43:10 +00:00
|
|
|
|
super(TwoFactorForm, self).__init__(*args, **kwargs)
|
|
|
|
|
|
|
2018-05-07 22:57:18 +01:00
|
|
|
|
sms_code = SMSCode('Text message code')
|
2015-12-08 12:36:54 +00:00
|
|
|
|
|
2018-05-07 21:24:23 +01:00
|
|
|
|
def validate(self):
|
|
|
|
|
|
|
|
|
|
|
|
if not self.sms_code.validate(self):
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
is_valid, reason = self.validate_code_func(self.sms_code.data)
|
|
|
|
|
|
|
2016-01-27 12:22:32 +00:00
|
|
|
|
if not is_valid:
|
2018-05-07 21:24:23 +01:00
|
|
|
|
self.sms_code.errors.append(reason)
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
return True
|
2016-01-27 12:22:32 +00:00
|
|
|
|
|
2015-12-07 16:56:11 +00:00
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class TextNotReceivedForm(StripWhitespaceForm):
|
2017-08-29 14:52:24 +01:00
|
|
|
|
mobile_number = international_phone_number()
|
2015-12-15 15:35:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class RenameServiceForm(StripWhitespaceForm):
|
2020-07-14 16:51:11 +01:00
|
|
|
|
name = GovukTextInputField(
|
2016-06-20 13:33:29 +01:00
|
|
|
|
u'Service name',
|
2016-03-10 14:29:31 +00:00
|
|
|
|
validators=[
|
2019-11-08 17:12:32 +00:00
|
|
|
|
DataRequired(message='Cannot be empty'),
|
|
|
|
|
|
MustContainAlphanumericCharacters()
|
2016-03-10 14:29:31 +00:00
|
|
|
|
])
|
|
|
|
|
|
|
2016-01-11 13:15:10 +00:00
|
|
|
|
|
2018-03-06 17:12:31 +00:00
|
|
|
|
class RenameOrganisationForm(StripWhitespaceForm):
|
2020-08-04 16:32:40 +01:00
|
|
|
|
name = GovukTextInputField(
|
2018-03-06 17:12:31 +00:00
|
|
|
|
u'Organisation name',
|
|
|
|
|
|
validators=[
|
2019-11-08 17:12:32 +00:00
|
|
|
|
DataRequired(message='Cannot be empty'),
|
|
|
|
|
|
MustContainAlphanumericCharacters()
|
2018-03-06 17:12:31 +00:00
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-09-05 12:10:24 +01:00
|
|
|
|
class AddGPOrganisationForm(StripWhitespaceForm):
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, service_name='unknown', **kwargs):
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
self.same_as_service_name.label.text = 'Is your GP practice called ‘{}’?'.format(service_name)
|
|
|
|
|
|
self.service_name = service_name
|
|
|
|
|
|
|
|
|
|
|
|
def get_organisation_name(self):
|
|
|
|
|
|
if self.same_as_service_name.data:
|
|
|
|
|
|
return self.service_name
|
|
|
|
|
|
return self.name.data
|
|
|
|
|
|
|
|
|
|
|
|
same_as_service_name = OnOffField(
|
|
|
|
|
|
'Is your GP practice called the same name as your service?',
|
|
|
|
|
|
choices=(
|
|
|
|
|
|
(True, 'Yes'),
|
|
|
|
|
|
(False, 'No'),
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2020-08-04 16:45:49 +01:00
|
|
|
|
name = GovukTextInputField(
|
2019-09-05 12:10:24 +01:00
|
|
|
|
'What’s your practice called?',
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def validate_name(self, field):
|
|
|
|
|
|
if self.same_as_service_name.data is False:
|
|
|
|
|
|
if not field.data:
|
2019-09-12 16:42:33 +01:00
|
|
|
|
raise ValidationError('Cannot be empty')
|
2019-09-05 12:10:24 +01:00
|
|
|
|
else:
|
|
|
|
|
|
field.data = ''
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-09-05 16:18:02 +01:00
|
|
|
|
class AddNHSLocalOrganisationForm(StripWhitespaceForm):
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, organisation_choices=None, **kwargs):
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
self.organisations.choices = organisation_choices
|
|
|
|
|
|
|
|
|
|
|
|
organisations = RadioField(
|
|
|
|
|
|
'Which NHS Trust or Clinical Commissioning Group do you work for?',
|
2020-04-22 17:49:03 +01:00
|
|
|
|
thing='an NHS Trust or Clinical Commissioning Group'
|
2019-09-05 16:18:02 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-02-19 17:26:16 +00:00
|
|
|
|
class OrganisationOrganisationTypeForm(StripWhitespaceForm):
|
2019-08-27 15:52:42 +01:00
|
|
|
|
organisation_type = OrganisationTypeField('What type of organisation is this?')
|
2019-02-19 17:26:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class OrganisationCrownStatusForm(StripWhitespaceForm):
|
|
|
|
|
|
crown_status = RadioField(
|
|
|
|
|
|
(
|
|
|
|
|
|
'Is this organisation a crown body?'
|
|
|
|
|
|
),
|
|
|
|
|
|
choices=[
|
|
|
|
|
|
('crown', 'Yes'),
|
|
|
|
|
|
('non-crown', 'No'),
|
|
|
|
|
|
('unknown', 'Not sure'),
|
|
|
|
|
|
],
|
2020-04-22 17:49:03 +01:00
|
|
|
|
thing='whether this organisation is a crown body',
|
2019-02-19 17:26:16 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class OrganisationAgreementSignedForm(StripWhitespaceForm):
|
|
|
|
|
|
agreement_signed = RadioField(
|
|
|
|
|
|
(
|
|
|
|
|
|
'Has this organisation signed the agreement?'
|
|
|
|
|
|
),
|
|
|
|
|
|
choices=[
|
|
|
|
|
|
('yes', 'Yes'),
|
|
|
|
|
|
('no', 'No'),
|
|
|
|
|
|
('unknown', 'No (but we have some service-specific agreements in place)'),
|
|
|
|
|
|
],
|
2020-04-22 17:49:03 +01:00
|
|
|
|
thing='whether this organisation has signed the agreement',
|
2019-02-19 17:26:16 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class OrganisationDomainsForm(StripWhitespaceForm):
|
|
|
|
|
|
|
|
|
|
|
|
def populate(self, domains_list):
|
|
|
|
|
|
for index, value in enumerate(domains_list):
|
|
|
|
|
|
self.domains[index].data = value
|
|
|
|
|
|
|
|
|
|
|
|
domains = FieldList(
|
|
|
|
|
|
StripWhitespaceStringField(
|
|
|
|
|
|
'',
|
|
|
|
|
|
validators=[
|
|
|
|
|
|
Optional(),
|
|
|
|
|
|
],
|
|
|
|
|
|
default=''
|
|
|
|
|
|
),
|
2019-03-22 16:27:30 +00:00
|
|
|
|
min_entries=20,
|
|
|
|
|
|
max_entries=20,
|
2019-02-19 17:26:16 +00:00
|
|
|
|
label="Domain names"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class CreateServiceForm(StripWhitespaceForm):
|
2020-08-07 12:18:29 +01:00
|
|
|
|
name = GovukTextInputField(
|
2019-07-16 15:37:27 +01:00
|
|
|
|
"What’s your service called?",
|
2017-10-04 11:49:32 +01:00
|
|
|
|
validators=[
|
2019-11-08 17:12:32 +00:00
|
|
|
|
DataRequired(message='Cannot be empty'),
|
|
|
|
|
|
MustContainAlphanumericCharacters()
|
2017-10-04 11:49:32 +01:00
|
|
|
|
])
|
2019-08-27 15:52:42 +01:00
|
|
|
|
organisation_type = OrganisationTypeField('Who runs this service?')
|
2017-10-05 12:06:40 +01:00
|
|
|
|
|
|
|
|
|
|
|
2019-07-16 15:37:27 +01:00
|
|
|
|
class CreateNhsServiceForm(CreateServiceForm):
|
2019-08-27 15:52:42 +01:00
|
|
|
|
organisation_type = OrganisationTypeField(
|
|
|
|
|
|
'Who runs this service?',
|
2019-08-27 16:26:50 +01:00
|
|
|
|
include_only={'nhs_central', 'nhs_local', 'nhs_gp'},
|
2019-08-27 15:52:42 +01:00
|
|
|
|
)
|
2019-07-16 15:37:27 +01:00
|
|
|
|
|
|
|
|
|
|
|
2019-07-01 10:19:33 +01:00
|
|
|
|
class NewOrganisationForm(
|
|
|
|
|
|
RenameOrganisationForm,
|
2019-07-01 17:01:11 +01:00
|
|
|
|
OrganisationOrganisationTypeForm,
|
2019-07-01 10:19:33 +01:00
|
|
|
|
OrganisationCrownStatusForm,
|
|
|
|
|
|
):
|
2019-07-01 17:01:11 +01:00
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
# Don’t offer the ‘not sure’ choice
|
|
|
|
|
|
self.crown_status.choices = self.crown_status.choices[:-1]
|
2019-07-01 10:19:33 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class FreeSMSAllowance(StripWhitespaceForm):
|
2020-08-07 10:31:09 +01:00
|
|
|
|
free_sms_allowance = GovukIntegerField(
|
2017-10-05 13:49:43 +01:00
|
|
|
|
'Numbers of text message fragments per year',
|
|
|
|
|
|
validators=[
|
2019-09-12 16:42:33 +01:00
|
|
|
|
DataRequired(message='Cannot be empty')
|
2017-10-05 13:49:43 +01:00
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class ConfirmPasswordForm(StripWhitespaceForm):
|
2016-01-22 16:34:36 +00:00
|
|
|
|
def __init__(self, validate_password_func, *args, **kwargs):
|
|
|
|
|
|
self.validate_password_func = validate_password_func
|
|
|
|
|
|
super(ConfirmPasswordForm, self).__init__(*args, **kwargs)
|
|
|
|
|
|
|
2020-08-07 09:53:09 +01:00
|
|
|
|
password = GovukPasswordField(u'Enter password')
|
2016-01-11 13:15:10 +00:00
|
|
|
|
|
2016-01-22 16:34:36 +00:00
|
|
|
|
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-12-11 16:02:12 +00:00
|
|
|
|
class BaseTemplateForm(StripWhitespaceForm):
|
2020-08-07 12:07:16 +01:00
|
|
|
|
name = GovukTextInputField(
|
|
|
|
|
|
"Template name",
|
2019-09-12 16:42:33 +01:00
|
|
|
|
validators=[DataRequired(message="Cannot be empty")])
|
2016-01-22 12:19:15 +00:00
|
|
|
|
|
2016-01-22 12:15:47 +00:00
|
|
|
|
template_content = TextAreaField(
|
2020-08-07 12:07:16 +01:00
|
|
|
|
"Message",
|
2016-04-07 16:02:06 +01:00
|
|
|
|
validators=[
|
2019-09-12 16:42:33 +01:00
|
|
|
|
DataRequired(message="Cannot be empty"),
|
2017-02-15 15:06:47 +00:00
|
|
|
|
NoCommasInPlaceHolders()
|
2016-04-07 16:02:06 +01:00
|
|
|
|
]
|
|
|
|
|
|
)
|
2017-01-19 09:35:34 +00:00
|
|
|
|
process_type = RadioField(
|
2020-08-07 12:07:16 +01:00
|
|
|
|
"Use priority queue?",
|
2017-01-19 09:35:34 +00:00
|
|
|
|
choices=[
|
|
|
|
|
|
('priority', 'Yes'),
|
|
|
|
|
|
('normal', 'No'),
|
|
|
|
|
|
],
|
2020-04-22 17:49:03 +01:00
|
|
|
|
thing='yes or no',
|
2017-01-19 09:35:34 +00:00
|
|
|
|
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):
|
2020-07-03 15:46:00 +01:00
|
|
|
|
OnlySMSCharacters(template_type='sms')(None, field)
|
2017-02-15 15:06:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
2020-07-01 16:43:08 +01:00
|
|
|
|
class BroadcastTemplateForm(SMSTemplateForm):
|
2020-07-03 15:46:00 +01:00
|
|
|
|
def validate_template_content(self, field):
|
|
|
|
|
|
OnlySMSCharacters(template_type='broadcast')(None, field)
|
2020-07-01 16:43:08 +01:00
|
|
|
|
|
|
|
|
|
|
|
2020-03-10 15:12:19 +00:00
|
|
|
|
class LetterAddressForm(StripWhitespaceForm):
|
|
|
|
|
|
|
2020-04-27 10:57:25 +01:00
|
|
|
|
def __init__(self, *args, allow_international_letters=False, **kwargs):
|
|
|
|
|
|
self.allow_international_letters = allow_international_letters
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
2020-04-02 17:21:27 +01:00
|
|
|
|
address = PostalAddressField(
|
2020-03-10 15:12:19 +00:00
|
|
|
|
'Address',
|
|
|
|
|
|
validators=[DataRequired(message="Cannot be empty")]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def validate_address(self, field):
|
|
|
|
|
|
|
2020-04-27 10:57:25 +01:00
|
|
|
|
address = PostalAddress(
|
|
|
|
|
|
field.data,
|
|
|
|
|
|
allow_international_letters=self.allow_international_letters,
|
|
|
|
|
|
)
|
2020-03-10 15:12:19 +00:00
|
|
|
|
|
2020-04-02 17:21:27 +01:00
|
|
|
|
if not address.has_enough_lines:
|
|
|
|
|
|
raise ValidationError(
|
|
|
|
|
|
f'Address must be at least {PostalAddress.MIN_LINES} lines long'
|
|
|
|
|
|
)
|
2020-03-10 15:12:19 +00:00
|
|
|
|
|
2020-04-02 17:21:27 +01:00
|
|
|
|
if address.has_too_many_lines:
|
|
|
|
|
|
raise ValidationError(
|
|
|
|
|
|
f'Address must be no more than {PostalAddress.MAX_LINES} lines long'
|
|
|
|
|
|
)
|
2020-03-10 15:12:19 +00:00
|
|
|
|
|
2020-04-27 10:57:25 +01:00
|
|
|
|
if not address.has_valid_last_line:
|
|
|
|
|
|
if self.allow_international_letters:
|
|
|
|
|
|
raise ValidationError(
|
|
|
|
|
|
f'Last line of the address must be a UK postcode or another country'
|
|
|
|
|
|
)
|
2020-09-10 11:18:20 +01:00
|
|
|
|
if address.international:
|
|
|
|
|
|
raise ValidationError(
|
|
|
|
|
|
f'You do not have permission to send letters to other countries'
|
|
|
|
|
|
)
|
2020-04-02 17:32:26 +01:00
|
|
|
|
raise ValidationError(
|
|
|
|
|
|
f'Last line of the address must be a real UK postcode'
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2020-07-28 13:44:14 +01:00
|
|
|
|
if address.has_invalid_characters:
|
|
|
|
|
|
raise ValidationError(
|
|
|
|
|
|
'Address lines must not start with any of the following characters: @ ( ) = [ ] ” \\ / ,'
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2020-03-10 15:12:19 +00:00
|
|
|
|
|
2017-02-15 15:06:47 +00:00
|
|
|
|
class EmailTemplateForm(BaseTemplateForm):
|
2016-04-14 14:04:41 +01:00
|
|
|
|
subject = TextAreaField(
|
2016-02-22 14:45:13 +00:00
|
|
|
|
u'Subject',
|
2019-09-12 16:42:33 +01:00
|
|
|
|
validators=[DataRequired(message="Cannot be empty")])
|
2016-02-22 14:45:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
2016-11-08 13:12:07 +00:00
|
|
|
|
class LetterTemplateForm(EmailTemplateForm):
|
2017-03-13 10:55:15 +00:00
|
|
|
|
subject = TextAreaField(
|
2017-05-10 11:27:45 +01:00
|
|
|
|
u'Main heading',
|
2019-09-12 16:42:33 +01:00
|
|
|
|
validators=[DataRequired(message="Cannot be empty")])
|
2017-03-13 10:55:15 +00:00
|
|
|
|
|
|
|
|
|
|
template_content = TextAreaField(
|
|
|
|
|
|
u'Body',
|
|
|
|
|
|
validators=[
|
2019-09-12 16:42:33 +01:00
|
|
|
|
DataRequired(message="Cannot be empty"),
|
2017-03-13 10:55:15 +00:00
|
|
|
|
NoCommasInPlaceHolders()
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
2016-11-08 13:12:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
2019-01-29 15:43:59 +00:00
|
|
|
|
class LetterTemplatePostageForm(StripWhitespaceForm):
|
|
|
|
|
|
postage = RadioField(
|
|
|
|
|
|
'Choose the postage for this letter template',
|
|
|
|
|
|
choices=[
|
|
|
|
|
|
('first', 'First class'),
|
|
|
|
|
|
('second', 'Second class'),
|
|
|
|
|
|
],
|
2020-04-22 17:49:03 +01:00
|
|
|
|
thing='first class or second class',
|
2019-01-29 15:43:59 +00:00
|
|
|
|
validators=[DataRequired()]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-10-09 11:23:02 +01:00
|
|
|
|
class LetterUploadPostageForm(StripWhitespaceForm):
|
2020-05-20 11:12:33 +01:00
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, postage_zone, **kwargs):
|
|
|
|
|
|
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
if postage_zone != Postage.UK:
|
|
|
|
|
|
self.postage.choices = [(postage_zone, '')]
|
|
|
|
|
|
self.postage.data = postage_zone
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def show_postage(self):
|
|
|
|
|
|
return len(self.postage.choices) > 1
|
|
|
|
|
|
|
2019-10-09 11:23:02 +01:00
|
|
|
|
postage = RadioField(
|
|
|
|
|
|
'Choose the postage for this letter',
|
|
|
|
|
|
choices=[
|
|
|
|
|
|
('first', 'First class post'),
|
|
|
|
|
|
('second', 'Second class post'),
|
|
|
|
|
|
],
|
|
|
|
|
|
default='second',
|
|
|
|
|
|
validators=[DataRequired()]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class ForgotPasswordForm(StripWhitespaceForm):
|
2016-10-26 14:01:01 +01:00
|
|
|
|
email_address = email_address(gov_user=False)
|
2016-01-04 14:00:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class NewPasswordForm(StripWhitespaceForm):
|
2016-01-08 12:00:52 +00:00
|
|
|
|
new_password = password()
|
2016-01-11 15:00:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class ChangePasswordForm(StripWhitespaceForm):
|
2016-01-27 12:22:32 +00:00
|
|
|
|
def __init__(self, validate_password_func, *args, **kwargs):
|
|
|
|
|
|
self.validate_password_func = validate_password_func
|
|
|
|
|
|
super(ChangePasswordForm, self).__init__(*args, **kwargs)
|
|
|
|
|
|
|
2016-01-12 11:25:46 +00:00
|
|
|
|
old_password = password('Current password')
|
|
|
|
|
|
new_password = password('New password')
|
|
|
|
|
|
|
2016-01-27 12:22:32 +00:00
|
|
|
|
def validate_old_password(self, field):
|
|
|
|
|
|
if not self.validate_password_func(field.data):
|
|
|
|
|
|
raise ValidationError('Invalid password')
|
|
|
|
|
|
|
2016-01-12 11:25:46 +00:00
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class CsvUploadForm(StripWhitespaceForm):
|
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()])
|
2016-01-12 10:28:14 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class ChangeNameForm(StripWhitespaceForm):
|
2020-08-07 15:16:04 +01:00
|
|
|
|
new_name = GovukTextInputField(u'Your name')
|
2016-01-12 10:28:14 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class ChangeEmailForm(StripWhitespaceForm):
|
2016-01-27 12:22:32 +00:00
|
|
|
|
def __init__(self, validate_email_func, *args, **kwargs):
|
|
|
|
|
|
self.validate_email_func = validate_email_func
|
|
|
|
|
|
super(ChangeEmailForm, self).__init__(*args, **kwargs)
|
|
|
|
|
|
|
2016-01-12 10:28:14 +00:00
|
|
|
|
email_address = email_address()
|
|
|
|
|
|
|
2016-01-27 12:22:32 +00:00
|
|
|
|
def validate_email_address(self, field):
|
|
|
|
|
|
is_valid = self.validate_email_func(field.data)
|
2018-02-19 16:53:29 +00:00
|
|
|
|
if is_valid:
|
2016-01-27 12:22:32 +00:00
|
|
|
|
raise ValidationError("The email address is already in use")
|
|
|
|
|
|
|
2016-01-12 10:28:14 +00:00
|
|
|
|
|
2019-04-18 16:03:13 +01:00
|
|
|
|
class ChangeNonGovEmailForm(ChangeEmailForm):
|
|
|
|
|
|
email_address = email_address(gov_user=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class ChangeMobileNumberForm(StripWhitespaceForm):
|
2017-08-29 14:52:24 +01:00
|
|
|
|
mobile_number = international_phone_number()
|
2016-01-12 10:28:14 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class ChooseTimeForm(StripWhitespaceForm):
|
2016-08-07 09:17:49 +01:00
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
|
super(ChooseTimeForm, self).__init__(*args, **kwargs)
|
|
|
|
|
|
self.scheduled_for.choices = [('', 'Now')] + [
|
2016-10-11 14:11:10 +01:00
|
|
|
|
get_time_value_and_label(hour) for hour in get_next_hours_until(
|
|
|
|
|
|
get_furthest_possible_scheduled_time()
|
|
|
|
|
|
)
|
2016-08-07 09:17:49 +01:00
|
|
|
|
]
|
2016-10-11 14:17:29 +01:00
|
|
|
|
self.scheduled_for.categories = get_next_days_until(get_furthest_possible_scheduled_time())
|
2016-08-07 09:17:49 +01:00
|
|
|
|
|
|
|
|
|
|
scheduled_for = RadioField(
|
|
|
|
|
|
'When should Notify send these messages?',
|
|
|
|
|
|
default='',
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class CreateKeyForm(StripWhitespaceForm):
|
2018-11-13 14:27:01 +00:00
|
|
|
|
def __init__(self, existing_keys, *args, **kwargs):
|
2018-11-13 09:57:17 +00:00
|
|
|
|
self.existing_key_names = [
|
|
|
|
|
|
key['name'].lower() for key in existing_keys
|
2019-03-04 19:50:00 +00:00
|
|
|
|
if not key['expiry_date']
|
2018-11-13 09:57:17 +00:00
|
|
|
|
]
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
2016-01-21 14:15:36 +00:00
|
|
|
|
|
2016-06-29 17:10:49 +01:00
|
|
|
|
key_type = RadioField(
|
2017-04-26 13:21:27 +01:00
|
|
|
|
'Type of key',
|
2020-04-22 17:49:03 +01:00
|
|
|
|
thing='the type of key',
|
2016-06-29 17:10:49 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
2020-08-07 12:20:05 +01:00
|
|
|
|
key_name = GovukTextInputField("Name for this key", validators=[
|
2016-01-21 14:15:36 +00:00
|
|
|
|
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:
|
2016-01-21 14:15:36 +00:00
|
|
|
|
raise ValidationError('A key with this name already exists')
|
2016-04-19 13:51:16 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class SupportType(StripWhitespaceForm):
|
2016-12-12 11:25:43 +00:00
|
|
|
|
support_type = RadioField(
|
|
|
|
|
|
'How can we help you?',
|
|
|
|
|
|
choices=[
|
2020-03-24 15:36:39 +00:00
|
|
|
|
(PROBLEM_TICKET_TYPE, 'Report a problem'),
|
|
|
|
|
|
(QUESTION_TICKET_TYPE, 'Ask a question or give feedback'),
|
2016-12-12 11:25:43 +00:00
|
|
|
|
],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-03-24 15:01:42 +00:00
|
|
|
|
class SupportRedirect(StripWhitespaceForm):
|
|
|
|
|
|
who = RadioField(
|
|
|
|
|
|
'What do you need help with?',
|
|
|
|
|
|
choices=[
|
|
|
|
|
|
('public-sector', 'I work in the public sector and need to send emails, text messages or letters'),
|
|
|
|
|
|
('public', 'I’m a member of the public with a question for the government'),
|
|
|
|
|
|
],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-03-24 15:13:53 +00:00
|
|
|
|
class FeedbackOrProblem(StripWhitespaceForm):
|
2020-08-07 15:16:04 +01:00
|
|
|
|
name = GovukTextInputField('Name (optional)')
|
2020-03-23 11:04:03 +00:00
|
|
|
|
email_address = email_address(label='Email address', gov_user=False, required=True)
|
2019-09-12 16:42:33 +01:00
|
|
|
|
feedback = TextAreaField('Your message', validators=[DataRequired(message="Cannot be empty")])
|
2016-04-26 13:31:57 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class Triage(StripWhitespaceForm):
|
2016-12-12 11:44:11 +00:00
|
|
|
|
severe = RadioField(
|
|
|
|
|
|
'Is it an emergency?',
|
|
|
|
|
|
choices=[
|
|
|
|
|
|
('yes', 'Yes'),
|
|
|
|
|
|
('no', 'No'),
|
|
|
|
|
|
],
|
2020-04-22 17:49:03 +01:00
|
|
|
|
thing='yes or no',
|
2016-12-12 11:44:11 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-02-15 11:03:19 +00:00
|
|
|
|
class EstimateUsageForm(StripWhitespaceForm):
|
2019-02-15 13:34:29 +00:00
|
|
|
|
|
|
|
|
|
|
volume_email = ForgivingIntegerField(
|
2018-08-30 11:00:39 +01:00
|
|
|
|
'How many emails do you expect to send in the next year?',
|
2019-02-27 13:02:37 +00:00
|
|
|
|
things='emails',
|
2019-02-27 15:10:34 +00:00
|
|
|
|
format_error_suffix='you expect to send',
|
2016-10-24 15:46:22 +01:00
|
|
|
|
)
|
2019-02-15 13:34:29 +00:00
|
|
|
|
volume_sms = ForgivingIntegerField(
|
2018-08-30 11:00:39 +01:00
|
|
|
|
'How many text messages do you expect to send in the next year?',
|
2019-02-27 13:02:37 +00:00
|
|
|
|
things='text messages',
|
2019-02-27 15:10:34 +00:00
|
|
|
|
format_error_suffix='you expect to send',
|
2016-10-24 13:38:55 +01:00
|
|
|
|
)
|
2019-02-15 13:34:29 +00:00
|
|
|
|
volume_letter = ForgivingIntegerField(
|
2018-08-30 11:00:39 +01:00
|
|
|
|
'How many letters do you expect to send in the next year?',
|
2019-02-27 13:02:37 +00:00
|
|
|
|
things='letters',
|
2019-02-27 15:10:34 +00:00
|
|
|
|
format_error_suffix='you expect to send',
|
2016-10-24 13:38:55 +01:00
|
|
|
|
)
|
2019-02-15 11:03:19 +00:00
|
|
|
|
consent_to_research = RadioField(
|
2018-08-30 11:51:34 +01:00
|
|
|
|
'Can we contact you when we’re doing user research?',
|
|
|
|
|
|
choices=[
|
|
|
|
|
|
('yes', 'Yes'),
|
|
|
|
|
|
('no', 'No'),
|
|
|
|
|
|
],
|
2020-04-22 17:49:03 +01:00
|
|
|
|
thing='yes or no',
|
2018-08-30 11:51:34 +01:00
|
|
|
|
)
|
2016-05-11 09:43:55 +01:00
|
|
|
|
|
2019-02-15 13:34:29 +00:00
|
|
|
|
at_least_one_volume_filled = True
|
|
|
|
|
|
|
|
|
|
|
|
def validate(self, *args, **kwargs):
|
|
|
|
|
|
|
|
|
|
|
|
if self.volume_email.data == self.volume_sms.data == self.volume_letter.data == 0:
|
|
|
|
|
|
self.at_least_one_volume_filled = False
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
return super().validate(*args, **kwargs)
|
|
|
|
|
|
|
2016-05-11 09:43:55 +01:00
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class ProviderForm(StripWhitespaceForm):
|
2020-08-07 10:31:09 +01:00
|
|
|
|
priority = GovukIntegerField(
|
|
|
|
|
|
'Priority', [validators.NumberRange(min=1, max=100, message="Must be between 1 and 100")]
|
|
|
|
|
|
)
|
2016-05-16 13:09:58 +01:00
|
|
|
|
|
|
|
|
|
|
|
2019-12-02 18:15:00 +00:00
|
|
|
|
class ProviderRatioForm(StripWhitespaceForm):
|
|
|
|
|
|
|
|
|
|
|
|
ratio = RadioField(choices=[
|
|
|
|
|
|
(str(value), '{}% / {}%'.format(value, 100 - value))
|
|
|
|
|
|
for value in range(100, -10, -10)
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def percentage_left(self):
|
|
|
|
|
|
return int(self.ratio.data)
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def percentage_right(self):
|
|
|
|
|
|
return 100 - self.percentage_left
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-08-09 11:59:05 +01:00
|
|
|
|
class ServiceContactDetailsForm(StripWhitespaceForm):
|
|
|
|
|
|
contact_details_type = RadioField(
|
|
|
|
|
|
'Type of contact details',
|
|
|
|
|
|
choices=[
|
|
|
|
|
|
('url', 'Link'),
|
|
|
|
|
|
('email_address', 'Email address'),
|
|
|
|
|
|
('phone_number', 'Phone number'),
|
|
|
|
|
|
],
|
2018-06-05 10:37:41 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
2020-08-07 12:21:05 +01:00
|
|
|
|
url = GovukTextInputField("URL")
|
|
|
|
|
|
email_address = GovukEmailField("Email address")
|
2020-08-13 11:40:09 +01:00
|
|
|
|
# This is a text field because the number provided by the user can also be a short code
|
2020-08-07 12:21:05 +01:00
|
|
|
|
phone_number = GovukTextInputField("Phone number")
|
2018-08-09 11:59:05 +01:00
|
|
|
|
|
|
|
|
|
|
def validate(self):
|
|
|
|
|
|
|
|
|
|
|
|
if self.contact_details_type.data == 'url':
|
|
|
|
|
|
self.url.validators = [DataRequired(), URL(message='Must be a valid URL')]
|
|
|
|
|
|
|
|
|
|
|
|
elif self.contact_details_type.data == 'email_address':
|
|
|
|
|
|
self.email_address.validators = [DataRequired(), Length(min=5, max=255), ValidEmail()]
|
|
|
|
|
|
|
|
|
|
|
|
elif self.contact_details_type.data == 'phone_number':
|
|
|
|
|
|
# we can't use the existing phone number validation functions here since we want to allow landlines
|
|
|
|
|
|
def valid_phone_number(self, num):
|
|
|
|
|
|
try:
|
|
|
|
|
|
normalise_phone_number(num.data)
|
|
|
|
|
|
return True
|
|
|
|
|
|
except InvalidPhoneError:
|
|
|
|
|
|
raise ValidationError('Must be a valid phone number')
|
|
|
|
|
|
self.phone_number.validators = [DataRequired(), Length(min=5, max=20), valid_phone_number]
|
|
|
|
|
|
|
|
|
|
|
|
return super().validate()
|
|
|
|
|
|
|
2018-06-05 10:37:41 +01:00
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class ServiceReplyToEmailForm(StripWhitespaceForm):
|
2019-07-09 16:47:11 +01:00
|
|
|
|
email_address = email_address(label='Reply-to email address', gov_user=False)
|
2020-04-09 16:31:19 +01:00
|
|
|
|
is_default = govukCheckboxField("Make this email address the default")
|
2016-07-01 13:47:22 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class ServiceSmsSenderForm(StripWhitespaceForm):
|
2020-08-04 16:58:04 +01:00
|
|
|
|
sms_sender = GovukTextInputField(
|
2016-08-22 16:10:57 +01:00
|
|
|
|
'Text message sender',
|
|
|
|
|
|
validators=[
|
2019-09-12 16:42:33 +01:00
|
|
|
|
DataRequired(message="Cannot be empty"),
|
2018-01-31 10:26:37 +00:00
|
|
|
|
Length(max=11, message="Enter 11 characters or fewer"),
|
2020-04-21 12:51:54 +01:00
|
|
|
|
Length(min=3, message="Enter 3 characters or more"),
|
2020-04-02 15:57:46 +01:00
|
|
|
|
LettersNumbersFullStopsAndUnderscoresOnly(),
|
2018-02-28 11:50:41 +00:00
|
|
|
|
DoesNotStartWithDoubleZero(),
|
2016-08-22 16:10:57 +01:00
|
|
|
|
]
|
|
|
|
|
|
)
|
2020-04-09 16:31:19 +01:00
|
|
|
|
is_default = govukCheckboxField("Make this text message sender the default")
|
2016-07-01 13:47:22 +01:00
|
|
|
|
|
2016-08-08 10:28:40 +01:00
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class ServiceEditInboundNumberForm(StripWhitespaceForm):
|
2020-04-09 16:31:19 +01:00
|
|
|
|
is_default = govukCheckboxField("Make this text message sender the default")
|
2017-10-30 14:30:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class ServiceLetterContactBlockForm(StripWhitespaceForm):
|
2017-05-17 15:35:21 +01:00
|
|
|
|
letter_contact_block = TextAreaField(
|
|
|
|
|
|
validators=[
|
2019-09-12 16:42:33 +01:00
|
|
|
|
DataRequired(message="Cannot be empty"),
|
2017-05-17 15:35:21 +01:00
|
|
|
|
NoCommasInPlaceHolders()
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
2020-04-09 16:31:19 +01:00
|
|
|
|
is_default = govukCheckboxField("Set as your default address")
|
2017-03-02 15:56:28 +00:00
|
|
|
|
|
2017-10-27 10:56:03 +01:00
|
|
|
|
def validate_letter_contact_block(self, field):
|
2017-03-03 16:15:15 +00:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2019-02-18 16:27:53 +00:00
|
|
|
|
class ServiceOnOffSettingForm(StripWhitespaceForm):
|
|
|
|
|
|
|
2019-03-25 14:46:42 +00:00
|
|
|
|
def __init__(self, name, *args, truthy='On', falsey='Off', **kwargs):
|
2019-01-29 14:51:31 +00:00
|
|
|
|
super().__init__(*args, **kwargs)
|
2019-02-18 16:27:53 +00:00
|
|
|
|
self.enabled.label.text = name
|
2019-03-25 14:46:42 +00:00
|
|
|
|
self.enabled.choices = [
|
|
|
|
|
|
(True, truthy),
|
|
|
|
|
|
(False, falsey),
|
|
|
|
|
|
]
|
2019-02-18 16:27:53 +00:00
|
|
|
|
|
|
|
|
|
|
enabled = OnOffField('Choices')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ServiceSwitchChannelForm(ServiceOnOffSettingForm):
|
|
|
|
|
|
def __init__(self, channel, *args, **kwargs):
|
|
|
|
|
|
name = 'Send {}'.format({
|
2019-01-29 14:51:31 +00:00
|
|
|
|
'email': 'emails',
|
|
|
|
|
|
'sms': 'text messages',
|
|
|
|
|
|
'letter': 'letters',
|
|
|
|
|
|
}.get(channel))
|
2018-01-19 13:45:54 +00:00
|
|
|
|
|
2019-02-18 16:27:53 +00:00
|
|
|
|
super().__init__(name, *args, **kwargs)
|
2018-01-19 13:45:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
2019-02-19 17:26:16 +00:00
|
|
|
|
class SetEmailBranding(StripWhitespaceForm):
|
2016-08-08 10:28:40 +01:00
|
|
|
|
|
2018-11-08 14:46:18 +00:00
|
|
|
|
branding_style = RadioFieldWithNoneOption(
|
2018-02-07 10:30:49 +00:00
|
|
|
|
'Branding style',
|
2020-04-22 17:49:03 +01:00
|
|
|
|
thing='a branding style',
|
2016-08-08 10:28:40 +01:00
|
|
|
|
)
|
2016-09-20 12:30:00 +01:00
|
|
|
|
|
2018-11-12 09:04:39 +00:00
|
|
|
|
DEFAULT = (FieldWithNoneOption.NONE_OPTION_VALUE, 'GOV.UK')
|
2018-08-31 17:31:26 +01:00
|
|
|
|
|
2019-02-01 16:31:34 +00:00
|
|
|
|
def __init__(self, all_branding_options, current_branding):
|
2018-08-31 17:31:26 +01:00
|
|
|
|
|
2019-02-01 16:31:34 +00:00
|
|
|
|
super().__init__(branding_style=current_branding)
|
2018-08-31 17:31:26 +01:00
|
|
|
|
|
|
|
|
|
|
self.branding_style.choices = sorted(
|
2019-02-01 16:31:34 +00:00
|
|
|
|
all_branding_options + [self.DEFAULT],
|
2018-08-31 17:31:26 +01:00
|
|
|
|
key=lambda branding: (
|
2019-02-01 16:31:34 +00:00
|
|
|
|
branding[0] != current_branding,
|
2018-08-31 17:31:26 +01:00
|
|
|
|
branding[0] is not self.DEFAULT[0],
|
|
|
|
|
|
branding[1].lower(),
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2016-09-20 12:30:00 +01:00
|
|
|
|
|
2019-02-19 17:26:16 +00:00
|
|
|
|
class SetLetterBranding(SetEmailBranding):
|
2019-02-01 16:34:54 +00:00
|
|
|
|
# form is the same, but instead of GOV.UK we have None as a valid option
|
|
|
|
|
|
DEFAULT = (FieldWithNoneOption.NONE_OPTION_VALUE, 'None')
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-02-19 17:26:16 +00:00
|
|
|
|
class PreviewBranding(StripWhitespaceForm):
|
2018-08-02 14:46:01 +01:00
|
|
|
|
|
2018-11-12 09:04:39 +00:00
|
|
|
|
branding_style = HiddenFieldWithNoneOption('branding_style')
|
2018-08-02 14:46:01 +01:00
|
|
|
|
|
|
|
|
|
|
|
2018-02-07 10:30:49 +00:00
|
|
|
|
class ServiceUpdateEmailBranding(StripWhitespaceForm):
|
2020-08-06 15:36:34 +01:00
|
|
|
|
name = GovukTextInputField('Name of brand')
|
|
|
|
|
|
text = GovukTextInputField('Text')
|
|
|
|
|
|
colour = GovukTextInputField(
|
2018-02-07 10:30:49 +00:00
|
|
|
|
'Colour',
|
|
|
|
|
|
validators=[
|
2018-11-07 10:49:04 +00:00
|
|
|
|
Regexp(regex="^$|^#(?:[0-9a-fA-F]{3}){1,2}$", message='Must be a valid color hex code (starting with #)')
|
2020-08-06 15:36:34 +01:00
|
|
|
|
],
|
|
|
|
|
|
param_extensions={
|
|
|
|
|
|
"classes": "govuk-input--width-6",
|
|
|
|
|
|
"attributes": {"data-module": "colour-preview"}
|
|
|
|
|
|
}
|
2018-02-07 10:30:49 +00:00
|
|
|
|
)
|
2017-07-28 15:17:50 +01:00
|
|
|
|
file = FileField_wtf('Upload a PNG logo', validators=[FileAllowed(['png'], 'PNG Images only!')])
|
2018-08-23 14:21:41 +01:00
|
|
|
|
brand_type = RadioField(
|
2018-08-23 17:44:34 +01:00
|
|
|
|
"Brand type",
|
|
|
|
|
|
choices=[
|
|
|
|
|
|
('both', 'GOV.UK and branding'),
|
|
|
|
|
|
('org', 'Branding only'),
|
|
|
|
|
|
('org_banner', 'Branding banner'),
|
|
|
|
|
|
]
|
2018-08-23 14:21:41 +01:00
|
|
|
|
)
|
2017-07-28 15:17:50 +01:00
|
|
|
|
|
2019-11-01 10:43:01 +00:00
|
|
|
|
def validate_name(self, name):
|
2019-01-25 16:54:24 +00:00
|
|
|
|
op = request.form.get('operation')
|
2019-11-01 10:43:01 +00:00
|
|
|
|
if op == 'email-branding-details' and not self.name.data:
|
2019-01-25 16:54:24 +00:00
|
|
|
|
raise ValidationError('This field is required')
|
|
|
|
|
|
|
2017-07-28 15:17:50 +01:00
|
|
|
|
|
2019-01-31 16:56:35 +00:00
|
|
|
|
class SVGFileUpload(StripWhitespaceForm):
|
|
|
|
|
|
file = FileField_wtf(
|
|
|
|
|
|
'Upload an SVG logo',
|
|
|
|
|
|
validators=[
|
|
|
|
|
|
FileAllowed(['svg'], 'SVG Images only!'),
|
2020-03-04 14:25:14 +00:00
|
|
|
|
DataRequired(message="You need to upload a file to submit"),
|
|
|
|
|
|
NoEmbeddedImagesInSVG()
|
2019-01-31 16:56:35 +00:00
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ServiceLetterBrandingDetails(StripWhitespaceForm):
|
2020-08-07 14:06:51 +01:00
|
|
|
|
name = GovukTextInputField('Name of brand', validators=[DataRequired()])
|
2019-01-31 16:56:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
2018-09-26 16:42:40 +01:00
|
|
|
|
class PDFUploadForm(StripWhitespaceForm):
|
|
|
|
|
|
file = FileField_wtf(
|
2019-09-06 10:39:23 +01:00
|
|
|
|
'Upload a letter in PDF format',
|
2018-09-27 17:55:18 +01:00
|
|
|
|
validators=[
|
2019-10-15 15:56:06 +01:00
|
|
|
|
FileAllowed(['pdf'], 'Save your letter as a PDF and try again.'),
|
|
|
|
|
|
DataRequired(message="You need to choose a file to upload")
|
2018-09-27 17:55:18 +01:00
|
|
|
|
]
|
2018-09-26 16:42:40 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-08-07 10:48:21 +01:00
|
|
|
|
class EmailFieldInGuestList(GovukEmailField, StripWhitespaceStringField):
|
2017-12-11 16:22:37 +00:00
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-06-12 09:02:40 +01:00
|
|
|
|
class InternationalPhoneNumberInGuestList(InternationalPhoneNumber, StripWhitespaceStringField):
|
2017-12-11 16:22:37 +00:00
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-06-12 09:02:40 +01:00
|
|
|
|
class GuestList(StripWhitespaceForm):
|
2016-09-20 12:30:00 +01:00
|
|
|
|
|
|
|
|
|
|
def populate(self, email_addresses, phone_numbers):
|
2020-06-12 09:02:40 +01:00
|
|
|
|
for form_field, existing_guest_list in (
|
2016-09-20 12:30:00 +01:00
|
|
|
|
(self.email_addresses, email_addresses),
|
|
|
|
|
|
(self.phone_numbers, phone_numbers)
|
|
|
|
|
|
):
|
2020-06-12 09:02:40 +01:00
|
|
|
|
for index, value in enumerate(existing_guest_list):
|
2016-09-20 12:30:00 +01:00
|
|
|
|
form_field[index].data = value
|
|
|
|
|
|
|
|
|
|
|
|
email_addresses = FieldList(
|
2020-06-12 09:02:40 +01:00
|
|
|
|
EmailFieldInGuestList(
|
2016-09-20 12:30:00 +01:00
|
|
|
|
'',
|
|
|
|
|
|
validators=[
|
|
|
|
|
|
Optional(),
|
2018-02-14 14:35:16 +00:00
|
|
|
|
ValidEmail()
|
2016-09-20 12:30:00 +01:00
|
|
|
|
],
|
|
|
|
|
|
default=''
|
|
|
|
|
|
),
|
|
|
|
|
|
min_entries=5,
|
|
|
|
|
|
max_entries=5,
|
|
|
|
|
|
label="Email addresses"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
phone_numbers = FieldList(
|
2020-06-12 09:02:40 +01:00
|
|
|
|
InternationalPhoneNumberInGuestList(
|
2016-09-20 12:30:00 +01:00
|
|
|
|
'',
|
|
|
|
|
|
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-12-11 16:02:12 +00:00
|
|
|
|
class DateFilterForm(StripWhitespaceForm):
|
2020-08-07 10:24:56 +01:00
|
|
|
|
start_date = GovukDateField("Start Date", [validators.optional()])
|
|
|
|
|
|
end_date = GovukDateField("End Date", [validators.optional()])
|
2020-04-09 16:31:19 +01:00
|
|
|
|
include_from_test_key = govukCheckboxField("Include test keys")
|
2017-02-28 12:16:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
2019-07-22 13:09:03 +01:00
|
|
|
|
class RequiredDateFilterForm(StripWhitespaceForm):
|
2020-08-07 10:24:56 +01:00
|
|
|
|
start_date = GovukDateField("Start Date")
|
|
|
|
|
|
end_date = GovukDateField("End Date")
|
2019-07-22 13:09:03 +01:00
|
|
|
|
|
|
|
|
|
|
|
2019-02-06 17:32:13 +00:00
|
|
|
|
class SearchByNameForm(StripWhitespaceForm):
|
2017-03-14 10:46:38 +00:00
|
|
|
|
|
2020-08-07 10:16:24 +01:00
|
|
|
|
search = GovukSearchField(
|
2019-08-15 12:41:46 +01:00
|
|
|
|
'Search by name',
|
|
|
|
|
|
validators=[DataRequired("You need to enter full or partial name to search by.")],
|
|
|
|
|
|
)
|
2017-05-04 09:30:55 +01:00
|
|
|
|
|
|
|
|
|
|
|
2018-07-10 16:33:13 +01:00
|
|
|
|
class SearchUsersByEmailForm(StripWhitespaceForm):
|
|
|
|
|
|
|
2020-08-07 10:16:24 +01:00
|
|
|
|
search = GovukSearchField(
|
2018-07-10 17:24:20 +01:00
|
|
|
|
'Search by name or email address',
|
2018-07-10 16:33:13 +01:00
|
|
|
|
validators=[
|
2018-07-10 17:24:20 +01:00
|
|
|
|
DataRequired("You need to enter full or partial email address to search by.")
|
|
|
|
|
|
],
|
2018-07-10 16:33:13 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-01-26 17:21:06 +00:00
|
|
|
|
class SearchUsersForm(StripWhitespaceForm):
|
|
|
|
|
|
|
2020-08-07 10:16:24 +01:00
|
|
|
|
search = GovukSearchField('Search by name or email address')
|
2018-01-26 17:21:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class SearchNotificationsForm(StripWhitespaceForm):
|
2017-05-30 13:51:25 +01:00
|
|
|
|
|
2020-08-07 10:16:24 +01:00
|
|
|
|
to = GovukSearchField()
|
2018-08-07 14:44:09 +01:00
|
|
|
|
|
|
|
|
|
|
labels = {
|
|
|
|
|
|
'email': 'Search by email address',
|
|
|
|
|
|
'sms': 'Search by phone number',
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, message_type, *args, **kwargs):
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
self.to.label.text = self.labels.get(
|
|
|
|
|
|
message_type,
|
|
|
|
|
|
'Search by phone number or email address',
|
|
|
|
|
|
)
|
2017-05-30 13:51:25 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class PlaceholderForm(StripWhitespaceForm):
|
2017-05-04 09:30:55 +01:00
|
|
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class ServiceInboundNumberForm(StripWhitespaceForm):
|
2017-10-24 15:37:44 +01:00
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
self.inbound_number.choices = kwargs['inbound_number_choices']
|
|
|
|
|
|
|
|
|
|
|
|
inbound_number = RadioField(
|
|
|
|
|
|
"Select your inbound number",
|
2020-04-22 17:49:03 +01:00
|
|
|
|
thing='an inbound number',
|
2017-10-24 15:37:44 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-04-26 17:23:44 +01:00
|
|
|
|
class CallbackForm(StripWhitespaceForm):
|
2020-08-03 15:10:02 +01:00
|
|
|
|
url = GovukTextInputField(
|
|
|
|
|
|
"URL",
|
|
|
|
|
|
validators=[DataRequired(message='Cannot be empty'),
|
|
|
|
|
|
Regexp(regex="^https.*", message='Must be a valid https URL')]
|
|
|
|
|
|
)
|
|
|
|
|
|
bearer_token = GovukPasswordField(
|
|
|
|
|
|
"Bearer token",
|
|
|
|
|
|
validators=[DataRequired(message='Cannot be empty'),
|
|
|
|
|
|
Length(min=10, message='Must be at least 10 characters')]
|
|
|
|
|
|
)
|
2018-04-26 17:23:44 +01:00
|
|
|
|
|
|
|
|
|
|
def validate(self):
|
2018-07-06 11:47:35 +01:00
|
|
|
|
return super().validate() or self.url.data == ''
|
2018-04-26 17:23:44 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class SMSPrefixForm(StripWhitespaceForm):
|
2017-11-03 15:01:41 +00:00
|
|
|
|
enabled = RadioField(
|
2017-11-06 14:12:25 +00:00
|
|
|
|
'',
|
2017-11-03 15:01:41 +00:00
|
|
|
|
choices=[
|
|
|
|
|
|
('on', 'On'),
|
|
|
|
|
|
('off', 'Off'),
|
|
|
|
|
|
],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-05-04 09:30:55 +01:00
|
|
|
|
def get_placeholder_form_instance(
|
|
|
|
|
|
placeholder_name,
|
|
|
|
|
|
dict_to_populate_from,
|
2018-03-16 14:17:43 +00:00
|
|
|
|
template_type,
|
2017-05-25 09:40:37 +01:00
|
|
|
|
allow_international_phone_numbers=False,
|
2017-05-04 09:30:55 +01:00
|
|
|
|
):
|
|
|
|
|
|
|
2018-03-16 14:17:43 +00:00
|
|
|
|
if (
|
|
|
|
|
|
Columns.make_key(placeholder_name) == 'emailaddress' and
|
|
|
|
|
|
template_type == 'email'
|
|
|
|
|
|
):
|
2017-05-25 09:40:37 +01:00
|
|
|
|
field = email_address(label=placeholder_name, gov_user=False)
|
2018-03-16 14:17:43 +00:00
|
|
|
|
elif (
|
|
|
|
|
|
Columns.make_key(placeholder_name) == 'phonenumber' and
|
|
|
|
|
|
template_type == 'sms'
|
|
|
|
|
|
):
|
2017-05-25 09:40:37 +01:00
|
|
|
|
if allow_international_phone_numbers:
|
|
|
|
|
|
field = international_phone_number(label=placeholder_name)
|
|
|
|
|
|
else:
|
2017-08-29 14:52:24 +01:00
|
|
|
|
field = uk_mobile_number(label=placeholder_name)
|
2017-05-25 09:40:37 +01:00
|
|
|
|
else:
|
2020-08-07 15:16:04 +01:00
|
|
|
|
field = GovukTextInputField(placeholder_name, validators=[
|
2019-09-12 16:42:33 +01:00
|
|
|
|
DataRequired(message='Cannot be empty')
|
2017-05-25 09:40:37 +01:00
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
PlaceholderForm.placeholder_value = field
|
2017-05-04 09:30:55 +01:00
|
|
|
|
|
|
|
|
|
|
return PlaceholderForm(
|
|
|
|
|
|
placeholder_value=dict_to_populate_from.get(placeholder_name, '')
|
|
|
|
|
|
)
|
2017-10-16 16:35:35 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-11 16:02:12 +00:00
|
|
|
|
class SetSenderForm(StripWhitespaceForm):
|
2017-10-16 16:35:35 +01:00
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
self.sender.choices = kwargs['sender_choices']
|
|
|
|
|
|
self.sender.label.text = kwargs['sender_label']
|
|
|
|
|
|
|
|
|
|
|
|
sender = RadioField()
|
2018-01-03 10:44:36 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SetTemplateSenderForm(StripWhitespaceForm):
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
self.sender.choices = kwargs['sender_choices']
|
|
|
|
|
|
self.sender.label.text = 'Select your sender'
|
|
|
|
|
|
|
|
|
|
|
|
sender = RadioField()
|
2018-02-12 09:26:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LinkOrganisationsForm(StripWhitespaceForm):
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
self.organisations.choices = kwargs['choices']
|
|
|
|
|
|
|
|
|
|
|
|
organisations = RadioField(
|
|
|
|
|
|
'Select an organisation',
|
|
|
|
|
|
validators=[
|
|
|
|
|
|
DataRequired()
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
2018-05-18 14:05:48 +01:00
|
|
|
|
|
|
|
|
|
|
|
2020-01-17 17:03:07 +00:00
|
|
|
|
class BrandingOptions(StripWhitespaceForm):
|
2018-05-18 14:05:48 +01:00
|
|
|
|
|
2019-09-12 14:33:11 +01:00
|
|
|
|
FALLBACK_OPTION_VALUE = 'something_else'
|
|
|
|
|
|
FALLBACK_OPTION = (FALLBACK_OPTION_VALUE, 'Something else')
|
|
|
|
|
|
|
2020-01-17 17:03:07 +00:00
|
|
|
|
options = RadioField('Choose your new branding')
|
2019-09-12 14:33:11 +01:00
|
|
|
|
something_else = TextAreaField('Describe the branding you want')
|
2018-07-17 14:39:04 +01:00
|
|
|
|
|
2020-01-17 17:03:07 +00:00
|
|
|
|
def __init__(self, service, *args, branding_type="email", **kwargs):
|
2019-09-12 13:45:35 +01:00
|
|
|
|
super().__init__(*args, **kwargs)
|
2020-01-17 17:03:07 +00:00
|
|
|
|
self.options.choices = tuple(self.get_available_choices(service, branding_type))
|
2020-01-21 14:52:21 +00:00
|
|
|
|
self.options.label.text = 'Choose your new {} branding'.format(branding_type)
|
2019-10-09 16:42:30 +01:00
|
|
|
|
if self.something_else_is_only_option:
|
|
|
|
|
|
self.options.data = self.FALLBACK_OPTION_VALUE
|
2019-09-12 13:45:35 +01:00
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
2020-01-17 17:03:07 +00:00
|
|
|
|
def get_available_choices(service, branding_type):
|
|
|
|
|
|
if branding_type == "email":
|
|
|
|
|
|
organisation_branding_id = service.organisation.email_branding_id if service.organisation else None
|
|
|
|
|
|
service_branding_id = service.email_branding_id
|
|
|
|
|
|
service_branding_name = service.email_branding_name
|
|
|
|
|
|
elif branding_type == "letter":
|
|
|
|
|
|
organisation_branding_id = service.organisation.letter_branding_id if service.organisation else None
|
|
|
|
|
|
service_branding_id = service.letter_branding_id
|
|
|
|
|
|
service_branding_name = service.letter_branding_name
|
2019-09-12 13:45:35 +01:00
|
|
|
|
|
|
|
|
|
|
if (
|
2020-01-21 16:39:15 +00:00
|
|
|
|
service.organisation_type == Organisation.TYPE_CENTRAL
|
|
|
|
|
|
and organisation_branding_id is None
|
|
|
|
|
|
and service_branding_id is not None
|
|
|
|
|
|
and branding_type == "email"
|
2019-09-12 13:45:35 +01:00
|
|
|
|
):
|
|
|
|
|
|
yield ('govuk', 'GOV.UK')
|
|
|
|
|
|
|
|
|
|
|
|
if (
|
2020-01-21 16:39:15 +00:00
|
|
|
|
service.organisation_type == Organisation.TYPE_CENTRAL
|
|
|
|
|
|
and service.organisation
|
|
|
|
|
|
and organisation_branding_id is None
|
|
|
|
|
|
and service_branding_name.lower() != 'GOV.UK and {}'.format(service.organisation.name).lower()
|
|
|
|
|
|
and branding_type == "email"
|
2019-09-12 13:45:35 +01:00
|
|
|
|
):
|
|
|
|
|
|
yield ('govuk_and_org', 'GOV.UK and {}'.format(service.organisation.name))
|
|
|
|
|
|
|
|
|
|
|
|
if (
|
2019-09-12 15:03:32 +01:00
|
|
|
|
service.organisation_type in {
|
|
|
|
|
|
Organisation.TYPE_NHS_CENTRAL,
|
|
|
|
|
|
Organisation.TYPE_NHS_LOCAL,
|
|
|
|
|
|
Organisation.TYPE_NHS_GP,
|
2020-01-21 16:39:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
and service_branding_name != 'NHS'
|
2019-09-12 13:45:35 +01:00
|
|
|
|
):
|
|
|
|
|
|
yield ('nhs', 'NHS')
|
|
|
|
|
|
|
|
|
|
|
|
if (
|
2020-01-21 16:39:15 +00:00
|
|
|
|
service.organisation
|
|
|
|
|
|
and service.organisation_type not in {
|
2019-09-12 15:03:32 +01:00
|
|
|
|
Organisation.TYPE_NHS_LOCAL,
|
|
|
|
|
|
Organisation.TYPE_NHS_CENTRAL,
|
|
|
|
|
|
Organisation.TYPE_NHS_GP,
|
2020-01-21 16:39:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
and (
|
|
|
|
|
|
service_branding_id is None
|
|
|
|
|
|
or service_branding_id != organisation_branding_id
|
2019-09-12 13:45:35 +01:00
|
|
|
|
)
|
|
|
|
|
|
):
|
|
|
|
|
|
yield ('organisation', service.organisation.name)
|
|
|
|
|
|
|
2020-01-17 17:03:07 +00:00
|
|
|
|
yield BrandingOptions.FALLBACK_OPTION
|
2019-09-12 14:33:11 +01:00
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def something_else_is_only_option(self):
|
|
|
|
|
|
return self.options.choices == (self.FALLBACK_OPTION,)
|
|
|
|
|
|
|
|
|
|
|
|
def validate_something_else(self, field):
|
|
|
|
|
|
if (
|
2020-01-21 16:39:15 +00:00
|
|
|
|
self.something_else_is_only_option
|
|
|
|
|
|
or self.options.data == self.FALLBACK_OPTION_VALUE
|
2019-09-12 14:33:11 +01:00
|
|
|
|
) and not field.data:
|
2019-09-18 14:21:25 +01:00
|
|
|
|
raise ValidationError('Cannot be empty')
|
2019-09-12 14:33:11 +01:00
|
|
|
|
|
|
|
|
|
|
if self.options.data != self.FALLBACK_OPTION_VALUE:
|
|
|
|
|
|
field.data = ''
|
2019-09-12 13:45:35 +01:00
|
|
|
|
|
2018-07-17 14:39:04 +01:00
|
|
|
|
|
|
|
|
|
|
class ServiceDataRetentionForm(StripWhitespaceForm):
|
|
|
|
|
|
|
|
|
|
|
|
notification_type = RadioField(
|
|
|
|
|
|
'What notification type?',
|
|
|
|
|
|
choices=[
|
|
|
|
|
|
('email', 'Email'),
|
|
|
|
|
|
('sms', 'SMS'),
|
|
|
|
|
|
('letter', 'Letter'),
|
|
|
|
|
|
],
|
|
|
|
|
|
validators=[DataRequired()],
|
|
|
|
|
|
)
|
2020-08-07 10:31:09 +01:00
|
|
|
|
days_of_retention = GovukIntegerField(
|
|
|
|
|
|
label="Days of retention",
|
|
|
|
|
|
validators=[validators.NumberRange(min=3, max=90, message="Must be between 3 and 90")],
|
|
|
|
|
|
)
|
2018-07-17 14:39:04 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ServiceDataRetentionEditForm(StripWhitespaceForm):
|
2020-08-07 10:31:09 +01:00
|
|
|
|
days_of_retention = GovukIntegerField(
|
|
|
|
|
|
label="Days of retention",
|
|
|
|
|
|
validators=[validators.NumberRange(min=3, max=90, message="Must be between 3 and 90")],
|
|
|
|
|
|
)
|
2018-09-06 16:34:23 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ReturnedLettersForm(StripWhitespaceForm):
|
|
|
|
|
|
references = TextAreaField(
|
|
|
|
|
|
u'Letter references',
|
|
|
|
|
|
validators=[
|
2019-09-12 16:42:33 +01:00
|
|
|
|
DataRequired(message="Cannot be empty"),
|
2018-09-06 16:34:23 +01:00
|
|
|
|
]
|
|
|
|
|
|
)
|
2018-11-01 16:02:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TemplateFolderForm(StripWhitespaceForm):
|
2019-03-18 16:12:12 +00:00
|
|
|
|
def __init__(self, all_service_users=None, *args, **kwargs):
|
2019-03-15 14:13:27 +00:00
|
|
|
|
super().__init__(*args, **kwargs)
|
2019-03-18 16:12:12 +00:00
|
|
|
|
if all_service_users is not None:
|
|
|
|
|
|
self.users_with_permission.all_service_users = all_service_users
|
|
|
|
|
|
self.users_with_permission.choices = [
|
|
|
|
|
|
(item.id, item.name) for item in all_service_users
|
2019-03-15 14:13:27 +00:00
|
|
|
|
]
|
|
|
|
|
|
|
2020-04-09 20:56:17 +01:00
|
|
|
|
users_with_permission = govukCollapsibleCheckboxesField(
|
|
|
|
|
|
'Team members who can see this folder',
|
|
|
|
|
|
field_label='folder')
|
2020-08-03 14:34:18 +01:00
|
|
|
|
name = GovukTextInputField('Folder name', validators=[DataRequired(message='Cannot be empty')])
|
2018-11-08 11:56:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
2018-11-20 18:03:57 +00:00
|
|
|
|
def required_for_ops(*operations):
|
|
|
|
|
|
operations = set(operations)
|
|
|
|
|
|
|
|
|
|
|
|
def validate(form, field):
|
2018-12-07 16:38:48 +00:00
|
|
|
|
if form.op not in operations and any(field.raw_data):
|
2018-11-20 18:03:57 +00:00
|
|
|
|
# super weird
|
2018-12-07 16:38:48 +00:00
|
|
|
|
raise validators.StopValidation('Must be empty')
|
|
|
|
|
|
if form.op in operations and not any(field.raw_data):
|
2019-09-12 16:42:33 +01:00
|
|
|
|
raise validators.StopValidation('Cannot be empty')
|
2018-11-20 18:03:57 +00:00
|
|
|
|
return validate
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-11-08 11:56:29 +00:00
|
|
|
|
class TemplateAndFoldersSelectionForm(Form):
|
2018-11-27 14:33:50 +00:00
|
|
|
|
"""
|
2018-12-07 16:38:48 +00:00
|
|
|
|
This form expects the form data to include an operation, based on which submit button is clicked.
|
2018-11-27 14:33:50 +00:00
|
|
|
|
If enter is pressed, unknown will be sent by a hidden submit button at the top of the form.
|
|
|
|
|
|
The value of this operation affects which fields are required, expected to be empty, or optional.
|
|
|
|
|
|
|
|
|
|
|
|
* unknown
|
|
|
|
|
|
currently not implemented, but in the future will try and work out if there are any obvious commands that can be
|
|
|
|
|
|
assumed based on which fields are empty vs populated.
|
2018-12-04 14:47:05 +00:00
|
|
|
|
* move-to-existing-folder
|
2018-11-27 14:33:50 +00:00
|
|
|
|
must have data for templates_and_folders checkboxes, and move_to radios
|
2018-12-04 14:47:05 +00:00
|
|
|
|
* move-to-new-folder
|
2018-11-27 14:33:50 +00:00
|
|
|
|
must have data for move_to_new_folder_name, cannot have data for move_to_existing_folder_name
|
2018-12-04 14:47:05 +00:00
|
|
|
|
* add-new-folder
|
2018-11-27 14:33:50 +00:00
|
|
|
|
must have data for move_to_existing_folder_name, cannot have data for move_to_new_folder_name
|
|
|
|
|
|
"""
|
2018-11-08 11:56:29 +00:00
|
|
|
|
|
|
|
|
|
|
ALL_TEMPLATES_FOLDER = {
|
2019-01-04 13:34:15 +00:00
|
|
|
|
'name': 'Templates',
|
2018-11-12 09:04:39 +00:00
|
|
|
|
'id': RadioFieldWithNoneOption.NONE_OPTION_VALUE,
|
2018-11-08 11:56:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
|
self,
|
2018-11-23 16:29:21 +00:00
|
|
|
|
all_template_folders,
|
|
|
|
|
|
template_list,
|
2020-07-01 16:43:08 +01:00
|
|
|
|
available_template_types,
|
2018-11-28 13:48:13 +00:00
|
|
|
|
allow_adding_copy_of_template,
|
2018-11-08 11:56:29 +00:00
|
|
|
|
*args,
|
|
|
|
|
|
**kwargs
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
2020-07-01 16:43:08 +01:00
|
|
|
|
self.available_template_types = available_template_types
|
|
|
|
|
|
|
2018-11-23 16:29:21 +00:00
|
|
|
|
self.templates_and_folders.choices = template_list.as_id_and_name
|
2018-11-08 11:56:29 +00:00
|
|
|
|
|
2018-11-20 18:03:57 +00:00
|
|
|
|
self.op = None
|
2018-11-28 13:48:13 +00:00
|
|
|
|
self.is_move_op = self.is_add_folder_op = self.is_add_template_op = False
|
2018-11-15 17:31:07 +00:00
|
|
|
|
|
2018-12-18 14:40:53 +00:00
|
|
|
|
self.move_to.all_template_folders = all_template_folders
|
2018-11-23 16:29:21 +00:00
|
|
|
|
self.move_to.choices = [
|
|
|
|
|
|
(item['id'], item['name'])
|
|
|
|
|
|
for item in ([self.ALL_TEMPLATES_FOLDER] + all_template_folders)
|
2018-11-08 11:56:29 +00:00
|
|
|
|
]
|
|
|
|
|
|
|
2018-11-30 15:02:55 +00:00
|
|
|
|
self.add_template_by_template_type.choices = list(filter(None, [
|
2020-08-10 15:12:07 +01:00
|
|
|
|
('email', 'Email') if 'email' in available_template_types else None,
|
|
|
|
|
|
('sms', 'Text message') if 'sms' in available_template_types else None,
|
2020-07-01 16:43:08 +01:00
|
|
|
|
('letter', 'Letter') if 'letter' in available_template_types else None,
|
|
|
|
|
|
('broadcast', 'Broadcast') if 'broadcast' in available_template_types else None,
|
2019-02-20 14:39:40 +00:00
|
|
|
|
('copy-existing', 'Copy an existing template') if allow_adding_copy_of_template else None,
|
2018-11-30 15:02:55 +00:00
|
|
|
|
]))
|
2018-11-28 13:48:13 +00:00
|
|
|
|
|
2020-07-01 16:43:08 +01:00
|
|
|
|
@property
|
|
|
|
|
|
def trying_to_add_unavailable_template_type(self):
|
|
|
|
|
|
return all((
|
|
|
|
|
|
self.is_add_template_op,
|
|
|
|
|
|
self.add_template_by_template_type.data,
|
|
|
|
|
|
self.add_template_by_template_type.data not in self.available_template_types,
|
|
|
|
|
|
))
|
|
|
|
|
|
|
2018-11-30 16:31:42 +00:00
|
|
|
|
def is_selected(self, template_folder_id):
|
|
|
|
|
|
return template_folder_id in (self.templates_and_folders.data or [])
|
|
|
|
|
|
|
2018-11-15 17:31:07 +00:00
|
|
|
|
def validate(self):
|
2018-11-20 18:03:57 +00:00
|
|
|
|
self.op = request.form.get('operation')
|
2018-11-15 17:31:07 +00:00
|
|
|
|
|
2018-12-04 14:47:05 +00:00
|
|
|
|
self.is_move_op = self.op in {'move-to-existing-folder', 'move-to-new-folder'}
|
|
|
|
|
|
self.is_add_folder_op = self.op in {'add-new-folder', 'move-to-new-folder'}
|
|
|
|
|
|
self.is_add_template_op = self.op in {'add-new-template'}
|
2018-11-15 17:31:07 +00:00
|
|
|
|
|
2018-11-28 13:48:13 +00:00
|
|
|
|
if not (self.is_add_folder_op or self.is_move_op or self.is_add_template_op):
|
2018-11-15 17:31:07 +00:00
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
return super().validate()
|
|
|
|
|
|
|
2018-11-20 18:03:57 +00:00
|
|
|
|
def get_folder_name(self):
|
2018-12-04 14:47:05 +00:00
|
|
|
|
if self.op == 'add-new-folder':
|
2018-11-20 18:03:57 +00:00
|
|
|
|
return self.add_new_folder_name.data
|
2018-12-04 14:47:05 +00:00
|
|
|
|
elif self.op == 'move-to-new-folder':
|
2018-11-20 18:03:57 +00:00
|
|
|
|
return self.move_to_new_folder_name.data
|
|
|
|
|
|
return None
|
2018-11-15 17:31:07 +00:00
|
|
|
|
|
2020-04-09 11:51:11 +01:00
|
|
|
|
templates_and_folders = govukCheckboxesField(
|
|
|
|
|
|
'Choose templates or folders',
|
|
|
|
|
|
validators=[required_for_ops('move-to-new-folder', 'move-to-existing-folder')],
|
|
|
|
|
|
choices=[], # added to keep order of arguments, added properly in __init__
|
|
|
|
|
|
param_extensions={
|
|
|
|
|
|
"fieldset": {
|
|
|
|
|
|
"legend": {
|
|
|
|
|
|
"classes": "govuk-visually-hidden"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
2019-01-03 14:58:20 +00:00
|
|
|
|
# if no default set, it is set to None, which process_data transforms to '__NONE__'
|
|
|
|
|
|
# this means '__NONE__' (self.ALL_TEMPLATES option) is selected when no form data has been submitted
|
|
|
|
|
|
# set default to empty string so process_data method doesn't perform any transformation
|
2018-12-18 14:40:53 +00:00
|
|
|
|
move_to = NestedRadioField(
|
|
|
|
|
|
'Choose a folder',
|
2019-01-02 17:12:18 +00:00
|
|
|
|
default='',
|
2018-12-18 14:40:53 +00:00
|
|
|
|
validators=[
|
2019-01-03 14:58:20 +00:00
|
|
|
|
required_for_ops('move-to-existing-folder'),
|
|
|
|
|
|
Optional()
|
2018-12-18 14:40:53 +00:00
|
|
|
|
])
|
2020-08-05 11:57:00 +01:00
|
|
|
|
add_new_folder_name = GovukTextInputField('Folder name', validators=[required_for_ops('add-new-folder')])
|
|
|
|
|
|
move_to_new_folder_name = GovukTextInputField('Folder name', validators=[required_for_ops('move-to-new-folder')])
|
2018-11-28 13:48:13 +00:00
|
|
|
|
|
2019-02-20 14:39:40 +00:00
|
|
|
|
add_template_by_template_type = RadioFieldWithRequiredMessage('New template', validators=[
|
2018-12-07 16:38:48 +00:00
|
|
|
|
required_for_ops('add-new-template'),
|
2018-12-03 17:29:23 +00:00
|
|
|
|
Optional(),
|
2018-12-07 17:09:15 +00:00
|
|
|
|
], required_message='Select the type of template you want to add')
|
2019-02-15 10:27:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ClearCacheForm(StripWhitespaceForm):
|
|
|
|
|
|
model_type = RadioField(
|
|
|
|
|
|
'What do you want to clear today',
|
|
|
|
|
|
)
|
2019-05-13 14:50:40 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GoLiveNotesForm(StripWhitespaceForm):
|
|
|
|
|
|
request_to_go_live_notes = TextAreaField(
|
|
|
|
|
|
'Go live notes',
|
|
|
|
|
|
filters=[lambda x: x or None],
|
|
|
|
|
|
)
|
2019-06-18 14:24:29 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AcceptAgreementForm(StripWhitespaceForm):
|
|
|
|
|
|
|
2019-06-18 15:26:04 +01:00
|
|
|
|
@classmethod
|
|
|
|
|
|
def from_organisation(cls, org):
|
|
|
|
|
|
|
|
|
|
|
|
if org.agreement_signed_on_behalf_of_name and org.agreement_signed_on_behalf_of_email_address:
|
|
|
|
|
|
who = 'someone-else'
|
|
|
|
|
|
elif org.agreement_signed_version: # only set if user has submitted form previously
|
|
|
|
|
|
who = 'me'
|
|
|
|
|
|
else:
|
|
|
|
|
|
who = None
|
|
|
|
|
|
|
|
|
|
|
|
return cls(
|
|
|
|
|
|
version=org.agreement_signed_version,
|
|
|
|
|
|
who=who,
|
|
|
|
|
|
on_behalf_of_name=org.agreement_signed_on_behalf_of_name,
|
|
|
|
|
|
on_behalf_of_email=org.agreement_signed_on_behalf_of_email_address,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2020-08-07 15:16:04 +01:00
|
|
|
|
version = GovukTextInputField(
|
2019-07-18 10:46:01 +01:00
|
|
|
|
'Which version of the agreement do you want to accept?'
|
2019-06-18 14:24:29 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
who = RadioField(
|
2019-07-18 15:58:24 +01:00
|
|
|
|
'Who are you accepting the agreement for?',
|
2019-06-18 14:24:29 +01:00
|
|
|
|
choices=(
|
|
|
|
|
|
(
|
|
|
|
|
|
'me',
|
2019-07-18 15:58:24 +01:00
|
|
|
|
'Yourself',
|
2019-06-18 14:24:29 +01:00
|
|
|
|
),
|
|
|
|
|
|
(
|
|
|
|
|
|
'someone-else',
|
2019-07-18 15:58:24 +01:00
|
|
|
|
'Someone else',
|
2019-06-18 14:24:29 +01:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2020-08-07 15:16:04 +01:00
|
|
|
|
on_behalf_of_name = GovukTextInputField(
|
2019-07-18 10:46:01 +01:00
|
|
|
|
'What’s their name?'
|
2019-06-18 14:24:29 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
on_behalf_of_email = email_address(
|
|
|
|
|
|
'What’s their email address?',
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
gov_user=False,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2019-06-18 15:26:04 +01:00
|
|
|
|
def __validate_if_nominating(self, field):
|
|
|
|
|
|
if self.who.data == 'someone-else':
|
|
|
|
|
|
if not field.data:
|
2019-09-12 16:42:33 +01:00
|
|
|
|
raise ValidationError('Cannot be empty')
|
2019-06-18 15:26:04 +01:00
|
|
|
|
else:
|
|
|
|
|
|
field.data = ''
|
|
|
|
|
|
|
|
|
|
|
|
validate_on_behalf_of_name = __validate_if_nominating
|
|
|
|
|
|
validate_on_behalf_of_email = __validate_if_nominating
|
|
|
|
|
|
|
2019-06-18 14:24:29 +01:00
|
|
|
|
def validate_version(self, field):
|
|
|
|
|
|
try:
|
|
|
|
|
|
float(field.data)
|
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
|
|
raise ValidationError("Must be a number")
|
2020-07-06 10:53:16 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BroadcastAreaForm(StripWhitespaceForm):
|
|
|
|
|
|
|
2020-07-09 12:04:18 +01:00
|
|
|
|
areas = govukCheckboxesField('Choose areas to broadcast to')
|
2020-07-06 10:53:16 +01:00
|
|
|
|
|
|
|
|
|
|
def __init__(self, choices, *args, **kwargs):
|
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
self.areas.choices = choices
|
2020-07-09 12:04:18 +01:00
|
|
|
|
self.areas.render_as_list = True
|
|
|
|
|
|
self.areas.param_extensions = {'fieldset': {'legend': {'classes': 'govuk-visually-hidden'}}}
|
2020-07-06 10:53:16 +01:00
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def from_library(cls, library):
|
|
|
|
|
|
return cls(choices=[
|
|
|
|
|
|
(area.id, area.name) for area in sorted(library)
|
|
|
|
|
|
])
|
2020-07-31 16:20:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BroadcastAreaFormWithSelectAll(BroadcastAreaForm):
|
|
|
|
|
|
|
|
|
|
|
|
select_all = govukCheckboxField('Select all')
|
|
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def from_library(cls, library, select_all_choice):
|
|
|
|
|
|
instance = super().from_library(library)
|
|
|
|
|
|
(
|
|
|
|
|
|
instance.select_all.area_slug,
|
|
|
|
|
|
instance.select_all.label.text,
|
|
|
|
|
|
) = select_all_choice
|
|
|
|
|
|
return instance
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def selected_areas(self):
|
|
|
|
|
|
if self.select_all.data:
|
|
|
|
|
|
return [self.select_all.area_slug]
|
|
|
|
|
|
return self.areas.data
|