Source code for Products.GenericSetup.content

##############################################################################
#
# Copyright (c) 2005 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Filesystem exporter / importer adapters.
"""

from csv import reader
from csv import writer

import six
from six import BytesIO
from six.moves import cStringIO
from six.moves.configparser import ConfigParser

from zope.component import queryAdapter
from zope.interface import implementer

from .interfaces import IContentFactory
from .interfaces import IContentFactoryName
from .interfaces import IFilesystemExporter
from .interfaces import IFilesystemImporter
from .interfaces import IINIAware
from .interfaces import ISetupTool
from .utils import _getDottedName
from .utils import _resolveDottedName


#
#   setup_tool handlers
#


def exportSiteStructure(context):
    IFilesystemExporter(context.getSite()).export(context, 'structure', True)


def importSiteStructure(context):
    IFilesystemImporter(context.getSite()).import_(context, 'structure', True)


#
#   Filesystem export/import adapters
#
[docs]@implementer(IFilesystemExporter, IFilesystemImporter) class FolderishExporterImporter(object): """ Tree-walking exporter / importer for "folderish" types. Folderish instances are mapped to directories within the 'structure' portion of the profile, where the folder's relative path within the site corresponds to the path of its directory under 'structure'. The subobjects of a folderish instance are enumerated in the '.objects' file in the corresponding directory. This file is a CSV file, with one row per subobject, with the following wtructure:: "<subobject id>","<subobject portal_type>" Subobjects themselves are represented as individual files or subdirectories within the parent's directory. """ def __init__(self, context): self.context = context
[docs] def listExportableItems(self): """ See IFilesystemExporter. """ exportable = self.context.objectItems() exportable = [x for x in exportable if not ISetupTool.providedBy(x[1])] exportable = [x + (IFilesystemExporter(x[1], None),) for x in exportable] return exportable
[docs] def export(self, export_context, subdir, root=False): """ See IFilesystemExporter. """ context = self.context if not root: subdir = '%s/%s' % (subdir, context.getId()) exportable = self.listExportableItems() stream = cStringIO() csv_writer = writer(stream) for object_id, object, adapter in exportable: factory_namer = IContentFactoryName(object, None) if factory_namer is None: factory_name = _getDottedName(object.__class__) else: factory_name = factory_namer() csv_writer.writerow((object_id, factory_name)) c_type = 'text/comma-separated-values' export_context.writeDataFile('.objects', text=stream.getvalue(), content_type=c_type, subdir=subdir) prop_adapter = IINIAware(context, None) if prop_adapter is not None: export_context.writeDataFile('.properties', text=prop_adapter.as_ini(), content_type='text/plain', subdir=subdir) for object_id, object, adapter in exportable: if adapter is not None: adapter.export(export_context, subdir)
[docs] def import_(self, import_context, subdir, root=False): """ See IFilesystemImporter. """ context = self.context if not root: subdir = '%s/%s' % (subdir, context.getId()) prop_adapter = IINIAware(context, None) if prop_adapter is not None: prop_text = import_context.readDataFile('.properties', subdir=subdir) if prop_text is not None: prop_adapter.put_ini(prop_text) preserve = import_context.readDataFile('.preserve', subdir) must_preserve = self._mustPreserve() prior = context.objectIds() if not preserve: preserve = [] else: # Make sure ``preserve`` is a native string if six.PY3 and not isinstance(preserve, str): preserve = preserve.decode('UTF-8') preserve = _globtest(preserve, prior) preserve.extend([x[0] for x in must_preserve]) for id in prior: if id not in preserve: context._delObject(id) objects = import_context.readDataFile('.objects', subdir) if objects is None: return dialect = 'excel' if six.PY3 and not isinstance(objects, str): objects = objects.decode('UTF-8') stream = cStringIO(objects) rowiter = reader(stream, dialect) rows = tuple([i for i in rowiter if i]) existing = context.objectIds() for object_id, type_name in rows: if object_id not in existing: object = self._makeInstance(object_id, type_name, subdir, import_context) if object is None: logger = import_context.getLogger('SFWA') logger.warning("Couldn't make instance: %s/%s" % (subdir, object_id)) continue wrapped = context._getOb(object_id) adapted = queryAdapter(wrapped, IFilesystemImporter) if adapted is not None: adapted.import_(import_context, subdir)
def _makeInstance(self, instance_id, type_name, subdir, import_context): context = self.context class _OldStyleClass: pass if '.' in type_name: factory = _resolveDottedName(type_name) if getattr(factory, '__bases__', None) is not None: def _factory(instance_id, container=self.context, klass=factory): try: instance = klass(instance_id) except (TypeError, ValueError): instance = klass() instance._setId(instance_id) container._setObject(instance_id, instance) return instance factory = _factory else: factory = queryAdapter(self.context, IContentFactory, name=type_name, ) if factory is None: return None try: instance = factory(instance_id) except ValueError: # invalid type return None if context._getOb(instance_id, None) is None: context._setObject(instance_id, instance) return context._getOb(instance_id) def _mustPreserve(self): return [x for x in self.context.objectItems() if ISetupTool.providedBy(x[1])]
def _globtest(globpattern, namelist): """ Filter names in 'namelist', returning those which match 'globpattern'. """ import re pattern = globpattern.replace(".", r"\.") # mask dots pattern = pattern.replace("*", r".*") # change glob sequence pattern = pattern.replace("?", r".") # change glob char pattern = '|'.join(pattern.split()) # 'or' each line compiled = re.compile(pattern) return list(filter(compiled.match, namelist)) @implementer(IFilesystemExporter, IFilesystemImporter) class CSVAwareFileAdapter(object): """ Adapter for content whose "natural" representation is CSV. """ def __init__(self, context): self.context = context def export(self, export_context, subdir, root=False): """ See IFilesystemExporter. """ export_context.writeDataFile('%s.csv' % self.context.getId(), self.context.as_csv(), 'text/comma-separated-values', subdir) def listExportableItems(self): """ See IFilesystemExporter. """ return () def import_(self, import_context, subdir, root=False): """ See IFilesystemImporter. """ cid = self.context.getId() data = import_context.readDataFile('%s.csv' % cid, subdir) if data is None: logger = import_context.getLogger('CSAFA') logger.info('no .csv file for %s/%s' % (subdir, cid)) else: stream = BytesIO(data) self.context.put_csv(stream) @implementer(IFilesystemExporter, IFilesystemImporter) class INIAwareFileAdapter(object): """ Exporter/importer for content whose "natural" representation is an '.ini' file. """ def __init__(self, context): self.context = context def export(self, export_context, subdir, root=False): """ See IFilesystemExporter. """ export_context.writeDataFile('%s.ini' % self.context.getId(), self.context.as_ini(), 'text/plain', subdir) def listExportableItems(self): """ See IFilesystemExporter. """ return () def import_(self, import_context, subdir, root=False): """ See IFilesystemImporter. """ cid = self.context.getId() data = import_context.readDataFile('%s.ini' % cid, subdir) if data is None: logger = import_context.getLogger('SGAIFA') logger.info('no .ini file for %s/%s' % (subdir, cid)) else: self.context.put_ini(data) @implementer(IINIAware) class SimpleINIAware(object): """ Exporter/importer for content which doesn't know from INI. """ def __init__(self, context): self.context = context def getId(self): return self.context.getId() def as_ini(self): """ """ context = self.context parser = ConfigParser() stream = cStringIO() for k, v in context.propertyItems(): parser.set('DEFAULT', k, str(v)) parser.write(stream) return stream.getvalue() def put_ini(self, text): """ """ context = self.context parser = ConfigParser() # read_file/readfp expect text, not bytes if isinstance(text, six.binary_type): text = text.decode('UTF-8') try: parser.read_file(cStringIO(text)) except AttributeError: # Python 2 parser.readfp(cStringIO(text)) for option, value in parser.defaults().items(): prop_type = context.getPropertyType(option) if prop_type is None: context._setProperty(option, value, 'string') else: context._updateProperty(option, value) class FauxDAVRequest: def __init__(self, **kw): self._data = {} self._headers = {} self._data.update(kw) def __getitem__(self, key): return self._data[key] def get(self, key, default=None): return self._data.get(key, default) def get_header(self, key, default=None): return self._headers.get(key, default) class FauxDAVResponse: def setHeader(self, key, value, lock=False): pass # stub this out to mollify webdav.Resource def setStatus(self, value, reason=None): pass # stub this out to mollify webdav.Resource @implementer(IFilesystemExporter, IFilesystemImporter) class DAVAwareFileAdapter(object): """ Exporter/importer for content who handle their own FTP / DAV PUTs. """ def __init__(self, context): self.context = context def _getFileName(self): """ Return the name under which our file data is stored. """ return '%s' % self.context.getId() def export(self, export_context, subdir, root=False): """ See IFilesystemExporter. """ export_context.writeDataFile(self._getFileName(), self.context.manage_FTPget(), 'text/plain', subdir) def listExportableItems(self): """ See IFilesystemExporter. """ return () def import_(self, import_context, subdir, root=False): """ See IFilesystemImporter. """ cid = self.context.getId() data = import_context.readDataFile(self._getFileName(), subdir) if data is None: logger = import_context.getLogger('SGAIFA') logger.info('no .ini file for %s/%s' % (subdir, cid)) else: request = FauxDAVRequest(BODY=data, BODYFILE=BytesIO(data)) response = FauxDAVResponse() self.context.PUT(request, response)