From 0816a6e5703d2b4775d0c7d79feafc03931f130e Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Thu, 2 Sep 2021 11:43:30 +0200 Subject: [PATCH] wf/export_to_model: support XML templates (#56537) --- tests/test_workflows.py | 50 +++++++++++++++++++++++++++++++++++++-- wcs/wf/export_to_model.py | 50 +++++++++++++++++++++++++++++++++++---- 2 files changed, 94 insertions(+), 6 deletions(-) diff --git a/tests/test_workflows.py b/tests/test_workflows.py index dd76de00..cc569765 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -45,7 +45,7 @@ from wcs.fields import ( from wcs.formdata import Evolution from wcs.formdef import FormDef from wcs.qommon.errors import ConnectionError -from wcs.qommon.form import Form, UploadedFile +from wcs.qommon.form import Form, UploadedFile, UploadValidationError from wcs.qommon.http_request import HTTPRequest from wcs.qommon.upload_storage import PicklableUpload from wcs.wf.aggregation_email import ( @@ -60,7 +60,7 @@ from wcs.wf.create_formdata import CreateFormdataWorkflowStatusItem, Mapping from wcs.wf.criticality import MODE_DEC, MODE_INC, MODE_SET, ModifyCriticalityWorkflowStatusItem from wcs.wf.dispatch import DispatchWorkflowStatusItem from wcs.wf.edit_carddata import EditCarddataWorkflowStatusItem -from wcs.wf.export_to_model import ExportToModel, transform_to_pdf +from wcs.wf.export_to_model import ExportToModel, TemplatingError, transform_to_pdf from wcs.wf.form import FormWorkflowStatusItem, WorkflowFormFieldsFormDef from wcs.wf.geolocate import GeolocateWorkflowStatusItem from wcs.wf.jump import JumpWorkflowStatusItem, _apply_timeouts @@ -3987,6 +3987,52 @@ def test_export_to_model_django_template(pub): assert b'>A <> name<' in new_content +def test_export_to_model_xml(pub): + formdef = FormDef() + formdef.name = 'foo-export-to-template-with-django' + formdef.fields = [ + StringField(id='1', label='String', type='string', varname='string'), + ] + formdef.store() + formdata = formdef.data_class()() + formdata.data = {'1': 'écho'} + formdata.just_created() + formdata.store() + + # good XML + item = ExportToModel() + item.method = 'non-interactive' + item.attach_to_history = True + + def run(template, filename='/foo/template.xml', content_type='application/xml'): + upload = QuixoteUpload(filename, content_type=content_type) + upload.fp = io.BytesIO() + upload.fp.write(template.encode()) + upload.fp.seek(0) + item.model_file = UploadedFile(pub.app_dir, None, upload) + item.convert_to_pdf = False + pub.substitutions.reset() + pub.substitutions.feed(formdata) + item.perform(formdata) + with open(formdata.evolution[0].parts[0].filename) as fd: + return fd.read() + + # unknown file format + with pytest.raises(UploadValidationError): + run( + template='{{ form_var_string }}', + filename='/foo/template.txt', + content_type='application/octet-stream', + ) + + # good XML + assert run(template='{{ form_var_string }}') == 'écho' + + # malformed XML + with pytest.raises(TemplatingError): + run(template='{{ form_var_string }}') + + @pytest.mark.parametrize('filename', ['template-form-details.odt', 'template-form-details-no-styles.odt']) def test_export_to_model_form_details_section(pub, filename): BlockDef.wipe() diff --git a/wcs/wf/export_to_model.py b/wcs/wf/export_to_model.py index cfb27d27..09e77dc9 100644 --- a/wcs/wf/export_to_model.py +++ b/wcs/wf/export_to_model.py @@ -282,6 +282,8 @@ class ExportToModel(WorkflowStatusItem): fp = upload.get_file() else: raise UploadValidationError('unknown upload object %r' % upload) + + # RTF if upload.content_type and upload.content_type == 'application/rtf': return 'rtf' if ( @@ -292,6 +294,8 @@ class ExportToModel(WorkflowStatusItem): if fp.read(10).startswith(b'{\\rtf'): fp.seek(0) return 'rtf' + + # OpenDocument fp.seek(0) if upload.content_type and upload.content_type.startswith('application/vnd.oasis.opendocument.'): return 'opendocument' @@ -302,6 +306,12 @@ class ExportToModel(WorkflowStatusItem): return 'opendocument' if is_opendocument(fp): return 'opendocument' + + # XML + if (upload.content_type and upload.content_type in ('text/xml', 'application/xml')) or ( + upload.content_type is None and upload.base_filename and upload.base_filename.endswith('.rtf') + ): + return 'xml' raise UploadValidationError(_('Only RTF and OpenDocument files can be used')) def get_parameters(self): @@ -486,14 +496,46 @@ class ExportToModel(WorkflowStatusItem): outstream = self.apply_rtf_template_to_formdata(formdata) elif kind == 'opendocument': outstream = self.apply_od_template_to_formdata(formdata) + elif kind == 'xml': + outstream = self.apply_text_template_to_formdata(formdata) else: raise Exception('unsupported model kind %r' % kind) - if self.convert_to_pdf: - if transform_to_pdf is None: - raise Exception('libreoffice is missing') - return transform_to_pdf(outstream) + if kind == 'xml': + outstream.seek(0) + try: + ET.parse(outstream) + except ET.ParseError as e: + get_publisher().record_error( + _('Error in template for export to model'), formdata=formdata, exception=e + ) + raise TemplatingError(_('Error in template: output is not valid XML (%s)') % str(e)) + finally: + outstream.seek(0) + else: + if self.convert_to_pdf: + if transform_to_pdf is None: + raise Exception('libreoffice is missing') + return transform_to_pdf(outstream) return outstream + def apply_text_template_to_formdata(self, formdata): + try: + # force ezt_only=True because an RTF file may contain {{ characters + # and would be seen as a Django template + return io.BytesIO( + force_bytes( + template_on_formdata( + formdata, + self.model_file.get_file().read().decode(errors='surrogateescape'), + ) + ) + ) + except TemplateError as e: + get_publisher().record_error( + _('Error in template for export to model'), formdata=formdata, exception=e + ) + raise TemplatingError(_('Error in template: %s') % str(e)) + def apply_rtf_template_to_formdata(self, formdata): try: # force ezt_only=True because an RTF file may contain {{ characters -- 2.32.0.rc0