From fa6d1c127c33c824983b337a534f89428b53a456 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Tue, 16 Jul 2019 20:41:33 +0200
Subject: [PATCH] settings: make site export async (#34915)

---
 tests/test_admin_pages.py |  18 +++++++
 wcs/admin/settings.py     | 101 ++++++++++++++++++++++++++++----------
 2 files changed, 92 insertions(+), 27 deletions(-)

diff --git a/tests/test_admin_pages.py b/tests/test_admin_pages.py
index 44f8f8929..bfc5ea8d2 100644
--- a/tests/test_admin_pages.py
+++ b/tests/test_admin_pages.py
@@ -9,6 +9,7 @@ import re
 import shutil
 import StringIO
 import tarfile
+import urlparse
 import time
 import xml.etree.ElementTree as ET
 import zipfile
@@ -3939,11 +3940,20 @@ 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=')
+    job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
+    resp = resp.follow()
+    assert 'completed' in resp.body
+    resp = resp.click('Download Export')
     zip_content = StringIO.StringIO(resp.body)
     zipf = zipfile.ZipFile(zip_content, 'a')
     filelist = zipf.namelist()
     assert len(filelist) == 0
 
+    # check afterjob ajax call
+    status_resp = app.get('/afterjobs/' + job_id)
+    assert status_resp.body == 'completed|completed'
+
     formdef = FormDef()
     formdef.name = 'foo'
     formdef.store()
@@ -3968,6 +3978,10 @@ 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=')
+    job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
+    resp = resp.follow()
+    resp = resp.click('Download Export')
     zip_content = StringIO.StringIO(resp.body)
     zipf = zipfile.ZipFile(zip_content, 'a')
     filelist = zipf.namelist()
@@ -4003,6 +4017,10 @@ 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=')
+    job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
+    resp = resp.follow()
+    resp = resp.click('Download Export')
     zip_content = StringIO.StringIO(resp.body)
     zipf = zipfile.ZipFile(zip_content, 'a')
     filelist = zipf.namelist()
diff --git a/wcs/admin/settings.py b/wcs/admin/settings.py
index eab1af9a8..8861c2c04 100644
--- a/wcs/admin/settings.py
+++ b/wcs/admin/settings.py
@@ -41,6 +41,7 @@ from qommon import template
 from qommon.form import *
 from qommon.sms import SMS
 
+from qommon.afterjobs import AfterJob
 from qommon.backoffice.menu import html_top
 from qommon.admin.menu import error_page
 from qommon.admin.cfg import cfg_submit
@@ -840,6 +841,9 @@ class SettingsDirectory(QommonSettingsDirectory):
             return redirect('.')
 
     def export(self):
+        if get_request().form.get('job') or get_request().form.get('download'):
+            return self.export_pending()
+
         form = Form(enctype="multipart/form-data")
         form.add(CheckboxWidget, 'formdefs', title = _('Forms'), value = True)
         form.add(CheckboxWidget, 'workflows', title = _('Workflows'), value = True)
@@ -861,10 +865,37 @@ class SettingsDirectory(QommonSettingsDirectory):
             r += htmltext('<h2>%s</h2>') % _('Export')
             r += form.render()
             return r.getvalue()
-        else:
-            return self.export_submit(form)
 
-    def export_submit(self, form):
+        class Exporter(object):
+            def __init__(self, dirs, settings):
+                self.app_dir = get_publisher().app_dir
+                self.dirs = dirs
+                self.settings = settings
+
+            def export(self, job):
+                c = StringIO()
+                z = zipfile.ZipFile(c, 'w')
+                for d in self.dirs:
+                    path = os.path.join(self.app_dir, d)
+                    if not os.path.exists(path):
+                        continue
+                    for f in os.listdir(path):
+                        if f == '.indexes':
+                            continue
+                        z.write(os.path.join(path, f), os.path.join(d, f))
+                if self.settings:
+                    z.write(os.path.join(self.app_dir, 'config.pck'), 'config.pck')
+                    for f in os.listdir(self.app_dir):
+                        if f.startswith('idp-') and os.path.splitext(f)[-1] in ('.pem', '.xml'):
+                            z.write(os.path.join(self.app_dir, f), f)
+                    if os.path.exists(os.path.join(self.app_dir, 'config')):
+                        for f in os.listdir(os.path.join(self.app_dir, 'config')):
+                            z.write(os.path.join(self.app_dir, 'config', f), os.path.join('config', f))
+                z.close()
+
+                job.file_content = c.getvalue()
+                job.store()
+
         dirs = []
         for w in ('formdefs', 'workflows', 'roles', 'categories',
                 'datasources', 'wscalls'):
@@ -875,32 +906,48 @@ class SettingsDirectory(QommonSettingsDirectory):
         if not dirs and not form.get_widget('settings').parse():
             return redirect('.')
 
-        c = StringIO()
-        z = zipfile.ZipFile(c, 'w')
-        app_dir = get_publisher().app_dir
-        for d in dirs:
-            path = os.path.join(app_dir, d)
-            if not os.path.exists(path):
-                continue
-            for f in os.listdir(path):
-                if f == '.indexes':
-                    continue
-                z.write(os.path.join(path, f), os.path.join(d, f))
-        if form.get_widget('settings').parse():
-            z.write(os.path.join(app_dir, 'config.pck'), 'config.pck')
-            for f in os.listdir(app_dir):
-                if f.startswith('idp-') and os.path.splitext(f)[-1] in ('.pem', '.xml'):
-                    z.write(os.path.join(app_dir, f), f)
-            if os.path.exists(os.path.join(app_dir, 'config')):
-                for f in os.listdir(os.path.join(app_dir, 'config')):
-                    z.write(os.path.join(app_dir, 'config', f), os.path.join('config', f))
+        exporter = Exporter(dirs, settings=form.get_widget('settings').parse())
 
-        z.close()
+        job = get_response().add_after_job(
+                N_('Exporting site settings'),
+                exporter.export)
+        job.store()
+        return redirect('export?job=%s' % job.id)
 
-        response = get_response()
-        response.set_content_type('application/x-wcs')
-        response.set_header('content-disposition', 'attachment; filename=export.wcs')
-        return c.getvalue()
+    def export_pending(self):
+        job_id = get_request().form.get('job') or 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()
 
     def p_import(self):
         form = Form(enctype='multipart/form-data')
-- 
2.22.0

