Back to index

plone3  3.1.7
UIDCatalog.py
Go to the documentation of this file.
00001 import os
00002 import sys
00003 import time
00004 import urllib
00005 import traceback
00006 from zope.interface import implements
00007 
00008 from Globals import InitializeClass
00009 from Globals import DTMLFile
00010 from ExtensionClass import Base
00011 from ZODB.POSException import ConflictError
00012 from zExceptions import NotFound
00013 from AccessControl import ClassSecurityInfo
00014 from AccessControl.Permissions import manage_zcatalog_entries as ManageZCatalogEntries
00015 from Acquisition import aq_base, aq_parent, aq_inner
00016 from Products.ZCatalog.ZCatalog import ZCatalog
00017 from Products.ZCatalog.Catalog import Catalog
00018 from Products.ZCatalog.CatalogBrains import AbstractCatalogBrain
00019 from Products import CMFCore
00020 from Products.CMFCore.utils import UniqueObject
00021 from Products.CMFCore.utils import getToolByName
00022 from Products.Archetypes.config import UID_CATALOG
00023 from Products.Archetypes.config import TOOL_NAME
00024 from Products.Archetypes.debug import log
00025 from Products.Archetypes.interfaces import IUIDCatalog
00026 from Products.Archetypes.utils import getRelURL
00027 
00028 _catalog_dtml = os.path.join(os.path.dirname(CMFCore.__file__), 'dtml')
00029 
00030 def manage_addUIDCatalog(self, id, title,
00031                          vocab_id=None, # Deprecated
00032                          REQUEST=None):
00033     """Add the UID Catalog
00034     """
00035     id = str(id)
00036     title = str(title)
00037     c = UIDCatalog(id, title, vocab_id, self)
00038     self._setObject(id, c)
00039 
00040     if REQUEST is not None:
00041         return self.manage_main(self, REQUEST,update_menu=1)
00042 
00043 
00044 class PluggableCatalog(Catalog):
00045     # Catalog overrides
00046     # smarter brains, squirrely traversal
00047 
00048     security = ClassSecurityInfo()
00049     # XXX FIXME more security
00050 
00051     def useBrains(self, brains):
00052         """Tricky brains overrides, we need to use our own class here
00053         with annotation support
00054         """
00055         class plugbrains(self.BASE_CLASS, brains):
00056             pass
00057 
00058         schema = self.schema
00059         scopy = schema.copy()
00060 
00061         scopy['data_record_id_']=len(schema.keys())
00062         scopy['data_record_score_']=len(schema.keys())+1
00063         scopy['data_record_normalized_score_']=len(schema.keys())+2
00064 
00065         plugbrains.__record_schema__ = scopy
00066 
00067         self._v_brains = brains
00068         self._v_result_class = plugbrains
00069 
00070 InitializeClass(PluggableCatalog)
00071 
00072 class UIDCatalogBrains(AbstractCatalogBrain):
00073     """fried my little brains"""
00074 
00075     security = ClassSecurityInfo()
00076 
00077     def getObject(self, REQUEST=None):
00078         """
00079         Used to resolve UIDs into real objects. This also must be
00080         annotation aware. The protocol is:
00081         We have the path to an object. We get this object. If its
00082         UID is not the UID in the brains then we need to pull it
00083         from the reference annotation and return that object
00084 
00085         Thus annotation objects store the path to the source object
00086         """
00087         obj = None
00088         try:
00089             path = self.getPath()
00090             try:
00091                 portal = getToolByName(self, 'portal_url').getPortalObject()
00092                 obj = portal.unrestrictedTraverse(self.getPath())
00093                 obj = aq_inner( obj )
00094             except (ConflictError, KeyboardInterrupt):
00095                 raise
00096             except: #NotFound # XXX bare exception
00097                 pass
00098 
00099             if obj is None:
00100                 if REQUEST is None:
00101                     REQUEST = self.REQUEST
00102                 obj = self.aq_parent.resolve_url(self.getPath(), REQUEST)
00103 
00104             return obj
00105         except (ConflictError, KeyboardInterrupt):
00106             raise
00107         except:
00108             log('UIDCatalogBrains getObject raised an error:\n %s' %
00109                 '\n'.join(traceback.format_exception(*sys.exc_info())))
00110             pass
00111 
00112 InitializeClass(UIDCatalogBrains)
00113 
00114 class IndexableObjectWrapper(object):
00115     """Wwrapper for object indexing
00116     """
00117     def __init__(self, obj):
00118         self._obj = obj
00119 
00120     def __getattr__(self, name):
00121         return getattr(self._obj, name)
00122 
00123     def Title(self):
00124         # TODO: dumb try to make sure UID catalog doesn't fail if Title can't be
00125         # converted to an ascii string
00126         # Title is used for sorting only, maybe we could replace it by a better
00127         # version
00128         title = self._obj.Title()
00129         if isinstance(title, unicode):
00130             return title.encode('utf-8')
00131         try:
00132             return str(title)
00133         except UnicodeDecodeError:
00134             return self._obj.getId()
00135 
00136 _marker=[]
00137 
00138 class UIDResolver(Base):
00139 
00140     security = ClassSecurityInfo()
00141     # XXX FIXME more security
00142 
00143     def resolve_url(self, path, REQUEST):
00144         """Strip path prefix during resolution, This interacts with
00145         the default brains.getObject model and allows and fakes the
00146         ZCatalog protocol for traversal
00147         """
00148         portal_object = self.portal_url.getPortalObject()
00149         try:
00150             return portal_object.unrestrictedTraverse(path)
00151         except (KeyError, AttributeError, NotFound):
00152             # ObjectManager may raise a KeyError when the object isn't there
00153             return None
00154 
00155     def catalog_object(self, obj, uid=None, **kwargs):
00156         """Use the relative path from the portal root as uid
00157 
00158         Ordinary the catalog is using the path from root towards object but we
00159         want only a relative path from the portal root
00160 
00161         Note: This method could be optimized by improving the calculation of the
00162               relative path like storing the portal root physical path in a
00163               _v_ var.
00164         """
00165         portal_path_len = getattr(aq_base(self), '_v_portal_path_len', None)
00166 
00167         if not portal_path_len:
00168             # cache the lenght of the portal path in a _v_ var
00169             urlTool = getToolByName(self, 'portal_url')
00170             portal_path = urlTool.getPortalObject().getPhysicalPath()
00171             portal_path_len = len(portal_path)
00172             self._v_portal_path_len = portal_path_len
00173 
00174         relpath = obj.getPhysicalPath()[portal_path_len:]
00175         uid = '/'.join(relpath)
00176         __traceback_info__ = (repr(obj), uid)
00177         ZCatalog.catalog_object(self, obj, uid, **kwargs)
00178 
00179 InitializeClass(UIDResolver)
00180 
00181 class UIDBaseCatalog(PluggableCatalog):
00182     BASE_CLASS = UIDCatalogBrains
00183 
00184 
00185 class UIDCatalog(UniqueObject, UIDResolver, ZCatalog):
00186     """Unique id catalog
00187     """
00188 
00189     id = UID_CATALOG
00190     security = ClassSecurityInfo()
00191     implements(IUIDCatalog)
00192 
00193     manage_catalogFind = DTMLFile('catalogFind', _catalog_dtml)
00194 
00195     manage_options = ZCatalog.manage_options + \
00196         ({'label': 'Rebuild catalog',
00197          'action': 'manage_rebuildCatalog',}, )
00198 
00199 
00200     def __init__(self, id, title='', vocab_id=None, container=None):
00201         """We hook up the brains now"""
00202         ZCatalog.__init__(self, id, title, vocab_id, container)
00203         self._catalog = UIDBaseCatalog()
00204 
00205     security.declareProtected(ManageZCatalogEntries, 'catalog_object')
00206     def catalog_object(self, object, uid, idxs=[],
00207                        update_metadata=1, pghandler=None):
00208         w = IndexableObjectWrapper(object)
00209         try:
00210             # pghandler argument got added in Zope 2.8
00211             ZCatalog.catalog_object(self, w, uid, idxs,
00212                                     update_metadata, pghandler=pghandler)
00213         except TypeError:
00214             try:
00215                 # update_metadata argument got added somewhere into
00216                 # the Zope 2.6 line (?)
00217                 ZCatalog.catalog_object(self, w, uid, idxs, update_metadata)
00218             except TypeError:
00219                 ZCatalog.catalog_object(self, w, uid, idxs)
00220 
00221     def _catalogObject(self, obj, path):
00222         """Catalog the object. The object will be cataloged with the absolute
00223            path in case we don't pass the relative url.
00224         """ 
00225         url = getRelURL(self, obj.getPhysicalPath()) 
00226         self.catalog_object(obj, url) 
00227 
00228     security.declareProtected(CMFCore.permissions.ManagePortal, 'manage_rebuildCatalog')
00229     def manage_rebuildCatalog(self, REQUEST=None, RESPONSE=None):
00230         """
00231         """
00232         elapse = time.time()
00233         c_elapse = time.clock()
00234 
00235         atool   = getToolByName(self, TOOL_NAME)
00236         obj     = aq_parent(self)
00237         path    = '/'.join(obj.getPhysicalPath())
00238         if not REQUEST:
00239             REQUEST = self.REQUEST
00240 
00241         # build a list of archetype meta types
00242         mt = tuple([typ['meta_type'] for typ in atool.listRegisteredTypes()])
00243 
00244         # clear the catalog
00245         self.manage_catalogClear()
00246 
00247         # find and catalog objects
00248         self.ZopeFindAndApply(obj,
00249                               obj_metatypes=mt,
00250                               search_sub=1,
00251                               REQUEST=REQUEST,
00252                               apply_func=self._catalogObject,
00253                               apply_path=path)
00254 
00255         elapse = time.time() - elapse
00256         c_elapse = time.clock() - c_elapse
00257 
00258         if RESPONSE:
00259             RESPONSE.redirect(
00260             REQUEST.URL1 +
00261             '/manage_catalogView?manage_tabs_message=' +
00262             urllib.quote('Catalog Rebuilded\n'
00263                          'Total time: %s\n'
00264                          'Total CPU time: %s'
00265                          % (`elapse`, `c_elapse`))
00266             )
00267 
00268 InitializeClass(UIDCatalog)