Project

General

Profile

0002-wf-export_to_model-isolate-ExportToModel-4292.patch

Benjamin Dauvergne, 28 October 2015 01:13 PM

Download (22.6 KB)

View differences:

Subject: [PATCH 2/4] wf/export_to_model: isolate ExportToModel (#4292)

 tests/test_form_pages.py  |   3 +-
 wcs/wf/export_to_model.py | 273 ++++++++++++++++++++++++++++++++++++++++++++++
 wcs/workflows.py          | 214 +-----------------------------------
 3 files changed, 276 insertions(+), 214 deletions(-)
 create mode 100644 wcs/wf/export_to_model.py
tests/test_form_pages.py
9 9
from wcs.qommon.form import UploadedFile
10 10
from wcs.qommon.ident.password_accounts import PasswordAccount
11 11
from wcs.formdef import FormDef
12
from wcs.workflows import Workflow, EditableWorkflowStatusItem, ExportToModel
12
from wcs.workflows import Workflow, EditableWorkflowStatusItem
13
from wcs.wf.export_to_model import ExportToModel
13 14
from wcs.wf.jump import JumpWorkflowStatusItem
14 15
from wcs.wf.attachment import AddAttachmentWorkflowStatusItem
15 16
from wcs.wf.form import FormWorkflowStatusItem, WorkflowFormFieldsFormDef
wcs/wf/export_to_model.py
1
# w.c.s. - web application for online forms
2
# Copyright (C) 2005-2010  Entr'ouvert
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16

  
17
from StringIO import StringIO
18
from xml.etree import ElementTree as ET
19

  
20
from quixote import get_response, get_request, get_publisher
21
from quixote.directory import Directory
22
from quixote.html import htmltext
23
from qommon import get_logger
24
from qommon import ezt
25
from qommon.form import (SingleSelectWidget, WidgetList, CheckboxWidget,
26
                         StringWidget, UploadWidget, WysiwygTextWidget, Upload,
27
                         UploadedFile)
28
from qommon.errors import PublishError
29
import qommon
30

  
31
from wcs.fields import SubtitleField, TitleField, CommentField, PageField
32
from wcs.workflows import (WorkflowStatusItem, AttachmentEvolutionPart,
33
                           template_on_formdata, register_item_class)
34

  
35

  
36
class TemplatingError(PublishError):
37
    def __init__(self, description):
38
        self.title = _('Templating Error')
39
        self.description = description
40

  
41

  
42
def get_varnames(fields):
43
    '''Extract variable names for helping people fill their templates.
44

  
45
       Prefer to variable name to the numeric field name.
46
    '''
47
    varnames = []
48
    for field in fields:
49
        if isinstance(field, (SubtitleField, TitleField, CommentField,
50
                              PageField)):
51
            continue
52
        # add it as f$n$
53
        label = field.label
54
        if field.varname:
55
            varnames.append(('var_%s' % field.varname, label))
56
        else:
57
            varnames.append(('f%s' % field.id, label))
58
    return varnames
59

  
60

  
61
class ExportToModelDirectory(Directory):
62
    _q_exports = ['']
63

  
64
    def __init__(self, formdata, wfstatusitem, wfstatus):
65
        self.formdata = formdata
66
        self.wfstatusitem = wfstatusitem
67

  
68
    def _q_index(self):
69
        if not self.wfstatusitem.model_file:
70
            raise TemplatingError(_('No model defined for this action'))
71
        response = get_response()
72
        response.content_type = self.wfstatusitem.model_file.content_type
73
        response.set_header('location', '..')
74

  
75
        if response.content_type != 'text/html':
76
            response.set_header('content-disposition',
77
                                'attachment; filename="%s"' %
78
                                self.wfstatusitem.model_file.base_filename)
79
        return self.wfstatusitem.apply_template_to_formdata(self.formdata)
80

  
81

  
82
def char2rtf(c):
83
    if ord(c) < 128:
84
        return c
85
    else:
86
        return '\\u%d?' % ord(c)
87

  
88

  
89
def str2rtf(s):
90
    s = ''.join([char2rtf(c) for c in s])
91
    return '{\\uc1{%s}}' % s
92

  
93

  
94
def rtf_process(value):
95
    if value is None:
96
        return None
97
    return str2rtf(unicode(str(value), get_publisher().site_charset))
98

  
99

  
100
class ExportToModel(WorkflowStatusItem):
101
    description = N_('Create Document')
102
    key = 'export_to_model'
103
    support_substitution_variables = True
104

  
105
    endpoint = False
106
    waitpoint = True
107

  
108
    label = None
109
    model_file = None
110
    attach_to_history = False
111
    directory_class = ExportToModelDirectory
112
    by = ['_receiver']
113
    backoffice_info_text = None
114

  
115
    def render_as_line(self):
116
        if self.label:
117
            if self.model_file:
118
                msg = 'with model named %(file_name)s of %(size)s bytes'
119
                model = _(msg) % {
120
                    'file_name': self.model_file.base_filename,
121
                    'size': self.model_file.size}
122
            else:
123
                model = _('no model set')
124
            msg = 'Create document, labeled %(label)s, %(model)s'
125
            return _(msg) % {
126
                'label': self.label,
127
                'model': model}
128
        else:
129
            return _('Create document (not completed)')
130

  
131
    def fill_form(self, form, formdata, user):
132
        label = self.label
133
        if not label:
134
            label = _('Create Document')
135
        form.add_submit('button%s' % self.id, label)
136
        widget = form.get_widget('button%s' % self.id)
137
        widget.backoffice_info_text = self.backoffice_info_text
138

  
139
    def submit_form(self, form, formdata, user, evo):
140
        if form.get_submit() == 'button%s' % self.id:
141
            if not evo.comment:
142
                evo.comment = _('Form exported in a model')
143
            in_backoffice = get_request() and get_request().is_in_backoffice()
144
            if self.attach_to_history:
145
                evo.add_part(AttachmentEvolutionPart(
146
                    self.model_file.base_filename,
147
                    StringIO(self.apply_template_to_formdata(formdata)),
148
                    content_type=self.model_file.content_type))
149
                return formdata.get_url(backoffice=in_backoffice)
150
            base_url = formdata.get_url(backoffice=in_backoffice)
151
            return base_url + self.get_directory_name()
152

  
153
    def model_file_validation(self, upload):
154
        if upload.content_type and upload.content_type == 'application/rtf':
155
            return True, ''
156
        if (upload.content_type and upload.content_type == 'application/octet-stream') or \
157
                upload.content_type is None:
158
            if upload.base_filename and upload.base_filename.endswith('.rtf'):
159
                return True, ''
160
        upload.fp.seek(0)
161
        if upload.read(10).startswith('{\\rtf'):
162
            upload.fp.seek(0)
163
            return True, ''
164
        return False, _('Only RTF files can be used')
165

  
166
    def add_parameters_widgets(self, form, parameters, prefix='',
167
                               formdef=None):
168
        if 'by' in parameters:
169
            options = [(None, '---', None)] + self.get_list_of_roles()
170
            form.add(WidgetList, '%sby' % prefix, title=_('By'),
171
                     element_type=SingleSelectWidget,
172
                     value=self.by,
173
                     add_element_label=_('Add Role'),
174
                     element_kwargs={
175
                         'render_br': False,
176
                         'options': options})
177
        if 'attach_to_history' in parameters:
178
            form.add(CheckboxWidget, '%sattach_to_history' % prefix,
179
                     title=_('Attach generated file to the form history'),
180
                     value=self.attach_to_history)
181
        if 'label' in parameters:
182
            form.add(StringWidget, '%slabel' % prefix,
183
                     title=_('Button Label'),
184
                     value=self.label)
185
        if 'model_file' in parameters:
186
            ids = (self.parent.parent.id, self.parent.id, self.id)
187
            if formdef:
188
                hint = htmltext('%s: <ul class="varnames">') \
189
                    % _('Available variables')
190
                varnames = get_varnames(formdef.fields)
191
                for pair in varnames:
192
                    hint += htmltext('<li><tt class="varname">[%s]</tt>'
193
                                     ' <label>%s</label></span></li>') % pair
194
                hint += htmltext('</ul>')
195
                ids = (formdef.id,) + ids
196
                filename = 'export_to_model-%s-%s-%s-%s.upload' % ids
197
            else:
198
                hint = _('You can use variables in your model using '
199
                         'the [variable] syntax, available variables '
200
                         'depends on the form.')
201
                filename = 'export_to_model-%s-%s-%s.upload' % ids
202
            widget_name = '%smodel_file' % prefix
203
            if formdef and formdef.workflow_options and \
204
                    formdef.workflow_options.get(widget_name) is not None:
205
                value = formdef.workflow_options.get(widget_name)
206
            else:
207
                value = self.model_file
208
            if value:
209
                prefix = htmltext('<div>%s: <a href="?file=%s">%s</a></div>') % \
210
                    (_('Current value'), widget_name, value.base_filename)
211
                hint = prefix + hint
212
            form.add(UploadWidget, widget_name, directory='models',
213
                     filename=filename, title=_('Model'), hint=hint,
214
                     validation=self.model_file_validation, value=value)
215
        if 'backoffice_info_text' in parameters:
216
            form.add(WysiwygTextWidget, '%sbackoffice_info_text' % prefix,
217
                     title=_('Information Text for Backoffice'),
218
                     value=self.backoffice_info_text)
219

  
220
    def get_directory_name(self):
221
        return qommon.misc.simplify(self.label or 'export_to_model', space='_')
222
    directory_name = property(get_directory_name)
223

  
224
    def apply_template_to_formdata(self, formdata):
225
        process = None
226
        if self.model_file.base_filename.endswith('.rtf'):
227
            process = rtf_process
228
        try:
229
            return template_on_formdata(formdata,
230
                                        self.model_file.get_file().read(),
231
                                        process=process)
232
        except ezt.UnknownReference, e:
233
            url = formdata.get_url()
234
            get_logger().error('error in template for export to model [%s], '
235
                               'unknown reference %s' % (url, str(e)))
236
            raise TemplatingError(_('Error in the template, reference %s is '
237
                                    'unknown') % str(e))
238
        except ezt.EZTException, e:
239
            url = formdata.get_url()
240
            get_logger().error('error in template for export to model [%s], '
241
                               'model could not be generated' % url)
242
            raise TemplatingError(_('Unknown error in the '
243
                                    'template: %s') % str(e))
244

  
245
    def get_parameters(self):
246
        return ('by', 'label', 'model_file', 'attach_to_history',
247
                'backoffice_info_text')
248

  
249
    def model_file_export_to_xml(self, xml_item, charset, include_id=False):
250
        if not self.model_file:
251
            return
252
        el = ET.SubElement(xml_item, 'model_file')
253
        ET.SubElement(el, 'base_filename').text = self.model_file.base_filename
254
        ET.SubElement(el, 'content_type').text = self.model_file.content_type
255
        ET.SubElement(el, 'content').text = self.model_file.get_file().read()
256

  
257
    def model_file_init_with_xml(self, elem, charset, include_id=False):
258
        if elem is None:
259
            return
260
        base_filename = elem.find('base_filename').text
261
        content_type = elem.find('content_type').text
262
        content = elem.find('content').text
263

  
264
        ids = (self.parent.parent.id, self.parent.id, self.id)
265
        filename = 'export_to_model-%s-%s-%s.upload' % ids
266

  
267
        upload = Upload(base_filename, content_type)
268
        upload.fp = StringIO()
269
        upload.fp.write(content)
270
        upload.fp.seek(0)
271
        self.model_file = UploadedFile('models', filename, upload)
272

  
273
register_item_class(ExportToModel)
wcs/workflows.py
112 112
                    os.path.basename(self.filename), self.orig_filename))
