class ImportStepRegistry( Implicit ): """ Manage knowledge about steps to create / configure site. o Steps are composed together to define a site profile. """ implements(IImportStepRegistry) security = ClassSecurityInfo() def __init__( self ): self.clear() security.declareProtected( ManagePortal, 'listSteps' ) def listSteps( self ): """ Return a sequence of IDs of registered steps. o Order is not significant. """ return self._registered.keys() security.declareProtected( ManagePortal, 'sortSteps' ) def sortSteps( self ): """ Return a sequence of registered step IDs o Sequence is sorted topologically by dependency, with the dependent steps *after* the steps they depend on. """ return self._computeTopologicalSort() security.declareProtected( ManagePortal, 'checkComplete' ) def checkComplete( self ): """ Return a sequence of ( node, edge ) tuples for unsatisifed deps. """ result = [] seen = {} graph = self._computeTopologicalSort() for node in graph: dependencies = self.getStepMetadata( node )[ 'dependencies' ] for dependency in dependencies: if seen.get( dependency ) is None: result.append( ( node, dependency ) ) seen[ node ] = 1 return result security.declareProtected( ManagePortal, 'getStepMetadata' ) def getStepMetadata( self, key, default=None ): """ Return a mapping of metadata for the step identified by 'key'. o Return 'default' if no such step is registered. o The 'handler' metadata is available via 'getStep'. """ result = {} info = self._registered.get( key ) if info is None: return default return info.copy() security.declareProtected( ManagePortal, 'listStepMetadata' ) def listStepMetadata( self ): """ Return a sequence of mappings describing registered steps. o Mappings will be ordered alphabetically. """ step_ids = self.listSteps() step_ids.sort() return [ self.getStepMetadata( x ) for x in step_ids ] security.declareProtected( ManagePortal, 'generateXML' ) def generateXML( self ): """ Return a round-trippable XML representation of the registry. o 'handler' values are serialized using their dotted names. """ return self._exportTemplate() security.declarePrivate( 'getStep' ) def getStep( self, key, default=None ): """ Return the IImportPlugin registered for 'key'. o Return 'default' if no such step is registered. """ marker = object() info = self._registered.get( key, marker ) if info is marker: return default return _resolveDottedName( info[ 'handler' ] ) security.declarePrivate( 'registerStep' ) def registerStep( self , id , version , handler , dependencies=() , title=None , description=None ): """ Register a setup step. o 'id' is a unique name for this step, o 'version' is a string for comparing versions, it is preferred to be a yyyy/mm/dd-ii formatted string (date plus two-digit ordinal). when comparing two version strings, the version with the lower sort order is considered the older version. - Newer versions of a step supplant older ones. - Attempting to register an older one after a newer one results in a KeyError. o 'handler' should implement IImportPlugin. o 'dependencies' is a tuple of step ids which have to run before this step in order to be able to run at all. Registration of steps that have unmet dependencies are deferred until the dependencies have been registered. o 'title' is a one-line UI description for this step. If None, the first line of the documentation string of the handler is used, or the id if no docstring can be found. o 'description' is a one-line UI description for this step. If None, the remaining line of the documentation string of the handler is used, or default to ''. """ already = self.getStepMetadata( id ) if already and already[ 'version' ] > version: raise KeyError( 'Existing registration for step %s, version %s' % ( id, already[ 'version' ] ) ) if title is None or description is None: t, d = _extractDocstring( handler, id, '' ) title = title or t description = description or d info = { 'id' : id , 'version' : version , 'handler' : _getDottedName( handler ) , 'dependencies' : dependencies , 'title' : title , 'description' : description } self._registered[ id ] = info security.declarePrivate( 'parseXML' ) def parseXML( self, text, encoding=None ): """ Parse 'text'. """ reader = getattr( text, 'read', None ) if reader is not None: text = reader() parser = _ImportStepRegistryParser( encoding ) parseString( text, parser ) return parser._parsed security.declarePrivate( 'clear' ) def clear( self ): self._registered = {} # # Helper methods # security.declarePrivate( '_computeTopologicalSort' ) def _computeTopologicalSort( self ): result = [] graph = [ ( x[ 'id' ], x[ 'dependencies' ] ) for x in self._registered.values() ] for node, edges in graph: after = -1 for edge in edges: if edge in result: after = max( after, result.index( edge ) ) result.insert( after + 1, node ) return result security.declarePrivate( '_exportTemplate' ) _exportTemplate = PageTemplateResource( 'xml/isrExport.xml', globals() )
class ToolsetRegistry( Implicit ): """ Track required / forbidden tools. """ implements(IToolsetRegistry) security = ClassSecurityInfo() security.setDefaultAccess( 'allow' ) def __init__( self ): self.clear() # # Toolset API # security.declareProtected( ManagePortal, 'listForbiddenTools' ) def listForbiddenTools( self ): """ See IToolsetRegistry. """ result = list( self._forbidden ) result.sort() return result security.declareProtected( ManagePortal, 'addForbiddenTool' ) def addForbiddenTool( self, tool_id ): """ See IToolsetRegistry. """ if tool_id in self._forbidden: return if self._required.get( tool_id ) is not None: raise ValueError, 'Tool %s is required!' % tool_id self._forbidden.append( tool_id ) security.declareProtected( ManagePortal, 'listRequiredTools' ) def listRequiredTools( self ): """ See IToolsetRegistry. """ result = list( self._required.keys() ) result.sort() return result security.declareProtected( ManagePortal, 'getRequiredToolInfo' ) def getRequiredToolInfo( self, tool_id ): """ See IToolsetRegistry. """ return self._required[ tool_id ] security.declareProtected( ManagePortal, 'listRequiredToolInfo' ) def listRequiredToolInfo( self ): """ See IToolsetRegistry. """ return [ self.getRequiredToolInfo( x ) for x in self.listRequiredTools() ] security.declareProtected( ManagePortal, 'addRequiredTool' ) def addRequiredTool( self, tool_id, dotted_name ): """ See IToolsetRegistry. """ if tool_id in self._forbidden: raise ValueError, "Forbidden tool ID: %s" % tool_id self._required[ tool_id ] = { 'id' : tool_id , 'class' : dotted_name } security.declareProtected( ManagePortal, 'generateXML' ) def generateXML( self ): """ Pseudo API. """ return self._toolsetConfig() security.declareProtected( ManagePortal, 'parseXML' ) def parseXML( self, text, encoding=None ): """ Pseudo-API """ reader = getattr( text, 'read', None ) if reader is not None: text = reader() parser = _ToolsetParser( encoding ) parseString( text, parser ) for tool_id in parser._forbidden: self.addForbiddenTool( tool_id ) for tool_id, dotted_name in parser._required.items(): self.addRequiredTool( tool_id, dotted_name ) security.declarePrivate( 'clear' ) def clear( self ): self._forbidden = [] self._required = {} # # Helper methods. # security.declarePrivate( '_toolsetConfig' ) _toolsetConfig = PageTemplateResource( 'xml/tscExport.xml' , globals() , __name__='toolsetConfig' )
def _getExportTemplate(self): return PageTemplateResource('xml/rmeExport.xml', globals())
class ExportStepRegistry( Implicit ): """ Registry of known site-configuration export steps. o Each step is registered with a unique id. o When called, with the portal object passed in as an argument, the step must return a sequence of three-tuples, ( 'data', 'content_type', 'filename' ), one for each file exported by the step. - 'data' is a string containing the file data; - 'content_type' is the MIME type of the data; - 'filename' is a suggested filename for use when downloading. """ implements(IExportStepRegistry) security = ClassSecurityInfo() def __init__( self ): self.clear() security.declareProtected( ManagePortal, 'listSteps' ) def listSteps( self ): """ Return a list of registered step IDs. """ return self._registered.keys() security.declareProtected( ManagePortal, 'getStepMetadata' ) def getStepMetadata( self, key, default=None ): """ Return a mapping of metadata for the step identified by 'key'. o Return 'default' if no such step is registered. o The 'handler' metadata is available via 'getStep'. """ info = self._registered.get( key ) if info is None: return default return info.copy() security.declareProtected( ManagePortal, 'listStepMetadata' ) def listStepMetadata( self ): """ Return a sequence of mappings describing registered steps. o Steps will be alphabetical by ID. """ step_ids = self.listSteps() step_ids.sort() return [ self.getStepMetadata( x ) for x in step_ids ] security.declareProtected( ManagePortal, 'generateXML' ) def generateXML( self ): """ Return a round-trippable XML representation of the registry. o 'handler' values are serialized using their dotted names. """ return self._exportTemplate() security.declarePrivate( 'getStep' ) def getStep( self, key, default=None ): """ Return the IExportPlugin registered for 'key'. o Return 'default' if no such step is registered. """ marker = object() info = self._registered.get( key, marker ) if info is marker: return default return _resolveDottedName( info[ 'handler' ] ) security.declarePrivate( 'registerStep' ) def registerStep( self, id, handler, title=None, description=None ): """ Register an export step. o 'id' is the unique identifier for this step o 'step' should implement IExportPlugin. o 'title' is a one-line UI description for this step. If None, the first line of the documentation string of the step is used, or the id if no docstring can be found. o 'description' is a one-line UI description for this step. If None, the remaining line of the documentation string of the step is used, or default to ''. """ if title is None or description is None: t, d = _extractDocstring( handler, id, '' ) title = title or t description = description or d info = { 'id' : id , 'handler' : _getDottedName( handler ) , 'title' : title , 'description' : description } self._registered[ id ] = info security.declarePrivate( 'parseXML' ) def parseXML( self, text, encoding=None ): """ Parse 'text'. """ reader = getattr( text, 'read', None ) if reader is not None: text = reader() parser = _ExportStepRegistryParser( encoding ) parseString( text, parser ) return parser._parsed security.declarePrivate( 'clear' ) def clear( self ): self._registered = {} # # Helper methods # security.declarePrivate( '_exportTemplate' ) _exportTemplate = PageTemplateResource( 'xml/esrExport.xml', globals() )
class SetupTool(Folder): """ Profile-based site configuration manager. """ implements(ISetupTool) meta_type = 'Generic Setup Tool' _import_context_id = '' security = ClassSecurityInfo() def __init__(self, id): self.id = str(id) self._import_registry = ImportStepRegistry() self._export_registry = ExportStepRegistry() self._export_registry.registerStep( 'step_registries', exportStepRegistries, 'Export import / export steps.', ) self._toolset_registry = ToolsetRegistry() # # ISetupTool API # security.declareProtected(ManagePortal, 'getEncoding') def getEncoding(self): """ See ISetupTool. """ return 'ascii' security.declareProtected(ManagePortal, 'getImportContextId') def getImportContextID(self): """ See ISetupTool. """ return self._import_context_id security.declareProtected(ManagePortal, 'setImportContext') def setImportContext(self, context_id, encoding=None): """ See ISetupTool. """ self._import_context_id = context_id self._updateImportStepsRegistry(encoding) self._updateExportStepsRegistry(encoding) self._updateToolsetRegistry(encoding) security.declareProtected(ManagePortal, 'getImportStepRegistry') def getImportStepRegistry(self): """ See ISetupTool. """ return self._import_registry security.declareProtected(ManagePortal, 'getImportStepRegistry') def getExportStepRegistry(self): """ See ISetupTool. """ return self._export_registry security.declareProtected(ManagePortal, 'getToolsetRegistry') def getToolsetRegistry(self): """ See ISetupTool. """ return self._toolset_registry security.declareProtected(ManagePortal, 'executeStep') def runImportStep(self, step_id, run_dependencies=True, purge_old=None): """ See ISetupTool. """ context = self._getImportContext(self._import_context_id, purge_old) info = self._import_registry.getStepMetadata(step_id) if info is None: raise ValueError, 'No such import step: %s' % step_id dependencies = info.get('dependencies', ()) messages = {} steps = [] if run_dependencies: for dependency in dependencies: if dependency not in steps: message = self._doRunImportStep(dependency, context) messages[dependency] = message steps.append(dependency) message = self._doRunImportStep(step_id, context) messages[step_id] = message steps.append(step_id) return {'steps': steps, 'messages': messages} security.declareProtected(ManagePortal, 'runAllSetupSteps') def runAllImportSteps(self, purge_old=None): """ See ISetupTool. """ context = self._getImportContext(self._import_context_id, purge_old) steps = self._import_registry.sortSteps() messages = {} for step in steps: message = self._doRunImportStep(step, context) messages[step] = message return {'steps': steps, 'messages': messages} security.declareProtected(ManagePortal, 'runExportStep') def runExportStep(self, step_id): """ See ISetupTool. """ return self._doRunExportSteps([step_id]) security.declareProtected(ManagePortal, 'runAllExportSteps') def runAllExportSteps(self): """ See ISetupTool. """ return self._doRunExportSteps(self._export_registry.listSteps()) security.declareProtected(ManagePortal, 'createSnapshot') def createSnapshot(self, snapshot_id): """ See ISetupTool. """ context = SnapshotExportContext(self, snapshot_id) messages = {} steps = self._export_registry.listSteps() for step_id in steps: handler = self._export_registry.getStep(step_id) if handler is None: raise ValueError('Invalid export step: %s' % step_id) messages[step_id] = handler(context) return { 'steps': steps, 'messages': messages, 'url': context.getSnapshotURL(), 'snapshot': context.getSnapshotFolder() } security.declareProtected(ManagePortal, 'compareConfigurations') def compareConfigurations( self, lhs_context, rhs_context, missing_as_empty=False, ignore_blanks=False, skip=('CVS', '.svn'), ): """ See ISetupTool. """ differ = ConfigDiff( lhs_context, rhs_context, missing_as_empty, ignore_blanks, skip, ) return differ.compare() security.declareProtected(ManagePortal, 'markupComparison') def markupComparison(self, lines): """ See ISetupTool. """ result = [] for line in lines.splitlines(): if line.startswith('** '): if line.find('File') > -1: if line.find('replaced') > -1: result.append(('file-to-dir', line)) elif line.find('added') > -1: result.append(('file-added', line)) else: result.append(('file-removed', line)) else: if line.find('replaced') > -1: result.append(('dir-to-file', line)) elif line.find('added') > -1: result.append(('dir-added', line)) else: result.append(('dir-removed', line)) elif line.startswith('@@'): result.append(('diff-range', line)) elif line.startswith(' '): result.append(('diff-context', line)) elif line.startswith('+'): result.append(('diff-added', line)) elif line.startswith('-'): result.append(('diff-removed', line)) elif line == '\ No newline at end of file': result.append(('diff-context', line)) else: result.append(('diff-header', line)) return '<pre>\n%s\n</pre>' % ('\n'.join( [('<span class="%s">%s</span>' % (cl, escape(l))) for cl, l in result])) # # ZMI # manage_options = ( Folder.manage_options[:1] + ( { 'label': 'Properties', 'action': 'manage_tool' }, { 'label': 'Import', 'action': 'manage_importSteps' }, { 'label': 'Export', 'action': 'manage_exportSteps' }, { 'label': 'Snapshots', 'action': 'manage_snapshots' }, { 'label': 'Comparison', 'action': 'manage_showDiff' }, ) + Folder.manage_options[3:] # skip "View", "Properties" ) security.declareProtected(ManagePortal, 'manage_tool') manage_tool = PageTemplateResource('www/sutProperties.zpt', globals()) security.declareProtected(ManagePortal, 'manage_updateToolProperties') def manage_updateToolProperties(self, context_id, RESPONSE): """ Update the tool's settings. """ self.setImportContext(context_id) RESPONSE.redirect('%s/manage_tool?manage_tabs_message=%s' % (self.absolute_url(), 'Properties+updated.')) security.declareProtected(ManagePortal, 'manage_importSteps') manage_importSteps = PageTemplateResource('www/sutImportSteps.zpt', globals()) security.declareProtected(ManagePortal, 'manage_importSelectedSteps') def manage_importSelectedSteps( self, ids, run_dependencies, RESPONSE, ): """ Import the steps selected by the user. """ if not ids: message = 'No+steps+selected.' else: steps_run = [] for step_id in ids: result = self.runImportStep(step_id, run_dependencies) steps_run.extend(result['steps']) message = 'Steps+run:%s' % '+,'.join(steps_run) RESPONSE.redirect('%s/manage_importSteps?manage_tabs_message=%s' % (self.absolute_url(), message)) security.declareProtected(ManagePortal, 'manage_importSelectedSteps') def manage_importAllSteps(self, RESPONSE): """ Import all steps. """ result = self.runAllImportSteps() message = 'Steps+run:%s' % '+,'.join(result['steps']) RESPONSE.redirect('%s/manage_importSteps?manage_tabs_message=%s' % (self.absolute_url(), message)) security.declareProtected(ManagePortal, 'manage_exportSteps') manage_exportSteps = PageTemplateResource('www/sutExportSteps.zpt', globals()) security.declareProtected(ManagePortal, 'manage_exportSelectedSteps') def manage_exportSelectedSteps(self, ids, RESPONSE): """ Export the steps selected by the user. """ if not ids: RESPONSE.redirect('%s/manage_exportSteps?manage_tabs_message=%s' % (self.absolute_url(), 'No+steps+selected.')) result = self._doRunExportSteps(ids) RESPONSE.setHeader('Content-type', 'application/x-gzip') RESPONSE.setHeader('Content-disposition', 'attachment; filename=%s' % result['filename']) return result['tarball'] security.declareProtected(ManagePortal, 'manage_exportAllSteps') def manage_exportAllSteps(self, RESPONSE): """ Export all steps. """ result = self.runAllExportSteps() RESPONSE.setHeader('Content-type', 'application/x-gzip') RESPONSE.setHeader('Content-disposition', 'attachment; filename=%s' % result['filename']) return result['tarball'] security.declareProtected(ManagePortal, 'manage_snapshots') manage_snapshots = PageTemplateResource('www/sutSnapshots.zpt', globals()) security.declareProtected(ManagePortal, 'listSnapshotInfo') def listSnapshotInfo(self): """ Return a list of mappings describing available snapshots. o Keys include: 'id' -- snapshot ID 'title' -- snapshot title or ID 'url' -- URL of the snapshot folder """ result = [] snapshots = self._getOb('snapshots', None) if snapshots: for id, folder in snapshots.objectItems('Folder'): result.append({ 'id': id, 'title': folder.title_or_id(), 'url': folder.absolute_url() }) return result security.declareProtected(ManagePortal, 'listProfileInfo') def listProfileInfo(self): """ Return a list of mappings describing registered profiles. o Keys include: 'id' -- profile ID 'title' -- profile title or ID 'description' -- description of the profile 'path' -- path to the profile within its product 'product' -- name of the registering product """ return _profile_registry.listProfileInfo() security.declareProtected(ManagePortal, 'listContextInfos') def listContextInfos(self): """ List registered profiles and snapshots. """ s_infos = [{ 'id': 'snapshot-%s' % info['id'], 'title': info['title'] } for info in self.listSnapshotInfo()] p_infos = [{ 'id': 'profile-%s' % info['id'], 'title': info['title'] } for info in self.listProfileInfo()] return tuple(s_infos + p_infos) security.declareProtected(ManagePortal, 'manage_createSnapshot') def manage_createSnapshot(self, RESPONSE, snapshot_id=None): """ Create a snapshot with the given ID. o If no ID is passed, generate one. """ if snapshot_id is None: timestamp = time.gmtime() snapshot_id = 'snapshot-%4d%02d%02d%02d%02d%02d' % timestamp[:6] self.createSnapshot(snapshot_id) RESPONSE.redirect('%s/manage_snapshots?manage_tabs_message=%s' % (self.absolute_url(), 'Snapshot+created.')) security.declareProtected(ManagePortal, 'manage_showDiff') manage_showDiff = PageTemplateResource('www/sutCompare.zpt', globals()) def manage_downloadDiff( self, lhs, rhs, missing_as_empty, ignore_blanks, RESPONSE, ): """ Crack request vars and call compareConfigurations. o Return the result as a 'text/plain' stream, suitable for framing. """ comparison = self.manage_compareConfigurations( lhs, rhs, missing_as_empty, ignore_blanks, ) RESPONSE.setHeader('Content-Type', 'text/plain') return _PLAINTEXT_DIFF_HEADER % (lhs, rhs, comparison) security.declareProtected(ManagePortal, 'manage_compareConfigurations') def manage_compareConfigurations( self, lhs, rhs, missing_as_empty, ignore_blanks, ): """ Crack request vars and call compareConfigurations. """ lhs_context = self._getImportContext(lhs) rhs_context = self._getImportContext(rhs) return self.compareConfigurations( lhs_context, rhs_context, missing_as_empty, ignore_blanks, ) # # Helper methods # security.declarePrivate('_getProductPath') def _getProductPath(self, product_name): """ Return the absolute path of the product's directory. """ try: product = __import__('Products.%s' % product_name, globals(), {}, ['initialize']) except ImportError: raise ValueError, 'Not a valid product name: %s' % product_name return product.__path__[0] security.declarePrivate('_getImportContext') def _getImportContext(self, context_id, should_purge=None): """ Crack ID and generate appropriate import context. """ encoding = self.getEncoding() if context_id.startswith('profile-'): context_id = context_id[len('profile-'):] info = _profile_registry.getProfileInfo(context_id) if info.get('product'): path = os.path.join(self._getProductPath(info['product']), info['path']) else: path = info['path'] if should_purge is None: should_purge = (info.get('type') != EXTENSION) return DirectoryImportContext(self, path, should_purge, encoding) # else snapshot context_id = context_id[len('snapshot-'):] if should_purge is None: should_purge = True return SnapshotImportContext(self, context_id, should_purge, encoding) security.declarePrivate('_updateImportStepsRegistry') def _updateImportStepsRegistry(self, encoding): """ Update our import steps registry from our profile. """ context = self._getImportContext(self._import_context_id) xml = context.readDataFile(IMPORT_STEPS_XML) if xml is None: return info_list = self._import_registry.parseXML(xml, encoding) for step_info in info_list: id = step_info['id'] version = step_info['version'] handler = _resolveDottedName(step_info['handler']) dependencies = tuple(step_info.get('dependencies', ())) title = step_info.get('title', id) description = ''.join(step_info.get('description', [])) self._import_registry.registerStep( id=id, version=version, handler=handler, dependencies=dependencies, title=title, description=description, ) security.declarePrivate('_updateExportStepsRegistry') def _updateExportStepsRegistry(self, encoding): """ Update our export steps registry from our profile. """ context = self._getImportContext(self._import_context_id) xml = context.readDataFile(EXPORT_STEPS_XML) if xml is None: return info_list = self._export_registry.parseXML(xml, encoding) for step_info in info_list: id = step_info['id'] handler = _resolveDottedName(step_info['handler']) title = step_info.get('title', id) description = ''.join(step_info.get('description', [])) self._export_registry.registerStep( id=id, handler=handler, title=title, description=description, ) security.declarePrivate('_updateToolsetRegistry') def _updateToolsetRegistry(self, encoding): """ Update our toolset registry from our profile. """ context = self._getImportContext(self._import_context_id) xml = context.readDataFile(TOOLSET_XML) if xml is None: return self._toolset_registry.parseXML(xml, encoding) security.declarePrivate('_doRunImportStep') def _doRunImportStep(self, step_id, context): """ Run a single import step, using a pre-built context. """ handler = self._import_registry.getStep(step_id) if handler is None: raise ValueError('Invalid import step: %s' % step_id) return handler(context) security.declarePrivate('_doRunExportSteps') def _doRunExportSteps(self, steps): """ See ISetupTool. """ context = TarballExportContext(self) messages = {} for step_id in steps: handler = self._export_registry.getStep(step_id) if handler is None: raise ValueError('Invalid export step: %s' % step_id) messages[step_id] = handler(context) return { 'steps': steps, 'messages': messages, 'tarball': context.getArchive(), 'filename': context.getArchiveFilename() }
raise ValueError('Invalid export step: %s' % step_id) messages[step_id] = handler(context) return { 'steps': steps, 'messages': messages, 'tarball': context.getArchive(), 'filename': context.getArchiveFilename() } InitializeClass(SetupTool) _PLAINTEXT_DIFF_HEADER = """\ Comparing configurations: '%s' and '%s' %s""" _TOOL_ID = 'setup_tool' addSetupToolForm = PageTemplateResource('www/toolAdd.zpt', globals()) def addSetupTool(dispatcher, RESPONSE): """ """ dispatcher._setObject(_TOOL_ID, SetupTool(_TOOL_ID)) RESPONSE.redirect('%s/manage_main' % dispatcher.absolute_url())
class TypesTool(UniqueObject, IFAwareObjectManager, Folder, ActionProviderBase): """ Provides a configurable registry of portal content types. """ implements(ITypesTool) __implements__ = (z2ITypesTool, ActionProviderBase.__implements__) id = 'portal_types' meta_type = 'CMF Types Tool' _product_interfaces = (ITypeInformation, ) security = ClassSecurityInfo() manage_options = (Folder.manage_options[:1] + ({ 'label': 'Aliases', 'action': 'manage_aliases' }, ) + ActionProviderBase.manage_options + ({ 'label': 'Overview', 'action': 'manage_overview' }, ) + Folder.manage_options[1:]) # # ZMI methods # security.declareProtected(ManagePortal, 'manage_overview') manage_overview = DTMLResource('dtml/explainTypesTool', globals()) security.declareProtected(ManagePortal, 'manage_aliases') manage_aliases = PageTemplateResource('typesAliases.zpt', globals()) # # ObjectManager methods # def all_meta_types(self): # this is a workaround and should be removed again if allowedTypes # have an interface we can use in _product_interfaces all = TypesTool.inheritedAttribute('all_meta_types')(self) others = [ mt for mt in Products.meta_types if mt['name'] in allowedTypes ] return tuple(all) + tuple(others) # # other methods # security.declareProtected(ManagePortal, 'manage_addTypeInformation') def manage_addTypeInformation(self, add_meta_type, id=None, typeinfo_name=None, RESPONSE=None): """Create a TypeInformation in self. """ # BBB: typeinfo_name is ignored if not id: raise BadRequest('An id is required.') for mt in Products.meta_types: if mt['name'] == add_meta_type: klass = mt['instance'] break else: raise ValueError, ('Meta type %s is not a type class.' % add_meta_type) id = str(id) ob = klass(id) self._setObject(id, ob) if RESPONSE is not None: RESPONSE.redirect('%s/manage_main' % self.absolute_url()) security.declareProtected(ManagePortal, 'manage_setTIMethodAliases') def manage_setTIMethodAliases(self, REQUEST): """ Config method aliases. """ form = REQUEST.form aliases = {} for k, v in form['aliases'].items(): v = v.strip() if v: aliases[k] = v for ti in self.listTypeInfo(): _dict = {} for k, v in form[ti.getId()].items(): if aliases.has_key(k): _dict[aliases[k]] = v ti.setMethodAliases(_dict) REQUEST.RESPONSE.redirect('%s/manage_aliases' % self.absolute_url()) security.declareProtected(AccessContentsInformation, 'getTypeInfo') def getTypeInfo(self, contentType): """ Return an instance which implements the TypeInformation interface, corresponding to the specified 'contentType'. If contentType is actually an object, rather than a string, attempt to look up the appropriate type info using its portal_type. """ if not isinstance(contentType, basestring): if hasattr(aq_base(contentType), 'getPortalTypeName'): contentType = contentType.getPortalTypeName() if contentType is None: return None else: return None ob = getattr(self, contentType, None) if getattr(aq_base(ob), '_isTypeInformation', 0): return ob else: return None security.declareProtected(AccessContentsInformation, 'listTypeInfo') def listTypeInfo(self, container=None): """ Return a sequence of instances which implement the TypeInformation interface, one for each content type registered in the portal. """ rval = [] for t in self.objectValues(): # Filter out things that aren't TypeInformation and # types for which the user does not have adequate permission. if not getattr(aq_base(t), '_isTypeInformation', 0): continue if not t.getId(): # XXX What's this used for ? # Not ready. continue # check we're allowed to access the type object if container is not None: if not t.isConstructionAllowed(container): continue rval.append(t) return rval security.declareProtected(AccessContentsInformation, 'listContentTypes') def listContentTypes(self, container=None, by_metatype=0): """ List type info IDs. Passing 'by_metatype' is deprecated (type information may not correspond 1:1 to an underlying meta_type). This argument will be removed when CMFCore/dtml/catalogFind.dtml doesn't need it anymore. """ typenames = {} for t in self.listTypeInfo(container): if by_metatype: warn( 'TypeInformation.listContentTypes(by_metatype=1) is ' 'deprecated.', DeprecationWarning) name = t.Metatype() else: name = t.getId() if name: typenames[name] = None result = typenames.keys() result.sort() return result security.declarePublic('constructContent') def constructContent(self, type_name, container, id, RESPONSE=None, *args, **kw): """ Build an instance of the appropriate content class in 'container', using 'id'. """ info = self.getTypeInfo(type_name) if info is None: raise ValueError('No such content type: %s' % type_name) ob = info.constructInstance(container, id, *args, **kw) if RESPONSE is not None: immediate_url = '%s/%s' % (ob.absolute_url(), info.immediate_view) RESPONSE.redirect(immediate_url) return ob.getId() security.declarePrivate('listActions') def listActions(self, info=None, object=None): """ List all the actions defined by a provider. """ actions = list(self._actions) if object is None and info is not None: object = info.object if object is not None: type_info = self.getTypeInfo(object) if type_info is not None: actions.extend(type_info.listActions()) return actions security.declareProtected(ManagePortal, 'listMethodAliasKeys') def listMethodAliasKeys(self): """ List all defined method alias names. """ _dict = {} for ti in self.listTypeInfo(): aliases = ti.getMethodAliases() for k, v in aliases.items(): _dict[k] = 1 rval = _dict.keys() rval.sort() return rval
class TypeInformation(SimpleItemWithProperties, ActionProviderBase): """ Base class for information about a content type. """ _isTypeInformation = 1 manage_options = (SimpleItemWithProperties.manage_options[:1] + ({ 'label': 'Aliases', 'action': 'manage_aliases' }, ) + ActionProviderBase.manage_options + SimpleItemWithProperties.manage_options[1:]) security = ClassSecurityInfo() security.declareProtected(ManagePortal, 'manage_editProperties') security.declareProtected(ManagePortal, 'manage_changeProperties') security.declareProtected(ManagePortal, 'manage_propertiesForm') _basic_properties = ( { 'id': 'title', 'type': 'string', 'mode': 'w', 'label': 'Title' }, { 'id': 'description', 'type': 'text', 'mode': 'w', 'label': 'Description' }, { 'id': 'i18n_domain', 'type': 'string', 'mode': 'w', 'label': 'I18n Domain' }, { 'id': 'content_icon', 'type': 'string', 'mode': 'w', 'label': 'Icon' }, { 'id': 'content_meta_type', 'type': 'string', 'mode': 'w', 'label': 'Product meta type' }, ) _advanced_properties = ( { 'id': 'immediate_view', 'type': 'string', 'mode': 'w', 'label': 'Initial view name' }, { 'id': 'global_allow', 'type': 'boolean', 'mode': 'w', 'label': 'Implicitly addable?' }, { 'id': 'filter_content_types', 'type': 'boolean', 'mode': 'w', 'label': 'Filter content types?' }, { 'id': 'allowed_content_types', 'type': 'multiple selection', 'mode': 'w', 'label': 'Allowed content types', 'select_variable': 'listContentTypes' }, { 'id': 'allow_discussion', 'type': 'boolean', 'mode': 'w', 'label': 'Allow Discussion?' }, ) title = '' description = '' i18n_domain = '' content_meta_type = '' content_icon = '' immediate_view = '' filter_content_types = True allowed_content_types = () allow_discussion = False global_allow = True def __init__(self, id, **kw): self.id = id if not kw: return kw = kw.copy() # Get a modifiable dict. if (not kw.has_key('content_meta_type') and kw.has_key('meta_type')): kw['content_meta_type'] = kw['meta_type'] if (not kw.has_key('content_icon') and kw.has_key('icon')): kw['content_icon'] = kw['icon'] self.manage_changeProperties(**kw) actions = kw.get('actions', ()) # make sure we have a copy _actions = [] for action in actions: _actions.append(action.copy()) actions = tuple(_actions) # We don't know if actions need conversion, so we always add oldstyle # _actions and convert them. self._actions = actions self._convertActions() aliases = kw.get('aliases', _marker) if aliases is _marker: self._guessMethodAliases() else: self.setMethodAliases(aliases) # # ZMI methods # security.declareProtected(ManagePortal, 'manage_aliases') manage_aliases = PageTemplateResource('www/typeinfoAliases.zpt', globals()) security.declareProtected(ManagePortal, 'manage_setMethodAliases') def manage_setMethodAliases(self, REQUEST): """ Config method aliases. """ form = REQUEST.form aliases = {} for k, v in form['aliases'].items(): v = v.strip() if v: aliases[k] = v _dict = {} for k, v in form['methods'].items(): if aliases.has_key(k): _dict[aliases[k]] = v self.setMethodAliases(_dict) REQUEST.RESPONSE.redirect('%s/manage_aliases' % self.absolute_url()) # # Accessors # security.declareProtected(View, 'Title') def Title(self): """ Return the "human readable" type name (note that it may not map exactly to the 'portal_type', e.g., for l10n/i18n or where a single content class is being used twice, under different names. """ if self.title and self.i18n_domain: return MessageID(self.title, self.i18n_domain) else: return self.title or self.getId() security.declareProtected(View, 'Description') def Description(self): """ Textual description of the class of objects (intended for display in a "constructor list"). """ if self.description and self.i18n_domain: return MessageID(self.description, self.i18n_domain) else: return self.description security.declareProtected(View, 'Metatype') def Metatype(self): """ Returns the Zope 'meta_type' for this content object. May be used for building the list of portal content meta types. """ return self.content_meta_type security.declareProtected(View, 'getIcon') def getIcon(self): """ Returns the icon for this content object. """ return self.content_icon security.declarePublic('allowType') def allowType(self, contentType): """ Can objects of 'contentType' be added to containers whose type object we are? """ if not self.filter_content_types: ti = self.getTypeInfo(contentType) if ti is None or ti.globalAllow(): return 1 #If a type is enabled to filter and no content_types are allowed if not self.allowed_content_types: return 0 if contentType in self.allowed_content_types: return 1 # Backward compatibility for code that expected Type() to work. for ti in self.listTypeInfo(): if ti.Title() == contentType: return ti.getId() in self.allowed_content_types return 0 security.declarePublic('getId') def getId(self): return self.id security.declarePublic('allowDiscussion') def allowDiscussion(self): """ Can this type of object support discussion? """ return self.allow_discussion security.declarePublic('globalAllow') def globalAllow(self): """ Should this type be implicitly addable anywhere? """ return self.global_allow security.declarePublic('listActions') def listActions(self, info=None, object=None): """ Return a sequence of the action info objects for this type. """ if self._actions and isinstance(self._actions[0], dict): self._convertActions() return self._actions or () security.declarePrivate('_convertActions') def _convertActions(self): """ Upgrade dictionary-based actions. """ aa, self._actions = self._actions, () for action in aa: # Some backward compatibility stuff. if not 'id' in action: action['id'] = cookString(action['name']) if not 'title' in action: action['title'] = action.get('name', action['id'].capitalize()) # historically, action['action'] is simple string actiontext = action.get('action').strip() or 'string:${object_url}' if actiontext[:7] not in ('python:', 'string:'): actiontext = 'string:${object_url}/%s' % actiontext self.addAction(id=action['id'], name=action['title'], action=actiontext, condition=action.get('condition'), permission=action.get('permissions', ()), category=action.get('category', 'object'), visible=action.get('visible', True)) security.declarePublic('constructInstance') def constructInstance(self, container, id, *args, **kw): """Build an instance of the type. Builds the instance in 'container', using 'id' as its id. Returns the object. """ if not self.isConstructionAllowed(container): raise AccessControl_Unauthorized('Cannot create %s' % self.getId()) ob = self._constructInstance(container, id, *args, **kw) return self._finishConstruction(ob) security.declarePrivate('_finishConstruction') def _finishConstruction(self, ob): """ Finish the construction of a content object. Set its portal_type, insert it into the workflows. """ if hasattr(ob, '_setPortalTypeName'): ob._setPortalTypeName(self.getId()) if hasattr(aq_base(ob), 'notifyWorkflowCreated'): ob.notifyWorkflowCreated() ob.reindexObject() return ob security.declareProtected(ManagePortal, 'getMethodAliases') def getMethodAliases(self): """ Get method aliases dict. """ if not hasattr(self, '_aliases'): self._guessMethodAliases() aliases = self._aliases # for aliases created with CMF 1.5.0beta for key, method_id in aliases.items(): if isinstance(method_id, tuple): aliases[key] = method_id[0] self._p_changed = True return aliases.copy() security.declareProtected(ManagePortal, 'setMethodAliases') def setMethodAliases(self, aliases): """ Set method aliases dict. """ _dict = {} for k, v in aliases.items(): v = v.strip() if v: _dict[k.strip()] = v if not getattr(self, '_aliases', None) == _dict: self._aliases = _dict return True else: return False security.declarePublic('queryMethodID') def queryMethodID(self, alias, default=None, context=None): """ Query method ID by alias. """ if not hasattr(self, '_aliases'): self._guessMethodAliases() aliases = self._aliases method_id = aliases.get(alias, default) # for aliases created with CMF 1.5.0beta if isinstance(method_id, tuple): method_id = method_id[0] return method_id security.declarePrivate('_guessMethodAliases') def _guessMethodAliases(self): """ Guess and set Method Aliases. Used for upgrading old TIs. """ context = getActionContext(self) actions = self.listActions() ordered = [] _dict = {} viewmethod = '' # order actions and search 'mkdir' action for action in actions: if action.getId() == 'view': ordered.insert(0, action) elif action.getId() == 'mkdir': try: mkdirmethod = action.action(context).strip() except AttributeError: continue if mkdirmethod.startswith('/'): mkdirmethod = mkdirmethod[1:] _dict['mkdir'] = mkdirmethod else: ordered.append(action) # search 'view' action for action in ordered: perms = action.getPermissions() if not perms or View in perms: try: viewmethod = action.action(context).strip() except (AttributeError, TypeError): break if viewmethod.startswith('/'): viewmethod = viewmethod[1:] if not viewmethod: viewmethod = '(Default)' break else: viewmethod = '(Default)' if viewmethod: _dict['view'] = viewmethod # search default action for action in ordered: try: defmethod = action.action(context).strip() except (AttributeError, TypeError): break if defmethod.startswith('/'): defmethod = defmethod[1:] if not defmethod: break else: if viewmethod: _dict['(Default)'] = viewmethod # correct guessed values if we know better if self.content_meta_type in ('Portal File', 'Portal Folder', 'Portal Image'): _dict['(Default)'] = 'index_html' if viewmethod == '(Default)': _dict['view'] = 'index_html' if self.content_meta_type in ('Document', 'News Item'): _dict['gethtml'] = 'source_html' self.setMethodAliases(_dict) return 1
""" constructor = self.restrictedTraverse(self.constructor_path) # make sure ownership is explicit before switching the context if not hasattr(aq_base(constructor), '_owner'): constructor._owner = aq_get(constructor, '_owner') # Rewrap to get into container's context. constructor = aq_base(constructor).__of__(container) id = str(id) return constructor(container, id, *args, **kw) InitializeClass(ScriptableTypeInformation) _addTypeInfo_template = PageTemplateResource('addTypeInfo.zpt', globals()) def manage_addFactoryTIForm(dispatcher, REQUEST): """ Get the add form for factory-based type infos. """ template = _addTypeInfo_template.__of__(dispatcher) meta_type = FactoryTypeInformation.meta_type return template(add_meta_type=meta_type, profiles=_getProfileInfo(dispatcher, meta_type)) def manage_addScriptableTIForm(dispatcher, REQUEST): """ Get the add form for scriptable type infos. """ template = _addTypeInfo_template.__of__(dispatcher)