From 37c9f0a718a710d6e56587ed5db135a0dd3478c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Tue, 21 Jun 2022 16:16:13 +0200
Subject: [PATCH] misc: allow downloading file while submitting (#66460)

---
 tests/form_pages/test_all.py                        |  7 ++++++-
 tests/form_pages/test_file_field.py                 |  2 +-
 wcs/forms/common.py                                 | 10 ++++++++--
 wcs/forms/root.py                                   |  4 ++--
 wcs/qommon/static/js/qommon.fileupload.js           |  7 ++++++-
 wcs/qommon/templates/qommon/forms/widgets/file.html |  5 ++++-
 wcs/root.py                                         |  1 +
 7 files changed, 28 insertions(+), 8 deletions(-)

diff --git a/tests/form_pages/test_all.py b/tests/form_pages/test_all.py
index 0919997d8..d896a1a3d 100644
--- a/tests/form_pages/test_all.py
+++ b/tests/form_pages/test_all.py
@@ -2528,7 +2528,11 @@ def test_form_draft_with_file(pub):
     assert resp.location.startswith('http://example.net/test/?mt=')
     resp = resp.follow()
     resp = resp.forms[1].submit('previous')
-    assert '<span class="filename">test.txt</span>' in resp.text
+    assert resp.pyquery('.filename').text() == 'test.txt'
+    # check file is downloadable
+    r2 = resp.click('test.txt')
+    assert r2.content_type == 'text/plain'
+    assert r2.text == 'foobar'
 
     # check submitted form keeps the file
     resp = resp.forms[1].submit('submit')  # -> confirmation page
@@ -8757,6 +8761,7 @@ def test_create_formdata_anonymous_draft(create_formdata):
     resp = resp.follow()
     resp = resp.follow()
     assert 'zob' in resp
+    assert resp.click('test.txt').text == 'foobar'
     resp = resp.forms[1].submit('submit')  # -> validation
     resp = resp.forms[1].submit('submit')  # -> submission
     assert create_formdata['target_formdef'].data_class().count() == 1
diff --git a/tests/form_pages/test_file_field.py b/tests/form_pages/test_file_field.py
index 8428f0b97..e6de9dcd9 100644
--- a/tests/form_pages/test_file_field.py
+++ b/tests/form_pages/test_file_field.py
@@ -215,7 +215,7 @@ def test_form_file_field_image_submit(pub):
     resp = resp.forms[0].submit('submit')
     assert 'Check values then click submit.' in resp.text
     assert '<img alt="" src="tempfile?' in resp.text
-    tempfile_id = resp.text[resp.text.index('tempfile?') :].split('&', 1)[0].split('=')[1]
+    tempfile_id = resp.pyquery('.fileinfo .filename a').attr.href.split('=')[1]
 
     resp_tempfile = app.get('/test/tempfile?t=%s' % tempfile_id)
     assert resp_tempfile.body == image_content
diff --git a/wcs/forms/common.py b/wcs/forms/common.py
index 04ba96c2e..f57bae60c 100644
--- a/wcs/forms/common.py
+++ b/wcs/forms/common.py
@@ -133,7 +133,7 @@ class FormTemplateMixin:
 
 
 class FormStatusPage(Directory, FormTemplateMixin):
-    _q_exports_orig = ['', 'download', 'json', 'action', 'live']
+    _q_exports_orig = ['', 'download', 'json', 'action', 'live', 'tempfile']
     _q_extra_exports = []
     form_page_class = None
 
@@ -144,12 +144,13 @@ class FormStatusPage(Directory, FormTemplateMixin):
     def html_top(self, title=None):
         template.html_top(title=title, default_org=_('Forms'))
 
-    def __init__(self, formdef, filled, register_workflow_subdirs=True, custom_view=None):
+    def __init__(self, formdef, filled, register_workflow_subdirs=True, custom_view=None, parent_view=None):
         get_publisher().substitutions.feed(filled)
         self.formdef = formdef
         self.formdata = filled
         self.filled = filled
         self.custom_view = custom_view
+        self.parent_view = parent_view
         self._q_exports = self._q_exports_orig[:]
         for q in self._q_extra_exports:
             if q not in self._q_exports:
@@ -205,6 +206,11 @@ class FormStatusPage(Directory, FormTemplateMixin):
             anonymise=anonymise, include_files=get_query_flag('include-files-content', default=True)
         )
 
