class IPODTemplate(model.Schema): """ PODTemplate dexterity schema. """ model.primary('odt_file') form.widget('odt_file', NamedFileWidget) odt_file = NamedBlobFile(title=_(u'ODT File'), ) form.omitted('initial_md5') initial_md5 = schema.TextLine( description= u'Initially loaded file md5. Will be compared with os file md5.') form.omitted('style_modification_md5') style_modification_md5 = schema.TextLine( description=u'Working md5, stored when styles only update.') form.widget('enabled', RadioFieldWidget) enabled = schema.Bool( title=_(u'Enabled'), default=True, required=False, ) form.widget('style_template', SelectWidget) style_template = schema.List( title=_(u'Style template'), description=_( u'Choose the style template to apply for this template.'), value_type=schema.Choice( source='collective.documentgenerator.StyleTemplates'), required=True, )
def validate(self, value, force=False): # thanks to widget, we get just-loaded file.filename. widgets = self.view.widgets current_filename = u"" for element in widgets.items(): if element[0] == 'odt_file': current_filename = safe_unicode(element[1].filename) if current_filename: FORMATS_DICT = { 'ods': ODS_FORMATS + NEUTRAL_FORMATS, 'odt': ODT_FORMATS + NEUTRAL_FORMATS } extension = current_filename.split('.')[-1] authorise_element_list = FORMATS_DICT[extension] authorise_extension_list = [ elem[0] for elem in authorise_element_list ] if not value: raise Invalid(_(u"No format selected")) for element in value: if element not in authorise_extension_list: elem = self.get_invalid_error(element) error_message = _( u"element_not_valid", default= u"Element ${elem} is not valid for .${extension} template : \"${template}\"", mapping={ u"elem": elem, u"extension": extension, u"template": current_filename }) raise Invalid(error_message)
def update_dict_with_validation(original_dict, update_dict, error_message=_("Dict update collision on key")): for key in update_dict: if key in original_dict: raise Invalid(_("${error_message} for key = '${key}'", mapping={'error_message': error_message, 'key': key})) original_dict[key] = update_dict[key]
def perform_replacements(self, form_data): """ Execute replacements action """ if len(form_data["replacements"]) == 0: self.status = _("Nothing to replace.") return templates = self.get_selected_templates(form_data) self.results_table = {} with SearchAndReplacePODTemplates(templates) as replace: for row in form_data["replacements"]: row["replace_expr"] = row["replace_expr"] or "" search_expr = row["search_expr"] replace_expr = row["replace_expr"] replace_results = replace.replace(search_expr, replace_expr, is_regex=row["is_regex"]) for template_uid, template_result in replace_results.items(): template = uuidToObject(template_uid) template_path = get_site_root_relative_path(template) self.results_table[template_path] = template_result if len(self.results_table) == 0: self.status = _("Nothing found.")
class IDocumentGeneratorSearchReplacePanelSchema(interface.Interface): """ Schema for DocumentGeneratorSearchReplacePanel """ selected_templates = schema.List( title=_(u"heading_selected_templates", default=u"Selected templates"), description=_(u"description_selected_templates", default=u""), required=False, default=[], missing_value=[], value_type=schema.Choice( source="collective.documentgenerator.AllPODTemplateWithFile"), ) directives.widget("replacements", DataGridFieldFactory) replacements = schema.List( title=_(u"Replacements"), description=_("The replacements that will be made."), required=False, value_type=DictRow(schema=IReplacementRowSchema, required=True), ) @invariant def has_valid_regexes(data): if hasattr(data, 'replacements'): for i, row in enumerate(data.replacements): if row["is_regex"]: try: re.compile(row["search_expr"]) except re.error: raise Invalid( _(u"Incorrect regex at row #{0} : \"{1}\"").format( i + 1, row["search_expr"]))
class DocumentGeneratorControlPanelEditForm(RegistryEditForm): schema = IDocumentGeneratorControlPanelSchema label = _(u'Document Generator settings') description = _(u'') @button.buttonAndHandler(_('Save'), name=None) def handle_save(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return self.applyChanges(data) IStatusMessage(self.request).addStatusMessage(_(u'Changes saved'), 'info') self.context.REQUEST.RESPONSE.redirect( '@@collective.documentgenerator-controlpanel') @button.buttonAndHandler(_('Cancel'), name='cancel') def handleCancel(self, action): IStatusMessage(self.request).addStatusMessage(_(u'Edit cancelled'), 'info') self.request.response.redirect('{context_url}/{view}'.format( context_url=self.context.absolute_url(), view="@@overview-controlpanel"))
def __call__(self, context): voc_terms = [ SimpleTerm('disabled', 0, _('Disabled')), SimpleTerm('nothing', 1, _('Nothing')), SimpleTerm('optimize', 2, _('Optimize')), SimpleTerm('distribute', 3, _('Distribute'))] return SimpleVocabulary(voc_terms)
def __call__(self, context): voc_terms = [ SimpleTerm(u'auto', u'auto', _('Auto')), SimpleTerm(True, True, _('Yes')), SimpleTerm(False, False, _('No')), ] return SimpleVocabulary(voc_terms)
def renderCell(self, item): if item.has_been_modified(): icon = ('++resource++collective.documentgenerator/nok.png', translate(_('Modified'), context=self.request)) else: icon = ('++resource++collective.documentgenerator/ok.png', translate(_('Original'), context=self.request)) return u"<img title='{0}' src='{1}' />".format( safe_unicode(icon[1]).replace("'", "'"), u"{0}/{1}".format(self.table.portal_url, icon[0]))
def _update_template_styles(pod_template, style_template_filename): """ Update template pod_template by templateStyle. """ # we check if the pod_template has been modified except by style only style_changes_only = pod_template.style_modification_md5 and \ pod_template.current_md5 == pod_template.style_modification_md5 # save in temporary file, the template temp_file = create_temporary_file(pod_template.odt_file, 'pod_template.odt') new_template = open(temp_file.name, 'w') new_template.write(pod_template.odt_file.data) new_template.close() # merge style from templateStyle in template cmd = '{path} {script} {tmp_file} {extension} -p{port} -t{style_template}'.format( path=config.get_uno_path(), script=CONVSCRIPT, tmp_file=temp_file.name, extension='odt', port=config.get_oo_port(), style_template=style_template_filename ) (stdout, stderr) = executeCommand(cmd.split()) if stderr: logger.error("Error during command '%s'" % cmd) logger.error("Error is '%s'" % stderr) portal = api.portal.get() request = portal.REQUEST try: api.portal.show_message(message=_(u"Problem during styles update on template '${tmpl}': ${err}", mapping={'tmpl': safe_unicode(pod_template.absolute_url_path()), 'err': safe_unicode(stderr)}), request=request, type='error') except: pass raise Redirect(request.get('ACTUAL_URL'), _(u"Problem during styles update on template '${tmpl}': ${err}", mapping={'tmpl': safe_unicode(pod_template.absolute_url_path()), 'err': safe_unicode(stderr)})) # read the merged file resTempFileName = '.res.'.join(temp_file.name.rsplit('.', 1)) if os.path.isfile(resTempFileName): resTemplate = open(resTempFileName, 'rb') # update template result = NamedBlobFile(data=resTemplate.read(), contentType='application/vnd.oasis.opendocument.text', filename=pod_template.odt_file.filename) pod_template.odt_file = result remove_tmp_file(resTempFileName) # if only styles were modified: update the style_modification_md5 attribute if style_changes_only: pod_template.style_modification_md5 = pod_template.current_md5 remove_tmp_file(temp_file.name)
def __call__(self, context): # adapt first term value depending on global configuration value global_value = _('Global value (disabled)') if get_optimize_tables(): global_value = _('Global value (enabled)') voc_terms = [ SimpleTerm(-1, -1, global_value), SimpleTerm(0, 0, _('Force disable')), SimpleTerm(1, 1, _('Force enable'))] return SimpleVocabulary(voc_terms)
def renderCell(self, item): if not base_hasattr(item, 'enabled'): return u'-' if item.enabled: icon = ('++resource++collective.documentgenerator/ok.png', translate(_('Enabled'), context=self.request)) else: icon = ('++resource++collective.documentgenerator/nok.png', translate(_('Disabled'), context=self.request)) return u"<img title='{0}' src='{1}' />".format( safe_unicode(icon[1]).replace("'", "'"), u"{0}/{1}".format(self.table.portal_url, icon[0]))
class IContextVariablesRowSchema(Interface): """ Schema for DataGridField widget's row of field 'context_variables' """ name = schema.TextLine( title=_(u'Variable name'), required=True, ) value = schema.TextLine( title=_(u'Value'), required=False, )
class IDocumentGeneratorControlPanelSchema(Interface): """ """ oo_port = schema.Int(title=_(u'oo_port'), description=_(u'Port Number of OO'), required=False, default=2002) uno_path = schema.TextLine(title=_(u'uno path'), description=_(u'Path of python with uno'), required=False, default=u'/usr/bin/python', constraint=check_for_uno)
class IDocumentGeneratorControlPanelSchema(Interface): """ """ oo_port = schema.Int(title=_(u'oo_port'), description=_(u'Port Number of OO.'), required=False, default=int(os.getenv('OO_PORT', 2002))) uno_path = schema.TextLine(title=_(u'uno path'), description=_(u'Path of python with uno.'), required=False, default=safe_unicode( os.getenv('PYTHON_UNO', u'/usr/bin/python')), constraint=check_for_uno) optimize_tables = schema.Bool( title=_(u'Optimize tables'), description=_(u'This will apply the "Optimize table columns width" of ' u'LibreOffice to tables that do not use the ' u'"table-layout: fixed" CSS style.'), required=False, default=False) raiseOnError_for_non_managers = schema.Bool( title=_(u'Raise an error instead generating the document'), description=_( u'If enabled, this will avoid generating a document ' u'containing an error, instead a common Plone error will ' u'be raised. Nevertheless to ease debugging, Managers ' u'will continue to get errors in the generated document ' u'if it uses .odt format.'), required=False, default=False)
class IReplacementRowSchema(interface.Interface): """ Schema for DataGridField widget's row of field 'replacements' """ search_expr = schema.TextLine(title=_(u"Search"), required=True, default=u"") replace_expr = schema.TextLine(title=_(u"Replace"), required=False, default=u"") directives.widget("is_regex", SingleCheckBoxFieldWidget) is_regex = schema.Bool(title=_(u"Regex?"), )
class IMergeTemplatesRowSchema(zope.interface.Interface): """ Schema for DataGridField widget's row of field 'merge_templates' """ template = schema.Choice( title=_(u'Template'), vocabulary='collective.documentgenerator.MergeTemplates', required=True, ) pod_context_name = schema.TextLine( title=_(u'POD context name'), required=True, )
def validate_context_variables(data): keys = [] forbidden = ['context', 'view', 'uids', 'brains', 'self'] to_check = copy.deepcopy(data.context_variables or []) to_check.extend(copy.deepcopy(data.merge_templates or [])) for line in to_check: value = ('name' in line and line['name']) or ('pod_context_name' in line and line['pod_context_name']) if value in forbidden: raise Invalid(_("You can't use one of these words: ${list}", mapping={'list': ', '.join(forbidden)})) if value in keys: raise Invalid(_("You have twice used the same name '${name}'", mapping={'name': value})) else: keys.append(value)
class PathColumn(LinkColumn): """Column that displays path.""" header = _("Path") weight = 20 cssClasses = {'td': 'path-column'} linkTarget = '_blank' def getLinkURL(self, item): """Setup link url.""" return item.__parent__.absolute_url() def rel_path_title(self, rel_path): parts = rel_path.split('/') context = self.table.context for i, part in enumerate(parts): current_path = '/'.join(parts[:i + 1]) parent_path = '/'.join(parts[:i]) if part == '..': current_title = u'..' context = context.__parent__ else: context = context[part] current_title = context.title self.table.paths[current_path] = (parent_path and u'%s/%s' % (self.table.paths[parent_path], current_title) or current_title) def getLinkContent(self, item): path = os.path.dirname(item.absolute_url_path()) rel_path = os.path.relpath(path, self.table.context_path) if rel_path not in self.table.paths: self.rel_path_title(rel_path) return self.table.paths[rel_path]
class IMergeTemplatesRowSchema(Interface): """ Schema for DataGridField widget's row of field 'merge_templates' """ template = schema.Choice( title=_(u'Template'), vocabulary='collective.documentgenerator.MergeTemplates', required=True, ) pod_context_name = schema.TextLine( title=_(u'POD context name'), required=True, ) form.widget('do_rendering', RadioFieldWidget) do_rendering = schema.Bool(title=_(u'Do rendering'), )
def handleCancel(self, action): IStatusMessage(self.request).addStatusMessage(_(u'Edit cancelled'), 'info') self.request.response.redirect( '{context_url}/{view}'.format( context_url=self.context.absolute_url(), view="@@overview-controlpanel" ) )
def handleCancel(self, action): IStatusMessage(self.request).addStatusMessage(_(u'Edit cancelled'), 'info') self.request.response.redirect( '{context_url}/{view}'.format( context_url=self.context.absolute_url(), view=self.control_panel_view ) )
class IPODTemplate(model.Schema): """ PODTemplate dexterity schema. """ model.primary('odt_file') form.widget('odt_file', NamedFileWidget) odt_file = NamedBlobFile(title=_(u'ODT File'), ) form.omitted('initial_md5') initial_md5 = schema.TextLine() enabled = schema.Bool( title=_(u'Enabled'), default=True, required=False, )
class IStyleTemplate(model.Schema): """ StyleTemplate dexterity schema. """ model.primary('odt_file') form.widget('odt_file', NamedFileWidget) odt_file = NamedBlobFile(title=_(u'ODT File'), )
def handle_save(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return self.applyChanges(data) IStatusMessage(self.request).addStatusMessage(_(u'Changes saved'), 'info') self.context.REQUEST.RESPONSE.redirect('@@collective.documentgenerator-controlpanel')
def has_valid_regexes(data): if hasattr(data, 'replacements'): for i, row in enumerate(data.replacements): if row["is_regex"]: try: re.compile(row["search_expr"]) except re.error: raise Invalid( _(u"Incorrect regex at row #{0} : \"{1}\"").format( i + 1, row["search_expr"]))
class IConfigurablePODTemplate(IPODTemplate): """ ConfigurablePODTemplate dexterity schema. """ def pod_formats_constraint(value): """ By default, it seems that 'required' is not correctly validated so we double check that the field is not empty... """ if not value: raise zope.interface.Invalid( zope_message_factory(u'Required input is missing.')) return True pod_formats = schema.List( title=_(u'Available formats'), description=_( u'Select format in which the template will be generable.'), value_type=schema.Choice( source='collective.documentgenerator.Formats'), required=True, default=[ 'odt', ], constraint=pod_formats_constraint, ) form.widget('pod_portal_types', SelectWidget, multiple='multiple', size=15) pod_portal_types = schema.List( title=_(u'Allowed portal types'), description=_( u'Select for which content types the template will be available.'), value_type=schema.Choice( source='collective.documentgenerator.PortalTypes'), required=False, ) form.widget('style_template', SelectWidget) style_template = schema.List( title=_(u'Style template'), description=_( u'Choose the style template to apply for this template.'), value_type=schema.Choice( source='collective.documentgenerator.StyleTemplates'), required=True, ) form.widget('merge_templates', DataGridFieldFactory) merge_templates = schema.List( title=_(u'Templates to merge.'), required=False, value_type=DictRow(schema=IMergeTemplatesRowSchema, required=False), default=[], )
def validate(self, selected_formats): # thanks to widget, we get just-loaded file.filename. widgets = self.view.widgets current_filename = "" for element in widgets.items(): if element[0] == 'odt_file': current_filename = element[1].filename if current_filename: FORMATS_DICT = {'ods': ODS_FORMATS + NEUTRAL_FORMATS, 'odt': ODT_FORMATS + NEUTRAL_FORMATS} extension = current_filename.split('.')[-1] authorise_element_list = FORMATS_DICT[extension] authorise_extension_list = [elem[0] for elem in authorise_element_list] if not selected_formats: raise Invalid(_(u"No format selected")) for element in selected_formats: if element not in authorise_extension_list: elem = self.get_invalid_error(element) error_message = _(u"element_not_valid", default=u"Element ${elem} is not valid for .${extension} template : \"${template}\"", mapping={u"elem": elem, u"extension": extension, u"template": current_filename}) raise Invalid(error_message)
class IStyleTemplate(model.Schema): """ StyleTemplate dexterity schema. """ model.primary('odt_file') form.widget('odt_file', NamedFileWidget) odt_file = NamedBlobFile(title=_(u'ODT File'), ) form.omitted('initial_md5') initial_md5 = schema.TextLine( description= u'Initially loaded file md5. Will be compared with os file md5.')
def perform_preview(self, form_data): """ Execute preview action """ if len(form_data["replacements"]) == 0: self.status = _("Nothing to preview.") return templates = self.get_selected_templates(form_data) self.results_table = {} with SearchAndReplacePODTemplates(templates) as search_replace: for row in form_data["replacements"]: search_expr = row["search_expr"] search_results = search_replace.search( search_expr, is_regex=row["is_regex"]) for template_uid, template_result in search_results.items(): template = uuidToObject(template_uid) template_path = get_site_root_relative_path(template) self.results_table[template_path] = template_result self.is_previewing = True if len(self.results_table) == 0: self.status = _("Nothing found.")
class OriginalColumn(Column): """Column that displays original status.""" header = _("Status") weight = 40 cssClasses = {'td': 'original-column'} def __init__(self, context, request, table): super(OriginalColumn, self).__init__(context, request, table) voc_name = 'collective.documentgenerator.ExistingPODTemplate' vocabulary = getUtility(IVocabularyFactory, voc_name) self.templates_voc = vocabulary(context) def renderCell(self, item): img = suffix = msg = info = u'' real_template = item if base_hasattr(item, 'pod_template_to_use' ) and item.pod_template_to_use is not None: real_template = item.get_pod_template_to_use() suffix = u'_use' if item.pod_template_to_use in self.templates_voc: info = translate(u', from ${template}', context=self.request, domain='collective.documentgenerator', mapping={ 'template': self.templates_voc.getTerm( item.pod_template_to_use).title }) elif base_hasattr(item, 'is_reusable') and item.is_reusable: suffix, info = u'_used', translate( u', is reusable template', context=self.request, domain='collective.documentgenerator') if real_template is None: img, msg = u'missing', u'Linked template deleted !' elif real_template.has_been_modified(): img, msg = u'nok', u'Modified' else: img, msg = u'ok', u'Original' icon = ('++resource++collective.documentgenerator/{}{}.svg'.format( img, suffix), u'{}{}'.format( translate(msg, context=self.request, domain='collective.documentgenerator'), info)) return u"<img class='svg-icon' title='{0}' src='{1}' />".format( safe_unicode(icon[1]).replace("'", "'"), u"{0}/{1}".format(self.table.portal_url, icon[0]))
class ActionsColumn(Column): """ A column displaying available actions of the listed item. Need imio.actionspanel to be used ! """ header = _("Actions") weight = 70 params = {'useIcons': True, 'showHistory': False, 'showActions': True, 'showOwnDelete': False, 'showArrows': True, 'showTransitions': False, 'showExtEdit': True, 'edit_action_class': 'dg_edit_action', 'edit_action_target': '_blank'} cssClasses = {'td': 'actions-column'} def renderCell(self, item): view = getMultiAdapter((item, self.request), name='actions_panel') return view(**self.params)
class FormatsColumn(Column): """Column that displays pod formats.""" header = _("Pod formats") weight = 50 cssClasses = {'td': 'formats-column'} def renderCell(self, item): if not base_hasattr(item, 'pod_formats'): return '' ret = [] for fmt in item.pod_formats or []: ret.append(u"<img title='{0}' src='{1}' />".format( fmt, '%s/++resource++collective.documentgenerator/%s.png' % (self.table.portal_url, fmt))) return '\n'.join(ret)
def __call__(self, context): # adapt first term value depending on global configuration value # get term from global value vocabulary vocabulary = queryUtility( IVocabularyFactory, 'collective.documentgenerator.ConfigColumnModifier') voc = vocabulary(context) global_value = _(voc.getTerm(get_column_modifier()).title) global_value_term = _('Global value (${global_value})', mapping={'global_value': global_value}) voc_terms = [ SimpleTerm(-1, -1, global_value_term), SimpleTerm('disabled', 0, _('Force disabled')), SimpleTerm('nothing', 1, _('Force nothing')), SimpleTerm('optimize', 2, _('Force optimize')), SimpleTerm('distribute', 3, _('Force distribute'))] return SimpleVocabulary(voc_terms)
from collective.documentgenerator.interfaces import IDocumentGeneratorSettings from plone.app.registry.browser.controlpanel import ControlPanelFormWrapper from plone.app.registry.browser.controlpanel import RegistryEditForm from Products.CMFPlone.utils import safe_unicode from Products.statusmessages.interfaces import IStatusMessage from z3c.form import button from zope import schema from zope.interface import implements from zope.interface import Interface import inspect import os COLUMN_MODIFIER_DESCR = _( u'If enabled, this will allow the "table-layout: fixed|auto|none" ' u'CSS style handling while generating document. If no such style is defined on the table, ' u'the chosen column modifier of LibreOffice will be applied.') def check_for_uno(value): """ """ try: inspect.isabstract(IDocumentGeneratorControlPanelSchema) except Exception: return True if 'python' not in value and os.system(value + ' -V') != 0: raise interfaces.InvalidPythonPath() if os.system(value + ' -c "import unohelper"') != 0: raise interfaces.InvalidUnoPath()