Back to index

plone3  3.1.7
Referenceable.py
Go to the documentation of this file.
00001 from zope.interface import implements
00002 
00003 from Products.Archetypes import config
00004 from Products.Archetypes.exceptions import ReferenceException
00005 from Products.Archetypes.interfaces import IReferenceable
00006 from Products.Archetypes.interfaces.referenceable import IReferenceable as DEPRECATED
00007 from Products.Archetypes.utils import shasattr
00008 
00009 from Acquisition import aq_base, aq_parent, aq_inner
00010 from OFS.ObjectManager import BeforeDeleteException
00011 
00012 from Products.CMFCore.utils import getToolByName
00013 from Products.CMFCore.permissions import View
00014 from OFS.CopySupport import CopySource
00015 from OFS.Folder import Folder
00016 from utils import getRelURL
00017 
00018 from Globals import InitializeClass
00019 from AccessControl import ClassSecurityInfo
00020 
00021 ####
00022 ## In the case of:
00023 ## - a copy:
00024 ##   * we want to lose refs on the new object
00025 ##   * we want to keep refs on the orig object
00026 ## - a cut/paste
00027 ##   * we want to keep refs
00028 ## - a delete:
00029 ##   * to lose refs
00030 ####
00031 
00032 #include graph supporting methods
00033 from ref_graph import get_cmapx, get_png
00034 
00035 class Referenceable(CopySource):
00036     """ A Mix-in for Referenceable objects """
00037     isReferenceable = 1
00038     implements(IReferenceable)
00039     __implements__ = (DEPRECATED,)
00040 
00041     security = ClassSecurityInfo()
00042     # XXX FIXME more security
00043 
00044     def reference_url(self):
00045         """like absoluteURL, but return a link to the object with this UID"""
00046         tool = getToolByName(self, config.REFERENCE_CATALOG)
00047         return tool.reference_url(self)
00048 
00049     def hasRelationshipTo(self, target, relationship=None):
00050         tool = getToolByName(self, config.REFERENCE_CATALOG)
00051         return tool.hasRelationshipTo(self, target, relationship)
00052 
00053     def addReference(self, object, relationship=None, referenceClass=None,
00054                      updateReferences=True, **kwargs):
00055         tool = getToolByName(self, config.REFERENCE_CATALOG)
00056         return tool.addReference(self, object, relationship, referenceClass,
00057                                  updateReferences, **kwargs)
00058 
00059     def deleteReference(self, target, relationship=None):
00060         tool = getToolByName(self, config.REFERENCE_CATALOG)
00061         return tool.deleteReference(self, target, relationship)
00062 
00063     def deleteReferences(self, relationship=None):
00064         tool = getToolByName(self, config.REFERENCE_CATALOG)
00065         return tool.deleteReferences(self, relationship)
00066 
00067     def getRelationships(self):
00068         """What kinds of relationships does this object have"""
00069         tool = getToolByName(self, config.REFERENCE_CATALOG)
00070         return tool.getRelationships(self)
00071 
00072     def getBRelationships(self):
00073         """
00074         What kinds of relationships does this object have from others
00075         """
00076         tool = getToolByName(self, config.REFERENCE_CATALOG)
00077         return tool.getBackRelationships(self)
00078 
00079     def getRefs(self, relationship=None, targetObject=None):
00080         """get all the referenced objects for this object"""
00081         tool = getToolByName(self, config.REFERENCE_CATALOG)
00082         refs = tool.getReferences(self, relationship, targetObject=targetObject)
00083         if refs:
00084             return [ref.getTargetObject() for ref in refs]
00085         return []
00086 
00087     def _getURL(self):
00088         """the url used as the relative path based uid in the catalogs"""
00089         return getRelURL(self, self.getPhysicalPath())
00090 
00091     def getBRefs(self, relationship=None, targetObject=None):
00092         """get all the back referenced objects for this object"""
00093         tool = getToolByName(self, config.REFERENCE_CATALOG)
00094         refs = tool.getBackReferences(self, relationship, targetObject=targetObject)
00095         if refs:
00096             return [ref.getSourceObject() for ref in refs]
00097         return []
00098 
00099     #aliases
00100     getReferences=getRefs
00101     getBackReferences=getBRefs
00102 
00103     def getReferenceImpl(self, relationship=None, targetObject=None):
00104         """get all the reference objects for this object    """
00105         tool = getToolByName(self, config.REFERENCE_CATALOG)
00106         refs = tool.getReferences(self, relationship, targetObject=targetObject)
00107         if refs:
00108             return refs
00109         return []
00110 
00111     def getBackReferenceImpl(self, relationship=None, targetObject=None):
00112         """get all the back reference objects for this object"""
00113         tool = getToolByName(self, config.REFERENCE_CATALOG)
00114         refs = tool.getBackReferences(self, relationship, targetObject=targetObject)
00115         if refs:
00116             return refs
00117         return []
00118 
00119     def _register(self, reference_manager=None):
00120         """register with the archetype tool for a unique id"""
00121         if self.UID() is not None:
00122             return
00123 
00124         if reference_manager is None:
00125             reference_manager = getToolByName(self, config.REFERENCE_CATALOG)
00126         reference_manager.registerObject(self)
00127 
00128 
00129     def _unregister(self):
00130         """unregister with the archetype tool, remove all references"""
00131         reference_manager = getToolByName(self, config.REFERENCE_CATALOG)
00132         reference_manager.unregisterObject(self)
00133 
00134     def _getReferenceAnnotations(self):
00135         """given an object extract the bag of references for which it
00136         is the source"""
00137         if not getattr(aq_base(self), config.REFERENCE_ANNOTATION, None):
00138             setattr(self, config.REFERENCE_ANNOTATION,
00139                     Folder(config.REFERENCE_ANNOTATION))
00140 
00141         return getattr(self, config.REFERENCE_ANNOTATION).__of__(self)
00142 
00143     def _delReferenceAnnotations(self):
00144         """Removes annotation from self
00145         """
00146         if getattr(aq_base(self), config.REFERENCE_ANNOTATION, None):
00147             delattr(self, config.REFERENCE_ANNOTATION)
00148 
00149     def UID(self):
00150         return getattr(self, config.UUID_ATTR, None)
00151 
00152     def _setUID(self, uid):
00153         old_uid = self.UID()
00154         if old_uid is None:
00155             # Nothing to be done.
00156             return
00157         # Update forward references
00158         fw_refs = self.getReferenceImpl()
00159         for ref in fw_refs:
00160             assert ref.sourceUID == old_uid
00161             ref.sourceUID = uid
00162             item = ref
00163             container = aq_parent(aq_inner(ref))
00164             # We call manage_afterAdd to inform the
00165             # reference catalog about changes.
00166             ref.manage_afterAdd(item, container)
00167         # Update back references
00168         back_refs = self.getBackReferenceImpl()
00169         for ref in back_refs:
00170             assert ref.targetUID == old_uid
00171             ref.targetUID = uid
00172             item = ref
00173             container = aq_parent(aq_inner(ref))
00174             # We call manage_afterAdd to inform the
00175             # reference catalog about changes.
00176             ref.manage_afterAdd(item, container)
00177         setattr(self, config.UUID_ATTR, uid)
00178         item = self
00179         container = aq_parent(aq_inner(item))
00180         # We call manage_afterAdd to inform the
00181         # reference catalog about changes.
00182         self.manage_afterAdd(item, container)
00183 
00184     def _updateCatalog(self, container):
00185         """Update catalog after copy, rename ...
00186         """
00187         # the UID index needs to be updated for any annotations we
00188         # carry
00189         try:
00190             uc = getToolByName(container, config.UID_CATALOG)
00191         except AttributeError:
00192             # TODO when trying to rename or copy a whole site than
00193             # container is the object "under" the portal so we can
00194             # NEVER ever find the catalog which is bad ...
00195             container = aq_parent(self)
00196             uc = getToolByName(container, config.UID_CATALOG)
00197 
00198         rc = getToolByName(uc, config.REFERENCE_CATALOG)
00199 
00200         self._catalogUID(container, uc=uc)
00201         self._catalogRefs(container, uc=uc, rc=rc)
00202 
00203     ## OFS Hooks
00204     def manage_afterAdd(self, item, container):
00205         """
00206         Get a UID
00207         (Called when the object is created or moved.)
00208         """
00209         isCopy = getattr(item, '_v_is_cp', None)
00210         # Before copying we take a copy of the references that are to be copied 
00211         # on the new copy
00212         rfields=self.Schema().filterFields(type="reference", keepReferencesOnCopy=1) 
00213         rrefs={}
00214         if isCopy:
00215             # If the object is a copy of a existing object we
00216             # want to renew the UID, and drop all existing references
00217             # on the newly-created copy.
00218             for r in rfields:
00219                 rrefs[r.getName()]=r.get(self)
00220             setattr(self, config.UUID_ATTR, None)
00221             self._delReferenceAnnotations()
00222 
00223         ct = getToolByName(container, config.REFERENCE_CATALOG, None)
00224         self._register(reference_manager=ct)
00225         self._updateCatalog(container)
00226         self._referenceApply('manage_afterAdd', item, container)
00227         # copy the references 
00228         if isCopy:
00229             for r in rfields:
00230                 r.set(self,rrefs[r.getName()])
00231                  
00232 
00233     def manage_afterClone(self, item):
00234         """
00235         Get a new UID (effectivly dropping reference)
00236         (Called when the object is cloned.)
00237         """
00238         uc = getToolByName(self, config.UID_CATALOG)
00239 
00240         isCopy = getattr(item, '_v_is_cp', None)
00241         if isCopy:
00242             # if isCopy is True, manage_afterAdd should have assigned a
00243             # UID already.  Don't mess with UID anymore.
00244             return
00245 
00246         # TODO Should we ever get here after the isCopy flag addition??
00247         # If the object has no UID or the UID already exists, then
00248         # we should get a new one
00249         if (not shasattr(self,config.UUID_ATTR) or
00250             len(uc(UID=self.UID()))):
00251             setattr(self, config.UUID_ATTR, None)
00252 
00253         self._register()
00254         self._updateCatalog(self)
00255 
00256     def manage_beforeDelete(self, item, container):
00257         """
00258         Remove self from the catalog.
00259         (Called when the object is deleted or moved.)
00260         """
00261 
00262         # Change this to be "item", this is the root of this recursive
00263         # chain and it will be flagged in the correct mode
00264         storeRefs = getattr(item, '_v_cp_refs', None)
00265         if storeRefs is None:
00266             # The object is really going away, we want to remove
00267             # its references
00268             rc = getToolByName(self, config.REFERENCE_CATALOG)
00269             references = rc.getReferences(self)
00270             back_references = rc.getBackReferences(self)
00271             try:
00272                 #First check the 'delete cascade' case
00273                 if references:
00274                     for ref in references:
00275                         ref.beforeSourceDeleteInformTarget()
00276                 #Then check the 'holding/ref count' case
00277                 if back_references:
00278                     for ref in back_references:
00279                         ref.beforeTargetDeleteInformSource()
00280                 # If nothing prevented it, remove all the refs
00281                 self.deleteReferences()
00282             except ReferenceException, E:
00283                 raise BeforeDeleteException(E)
00284 
00285         self._referenceApply('manage_beforeDelete', item, container)
00286 
00287         # Track the UUID
00288         # The object has either gone away, moved or is being
00289         # renamed, we still need to remove all UID/child refs
00290         self._uncatalogUID(container)
00291         self._uncatalogRefs(container)
00292 
00293 
00294 
00295     ## Catalog Helper methods
00296     def _catalogUID(self, aq, uc=None):
00297         if not uc:
00298             uc = getToolByName(aq, config.UID_CATALOG)
00299         url = self._getURL()
00300         uc.catalog_object(self, url)
00301 
00302     def _uncatalogUID(self, aq, uc=None):
00303         if not uc:
00304             uc = getToolByName(self, config.UID_CATALOG)
00305         url = self._getURL()
00306         # XXX This is an ugly workaround. This method shouldn't be called
00307         # twice for an object in the first place, so we don't have to check
00308         # if it is still cataloged. 
00309         rid = uc.getrid(url)
00310         if rid is not None:
00311             uc.uncatalog_object(url)
00312 
00313     def _catalogRefs(self, aq, uc=None, rc=None):
00314         annotations = self._getReferenceAnnotations()
00315         if annotations:
00316             if not uc:
00317                 uc = getToolByName(aq, config.UID_CATALOG)
00318             if not rc:
00319                 rc = getToolByName(aq, config.REFERENCE_CATALOG)
00320             for ref in annotations.objectValues():
00321                 url = getRelURL(uc, ref.getPhysicalPath())
00322                 uc.catalog_object(ref, url)
00323                 rc.catalog_object(ref, url)
00324                 ref._catalogRefs(uc, uc, rc)
00325 
00326     def _uncatalogRefs(self, aq, uc=None, rc=None):
00327         annotations = self._getReferenceAnnotations()
00328         if annotations:
00329             if not uc:
00330                 uc = getToolByName(self, config.UID_CATALOG)
00331             if not rc:
00332                 rc = getToolByName(self, config.REFERENCE_CATALOG)
00333             for ref in annotations.objectValues():
00334                 url = getRelURL(uc, ref.getPhysicalPath())
00335                 # XXX This is an ugly workaround. This method shouldn't be
00336                 # called twice for an object in the first place, so we don't
00337                 # have to check if it is still cataloged. 
00338                 uc_rid = uc.getrid(url)
00339                 if uc_rid is not None:
00340                     uc.uncatalog_object(url)
00341                 rc_rid = rc.getrid(url)
00342                 if rc_rid is not None:
00343                     rc.uncatalog_object(url)
00344 
00345     def _getCopy(self, container):
00346         # We only set the '_v_is_cp' flag here if it was already set.
00347         #
00348         # _getCopy gets called after _notifyOfCopyTo, which should set
00349         # _v_cp_refs appropriatedly.
00350         #
00351         # _getCopy is also called from WebDAV MOVE (though not from
00352         # 'manage_pasteObjects')
00353         is_cp_flag = getattr(self, '_v_is_cp', None)
00354         cp_refs_flag = getattr(self, '_v_cp_refs', None)
00355         ob = CopySource._getCopy(self, container)
00356         if is_cp_flag:
00357             setattr(ob, '_v_is_cp', is_cp_flag)
00358         if cp_refs_flag:
00359             setattr(ob, '_v_cp_refs', cp_refs_flag)
00360         return ob
00361 
00362     def _notifyOfCopyTo(self, container, op=0):
00363         """keep reference info internally when op == 1 (move)
00364         because in those cases we need to keep refs"""
00365         # This isn't really safe for concurrent usage, but the
00366         # worse case is not that bad and could be fixed with a reindex
00367         # on the archetype tool
00368         if op==1:
00369             self._v_cp_refs = 1
00370             self._v_is_cp = 0
00371         if op==0:
00372             self._v_cp_refs = 0
00373             self._v_is_cp = 1
00374 
00375     # Recursion Mgmt
00376     def _referenceApply(self, methodName, *args, **kwargs):
00377         # We always apply commands to our reference children
00378         # and if we are folderish we need to get those too
00379         # where as references are concerned
00380         children = []
00381         if shasattr(self, 'objectValues'):
00382             # Only apply to objects that subclass
00383             # from Referenceable, and only apply the
00384             # method from Referenceable. Otherwise manage_* will get
00385             # called multiple times.
00386             nc = lambda obj: isinstance(obj, Referenceable)
00387             children.extend(filter(nc, self.objectValues()))
00388         children.extend(self._getReferenceAnnotations().objectValues())
00389         if children:
00390             for child in children:
00391                 if shasattr(Referenceable, methodName):
00392                     method = getattr(Referenceable, methodName)
00393                     method(*((child,) + args), **kwargs)
00394 
00395     # graph hooks
00396     security.declareProtected(View, 'getReferenceMap')
00397     def getReferenceMap(self):
00398         """The client side map for this objects references"""
00399         return get_cmapx(self)
00400 
00401     security.declareProtected(View, 'getReferencePng')
00402     def getReferencePng(self, REQUEST=None):
00403         """A png of the references for this object"""
00404         if REQUEST:
00405             REQUEST.RESPONSE.setHeader('content-type', 'image/png')
00406         return get_png(self)
00407 
00408 InitializeClass(Referenceable)