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