Project

General

Profile

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

Benjamin Dauvergne, 14 October 2015 01:23 PM

Download (21.8 KB)

View differences:

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

 tests/test_form_pages.py  |   3 +-
 wcs/wf/export_to_model.py | 257 ++++++++++++++++++++++++++++++++++++++++++++++
 wcs/workflows.py          | 214 +-------------------------------------
 3 files changed, 260 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
from StringIO import StringIO
2
from xml.etree import ElementTree as ET
3

  
4
from quixote import get_response, get_request, get_publisher
5
from quixote.directory import Directory
6
from quixote.html import htmltext
7
from qommon import get_logger
8
from qommon import ezt
9
from qommon.form import (SingleSelectWidget, WidgetList, CheckboxWidget,
10
                         StringWidget, UploadWidget, WysiwygTextWidget, Upload,
11
                         UploadedFile)
12
from qommon.errors import PublishError
13
import qommon
14

  
15
from wcs.fields import SubtitleField, TitleField, CommentField, PageField
16
from wcs.workflows import (WorkflowStatusItem, AttachmentEvolutionPart,
17
                           template_on_formdata, register_item_class)
18

  
19

  
20
class TemplatingError(PublishError):
21
    def __init__(self, description):
22
        self.title = _('Templating Error')
23
        self.description = description
24

  
25

  
26
def get_varnames(fields):
27
    '''Extract variable names for helping people fill their templates.
28

  
29
       Prefer to variable name to the numeric field name.
30
    '''
31
    varnames = []
32
    for field in fields:
33
        if isinstance(field, (SubtitleField, TitleField, CommentField,
34
                              PageField)):
35
            continue
36
        # add it as f$n$
37
        label = field.label
38
        if field.varname:
39
            varnames.append(('var_%s' % field.varname, label))
40
        else:
41
            varnames.append(('f%s' % field.id, label))
42
    return varnames
43

  
44

  
45
class ExportToModelDirectory(Directory):
46
    _q_exports = ['']
47

  
48
    def __init__(self, formdata, wfstatusitem, wfstatus):
49
        self.formdata = formdata
50
        self.wfstatusitem = wfstatusitem
51

  
52
    def _q_index(self):
53
        if not self.wfstatusitem.model_file:
54
            raise TemplatingError(_('No model defined for this action'))
55
        response = get_response()
56
        response.content_type = self.wfstatusitem.model_file.content_type
57
        response.set_header('location', '..')
58

  
59
        if response.content_type != 'text/html':
60
            response.set_header('content-disposition',
61
                                'attachment; filename="%s"' %
62
                                self.wfstatusitem.model_file.base_filename)
63
        return self.wfstatusitem.apply_template_to_formdata(self.formdata)
64

  
65

  
66
def char2rtf(c):
67
    if ord(c) < 128:
68
        return c
69
    else:
70
        return '\\u%d?' % ord(c)
71

  
72

  
73
def str2rtf(s):
74
    s = ''.join([char2rtf(c) for c in s])
75
    return '{\\uc1{%s}}' % s
76

  
77

  
78
def rtf_process(value):
79
    if value is None:
80
        return None
81
    return str2rtf(unicode(str(value), get_publisher().site_charset))
82

  
83

  
84
class ExportToModel(WorkflowStatusItem):
85
    description = N_('Create Document')
86
    key = 'export_to_model'
87
    support_substitution_variables = True
88

  
89
    endpoint = False
90
    waitpoint = True
91

  
92
    label = None
93
    model_file = None
94
    attach_to_history = False
95
    directory_class = ExportToModelDirectory
96
    by = ['_receiver']
97
    backoffice_info_text = None
98

  
99
    def render_as_line(self):
100
        if self.label:
101
            if self.model_file:
102
                msg = 'with model named %(file_name)s of %(size)s bytes'
103
                model = _(msg) % {
104
                    'file_name': self.model_file.base_filename,
105
                    'size': self.model_file.size}
106
            else:
107
                model = _('no model set')
108
            msg = 'Create document, labeled %(label)s, %(model)s'
