mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-02-20 10:24:39 -05:00
The GDS Way™[1] recommends using Flake8 to lint Python projects.
This commit takes the Flake8 config from Digital Marketplace API[2] and
removes the bits we don’t need.
It changes the `max_complexity` setting to 14, which is the most complex
code we have in this repo currently (we shouldn’t be writing code _more_
complex than what we already have).
This commit also fixes the errors found by Flake8, which includes 6(!)
tests which were never getting run because they had the same names as
existing tests.
Here is a full list of the errors that were found and fixed:
```
./app/__init__.py:2:1: F401 're' imported but unused
./app/__init__.py:4:1: F401 'json' imported but unused
./app/__init__.py:8:1: F401 'dateutil' imported but unused
./app/__init__.py:11:1: F401 'flask.escape' imported but unused
./app/__init__.py:41:1: F401 'app.proxy_fix' imported but unused
./app/__init__.py:129:5: F821 undefined name 'proxy_fix'
./app/__init__.py:221:19: F821 undefined name 'highlight'
./app/__init__.py:221:35: F821 undefined name 'JavascriptLexer'
./app/__init__.py:221:54: F821 undefined name 'HtmlFormatter'
./app/config.py:2:1: F401 'datetime.timedelta' imported but unused
./app/event_handlers.py:2:1: F401 'flask_login.current_user' imported but unused
./app/utils.py:11:1: F401 'dateutil.parser' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.two_factor' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.notifications' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.add_service' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.forgot_password' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.inbound_number' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.styleguide' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.organisations' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.letter_jobs' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.verify' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.conversation' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.api_keys' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.send' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.dashboard' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.jobs' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.manage_users' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.sign_in' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.sign_out' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.code_not_received' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.invites' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.platform_admin' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.providers' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.service_settings' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.index' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.new_password' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.user_profile' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.feedback' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.choose_service' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.templates' imported but unused
./app/main/__init__.py:5:1: F401 'app.main.views.register' imported but unused
./app/main/forms.py:12:1: F401 'wtforms.SelectField' imported but unused
./app/main/views/api_keys.py:37:29: E241 multiple spaces after ':'
./app/main/views/feedback.py:3:1: F401 'flask.flash' imported but unused
./app/main/views/feedback.py:122:17: E123 closing bracket does not match indentation of opening bracket's line
./app/main/views/inbound_number.py:1:1: F401 'flask.url_for' imported but unused
./app/main/views/inbound_number.py:1:1: F401 'flask.session' imported but unused
./app/main/views/inbound_number.py:1:1: F401 'flask.redirect' imported but unused
./app/main/views/inbound_number.py:1:1: F401 'flask.request' imported but unused
./app/main/views/inbound_number.py:13:1: F401 'flask.jsonify' imported but unused
./app/main/views/jobs.py:31:1: F401 'app.utils.get_template' imported but unused
./app/main/views/letter_jobs.py:1:1: F401 'datetime' imported but unused
./app/main/views/letter_jobs.py:6:1: F401 'app.format_datetime_24h' imported but unused
./app/main/views/manage_users.py:111:9: E123 closing bracket does not match indentation of opening bracket's line
./app/main/views/notifications.py:121:5: F841 local variable 'status_args' is assigned to but never used
./app/main/views/organisations.py:1:1: F401 'flask.request' imported but unused
./app/main/views/service_settings.py:77:9: E123 closing bracket does not match indentation of opening bracket's line
./app/main/views/service_settings.py:82:9: E123 closing bracket does not match indentation of opening bracket's line
./app/main/views/service_settings.py:420:13: E123 closing bracket does not match indentation of opening bracket's line
./app/main/views/sign_in.py:12:1: F401 'flask_login.confirm_login' imported but unused
./app/main/views/sign_in.py:17:1: F401 'app.service_api_client' imported but unused
./app/main/views/sign_in.py:62:13: E123 closing bracket does not match indentation of opening bracket's line
./app/main/views/templates.py:4:1: F401 'flask.json' imported but unused
./app/main/views/templates.py:17:1: F401 'notifications_utils.formatters.escape_html' imported but unused
./app/main/views/templates.py:23:1: F401 'app.utils.get_help_argument' imported but unused
./app/main/views/templates.py:64:13: E123 closing bracket does not match indentation of opening bracket's line
./app/notify_client/service_api_client.py:6:1: F401 '.notification_api_client' imported but unused
./app/notify_client/user_api_client.py:1:1: F401 'uuid' imported but unused
./app/notify_client/user_api_client.py:3:1: F401 'flask.session' imported but unused
./tests/__init__.py:1:1: F401 'csv' imported but unused
./tests/app/main/test_asset_fingerprinter.py:2:1: F401 'os' imported but unused
./tests/app/main/test_asset_fingerprinter.py:4:1: F401 'unittest.mock' imported but unused
./tests/app/main/test_asset_fingerprinter.py:98:9: F841 local variable 'string_with_unicode_character' is assigned to but never used
./tests/app/main/test_errorhandlers.py:2:1: F401 'flask.url_for' imported but unused
./tests/app/main/test_permissions.py:26:13: F841 local variable 'response' is assigned to but never used
./tests/app/main/test_placeholder_form.py:3:1: F401 'wtforms.Label' imported but unused
./tests/app/main/test_placeholder_form.py:11:10: F841 local variable 'req' is assigned to but never used
./tests/app/main/test_two_factor_form.py:10:67: F841 local variable 'req' is assigned to but never used
./tests/app/main/test_two_factor_form.py:23:65: F841 local variable 'req' is assigned to but never used
./tests/app/main/test_two_factor_form.py:37:48: F841 local variable 'req' is assigned to but never used
./tests/app/main/test_two_factor_form.py:51:67: F841 local variable 'req' is assigned to but never used
./tests/app/main/test_two_factor_form.py:65:67: F841 local variable 'req' is assigned to but never used
./tests/app/main/views/test_accept_invite.py:356:5: F841 local variable 'element' is assigned to but never used
./tests/app/main/views/test_activity.py:11:1: F811 redefinition of unused 'mock_get_notifications' from line 11
./tests/app/main/views/test_activity.py:18:1: F401 'datetime.datetime' imported but unused
./tests/app/main/views/test_activity.py:102:5: F841 local variable 'content' is assigned to but never used
./tests/app/main/views/test_activity.py:104:5: F841 local variable 'notification' is assigned to but never used
./tests/app/main/views/test_activity.py:337:5: F841 local variable '_notifications_mock' is assigned to but never used
./tests/app/main/views/test_activity.py:373:13: E126 continuation line over-indented for hanging indent
./tests/app/main/views/test_activity.py:378:9: E121 continuation line under-indented for hanging indent
./tests/app/main/views/test_activity.py:404:13: E126 continuation line over-indented for hanging indent
./tests/app/main/views/test_activity.py:407:9: E121 continuation line under-indented for hanging indent
./tests/app/main/views/test_api_keys.py:354:5: F841 local variable 'response' is assigned to but never used
./tests/app/main/views/test_conversation.py:5:1: F401 'bs4.BeautifulSoup' imported but unused
./tests/app/main/views/test_conversation.py:198:5: F841 local variable 'mock_get_inbound_sms' is assigned to but never used
./tests/app/main/views/test_dashboard.py:53:5: F841 local variable 'mock_template_stats' is assigned to but never used
./tests/app/main/views/test_dashboard.py:72:5: F841 local variable 'mock_template_stats' is assigned to but never used
./tests/app/main/views/test_jobs.py:2:1: F401 'uuid' imported but unused
./tests/app/main/views/test_jobs.py:3:1: F401 'urllib.parse.urlparse' imported but unused
./tests/app/main/views/test_jobs.py:3:1: F401 'urllib.parse.quote' imported but unused
./tests/app/main/views/test_jobs.py:3:1: F401 'urllib.parse.parse_qs' imported but unused
./tests/app/main/views/test_jobs.py:9:1: F401 'app.main.views.jobs.get_status_filters' imported but unused
./tests/app/main/views/test_jobs.py:10:1: F401 'tests.notification_json' imported but unused
./tests/app/main/views/test_letters.py:6:1: F401 'tests.service_json' imported but unused
./tests/app/main/views/test_notifications.py:5:1: F401 'app.utils.REQUESTED_STATUSES' imported but unused
./tests/app/main/views/test_notifications.py:5:1: F401 'app.utils.DELIVERED_STATUSES' imported but unused
./tests/app/main/views/test_notifications.py:5:1: F401 'app.utils.SENDING_STATUSES' imported but unused
./tests/app/main/views/test_notifications.py:5:1: F401 'app.utils.FAILURE_STATUSES' imported but unused
./tests/app/main/views/test_platform_admin.py:242:13: E126 continuation line over-indented for hanging indent
./tests/app/main/views/test_platform_admin.py:247:13: E126 continuation line over-indented for hanging indent
./tests/app/main/views/test_send.py:3:1: F401 'unittest.mock.Mock' imported but unused
./tests/app/main/views/test_send.py:18:1: F811 redefinition of unused 'mock_get_service' from line 18
./tests/app/main/views/test_send.py:18:1: F401 'tests.conftest.multiple_letter_contact_blocks' imported but unused
./tests/app/main/views/test_send.py:18:1: F401 'tests.conftest.no_sms_senders' imported but unused
./tests/app/main/views/test_send.py:18:1: F401 'tests.conftest.multiple_sms_senders' imported but unused
./tests/app/main/views/test_send.py:18:1: F401 'tests.conftest.no_letter_contact_blocks' imported but unused
./tests/app/main/views/test_send.py:102:5: F841 local variable 'response' is assigned to but never used
./tests/app/main/views/test_send.py:870:5: F841 local variable 'response' is assigned to but never used
./tests/app/main/views/test_send.py:1367:5: F841 local variable 'service_id' is assigned to but never used
./tests/app/main/views/test_send.py:1451:13: E126 continuation line over-indented for hanging indent
./tests/app/main/views/test_send.py:1620:80: E226 missing whitespace around arithmetic operator
./tests/app/main/views/test_send.py:1909:13: E126 continuation line over-indented for hanging indent
./tests/app/main/views/test_send.py:1912:9: E121 continuation line under-indented for hanging indent
./tests/app/main/views/test_service_settings.py:13:1: F811 redefinition of unused 'no_reply_to_email_addresses' from line 13
./tests/app/main/views/test_service_settings.py:13:1: F401 'tests.conftest.single_reply_to_email_address' imported but unused
./tests/app/main/views/test_service_settings.py:28:5: E123 closing bracket does not match indentation of opening bracket's line
./tests/app/main/views/test_service_settings.py:104:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:166:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:186:5: F841 local variable 'mocked_get_fn' is assigned to but never used
./tests/app/main/views/test_service_settings.py:217:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:237:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:257:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:307:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:340:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:466:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:555:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:615:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:719:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:874:5: F841 local variable 'page' is assigned to but never used
./tests/app/main/views/test_service_settings.py:902:5: F841 local variable 'page' is assigned to but never used
./tests/app/main/views/test_service_settings.py:954:5: F841 local variable 'page' is assigned to but never used
./tests/app/main/views/test_service_settings.py:986:5: F841 local variable 'page' is assigned to but never used
./tests/app/main/views/test_service_settings.py:1101:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:1121:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:1271:1: F811 redefinition of unused 'test_set_letter_contact_block_saves' from line 1189
./tests/app/main/views/test_service_settings.py:1433:5: F841 local variable 'page' is assigned to but never used
./tests/app/main/views/test_service_settings.py:1495:5: F841 local variable 'mocked_get_fn' is assigned to but never used
./tests/app/main/views/test_service_settings.py:1540:5: F841 local variable 'mocked_get_fn' is assigned to but never used
./tests/app/main/views/test_service_settings.py:1570:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:1589:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:1621:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:1641:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:1658:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:1676:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:1697:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:1759:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_service_settings.py:1775:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13
./tests/app/main/views/test_templates.py:3:1: F401 'uuid' imported but unused
./tests/app/main/views/test_templates.py:11:1: F401 'tests.conftest.mock_get_user' imported but unused
./tests/app/main/views/test_templates.py:514:1: F811 redefinition of unused 'mock_get_user' from line 11
./tests/app/main/views/test_templates.py:672:1: F811 redefinition of unused 'mock_get_user' from line 11
./tests/app/main/views/test_templates.py:795:1: F811 redefinition of unused 'mock_get_user' from line 11
./tests/app/main/views/test_templates.py:835:1: F811 redefinition of unused 'mock_get_user' from line 11
./tests/app/main/views/test_two_factor.py:67:13: E126 continuation line over-indented for hanging indent
./tests/app/notify_client/test_notification_client.py:79:5: F841 local variable 'mock_post' is assigned to but never used
```
1. https://gds-way.cloudapps.digital/manuals/programming-languages/python/linting.html#how-to-use-flake8
2. d5ab8afef4/.flake8
703 lines
20 KiB
Python
703 lines
20 KiB
Python
import re
|
||
import pytz
|
||
|
||
from flask_wtf import FlaskForm as Form
|
||
from datetime import datetime, timedelta
|
||
|
||
from notifications_utils.recipients import (
|
||
validate_phone_number,
|
||
InvalidPhoneError
|
||
)
|
||
from notifications_utils.columns import Columns
|
||
from wtforms import (
|
||
widgets,
|
||
validators,
|
||
StringField,
|
||
PasswordField,
|
||
ValidationError,
|
||
TextAreaField,
|
||
FileField,
|
||
BooleanField,
|
||
HiddenField,
|
||
IntegerField,
|
||
RadioField,
|
||
FieldList,
|
||
DateField,
|
||
)
|
||
from wtforms.fields.html5 import EmailField, TelField, SearchField
|
||
from wtforms.validators import (DataRequired, Email, Length, Regexp, Optional)
|
||
from flask_wtf.file import FileField as FileField_wtf, FileAllowed
|
||
|
||
from app.main.validators import (Blacklist, CsvFileValidator, ValidGovEmail, NoCommasInPlaceHolders, OnlyGSMCharacters)
|
||
|
||
|
||
def get_time_value_and_label(future_time):
|
||
return (
|
||
future_time.replace(tzinfo=None).isoformat(),
|
||
'{} at {}'.format(
|
||
get_human_day(future_time.astimezone(pytz.timezone('Europe/London'))),
|
||
get_human_time(future_time.astimezone(pytz.timezone('Europe/London')))
|
||
)
|
||
)
|
||
|
||
|
||
def get_human_time(time):
|
||
return {
|
||
'0': 'midnight',
|
||
'12': 'midday'
|
||
}.get(
|
||
time.strftime('%-H'),
|
||
time.strftime('%-I%p').lower()
|
||
)
|
||
|
||
|
||
def get_human_day(time, prefix_today_with='T'):
|
||
# Add 1 hour to get ‘midnight today’ instead of ‘midnight tomorrow’
|
||
time = (time - timedelta(hours=1)).strftime('%A')
|
||
if time == datetime.utcnow().strftime('%A'):
|
||
return '{}oday'.format(prefix_today_with)
|
||
if time == (datetime.utcnow() + timedelta(days=1)).strftime('%A'):
|
||
return 'Tomorrow'
|
||
return time
|
||
|
||
|
||
def get_furthest_possible_scheduled_time():
|
||
return (datetime.utcnow() + timedelta(days=4)).replace(hour=0)
|
||
|
||
|
||
def get_next_hours_until(until):
|
||
now = datetime.utcnow()
|
||
hours = int((until - now).total_seconds() / (60 * 60))
|
||
return [
|
||
(now + timedelta(hours=i)).replace(minute=0, second=0).replace(tzinfo=pytz.utc)
|
||
for i in range(1, hours + 1)
|
||
]
|
||
|
||
|
||
def get_next_days_until(until):
|
||
now = datetime.utcnow()
|
||
days = int((until - now).total_seconds() / (60 * 60 * 24))
|
||
return [
|
||
get_human_day(
|
||
(now + timedelta(days=i)).replace(tzinfo=pytz.utc),
|
||
prefix_today_with='Later t'
|
||
)
|
||
for i in range(0, days + 1)
|
||
]
|
||
|
||
|
||
def email_address(label='Email address', gov_user=True):
|
||
validators = [
|
||
Length(min=5, max=255),
|
||
DataRequired(message='Can’t be empty'),
|
||
Email(message='Enter a valid email address')
|
||
]
|
||
|
||
if gov_user:
|
||
validators.append(ValidGovEmail())
|
||
return EmailField(label, validators)
|
||
|
||
|
||
class UKMobileNumber(TelField):
|
||
def pre_validate(self, form):
|
||
try:
|
||
validate_phone_number(self.data)
|
||
except InvalidPhoneError as e:
|
||
raise ValidationError(str(e))
|
||
|
||
|
||
class InternationalPhoneNumber(TelField):
|
||
def pre_validate(self, form):
|
||
try:
|
||
validate_phone_number(self.data, international=True)
|
||
except InvalidPhoneError as e:
|
||
raise ValidationError(str(e))
|
||
|
||
|
||
def uk_mobile_number(label='Mobile number'):
|
||
return UKMobileNumber(label,
|
||
validators=[DataRequired(message='Can’t be empty')])
|
||
|
||
|
||
def international_phone_number(label='Mobile number'):
|
||
return InternationalPhoneNumber(
|
||
label,
|
||
validators=[DataRequired(message='Can’t be empty')]
|
||
)
|
||
|
||
|
||
def password(label='Password'):
|
||
return PasswordField(label,
|
||
validators=[DataRequired(message='Can’t be empty'),
|
||
Length(8, 255, message='Must be at least 8 characters'),
|
||
Blacklist(message='Choose a password that’s harder to guess')])
|
||
|
||
|
||
def sms_code():
|
||
verify_code = '^\d{5}$'
|
||
return StringField('Text message code',
|
||
validators=[DataRequired(message='Can’t be empty'),
|
||
Regexp(regex=verify_code,
|
||
message='Code not found')])
|
||
|
||
|
||
class LoginForm(Form):
|
||
email_address = StringField('Email address', validators=[
|
||
Length(min=5, max=255),
|
||
DataRequired(message='Can’t be empty'),
|
||
Email(message='Enter a valid email address')
|
||
])
|
||
password = PasswordField('Password', validators=[
|
||
DataRequired(message='Enter your password')
|
||
])
|
||
|
||
|
||
class RegisterUserForm(Form):
|
||
name = StringField('Full name',
|
||
validators=[DataRequired(message='Can’t be empty')])
|
||
email_address = email_address()
|
||
mobile_number = international_phone_number()
|
||
password = password()
|
||
|
||
|
||
class RegisterUserFromInviteForm(Form):
|
||
name = StringField('Full name',
|
||
validators=[DataRequired(message='Can’t be empty')])
|
||
mobile_number = international_phone_number()
|
||
password = password()
|
||
service = HiddenField('service')
|
||
email_address = HiddenField('email_address')
|
||
|
||
|
||
class PermissionsForm(Form):
|
||
send_messages = BooleanField("Send messages from existing templates")
|
||
manage_templates = BooleanField("Add and edit templates")
|
||
manage_service = BooleanField("Modify this service and its team")
|
||
manage_api_keys = BooleanField("Create and revoke API keys")
|
||
|
||
|
||
class InviteUserForm(PermissionsForm):
|
||
email_address = email_address(gov_user=False)
|
||
|
||
def __init__(self, invalid_email_address, *args, **kwargs):
|
||
super(InviteUserForm, self).__init__(*args, **kwargs)
|
||
self.invalid_email_address = invalid_email_address.lower()
|
||
|
||
def validate_email_address(self, field):
|
||
if field.data.lower() == self.invalid_email_address:
|
||
raise ValidationError("You can’t send an invitation to yourself")
|
||
|
||
|
||
class TwoFactorForm(Form):
|
||
def __init__(self, validate_code_func, *args, **kwargs):
|
||
'''
|
||
Keyword arguments:
|
||
validate_code_func -- Validates the code with the API.
|
||
'''
|
||
self.validate_code_func = validate_code_func
|
||
super(TwoFactorForm, self).__init__(*args, **kwargs)
|
||
|
||
sms_code = sms_code()
|
||
|
||
def validate_sms_code(self, field):
|
||
is_valid, reason = self.validate_code_func(field.data)
|
||
if not is_valid:
|
||
raise ValidationError(reason)
|
||
|
||
|
||
class EmailNotReceivedForm(Form):
|
||
email_address = email_address()
|
||
|
||
|
||
class TextNotReceivedForm(Form):
|
||
mobile_number = international_phone_number()
|
||
|
||
|
||
class ServiceNameForm(Form):
|
||
def __init__(self, *args, **kwargs):
|
||
super(ServiceNameForm, self).__init__(*args, **kwargs)
|
||
|
||
name = StringField(
|
||
u'Service name',
|
||
validators=[
|
||
DataRequired(message='Can’t be empty')
|
||
])
|
||
|
||
|
||
class ConfirmPasswordForm(Form):
|
||
def __init__(self, validate_password_func, *args, **kwargs):
|
||
self.validate_password_func = validate_password_func
|
||
super(ConfirmPasswordForm, self).__init__(*args, **kwargs)
|
||
|
||
password = PasswordField(u'Enter password')
|
||
|
||
def validate_password(self, field):
|
||
if not self.validate_password_func(field.data):
|
||
raise ValidationError('Invalid password')
|
||
|
||
|
||
class BaseTemplateForm(Form):
|
||
name = StringField(
|
||
u'Template name',
|
||
validators=[DataRequired(message="Can’t be empty")])
|
||
|
||
template_content = TextAreaField(
|
||
u'Message',
|
||
validators=[
|
||
DataRequired(message="Can’t be empty"),
|
||
NoCommasInPlaceHolders()
|
||
]
|
||
)
|
||
process_type = RadioField(
|
||
'Use priority queue?',
|
||
choices=[
|
||
('priority', 'Yes'),
|
||
('normal', 'No'),
|
||
],
|
||
validators=[DataRequired()],
|
||
default='normal'
|
||
)
|
||
|
||
|
||
class SMSTemplateForm(BaseTemplateForm):
|
||
def validate_template_content(self, field):
|
||
OnlyGSMCharacters()(None, field)
|
||
|
||
|
||
class EmailTemplateForm(BaseTemplateForm):
|
||
subject = TextAreaField(
|
||
u'Subject',
|
||
validators=[DataRequired(message="Can’t be empty")])
|
||
|
||
|
||
class LetterTemplateForm(EmailTemplateForm):
|
||
|
||
subject = TextAreaField(
|
||
u'Main heading',
|
||
validators=[DataRequired(message="Can’t be empty")])
|
||
|
||
template_content = TextAreaField(
|
||
u'Body',
|
||
validators=[
|
||
DataRequired(message="Can’t be empty"),
|
||
NoCommasInPlaceHolders()
|
||
]
|
||
)
|
||
|
||
|
||
class ForgotPasswordForm(Form):
|
||
email_address = email_address(gov_user=False)
|
||
|
||
|
||
class NewPasswordForm(Form):
|
||
new_password = password()
|
||
|
||
|
||
class ChangePasswordForm(Form):
|
||
def __init__(self, validate_password_func, *args, **kwargs):
|
||
self.validate_password_func = validate_password_func
|
||
super(ChangePasswordForm, self).__init__(*args, **kwargs)
|
||
|
||
old_password = password('Current password')
|
||
new_password = password('New password')
|
||
|
||
def validate_old_password(self, field):
|
||
if not self.validate_password_func(field.data):
|
||
raise ValidationError('Invalid password')
|
||
|
||
|
||
class CsvUploadForm(Form):
|
||
file = FileField('Add recipients', validators=[DataRequired(
|
||
message='Please pick a file'), CsvFileValidator()])
|
||
|
||
|
||
class ChangeNameForm(Form):
|
||
new_name = StringField(u'Your name')
|
||
|
||
|
||
class ChangeEmailForm(Form):
|
||
def __init__(self, validate_email_func, *args, **kwargs):
|
||
self.validate_email_func = validate_email_func
|
||
super(ChangeEmailForm, self).__init__(*args, **kwargs)
|
||
|
||
email_address = email_address()
|
||
|
||
def validate_email_address(self, field):
|
||
is_valid = self.validate_email_func(field.data)
|
||
if not is_valid:
|
||
raise ValidationError("The email address is already in use")
|
||
|
||
|
||
class ChangeMobileNumberForm(Form):
|
||
mobile_number = international_phone_number()
|
||
|
||
|
||
class ConfirmMobileNumberForm(Form):
|
||
def __init__(self, validate_code_func, *args, **kwargs):
|
||
self.validate_code_func = validate_code_func
|
||
super(ConfirmMobileNumberForm, self).__init__(*args, **kwargs)
|
||
|
||
sms_code = sms_code()
|
||
|
||
def validate_sms_code(self, field):
|
||
is_valid, msg = self.validate_code_func(field.data)
|
||
if not is_valid:
|
||
raise ValidationError(msg)
|
||
|
||
|
||
class ChooseTimeForm(Form):
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super(ChooseTimeForm, self).__init__(*args, **kwargs)
|
||
self.scheduled_for.choices = [('', 'Now')] + [
|
||
get_time_value_and_label(hour) for hour in get_next_hours_until(
|
||
get_furthest_possible_scheduled_time()
|
||
)
|
||
]
|
||
self.scheduled_for.categories = get_next_days_until(get_furthest_possible_scheduled_time())
|
||
|
||
scheduled_for = RadioField(
|
||
'When should Notify send these messages?',
|
||
default='',
|
||
validators=[
|
||
DataRequired()
|
||
]
|
||
)
|
||
|
||
|
||
class CreateKeyForm(Form):
|
||
def __init__(self, existing_key_names=[], *args, **kwargs):
|
||
self.existing_key_names = [x.lower() for x in existing_key_names]
|
||
super(CreateKeyForm, self).__init__(*args, **kwargs)
|
||
|
||
key_type = RadioField(
|
||
'Type of key',
|
||
validators=[
|
||
DataRequired()
|
||
]
|
||
)
|
||
|
||
key_name = StringField(u'Description of key', validators=[
|
||
DataRequired(message='You need to give the key a name')
|
||
])
|
||
|
||
def validate_key_name(self, key_name):
|
||
if key_name.data.lower() in self.existing_key_names:
|
||
raise ValidationError('A key with this name already exists')
|
||
|
||
|
||
class SupportType(Form):
|
||
support_type = RadioField(
|
||
'How can we help you?',
|
||
choices=[
|
||
('problem', 'Report a problem'),
|
||
('question', 'Ask a question or give feedback'),
|
||
],
|
||
validators=[DataRequired()]
|
||
)
|
||
|
||
|
||
class Feedback(Form):
|
||
name = StringField('Name')
|
||
email_address = StringField('Email address')
|
||
feedback = TextAreaField('Your message', validators=[DataRequired(message="Can’t be empty")])
|
||
|
||
|
||
class Problem(Feedback):
|
||
email_address = email_address(label='Email address', gov_user=False)
|
||
|
||
|
||
class Triage(Form):
|
||
severe = RadioField(
|
||
'Is it an emergency?',
|
||
choices=[
|
||
('yes', 'Yes'),
|
||
('no', 'No'),
|
||
],
|
||
validators=[DataRequired()]
|
||
)
|
||
|
||
|
||
class RequestToGoLiveForm(Form):
|
||
mou = RadioField(
|
||
(
|
||
'Has your organisation accepted the GOV.UK Notify data sharing and financial '
|
||
'agreement (Memorandum of Understanding)?'
|
||
),
|
||
choices=[
|
||
('yes', 'Yes'),
|
||
('no', 'No'),
|
||
('don’t know', 'I don’t know')
|
||
],
|
||
validators=[DataRequired()]
|
||
)
|
||
channel_email = BooleanField('Emails')
|
||
channel_sms = BooleanField('Text messages')
|
||
channel_letter = BooleanField('Letters')
|
||
start_date = StringField(
|
||
'When will you be ready to start sending messages?',
|
||
validators=[DataRequired(message='Can’t be empty')]
|
||
)
|
||
start_volume = StringField(
|
||
'How many messages do you expect to send to start with?',
|
||
validators=[DataRequired(message='Can’t be empty')]
|
||
)
|
||
peak_volume = StringField(
|
||
'Will the number of messages increase and when will that start?',
|
||
validators=[DataRequired(message='Can’t be empty')]
|
||
)
|
||
upload_or_api = RadioField(
|
||
'How are you going to send messages?',
|
||
choices=[
|
||
('File upload', 'Upload a spreadsheet of recipients'),
|
||
('API', 'Integrate with the GOV.UK Notify API'),
|
||
('API and file upload', 'Both')
|
||
],
|
||
validators=[DataRequired()]
|
||
)
|
||
|
||
|
||
class ProviderForm(Form):
|
||
priority = IntegerField('Priority', [validators.NumberRange(min=1, max=100, message="Must be between 1 and 100")])
|
||
|
||
|
||
class ServiceReplyToEmailForm(Form):
|
||
email_address = email_address(label='Email reply to address')
|
||
is_default = BooleanField("Make this email address the default")
|
||
|
||
|
||
class ServiceSmsSender(Form):
|
||
sms_sender = StringField(
|
||
'Text message sender',
|
||
validators=[
|
||
DataRequired(message="Can’t be empty"),
|
||
Length(max=11, message="Enter 11 characters or fewer")
|
||
]
|
||
)
|
||
|
||
def validate_sms_sender(form, field):
|
||
if field.data and not re.match(r'^[a-zA-Z0-9\s]+$', field.data):
|
||
raise ValidationError('Use letters and numbers only')
|
||
|
||
|
||
class ServiceLetterContactBlockForm(Form):
|
||
letter_contact_block = TextAreaField(
|
||
validators=[
|
||
DataRequired(message="Can’t be empty"),
|
||
NoCommasInPlaceHolders()
|
||
]
|
||
)
|
||
is_default = BooleanField("Set as your default address")
|
||
|
||
def validate_letter_contact_block(form, field):
|
||
line_count = field.data.strip().count('\n')
|
||
if line_count >= 10:
|
||
raise ValidationError(
|
||
'Contains {} lines, maximum is 10'.format(line_count + 1)
|
||
)
|
||
|
||
|
||
class ServiceBrandingOrg(Form):
|
||
|
||
def __init__(self, organisations=[], *args, **kwargs):
|
||
self.organisation.choices = organisations
|
||
super(ServiceBrandingOrg, self).__init__(*args, **kwargs)
|
||
|
||
branding_type = RadioField(
|
||
'Branding',
|
||
choices=[
|
||
('govuk', 'GOV.UK only'),
|
||
('both', 'GOV.UK and organisation'),
|
||
('org', 'Organisation only'),
|
||
('org_banner', 'Organisation banner')
|
||
],
|
||
validators=[
|
||
DataRequired()
|
||
]
|
||
)
|
||
|
||
organisation = RadioField(
|
||
'Organisation',
|
||
validators=[
|
||
DataRequired()
|
||
]
|
||
)
|
||
|
||
|
||
class ServiceSelectOrg(Form):
|
||
|
||
def __init__(self, organisations=[], *args, **kwargs):
|
||
self.organisation.choices = organisations
|
||
super(ServiceSelectOrg, self).__init__(*args, **kwargs)
|
||
|
||
organisation = RadioField(
|
||
'Organisation',
|
||
validators=[
|
||
DataRequired()
|
||
]
|
||
)
|
||
|
||
|
||
class ServiceManageOrg(Form):
|
||
|
||
name = StringField('Name')
|
||
|
||
colour = StringField('Colour', render_kw={'onkeyup': 'update_colour_span()', 'onblur': 'update_colour_span()'})
|
||
file = FileField_wtf('Upload a PNG logo', validators=[FileAllowed(['png'], 'PNG Images only!')])
|
||
|
||
|
||
class LetterBranding(Form):
|
||
|
||
def __init__(self, choices=[], *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
self.dvla_org_id.choices = choices
|
||
|
||
dvla_org_id = RadioField(
|
||
'Which logo should this service’s letter have?',
|
||
validators=[
|
||
DataRequired()
|
||
]
|
||
)
|
||
|
||
|
||
class Whitelist(Form):
|
||
|
||
def populate(self, email_addresses, phone_numbers):
|
||
for form_field, existing_whitelist in (
|
||
(self.email_addresses, email_addresses),
|
||
(self.phone_numbers, phone_numbers)
|
||
):
|
||
for index, value in enumerate(existing_whitelist):
|
||
form_field[index].data = value
|
||
|
||
email_addresses = FieldList(
|
||
EmailField(
|
||
'',
|
||
validators=[
|
||
Optional(),
|
||
Email(message='Enter valid email addresses')
|
||
],
|
||
default=''
|
||
),
|
||
min_entries=5,
|
||
max_entries=5,
|
||
label="Email addresses"
|
||
)
|
||
|
||
phone_numbers = FieldList(
|
||
InternationalPhoneNumber(
|
||
'',
|
||
validators=[
|
||
Optional()
|
||
],
|
||
default=''
|
||
),
|
||
min_entries=5,
|
||
max_entries=5,
|
||
label="Mobile numbers"
|
||
)
|
||
|
||
|
||
class DateFilterForm(Form):
|
||
start_date = DateField("Start Date", [validators.optional()])
|
||
end_date = DateField("End Date", [validators.optional()])
|
||
include_from_test_key = BooleanField("Include test keys", default="checked", false_values={"N"})
|
||
|
||
|
||
class ChooseTemplateType(Form):
|
||
|
||
template_type = RadioField(
|
||
'What kind of template do you want to add?',
|
||
validators=[
|
||
DataRequired()
|
||
]
|
||
)
|
||
|
||
def __init__(self, include_letters=False, *args, **kwargs):
|
||
|
||
super().__init__(*args, **kwargs)
|
||
|
||
self.template_type.choices = filter(None, [
|
||
('email', 'Email'),
|
||
('sms', 'Text message'),
|
||
('letter', 'Letter') if include_letters else None
|
||
])
|
||
|
||
|
||
class SearchTemplatesForm(Form):
|
||
|
||
search = SearchField('Search by name')
|
||
|
||
|
||
class SearchNotificationsForm(Form):
|
||
|
||
to = SearchField('Search by phone number or email address')
|
||
|
||
|
||
class PlaceholderForm(Form):
|
||
|
||
pass
|
||
|
||
|
||
class PasswordFieldShowHasContent(StringField):
|
||
widget = widgets.PasswordInput(hide_value=False)
|
||
|
||
|
||
class ServiceInboundApiForm(Form):
|
||
url = StringField("Inbound sms url",
|
||
validators=[DataRequired(message='Can’t be empty'),
|
||
Regexp(regex="^https.*",
|
||
message='Must be a valid https url')]
|
||
)
|
||
bearer_token = PasswordFieldShowHasContent("Bearer token",
|
||
validators=[DataRequired(message='Can’t be empty'),
|
||
Length(min=10, message='Must be at least 10 characters')])
|
||
|
||
|
||
class InternationalSMSForm(Form):
|
||
enabled = RadioField(
|
||
'Send text messages to international phone numbers',
|
||
choices=[
|
||
('on', 'On'),
|
||
('off', 'Off'),
|
||
],
|
||
)
|
||
|
||
|
||
def get_placeholder_form_instance(
|
||
placeholder_name,
|
||
dict_to_populate_from,
|
||
optional_placeholder=False,
|
||
allow_international_phone_numbers=False,
|
||
):
|
||
|
||
if Columns.make_key(placeholder_name) == 'emailaddress':
|
||
field = email_address(label=placeholder_name, gov_user=False)
|
||
elif Columns.make_key(placeholder_name) == 'phonenumber':
|
||
if allow_international_phone_numbers:
|
||
field = international_phone_number(label=placeholder_name)
|
||
else:
|
||
field = uk_mobile_number(label=placeholder_name)
|
||
elif optional_placeholder:
|
||
field = StringField(placeholder_name)
|
||
else:
|
||
field = StringField(placeholder_name, validators=[
|
||
DataRequired(message='Can’t be empty')
|
||
])
|
||
|
||
PlaceholderForm.placeholder_value = field
|
||
|
||
return PlaceholderForm(
|
||
placeholder_value=dict_to_populate_from.get(placeholder_name, '')
|
||
)
|
||
|
||
|
||
class SetSenderForm(Form):
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
self.sender.choices = kwargs['sender_choices']
|
||
self.sender.label.text = kwargs['sender_label']
|
||
|
||
sender = RadioField()
|