From a64718eec70338d5a593f6ec854757ebd3ecb7bc Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Tue, 13 Oct 2015 21:55:25 +0200 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 diff --git a/tests/test_form_pages.py b/tests/test_form_pages.py index af6228f..8a50a8d 100644 --- a/tests/test_form_pages.py +++ b/tests/test_form_pages.py @@ -9,7 +9,8 @@ from quixote.http_request import Upload as QuixoteUpload from wcs.qommon.form import UploadedFile from wcs.qommon.ident.password_accounts import PasswordAccount from wcs.formdef import FormDef -from wcs.workflows import Workflow, EditableWorkflowStatusItem, ExportToModel +from wcs.workflows import Workflow, EditableWorkflowStatusItem +from wcs.wf.export_to_model import ExportToModel from wcs.wf.jump import JumpWorkflowStatusItem from wcs.wf.attachment import AddAttachmentWorkflowStatusItem from wcs.wf.form import FormWorkflowStatusItem, WorkflowFormFieldsFormDef diff --git a/wcs/wf/export_to_model.py b/wcs/wf/export_to_model.py new file mode 100644 index 0000000..8053b2d --- /dev/null +++ b/wcs/wf/export_to_model.py @@ -0,0 +1,273 @@ +# w.c.s. - web application for online forms +# Copyright (C) 2005-2010 Entr'ouvert +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +from StringIO import StringIO +from xml.etree import ElementTree as ET + +from quixote import get_response, get_request, get_publisher +from quixote.directory import Directory +from quixote.html import htmltext +from qommon import get_logger +from qommon import ezt +from qommon.form import (SingleSelectWidget, WidgetList, CheckboxWidget, + StringWidget, UploadWidget, WysiwygTextWidget, Upload, + UploadedFile) +from qommon.errors import PublishError +import qommon + +from wcs.fields import SubtitleField, TitleField, CommentField, PageField +from wcs.workflows import (WorkflowStatusItem, AttachmentEvolutionPart, + template_on_formdata, register_item_class) + + +class TemplatingError(PublishError): + def __init__(self, description): + self.title = _('Templating Error') + self.description = description + + +def get_varnames(fields): + '''Extract variable names for helping people fill their templates. + + Prefer to variable name to the numeric field name. + ''' + varnames = [] + for field in fields: + if isinstance(field, (SubtitleField, TitleField, CommentField, + PageField)): + continue + # add it as f$n$ + label = field.label + if field.varname: + varnames.append(('var_%s' % field.varname, label)) + else: + varnames.append(('f%s' % field.id, label)) + return varnames + + +class ExportToModelDirectory(Directory): + _q_exports = [''] + + def __init__(self, formdata, wfstatusitem, wfstatus): + self.formdata = formdata + self.wfstatusitem = wfstatusitem + + def _q_index(self): + if not self.wfstatusitem.model_file: + raise TemplatingError(_('No model defined for this action')) + response = get_response() + response.content_type = self.wfstatusitem.model_file.content_type + response.set_header('location', '..') + + if response.content_type != 'text/html': + response.set_header('content-disposition', + 'attachment; filename="%s"' % + self.wfstatusitem.model_file.base_filename) + return self.wfstatusitem.apply_template_to_formdata(self.formdata) + + +def char2rtf(c): + if ord(c) < 128: + return c + else: + return '\\u%d?' % ord(c) + + +def str2rtf(s): + s = ''.join([char2rtf(c) for c in s]) + return '{\\uc1{%s}}' % s + + +def rtf_process(value): + if value is None: + return None + return str2rtf(unicode(str(value), get_publisher().site_charset)) + + +class ExportToModel(WorkflowStatusItem): + description = N_('Create Document') + key = 'export_to_model' + support_substitution_variables = True + + endpoint = False + waitpoint = True + + label = None + model_file = None + attach_to_history = False + directory_class = ExportToModelDirectory + by = ['_receiver'] + backoffice_info_text = None + + def render_as_line(self): + if self.label: + if self.model_file: + msg = 'with model named %(file_name)s of %(size)s bytes' + model = _(msg) % { + 'file_name': self.model_file.base_filename, + 'size': self.model_file.size} + else: + model = _('no model set') + msg = 'Create document, labeled %(label)s, %(model)s' + return _(msg) % { + 'label': self.label, + 'model': model} + else: + return _('Create document (not completed)') + + def fill_form(self, form, formdata, user): + label = self.label + if not label: + label = _('Create Document') + form.add_submit('button%s' % self.id, label) + widget = form.get_widget('button%s' % self.id) + widget.backoffice_info_text = self.backoffice_info_text + + def submit_form(self, form, formdata, user, evo): + if form.get_submit() == 'button%s' % self.id: + if not evo.comment: + evo.comment = _('Form exported in a model') + in_backoffice = get_request() and get_request().is_in_backoffice() + if self.attach_to_history: + evo.add_part(AttachmentEvolutionPart( + self.model_file.base_filename, + StringIO(self.apply_template_to_formdata(formdata)), + content_type=self.model_file.content_type)) + return formdata.get_url(backoffice=in_backoffice) + base_url = formdata.get_url(backoffice=in_backoffice) + return base_url + self.get_directory_name() + + def model_file_validation(self, upload): + if upload.content_type and upload.content_type == 'application/rtf': + return True, '' + if (upload.content_type and upload.content_type == 'application/octet-stream') or \ + upload.content_type is None: + if upload.base_filename and upload.base_filename.endswith('.rtf'): + return True, '' + upload.fp.seek(0) + if upload.read(10).startswith('{\\rtf'): + upload.fp.seek(0) + return True, '' + return False, _('Only RTF files can be used') + + def add_parameters_widgets(self, form, parameters, prefix='', + formdef=None): + if 'by' in parameters: + options = [(None, '---', None)] + self.get_list_of_roles() + form.add(WidgetList, '%sby' % prefix, title=_('By'), + element_type=SingleSelectWidget, + value=self.by, + add_element_label=_('Add Role'), + element_kwargs={ + 'render_br': False, + 'options': options}) + if 'attach_to_history' in parameters: + form.add(CheckboxWidget, '%sattach_to_history' % prefix, + title=_('Attach generated file to the form history'), + value=self.attach_to_history) + if 'label' in parameters: + form.add(StringWidget, '%slabel' % prefix, + title=_('Button Label'), + value=self.label) + if 'model_file' in parameters: + ids = (self.parent.parent.id, self.parent.id, self.id) + if formdef: + hint = htmltext('%s: ') + ids = (formdef.id,) + ids + filename = 'export_to_model-%s-%s-%s-%s.upload' % ids + else: + hint = _('You can use variables in your model using ' + 'the [variable] syntax, available variables ' + 'depends on the form.') + filename = 'export_to_model-%s-%s-%s.upload' % ids + widget_name = '%smodel_file' % prefix + if formdef and formdef.workflow_options and \ + formdef.workflow_options.get(widget_name) is not None: + value = formdef.workflow_options.get(widget_name) + else: + value = self.model_file + if value: + prefix = htmltext('
%s: %s
') % \ + (_('Current value'), widget_name, value.base_filename) + hint = prefix + hint + form.add(UploadWidget, widget_name, directory='models', + filename=filename, title=_('Model'), hint=hint, + validation=self.model_file_validation, value=value) + if 'backoffice_info_text' in parameters: + form.add(WysiwygTextWidget, '%sbackoffice_info_text' % prefix, + title=_('Information Text for Backoffice'), + value=self.backoffice_info_text) + + def get_directory_name(self): + return qommon.misc.simplify(self.label or 'export_to_model', space='_') + directory_name = property(get_directory_name) + + def apply_template_to_formdata(self, formdata): + process = None + if self.model_file.base_filename.endswith('.rtf'): + process = rtf_process + try: + return template_on_formdata(formdata, + self.model_file.get_file().read(), + process=process) + except ezt.UnknownReference, e: + url = formdata.get_url() + get_logger().error('error in template for export to model [%s], ' + 'unknown reference %s' % (url, str(e))) + raise TemplatingError(_('Error in the template, reference %s is ' + 'unknown') % str(e)) + except ezt.EZTException, e: + url = formdata.get_url() + get_logger().error('error in template for export to model [%s], ' + 'model could not be generated' % url) + raise TemplatingError(_('Unknown error in the ' + 'template: %s') % str(e)) + + def get_parameters(self): + return ('by', 'label', 'model_file', 'attach_to_history', + 'backoffice_info_text') + + def model_file_export_to_xml(self, xml_item, charset, include_id=False): + if not self.model_file: + return + el = ET.SubElement(xml_item, 'model_file') + ET.SubElement(el, 'base_filename').text = self.model_file.base_filename + ET.SubElement(el, 'content_type').text = self.model_file.content_type + ET.SubElement(el, 'content').text = self.model_file.get_file().read() + + def model_file_init_with_xml(self, elem, charset, include_id=False): + if elem is None: + return + base_filename = elem.find('base_filename').text + content_type = elem.find('content_type').text + content = elem.find('content').text + + ids = (self.parent.parent.id, self.parent.id, self.id) + filename = 'export_to_model-%s-%s-%s.upload' % ids + + upload = Upload(base_filename, content_type) + upload.fp = StringIO() + upload.fp.write(content) + upload.fp.seek(0) + self.model_file = UploadedFile('models', filename, upload) + +register_item_class(ExportToModel) diff --git a/wcs/workflows.py b/wcs/workflows.py index 8ea791f..8e3d650 100644 --- a/wcs/workflows.py +++ b/wcs/workflows.py @@ -112,32 +112,9 @@ class AttachmentEvolutionPart: #pylint: disable=C1001 os.path.basename(self.filename), self.orig_filename)) -class TemplatingError(qommon.errors.PublishError): - def __init__(self, description): - self.title = _('Templating Error') - self.description = description - - class DuplicateStatusNameError(Exception): pass -def get_varnames(fields): - '''Extract variable names for helping people fill their templates. - - Prefer to variable name to the numeric field name. - ''' - varnames = [] - for field in fields: - if isinstance(field, (SubtitleField, TitleField, CommentField, PageField)): - continue - # add it as f$n$ - label = field.label - if field.varname: - varnames.append(('var_%s' % field.varname, label)) - else: - varnames.append(('f%s' % field.id, label)) - return varnames - class WorkflowVariablesFieldsFormDef(FormDef): '''Class to handle workflow variables, it loads and saves from/to @@ -1620,196 +1597,6 @@ class EditableWorkflowStatusItem(WorkflowStatusItem): register_item_class(EditableWorkflowStatusItem) -class ExportToModelDirectory(Directory): - _q_exports = [ '' ] - - def __init__(self, formdata, wfstatusitem, wfstatus): - self.formdata = formdata - self.wfstatusitem = wfstatusitem - - def _q_index(self): - if not self.wfstatusitem.model_file: - raise TemplatingError(_('No model defined for this action')) - response = get_response() - response.content_type = self.wfstatusitem.model_file.content_type - response.set_header('location', '..') - - if response.content_type != 'text/html': - response.set_header('content-disposition', - 'attachment; filename="%s"' % - self.wfstatusitem.model_file.base_filename) - return self.wfstatusitem.apply_template_to_formdata(self.formdata) - -def char2rtf(c): - if ord(c) < 128: - return c - else: - return '\\u%d?' % ord(c) - -def str2rtf(s): - s = ''.join([ char2rtf(c) for c in s]) - return '{\\uc1{%s}}' % s - -def rtf_process(value): - if value is None: - return None - return str2rtf(unicode(str(value), get_publisher().site_charset)) - -class ExportToModel(WorkflowStatusItem): - description = N_('Create Document') - key = 'export_to_model' - support_substitution_variables = True - - endpoint = False - waitpoint = True - - label = None - model_file = None - attach_to_history = False - directory_class = ExportToModelDirectory - by = ['_receiver'] - backoffice_info_text = None - - def render_as_line(self): - if self.label: - if self.model_file: - model = _('with model named %(file_name)s of %(size)s bytes') % { - 'file_name': self.model_file.base_filename, - 'size': self.model_file.size } - else: - model = _('no model set') - return _('Create document, labeled %(label)s, %(model)s') % { - 'label': self.label, - 'model': model } - else: - return _('Create document (not completed)') - - def fill_form(self, form, formdata, user): - label = self.label - if not label: - label = _('Create Document') - form.add_submit('button%s' % self.id, label) - form.get_widget('button%s' % self.id).backoffice_info_text = self.backoffice_info_text - - def submit_form(self, form, formdata, user, evo): - if form.get_submit() == 'button%s' % self.id: - if not evo.comment: - evo.comment = _('Form exported in a model') - in_backoffice = get_request() and get_request().is_in_backoffice() - if self.attach_to_history: - evo.add_part(AttachmentEvolutionPart( - self.model_file.base_filename, - StringIO(self.apply_template_to_formdata(formdata)), - content_type=self.model_file.content_type)) - return formdata.get_url(backoffice=in_backoffice) - return formdata.get_url(backoffice=in_backoffice) + self.get_directory_name() - - def model_file_validation(self, upload): - if upload.content_type and upload.content_type == 'application/rtf': - return True, '' - if (upload.content_type and upload.content_type == 'application/octet-stream') or \ - upload.content_type is None: - if upload.base_filename and upload.base_filename.endswith('.rtf'): - return True, '' - upload.fp.seek(0) - if upload.read(10).startswith('{\\rtf'): - upload.fp.seek(0) - return True, '' - return False, _('Only RTF files can be used') - - def add_parameters_widgets(self, form, parameters, prefix='', formdef=None): - if 'by' in parameters: - form.add(WidgetList, '%sby' % prefix, title = _('By'), element_type = SingleSelectWidget, - value = self.by, - add_element_label = _('Add Role'), - element_kwargs={'render_br': False, - 'options': [(None, '---', None)] + self.get_list_of_roles()}) - if 'attach_to_history' in parameters: - form.add(CheckboxWidget, '%sattach_to_history' % prefix, - title=_('Attach generated file to the form history'), - value=self.attach_to_history) - if 'label' in parameters: - form.add(StringWidget, '%slabel' % prefix, title = _('Button Label'), value = self.label) - if 'model_file' in parameters: - ids = (self.parent.parent.id, self.parent.id, self.id) - if formdef: - hint = htmltext('%s: ') - ids = (formdef.id,) + ids - filename = 'export_to_model-%s-%s-%s-%s.upload' % ids - else: - hint = _('You can use variables in your model using ' - 'the [variable] syntax, available variables depends on the form.') - filename = 'export_to_model-%s-%s-%s.upload' % ids - widget_name = '%smodel_file' % prefix - if formdef and formdef.workflow_options and \ - formdef.workflow_options.get(widget_name) is not None: - value = formdef.workflow_options.get(widget_name) - else: - value = self.model_file - if value: - hint = htmltext('
%s: %s
') % \ - (_('Current value'), widget_name, value.base_filename) + hint - form.add(UploadWidget, widget_name, directory='models', - filename=filename, title=_('Model'), hint=hint, - validation=self.model_file_validation, value=value) - if 'backoffice_info_text' in parameters: - form.add(WysiwygTextWidget, '%sbackoffice_info_text' % prefix, - title=_('Information Text for Backoffice'), - value=self.backoffice_info_text) - - def get_directory_name(self): - return qommon.misc.simplify(self.label or 'export_to_model', space='_') - directory_name = property(get_directory_name) - - def apply_template_to_formdata(self, formdata): - process = None - if self.model_file.base_filename.endswith('.rtf'): - process = rtf_process - try: - return template_on_formdata(formdata, self.model_file.get_file().read(), - process=process) - except ezt.UnknownReference, e: - url = formdata.get_url() - get_logger().error('error in template for export to model [%s], unknown reference %s' % (url, str(e))) - raise TemplatingError(_('Error in the template, reference %s is unknown') % str(e)) - except ezt.EZTException, e: - url = formdata.get_url() - get_logger().error('error in template for export to model [%s], model could not be generated' % url) - raise TemplatingError(_('Unknown error in the template: %s') % str(e)) - - def get_parameters(self): - return ('by', 'label', 'model_file', 'attach_to_history', 'backoffice_info_text') - - def model_file_export_to_xml(self, xml_item, charset, include_id=False): - if not self.model_file: - return - el = ET.SubElement(xml_item, 'model_file') - ET.SubElement(el, 'base_filename').text = self.model_file.base_filename - ET.SubElement(el, 'content_type').text = self.model_file.content_type - ET.SubElement(el, 'content').text = self.model_file.get_file().read() - - def model_file_init_with_xml(self, elem, charset, include_id=False): - if elem is None: - return - base_filename = elem.find('base_filename').text - content_type = elem.find('content_type').text - content = elem.find('content').text - - ids = (self.parent.parent.id, self.parent.id, self.id) - filename = 'export_to_model-%s-%s-%s.upload' % ids - - upload = Upload(base_filename, content_type) - upload.fp = StringIO() - upload.fp.write(content) - upload.fp.seek(0) - self.model_file = UploadedFile('models', filename, upload) - -register_item_class(ExportToModel) - def load_extra(): import wf.aggregation_email @@ -1823,3 +1610,4 @@ def load_extra(): import wf.form import wf.register_comment import wf.anonymise + import wf.export_to_model -- 2.1.4