109
            return _(msg) % {
110
                'label': self.label,
111
                'model': model}
112
        else:
113
            return _('Create document (not completed)')
114

  
115
    def fill_form(self, form, formdata, user):
116
        label = self.label
117
        if not label:
118
            label = _('Create Document')
119
        form.add_submit('button%s' % self.id, label)
120
        widget = form.get_widget('button%s' % self.id)
121
        widget.backoffice_info_text = self.backoffice_info_text
122

  
123
    def submit_form(self, form, formdata, user, evo):
124
        if form.get_submit() == 'button%s' % self.id:
125
            if not evo.comment:
126
                evo.comment = _('Form exported in a model')
127
            in_backoffice = get_request() and get_request().is_in_backoffice()
128
            if self.attach_to_history:
129
                evo.add_part(AttachmentEvolutionPart(
130
                    self.model_file.base_filename,
131
                    StringIO(self.apply_template_to_formdata(formdata)),
132
                    content_type=self.model_file.content_type))
133
                return formdata.get_url(backoffice=in_backoffice)
134
            base_url = formdata.get_url(backoffice=in_backoffice)
135
            return base_url + self.get_directory_name()
136

  
137
    def model_file_validation(self, upload):
138
        if upload.content_type and upload.content_type == 'application/rtf':
139
            return True, ''
140
        if (upload.content_type and upload.content_type == 'application/octet-stream') or \
141
                upload.content_type is None:
142
            if upload.base_filename and upload.base_filename.endswith('.rtf'):
143
                return True, ''
144
        upload.fp.seek(0)
145
        if upload.read(10).startswith('{\\rtf'):
146
            upload.fp.seek(0)
147
            return True, ''
148
        return False, _('Only RTF files can be used')
149

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

  
204
    def get_directory_name(self):
205
        return qommon.misc.simplify(self.label or 'export_to_model', space='_')
206
    directory_name = property(get_directory_name)
207

  
208
    def apply_template_to_formdata(self, formdata):
209
        process = None
210
        if self.model_file.base_filename.endswith('.rtf'):
211
            process = rtf_process
212
        try:
213
            return template_on_formdata(formdata,
214
                                        self.model_file.get_file().read(),
215
                                        process=process)
216
        except ezt.UnknownReference, e:
217
            url = formdata.get_url()
218
            get_logger().error('error in template for export to model [%s], '
219
                               'unknown reference %s' % (url, str(e)))
220
            raise TemplatingError(_('Error in the template, reference %s is '
221
                                    'unknown') % str(e))
222
        except ezt.EZTException, e:
223
            url = formdata.get_url()
224
            get_logger().error('error in template for export to model [%s], '
225
                               'model could not be generated' % url)
226
            raise TemplatingError(_('Unknown error in the '
227
                                    'template: %s') % str(e))
228

  
229
    def get_parameters(self):
230
        return ('by', 'label', 'model_file', 'attach_to_history',
231
                'backoffice_info_text')
232

  
233
    def model_file_export_to_xml(self, xml_item, charset, include_id=False):
234
        if not self.model_file:
235
            return
236
        el = ET.SubElement(xml_item, 'model_file')
237
        ET.SubElement(el, 'base_filename').text = self.model_file.base_filename
238
        ET.SubElement(el, 'content_type').text = self.model_file.content_type
239
        ET.SubElement(el, 'content').text = self.model_file.get_file().read()
240

  
241
    def model_file_init_with_xml(self, elem, charset, include_id=False):
242
        if elem is None:
243
            return
244
        base_filename = elem.find('base_filename').text
245
        content_type = elem.find('content_type').text
246
        content = elem.find('content').text
247

  
248
        ids = (self.parent.parent.id, self.parent.id, self.id)
249
        filename = 'export_to_model-%s-%s-%s.upload' % ids
250

  
251
        upload = Upload(base_filename, content_type)
252
        upload.fp = StringIO()
253
        upload.fp.write(content)
254
        upload.fp.seek(0)
255
        self.model_file = UploadedFile('models', filename, upload)
256

  
257
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
-