From 855d18b5d63f204c9feed11a689c641f557f77c0 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Wed, 14 Oct 2015 00:18:44 +0200 Subject: [PATCH 3/3] wf/export_to_model: add opendocument support (#4292) --- tests/template-out.odt | Bin 0 -> 31328 bytes tests/template.odt | Bin 0 -> 9790 bytes tests/test_form_pages.py | 90 ++++++++++++++++++++++++++++++++++++++++++++-- wcs/wf/export_to_model.py | 85 +++++++++++++++++++++++++++++++++++++++---- 4 files changed, 165 insertions(+), 10 deletions(-) create mode 100644 tests/template-out.odt create mode 100644 tests/template.odt diff --git a/tests/template-out.odt b/tests/template-out.odt new file mode 100644 index 0000000000000000000000000000000000000000..14df78e9bc59da2a4757272498984df63c8b2aa3 GIT binary patch literal 31328 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 WSzQSY{q{7%o0rp#{=B<)d-Oj%Y+MWg literal 0 HcmV?d00001 diff --git a/tests/test_form_pages.py b/tests/test_form_pages.py index e208923..cc1a2ca 100644 --- a/tests/test_form_pages.py +++ b/tests/test_form_pages.py @@ -3,6 +3,7 @@ import hashlib import os import re import StringIO +import zipfile from webtest import Upload from quixote.http_request import Upload as QuixoteUpload @@ -23,6 +24,15 @@ from wcs.sessions import BasicSession from utilities import get_app, login, create_temporary_pub, clean_temporary_pub, emails + +def assert_equal_zip(stream1, stream2): + z1 = zipfile.ZipFile(stream1) + z2 = zipfile.ZipFile(stream2) + assert set(z1.namelist()) == set(z2.namelist()) + for name in z1.namelist(): + assert z1.read(name) == z2.read(name) + + def pytest_generate_tests(metafunc): if 'pub' in metafunc.fixturenames: metafunc.parametrize('pub', ['pickle', 'sql'], indirect=True) @@ -1135,7 +1145,7 @@ def test_formdata_generated_document_download(pub): st1 = wf.add_status('Status1', 'st1') export_to = ExportToModel() export_to.label = 'create doc' - upload = QuixoteUpload('/foo/test.rtf', content_type='text/rtf') + upload = QuixoteUpload('/foo/test.rtf', content_type='application/rtf') upload.fp = StringIO.StringIO() upload.fp.write('HELLO WORLD') upload.fp.seek(0) @@ -1178,11 +1188,11 @@ def test_formdata_generated_document_download(pub): resp = resp.click('test.rtf') assert resp.location.endswith('/test.rtf') resp = resp.follow() - assert resp.content_type == 'text/rtf' + assert resp.content_type == 'application/rtf' assert resp.body == 'HELLO WORLD' # change file content, same name - upload = QuixoteUpload('/foo/test.rtf', content_type='text/rtf') + upload = QuixoteUpload('/foo/test.rtf', content_type='application/rtf') upload.fp = StringIO.StringIO() upload.fp.write('HELLO NEW WORLD') upload.fp.seek(0) @@ -1197,6 +1207,80 @@ def test_formdata_generated_document_download(pub): assert resp.click('test.rtf', index=0).follow().body == 'HELLO WORLD' assert resp.click('test.rtf', index=1).follow().body == 'HELLO NEW WORLD' +def test_formdata_generated_document_odt_download(pub): + create_user(pub) + wf = Workflow(name='status') + st1 = wf.add_status('Status1', 'st1') + export_to = ExportToModel() + export_to.label = 'create doc' + template_filename = os.path.join(os.path.dirname(__file__), 'template.odt') + template = open(template_filename).read() + upload = QuixoteUpload('/foo/template.odt', content_type='application/octet-stream') + upload.fp = StringIO.StringIO() + upload.fp.write(template) + upload.fp.seek(0) + export_to.model_file = UploadedFile(pub.app_dir, None, upload) + export_to.id = '_export_to' + export_to.by = ['_submitter'] + st1.items.append(export_to) + export_to.parent = st1 + wf.store() + + formdef = create_formdef() + formdef.workflow_id = wf.id + formdef.fields = [] + formdef.store() + formdef.data_class().wipe() + + resp = login(get_app(pub), username='foo', password='foo').get('/test/') + resp = resp.forms[0].submit('submit') + assert 'Check values then click submit.' in resp.body + resp = resp.forms[0].submit('submit') + assert resp.status_int == 302 + form_location = resp.location + resp = resp.follow() + assert 'The form has been recorded' in resp.body + + resp = resp.form.submit('button_export_to') + + resp = resp.follow() # $form/$id/create_doc + resp = resp.follow() # $form/$id/create_doc/ + with open(os.path.join(os.path.dirname(__file__), 'template-out.odt')) as f: + assert_equal_zip(StringIO.StringIO(resp.body), f) + + export_to.attach_to_history = True + wf.store() + + resp = login(get_app(pub), username='foo', password='foo').get(form_location) + resp = resp.form.submit('button_export_to') + assert resp.location == form_location + resp = resp.follow() # back to form page + + resp = resp.click('template.odt') + assert resp.location.endswith('/template.odt') + resp = resp.follow() + assert resp.content_type == 'application/octet-stream' + with open(os.path.join(os.path.dirname(__file__), 'template-out.odt')) as f: + assert_equal_zip(StringIO.StringIO(resp.body), f) + + # change file content, same name + upload = QuixoteUpload('/foo/test.rtf', content_type='application/rtf') + upload.fp = StringIO.StringIO() + upload.fp.write('HELLO NEW WORLD') + upload.fp.seek(0) + export_to.model_file = UploadedFile(pub.app_dir, None, upload) + wf.store() + + resp = login(get_app(pub), username='foo', password='foo').get(form_location) + resp = resp.form.submit('button_export_to') + assert resp.location == form_location + resp = resp.follow() # back to form page + + with open(os.path.join(os.path.dirname(__file__), 'template-out.odt')) as f: + body = resp.click('template.odt', index=0).follow().body + assert_equal_zip(StringIO.StringIO(body), f) + assert resp.click('test.rtf', index=0).follow().body == 'HELLO NEW WORLD' + def test_formdata_form_file_download(pub): create_user(pub) wf = Workflow(name='status') diff --git a/wcs/wf/export_to_model.py b/wcs/wf/export_to_model.py index 63dfd6a..a179dfa 100644 --- a/wcs/wf/export_to_model.py +++ b/wcs/wf/export_to_model.py @@ -1,5 +1,6 @@ from StringIO import StringIO from xml.etree import ElementTree as ET +import zipfile from quixote import get_response, get_request, get_publisher from quixote.directory import Directory @@ -17,6 +18,32 @@ from wcs.workflows import (WorkflowStatusItem, AttachmentEvolutionPart, template_on_formdata, register_item_class) +def transform_opendocument(instream, outstream, process): + '''Take a file-like object containing an ODT, ODS, or any open-office + format, parse context.xml with element tree and apply process to its root + node. + ''' + zin = zipfile.ZipFile(instream, mode='r') + zout = zipfile.ZipFile(outstream, mode='w') + assert 'content.xml' in zin.namelist() + for filename in zin.namelist(): + content = zin.read(filename) + if filename == 'content.xml': + root = ET.fromstring(content) + process(root) + content = ET.tostring(root) + zout.writestr(filename, content) + zout.close() + + +def is_opendocument(stream): + try: + with zipfile.ZipFile(stream) as z: + return 'content.xml' in z.namelist() + except zipfile.BadZipfile: + return False + + class TemplatingError(PublishError): def __init__(self, description): self.title = _('Templating Error') @@ -135,17 +162,32 @@ class ExportToModel(WorkflowStatusItem): return base_url + self.get_directory_name() def model_file_validation(self, upload): + if hasattr(upload, 'fp'): + fp = upload.fp + elif hasattr(upload, 'get_file'): + fp = upload.get_file() + else: + raise Exception('unknown upload %r' % upload) if upload.content_type and upload.content_type == 'application/rtf': - return True, '' + return 'rtf', '' if (upload.content_type and upload.content_type == 'application/octet-stream') or \ upload.content_type is None: if upload.base_filename and upload.base_filename.endswith('.rtf'): - return True, '' - upload.fp.seek(0) - if upload.read(10).startswith('{\\rtf'): - upload.fp.seek(0) - return True, '' - return False, _('Only RTF files can be used') + return 'rtf', '' + fp.seek(0) + if fp.read(10).startswith('{\\rtf'): + fp.seek(0) + return 'rtf', '' + if upload.content_type and upload.content_type.startswith('application/vnd.oasis.opendocument.'): + return 'od', '' + if (upload.content_type and upload.content_type == 'application/octet-stream') or \ + upload.content_type is None: + if upload.base_filename and upload.base_filename.rsplit('.', 1) in ('odt', 'ods', 'odc', 'odb'): + return 'od', '' + fp.seek(0) + if is_opendocument(fp): + return 'od', '' + return False, _('Only RTF and OpenDocument files can be used') def add_parameters_widgets(self, form, parameters, prefix='', formdef=None): @@ -206,6 +248,16 @@ class ExportToModel(WorkflowStatusItem): directory_name = property(get_directory_name) def apply_template_to_formdata(self, formdata): + assert self.model_file + kind, msg = self.model_file_validation(self.model_file) + if kind == 'rtf': + return self.apply_rtf_template_to_formdata(formdata) + elif kind == 'od': + return self.apply_od_template_to_formdata(formdata) + else: + raise Exception('unsupported model kind %r' % kind) + + def apply_rtf_template_to_formdata(self, formdata): process = None if self.model_file.base_filename.endswith('.rtf'): process = rtf_process @@ -226,6 +278,25 @@ class ExportToModel(WorkflowStatusItem): raise TemplatingError(_('Unknown error in the ' 'template: %s') % str(e)) + + def apply_od_template_to_formdata(self, formdata): + def process_root(root): + def process_text(t): + if isinstance(t, unicode): + t = t.encode(get_publisher().site_charset) + t = template_on_formdata(formdata, t) + return unicode(t, get_publisher().site_charset) + for node in root.iter(): + if node.text: + node.text = process_text(node.text) + if node.tail: + node.tail = process_text(node.tail) + outstream = StringIO() + transform_opendocument(self.model_file.get_file(), outstream, + process_root) + return outstream.getvalue() + + def get_parameters(self): return ('by', 'label', 'model_file', 'attach_to_history', 'backoffice_info_text') -- 2.1.4