466 lines
16 KiB
Python
466 lines
16 KiB
Python
import io
|
|
import os
|
|
import time
|
|
import zipfile
|
|
|
|
import pytest
|
|
from django.utils.encoding import force_bytes
|
|
from PIL import Image
|
|
from pyzbar.pyzbar import ZBarSymbol
|
|
from pyzbar.pyzbar import decode as zbar_decode_qrcode
|
|
from quixote import cleanup
|
|
from quixote.http_request import Upload as QuixoteUpload
|
|
|
|
from wcs import sessions
|
|
from wcs.blocks import BlockDef
|
|
from wcs.fields import (
|
|
BlockField,
|
|
BoolField,
|
|
DateField,
|
|
EmailField,
|
|
FileField,
|
|
ItemField,
|
|
ItemsField,
|
|
NumericField,
|
|
PageField,
|
|
StringField,
|
|
SubtitleField,
|
|
TableField,
|
|
TextField,
|
|
TitleField,
|
|
)
|
|
from wcs.formdef import FormDef
|
|
from wcs.qommon import force_str
|
|
from wcs.qommon.form import UploadedFile
|
|
from wcs.qommon.http_request import HTTPRequest
|
|
from wcs.qommon.upload_storage import PicklableUpload
|
|
from wcs.wf.export_to_model import ExportToModel, UploadValidationError, transform_to_pdf
|
|
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef
|
|
|
|
from ..utilities import clean_temporary_pub, create_temporary_pub
|
|
|
|
|
|
def setup_module(module):
|
|
cleanup()
|
|
|
|
|
|
def teardown_module(module):
|
|
clean_temporary_pub()
|
|
|
|
|
|
@pytest.fixture
|
|
def pub():
|
|
pub = create_temporary_pub()
|
|
pub.cfg['language'] = {'language': 'en'}
|
|
pub.cfg['identification'] = {'methods': ['password']}
|
|
pub.write_cfg()
|
|
req = HTTPRequest(None, {'SERVER_NAME': 'example.net', 'SCRIPT_NAME': ''})
|
|
req.response.filter = {}
|
|
req._user = None
|
|
pub._set_request(req)
|
|
req.session = sessions.BasicSession(id=1)
|
|
pub.set_config(req)
|
|
return pub
|
|
|
|
|
|
@pytest.mark.skipif(transform_to_pdf is None, reason='libreoffice not found')
|
|
def test_transform_to_pdf():
|
|
with open(os.path.join(os.path.dirname(__file__), '..', 'template.odt'), 'rb') as instream:
|
|
outstream = transform_to_pdf(instream)
|
|
assert outstream is not False
|
|
assert outstream.read(10).startswith(b'%PDF-')
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'template_name', ['template-with-image.odt', 'template-with-image-django-syntax.odt']
|
|
)
|
|
def test_export_to_model_image(pub, template_name):
|
|
formdef = FormDef()
|
|
formdef.name = 'baz'
|
|
formdef.fields = [
|
|
FileField(id='3', label='File', varname='image'),
|
|
]
|
|
formdef.store()
|
|
|
|
upload = PicklableUpload('test.jpeg', 'image/jpeg')
|
|
with open(os.path.join(os.path.dirname(__file__), '..', 'image-with-gps-data.jpeg'), 'rb') as fd:
|
|
image_data = fd.read()
|
|
upload.receive([image_data])
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.data = {'3': upload}
|
|
formdata.just_created()
|
|
formdata.store()
|
|
pub.substitutions.feed(formdata)
|
|
|
|
item = ExportToModel()
|
|
item.convert_to_pdf = False
|
|
item.method = 'non-interactive'
|
|
template_filename = os.path.join(os.path.dirname(__file__), '..', template_name)
|
|
with open(template_filename, 'rb') as fd:
|
|
template = fd.read()
|
|
upload = QuixoteUpload('/foo/template.odt', content_type='application/octet-stream')
|
|
upload.fp = io.BytesIO()
|
|
upload.fp.write(template)
|
|
upload.fp.seek(0)
|
|
item.model_file = UploadedFile(pub.app_dir, None, upload)
|
|
item.attach_to_history = True
|
|
|
|
item.perform(formdata)
|
|
|
|
assert formdata.evolution[-1].parts[-1].base_filename == 'template.odt'
|
|
with zipfile.ZipFile(formdata.evolution[-1].parts[-1].get_file_path(), mode='r') as zfile:
|
|
zinfo = zfile.getinfo('Pictures/10000000000000320000003276E9D46581B55C88.jpg')
|
|
# check the image has been replaced by the one from the formdata
|
|
assert zinfo.file_size == len(image_data)
|
|
|
|
# check with missing data or wrong kind of data
|
|
for field_value in (None, 'wrong kind'):
|
|
formdata = formdef.data_class()()
|
|
formdata.data = {'3': field_value}
|
|
formdata.just_created()
|
|
formdata.store()
|
|
pub.substitutions.feed(formdata)
|
|
|
|
item.perform(formdata)
|
|
|
|
with zipfile.ZipFile(formdata.evolution[-1].parts[-1].get_file_path(), mode='r') as zfile:
|
|
zinfo = zfile.getinfo('Pictures/10000000000000320000003276E9D46581B55C88.jpg')
|
|
# check the original image has been left
|
|
assert zinfo.file_size == 580
|
|
|
|
item.filename = 'formulaire-{{form_number}}/2.odt'
|
|
item.perform(formdata)
|
|
assert formdata.evolution[-1].parts[-1].base_filename == 'formulaire-%s-%s-2.odt' % (
|
|
formdef.id,
|
|
formdata.id,
|
|
)
|
|
|
|
|
|
def test_export_to_model_qrcode(pub):
|
|
FormDef.wipe()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'baz'
|
|
formdef.fields = []
|
|
formdef.store()
|
|
|
|
formdef.data_class().wipe()
|
|
formdata = formdef.data_class()()
|
|
formdata.data = {}
|
|
formdata.just_created()
|
|
formdata.store()
|
|
pub.substitutions.feed(formdata)
|
|
|
|
item = ExportToModel()
|
|
item.convert_to_pdf = False
|
|
item.method = 'non-interactive'
|
|
template_filename = os.path.join(os.path.dirname(__file__), '..', 'template-with-qrcode.odt')
|
|
with open(template_filename, 'rb') as fd:
|
|
template = fd.read()
|
|
upload = QuixoteUpload('/foo/template.odt', content_type='application/octet-stream')
|
|
upload.fp = io.BytesIO()
|
|
upload.fp.write(template)
|
|
upload.fp.seek(0)
|
|
item.model_file = UploadedFile(pub.app_dir, None, upload)
|
|
item.attach_to_history = True
|
|
|
|
item.perform(formdata)
|
|
|
|
assert formdata.evolution[-1].parts[-1].base_filename == 'template.odt'
|
|
with zipfile.ZipFile(formdata.evolution[-1].parts[-1].get_file_path(), mode='r') as zfile:
|
|
# base template use a jpg images and export_to_model does not rename it
|
|
# event when content is PNG, but it still works inside LibreOffice
|
|
# which ignores the filename extension.
|
|
image_filename = [name for name in zfile.namelist() if name.endswith('.jpg')][0]
|
|
with zfile.open(image_filename, 'r') as image_fd:
|
|
img = Image.open(image_fd)
|
|
assert (
|
|
zbar_decode_qrcode(img, symbols=[ZBarSymbol.QRCODE])[0].data.decode()
|
|
== 'http://example.net/backoffice/management/baz/1/'
|
|
)
|
|
|
|
|
|
def test_export_to_model_backoffice_field(pub):
|
|
wf = Workflow(name='email with attachments')
|
|
wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
|
|
wf.backoffice_fields_formdef.fields = [
|
|
FileField(id='bo1', label='bo field 1', varname='backoffice_file1'),
|
|
]
|
|
st1 = wf.add_status('Status1')
|
|
wf.store()
|
|
formdef = FormDef()
|
|
formdef.name = 'foo-export-to-bofile'
|
|
formdef.fields = [
|
|
StringField(id='1', label='String', varname='string'),
|
|
]
|
|
formdef.workflow_id = wf.id
|
|
formdef.store()
|
|
formdata = formdef.data_class()()
|
|
formdata.data = {}
|
|
formdata.just_created()
|
|
formdata.store()
|
|
pub.substitutions.feed(formdata)
|
|
|
|
item = ExportToModel()
|
|
item.method = 'non-interactive'
|
|
item.convert_to_pdf = False
|
|
template_filename = os.path.join(os.path.dirname(__file__), '..', 'template.odt')
|
|
with open(template_filename, 'rb') as fd:
|
|
template = fd.read()
|
|
upload = QuixoteUpload('/foo/template.odt', content_type='application/octet-stream')
|
|
upload.fp = io.BytesIO()
|
|
upload.fp.write(template)
|
|
upload.fp.seek(0)
|
|
item.model_file = UploadedFile(pub.app_dir, None, upload)
|
|
item.parent = st1
|
|
item.backoffice_filefield_id = 'bo1'
|
|
item.perform(formdata)
|
|
|
|
assert 'bo1' in formdata.data
|
|
fbo1 = formdata.data['bo1']
|
|
assert fbo1.base_filename == 'template.odt'
|
|
assert fbo1.content_type == 'application/octet-stream'
|
|
with zipfile.ZipFile(fbo1.get_file()) as zfile:
|
|
assert b'foo-export-to-bofile' in zfile.read('content.xml')
|
|
|
|
# no more 'bo1' backoffice field: do nothing
|
|
formdata = formdef.data_class()()
|
|
formdata.data = {}
|
|
formdata.just_created()
|
|
formdata.store()
|
|
pub.substitutions.feed(formdata)
|
|
# id is not bo1:
|
|
wf.backoffice_fields_formdef.fields = [
|
|
FileField(id='bo2', label='bo field 2'),
|
|
]
|
|
item.perform(formdata)
|
|
assert formdata.data == {}
|
|
# field is not a field file:
|
|
wf.backoffice_fields_formdef.fields = [
|
|
StringField(id='bo1', label='bo field 1'),
|
|
]
|
|
item.perform(formdata)
|
|
assert formdata.data == {}
|
|
# no field at all:
|
|
wf.backoffice_fields_formdef.fields = []
|
|
item.perform(formdata)
|
|
assert formdata.data == {}
|
|
|
|
|
|
def test_export_to_model_django_template(pub):
|
|
formdef = FormDef()
|
|
formdef.name = 'foo-export-to-template-with-django'
|
|
formdef.fields = [
|
|
StringField(id='1', label='String', varname='string'),
|
|
]
|
|
formdef.store()
|
|
formdata = formdef.data_class()()
|
|
formdata.data = {}
|
|
formdata.just_created()
|
|
formdata.store()
|
|
pub.substitutions.feed(formdata)
|
|
|
|
item = ExportToModel()
|
|
item.method = 'non-interactive'
|
|
item.attach_to_history = True
|
|
template_filename = os.path.join(os.path.dirname(__file__), '..', 'template-django.odt')
|
|
with open(template_filename, 'rb') as fd:
|
|
template = fd.read()
|
|
upload = QuixoteUpload('/foo/template-django.odt', content_type='application/octet-stream')
|
|
upload.fp = io.BytesIO()
|
|
upload.fp.write(template)
|
|
upload.fp.seek(0)
|
|
item.model_file = UploadedFile(pub.app_dir, None, upload)
|
|
item.convert_to_pdf = False
|
|
item.perform(formdata)
|
|
|
|
with open(formdata.evolution[0].parts[-1].get_file_path(), 'rb') as fd:
|
|
with zipfile.ZipFile(fd) as zout:
|
|
new_content = zout.read('content.xml')
|
|
assert b'>foo-export-to-template-with-django<' in new_content
|
|
|
|
formdef.name = 'Name with a \' simple quote'
|
|
formdef.store()
|
|
item.perform(formdata)
|
|
|
|
with open(formdata.evolution[0].parts[-1].get_file_path(), 'rb') as fd:
|
|
with zipfile.ZipFile(fd) as zout:
|
|
new_content = zout.read('content.xml')
|
|
assert b'>Name with a \' simple quote<' in new_content
|
|
|
|
formdef.name = 'A <> name'
|
|
formdef.store()
|
|
item.perform(formdata)
|
|
|
|
with open(formdata.evolution[0].parts[-1].get_file_path(), 'rb') as fd:
|
|
with zipfile.ZipFile(fd) as zout:
|
|
new_content = zout.read('content.xml')
|
|
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', 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'):
|
|
pub.loggederror_class.wipe()
|
|
upload = QuixoteUpload(filename, content_type=content_type)
|
|
upload.fp = io.BytesIO()
|
|
upload.fp.write(force_bytes(template))
|
|
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[-1].get_file_path()) as fd:
|
|
return fd.read()
|
|
|
|
# good XML
|
|
assert run(template='<a>{{ form_var_string }}</a>') == '<a>écho</a>'
|
|
assert (
|
|
run(template='<a>{{ form_var_string }}</a>', content_type='application/octet-stream') == '<a>écho</a>'
|
|
)
|
|
assert run(template='<a>{{ form_var_string }}</a>', filename='/foo/template.svg') == '<a>écho</a>'
|
|
|
|
# unknown file format
|
|
with pytest.raises(UploadValidationError) as e:
|
|
run(
|
|
template='<a>{{ form_var_string }}</a>',
|
|
filename='/foo/template.txt',
|
|
content_type='application/octet-stream',
|
|
)
|
|
assert str(e.value) == 'Only OpenDocument and XML files can be used.'
|
|
|
|
# invalid UTF-8
|
|
with pytest.raises(UploadValidationError) as e:
|
|
assert run(template=b'<name>test \xE0 {{form_var_string}}</name>') == ''
|
|
assert str(e.value) == 'XML model files must be UTF-8.'
|
|
|
|
# malformed XML
|
|
assert run(template='<a>{{ form_var_string }}<a>') == '<a>écho<a>'
|
|
# on error in the XML correctness no exception is raised but an error is logged
|
|
assert pub.loggederror_class.count() == 1
|
|
|
|
|
|
@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()
|
|
FormDef.wipe()
|
|
|
|
block = BlockDef()
|
|
block.name = 'foobar'
|
|
block.digest_template = 'X{{foobar_var_foo}}Y'
|
|
block.fields = [
|
|
StringField(id='123', required=True, label='Test', varname='foo'),
|
|
StringField(id='234', required=True, label='Test2', varname='bar'),
|
|
]
|
|
block.store()
|
|
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'foo-export-details'
|
|
formdef.fields = [
|
|
PageField(id='1', label='Page 1'),
|
|
TitleField(id='2', label='Title'),
|
|
SubtitleField(id='3', label='Subtitle'),
|
|
StringField(id='4', label='String', varname='string'),
|
|
EmailField(id='5', label='Email'),
|
|
TextField(id='6', label='Text'),
|
|
BoolField(id='8', label='Bool'),
|
|
FileField(id='9', label='File'),
|
|
DateField(id='10', label='Date'),
|
|
ItemField(id='11', label='Item', items=['foo', 'bar']),
|
|
TableField(id='12', label='Table', columns=['a', 'b'], rows=['c', 'd']),
|
|
PageField(id='13', label='Empty Page'),
|
|
TitleField(id='14', label='Empty Title'),
|
|
StringField(id='15', label='Empty String', varname='invisiblestr'),
|
|
BlockField(id='16', label='Block Field', block_slug='foobar'),
|
|
ItemsField(id='17', label='Items', items=['foo', 'bar']),
|
|
NumericField(id='18', label='Numeric Field'),
|
|
]
|
|
formdef.store()
|
|
formdef.data_class().wipe()
|
|
upload = PicklableUpload('test.jpeg', 'image/jpeg')
|
|
with open(os.path.join(os.path.dirname(__file__), '..', 'image-with-gps-data.jpeg'), 'rb') as fd:
|
|
upload.receive([fd.read()])
|
|
formdata = formdef.data_class()()
|
|
formdata.data = {
|
|
'4': 'string',
|
|
'5': 'foo@localhost',
|
|
'6': 'para1\npara2',
|
|
'8': False,
|
|
'9': upload,
|
|
'10': time.strptime('2015-05-12', '%Y-%m-%d'),
|
|
'11': 'foo',
|
|
'12': [['1', '2'], ['3', '4']],
|
|
# value from test_block_digest in tests/form_pages/test_block.py
|
|
'16': {
|
|
'data': [{'123': 'foo', '234': 'bar'}, {'123': 'foo2', '234': 'bar2'}],
|
|
'schema': {'123': 'string', '234': 'string'},
|
|
},
|
|
'16_display': 'XfooY, Xfoo2Y',
|
|
'18': 14.4,
|
|
}
|
|
formdata.just_created()
|
|
formdata.store()
|
|
pub.substitutions.feed(formdata)
|
|
|
|
item = ExportToModel()
|
|
item.method = 'non-interactive'
|
|
item.attach_to_history = True
|
|
template_filename = os.path.join(os.path.dirname(__file__), '..', filename)
|
|
with open(template_filename, 'rb') as fd:
|
|
template = fd.read()
|
|
upload = QuixoteUpload(filename, content_type='application/octet-stream')
|
|
upload.fp = io.BytesIO()
|
|
upload.fp.write(template)
|
|
upload.fp.seek(0)
|
|
item.model_file = UploadedFile(pub.app_dir, None, upload)
|
|
item.convert_to_pdf = False
|
|
item.perform(formdata)
|
|
|
|
with open(formdata.evolution[0].parts[-1].get_file_path(), 'rb') as fd:
|
|
with zipfile.ZipFile(fd) as zout:
|
|
new_content = force_str(zout.read('content.xml'))
|
|
# section content has been removed
|
|
assert 'Titre de page' not in new_content
|
|
assert 'Titre' not in new_content
|
|
assert 'Libellé de champ' not in new_content
|
|
assert 'Valeur de champ' not in new_content
|
|
# and new content has been inserted
|
|
assert '>Page 1<' in new_content
|
|
assert '>Title<' in new_content
|
|
assert '>Subtitle<' in new_content
|
|
assert '<text:span>string</text:span>' in new_content
|
|
assert '>para1<' in new_content
|
|
assert '>para2<' in new_content
|
|
assert '<text:span>No</text:span>' in new_content
|
|
assert 'xlink:href="http://example.net/foo-export-details/1/download?f=9"' in new_content
|
|
assert '>test.jpeg</text:a' in new_content
|
|
assert '>2015-05-12<' in new_content
|
|
assert 'Invisible' not in new_content
|
|
assert new_content.count('/table:table-cell') == 8
|
|
assert 'XfooY, Xfoo2Y' in new_content
|
|
assert '>14.4<' in new_content
|
|
|
|
if filename == 'template-form-details-no-styles.odt':
|
|
with open(formdata.evolution[0].parts[-1].get_file_path(), 'rb') as fd:
|
|
with zipfile.ZipFile(fd) as zout:
|
|
new_styles = force_str(zout.read('styles.xml'))
|
|
assert 'Field_20_Label' in new_styles
|