113 113

  
114 114

  
115
class TemplatingError(qommon.errors.PublishError):
116
    def __init__(self, description):
117
        self.title = _('Templating Error')
118
        self.description = description
119

  
120

  
121 115
class DuplicateStatusNameError(Exception):
122 116
    pass
123 117

  
124
def get_varnames(fields):
125
    '''Extract variable names for helping people fill their templates.
126

  
127
       Prefer to variable name to the numeric field name.
128
    '''
129
    varnames = []
130
    for field in fields:
131
        if isinstance(field, (SubtitleField, TitleField, CommentField, PageField)):
132
            continue
133
        # add it as f$n$
134
        label = field.label
135
        if field.varname:
136
            varnames.append(('var_%s' % field.varname, label))
137
        else:
138
            varnames.append(('f%s' % field.id, label))
139
    return varnames
140

  
141 118

  
142 119
class WorkflowVariablesFieldsFormDef(FormDef):
143 120
    '''Class to handle workflow variables, it loads and saves from/to
......
1620 1597

  
1621 1598
register_item_class(EditableWorkflowStatusItem)
1622 1599

  
1623
class ExportToModelDirectory(Directory):
1624
    _q_exports = [ '' ]
1625

  
1626
    def __init__(self, formdata, wfstatusitem, wfstatus):
1627
        self.formdata = formdata
1628
        self.wfstatusitem = wfstatusitem
1629

  
1630
    def _q_index(self):
1631
        if not self.wfstatusitem.model_file:
1632
            raise TemplatingError(_('No model defined for this action'))
1633
        response = get_response()
1634
        response.content_type = self.wfstatusitem.model_file.content_type
1635
        response.set_header('location', '..')
1636

  
1637
        if response.content_type != 'text/html':
1638
            response.set_header('content-disposition',
1639
                    'attachment; filename="%s"' %
1640
                        self.wfstatusitem.model_file.base_filename)
1641
        return self.wfstatusitem.apply_template_to_formdata(self.formdata)
1642

  
1643
def char2rtf(c):
1644
    if ord(c) < 128:
1645
        return c
1646
    else:
1647
        return '\\u%d?' % ord(c)
1648

  
1649
def str2rtf(s):
1650
    s = ''.join([ char2rtf(c) for c in s])
1651
    return '{\\uc1{%s}}' % s
1652

  
1653
def rtf_process(value):
1654
    if value is None:
1655
        return None
1656
    return str2rtf(unicode(str(value), get_publisher().site_charset))
1657

  
1658
class ExportToModel(WorkflowStatusItem):
1659
    description = N_('Create Document')
1660
    key = 'export_to_model'
1661
    support_substitution_variables = True
1662

  
1663
    endpoint = False
1664
    waitpoint = True
1665

  
1666
    label = None
1667
    model_file = None
1668
    attach_to_history = False
1669
    directory_class = ExportToModelDirectory
1670
    by = ['_receiver']
1671
    backoffice_info_text = None
1672

  
1673
    def render_as_line(self):
1674
        if self.label:
1675
            if self.model_file:
1676
                model = _('with model named %(file_name)s of %(size)s bytes') % {
1677
                    'file_name': self.model_file.base_filename,
1678
                    'size': self.model_file.size }
1679
            else:
1680
                model = _('no model set')
1681
            return _('Create document, labeled %(label)s, %(model)s') % {
1682
                    'label': self.label,
1683
                    'model': model }
1684
        else:
1685
            return _('Create document (not completed)')
1686

  
1687
    def fill_form(self, form, formdata, user):
1688
        label = self.label
1689
        if not label:
1690
            label = _('Create Document')
1691
        form.add_submit('button%s' % self.id, label)
1692
        form.get_widget('button%s' % self.id).backoffice_info_text = self.backoffice_info_text
1693

  
1694
    def submit_form(self, form, formdata, user, evo):
1695
        if form.get_submit() == 'button%s' % self.id:
1696
            if not evo.comment:
1697
                evo.comment = _('Form exported in a model')
1698
            in_backoffice = get_request() and get_request().is_in_backoffice()
1699
            if self.attach_to_history:
1700
                evo.add_part(AttachmentEvolutionPart(
1701
                    self.model_file.base_filename,
1702
                    StringIO(self.apply_template_to_formdata(formdata)),
1703
                    content_type=self.model_file.content_type))
1704
                return formdata.get_url(backoffice=in_backoffice)
1705
            return formdata.get_url(backoffice=in_backoffice) + self.get_directory_name()
1706

  
1707
    def model_file_validation(self, upload):
1708
        if upload.content_type and upload.content_type == 'application/rtf':
1709
            return True, ''
1710
        if (upload.content_type and upload.content_type == 'application/octet-stream') or \
1711
                upload.content_type is None:
1712
            if upload.base_filename and upload.base_filename.endswith('.rtf'):
1713
                return True, ''
1714
        upload.fp.seek(0)
1715
        if upload.read(10).startswith('{\\rtf'):
1716
            upload.fp.seek(0)
1717
            return True, ''
1718
        return False, _('Only RTF files can be used')
1719

  
1720
    def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
1721
        if 'by' in parameters:
1722
            form.add(WidgetList, '%sby' % prefix, title = _('By'), element_type = SingleSelectWidget,
1723
                value = self.by,
1724
                add_element_label = _('Add Role'),
1725
                element_kwargs={'render_br': False,
1726
                                'options': [(None, '---', None)] + self.get_list_of_roles()})
1727
        if 'attach_to_history' in parameters:
1728
            form.add(CheckboxWidget, '%sattach_to_history' % prefix,
1729
                    title=_('Attach generated file to the form history'),
1730
                    value=self.attach_to_history)
1731
        if 'label' in parameters:
1732
            form.add(StringWidget, '%slabel' % prefix, title = _('Button Label'), value = self.label)
1733
        if 'model_file' in parameters:
1734
            ids = (self.parent.parent.id, self.parent.id, self.id)
1735
            if formdef:
1736
                hint = htmltext('%s: <ul class="varnames">') % _('Available variables')
1737
                varnames = get_varnames(formdef.fields)
1738
                for pair in varnames:
1739
                    hint += htmltext('<li><tt class="varname">[%s]</tt> <label>%s</label></span></li>') % pair
1740
                hint += htmltext('</ul>')
1741
                ids = (formdef.id,) + ids
1742
                filename = 'export_to_model-%s-%s-%s-%s.upload' % ids
1743
            else:
1744
                hint = _('You can use variables in your model using '
1745
                        'the [variable] syntax, available variables depends on the form.')
1746
                filename = 'export_to_model-%s-%s-%s.upload' % ids
1747
            widget_name = '%smodel_file' % prefix
1748
            if formdef and formdef.workflow_options and \
1749
                    formdef.workflow_options.get(widget_name) is not None:
1750
                value = formdef.workflow_options.get(widget_name)
1751
            else:
1752
                value = self.model_file
1753
            if value:
1754
                hint = htmltext('<div>%s: <a href="?file=%s">%s</a></div>') % \
1755
                        (_('Current value'), widget_name, value.base_filename) + hint
1756
            form.add(UploadWidget, widget_name, directory='models',
1757
                    filename=filename, title=_('Model'), hint=hint,
1758
                    validation=self.model_file_validation, value=value)
1759
        if 'backoffice_info_text' in parameters:
1760
            form.add(WysiwygTextWidget, '%sbackoffice_info_text' % prefix,
1761
                     title=_('Information Text for Backoffice'),
1762
                     value=self.backoffice_info_text)
1763

  
1764
    def get_directory_name(self):
1765
        return qommon.misc.simplify(self.label or 'export_to_model', space='_')
1766
    directory_name = property(get_directory_name)
1767

  
1768
    def apply_template_to_formdata(self, formdata):
1769
        process = None
1770
        if self.model_file.base_filename.endswith('.rtf'):
1771
            process = rtf_process
1772
        try:
1773
            return template_on_formdata(formdata, self.model_file.get_file().read(),
1774
                    process=process)
1775
        except ezt.UnknownReference, e:
1776
            url = formdata.get_url()
1777
            get_logger().error('error in template for export to model [%s], unknown reference %s' % (url, str(e)))
1778
            raise TemplatingError(_('Error in the template, reference %s is unknown') % str(e))
1779
        except ezt.EZTException, e:
1780
            url = formdata.get_url()
1781
            get_logger().error('error in template for export to model [%s], model could not be generated' % url)
1782
            raise TemplatingError(_('Unknown error in the template: %s') % str(e))
1783

  
1784
    def get_parameters(self):
1785
        return ('by', 'label', 'model_file', 'attach_to_history', 'backoffice_info_text')
1786

  
1787
    def model_file_export_to_xml(self, xml_item, charset, include_id=False):
1788
        if not self.model_file:
1789
            return
1790
        el = ET.SubElement(xml_item, 'model_file')
1791
        ET.SubElement(el, 'base_filename').text = self.model_file.base_filename
1792
        ET.SubElement(el, 'content_type').text = self.model_file.content_type
1793
        ET.SubElement(el, 'content').text = self.model_file.get_file().read()
1794

  
1795
    def model_file_init_with_xml(self, elem, charset, include_id=False):
1796
        if elem is None:
1797
            return
1798
        base_filename = elem.find('base_filename').text
1799
        content_type = elem.find('content_type').text
1800
        content = elem.find('content').text
1801

  
1802
        ids = (self.parent.parent.id, self.parent.id, self.id)
1803
        filename = 'export_to_model-%s-%s-%s.upload' % ids
1804

  
1805
        upload = Upload(base_filename, content_type)
1806
        upload.fp = StringIO()
1807
        upload.fp.write(content)
1808
        upload.fp.seek(0)
1809
        self.model_file = UploadedFile('models', filename, upload)
1810

  
1811
register_item_class(ExportToModel)
1812

  
1813 1600

  
1814 1601
def load_extra():
1815 1602
    import wf.aggregation_email
......
1823 1610
    import wf.form
1824 1611
    import wf.register_comment
1825 1612
    import wf.anonymise
1613
    import wf.export_to_model
1826
-