From b662600102b6ff6fc46a578acc07ff97ad46a562 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Tue, 29 Dec 2020 16:18:06 +0100
Subject: [PATCH] backoffice: use a single page for all processing afterjob
 pages (#49772)

---
 tests/admin_pages/test_all.py                 |  8 +-
 tests/backoffice_pages/test_carddata.py       |  1 -
 tests/backoffice_pages/test_export.py         |  4 +-
 wcs/admin/forms.py                            | 67 ++-------------
 wcs/admin/settings.py                         | 44 +++-------
 wcs/backoffice/data_management.py             | 47 ++++++-----
 wcs/backoffice/management.py                  | 82 ++++++-------------
 wcs/backoffice/root.py                        | 16 +++-
 wcs/qommon/afterjobs.py                       |  3 +
 wcs/qommon/static/css/dc2/admin.css           |  1 +
 .../wcs/backoffice/card-data-import-form.html | 28 -------
 wcs/templates/wcs/backoffice/processing.html  | 18 ++++
 12 files changed, 111 insertions(+), 208 deletions(-)
 create mode 100644 wcs/templates/wcs/backoffice/processing.html

diff --git a/tests/admin_pages/test_all.py b/tests/admin_pages/test_all.py
index 01ee9b46b..7d291ef3f 100644
--- a/tests/admin_pages/test_all.py
+++ b/tests/admin_pages/test_all.py
@@ -654,7 +654,7 @@ def test_settings_export_import(pub):
     resp = resp.form.submit('cancel')
     resp = app.get('/backoffice/settings/export')
     resp = resp.form.submit('submit')
-    assert resp.location.startswith('http://example.net/backoffice/settings/export?job=')
+    assert resp.location.startswith('http://example.net/backoffice/processing?job=')
     job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
     resp = resp.follow()
     assert 'completed' in resp.text
@@ -702,7 +702,7 @@ def test_settings_export_import(pub):
 
     resp = app.get('/backoffice/settings/export')
     resp = resp.form.submit('submit')
-    assert resp.location.startswith('http://example.net/backoffice/settings/export?job=')
+    assert resp.location.startswith('http://example.net/backoffice/processing?job=')
     job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
     resp = resp.follow()
     resp = resp.click('Download Export')
@@ -782,7 +782,7 @@ def test_settings_export_import(pub):
     resp.form['datasources'] = False
     resp.form['wscalls'] = False
     resp = resp.form.submit('submit')
-    assert resp.location.startswith('http://example.net/backoffice/settings/export?job=')
+    assert resp.location.startswith('http://example.net/backoffice/processing?job=')
     job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
     resp = resp.follow()
     resp = resp.click('Download Export')
@@ -816,7 +816,7 @@ def test_settings_export_import(pub):
     pub.write_cfg()
     resp = app.get('/backoffice/settings/export')
     resp = resp.form.submit('submit')
-    assert resp.location.startswith('http://example.net/backoffice/settings/export?job=')
+    assert resp.location.startswith('http://example.net/backoffice/processing?job=')
     job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
     resp = resp.follow()
     resp = resp.click('Download Export')
diff --git a/tests/backoffice_pages/test_carddata.py b/tests/backoffice_pages/test_carddata.py
index 70618ea71..a6df57b9b 100644
--- a/tests/backoffice_pages/test_carddata.py
+++ b/tests/backoffice_pages/test_carddata.py
@@ -356,7 +356,6 @@ def test_backoffice_cards_import_data_from_csv(pub):
                                    'text/csv')
     resp = resp.forms[0].submit().follow()
     assert 'Importing data into cards' in resp
-    assert 'Column File will be ignored: type File Upload not supported.' in resp
     assert carddef.data_class().count() == 149
     card1, card2 = carddef.data_class().select(order_by='id')[:2]
     assert card1.data['1'] == '48.81;2.37'
diff --git a/tests/backoffice_pages/test_export.py b/tests/backoffice_pages/test_export.py
index c046cc8d1..257e907cd 100644
--- a/tests/backoffice_pages/test_export.py
+++ b/tests/backoffice_pages/test_export.py
@@ -187,7 +187,7 @@ def test_backoffice_export_long_listings(pub, threshold):
     app = login(get_app(pub))
     resp = app.get('/backoffice/management/form-title/')
     resp = resp.click('Export as CSV File')
