Merge branch 'master' into reinstate-new-rate-api

Conflicts:
	app/main/views/dashboard.py
This commit is contained in:
Martyn Inglis
2017-06-07 14:44:27 +01:00
44 changed files with 1079 additions and 186 deletions

View File

@@ -7,6 +7,7 @@ from notifications_utils.recipients import (
validate_phone_number,
InvalidPhoneError
)
from notifications_utils.columns import Columns
from wtforms import (
validators,
StringField,
@@ -102,11 +103,26 @@ class UKMobileNumber(TelField):
raise ValidationError(str(e))
def mobile_number():
return UKMobileNumber('Mobile number',
class InternationalPhoneNumber(TelField):
def pre_validate(self, form):
try:
validate_phone_number(self.data, international=True)
except InvalidPhoneError as e:
raise ValidationError(str(e))
def mobile_number(label='Mobile number'):
return UKMobileNumber(label,
validators=[DataRequired(message='Cant be empty')])
def international_phone_number(label='Mobile number'):
return InternationalPhoneNumber(
label,
validators=[DataRequired(message='Cant be empty')]
)
def password(label='Password'):
return PasswordField(label,
validators=[DataRequired(message='Cant be empty'),
@@ -502,7 +518,11 @@ class ServiceSmsSender(Form):
class ServiceLetterContactBlock(Form):
letter_contact_block = TextAreaField()
letter_contact_block = TextAreaField(
validators=[
NoCommasInPlaceHolders()
]
)
def validate_letter_contact_block(form, field):
line_count = field.data.strip().count('\n')
@@ -621,6 +641,11 @@ class SearchTemplatesForm(Form):
search = SearchField('Search by name')
class SearchNotificationsForm(Form):
to = SearchField('Search by phone number or email address')
class PlaceholderForm(Form):
pass
@@ -629,15 +654,25 @@ class PlaceholderForm(Form):
def get_placeholder_form_instance(
placeholder_name,
dict_to_populate_from,
optional_placeholder=False
optional_placeholder=False,
allow_international_phone_numbers=False,
):
PlaceholderForm.placeholder_value = StringField(
placeholder_name,
validators=[
if Columns.make_key(placeholder_name) == 'emailaddress':
field = email_address(label=placeholder_name, gov_user=False)
elif Columns.make_key(placeholder_name) == 'phonenumber':
if allow_international_phone_numbers:
field = international_phone_number(label=placeholder_name)
else:
field = mobile_number(label=placeholder_name)
elif optional_placeholder:
field = StringField(placeholder_name)
else:
field = StringField(placeholder_name, validators=[
DataRequired(message='Cant be empty')
] if not optional_placeholder else []
)
])
PlaceholderForm.placeholder_value = field
return PlaceholderForm(
placeholder_value=dict_to_populate_from.get(placeholder_name, '')

View File

@@ -2,7 +2,6 @@ from datetime import datetime
from functools import partial
from flask import (
render_template,
current_app,
url_for,
session,
jsonify,
@@ -13,6 +12,7 @@ from flask_login import login_required
from app.main import main
from app import (
current_service,
job_api_client,
service_api_client,
template_statistics_client
@@ -136,6 +136,20 @@ def monthly(service_id):
)
@main.route("/services/<service_id>/inbox")
@login_required
@user_has_permissions('manage_settings', admin_override=True)
def inbox(service_id):
if 'inbound_sms' not in current_service['permissions']:
abort(403)
return render_template(
'views/dashboard/inbox.html',
messages=service_api_client.get_inbound_sms(service_id),
)
def aggregate_usage(template_statistics, sort_key='count'):
return sorted(
template_statistics,
@@ -167,6 +181,13 @@ def get_dashboard_partials(service_id):
'views/dashboard/_upcoming.html',
scheduled_jobs=scheduled_jobs
),
'inbox': render_template(
'views/dashboard/_inbox.html',
inbound_sms_summary=(
service_api_client.get_inbound_sms_summary(service_id)
if 'inbound_sms' in current_service['permissions'] else None
),
),
'totals': render_template(
'views/dashboard/_totals.html',
service_id=service_id,
@@ -213,7 +234,8 @@ def calculate_free_tier_usage(usage, service):
def calculate_usage(usage):
sms_free_allowance = current_app.config['SMS_FREE_TIER_AMOUNT']
# TODO: Don't hardcode these - get em from the API
sms_free_allowance = 250000
sms_rate = 0 if len(usage) == 0 else usage[0].get("rate", 0)
sms_sent = get_sum_billing_units(breakdown for breakdown in usage if breakdown['notification_type'] == 'sms')

View File

@@ -8,6 +8,11 @@ from app.main.forms import SupportType, Feedback, Problem, Triage
from datetime import datetime
@main.route('/feedback', methods=['GET'])
def old_feedback():
return redirect(url_for('.support'))
@main.route('/support', methods=['GET', 'POST'])
def support():
form = SupportType()

View File

@@ -26,6 +26,7 @@ from app import (
current_service,
format_datetime_short)
from app.main import main
from app.main.forms import SearchNotificationsForm
from app.utils import (
get_page_from_request,
generate_next_dict,
@@ -197,8 +198,10 @@ def view_notifications(service_id, message_type):
'views/notifications.html',
partials=get_notifications(service_id, message_type),
message_type=message_type,
status=request.args.get('status'),
page=request.args.get('page', 1)
status=request.args.get('status') or 'sending,delivered,failed',
page=request.args.get('page', 1),
to=request.args.get('to'),
search_form=SearchNotificationsForm(to=request.args.get('to')),
)
@@ -241,7 +244,9 @@ def get_notifications(service_id, message_type, status_override=None):
page=page,
template_type=[message_type],
status=filter_args.get('status'),
limit_days=current_app.config['ACTIVITY_STATS_LIMIT_DAYS'])
limit_days=current_app.config['ACTIVITY_STATS_LIMIT_DAYS'],
to=request.args.get('to'),
)
url_args = {
'message_type': message_type,
@@ -249,11 +254,11 @@ def get_notifications(service_id, message_type, status_override=None):
}
prev_page = None
if notifications['links'].get('prev', None):
if 'links' in notifications and notifications['links'].get('prev', None):
prev_page = generate_previous_dict('main.view_notifications', service_id, page, url_args=url_args)
next_page = None
if notifications['links'].get('next', None):
if 'links' in notifications and notifications['links'].get('next', None):
next_page = generate_next_dict('main.view_notifications', service_id, page, url_args)
return {

View File

@@ -157,14 +157,18 @@ def get_example_csv(service_id, template_id):
}
@main.route("/services/<service_id>/send/<template_id>/test")
@main.route("/services/<service_id>/send/<template_id>/test", endpoint='send_test')
@main.route("/services/<service_id>/send/<template_id>/one-off", endpoint='send_one_off')
@login_required
@user_has_permissions('send_texts', 'send_emails', 'send_letters')
def send_test(service_id, template_id):
session['send_test_values'] = dict()
session['send_test_letter_page_count'] = None
return redirect(url_for(
'.send_test_step',
{
'main.send_test': '.send_test_step',
'main.send_one_off': '.send_one_off_step',
}[request.endpoint],
service_id=service_id,
template_id=template_id,
step_index=0,
@@ -172,14 +176,28 @@ def send_test(service_id, template_id):
))
@main.route("/services/<service_id>/send/<template_id>/test/step-<int:step_index>", methods=['GET', 'POST'])
@main.route(
"/services/<service_id>/send/<template_id>/test/step-<int:step_index>",
methods=['GET', 'POST'],
endpoint='send_test_step',
)
@main.route(
"/services/<service_id>/send/<template_id>/one-off/step-<int:step_index>",
methods=['GET', 'POST'],
endpoint='send_one_off_step',
)
@login_required
@user_has_permissions('send_texts', 'send_emails', 'send_letters')
def send_test_step(service_id, template_id, step_index):
if 'send_test_values' not in session:
return redirect(url_for(
'.send_test', service_id=service_id, template_id=template_id
{
'main.send_test_step': '.send_test',
'main.send_one_off_step': '.send_one_off',
}[request.endpoint],
service_id=service_id,
template_id=template_id,
))
template = service_api_client.get_service_template(service_id, template_id)['data']
@@ -201,7 +219,10 @@ def send_test_step(service_id, template_id, step_index):
page_count=session['send_test_letter_page_count']
)
placeholders = fields_to_fill_in(template)
placeholders = fields_to_fill_in(
template,
prefill_current_user=(request.endpoint == 'main.send_test_step'),
)
if len(placeholders) == 0:
return make_and_upload_csv_file(service_id, template)
@@ -209,28 +230,33 @@ def send_test_step(service_id, template_id, step_index):
try:
current_placeholder = placeholders[step_index]
except IndexError:
if all_placeholders_in_session(placeholders):
return make_and_upload_csv_file(service_id, template)
return redirect(url_for(
'.send_test', service_id=service_id, template_id=template_id
{
'main.send_test_step': '.send_test',
'main.send_one_off_step': '.send_one_off',
}[request.endpoint],
service_id=service_id,
template_id=template_id,
))
optional_placeholder = (current_placeholder in optional_address_columns)
form = get_placeholder_form_instance(
current_placeholder,
dict_to_populate_from=get_normalised_send_test_values_from_session(),
optional_placeholder=optional_placeholder,
allow_international_phone_numbers=current_service['can_send_international_sms'],
)
if form.validate_on_submit():
session['send_test_values'][current_placeholder] = form.placeholder_value.data
if all(
get_normalised_send_test_values_from_session().get(placeholder, False) not in (False, None)
for placeholder in placeholders
):
if all_placeholders_in_session(placeholders):
return make_and_upload_csv_file(service_id, template)
return redirect(url_for(
'.send_test_step',
request.endpoint,
service_id=service_id,
template_id=template_id,
step_index=step_index + 1,
@@ -247,7 +273,7 @@ def send_test_step(service_id, template_id, step_index):
)
else:
back_link = url_for(
'.send_test_step',
request.endpoint,
service_id=service_id,
template_id=template_id,
step_index=step_index - 1,
@@ -256,13 +282,27 @@ def send_test_step(service_id, template_id, step_index):
template.values = get_normalised_send_test_values_from_session()
template.values[current_placeholder] = None
if (
request.endpoint == 'main.send_one_off_step' and
step_index == 0 and
template.template_type != 'letter'
):
skip_link = (
'Use my {}'.format(first_column_headings[template.template_type][0]),
url_for('.send_test', service_id=service_id, template_id=template.id),
)
else:
skip_link = None
return render_template(
'views/send-test.html',
page_title=get_send_test_page_title(template.template_type, get_help_argument()),
template=template,
form=form,
skip_link=skip_link,
optional_placeholder=optional_placeholder,
help=get_help_argument(),
back_link=back_link,
help=get_help_argument(),
)
@@ -470,11 +510,11 @@ def get_check_messages_back_url(service_id, template_type):
return url_for('main.choose_template', service_id=service_id)
def fields_to_fill_in(template):
def fields_to_fill_in(template, prefill_current_user=False):
recipient_columns = first_column_headings[template.template_type]
if 'letter' == template.template_type:
if 'letter' == template.template_type or not prefill_current_user:
return recipient_columns + list(template.placeholders)
session['send_test_values'][recipient_columns[0]] = {
@@ -513,3 +553,18 @@ def make_and_upload_csv_file(service_id, template):
from_test=True,
help=2 if get_help_argument() else 0
))
def all_placeholders_in_session(placeholders):
return all(
get_normalised_send_test_values_from_session().get(placeholder, False) not in (False, None)
for placeholder in placeholders
)
def get_send_test_page_title(template_type, help_argument):
if help_argument:
return 'Example text message'
if template_type == 'letter':
return 'Print a test letter'
return 'Send to one recipient'

View File

@@ -14,6 +14,8 @@ from flask_login import (
login_required,
current_user
)
from notifications_utils.field import Field
from notifications_python_client.errors import HTTPError
from app import service_api_client
@@ -46,7 +48,9 @@ def service_settings(service_id):
organisation=organisation,
letter_branding=letter_branding_organisations.get(
current_service.get('dvla_organisation', '001')
)
),
can_receive_inbound=('inbound_sms' in current_service['permissions']),
letter_contact_block=Field(current_service['letter_contact_block'], html='escape')
)
@@ -264,14 +268,27 @@ def service_set_reply_to_email(service_id):
@user_has_permissions('manage_settings', admin_override=True)
def service_set_sms_sender(service_id):
form = ServiceSmsSender()
if form.validate_on_submit():
set_inbound_sms = request.args.get('set_inbound_sms', False)
if set_inbound_sms == 'True':
permissions = current_service['permissions']
if 'inbound_sms' in permissions:
permissions.remove('inbound_sms')
else:
permissions.append('inbound_sms')
service_api_client.update_service_with_properties(
current_service['id'],
{'permissions': permissions,
'sms_sender': form.sms_sender.data or None}
)
else:
service_api_client.update_service(
current_service['id'],
sms_sender=form.sms_sender.data or None
)
return redirect(url_for('.service_settings', service_id=service_id))
if request.method == 'GET':
form.sms_sender.data = current_service.get('sms_sender')
if form.validate_on_submit():
service_api_client.update_service(
current_service['id'],
sms_sender=form.sms_sender.data or None
)
return redirect(url_for('.service_settings', service_id=service_id))
return render_template(
'views/service-settings/set-sms-sender.html',
form=form)