source: products/quintagroup.transmogrifier/trunk/quintagroup/transmogrifier/portlets.py @ 1584

Last change on this file since 1584 was 1584, checked in by mylan, 14 years ago

Fix breakage portlets importing/exporting for objects, which are not adapterd to IPortletAssignmentMapping

File size: 8.8 KB
Line 
1import logging
2from xml.dom import minidom
3
4from zope.interface import classProvides, implements, providedBy
5from zope.component import getUtilitiesFor, queryMultiAdapter, getUtility, \
6    getMultiAdapter, adapts
7from zope.component.interfaces import IFactory
8from zope.app.container.interfaces import INameChooser
9from zope.schema._bootstrapinterfaces import ConstraintNotSatisfied
10from zope.schema.interfaces import ICollection
11
12
13from plone.portlets.interfaces import ILocalPortletAssignable, IPortletManager,\
14    IPortletAssignmentMapping, IPortletAssignment
15from plone.portlets.constants import CONTEXT_CATEGORY
16from plone.app.portlets.interfaces import IPortletTypeInterface
17from plone.app.portlets.exportimport.interfaces import IPortletAssignmentExportImportHandler
18from plone.app.portlets.exportimport.portlets import PropertyPortletAssignmentExportImportHandler
19from plone.app.portlets.interfaces import IPortletTypeInterface
20
21from collective.transmogrifier.interfaces import ISection, ISectionBlueprint
22from collective.transmogrifier.utils import defaultMatcher
23
24class PortletsExporterSection(object):
25    classProvides(ISectionBlueprint)
26    implements(ISection)
27
28    def __init__(self, transmogrifier, name, options, previous):
29        self.previous = previous
30        self.context = transmogrifier.context
31
32        self.pathkey = defaultMatcher(options, 'path-key', name, 'path')
33        self.fileskey = options.get('files-key', '_files').strip()
34
35        self.doc = minidom.Document()
36
37    def __iter__(self):
38        self.portlet_schemata = dict([(iface, name,) for name, iface in 
39            getUtilitiesFor(IPortletTypeInterface)])
40        self.portlet_managers = list(getUtilitiesFor(IPortletManager))
41
42        for item in self.previous:
43            pathkey = self.pathkey(*item.keys())[0]
44
45            if not pathkey:
46                yield item; continue
47
48            path = item[pathkey]
49            obj = self.context.unrestrictedTraverse(path, None)
50            if obj is None:         # path doesn't exist
51                yield item; continue
52
53            if ILocalPortletAssignable.providedBy(obj):
54                data = None
55
56                root = self.doc.createElement('portlets')
57
58                for elem in self.exportAssignments(obj):
59                    root.appendChild(elem)
60                #for elem in self.exportBlacklists(obj)
61                    #root.appendChild(elem)
62                if root.hasChildNodes():
63                    self.doc.appendChild(root)
64                    data = self.doc.toprettyxml(indent='  ', encoding='utf-8')
65                    self.doc.unlink()
66
67                if data:
68                    files = item.setdefault(self.fileskey, {})
69                    item[self.fileskey]['portlets'] = {
70                        'name': '.portlets.xml',
71                        'data': data,
72                    }
73            yield item
74
75    def exportAssignments(self, obj):
76        assignments = []
77        for manager_name, manager in self.portlet_managers:
78            mapping = queryMultiAdapter((obj, manager), IPortletAssignmentMapping)
79            if mapping is None:
80                continue
81
82            mapping = mapping.__of__(obj)
83
84            for name, assignment in mapping.items():
85                type_ = None
86                for schema in providedBy(assignment).flattened():
87                    type_ = self.portlet_schemata.get(schema, None)
88                    if type_ is not None:
89                        break
90
91                if type_ is not None:
92                    child = self.doc.createElement('assignment')
93                    child.setAttribute('manager', manager_name)
94                    child.setAttribute('category', CONTEXT_CATEGORY)
95                    child.setAttribute('key', '/'.join(obj.getPhysicalPath()))
96                    child.setAttribute('type', type_)
97                    child.setAttribute('name', name)
98
99                    assignment = assignment.__of__(mapping)
100                    # use existing adapter for exporting a portlet assignment
101                    handler = IPortletAssignmentExportImportHandler(assignment)
102                    handler.export_assignment(schema, self.doc, child)
103
104                    assignments.append(child)
105
106        return assignments
107
108class PortletsImporterSection(object):
109    classProvides(ISectionBlueprint)
110    implements(ISection)
111
112    def __init__(self, transmogrifier, name, options, previous):
113        self.previous = previous
114        self.context = transmogrifier.context
115
116        self.pathkey = defaultMatcher(options, 'path-key', name, 'path')
117        self.fileskey = defaultMatcher(options, 'files-key', name, 'files')
118
119    def __iter__(self):
120
121        for item in self.previous:
122            pathkey = self.pathkey(*item.keys())[0]
123            fileskey = self.fileskey(*item.keys())[0]
124
125            if not (pathkey and fileskey):
126                yield item; continue
127            if 'portlets' not in item[fileskey]:
128                yield item; continue
129
130            path = item[pathkey]
131            obj = self.context.unrestrictedTraverse(path, None)
132            if obj is None:         # path doesn't exist
133                yield item; continue
134
135            if ILocalPortletAssignable.providedBy(obj):
136                data = None
137                data = item[fileskey]['portlets']['data']
138                doc = minidom.parseString(data)
139                root = doc.documentElement
140                for elem in root.childNodes:
141                    if elem.nodeName == 'assignment':
142                        self.importAssignment(obj, elem)
143                    #elif elem.nodeName == 'blacklist':
144                        #self.importBlacklist(obj, elem)
145
146            yield item
147
148    def importAssignment(self, obj, node):
149        """ Import an assignment from a node
150        """
151        # 1. Determine the assignment mapping and the name
152        manager_name = node.getAttribute('manager')
153        category = node.getAttribute('category')
154
155        manager = getUtility(IPortletManager, manager_name)
156        mapping = getMultiAdapter((obj, manager), IPortletAssignmentMapping)
157        if mapping is None:
158            return
159
160        # 2. Either find or create the assignment
161        assignment = None
162        name = node.getAttribute('name')
163        if name:
164            assignment = mapping.get(name, None)
165
166        type_ = node.getAttribute('type')
167
168        if assignment is None:
169            portlet_factory = getUtility(IFactory, name=type_)
170            assignment = portlet_factory()
171
172            if not name:
173                chooser = INameChooser(mapping)
174                name = chooser.chooseName(None, assignment)
175
176            mapping[name] = assignment
177
178        # aq-wrap it so that complex fields will work
179        assignment = assignment.__of__(obj)
180
181        # 3. Use an adapter to update the portlet settings
182        portlet_interface = getUtility(IPortletTypeInterface, name=type_)
183        assignment_handler = IPortletAssignmentExportImportHandler(assignment)
184        assignment_handler.import_assignment(portlet_interface, node)
185
186logger = logging.getLogger('quintagroup.transmogrifier.portletsimporter')
187
188class PortletAssignmentExportImportHandler(PropertyPortletAssignmentExportImportHandler):
189    """ This adapter is needed because original fails to handle text from
190        pretty printed XML file.
191    """
192    adapts(IPortletAssignment)
193
194    def extract_text(self, node):
195        text = super(PortletAssignmentExportImportHandler, self).extract_text(node)
196        # strip text to remove newlines and space character from the beginning
197        # and the end
198        return text.strip()
199
200    def import_node(self, interface, child):
201        """Import a single <property /> node
202        """
203        property_name = child.getAttribute('name')
204
205        field = interface.get(property_name, None)
206        if field is None:
207            return
208
209        field = field.bind(self.assignment)
210        value = None
211
212        # If we have a collection, we need to look at the value_type.
213        # We look for <element>value</element> child nodes and get the
214        # value from there
215        if ICollection.providedBy(field):
216            value_type = field.value_type
217            value = []
218            for element in child.childNodes:
219                if element.nodeName != 'element':
220                    continue
221                element_value = self.extract_text(element)
222                value.append(self.from_unicode(value_type, element_value))
223            value = self.field_typecast(field, value)
224
225        # Otherwise, just get the value of the <property /> node
226        else:
227            value = self.extract_text(child)
228            value = self.from_unicode(field, value)
229
230        try:
231            field.validate(value)
232        except ConstraintNotSatisfied, e:
233            logger.warning('"%s" value doesn\'t satisfy constaints for "%s:%s" field' % \
234                (value, self.assignment.__name__, field.__name__))
235
236        field.set(self.assignment, value)
Note: See TracBrowser for help on using the repository browser.