-    assert resp.location.startswith('http://example.net/backoffice/management/form-title/export?job=')
+    assert resp.location.startswith('http://example.net/backoffice/processing?job=')
     resp = resp.follow()
     assert 'completed' in resp.text
     resp = resp.click('Download Export')
@@ -201,7 +201,7 @@ def test_backoffice_export_long_listings(pub, threshold):
 
     resp = app.get('/backoffice/management/form-title/')
     resp = resp.click('Export a Spreadsheet')
-    assert resp.location.startswith('http://example.net/backoffice/management/form-title/export?job=')
+    assert resp.location.startswith('http://example.net/backoffice/processing?job=')
     job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
     resp = resp.follow()
     assert 'completed' in resp.text
diff --git a/wcs/admin/forms.py b/wcs/admin/forms.py
index ad0f471b8..91d280159 100644
--- a/wcs/admin/forms.py
+++ b/wcs/admin/forms.py
@@ -1247,8 +1247,6 @@ class FormDefPage(Directory):
         if get_publisher().is_using_postgresql():
             raise TraversalError()
 
-        if get_request().form.get('job'):
-            return self.archive_processing()
         if get_request().form.get('download'):
             return self.archive_download()
 
@@ -1315,7 +1313,10 @@ class FormDefPage(Directory):
             job = get_response().add_after_job(
                 str(N_('Archiving forms')),
                 archiver.archive)
-            return redirect('archive?job=%s' % job.id)
+            job.done_action_url = self.formdef.get_admin_url() + 'archive?job=%s' % job.id
+            job.done_action_label = _('Download Archive')
+            job.store()
+            return redirect(job.get_processing_url())
         else:
             archiver.archive()
 
@@ -1325,32 +1326,6 @@ class FormDefPage(Directory):
                 'attachment; filename=%s-archive.wcs' % self.formdef.url_name)
             return archiver.fd.getvalue()
 
-    def archive_processing(self):
-        try:
-            job = AfterJob.get(get_request().form.get('job'))
-        except KeyError:
-            return redirect('.')
-
-        self.html_top(title=_('Archiving'))
-        r = TemplateIO(html=True)
-        r += get_session().display_message()
-        get_response().add_javascript(['jquery.js', 'afterjob.js'])
-        r += htmltext('<dl class="job-status">')
-        r += htmltext('<dt>')
-        r += _(job.label)
-        r += htmltext('</dt>')
-        r += htmltext('<dd>')
-        r += htmltext('<span class="afterjob" id="%s">') % job.id
-        r += _(job.status)
-        r += htmltext('</span>')
-        r += htmltext('</dd>')
-        r += htmltext('</dl>')
-
-        r += htmltext('<div class="done">')
-        r += htmltext('<a href="archive?download=%s">%s</a>') % (job.id, _('Download Archive'))
-        r += htmltext('</div>')
-        return r.getvalue()
-
     def archive_download(self):
         try:
             job = AfterJob.get(get_request().form.get('download'))
@@ -1366,9 +1341,6 @@ class FormDefPage(Directory):
         return job.file_content
 
     def anonymise(self):
-        if get_request().form.get('job'):
-            return self.anonymise_processing()
-
         endpoints = []
         for status in self.formdef.workflow.get_endpoint_status():
             endpoints.append((str(status.id), status.name, str(status.id)))
@@ -1428,38 +1400,15 @@ class FormDefPage(Directory):
             job = get_response().add_after_job(
                 str(N_('Anonymising forms')),
                 anonymiser.anonymise)
-            return redirect('anonymise?job=%s' % job.id)
+            job.done_action_url = self.formdef.get_admin_url()
+            job.done_action_label = _('Back')
+            job.store()
+            return redirect(job.get_processing_url())
         else:
             anonymiser.anonymise()
 
         return redirect('.')
 
