import logging from xml.dom import minidom from zope.interface import classProvides, implements, providedBy from zope.component import getUtilitiesFor, queryMultiAdapter, getUtility, \ getMultiAdapter, adapts from zope.component.interfaces import IFactory from zope.app.container.interfaces import INameChooser from zope.schema._bootstrapinterfaces import ConstraintNotSatisfied from zope.schema.interfaces import ICollection from plone.portlets.interfaces import ILocalPortletAssignable, IPortletManager,\ IPortletAssignmentMapping, IPortletAssignment from plone.portlets.constants import CONTEXT_CATEGORY from plone.app.portlets.interfaces import IPortletTypeInterface from plone.app.portlets.exportimport.interfaces import IPortletAssignmentExportImportHandler from plone.app.portlets.exportimport.portlets import PropertyPortletAssignmentExportImportHandler from plone.app.portlets.interfaces import IPortletTypeInterface from collective.transmogrifier.interfaces import ISection, ISectionBlueprint from collective.transmogrifier.utils import defaultMatcher class PortletsExporterSection(object): classProvides(ISectionBlueprint) implements(ISection) def __init__(self, transmogrifier, name, options, previous): self.previous = previous self.context = transmogrifier.context self.pathkey = defaultMatcher(options, 'path-key', name, 'path') self.fileskey = options.get('files-key', '_files').strip() self.doc = minidom.Document() def __iter__(self): self.portlet_schemata = dict([(iface, name,) for name, iface in getUtilitiesFor(IPortletTypeInterface)]) self.portlet_managers = list(getUtilitiesFor(IPortletManager)) for item in self.previous: pathkey = self.pathkey(*item.keys())[0] if not pathkey: yield item; continue path = item[pathkey] obj = self.context.unrestrictedTraverse(path, None) if obj is None: # path doesn't exist yield item; continue if ILocalPortletAssignable.providedBy(obj): data = None root = self.doc.createElement('portlets') for elem in self.exportAssignments(obj): root.appendChild(elem) #for elem in self.exportBlacklists(obj) #root.appendChild(elem) if root.hasChildNodes(): self.doc.appendChild(root) data = self.doc.toprettyxml(indent=' ', encoding='utf-8') self.doc.unlink() if data: files = item.setdefault(self.fileskey, {}) item[self.fileskey]['portlets'] = { 'name': '.portlets.xml', 'data': data, } yield item def exportAssignments(self, obj): assignments = [] for manager_name, manager in self.portlet_managers: mapping = queryMultiAdapter((obj, manager), IPortletAssignmentMapping) mapping = mapping.__of__(obj) for name, assignment in mapping.items(): type_ = None for schema in providedBy(assignment).flattened(): type_ = self.portlet_schemata.get(schema, None) if type_ is not None: break if type_ is not None: child = self.doc.createElement('assignment') child.setAttribute('manager', manager_name) child.setAttribute('category', CONTEXT_CATEGORY) child.setAttribute('key', '/'.join(obj.getPhysicalPath())) child.setAttribute('type', type_) child.setAttribute('name', name) assignment = assignment.__of__(mapping) # use existing adapter for exporting a portlet assignment handler = IPortletAssignmentExportImportHandler(assignment) handler.export_assignment(schema, self.doc, child) assignments.append(child) return assignments class PortletsImporterSection(object): classProvides(ISectionBlueprint) implements(ISection) def __init__(self, transmogrifier, name, options, previous): self.previous = previous self.context = transmogrifier.context self.pathkey = defaultMatcher(options, 'path-key', name, 'path') self.fileskey = defaultMatcher(options, 'files-key', name, 'files') def __iter__(self): for item in self.previous: pathkey = self.pathkey(*item.keys())[0] fileskey = self.fileskey(*item.keys())[0] if not (pathkey and fileskey): yield item; continue if 'portlets' not in item[fileskey]: yield item; continue path = item[pathkey] obj = self.context.unrestrictedTraverse(path, None) if obj is None: # path doesn't exist yield item; continue if ILocalPortletAssignable.providedBy(obj): data = None data = item[fileskey]['portlets']['data'] doc = minidom.parseString(data) root = doc.documentElement for elem in root.childNodes: if elem.nodeName == 'assignment': self.importAssignment(obj, elem) #elif elem.nodeName == 'blacklist': #self.importBlacklist(obj, elem) yield item def importAssignment(self, obj, node): """ Import an assignment from a node """ # 1. Determine the assignment mapping and the name manager_name = node.getAttribute('manager') category = node.getAttribute('category') manager = getUtility(IPortletManager, manager_name) mapping = getMultiAdapter((obj, manager), IPortletAssignmentMapping) # 2. Either find or create the assignment assignment = None name = node.getAttribute('name') if name: assignment = mapping.get(name, None) type_ = node.getAttribute('type') if assignment is None: portlet_factory = getUtility(IFactory, name=type_) assignment = portlet_factory() if not name: chooser = INameChooser(mapping) name = chooser.chooseName(None, assignment) mapping[name] = assignment # aq-wrap it so that complex fields will work assignment = assignment.__of__(obj) # 3. Use an adapter to update the portlet settings portlet_interface = getUtility(IPortletTypeInterface, name=type_) assignment_handler = IPortletAssignmentExportImportHandler(assignment) assignment_handler.import_assignment(portlet_interface, node) logger = logging.getLogger('quintagroup.transmogrifier.portletsimporter') class PortletAssignmentExportImportHandler(PropertyPortletAssignmentExportImportHandler): """ This adapter is needed because original fails to handle text from pretty printed XML file. """ adapts(IPortletAssignment) def extract_text(self, node): text = super(PortletAssignmentExportImportHandler, self).extract_text(node) # strip text to remove newlines and space character from the beginning # and the end return text.strip() def import_node(self, interface, child): """Import a single node """ property_name = child.getAttribute('name') field = interface.get(property_name, None) if field is None: return field = field.bind(self.assignment) value = None # If we have a collection, we need to look at the value_type. # We look for value child nodes and get the # value from there if ICollection.providedBy(field): value_type = field.value_type value = [] for element in child.childNodes: if element.nodeName != 'element': continue element_value = self.extract_text(element) value.append(self.from_unicode(value_type, element_value)) value = self.field_typecast(field, value) # Otherwise, just get the value of the node else: value = self.extract_text(child) value = self.from_unicode(field, value) try: field.validate(value) except ConstraintNotSatisfied, e: logger.warning('"%s" value doesn\'t satisfy constaints for "%s:%s" field' % \ (value, self.assignment.__name__, field.__name__)) field.set(self.assignment, value)