Back to index

plone3  3.1.7
utils.py
Go to the documentation of this file.
00001 import os
00002 from os.path import isdir, join
00003 
00004 from Globals import package_home
00005 from OFS.ObjectManager import BadRequestException
00006 from Products.CMFCore.ActionInformation import ActionInformation
00007 from Products.CMFCore.DirectoryView import addDirectoryViews, \
00008      registerDirectory, manage_listAvailableDirectories
00009 from Products.CMFCore.utils import getToolByName, getPackageName
00010 from Products.Archetypes.config import REFERENCE_CATALOG
00011 from Products.Archetypes.ArchetypeTool import fixActionsForType
00012 from Products.Archetypes.ArchetypeTool import listTypes
00013 from Products.Archetypes.ArchetypeTool import process_types
00014 from Products.Archetypes.ArchetypeTool import base_factory_type_information
00015 from Products.Archetypes import types_globals
00016 from Products.Archetypes.interfaces.base import IBaseObject
00017 from Products.Archetypes.interfaces.ITemplateMixin import ITemplateMixin
00018 
00019 
00020 class Extra:
00021     """indexes extra properties holder"""
00022 
00023 def install_additional_templates(self, out, types):
00024     """Registers additionals templates for TemplateMixin classes.
00025     """
00026     at = getToolByName(self, 'archetype_tool')
00027     
00028     for t in types:
00029         klass = t['klass']
00030         if ITemplateMixin.isImplementedByInstancesOf(klass):
00031             portal_type = klass.portal_type
00032             default_view = getattr(klass, 'default_view', 'base_view')
00033             suppl_views = getattr(klass, 'suppl_views', ())
00034             views = []
00035 
00036             if not default_view:
00037                 default_view = 'base_view'
00038 
00039             at.registerTemplate(default_view)
00040             views.append(default_view)
00041 
00042             for view in suppl_views:
00043                 at.registerTemplate(view)
00044                 views.append(view)
00045 
00046             at.bindTemplate(portal_type, views)
00047 
00048 def install_subskin(self, out, globals=types_globals, product_skins_dir='skins'):
00049     skinstool=getToolByName(self, 'portal_skins')
00050 
00051     product = getPackageName(globals)
00052     registry_key = "%s:%s" % (product, product_skins_dir)
00053     registered_directories = manage_listAvailableDirectories()
00054     if registry_key not in registered_directories:
00055         try:
00056             registerDirectory(product_skins_dir, globals)
00057         except OSError, ex:
00058             if ex.errno == 2: # No such file or directory
00059                 return
00060             raise
00061     try:
00062         addDirectoryViews(skinstool, product_skins_dir, globals)
00063     except BadRequestException, e:
00064         # TODO: find a better way to do this, but that seems not feasible
00065         #      until Zope stops using string exceptions
00066         if str(e).endswith(' is reserved.'):
00067             # trying to use a reserved identifier, let the user know
00068             #
00069             # remember the cmf reserve ids of objects in the root of the
00070             # portal !
00071             raise
00072         # directory view has already been added
00073         pass
00074 
00075     fullProductSkinsPath = os.path.join(package_home(globals), product_skins_dir)
00076     files = os.listdir(fullProductSkinsPath)
00077     for productSkinName in files:
00078         # skip directories with a dot or special dirs
00079         # or maybe just startswith('.')?
00080         if '.' in productSkinName or productSkinName in ('CVS', '_svn', '{arch}'):
00081             continue
00082         if isdir(join(fullProductSkinsPath, productSkinName)):
00083             for skinName in skinstool.getSkinSelections():
00084                 path = skinstool.getSkinPath(skinName)
00085                 path = [i.strip() for i in  path.split(',')]
00086                 try:
00087                     if productSkinName not in path:
00088                         path.insert(path.index('custom') +1, productSkinName)
00089                 except ValueError:
00090                     if productSkinName not in path:
00091                         path.append(productSkinName)
00092                 path = ','.join(path)
00093                 skinstool.addSkinSelection(skinName, path)
00094 
00095 def install_types(self, out, types, package_name):
00096     typesTool = getToolByName(self, 'portal_types')
00097     folderish = []
00098     for klass in types:
00099         try:
00100             typesTool._delObject(klass.portal_type)
00101         except:
00102             pass
00103 
00104         typeinfo_name = "%s: %s (%s)" % (package_name, klass.__name__,
00105                                          klass.meta_type)
00106 
00107         # get the meta type of the FTI from the class, use the
00108         # default FTI as default
00109         fti_meta_type = getattr(klass, '_at_fti_meta_type', None)
00110         if not fti_meta_type or fti_meta_type == 'simple item':
00111             ## rr: explicitly catching 'simple item' because
00112             ## CMF 2.0 removed the meta_type from the basic TIs :-(
00113             ## seems to me, 'manage_addTypeInformation' is just broken
00114             fti_meta_type = 'Factory-based Type Information'
00115         try:
00116             typesTool.manage_addTypeInformation(fti_meta_type,
00117                                                 id=klass.portal_type,
00118                                                 typeinfo_name=typeinfo_name)
00119         except ValueError:
00120             print "failed to add '%s'" %  klass.portal_type
00121             print "fti_meta_type = %s" % fti_meta_type
00122         ## rr: from CMF-2.0 onward typeinfo_name from the call above
00123         ## is ignored and we have to do some more work
00124         t, fti = _getFtiAndDataFor(typesTool, klass.portal_type, klass.__name__, package_name)
00125         if t and fti:
00126             t.manage_changeProperties(**fti)
00127             if fti.has_key('aliases'):
00128                 t.setMethodAliases(fti['aliases'])
00129         
00130         # Set the human readable title explicitly
00131         if t:
00132             t.title = klass.archetype_name
00133 
00134         # If the class appears folderish and the 'use_folder_tabs' is
00135         # not set to a false value, then we add the portal_type to
00136         # Plone's 'use_folder_tabs' property
00137         use_folder_tabs = klass.isPrincipiaFolderish and \
00138                           getattr(klass, 'use_folder_tabs', 1)
00139         if use_folder_tabs:
00140             folderish.append(klass.portal_type)
00141     if folderish:
00142         pt = getToolByName(self, 'portal_properties', None)
00143         if pt is None:
00144             return
00145         sp = getattr(pt, 'site_properties', None)
00146         if sp is None:
00147             return
00148         props = ('use_folder_tabs', 'typesLinkToFolderContentsInFC')
00149         for prop in props:
00150             folders = sp.getProperty(prop, None)
00151             if folders is None:
00152                 continue
00153             folders = list(folders)
00154             folders.extend(folderish)
00155             folders = tuple(dict(zip(folders, folders)).keys())
00156             sp._updateProperty(prop, folders)
00157 
00158 def _getFtiAndDataFor(tool, typename, klassname, package_name):
00159     """helper method for type info setting
00160        returns fti object from the types tool and the data created
00161        by process_types for the fti
00162     """
00163     t = getattr(tool, typename, None)
00164     if t is None:
00165         return None, None
00166     all_ftis = process_types(listTypes(package_name),
00167                              package_name)[2]
00168     for fti in all_ftis:
00169         if fti['id'] == klassname:
00170             fti['content_meta_type'] = fti['meta_type']
00171             return t, fti
00172     return t, None
00173     
00174 
00175 def install_actions(self, out, types):
00176     typesTool = getToolByName(self, 'portal_types')
00177     for portal_type in types:
00178         ## rr: XXX TODO somehow the following doesn't do anymore what
00179         ## it used to do :-(
00180         fixActionsForType(portal_type, typesTool)
00181 
00182 def install_indexes(self, out, types):
00183     portal_catalog = catalog = getToolByName(self, 'portal_catalog')
00184     for cls in types:
00185         if 'indexes' not in cls.installMode:
00186             continue
00187 
00188         for field in cls.schema.fields():
00189             if not field.index:
00190                 continue
00191 
00192             if isinstance(field.index, basestring):
00193                 index = (field.index,)
00194             elif isinstance(field.index, (tuple, list)):
00195                 index = field.index
00196             else:
00197                 raise SyntaxError("Invalid Index Specification %r"
00198                                   % field.index)
00199 
00200             for alternative in index:
00201                 installed = None
00202                 index_spec = alternative.split(':', 1)
00203                 use_column  = 0
00204                 if len(index_spec) == 2 and index_spec[1] in ('schema', 'brains'):
00205                     use_column = 1
00206                 index_spec = index_spec[0]
00207 
00208                 accessor = field.getIndexAccessorName()
00209 
00210                 parts = index_spec.split('|')
00211                 # we want to be able to specify which catalog we want to use
00212                 # for each index. syntax is
00213                 # index=('member_catalog/:schema',)
00214                 # portal catalog is used by default if not specified
00215                 if parts[0].find('/') > 0:
00216                     str_idx = parts[0].find('/')
00217                     catalog_name = parts[0][:str_idx]
00218                     parts[0] = parts[0][str_idx+1:]
00219                     catalog = getToolByName(self, catalog_name)
00220                 else:
00221                     catalog = portal_catalog
00222                 
00223                 #####################
00224                 # add metadata column 
00225                 
00226                 # lets see if the catalog is itself an Archetype:
00227                 isArchetype = IBaseObject.isImplementedBy(catalog)
00228                 # archetypes based zcatalogs need to provide a different method 
00229                 # to list its schema-columns to not conflict with archetypes 
00230                 # schema                
00231                 hasNewWayMethod = hasattr(catalog, 'zcschema')
00232                 hasOldWayMethod = not isArchetype and hasattr(catalog, 'schema')
00233                 notInNewWayResults = hasNewWayMethod and accessor not in catalog.zcschema()
00234                 notInOldWayResults = hasOldWayMethod and accessor not in catalog.schema()
00235                 if use_column and (notInNewWayResults or notInOldWayResults):
00236                     try:
00237                         catalog.addColumn(accessor)
00238                     except:
00239                         import traceback
00240                         traceback.print_exc(file=out)
00241 
00242                 ###########
00243                 # add index
00244                 
00245                 # if you want to add a schema field without an index
00246                 #if not parts[0]:
00247                 #    continue
00248 
00249                 for itype in parts:
00250                     extras = itype.split(',')
00251                     if len(extras) > 1:
00252                         itype = extras[0]
00253                         props = Extra()
00254                         for extra in extras[1:]:
00255                             name, value = extra.split('=')
00256                             setattr(props, name.strip(), value.strip())
00257                     else:
00258                         props = None
00259                     try:
00260                         #Check for the index and add it if missing
00261                         catalog.addIndex(accessor, itype,
00262                                          extra=props)
00263                         catalog.manage_reindexIndex(ids=(accessor,))
00264                     except:
00265                         # FIXME: should only catch "Index Exists"
00266                         # damned string exception !
00267                         pass
00268                     else:
00269                         installed = 1
00270                         break
00271 
00272                 if installed:
00273                     break
00274 
00275 
00276 def isPloneSite(self):
00277     # we should just define a single attr for this
00278     if self.__class__.__name__ == "PloneSite":
00279         return 1
00280     for base in self.__class__.__bases__:
00281         if base.__name__ == "PloneSite":
00282             return 1
00283     if 'plone_utils' in self.objectIds():
00284         # Possibly older PloneSite
00285         # It may be risky to assert this, but the user should
00286         # have upgrade anyway, so its his fault :)
00287         return 1
00288     return 0
00289 
00290 
00291 def filterTypes(self, out, types, package_name):
00292     typesTool = getToolByName(self, 'portal_types')
00293 
00294     filtered_types = []
00295 
00296     for rti in types:
00297         t = rti['klass']
00298         name = rti['name']
00299         meta_type = rti['meta_type']
00300 
00301         isBaseObject = 0
00302         if IBaseObject.isImplementedByInstancesOf(t):
00303             isBaseObject = 1
00304         else:
00305             for k in t.__bases__:
00306                 if IBaseObject.isImplementedByInstancesOf(k):
00307                     isBaseObject = 1
00308                     break
00309 
00310         if isBaseObject:
00311             filtered_types.append(t)
00312         else:
00313             print >> out, ("%s doesnt implements IBaseObject. "
00314                            "Possible misconfiguration. "
00315                            "Check if your class has an "
00316                            "'__implements__ = IBaseObject' "
00317                            "(or IBaseContent, or IBaseFolder)" % repr(t))
00318 
00319     return filtered_types
00320 
00321 def setupEnvironment(self, out, types,
00322                      package_name,
00323                      globals=types_globals,
00324                      product_skins_dir='skins',
00325                      require_dependencies=True,
00326                      install_deps=1):
00327 
00328     if install_deps:
00329         qi=getToolByName(self, 'portal_quickinstaller', None)
00330         if require_dependencies:
00331             if not qi.isProductInstalled('CMFFormController'):
00332                 qi.installProduct('CMFFormController',locked=1)
00333                 print >>out, 'Installing CMFFormController'
00334             if not qi.isProductInstalled('MimetypesRegistry'):
00335                 qi.installProduct('MimetypesRegistry')
00336                 print >>out, 'Installing MimetypesRegistry'
00337             if not qi.isProductInstalled('PortalTransforms'):
00338                 qi.installProduct('PortalTransforms')
00339                 print >>out, 'Installing PortalTransforms'
00340             if not qi.isProductInstalled('Archetypes'):
00341                 qi.installProduct('Archetypes')
00342                 print >>out, 'Installing Archetypes'
00343 
00344     if product_skins_dir:
00345         install_subskin(self, out, globals, product_skins_dir)
00346 
00347     install_additional_templates(self, out, types)
00348 
00349     ftypes = filterTypes(self, out, types, package_name)
00350     install_indexes(self, out, ftypes)
00351     install_actions(self, out, ftypes)
00352 
00353 def doubleCheckDefaultTypeActions(self, ftypes):
00354     # rr: for some reason, AT's magic wrt adding the default type actions
00355     # stopped working when moving to CMF-2.0
00356     # Instead of trying to resurect the old way (which I tried but couldn't)
00357     # I make it brute force here
00358 
00359     typesTool = getToolByName(self, 'portal_types')
00360     defaultTypeActions = [ActionInformation(**action) for action in
00361                           base_factory_type_information[0]['actions']]
00362 
00363     for ftype in ftypes:
00364         portal_type = ftype.portal_type
00365         fti = typesTool.get(portal_type, None)
00366         if fti is None:
00367             continue
00368         actions = list(fti._actions)
00369         action_ids = [a.id for a in actions]
00370         prepend = []
00371         for a in defaultTypeActions:
00372             if a.id not in action_ids:
00373                 prepend.append(a.clone())
00374         if prepend:
00375             fti._actions = tuple(prepend + actions)
00376     
00377 
00378 ## The master installer
00379 def installTypes(self, out, types, package_name,
00380                  globals=types_globals, product_skins_dir='skins',
00381                  require_dependencies=True, refresh_references=False,
00382                  install_deps=True):
00383     """Use this for your site with your types"""
00384     ftypes = filterTypes(self, out, types, package_name)
00385     install_types(self, out, ftypes, package_name)
00386     # Pass the unfiltered types into setup as it does that on its own
00387     setupEnvironment(self, out, types, package_name,
00388                      globals, product_skins_dir, require_dependencies,
00389                      install_deps)
00390     ## rr: sometimes the default actions are still missing
00391     doubleCheckDefaultTypeActions(self, ftypes)
00392     if refresh_references and ftypes:
00393         rc = getToolByName(self, REFERENCE_CATALOG)
00394         rc.manage_rebuildCatalog()
00395 
00396 def refreshReferenceCatalog(self, out, types=None, package_name=None, ftypes=None):
00397     """refresh the reference catalog to reindex objects after reinstalling a
00398     AT based product.
00399     
00400     This may take a very long time but it seems to be required under some
00401     circumstances.
00402     """
00403     assert package_name
00404 
00405     if ftypes is None:
00406         ftypes = filterTypes(self, out, types, package_name)
00407 
00408     if not ftypes and not types:
00409         # no types to install
00410         return
00411 
00412     rc = getToolByName(self, REFERENCE_CATALOG)
00413     mt = tuple([t.meta_type for t in ftypes])
00414     
00415     # because manage_catalogFoundItems sucks we have to do it on our own ...
00416     func    = rc.catalog_object
00417     obj     = self
00418     path    = '/'.join(obj.getPhysicalPath())
00419     REQUEST = self.REQUEST
00420 
00421     rc.ZopeFindAndApply(obj,
00422                         obj_metatypes=mt,
00423                         search_sub=1,
00424                         REQUEST=REQUEST,
00425                         apply_func=func,
00426                         apply_path=path)