-    def anonymise_processing(self):
-        try:
-            job = AfterJob.get(get_request().form.get('job'))
-        except KeyError:
-            return redirect('.')
-
-        html_top('forms', title=_('Anonymising'))
-        r = TemplateIO(html=True)
-        r += get_session().display_message()
-        get_response().add_javascript(['jquery.js', 'afterjob.js'])
-        r += htmltext('<dl class="job-status">')
-        r += htmltext('<dt>')
-        r += _(job.label)
-        r += htmltext('</dt>')
-        r += htmltext('<dd>')
-        r += htmltext('<span class="afterjob" id="%s">') % job.id
-        r += _(job.status)
-        r += htmltext('</span>')
-        r += htmltext('</dd>')
-        r += htmltext('</dl>')
-
-        r += htmltext('<div class="done">')
-        r += htmltext('<a href="./">%s</a>') % _('Back')
-        r += htmltext('</div>')
-        return r.getvalue()
-
     def enable(self):
         self.formdef.disabled = False
         self.formdef.store(comment=_('Enable'))
diff --git a/wcs/admin/settings.py b/wcs/admin/settings.py
index f3c702b04..1e833fcb2 100644
--- a/wcs/admin/settings.py
+++ b/wcs/admin/settings.py
@@ -858,8 +858,8 @@ class SettingsDirectory(QommonSettingsDirectory):
         get_publisher().write_cfg()
 
     def export(self):
-        if get_request().form.get('job') or get_request().form.get('download'):
-            return self.export_pending()
+        if get_request().form.get('download'):
+            return self.export_download()
 
         form = Form(enctype="multipart/form-data")
         form.add(CheckboxWidget, 'formdefs', title = _('Forms'), value = True)
@@ -961,43 +961,23 @@ class SettingsDirectory(QommonSettingsDirectory):
         job = get_response().add_after_job(
                 N_('Exporting site settings'),
                 exporter.export)
+        job.done_action_url = get_request().get_url() + '?download=%s' % job.id
+        job.done_action_label = _('Download Export')
+        job.done_button_attributes = {'download': 'export.wcs'}
         job.store()
-        return redirect('export?job=%s' % job.id)
+        return redirect(job.get_processing_url())
 
-    def export_pending(self):
-        job_id = get_request().form.get('job') or get_request().form.get('download')
+    def export_download(self):
+        job_id = get_request().form.get('download')
         try:
             job = AfterJob.get(job_id)
         except KeyError:
             return redirect('.')
 
-        if get_request().form.get('download'):
-            response = get_response()
-            response.set_content_type('application/x-wcs')
-            response.set_header('content-disposition', 'attachment; filename=export.wcs')
-            return job.file_content
-
-        html_top('settings', title=_('Exporting'))
-        r = TemplateIO(html=True)
-        get_response().add_javascript(['jquery.js', 'afterjob.js'])
-        r += htmltext('<h2>%s</h2>') % _('Export')
-        r += htmltext('<div class="section"><dl class="job-status">')
-        r += htmltext('<dt>')
-        r += _(job.label)
-        r += htmltext('</dt>')
-        r += htmltext('<dd>')
-        r += htmltext('<span class="afterjob" id="%s">') % job.id
-        r += _(job.status)
-        r += htmltext('</span>')
-        r += htmltext('</dd>')
-        r += htmltext('</dl>')
-
-        r += htmltext('<div class="done">')
-        r += htmltext('<a download="export.wcs" href="export?download=%s">%s</a>') % (
-                job.id, _('Download Export'))
-        r += htmltext('</div>')
-        r += htmltext('</div>')
-        return r.getvalue()
+        response = get_response()
+        response.set_content_type('application/x-wcs')
+        response.set_header('content-disposition', 'attachment; filename=export.wcs')
+        return job.file_content
 
     def p_import(self):
         form = Form(enctype='multipart/form-data')
diff --git a/wcs/backoffice/data_management.py b/wcs/backoffice/data_management.py
index d6d44b2d0..f770c0acc 100644
--- a/wcs/backoffice/data_management.py
+++ b/wcs/backoffice/data_management.py
@@ -188,34 +188,27 @@ class CardPage(FormPage):
         if not self.can_user_add_cards():
             raise errors.AccessForbiddenError()
         context = {
-            'unsupported_fields': [],
             'required_fields': []
         }
