Back to index

plone3  3.1.7
ArchetypeTool.py
Go to the documentation of this file.
00001 from __future__ import nested_scopes
00002 
00003 import os.path
00004 import sys
00005 from copy import deepcopy
00006 from DateTime import DateTime
00007 from StringIO import StringIO
00008 from debug import deprecated
00009 
00010 from zope.interface import implements
00011 
00012 from Products.Archetypes import PloneMessageFactory as _
00013 from Products.Archetypes.interfaces import IArchetypeTool
00014 from Products.Archetypes.interfaces.base import IBaseObject
00015 from Products.Archetypes.interfaces.referenceable import IReferenceable
00016 from Products.Archetypes.interfaces.metadata import IExtensibleMetadata
00017 from Products.Archetypes.interfaces.ITemplateMixin import ITemplateMixin
00018 from Products.Archetypes.ClassGen import generateClass
00019 from Products.Archetypes.ClassGen import generateCtor
00020 from Products.Archetypes.ClassGen import generateZMICtor
00021 from Products.Archetypes.SQLStorageConfig import SQLStorageConfig
00022 from Products.Archetypes.config import TOOL_NAME
00023 from Products.Archetypes.config import UID_CATALOG
00024 from Products.Archetypes.config import HAS_GRAPHVIZ
00025 from Products.Archetypes.debug import log
00026 from Products.Archetypes.utils import findDict
00027 from Products.Archetypes.utils import DisplayList
00028 from Products.Archetypes.utils import mapply
00029 from Products.Archetypes.Renderer import renderer
00030 
00031 from Products.CMFCore import permissions
00032 from Products.CMFCore.ActionProviderBase import ActionProviderBase
00033 from Products.CMFCore.TypesTool import FactoryTypeInformation
00034 from Products.CMFCore.utils import getToolByName
00035 from Products.CMFCore.utils import registerToolInterface
00036 from Products.CMFCore.utils import UniqueObject
00037 from Products.CMFCore.interfaces.portal_catalog \
00038      import portal_catalog as ICatalogTool
00039 from Products.CMFCore.ActionInformation import ActionInformation
00040 from Products.CMFCore.Expression import Expression
00041 
00042 from AccessControl import ClassSecurityInfo
00043 from Acquisition import ImplicitAcquisitionWrapper
00044 from Globals import InitializeClass
00045 from Globals import PersistentMapping
00046 from OFS.Folder import Folder
00047 from Products.ZCatalog.IZCatalog import IZCatalog
00048 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
00049 from ZODB.POSException import ConflictError
00050 import transaction
00051 
00052 class BoundPageTemplateFile(PageTemplateFile):
00053 
00054     def __init__(self, *args, **kw):
00055         self._extra = kw['extra']
00056         del kw['extra']
00057         args = (self,) + args
00058         mapply(PageTemplateFile.__init__, *args, **kw)
00059 
00060     def pt_render(self, source=False, extra_context={}):
00061         options = extra_context.get('options', {})
00062         options.update(self._extra)
00063         extra_context['options'] = options
00064         return PageTemplateFile.pt_render(self, source, extra_context)
00065 
00066 try:
00067     from Products.CMFPlone.Configuration import getCMFVersion
00068 except ImportError:
00069     # Configuration and getCMFVersion come with Plone
00070     def getCMFVersion():
00071         from os.path import join
00072         from Globals import package_home
00073         from Products.CMFCore import cmfcore_globals
00074         path = join(package_home(cmfcore_globals),'version.txt')
00075         file = open(path, 'r')
00076         _version = file.read()
00077         file.close()
00078         return _version.strip()
00079 
00080 _www = os.path.join(os.path.dirname(__file__), 'www')
00081 _skins = os.path.join(os.path.dirname(__file__), 'skins')
00082 _zmi = os.path.join(_www, 'zmi')
00083 document_icon = os.path.join(_zmi, 'icons', 'document_icon.gif')
00084 folder_icon = os.path.join(_zmi, 'icons', 'folder_icon.gif')
00085 
00086 # This is the template that we produce our custom types from
00087 base_factory_type_information = (
00088     { 'id': 'Archetype'
00089       , 'content_icon': 'document_icon.gif'
00090       , 'meta_type': 'Archetype'
00091       , 'description': ('Archetype for flexible types')
00092       , 'product': 'Unknown Package'
00093       , 'factory': 'addContent'
00094       , 'immediate_view': 'base_edit'
00095       , 'global_allow': True
00096       , 'filter_content_types': False
00097       , 'allow_discussion': False
00098       , 'fti_meta_type' : FactoryTypeInformation.meta_type
00099       , 'aliases' : {'(Default)' : 'base_view',
00100                      'view' : '(Default)',
00101                      'index.html' : '(Default)',
00102                      'edit' : 'base_edit',
00103                      'properties' : 'base_metadata',
00104                      'gethtml' : '',
00105                      'mkdir' : '',
00106                      }
00107       , 'actions': (
00108                      { 'id': 'view',
00109                        'title': 'View',
00110                        'action': Expression('string:${object_url}/view'),
00111                        'permissions': (permissions.View,),
00112                        },
00113 
00114                      { 'id': 'edit',
00115                        'title': 'Edit',
00116                        'action': Expression('string:${object_url}/edit'),
00117                        'permissions': (permissions.ModifyPortalContent,),
00118                        'condition': Expression('not:object/@@plone_lock_info/is_locked_for_current_user')
00119                        },
00120 
00121                      { 'id': 'metadata',
00122                        'title': 'Properties',
00123                        'action': Expression('string:${object_url}/properties'),
00124                        'permissions': (permissions.ModifyPortalContent,),
00125                        },
00126 
00127                      { 'id': 'references',
00128                        'title': 'References',
00129                        'action': Expression('string:${object_url}/reference_graph'),
00130                        'condition': Expression('object/archetype_tool/has_graphviz'),
00131                        'permissions': (permissions.ModifyPortalContent,
00132                                        permissions.ReviewPortalContent,),
00133                        'visible' : True,
00134                        },
00135                      )
00136       }, )
00137 
00138 def fixActionsForType(portal_type, typesTool):
00139     if 'actions' in portal_type.installMode:
00140         typeInfo = getattr(typesTool, portal_type.portal_type, None)
00141         if typeInfo is None:
00142             return
00143         if hasattr(portal_type, 'actions'):
00144             # Look for each action we define in portal_type.actions in
00145             # typeInfo.action replacing it if its there and just
00146             # adding it if not
00147             ## rr: this is now trial-and-error programming
00148             ## I really don't know what's going on here
00149             ## most importantly I don't know why the default
00150             ## actions are not set in some cases :-(
00151             ## (maybe they are removed afterwards sometimes???)
00152             ## if getattr(portal_type,'include_default_actions', True):
00153             if True:
00154                 default = [ActionInformation(**action) for action in
00155                            base_factory_type_information[0]['actions']]
00156                 next = list(typeInfo._actions)
00157                 all = next + default
00158                 new = [a.clone() for a in all]
00159             else:
00160                 # If no standard actions are wished don't display them
00161                 new = []
00162             try:
00163                 cmfver = getCMFVersion()
00164             except ImportError:
00165                 cmfver = 'CMF-2.0'   ## rr: kind of a hack but all we
00166                                      ## need to know here for now
00167 
00168             for action in portal_type.actions:
00169                 # DM: "Expression" derives from "Persistent" and
00170                 # we must not put persistent objects into class attributes.
00171                 # Thus, copy "action"
00172                 action = action.copy()
00173 
00174                 if cmfver[:7] >= 'CMF-1.4' or cmfver == 'Unreleased':
00175                     # Then we know actions are defined new style as
00176                     # ActionInformations
00177                     hits = [a for a in new if a.id == action['id']]
00178 
00179                     # Change action and condition into expressions, if
00180                     # they are still strings
00181                     if action.has_key('action') and \
00182                            type(action['action']) in (type(''), type(u'')):
00183                         action['action'] = Expression(action['action'])
00184                     if action.has_key('condition') and \
00185                            type(action['condition']) in (type(''), type(u'')):
00186                         action['condition'] = Expression(action['condition'])
00187                     if action.has_key('name'):
00188                         action['title'] = action['name']
00189                         del action['name']
00190                     if hits:
00191                         hits[0].__dict__.update(action)
00192                     else:
00193                         new.append(ActionInformation(**action))
00194                 else:
00195                     hit = findDict(new, 'id', action['id'])
00196                     if hit:
00197                         hit.update(action)
00198                     else:
00199                         new.append(action)
00200 
00201             typeInfo._actions = tuple(new)
00202             typeInfo._p_changed = True
00203 
00204         if hasattr(portal_type, 'factory_type_information'):
00205             typeInfo.__dict__.update(portal_type.factory_type_information)
00206             typeInfo._p_changed = True
00207 
00208 
00209 def modify_fti(fti, klass, pkg_name):
00210     fti[0]['id'] = klass.__name__
00211     fti[0]['meta_type'] = klass.meta_type
00212     fti[0]['description'] = klass.__doc__
00213     fti[0]['factory'] = 'add%s' % klass.__name__
00214     fti[0]['product'] = pkg_name
00215 
00216     if hasattr(klass, 'content_icon'):
00217         fti[0]['content_icon'] = klass.content_icon
00218 
00219     if hasattr(klass, 'global_allow'):
00220         fti[0]['global_allow'] = klass.global_allow
00221 
00222     if hasattr(klass, 'allow_discussion'):
00223         fti[0]['allow_discussion'] = klass.allow_discussion
00224 
00225     if hasattr(klass, 'allowed_content_types'):
00226         allowed = klass.allowed_content_types
00227         fti[0]['allowed_content_types'] = allowed
00228         fti[0]['filter_content_types'] = allowed and True or False
00229 
00230     if hasattr(klass, 'filter_content_types'):
00231         fti[0]['filter_content_types'] = klass.filter_content_types
00232 
00233     if hasattr(klass, 'immediate_view'):
00234         fti[0]['immediate_view'] = klass.immediate_view
00235 
00236     if not IReferenceable.isImplementedByInstancesOf(klass):
00237         refs = findDict(fti[0]['actions'], 'id', 'references')
00238         refs['visible'] = False
00239 
00240     if not IExtensibleMetadata.isImplementedByInstancesOf(klass):
00241         refs = findDict(fti[0]['actions'], 'id', 'metadata')
00242         refs['visible'] = False
00243 
00244     # Set folder_listing to 'view' if the class implements ITemplateMixin
00245     if not ITemplateMixin.isImplementedByInstancesOf(klass):
00246         actions = []
00247         for action in fti[0]['actions']:
00248             if action['id'] != 'folderlisting':
00249                 actions.append(action)
00250             else:
00251                 action['action'] = 'string:${folder_url}/view'
00252                 actions.append(action)
00253         fti[0]['actions'] = tuple(actions)
00254 
00255     # Remove folderlisting action from non folderish content types
00256     if not getattr(klass,'isPrincipiaFolderish', None):
00257         actions = []
00258         for action in fti[0]['actions']:
00259             if action['id'] != 'folderlisting':
00260                 actions.append(action)
00261         fti[0]['actions'] = tuple(actions)
00262         
00263     # CMF 1.5 method aliases
00264     if getattr(klass, 'aliases', None):
00265         aliases = klass.aliases
00266         if not isinstance(aliases, dict):
00267             raise TypeError, "Invalid type for method aliases in class %s" % klass
00268         for required in ('(Default)', 'view',):
00269             if required not in aliases:
00270                 raise ValueError, "Alias %s is required but not provied by %s" % (
00271                                   required, klass)
00272         fti[0]['aliases'] = aliases 
00273         
00274     # Dynamic View FTI support
00275     if getattr(klass, 'default_view', False):
00276         default_view = klass.default_view
00277         if not isinstance(default_view, basestring):
00278             raise TypeError, "Invalid type for default view in class %s" % klass
00279         fti[0]['default_view'] = default_view
00280         fti[0]['view_methods'] = (default_view, )
00281         
00282         if getattr(klass, 'suppl_views', False):
00283             suppl_views = klass.suppl_views
00284             if not isinstance(suppl_views, (list, tuple)):
00285                 raise TypeError, "Invalid type for suppl views in class %s" % klass
00286             if not default_view in suppl_views:
00287                 suppl_views = suppl_views + (default_view, )
00288             fti[0]['view_methods'] = suppl_views
00289     if getattr(klass, '_at_fti_meta_type', False):
00290         fti[0]['fti_meta_type'] = klass._at_fti_meta_type
00291     else:
00292         if fti[0].get('fti_meta_type', False):
00293             klass._at_fti_meta_type = fti[0]['fti_meta_type']
00294         else:
00295             fti[0]['fti_meta_type'] = FactoryTypeInformation.meta_type
00296 
00297 
00298 def process_types(types, pkg_name):
00299     content_types = ()
00300     constructors = ()
00301     ftis = ()
00302 
00303     for rti in types:
00304         typeName = rti['name']
00305         klass = rti['klass']
00306         module = rti['module']
00307 
00308         if hasattr(module, 'factory_type_information'):
00309             fti = module.factory_type_information
00310         else:
00311             fti = deepcopy(base_factory_type_information)
00312             modify_fti(fti, klass, pkg_name)
00313 
00314         # Add a callback to modifty the fti
00315         if hasattr(module, 'modify_fti'):
00316             module.modify_fti(fti[0])
00317         else:
00318             m = None
00319             for k in klass.__bases__:
00320                 base_module = sys.modules[k.__module__]
00321                 if hasattr(base_module, 'modify_fti'):
00322                     m = base_module
00323                     break
00324             if m is not None:
00325                 m.modify_fti(fti[0])
00326 
00327         ctor = getattr(module, 'add%s' % typeName, None)
00328         if ctor is None:
00329             ctor = generateCtor(typeName, module)
00330 
00331         content_types += (klass,)
00332         constructors += (ctor,)
00333         ftis += fti
00334 
00335     return content_types, constructors, ftis
00336 
00337 
00338 _types = {}
00339 
00340 def _guessPackage(base):
00341     if base.startswith('Products'):
00342         base = base[9:]
00343         idx = base.index('.')
00344         if idx != -1:
00345             base = base[:idx]
00346     return base
00347 
00348 def registerType(klass, package=None):
00349     if not package:
00350         deprecated("registerType without a package name is deprecated. "
00351                    "Please apply a package name for class %s" % repr(klass),
00352                    level=2)
00353         package = _guessPackage(klass.__module__)
00354 
00355     # Registering a class results in classgen doing its thing
00356     # Set up accessor/mutators and sane meta/portal_type
00357     generateClass(klass)
00358 
00359     data = {
00360         'klass' : klass,
00361         'name' : klass.__name__,
00362         'identifier': klass.meta_type.capitalize().replace(' ', '_'),
00363         'meta_type': klass.meta_type,
00364         'portal_type': klass.portal_type,
00365         'package' : package,
00366         'module' : sys.modules[klass.__module__],
00367         'schema' : klass.schema,
00368         'signature' : klass.schema.signature(),
00369         # backward compatibility, remove later
00370         'type' : klass.schema,
00371         }
00372 
00373     key = '%s.%s' % (package, data['meta_type'])
00374     if key in _types.keys():
00375         existing = _types[key]
00376         existing_name = '%s.%s' % (existing['module'].__name__, existing['name'])
00377         override_name = '%s.%s' % (data['module'].__name__, data['name'])
00378         log('ArchetypesTool: Trying to register "%s" which ' \
00379             'has already been registered.  The new type %s ' \
00380             'is going to override %s' % (key, override_name, existing_name))
00381     _types[key] = data
00382 
00383 def fixAfterRenameType(context, old_portal_type, new_portal_type):
00384     """Helper method to fix some vars after renaming a type in portal_types
00385 
00386     It will raise an IndexError if called with a nonexisting old_portal_type.
00387     If you like to swallow the error please use a try/except block in your own
00388     code and do NOT 'fix' this method.
00389     """
00390     at_tool = getToolByName(context, TOOL_NAME)
00391     __traceback_info__ = (context, old_portal_type, new_portal_type,
00392                           [t['portal_type'] for t in _types.values()])
00393     # Will fail if old portal type wasn't registered (DO 'FIX' THE INDEX ERROR!)
00394     old_type = [t for t in _types.values()
00395                 if t['portal_type'] == old_portal_type][0]
00396 
00397     # Rename portal type
00398     old_type['portal_type'] = new_portal_type
00399 
00400     # Copy old templates to new portal name without references
00401     old_templates = at_tool._templates.get(old_portal_type)
00402     at_tool._templates[new_portal_type] = deepcopy(old_templates)
00403 
00404 def registerClasses(context, package, types=None):
00405     registered = listTypes(package)
00406     if types is not None:
00407         registered = filter(lambda t: t['meta_type'] in types, registered)
00408     for t in registered:
00409         module = t['module']
00410         typeName = t['name']
00411         meta_type = t['meta_type']
00412         portal_type = t['portal_type']
00413         klass = t['klass']
00414         ctorName = 'manage_add%s' % typeName
00415         constructor = getattr(module, ctorName, None)
00416         if constructor is None:
00417             constructor = generateZMICtor(typeName, module)
00418         addFormName = 'manage_add%sForm' % typeName
00419         setattr(module, addFormName,
00420                 BoundPageTemplateFile('base_add.pt', _zmi,
00421                                       __name__=addFormName,
00422                                       extra={'constructor':ctorName,
00423                                              'type':meta_type,
00424                                              'package':package,
00425                                              'portal_type':portal_type}
00426                                       ))
00427         editFormName = 'manage_edit%sForm' % typeName
00428         setattr(klass, editFormName,
00429                 BoundPageTemplateFile('base_edit.pt', _zmi,
00430                                       __name__=editFormName,
00431                                       extra={'handler':'processForm',
00432                                              'type':meta_type,
00433                                              'package':package,
00434                                              'portal_type':portal_type}
00435                                       ))
00436 
00437         position = False
00438         for item in klass.manage_options:
00439             if item['label'] != 'Contents':
00440                 continue
00441             position += 1
00442         folderish = getattr(klass, 'isPrincipiaFolderish', position)
00443         options = list(klass.manage_options)
00444         options.insert(position, {'label' : 'Edit',
00445                                   'action' : editFormName
00446                                   })
00447         klass.manage_options = tuple(options)
00448         generatedForm = getattr(module, addFormName)
00449         icon = folderish and folder_icon or document_icon
00450         if klass.__dict__.has_key('content_icon'):
00451             icon = klass.content_icon
00452         elif hasattr(klass, 'factory_type_information'):
00453             factory_type_information = klass.factory_type_information
00454             if factory_type_information.has_key('content_icon'):
00455                 icon = factory_type_information['content_icon']
00456 
00457         context.registerClass(
00458             t['klass'],
00459             constructors=(generatedForm, constructor),
00460             visibility=None,
00461             icon=icon
00462             )
00463 
00464 def listTypes(package=None):
00465     values = _types.values()
00466     if package:
00467         values = [v for v in values if v['package'] == package]
00468 
00469     return values
00470 
00471 def getType(name, package):
00472     key = '%s.%s' % (package, name)
00473     return _types[key]
00474 
00475 class WidgetWrapper:
00476     """Wrapper used for drawing widgets without an instance.
00477 
00478     E.g.: for a search form.
00479     """
00480     security = ClassSecurityInfo()
00481     security.declareObjectPublic()
00482     def __init__(self, **args):
00483         self._args = args
00484 
00485     def __call__(self):
00486         __traceback_info__ = self._args
00487         return renderer.render(**self._args)
00488 
00489 last_load = DateTime()
00490 
00491 class ArchetypeTool(UniqueObject, ActionProviderBase, \
00492                     SQLStorageConfig, Folder):
00493     """Archetypes tool, manage aspects of Archetype instances.
00494     """
00495     id = TOOL_NAME
00496     meta_type = TOOL_NAME.title().replace('_', ' ')
00497 
00498     implements(IArchetypeTool)
00499 
00500     isPrincipiaFolderish = True # Show up in the ZMI
00501 
00502     security = ClassSecurityInfo()
00503 
00504     meta_types = all_meta_types = ()
00505 
00506     manage_options = (
00507         (
00508         { 'label'  : 'Types',
00509           'action' : 'manage_debugForm',
00510           },
00511 
00512         {  'label'  : 'Catalogs',
00513            'action' : 'manage_catalogs',
00514            },
00515 
00516         { 'label'  : 'Templates',
00517           'action' : 'manage_templateForm',
00518           },
00519 
00520         {  'label'  : 'UIDs',
00521            'action' : 'manage_uids',
00522            },
00523 
00524         { 'label'  : 'Update Schema',
00525           'action' : 'manage_updateSchemaForm',
00526           },
00527 
00528         { 'label'  : 'Migration',
00529           'action' : 'manage_migrationForm',
00530           },
00531 
00532         ) + SQLStorageConfig.manage_options
00533         )
00534 
00535     security.declareProtected(permissions.ManagePortal,
00536                               'manage_uids')
00537     manage_uids = PageTemplateFile('viewContents', _www)
00538     security.declareProtected(permissions.ManagePortal,
00539                               'manage_templateForm')
00540     manage_templateForm = PageTemplateFile('manageTemplates',_www)
00541     security.declareProtected(permissions.ManagePortal,
00542                               'manage_debugForm')
00543     manage_debugForm = PageTemplateFile('generateDebug', _www)
00544     security.declareProtected(permissions.ManagePortal,
00545                               'manage_updateSchemaForm')
00546     manage_updateSchemaForm = PageTemplateFile('updateSchemaForm', _www)
00547     security.declareProtected(permissions.ManagePortal,
00548                               'manage_migrationForm')
00549     manage_migrationForm = PageTemplateFile('migrationForm', _www)
00550     security.declareProtected(permissions.ManagePortal,
00551                               'manage_dumpSchemaForm')
00552     manage_dumpSchemaForm = PageTemplateFile('schema', _www)
00553     security.declareProtected(permissions.ManagePortal,
00554                               'manage_catalogs')
00555     manage_catalogs = PageTemplateFile('manage_catalogs', _www)
00556 
00557 
00558     def __init__(self):
00559         self._schemas = PersistentMapping()
00560         self._templates = PersistentMapping()
00561         self._registeredTemplates = PersistentMapping()
00562         # meta_type -> [names of CatalogTools]
00563         self.catalog_map = PersistentMapping()
00564         self.catalog_map['Reference'] = [] # References not in portal_catalog
00565         # DM (avoid persistency bug): "_types" now maps known schemas to signatures
00566         self._types = {}
00567 
00568     security.declareProtected(permissions.ManagePortal,
00569                               'manage_dumpSchema')
00570     def manage_dumpSchema(self, REQUEST=None):
00571         """XML Dump Schema of passed in class.
00572         """
00573         from Products.Archetypes.Schema import getSchemata
00574         package = REQUEST.get('package', '')
00575         type_name = REQUEST.get('type_name', '')
00576         spec = self.getTypeSpec(package, type_name)
00577         type = self.lookupType(package, type_name)
00578         options = {}
00579         options['classname'] = spec
00580         options['schematas'] = getSchemata(type['klass'])
00581         REQUEST.RESPONSE.setHeader('Content-Type', 'text/xml')
00582         return self.manage_dumpSchemaForm(**options)
00583 
00584     # Template Management
00585     # Views can be pretty generic by iterating the schema so we don't
00586     # register by type anymore, we just create per site selection
00587     # lists
00588     #
00589     # We keep two lists, all register templates and their
00590     # names/titles and the mapping of type to template bindings both
00591     # are persistent
00592     security.declareProtected(permissions.ManagePortal,
00593                               'registerTemplate')
00594     def registerTemplate(self, template, name=None):
00595         # Lookup the template by name
00596         if not name:
00597             obj = self.unrestrictedTraverse(template, None)
00598             try:
00599                 name = obj.title_or_id()
00600             except:
00601                 name = template
00602 
00603         self._registeredTemplates[template] = name
00604 
00605 
00606     security.declareProtected(permissions.View, 'lookupTemplates')
00607     def lookupTemplates(self, instance_or_portaltype=None):
00608         """Lookup templates by giving an instance or a portal_type.
00609 
00610         Returns a DisplayList.
00611         """
00612         results = []
00613         if not isinstance(instance_or_portaltype, basestring):
00614             portal_type = instance_or_portaltype.getTypeInfo().getId()
00615         else:
00616             portal_type = instance_or_portaltype
00617         try:
00618             templates = self._templates[portal_type]
00619         except KeyError:
00620             return DisplayList()
00621             # XXX Look this up in the types tool later
00622             # self._templates[instance] = ['base_view',]
00623             # templates = self._templates[instance]
00624         for t in templates:
00625             results.append((t, self._registeredTemplates[t]))            
00626 
00627         return DisplayList(results).sortedByValue()
00628 
00629     security.declareProtected(permissions.View, 'listTemplates')
00630     def listTemplates(self):
00631         """Lists all the templates.
00632         """
00633         return DisplayList(self._registeredTemplates.items()).sortedByValue()
00634 
00635     security.declareProtected(permissions.ManagePortal, 'bindTemplate')
00636     def bindTemplate(self, portal_type, templateList):
00637         """Creates binding between a type and its associated views.
00638         """
00639         self._templates[portal_type] = templateList
00640 
00641     security.declareProtected(permissions.ManagePortal,
00642                               'manage_templates')
00643     def manage_templates(self, REQUEST=None):
00644         """Sets all the template/type mappings.
00645         """
00646         prefix = 'template_names_'
00647         for key in REQUEST.form.keys():
00648             if key.startswith(prefix):
00649                 k = key[len(prefix):]
00650                 v = REQUEST.form.get(key)
00651                 self.bindTemplate(k, v)
00652 
00653         add = REQUEST.get('addTemplate')
00654         name = REQUEST.get('newTemplate')
00655         if add and name:
00656             self.registerTemplate(name)
00657 
00658         return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_templateForm')
00659     
00660     security.declareProtected(permissions.View, 'typeImplementsInterfaces')
00661     def typeImplementsInterfaces(self, type, interfaces):
00662         """Checks if an type uses one of the given interfaces.
00663         """
00664         if isinstance(type, dict) and type.has_key('klass'):
00665             type = type['klass']
00666         for iface in interfaces:
00667             res = iface.isImplementedByInstancesOf(type)
00668             if res:
00669                 return True
00670         return False
00671     
00672     security.declareProtected(permissions.View, 'isTemplateEnabled')
00673     def isTemplateEnabled(self, type):
00674         """Checks if an type uses ITemplateMixin.
00675         """
00676         return self.typeImplementsInterfaces(type, [ITemplateMixin])
00677         
00678     security.declareProtected(permissions.View, 'listTemplateEnabledPortalTypes')
00679     def listTemplateEnabledPortalTypes(self):
00680         """Return a list of portal_types with ITemplateMixin
00681         """
00682         return self.listPortalTypesWithInterfaces([ITemplateMixin])
00683         
00684     security.declareProtected(permissions.View, 'listPortalTypesWithInterfaces')
00685     def listPortalTypesWithInterfaces(self, ifaces):
00686         """Returns a list of ftis of which the types implement one of
00687         the given interfaces.  Only returns AT types.
00688 
00689         Get a list of FTIs of types implementing IReferenceable:
00690         >>> tool = getToolByName(self.portal, TOOL_NAME)
00691         >>> meth = tool.listPortalTypesWithInterfaces
00692         >>> ftis = tool.listPortalTypesWithInterfaces([IReferenceable])
00693         
00694         Sort the type ids and print them:
00695         >>> type_ids = [fti.getId() for fti in ftis]
00696         >>> type_ids.sort()
00697         >>> type_ids
00698         ['ATBIFolder', 'ComplexType', ...]
00699         """
00700         pt = getToolByName(self, 'portal_types')
00701         value = []
00702         for data in listTypes():
00703             klass = data['klass']
00704             for iface in ifaces:
00705                 if iface.isImplementedByInstancesOf(klass):
00706                     ti = pt.getTypeInfo(data['portal_type'])
00707                     if ti is not None:
00708                         value.append(ti)
00709         return value
00710 
00711     # Type/Schema Management
00712     security.declareProtected(permissions.View, 'listRegisteredTypes')
00713     def listRegisteredTypes(self, inProject=False, portalTypes=False):
00714         """Return the list of sorted types.
00715         """
00716 
00717         def type_sort(a, b):
00718             v = cmp(a['package'], b['package'])
00719             if v != False: return v
00720             c = cmp(a['klass'].__class__.__name__,
00721                     b['klass'].__class__.__name__)
00722 
00723             if c == False:
00724                 return cmp(a['package'], b['package'])
00725             return c
00726 
00727         values = listTypes()
00728         values.sort(type_sort)
00729 
00730         if inProject:
00731             # portal_type can change (as it does after ATCT-migration), so we
00732             # need to check against the content_meta_type of each type-info
00733             ttool = getToolByName(self, 'portal_types')
00734             types = [ti.Metatype() for ti in ttool.listTypeInfo()]
00735            if portalTypes:
00736                 values = [v for v in values if v['portal_type'] in types]
00737             else:
00738                 values = [v for v in values if v['meta_type'] in types]
00739 
00740         return values
00741 
00742     security.declareProtected(permissions.View, 'getTypeSpec')
00743     def getTypeSpec(self, package, type):
00744         t = self.lookupType(package, type)
00745         module = t['klass'].__module__
00746         klass = t['name']
00747         return '%s.%s' % (module, klass)
00748 
00749     security.declareProtected(permissions.View, 'listTypes')
00750     def listTypes(self, package=None, type=None):
00751         """Just the class.
00752         """
00753         if type is None:
00754             return [t['klass'] for t in listTypes(package)]
00755         else:
00756             return [getType(type, package)['klass']]
00757 
00758     security.declareProtected(permissions.View, 'lookupType')
00759     def lookupType(self, package, type):
00760         types = self.listRegisteredTypes()
00761         for t in types:
00762             if t['package'] != package:
00763                 continue
00764             if t['meta_type'] == type:
00765                 # We have to return the schema wrapped into the acquisition of
00766                 # something to allow access. Otherwise we will end up with:
00767                 # Your user account is defined outside the context of the object
00768                 # being accessed.
00769                 t['schema'] = ImplicitAcquisitionWrapper(t['schema'], self)
00770                 return t
00771         return None
00772 
00773     security.declareProtected(permissions.ManagePortal,
00774                               'manage_installType')
00775     def manage_installType(self, typeName, package=None,
00776                            uninstall=None, REQUEST=None):
00777         """Un/Install a type TTW.
00778         """
00779         typesTool = getToolByName(self, 'portal_types')
00780         try:
00781             typesTool._delObject(typeName)
00782         except (ConflictError, KeyboardInterrupt):
00783             raise
00784         except: # XXX bare exception
00785             pass
00786         if uninstall is not None:
00787             if REQUEST:
00788                 return REQUEST.RESPONSE.redirect(self.absolute_url() +
00789                                                  '/manage_debugForm')
00790             return
00791 
00792         typeinfo_name = '%s: %s' % (package, typeName)
00793 
00794         # We want to run the process/modify_fti code which might not
00795         # have been called
00796         typeDesc = getType(typeName, package)
00797         process_types([typeDesc], package)
00798         klass = typeDesc['klass']
00799         
00800         # get the meta type of the FTI from the class, use the default FTI as default
00801         fti_meta_type = getattr(klass, '_at_fti_meta_type', None)
00802         if fti_meta_type in (None, 'simple item'):
00803             fti_meta_type = FactoryTypeInformation.meta_type
00804 
00805         typesTool.manage_addTypeInformation(fti_meta_type,
00806                                             id=typeName,
00807                                             typeinfo_name=typeinfo_name)
00808         t = getattr(typesTool, typeName, None)
00809         if t:
00810             t.title = getattr(klass, 'archetype_name',
00811                               typeDesc['portal_type'])
00812 
00813         # and update the actions as needed
00814         fixActionsForType(klass, typesTool)
00815 
00816         if REQUEST:
00817             return REQUEST.RESPONSE.redirect(self.absolute_url() +
00818                                              '/manage_debugForm')
00819 
00820     security.declarePublic('getSearchWidgets')
00821     def getSearchWidgets(self, package=None, type=None,
00822                          context=None, nosort=None):
00823         """Empty widgets for searching.
00824         """
00825         return self.getWidgets(package=package, type=type,
00826                                context=context, mode='search', nosort=nosort)
00827 
00828     security.declarePublic('getWidgets')
00829     def getWidgets(self, instance=None,
00830                    package=None, type=None,
00831                    context=None, mode='edit',
00832                    fields=None, schemata=None, nosort=None):
00833         """Empty widgets for standalone rendering.
00834         """
00835         widgets = []
00836         w_keys = {}
00837         context = context is not None and context or self
00838         instances = instance is not None and [instance] or []
00839         f_names = fields
00840         if not instances:
00841             for t in self.listTypes(package, type):
00842                 instance = t('fake_instance')
00843                 instance._at_is_fake_instance = True
00844                 wrapped = instance.__of__(context)
00845                 wrapped.initializeArchetype()
00846                 instances.append(wrapped)
00847         for instance in instances:
00848             if schemata is not None:
00849                 schema = instance.Schemata()[schemata].copy()
00850             else:
00851                 schema = instance.Schema().copy()
00852             fields = schema.fields()
00853             if mode == 'search':
00854                 # Include only fields which have an index
00855                 # XXX duplicate fieldnames may break this,
00856                 # as different widgets with the same name
00857                 # on different schemas means only the first
00858                 # one found will be used
00859                 indexes=self.portal_catalog.indexes()
00860                 fields = [f for f in fields
00861                           if (f.accessor and
00862                               not w_keys.has_key(f.accessor)
00863                               and f.accessor in indexes)]
00864             if f_names is not None:
00865                 fields = filter(lambda f: f.getName() in f_names, fields)
00866             for field in fields:
00867                 widget = field.widget
00868                 field_name = field.getName()
00869                 accessor = field.getAccessor(instance)
00870                 if mode == 'search':
00871                     field.required = False
00872                     field.addable = False # for ReferenceField
00873                     if not isinstance(field.vocabulary, DisplayList):
00874                         field.vocabulary = field.Vocabulary(instance)
00875                     if '' not in field.vocabulary.keys():
00876                         field.vocabulary = DisplayList([('', _(u'at_search_any', default=u'<any>'))]) + \
00877                                            field.vocabulary
00878                     widget.populate = False
00879                     field_name = field.accessor
00880                     # accessor must be a method which doesn't take an argument
00881                     # this lambda is facking an accessor
00882                     accessor = lambda: field.getDefault(instance)
00883 
00884                 w_keys[field_name] = None
00885                 widgets.append((field_name, WidgetWrapper(
00886                     field_name=field_name,
00887                     mode=mode,
00888                     widget=widget,
00889                     instance=instance,
00890                     field=field,
00891                     accessor=accessor)))
00892         if mode == 'search' and nosort == None:
00893             widgets.sort()
00894         return [widget for name, widget in widgets]
00895 
00896     security.declarePrivate('_rawEnum')
00897     def _rawEnum(self, callback, *args, **kwargs):
00898         """Finds all object to check if they are 'referenceable'.
00899         """
00900         catalog = getToolByName(self, 'portal_catalog')
00901         brains = catalog(id=[])
00902         for b in brains:
00903             o = b.getObject()
00904             if o is not None:
00905                 if IBaseObject.isImplementedBy(o):
00906                     callback(o, *args, **kwargs)
00907             else:
00908                 log('no object for brain: %s:%s' % (b,b.getURL()))
00909 
00910 
00911     security.declareProtected(permissions.View, 'enum')
00912     def enum(self, callback, *args, **kwargs):
00913         catalog = getToolByName(self, UID_CATALOG)
00914         keys = catalog.uniqueValuesFor('UID')
00915         for uid in keys:
00916             o = self.getObject(uid)
00917             if o:
00918                 callback(o, *args, **kwargs)
00919             else:
00920                 log('No object for %s' % uid)
00921 
00922 
00923     security.declareProtected(permissions.View, 'Content')
00924     def Content(self):
00925         """Return a list of all the content ids.
00926         """
00927         catalog = getToolByName(self, UID_CATALOG)
00928         keys = catalog.uniqueValuesFor('UID')
00929         results = catalog(UID=keys)
00930         return results
00931 
00932 
00933     ## Management Forms
00934     security.declareProtected(permissions.ManagePortal,
00935                               'manage_doGenerate')
00936     def manage_doGenerate(self, sids=(), REQUEST=None):
00937         """(Re)generate types.
00938         """
00939         schemas = []
00940         for sid in sids:
00941             schemas.append(self.getSchema(sid))
00942 
00943         for s in schemas:
00944             s.generate()
00945 
00946         if REQUEST:
00947             return REQUEST.RESPONSE.redirect(self.absolute_url() +
00948                                              '/manage_workspace')
00949 
00950     security.declareProtected(permissions.ManagePortal,
00951                               'manage_inspect')
00952     def manage_inspect(self, UID, REQUEST=None):
00953         """Dump some things about an object hook in the debugger for now.
00954         """
00955         object = self.getObject(UID)
00956         log(object, object.Schema(), dir(object))
00957 
00958         return REQUEST.RESPONSE.redirect(self.absolute_url() +
00959                                          '/manage_uids')
00960 
00961     security.declareProtected(permissions.ManagePortal,
00962                               'manage_reindex')
00963     def manage_reindex(self, REQUEST=None):
00964         """Assign UIDs to all basecontent objects.
00965         """
00966 
00967         def _index(object, archetype_tool):
00968             archetype_tool.registerContent(object)
00969 
00970         self._rawEnum(_index, self)
00971 
00972         return REQUEST.RESPONSE.redirect(self.absolute_url() +
00973                                          '/manage_uids')
00974 
00975     security.declareProtected(permissions.ManagePortal, 'index')
00976     index = manage_reindex
00977 
00978     def _listAllTypes(self):
00979         """List all types -- either currently known or known to us.
00980         """
00981         allTypes = _types.copy(); allTypes.update(self._types)
00982         return allTypes.keys()
00983 
00984     security.declareProtected(permissions.ManagePortal,
00985                               'getChangedSchema')
00986     def getChangedSchema(self):
00987         """Returns a list of tuples indicating which schema have changed.
00988 
00989         Tuples have the form (schema, changed).
00990         """
00991         list = []
00992         currentTypes = _types
00993         ourTypes = self._types
00994         modified = False
00995         keys = self._listAllTypes()
00996         keys.sort()
00997         for t in keys:
00998             if t not in ourTypes:
00999                 # Add it
01000                 ourTypes[t] = currentTypes[t]['signature']
01001                 modified = True
01002                 list.append((t, 0))
01003             elif t not in currentTypes:
01004                 # Huh: what shall we do? We remove it -- this might be wrong!
01005                 del ourTypes[t]
01006                 modified = True
01007                 # We do not add an entry because we cannot update
01008                 # these objects (having no longer type information for them)
01009             else:
01010                 list.append((t, ourTypes[t] != currentTypes[t]['signature']))
01011         if modified:
01012             self._p_changed = True
01013         return list
01014 
01015 
01016     security.declareProtected(permissions.ManagePortal,
01017                               'manage_updateSchema')
01018     def manage_updateSchema(self, REQUEST=None, update_all=None,
01019                             remove_instance_schemas=None):
01020         """Make sure all objects' schema are up to date.
01021         """
01022         out = StringIO()
01023         print >> out, 'Updating schema...'
01024 
01025         update_types = []
01026         if REQUEST is None:
01027             # DM (avoid persistency bug): avoid code duplication
01028             update_types = [ti[0] for ti in self.getChangedSchema() if ti[1]]
01029         else:
01030             # DM (avoid persistency bug):
01031             for t in self._listAllTypes():
01032                 if REQUEST.form.get(t, False):
01033                     update_types.append(t)
01034             update_all = REQUEST.form.get('update_all', False)
01035             remove_instance_schemas = REQUEST.form.get(
01036                 'remove_instance_schemas', False)
01037 
01038         # XXX: Enter this block only when there are types to update!
01039         if update_types:
01040             # Use the catalog's ZopeFindAndApply method to walk through
01041             # all objects in the portal.  This works much better than
01042             # relying on the catalog to find objects, because an object
01043             # may be uncatalogable because of its schema, and then you
01044             # can't update it if you require that it be in the catalog.
01045             catalog = getToolByName(self, 'portal_catalog')
01046             portal = getToolByName(self, 'portal_url').getPortalObject()
01047             meta_types = [_types[t]['meta_type'] for t in update_types]
01048             if remove_instance_schemas:
01049                 func_update_changed = self._removeSchemaAndUpdateChangedObject
01050                 func_update_all = self._removeSchemaAndUpdateObject
01051             else:
01052                 func_update_changed = self._updateChangedObject
01053                 func_update_all = self._updateObject
01054             if update_all:
01055                 catalog.ZopeFindAndApply(portal, obj_metatypes=meta_types,
01056                     search_sub=True, apply_func=func_update_all)
01057             else:
01058                 catalog.ZopeFindAndApply(portal, obj_metatypes=meta_types,
01059                     search_sub=True, apply_func=func_update_changed)
01060             for t in update_types:
01061                 self._types[t] = _types[t]['signature']
01062             self._p_changed = True
01063 
01064         print >> out, 'Done.'
01065         return out.getvalue()
01066 
01067     # A counter to ensure that in a given interval a subtransaction
01068     # commit is done.
01069     subtransactioncounter = 0
01070 
01071     def _updateObject(self, o, path, remove_instance_schemas=None):
01072         o._updateSchema(remove_instance_schemas=remove_instance_schemas)
01073         # Subtransactions to avoid eating up RAM when used inside a
01074         # 'ZopeFindAndApply' like in manage_updateSchema
01075         self.subtransactioncounter += 1
01076         # Only every 250 objects a sub-commit, otherwise it eats up all diskspace
01077         if not self.subtransactioncounter % 250:
01078             transaction.savepoint(optimistic=True)
01079 
01080     def _updateChangedObject(self, o, path):
01081         if not o._isSchemaCurrent():
01082             self._updateObject(o, path)
01083 
01084     def _removeSchemaAndUpdateObject(self, o, path):
01085         self._updateObject(o, path, remove_instance_schemas=True)
01086 
01087     def _removeSchemaAndUpdateChangedObject(self, o, path):
01088         if not o._isSchemaCurrent():
01089             self._removeSchemaAndUpdateObject(o, path)
01090 
01091     security.declareProtected(permissions.ManagePortal,
01092                               'manage_updateSchema')
01093     def manage_migrate(self, REQUEST=None):
01094         """Run Extensions.migrations.migrate.
01095         """
01096         from Products.Archetypes.Extensions.migrations import migrate
01097         out = migrate(self)
01098         self.manage_updateSchema()
01099         return out
01100 
01101     # Catalog management
01102     security.declareProtected(permissions.View,
01103                               'listCatalogs')
01104     def listCatalogs(self):
01105         """Show the catalog mapping.
01106         """
01107         return self.catalog_map
01108 
01109     security.declareProtected(permissions.ManagePortal,
01110                               'manage_updateCatalogs')
01111     def manage_updateCatalogs(self, REQUEST=None):
01112         """Set the catalog map for meta_type to include the list
01113         catalog_names.
01114         """
01115         prefix = 'catalog_names_'
01116         for key in REQUEST.form.keys():
01117             if key.startswith(prefix):
01118                 k = key[len(prefix):]
01119                 v = REQUEST.form.get(key)
01120                 self.setCatalogsByType(k, v)
01121 
01122         return REQUEST.RESPONSE.redirect(self.absolute_url() +
01123                                          '/manage_catalogs')
01124 
01125     security.declareProtected(permissions.ManagePortal,
01126                               'setCatalogsByType')
01127     def setCatalogsByType(self, portal_type, catalogList):
01128         """ associate catalogList with meta_type. (unfortunally not portal_type).
01129         
01130             catalogList is a list of strings with the ids of the catalogs.
01131             Each catalog is has to be a tool, means unique in site root.
01132         """
01133         self.catalog_map[portal_type] = catalogList
01134 
01135 
01136     security.declareProtected(permissions.View, 'getCatalogsByType')
01137     def getCatalogsByType(self, portal_type):
01138         """Return the catalog objects assoicated with a given type.
01139         """
01140         catalogs = []
01141         catalog_map = getattr(self, 'catalog_map', None)
01142         if catalog_map is not None:
01143             names = self.catalog_map.get(portal_type, ['portal_catalog'])
01144         else:
01145             names = ['portal_catalog']
01146         portal = getToolByName(self, 'portal_url').getPortalObject()
01147         for name in names:
01148             try:
01149                 catalogs.append(getToolByName(portal, name))
01150             except (ConflictError, KeyboardInterrupt):
01151                 raise
01152             except Exception, E:
01153                 log('No tool', name, E)
01154                 pass
01155         return catalogs
01156 
01157     security.declareProtected(permissions.View, 'getCatalogsInSite')
01158     def getCatalogsInSite(self):
01159         """Return a list of ids for objects implementing ZCatalog.
01160         """
01161         portal = getToolByName(self, 'portal_url').getPortalObject()
01162         res = []
01163         for object in portal.objectValues():
01164             if ICatalogTool.isImplementedBy(object):
01165                 res.append(object.getId())
01166                 continue
01167             if IZCatalog.isImplementedBy(object):
01168                 res.append(object.getId())
01169                 continue
01170 
01171         res.sort()
01172 
01173         return res
01174 
01175     security.declareProtected(permissions.View, 'visibleLookup')
01176     def visibleLookup(self, field, vis_key, vis_value='visible'):
01177         """Checks the value of a specific key in the field widget's
01178         'visible' dictionary.
01179 
01180         Returns True or False so it can be used within a lambda as
01181         the predicate for a filterFields call.
01182         """
01183         vis_dict = field.widget.visible
01184         value = ''
01185         if vis_dict.has_key(vis_key):
01186             value = field.widget.visible[vis_key]
01187         if value == vis_value:
01188             return True
01189         else:
01190             return False
01191 
01192     def lookupObject(self,uid):
01193         deprecated('ArchetypeTool.lookupObject is deprecated')
01194         return self.reference_catalog.lookupObject(uid)
01195 
01196     getObject = lookupObject
01197 
01198     def has_graphviz(self):
01199         """Runtime check for graphviz, used in condition on tab.
01200         """
01201         return HAS_GRAPHVIZ
01202 
01203 InitializeClass(ArchetypeTool)