Don’t store info about bad uploads in session

Because we now[1] store info about each file upload separately in the
session the session isn’t overridden every time you upload a file. This
is good because you can do multiple file uploads idempotently.

Generally we are cleaning up after ourselves because we pop anything to
do with that upload from the session. However there is an edge case: if
you never send the file then the info about the file stays in the
session in perpetuity[2]. This is generally happening when people are
uploading files that are impossible to send, ie ones that have errors.

So this commit makes two changes:

1. remove info about a file upload from the session as soon as we know
   that it contains errors
2. `POST` reuploads to the same endpoint as initial uploads because
   otherwise we need to keep info about bad uploads in the session,
   which would prevent us from doing 1.

1. https://github.com/alphagov/notifications-admin/pull/1968
2. or at least until the session is cleared by the user logging out
This commit is contained in:
Chris Hill-Scott
2018-03-29 11:56:53 +01:00
parent a3374f32d8
commit e74d261ec3
5 changed files with 46 additions and 48 deletions

View File

@@ -541,8 +541,14 @@ def _check_messages(service_id, template_type, upload_id, preview_row, letters_a
elif preview_row > 2:
abort(404)
session['file_uploads'][upload_id]['notification_count'] = len(recipients)
session['file_uploads'][upload_id]['valid'] = not recipients.has_errors
original_file_name = session['file_uploads'][upload_id].get('original_file_name')
if any(recipients) and not recipients.has_errors:
session['file_uploads'][upload_id]['notification_count'] = len(recipients)
session['file_uploads'][upload_id]['valid'] = True
else:
session['file_uploads'].pop(upload_id)
return dict(
recipients=recipients,
template=template,
@@ -550,7 +556,7 @@ def _check_messages(service_id, template_type, upload_id, preview_row, letters_a
row_errors=get_errors_for_csv(recipients, template.template_type),
count_of_recipients=len(recipients),
count_of_displayed_recipients=len(list(recipients.displayed_rows)),
original_file_name=session['file_uploads'][upload_id].get('original_file_name'),
original_file_name=original_file_name,
upload_id=upload_id,
form=CsvUploadForm(),
remaining_messages=remaining_messages,
@@ -610,17 +616,6 @@ def check_messages_preview(service_id, template_type, upload_id, filetype, row_i
return TemplatePreview.from_utils_template(template, filetype)
@main.route("/services/<service_id>/<template_type>/check/<upload_id>", methods=['POST'])
@main.route("/services/<service_id>/<template_type>/check/<upload_id>/row-<int:row_index>", methods=['POST'])
@login_required
@user_has_permissions('send_messages', restrict_admin_usage=True)
def recheck_messages(service_id, template_type, upload_id, row_index=0):
if not session.get('file_uploads', {}).get(upload_id):
return redirect(url_for('main.choose_template', service_id=service_id), code=301)
return send_messages(service_id, session['file_uploads'][upload_id].get('template_id'))
@main.route("/services/<service_id>/start-job/<upload_id>", methods=['POST'])
@login_required
@user_has_permissions('send_messages', restrict_admin_usage=True)

View File

@@ -1,5 +1,11 @@
{% macro file_upload(field, button_text="Choose file", alternate_link=None, alternate_link_text=None) %}
<form method="post" enctype="multipart/form-data" class="{% if field.errors %}form-group-error{% endif %}" data-module="file-upload">
{% macro file_upload(
field,
action=None,
button_text="Choose file",
alternate_link=None,
alternate_link_text=None
) %}
<form method="post" enctype="multipart/form-data" {% if action %}action="{{ action }}"{% endif %} class="{% if field.errors %}form-group-error{% endif %}" data-module="file-upload">
<label class="file-upload-label" for="{{ field.name }}">
<span class="visually-hidden">{{ field.label.text }}</span>
{% if hint %}

View File

@@ -135,7 +135,11 @@
{% if request.args.from_test %}
<a href="{{ back_link }}" class="page-footer-back-link">Back</a>
{% else %}
{{file_upload(form.file, button_text='Re-upload your file')}}
{{ file_upload(
form.file,
action=url_for('.send_messages', service_id=current_service.id, template_id=template.id),
button_text='Re-upload your file'
) }}
{% endif %}
</div>
<a href="#content" class="back-to-top-link">Back to top</a>

View File

@@ -47,7 +47,11 @@
<div class="js-stick-at-top-when-scrolling">
<div class="form-group">
{{ file_upload(form.file, button_text='Re-upload your file') }}
{{ file_upload(
form.file,
action=url_for('.send_messages', service_id=current_service.id, template_id=template.id),
button_text='Re-upload your file'
) }}
</div>
<a href="#content" class="back-to-top-link">Back to top</a>
</div>

View File

@@ -372,27 +372,22 @@ def test_upload_csvfile_with_errors_shows_check_page_with_errors(
"""
)
initial_upload = logged_in_client.post(
response = logged_in_client.post(
url_for('main.send_messages', service_id=service_one['id'], template_id=fake_uuid),
data={'file': (BytesIO(''.encode('utf-8')), 'invalid.csv')},
content_type='multipart/form-data',
follow_redirects=True
)
reupload = logged_in_client.post(
url_for('main.check_messages', service_id=service_one['id'], template_type='sms', upload_id=fake_uuid),
data={'file': (BytesIO(''.encode('utf-8')), 'invalid.csv')},
content_type='multipart/form-data',
follow_redirects=True
)
with logged_in_client.session_transaction() as session:
assert session['file_uploads'] == {}
for response in [initial_upload, reupload]:
assert response.status_code == 200
content = response.get_data(as_text=True)
assert 'There is a problem with invalid.csv' in content
assert '+447700900986' in content
assert 'Missing' in content
assert 'Re-upload your file' in content
assert response.status_code == 200
content = response.get_data(as_text=True)
assert 'There is a problem with invalid.csv' in content
assert '+447700900986' in content
assert 'Missing' in content
assert 'Re-upload your file' in content
@pytest.mark.parametrize('file_contents, expected_error,', [
@@ -483,7 +478,7 @@ def test_upload_csvfile_with_errors_shows_check_page_with_errors(
),
])
def test_upload_csvfile_with_missing_columns_shows_error(
logged_in_client,
client_request,
mocker,
mock_get_service_template_with_placeholders,
mock_s3_upload,
@@ -497,15 +492,16 @@ def test_upload_csvfile_with_missing_columns_shows_error(
mocker.patch('app.main.views.send.s3download', return_value=file_contents)
response = logged_in_client.post(
url_for('main.send_messages', service_id=service_one['id'], template_id=fake_uuid),
data={'file': (BytesIO(''.encode('utf-8')), 'invalid.csv')},
follow_redirects=True,
page = client_request.post(
'main.send_messages', service_id=service_one['id'], template_id=fake_uuid,
_data={'file': (BytesIO(''.encode('utf-8')), 'invalid.csv')},
_follow_redirects=True,
)
assert response.status_code == 200
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert ' '.join(page.select('.banner-dangerous')[0].text.split()) == expected_error
with client_request.session_transaction() as session:
assert session['file_uploads'] == {}
assert normalize_spaces(page.select('.banner-dangerous')[0].text) == expected_error
def test_upload_csv_invalid_extension(
@@ -1426,6 +1422,7 @@ def test_upload_csvfile_with_valid_phone_shows_all_numbers(
mock_get_service_template,
mock_get_users_by_service,
mock_get_detailed_service_for_today,
mock_get_live_service,
service_one,
fake_uuid,
mock_s3_upload,
@@ -2787,14 +2784,6 @@ def test_sms_sender_is_previewed(
'filetype': 'png'
}
),
(
'main.recheck_messages',
'POST',
{
'template_type': 'email',
'upload_id': fake_uuid()
}
),
(
'main.start_job',
'POST',