-        try:
-            job = AfterJob.get(get_request().form.get('job'))
-            get_response().add_javascript(['jquery.js', 'afterjob.js'])
-            context['job'] = job
-        except KeyError:
-            form = Form(enctype='multipart/form-data', use_tokens=False)
-            form.add(FileWidget, 'file', title=_('File'), required=True)
-            form.add_submit('submit', _('Submit'))
-            form.add_submit('cancel', _('Cancel'))
-            if form.get_widget('cancel').parse():
-                return redirect('.')
-
-            if form.is_submitted() and not form.has_errors():
-                try:
-                    return self.import_csv_submit(form.get_widget('file').parse().fp)
-                except ValueError as e:
-                    form.set_error('file', e)
-            context['form'] = form
+
+        form = Form(enctype='multipart/form-data', use_tokens=False)
+        form.add(FileWidget, 'file', title=_('File'), required=True)
+        form.add_submit('submit', _('Submit'))
+        form.add_submit('cancel', _('Cancel'))
+        if form.get_widget('cancel').parse():
+            return redirect('.')
+
+        if form.is_submitted() and not form.has_errors():
+            try:
+                return self.import_csv_submit(form.get_widget('file').parse().fp)
+            except ValueError as e:
+                form.set_error('file', e)
 
         get_response().breadcrumb.append(('import_csv', _('Import CSV')))
         html_top('data_management', _('Import CSV'))
+        context['form'] = form
 
         for field in self.get_import_csv_fields():
-            if field.convert_value_from_str is None:
-                context['unsupported_fields'].append(field)
             if not hasattr(field, 'required'):
                 continue
             if field.required and field.convert_value_from_str is None:
@@ -293,7 +286,7 @@ class CardPage(FormPage):
         job = ImportFromCsvAfterJob(carddef=self.formdef, data_lines=data_lines)
         if afterjob:
             get_response().add_after_job(job)
-            return redirect('import-csv?job=%s' % job.id)
+            return redirect(job.get_processing_url())
         else:
             job.execute()
 
@@ -378,3 +371,13 @@ class ImportFromCsvAfterJob(AfterJob):
             data_instance.just_created()
             data_instance.store()
             data_instance.perform_workflow()
+
+    def done_action_url(self):
+        carddef = self.kwargs['carddef_class'].get(self.kwargs['carddef_id'])
+        return carddef.get_url()
+
+    def done_action_label(self):
+        return _('Back to Listing')
+
+    def done_button_attributes(self):
+        return {'data-redirect-auto': 'true'}
diff --git a/wcs/backoffice/management.py b/wcs/backoffice/management.py
index 2decb2203..d60e2bd7c 100644
--- a/wcs/backoffice/management.py
+++ b/wcs/backoffice/management.py
@@ -1963,33 +1963,7 @@ class FormPage(Directory):
                     action_id=action['action'].id,
                     item_ids=item_ids))
         job.store()
-        return redirect('./?job=%s' % job.id)
-
-    def job_multi(self):
-        try:
-            job = AfterJob.get(get_request().form.get('job'))
-        except KeyError:
-            return redirect('.')
-
-        html_top('management', title=_('Executing Task'))
-        r = TemplateIO(html=True)
-        r += get_session().display_message()
-        get_response().add_javascript(['jquery.js', 'afterjob.js'])
-        r += htmltext('<dl class="job-status">')
-        r += htmltext('<dt>')
-        r += job.label
-        r += htmltext('</dt>')
-        r += htmltext('<dd>')
-        r += htmltext('<span class="afterjob" id="%s">') % job.id
-        r += _(job.status)
-        r += htmltext('</span>')
-        r += htmltext('</dd>')
-        r += htmltext('</dl>')
-
-        r += htmltext('<div class="done">')
-        r += htmltext('<a data-redirect-auto="true" href="./?%s">%s</a>') % (job.query_string, _('Back to Listing'))
-        r += htmltext('</div>')
-        return r.getvalue()
+        return redirect(job.get_processing_url())
 
     def csv(self):
         self.check_access()
@@ -2014,7 +1988,7 @@ class FormPage(Directory):
         if count > self.WCS_SYNC_EXPORT_LIMIT:
             job = get_response().add_after_job(job)
             job.store()
