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