First slice of csv upload of phone numbers for sending messages.

At the moment the file contents are not persisted by checked in
memory.

The first and last three records are show if all are valid.

If there are invalid rows, they are reported and the user is
prompted to go back and sort out upload file.

The storing of upload result (i.e. validation of file) in session
will be removed in next story which is about persisting of file
for later processing.
This commit is contained in:
Adam Shimali
2016-01-11 15:00:51 +00:00
parent aa44a7b036
commit 584533eb11
7 changed files with 231 additions and 98 deletions

View File

@@ -1,7 +1,10 @@
from flask_wtf import Form
from wtforms import StringField, PasswordField, ValidationError
from wtforms import StringField, PasswordField, ValidationError, FileField
from wtforms.validators import DataRequired, Email, Length, Regexp
from app.main.validators import Blacklist, ValidateUserCodes
from app.main.validators import Blacklist, ValidateUserCodes, CsvFileValidator
from app.main.dao import verify_codes_dao
from app.main.encryption import check_hash
def email_address():
@@ -127,3 +130,9 @@ class ForgotPasswordForm(Form):
class NewPasswordForm(Form):
new_password = password()
class CsvUploadForm(Form):
file = FileField('File to upload',
validators=[DataRequired(message='Please pick a file'),
CsvFileValidator()])

View File

@@ -38,3 +38,13 @@ class ValidateUserCodes(object):
break
if not valid_code:
raise ValidationError(self.invalid_msg)
class CsvFileValidator(object):
def __init__(self, message='Not a csv file'):
self.message = message
def __call__(self, form, field):
if not form.file.data.mimetype == 'text/csv':
raise ValidationError(self.message)

View File