-            return redirect('export?job=%s' % job.id)
+            return redirect(job.get_processing_url())
         else:
             job.execute()
 
@@ -2025,36 +1999,6 @@ class FormPage(Directory):
 
     def export(self):
         self.check_access()
-        if get_request().form.get('download'):
-            return self.export_download()
-
-        try:
-            job = AfterJob.get(get_request().form.get('job'))
-        except KeyError:
-            return redirect('.')
-
-        html_top('management', title=_('Exporting'))
-        r = TemplateIO(html=True)
-        r += get_session().display_message()
-        get_response().add_javascript(['jquery.js', 'afterjob.js'])
-        r += htmltext('<dl class="job-status">')
-        r += htmltext('<dt>')
-        r += _(job.label)
-        r += htmltext('</dt>')
-        r += htmltext('<dd>')
-        r += htmltext('<span class="afterjob" id="%s">') % job.id
-        r += _(job.status)
-        r += htmltext('</span>')
-        r += htmltext('</dd>')
-        r += htmltext('</dl>')
-
-        r += htmltext('<div class="done">')
-        r += htmltext('<a download="%s" href="export?download=%s">%s</a>') % (
-                job.file_name, job.id, _('Download Export'))
-        r += htmltext('</div>')
-        return r.getvalue()
-
-    def export_download(self):
         try:
             job = AfterJob.get(get_request().form.get('download'))
         except KeyError:
@@ -2092,7 +2036,7 @@ class FormPage(Directory):
         if count > self.WCS_SYNC_EXPORT_LIMIT and not get_request().is_api_url():
             job = get_response().add_after_job(job)
             job.store()
-            return redirect('export?job=%s' % job.id)
+            return redirect(job.get_processing_url())
         else:
             job.execute()
 
@@ -3419,6 +3363,16 @@ class MassActionAfterJob(AfterJob):
             self.completion_status = '{}/{}'.format(i + 1, len(formdatas))
             self.store()
 
+    def done_action_url(self):
+        formdef = self.kwargs['formdef_class'].get(self.kwargs['formdef_id'])
+        return formdef.get_url(backoffice=True)
+
+    def done_action_label(self):
+        return _('Back to Listing')
+
+    def done_button_attributes(self):
+        return {'data-redirect-auto': 'true'}
+
 
 class CsvExportAfterJob(AfterJob):
     label = N_('Exporting forms in CSV')
@@ -3476,6 +3430,16 @@ class CsvExportAfterJob(AfterJob):
         self.content_type = 'text/csv'
         self.store()
 
+    def done_action_url(self):
+        formdef = self.kwargs['formdef_class'].get(self.kwargs['formdef_id'])
+        return formdef.get_url(backoffice=True) + 'export?download=%s' % self.id
+
+    def done_action_label(self):
+        return _('Download Export')
+
+    def done_button_attributes(self):
+        return {'download': self.file_name}
+
 
 class OdsExportAfterJob(CsvExportAfterJob):
     def __init__(self, formdef, **kwargs):
diff --git a/wcs/backoffice/root.py b/wcs/backoffice/root.py
index 71e1d5ef5..6f7d8c0dd 100644
--- a/wcs/backoffice/root.py
+++ b/wcs/backoffice/root.py
@@ -25,6 +25,8 @@ from ..qommon.backoffice.menu import html_top
 
 from ..qommon import misc, get_cfg
 from ..qommon import errors
+from ..qommon import template
+from ..qommon.afterjobs import AfterJob
 from ..qommon.form import *
 
 from wcs.formdef import FormDef
@@ -44,7 +46,7 @@ from . import data_management
 
 
 class RootDirectory(BackofficeRootDirectory):
-    _q_exports = ['', 'pending', 'statistics', ('menu.json', 'menu_json')]
+    _q_exports = ['', 'pending', 'statistics', ('menu.json', 'menu_json'), 'processing']
 
     forms = wcs.admin.forms.FormsDirectory()
     roles = wcs.admin.roles.RolesDirectory()
@@ -285,3 +287,15 @@ class RootDirectory(BackofficeRootDirectory):
                     'studio', 'cards', 'data'):
                 menu_items[-1]['icon'] = k.strip('/')
         return menu_items
