Transform.py 11.2 KB
Newer Older
Nicolas Delaby's avatar
Nicolas Delaby committed
1 2
# -*- coding: utf-8 -*-
from zLOG import ERROR
Yusei Tahara's avatar
Yusei Tahara committed
3 4
from UserDict import UserDict

5 6
from zope.interface import implements

Yusei Tahara's avatar
Yusei Tahara committed
7
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
8 9
from App.class_init import default__class_init__ as InitializeClass
from Persistence import PersistentMapping
10
from persistent.list import PersistentList
Yusei Tahara's avatar
Yusei Tahara committed
11 12 13 14 15 16
from OFS.SimpleItem import SimpleItem
from AccessControl import ClassSecurityInfo

from Products.CMFCore.permissions import ManagePortal
from Products.CMFCore.utils import getToolByName

17
from Products.PortalTransforms.interfaces import ITransform
Yusei Tahara's avatar
Yusei Tahara committed
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
from Products.PortalTransforms.utils import TransformException, log, _www
from Products.PortalTransforms.transforms.broken import BrokenTransform


def import_from_name(module_name):
    """ import and return a module by its name """
    __traceback_info__ = (module_name, )
    m = __import__(module_name)
    try:
        for sub in module_name.split('.')[1:]:
            m = getattr(m, sub)
    except AttributeError, e:
        raise ImportError(str(e))
    return m

def make_config_persistent(kwargs):
    """ iterates on the given dictionnary and replace list by persistent list,
    dictionary by persistent mapping.
    """
    for key, value in kwargs.items():
        if type(value) == type({}):
            p_value = PersistentMapping(value)
            kwargs[key] = p_value
        elif type(value) in (type(()), type([])):
            p_value = PersistentList(value)
            kwargs[key] = p_value

def make_config_nonpersistent(kwargs):
    """ iterates on the given dictionary and replace ListClass by python List,
        and DictClass by python Dict
    """
    for key, value in kwargs.items():
        if isinstance(value, PersistentMapping):
            p_value = dict(value)
            kwargs[key] = p_value
        elif isinstance(value, PersistentList):
            p_value = list(value)
            kwargs[key] = p_value

VALIDATORS = {
    'int' : int,
    'string' : str,
    'list' : PersistentList,
    'dict' : PersistentMapping,
    }

class Transform(SimpleItem):
    """A transform is an external method with
    additional configuration information
    """

69
    implements(ITransform)
Yusei Tahara's avatar
Yusei Tahara committed
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118

    meta_type = 'Transform'
    meta_types = all_meta_types = ()

    manage_options = (
                      ({'label':'Configure',
                       'action':'manage_main'},
                       {'label':'Reload',
                       'action':'manage_reloadTransform'},) +
                      SimpleItem.manage_options
                      )

    manage_main = PageTemplateFile('configureTransform', _www)
    manage_reloadTransform = PageTemplateFile('reloadTransform', _www)
    tr_widgets = PageTemplateFile('tr_widgets', _www)

    security = ClassSecurityInfo()
    __allow_access_to_unprotected_subobjects__ = 1

    def __init__(self, id, module, transform=None):
        self.id = id
        self.module = module
        # DM 2004-09-09: 'Transform' instances are stored as
        #  part of a module level configuration structure
        #  Therefore, they must not contain persistent objects
        self._config = UserDict()
        self._config.__allow_access_to_unprotected_subobjects__ = 1
        self._config_metadata = UserDict()
        self._tr_init(1, transform)

    def __setstate__(self, state):
        """ __setstate__ is called whenever the instance is loaded
            from the ZODB, like when Zope is restarted.

            We should reload the wrapped transform at this time
        """
        Transform.inheritedAttribute('__setstate__')(self, state)
        self._tr_init()

    def _tr_init(self, set_conf=0, transform=None):
        """ initialize the zope transform by loading the wrapped transform """
        __traceback_info__ = (self.module, )
        if transform is None:
            transform = self._load_transform()
        else:
            self._v_transform = transform
        # check this is a valid transform
        if not hasattr(transform, '__class__'):
            raise TransformException('Invalid transform : transform is not a class')
119 120
        if not ITransform.providedBy(transform):
            raise TransformException('Invalid transform : ITransform is not implemented by %s' % transform.__class__)
Yusei Tahara's avatar
Yusei Tahara committed
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
        if not hasattr(transform, 'inputs'):
            raise TransformException('Invalid transform : missing required "inputs" attribute')
        if not hasattr(transform, 'output'):
            raise TransformException('Invalid transform : missing required "output" attribute')
        # manage configuration
        if set_conf and hasattr(transform, 'config'):
            conf = dict(transform.config)
            self._config.update(conf)
            make_config_persistent(self._config)
            if hasattr(transform, 'config_metadata'):
                conf = dict(transform.config_metadata)
                self._config_metadata.update(conf)
                make_config_persistent(self._config_metadata)
        transform.config = dict(self._config)
        make_config_nonpersistent(transform.config)
        transform.config_metadata = dict(self._config_metadata)
        make_config_nonpersistent(transform.config_metadata)

        self.inputs = transform.inputs
        self.output = transform.output
        self.output_encoding = getattr(transform, 'output_encoding', None)
        return transform

    def _load_transform(self):
145 146 147 148
        try:
            return self._v_transform
        except AttributeError:
            pass