@@ -1,7 +1,20 @@
from flask import request, render_template, redirect, url_for
import csv
import re
from flask import (
request,
render_template,
redirect,
url_for,
session,
flash,
current_app
)
from flask_login import login_required
from app.main import main
from app.main.forms import CsvUploadForm
# TODO move this to the templates directory
message_templates = [
@@ -23,45 +36,64 @@ message_templates = [
@main.route("/sms/send", methods=['GET', 'POST'])
@login_required
def sendsms():
if request.method == 'GET':
return render_template(
'views/send-sms.html',
message_templates=message_templates
)
elif request.method == 'POST':
return redirect(url_for('.checksms'))
form = CsvUploadForm()
if form.validate_on_submit():
csv_file = form.file.data
# in memory handing is temporary until next story to save csv file
try:
results = _build_upload_result(csv_file)
except Exception:
message = 'There was a problem with the file: {}'.format(
csv_file.filename)
flash(message)
return redirect(url_for('.sendsms'))
if not results['valid'] and not results['rejects']:
message = "The file {} contained no data".format(csv_file.filename)
flash(message, 'error')
return redirect(url_for('.sendsms'))
session[csv_file.filename] = results
return redirect(url_for('.checksms', recipients=csv_file.filename))
return render_template('views/send-sms.html',
message_templates=message_templates,
form=form)
@main.route("/sms/check", methods=['GET', 'POST'])
@login_required
def checksms():
recipients = [
{'phone': "+44 7700 900989", 'registration number': 'LC12 BFL', 'date': '24 December 2015'},
{'phone': "+44 7700 900479", 'registration number': 'DU04 AOM', 'date': '25 December 2015'},
{'phone': "+44 7700 900964", 'registration number': 'M91 MJB', 'date': '26 December 2015'},
{'phone': "+44 7700 900703", 'registration number': 'Y249 NPU', 'date': '31 December 2015'},
{'phone': "+44 7700 900730", 'registration number': 'LG55 UGB', 'date': '1 January 2016'},
{'phone': "+44 7700 900989", 'registration number': 'LC12 BFL', 'date': '24 December 2015'},
{'phone': "+44 7700 900479", 'registration number': 'DU04 AOM', 'date': '25 December 2015'},
{'phone': "+44 7700 900964", 'registration number': 'M91 MJB', 'date': '26 December 2015'},
{'phone': "+44 7700 900703", 'registration number': 'Y249 NPU', 'date': '31 December 2015'},
{'phone': "+44 7700 900730", 'registration number': 'LG55 UGB', 'date': '1 January 2016'},
]
number_of_recipients = len(recipients)
too_many_recipients_to_display = number_of_recipients > 7
if request.method == 'GET':
recipients_filename = request.args.get('recipients')
# upload results in session until file is persisted in next story
upload_result = session.get(recipients_filename)
if upload_result.get('rejects'):
flash('There was a problem with some of the numbers')
return render_template(
'views/check-sms.html',
number_of_recipients=number_of_recipients,
recipients={
"first_three": recipients[:3] if too_many_recipients_to_display else [],
"last_three": recipients[number_of_recipients - 3:] if too_many_recipients_to_display else [],
"all": recipients if not too_many_recipients_to_display else []
},
upload_result=upload_result,
message_template=message_templates[0]['body']
)
elif request.method == 'POST':
return redirect(url_for('.showjob'))
def _build_upload_result(csv_file):
pattern = re.compile(r'^\+44\s?\d{4}\s?\d{6}$')
reader = csv.DictReader(
csv_file.read().decode('utf-8').splitlines(),
lineterminator='\n',
quoting=csv.QUOTE_NONE)
valid, rejects = [], []
for i, row in enumerate(reader):
if pattern.match(row['phone']):
valid.append(row)
else:
rejects.append({"line_number": i+2, "phone": row['phone']})
return {"valid": valid, "rejects": rejects}

View File

@@ -10,57 +10,55 @@
{% block maincolumn_content %}
<h1 class="heading-xlarge">Send text messages</h1>
<h1 class="heading-xlarge">Send text messages</h1>
{% if upload_result.rejects %}
<h3 class="heading-small">The following numbers are invalid</h3>
{% for rejected in upload_result.rejects %}
<p>Line {{rejected.line_number}}: {{rejected.phone }}</a>
{% endfor %}
<p><a href="{{url_for('.sendsms')}}" class="button">Go back and resolve errors</a></p>
<h2 class="heading-medium">Check and confirm</h2>
{% else %}
<form method="POST" enctype="multipart/form-data">
<h2 class="heading-medium">Check and confirm</h2>
<form method="POST" enctype="multipart/form-data">
{{ page_footer(
"Send {} text messages".format(number_of_recipients)
) }}
<h3 class="heading-small">First 3 messages</h2>
{% if recipients.first_three and recipients.last_three %}
{% for recipient in recipients.first_three %}
{{ sms_message(
message_template|replace_placeholders(recipient),
"{}".format(recipient['phone'])
) }}
{% endfor %}
<h3 class="heading-small">Last 3 messages</h2>
{% for recipient in recipients.last_three %}
{{ sms_message(
message_template|replace_placeholders(recipient),
"{}".format(recipient['phone'])
) }}
{% endfor %}
{% else %}
{% for recipient in recipients.all %}
{{ sms_message(
message_template|replace_placeholders(recipient),
"{}".format(recipient['phone'])
) }}
{% endfor %}
{% endif %}
<p>
<a href="#">See all</a>
</p>
{{ page_footer(
button_text = "Send {} text messages".format(number_of_recipients),
{{ page_footer(
button_text = "Send {} text messages".format(upload_result.valid|count),
back_link = url_for(".sendsms")
) }}
)}}
</form>
{% if upload_result.valid | count > 6 %}
<h3 class="heading-small">First three message in file</h3>
{% for recipient in upload_result.valid[:3] %}
{{ sms_message(message_template|replace_placeholders(
recipient),
'{}'.format(recipient['phone'])
)}}
{% endfor %}
<h3 class="heading-small">Last three messages in file</h3>
{% for recipient in upload_result.valid[-3:] %}
{{ sms_message(message_template|replace_placeholders(
recipient),
'{}'.format(recipient['phone'])
)}}
{% endfor %}
{% else %}
<h3 class="heading-small">All messages in file</h3>
{% for recipient in upload_result.valid %}
{{ sms_message(message_template|replace_placeholders(
recipient),
'{}'.format(recipient['phone'])
)}}
{% endfor %}
{% endif %}
{{ page_footer(
button_text = "Send {} text messages".format(upload_result.valid|count),
back_link = url_for(".sendsms")
)}}
</form>
{% endif %}
{% endblock %}

View File

@@ -1,6 +1,7 @@
{% extends "withnav_template.html" %}
{% from "components/sms-message.html" import sms_message %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form-field.html" import render_field %}
{% block page_title %}
GOV.UK Notify | Send text messages
@@ -37,7 +38,7 @@
You can also <a href="#">download an example CSV</a>.
</p>
<p>
<input type="file" />
{{render_field(form.file)}}
</p>
{{ page_footer("Continue") }}