+
+    def processing(self):
+        html_top('/')
+        try:
+            job = AfterJob.get(get_request().form.get('job'))
+        except KeyError:
+            return redirect('.')
+
+        get_response().add_javascript(['jquery.js', 'afterjob.js'])
+        return template.QommonTemplateResponse(
+                templates=['wcs/backoffice/processing.html'],
+                context={'job': job})
diff --git a/wcs/qommon/afterjobs.py b/wcs/qommon/afterjobs.py
index c6aecaaf5..71d3aaa08 100644
--- a/wcs/qommon/afterjobs.py
+++ b/wcs/qommon/afterjobs.py
@@ -97,3 +97,6 @@ class AfterJob(StorableObject):
             obj_dict['job_cmd'] = None
             return obj_dict
         return self.__dict__
+
+    def get_processing_url(self):
+        return '/backoffice/processing?job=%s' % self.id
diff --git a/wcs/qommon/static/css/dc2/admin.css b/wcs/qommon/static/css/dc2/admin.css
index 8cdb207d5..6791f43ee 100644
--- a/wcs/qommon/static/css/dc2/admin.css
+++ b/wcs/qommon/static/css/dc2/admin.css
@@ -1753,6 +1753,7 @@ ul#evolutions li div.msg li::after {
 
 div.section > dl {
 	padding-bottom: 0;
+	padding-top: 0;
 }
 
 div.data-source-preview ul {
diff --git a/wcs/templates/wcs/backoffice/card-data-import-form.html b/wcs/templates/wcs/backoffice/card-data-import-form.html
index 953a524d3..7cb7efdbe 100644
--- a/wcs/templates/wcs/backoffice/card-data-import-form.html
+++ b/wcs/templates/wcs/backoffice/card-data-import-form.html
@@ -8,33 +8,6 @@
 {% endblock %}
 
 {% block content %}
-{% if job %}
-
-{% if unsupported_fields %}
-{% for field in unsupported_fields %}
-<div class="warningnotice">
-  {% blocktrans with label=field.label description=field.description %}
-  Column {{ label }} will be ignored: type {{ description }} not supported.
-  {% endblocktrans %}
-</div>
-{% endfor %}
-{% endif %}
-
-<dl class="job-status">
-  <dt>{{ job.label }}</dt>
-  {% if warnings %}
-  <dt>
-    {% for warning in warnings %}
-    <div class="warningnotice">{{ warning }}</div>
-    {% endfor %}
-  </dt>
-  {% endif %}
-  <dd><span class="afterjob" id="{{ job.id }}">{% trans job.status %}</span></dd>
-</dl>
-<div class="done">
-  <a data-redirect-auto="true" href=".">{% trans "Back to Listing" %}</a>
-</div>
-{% else %}
 
 {% if required_fields %}
 <div class="errornotice">
@@ -51,5 +24,4 @@
 <p><a href="data-sample-csv">{% trans "Download sample file for this card" %}</a></p>
 {{ form.render|safe }}
 {% endif %}
-{% endif %}
 {% endblock %}
diff --git a/wcs/templates/wcs/backoffice/processing.html b/wcs/templates/wcs/backoffice/processing.html
new file mode 100644
index 000000000..6a8c3feee
--- /dev/null
+++ b/wcs/templates/wcs/backoffice/processing.html
@@ -0,0 +1,18 @@
+{% extends "wcs/backoffice/base.html" %}
+{% load i18n %}
+
+{% block appbar-title %}{% trans "Executing task..." %}{% endblock %}
+
+{% block content %}
+<div class="section">
+<dl class="job-status">
+  <dt>{% trans job.label %}</dt>
+  <dd><span class="afterjob" id="{{ job.id }}">{% trans job.status %}</span></dd>
+</dl>
+</div>
+
+<div class="done" style="display: none">
+  <a {% for attr in job.done_button_attributes.items %}{{ attr.0 }}="{{ attr.1 }}"{% endfor %}
+     class="button" href="{{ job.done_action_url }}">{{ job.done_action_label }}</a>
+</div>
+{% endblock %}
-- 
2.30.0