149 150 151 152 153 154 155 156
        try:
            m = import_from_name(self.module)
        except ImportError, err:
            transform = BrokenTransform(self.id, self.module, err)
            msg = "Cannot register transform %s (ImportError), using BrokenTransform: Error\n %s" % (self.id, err)
            self.title = 'BROKEN'
            log(msg, severity=ERROR)
            return transform
Yusei Tahara's avatar
Yusei Tahara committed
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
        if not hasattr(m, 'register'):
            msg = 'Invalid transform module %s: no register function defined' % self.module
            raise TransformException(msg)
        try:
            transform = m.register()
        except Exception, err:
            transform = BrokenTransform(self.id, self.module, err)
            msg = "Cannot register transform %s, using BrokenTransform: Error\n %s" % (self.id, err)
            self.title = 'BROKEN'
            log(msg, severity=ERROR)
        else:
            self.title = ''
        self._v_transform = transform
        return transform

    security.declarePrivate('manage_beforeDelete')
    def manage_beforeDelete(self, item, container):
        SimpleItem.manage_beforeDelete(self, item, container)
        if self is item:
            # unregister self from catalog on deletion
            # While building business template, transform tool will be
            # copied and this transform will be removed from copied one
            # so that this must not use getToolByName to retrive the tool.
            tr_tool = self.aq_parent
            tr_tool._unmapTransform(self)

    security.declarePublic('get_documentation')
    def get_documentation(self):
        """ return transform documentation """
186
        return self._load_transform().__doc__
Yusei Tahara's avatar
Yusei Tahara committed
187

188
    security.declarePrivate('convert')
Yusei Tahara's avatar
Yusei Tahara committed
189 190
    def convert(self, *args, **kwargs):
        """ return apply the transform and return the result """
191
        return self._load_transform().convert(*args, **kwargs)
Yusei Tahara's avatar
Yusei Tahara committed
192 193 194 195 196 197 198 199 200

    security.declarePublic('name')
    def name(self):
        """return the name of the transform instance"""
        return self.id

    security.declareProtected(ManagePortal, 'get_parameters')
    def get_parameters(self):
        """ get transform's parameters names """
201
        return sorted(self._load_transform().config.keys())
Yusei Tahara's avatar
Yusei Tahara committed
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251

    security.declareProtected(ManagePortal, 'get_parameter_value')
    def get_parameter_value(self, key):
        """ get value of a transform's parameter """
        value = self._config[key]
        type = self.get_parameter_infos(key)[0]
        if type == 'dict':
            result = {}
            for key, val in value.items():
                result[key] = val
        elif type == 'list':
            result = list(value)
        else:
            result = value
        return result

    security.declareProtected(ManagePortal, 'get_parameter_infos')
    def get_parameter_infos(self, key):
        """ get informations about a parameter

        return a tuple (type, label, description [, type specific data])
        where type in (string, int, list, dict)
              label and description are two string describing the field
        there may be some additional elements specific to the type :
             (key label, value label) for the dict type
        """
        try:
            return tuple(self._config_metadata[key])
        except KeyError:
            return 'string', '', ''

    security.declareProtected(ManagePortal, 'set_parameters')
    def set_parameters(self, REQUEST=None, **kwargs):
        """ set transform's parameters """
        if not kwargs:
            kwargs = REQUEST.form
        self.preprocess_param(kwargs)
        for param, value in kwargs.items():
            try:
                self.get_parameter_value(param)
            except KeyError:
                log('Warning: ignored parameter %r' % param)
                continue
            meta = self.get_parameter_infos(param)
            self._config[param] = VALIDATORS[meta[0]](value)

        tr_tool = getToolByName(self, 'portal_transforms')
        # need to remap transform if necessary (i.e. configurable inputs / output)
        if kwargs.has_key('inputs') or kwargs.has_key('output'):
            tr_tool._unmapTransform(self)
252 253 254
            transform = self._load_transform()
            self.inputs = kwargs.get('inputs', transform.inputs)
            self.output = kwargs.get('output', transform.output)
Yusei Tahara's avatar
Yusei Tahara committed
255 256 257 258 259 260 261 262 263 264 265 266
            tr_tool._mapTransform(self)
        # track output encoding
        if kwargs.has_key('output_encoding'):
            self.output_encoding = kwargs['output_encoding']
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(tr_tool.absolute_url()+'/manage_main')


    security.declareProtected(ManagePortal, 'reload')
    def reload(self):
        """ reload the module where the transformation class is defined """
        log('Reloading transform %s' % self.module)
267 268
        if not self.module.startswith('erp5.'):
            reload(import_from_name(self.module))
Yusei Tahara's avatar
Yusei Tahara committed
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
        self._tr_init()

    def preprocess_param(self, kwargs):
        """ preprocess param fetched from an http post to handle optional dictionary
        """
        for param in self.get_parameters():
            if self.get_parameter_infos(param)[0] == 'dict':
                try:
                    keys = kwargs[param + '_key']
                    del kwargs[param + '_key']
                except:
                    keys = ()
                try:
                    values = kwargs[param + '_value']
                    del kwargs[param + '_value']
                except:
                    values = ()
                kwargs[param] = dict = {}
                for key, value in zip(keys, values):
                    key = key.strip()
                    if key:
                        value = value.strip()
                        if value:
                            dict[key] = value

InitializeClass(Transform)