0004-workflows-add-support-for-form-details-odt-section-i.patch
tests/test_form_pages.py | ||
---|---|---|
61 | 61 |
z2 = zipfile.ZipFile(stream2) |
62 | 62 |
assert set(z1.namelist()) == set(z2.namelist()) |
63 | 63 |
for name in z1.namelist(): |
64 |
if name == 'styles.xml': |
|
65 |
continue |
|
64 | 66 |
t1, t2 = z1.read(name), z2.read(name) |
65 | 67 |
assert t1 == t2, 'file "%s" differs' % name |
66 | 68 |
tests/test_workflows.py | ||
---|---|---|
10 | 10 |
import mock |
11 | 11 | |
12 | 12 |
from django.utils import six |
13 |
from django.utils.encoding import force_bytes |
|
13 |
from django.utils.encoding import force_bytes, force_text
|
|
14 | 14 |
from django.utils.six import BytesIO, StringIO |
15 | 15 |
from django.utils.six.moves.urllib import parse as urlparse |
16 | 16 | |
... | ... | |
23 | 23 |
from wcs.formdef import FormDef |
24 | 24 |
from wcs import sessions |
25 | 25 |
from wcs.fields import (StringField, DateField, MapField, FileField, ItemField, |
26 |
ItemsField, CommentField) |
|
26 |
ItemsField, CommentField, EmailField, PageField, TitleField, |
|
27 |
SubtitleField, TextField, BoolField, TableField) |
|
27 | 28 |
from wcs.formdata import Evolution |
28 | 29 |
from wcs.logged_errors import LoggedError |
29 | 30 |
from wcs.roles import Role |
... | ... | |
3130 | 3131 |
assert b'>A <> name<' in new_content |
3131 | 3132 | |
3132 | 3133 | |
3134 |
@pytest.mark.parametrize('filename', ['template-form-details.odt', 'template-form-details-no-styles.odt']) |
|
3135 |
def test_export_to_model_form_details_section(pub, filename): |
|
3136 |
FormDef.wipe() |
|
3137 |
formdef = FormDef() |
|
3138 |
formdef.name = 'foo-export-details' |
|
3139 |
formdef.fields = [ |
|
3140 |
PageField(id='1', label='Page 1', type='page'), |
|
3141 |
TitleField(id='2', label='Title', type='title'), |
|
3142 |
SubtitleField(id='3', label='Subtitle', type='subtitle'), |
|
3143 |
StringField(id='4', label='String', type='string', varname='string'), |
|
3144 |
EmailField(id='5', label='Email', type='email'), |
|
3145 |
TextField(id='6', label='Text', type='text'), |
|
3146 |
BoolField(id='8', label='Bool', type='bool'), |
|
3147 |
FileField(id='9', label='File', type='file'), |
|
3148 |
DateField(id='10', label='Date', type='date'), |
|
3149 |
ItemField(id='11', label='Item', type='item', items=['foo', 'bar']), |
|
3150 |
ItemsField(id='11', label='Items', type='items', items=['foo', 'bar']), |
|
3151 |
TableField(id='12', label='Table', type='table', columns=['a', 'b'], rows=['c', 'd']) |
|
3152 |
] |
|
3153 |
formdef.store() |
|
3154 |
formdef.data_class().wipe() |
|
3155 |
upload = PicklableUpload('test.jpeg', 'image/jpeg') |
|
3156 |
upload.receive([open(os.path.join(os.path.dirname(__file__), 'image-with-gps-data.jpeg'), 'rb').read()]) |
|
3157 |
formdata = formdef.data_class()() |
|
3158 |
formdata.data = { |
|
3159 |
'4': 'string', |
|
3160 |
'5': 'foo@localhost', |
|
3161 |
'6': 'para1\npara2', |
|
3162 |
'8': False, |
|
3163 |
'9': upload, |
|
3164 |
'10': time.strptime('2015-05-12', '%Y-%m-%d'), |
|
3165 |
'11': 'foo', |
|
3166 |
'12': [['1', '2'], ['3', '4']], |
|
3167 |
} |
|
3168 |
formdata.just_created() |
|
3169 |
formdata.store() |
|
3170 |
pub.substitutions.feed(formdata) |
|
3171 | ||
3172 |
item = ExportToModel() |
|
3173 |
item.method = 'non-interactive' |
|
3174 |
item.attach_to_history = True |
|
3175 |
template_filename = os.path.join(os.path.dirname(__file__), filename) |
|
3176 |
template = open(template_filename, 'rb').read() |
|
3177 |
upload = QuixoteUpload(filename, content_type='application/octet-stream') |
|
3178 |
upload.fp = BytesIO() |
|
3179 |
upload.fp.write(template) |
|
3180 |
upload.fp.seek(0) |
|
3181 |
item.model_file = UploadedFile(pub.app_dir, None, upload) |
|
3182 |
item.convert_to_pdf = False |
|
3183 |
item.perform(formdata) |
|
3184 | ||
3185 |
new_content = force_text(zipfile.ZipFile(open(formdata.evolution[0].parts[0].filename, 'rb')).read('content.xml')) |
|
3186 |
assert 'Titre de page' not in new_content # section contents has been replaced |
|
3187 |
assert '>Page 1<' in new_content |
|
3188 |
assert '>Title<' in new_content |
|
3189 |
assert '>Subtitle<' in new_content |
|
3190 |
assert '<text:span>string</text:span>' in new_content |
|
3191 |
assert '>para1<' in new_content |
|
3192 |
assert '>para2<' in new_content |
|
3193 |
assert '<text:span>No</text:span>' in new_content |
|
3194 |
assert 'xlink:href="http://example.net/foo-export-details/1/download?f=9"' in new_content |
|
3195 |
assert '>test.jpeg</text:a' in new_content |
|
3196 |
assert '>2015-05-12<' in new_content |
|
3197 |
assert new_content.count('/table:table-cell') == 8 |
|
3198 | ||
3199 |
if filename == 'template-form-details-no-styles.odt': |
|
3200 |
new_styles = force_text(zipfile.ZipFile(open(formdata.evolution[0].parts[0].filename, 'rb')).read('styles.xml')) |
|
3201 |
assert 'Field_20_Label' in new_styles |
|
3202 | ||
3203 | ||
3133 | 3204 |
def test_global_timeouts(two_pubs): |
3134 | 3205 |
pub = two_pubs |
3135 | 3206 |
FormDef.wipe() |
wcs/fields.py | ||
---|---|---|
875 | 875 |
return '' |
876 | 876 | |
877 | 877 |
def get_opendocument_node_value(self, value, formdata=None, **kwargs): |
878 |
if self.pre: |
|
879 |
p = ET.Element('{%s}p' % OD_NS['text']) |
|
880 |
line_break = '<nsa:line-break xmlns:nsa="%(ns)s"/>' % {'ns': OD_NS['text']} |
|
881 |
as_node = ET.fromstring(str(htmlescape(value)).replace('\n', line_break)) |
|
882 |
p.text = as_node.text |
|
883 |
p.tail = as_node.tail |
|
884 |
for child in as_node.getchildren(): |
|
885 |
p.append(child) |
|
886 |
return p |
|
887 |
else: |
|
888 |
paragraphs = [] |
|
889 |
for paragraph in value.splitlines(): |
|
890 |
if paragraph.strip(): |
|
891 |
p = ET.Element('{%s}p' % OD_NS['text']) |
|
892 |
p.text = paragraph |
|
893 |
paragraphs.append(p) |
|
894 |
return paragraphs |
|
878 |
paragraphs = [] |
|
879 |
for paragraph in value.splitlines(): |
|
880 |
if paragraph.strip(): |
|
881 |
p = ET.Element('{%s}p' % OD_NS['text']) |
|
882 |
p.text = paragraph |
|
883 |
paragraphs.append(p) |
|
884 |
return paragraphs |
|
895 | 885 | |
896 | 886 |
def get_view_short_value(self, value, max_len = 30): |
897 | 887 |
return ellipsize(str(value), max_len) |
wcs/wf/export_to_model.py | ||
---|---|---|
48 | 48 | |
49 | 49 |
OO_TEXT_NS = 'urn:oasis:names:tc:opendocument:xmlns:text:1.0' |
50 | 50 |
OO_OFFICE_NS = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0' |
51 |
OO_STYLE_NS = 'urn:oasis:names:tc:opendocument:xmlns:style:1.0' |
|
51 | 52 |
OO_DRAW_NS = 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0' |
53 |
OO_FO_NS = 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0' |
|
52 | 54 |
XLINK_NS = 'http://www.w3.org/1999/xlink' |
53 | 55 |
USER_FIELD_DECL = '{%s}user-field-decl' % OO_TEXT_NS |
54 | 56 |
USER_FIELD_GET = '{%s}user-field-get' % OO_TEXT_NS |
57 |
SECTION_NODE = '{%s}section' % OO_TEXT_NS |
|
58 |
SECTION_NAME = '{%s}name' % OO_TEXT_NS |
|
55 | 59 |
STRING_VALUE = '{%s}string-value' % OO_OFFICE_NS |
56 | 60 |
DRAW_FRAME = '{%s}frame' % OO_DRAW_NS |
57 | 61 |
DRAW_NAME = '{%s}name' % OO_DRAW_NS |
... | ... | |
94 | 98 |
new_images = {} |
95 | 99 |
assert 'content.xml' in zin.namelist() |
96 | 100 |
for filename in zin.namelist(): |
97 |
# first pass to process meta.xml and content.xml
|
|
98 |
if filename not in ('meta.xml', 'content.xml'): |
|
101 |
# first pass to process meta.xml, content.xml and styles.xml
|
|
102 |
if filename not in ('meta.xml', 'content.xml', 'styles.xml'):
|
|
99 | 103 |
continue |
100 | 104 |
content = zin.read(filename) |
101 | 105 |
root = ET.fromstring(content) |
... | ... | |
105 | 109 | |
106 | 110 |
for filename in zin.namelist(): |
107 | 111 |
# second pass to copy/replace other files |
108 |
if filename in ('meta.xml', 'content.xml'): |
|
112 |
if filename in ('meta.xml', 'content.xml', 'styles.xml'):
|
|
109 | 113 |
continue |
110 | 114 |
if filename in new_images: |
111 | 115 |
content = new_images[filename].get_content() |
... | ... | |
418 | 422 |
def apply_od_template_to_formdata(self, formdata): |
419 | 423 |
context = get_formdata_template_context(formdata) |
420 | 424 | |
425 |
def process_styles(root): |
|
426 |
styles_node = root.find('{%s}styles' % OO_OFFICE_NS) |
|
427 |
if styles_node is None: |
|
428 |
return |
|
429 |
style_names = set([x.attrib.get('{%s}name' % OO_STYLE_NS) for x in styles_node.getchildren()]) |
|
430 |
for style_name in ['Page_20_Title', 'Form_20_Title', 'Form_20_Subtitle', |
|
431 |
'Field_20_Label', 'Field_20_Value']: |
|
432 |
# if any style name is defined, don't alter styles |
|
433 |
if style_name in style_names: |
|
434 |
return |
|
435 |
for i, style_name in enumerate(['Field_20_Label', 'Field_20_Value', |
|
436 |
'Form_20_Subtitle', 'Form_20_Title', 'Page_20_Title']): |
|
437 |
style_node = ET.SubElement(styles_node, '{%s}style' % OO_STYLE_NS) |
|
438 |
style_node.attrib['{%s}name' % OO_STYLE_NS] = style_name |
|
439 |
style_node.attrib['{%s}display-name' % OO_STYLE_NS] = style_name.replace('_20_', ' ') |
|
440 |
style_node.attrib['{%s}family' % OO_STYLE_NS] = 'paragraph' |
|
441 |
para_props = ET.SubElement(style_node, '{%s}paragraph-properties' % OO_STYLE_NS) |
|
442 |
if 'Value' not in style_name: |
|
443 |
para_props.attrib['{%s}margin-top' % OO_FO_NS] = '0.5cm' |
|
444 |
else: |
|
445 |
para_props.attrib['{%s}margin-left' % OO_FO_NS] = '0.25cm' |
|
446 |
if 'Title' in style_name: |
|
447 |
text_props = ET.SubElement(style_node, '{%s}text-properties' % OO_STYLE_NS) |
|
448 |
text_props.attrib['{%s}font-size' % OO_FO_NS] = '%s%%' % (90 + i * 10) |
|
449 |
text_props.attrib['{%s}font-weight' % OO_FO_NS] = 'bold' |
|
450 | ||
421 | 451 |
def process_root(root, new_images): |
452 |
if root.tag == '{%s}document-styles' % OO_OFFICE_NS: |
|
453 |
return process_styles(root) |
|
454 | ||
422 | 455 |
# cache for keeping computed user-field-decl value around |
423 | 456 |
user_field_values = {} |
424 | 457 | |
425 | 458 |
def process_text(t): |
426 | 459 |
t = template_on_context(context, force_str(t), autoescape=False) |
427 | 460 |
return force_text(t, get_publisher().site_charset) |
461 |
nodes = [] |
|
428 | 462 |
for node in root.iter(): |
463 |
nodes.append(node) |
|
464 |
for node in nodes: |
|
429 | 465 |
got_blank_lines = False |
466 |
if node.tag == SECTION_NODE and node.attrib.get(SECTION_NAME) == 'form_details': |
|
467 |
# custom behaviour for {{form_details}}, create real odt |
|
468 |
# markup. |
|
469 |
for child in node.getchildren(): |
|
470 |
node.remove(child) |
|
471 |
self.insert_form_details(node, formdata) |
|
472 | ||
430 | 473 |
# apply template to user-field-decl and update user-field-get |
431 | 474 |
if node.tag == USER_FIELD_DECL and STRING_VALUE in node.attrib: |
432 | 475 |
node.attrib[STRING_VALUE] = process_text(node.attrib[STRING_VALUE]) |
... | ... | |
481 | 524 |
outstream.seek(0) |
482 | 525 |
return outstream |
483 | 526 | |
527 |
def insert_form_details(self, node, formdata): |
|
528 |
field_details = formdata.get_summary_field_details() |
|
529 |
section_node = node |
|
530 |
for field_value_info in field_details: |
|
531 |
f = field_value_info['field'] |
|
532 | ||
533 |
if f.type == 'page': |
|
534 |
page_title = ET.SubElement(section_node, '{%s}h' % OO_TEXT_NS) |
|
535 |
page_title.attrib['{%s}outline-level' % OO_TEXT_NS] = '1' |
|
536 |
page_title.attrib['{%s}style-name' % OO_TEXT_NS] = 'Page_20_Title' |
|
537 |
page_title.text = f.label |
|
538 |
continue |
|
539 | ||
540 |
if f.type in ('title', 'subtitle'): |
|
541 |
label = template_on_formdata(None, f.label, autoescape=False) |
|
542 |
title = ET.SubElement(section_node, '{%s}h' % OO_TEXT_NS) |
|
543 |
title.attrib['{%s}outline-level' % OO_TEXT_NS] = '2' |
|
544 |
title.attrib['{%s}style-name' % OO_TEXT_NS] = 'Form_20_Title' |
|
545 |
if f.type == 'subtitle': |
|
546 |
title.attrib['{%s}outline-level' % OO_TEXT_NS] = '3' |
|
547 |
title.attrib['{%s}style-name' % OO_TEXT_NS] = 'Form_20_Subtitle' |
|
548 |
title.text = label |
|
549 |
continue |
|
550 | ||
551 |
if f.type == 'comment': |
|
552 |
# comment can be free form HTML, ignore them. |
|
553 |
continue |
|
554 | ||
555 |
if not f.get_opendocument_node_value: |
|
556 |
# unsupported field type |
|
557 |
continue |
|
558 | ||
559 |
label_p = ET.SubElement(section_node, '{%s}p' % OO_TEXT_NS) |
|
560 |
label_p.attrib['{%s}style-name' % OO_TEXT_NS] = 'Field_20_Label' |
|
561 |
label_p.text = f.label |
|
562 |
value = field_value_info['value'] |
|
563 |
if value is None: |
|
564 |
unset_value_p = ET.SubElement(section_node, '{%s}p' % OO_TEXT_NS) |
|
565 |
unset_value_p.attrib['{%s}style-name' % OO_TEXT_NS] = 'Field_20_Value' |
|
566 |
unset_value_i = ET.SubElement(unset_value_p, '{%s}span' % OO_TEXT_NS) |
|
567 |
unset_value_i.text = _('Not set') |
|
568 |
else: |
|
569 |
node_value = f.get_opendocument_node_value(value, formdata) |
|
570 |
if isinstance(node_value, list): |
|
571 |
for node in node_value: |
|
572 |
section_node.append(node) |
|
573 |
node.attrib['{%s}style-name' % OO_TEXT_NS] = 'Field_20_Value' |
|
574 |
elif node_value.tag in ('{%s}span' % OO_TEXT_NS, '{%s}a' % OO_TEXT_NS): |
|
575 |
value_p = ET.SubElement(section_node, '{%s}p' % OO_TEXT_NS) |
|
576 |
value_p.attrib['{%s}style-name' % OO_TEXT_NS] = 'Field_20_Value' |
|
577 |
value_p.append(node_value) |
|
578 |
else: |
|
579 |
node_value.attrib['{%s}style-name' % OO_TEXT_NS] = 'Field_20_Value' |
|
580 |
section_node.append(node_value) |
|
581 | ||
484 | 582 |
def model_file_export_to_xml(self, xml_item, charset, include_id=False): |
485 | 583 |
if not self.model_file: |
486 | 584 |
return |
487 |
- |