+    def tempfile(self):
+        # allow for file uploaded via a file widget in a workflow form
+        # to be downloaded back from widget
+        return self.parent_view.tempfile()
+
     def workflow_messages(self, position='top'):
         if self.formdef.workflow:
             workflow_messages = self.filled.get_workflow_messages(position=position, user=get_request().user)
diff --git a/wcs/forms/root.py b/wcs/forms/root.py
index 03b4c337e..856daf805 100644
--- a/wcs/forms/root.py
+++ b/wcs/forms/root.py
@@ -1785,7 +1785,7 @@ class FormPage(Directory, FormTemplateMixin):
         except KeyError:
             raise errors.TraversalError()
 
-        return self.get_status_page_class()(self.formdef, filled)
+        return self.get_status_page_class()(self.formdef, filled, parent_view=self)
 
 
 class RootDirectory(AccessControlled, Directory):
@@ -2131,7 +2131,7 @@ class RootDirectory(AccessControlled, Directory):
 
 
 class PublicFormStatusPage(FormStatusPage):
-    _q_exports_orig = ['', 'download', 'status', 'live']
+    _q_exports_orig = ['', 'download', 'status', 'live', 'tempfile']
     form_page_class = FormPage
     history_templates = ['wcs/front/formdata_history.html', 'wcs/formdata_history.html']
     status_templates = ['wcs/front/formdata_status.html', 'wcs/formdata_status.html']
diff --git a/wcs/qommon/static/js/qommon.fileupload.js b/wcs/qommon/static/js/qommon.fileupload.js
index 313597985..44be65580 100644
--- a/wcs/qommon/static/js/qommon.fileupload.js
+++ b/wcs/qommon/static/js/qommon.fileupload.js
@@ -316,7 +316,12 @@ $.WcsFileUpload = {
                     return;
                 }
                 $(base_widget).find('.fileprogress').hide();
-                $(base_widget).find('.filename').text(data.result[0].name);
+                if (data.result[0].url) {
+                    $(base_widget).find('.filename').empty().append(
+                        $('<a>', {href: data.result[0].url, download: data.result[0].name, text: data.result[0].name}));
+                } else {
+                    $(base_widget).find('.filename').text(data.result[0].name);
+                }
                 $(base_widget).find('.fileinfo').show();
                 $(base_widget).find('input[type=hidden]').val(data.result[0].token);
                 $(base_widget).parents('form').find('div.buttons button').prop('disabled', false);
diff --git a/wcs/qommon/templates/qommon/forms/widgets/file.html b/wcs/qommon/templates/qommon/forms/widgets/file.html
index c8a3eb52c..3389847f7 100644
--- a/wcs/qommon/templates/qommon/forms/widgets/file.html
+++ b/wcs/qommon/templates/qommon/forms/widgets/file.html
@@ -24,7 +24,10 @@
        data-error="{% trans "Error during upload." %}"></div>
 </div>
 <div class="fileinfo {% if widget.readonly and widget.has_tempfile_image %}thumbnail{% endif %}">
-  <span class="filename">{{ widget.tempfile.base_filename }}</span>
+  <span class="filename">{% if widget.tempfile %}<a
+    href="tempfile?t={{ widget.tempfile.token }}"
+    download="{{widget.tempfile.base_filename}}"
+    >{{ widget.tempfile.base_filename }}</a>{% endif %}</span>
 {% if not widget.readonly %}
   <a href="#" role="button" class="remove" title="{% trans 'Remove this file' %}">{% trans "remove" %}</a>
 {% elif widget.has_tempfile_image %}
diff --git a/wcs/root.py b/wcs/root.py
index 32bea2db6..9ccebccf4 100644
--- a/wcs/root.py
+++ b/wcs/root.py
@@ -328,6 +328,7 @@ class RootDirectory(Directory):
                             'type': tempfile.get('content_type'),
                             'size': tempfile.get('size'),
                             'token': tempfile.get('token'),
+                            'url': 'tempfile?t=%s' % tempfile.get('token'),
                         }
                     )
                 except UploadStorageError as e:
-- 
2.36.1

