[CMF-checkins] CVS: CMF/CMFCore - ReferenceCatalog.py:1.1.2.1 Referenceable.py:1.1.2.1 UIDCatalog.py:1.1.2.1 reference_config.py:1.1.2.1 CMFCatalogAware.py:1.15.32.1

Benjamin Saller bcsaller at yahoo.com
Wed Mar 24 12:03:04 EST 2004


Update of /cvs-repository/CMF/CMFCore
In directory cvs.zope.org:/tmp/cvs-serv9302

Modified Files:
      Tag: at_reference_backport
	CMFCatalogAware.py 
Added Files:
      Tag: at_reference_backport
	ReferenceCatalog.py Referenceable.py UIDCatalog.py 
	reference_config.py 
Log Message:
*reference engine backport from Archetypes


=== Added File CMF/CMFCore/ReferenceCatalog.py ===
from AccessControl import ClassSecurityInfo
from Products.ZCatalog.ZCatalog import ZCatalog

from OFS.SimpleItem import SimpleItem
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.utils import UniqueObject
from Products.CMFCore import CMFCorePermissions
from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2

from utils import unique, make_uuid
from types import StringType, UnicodeType

from Globals import InitializeClass

from Referenceable import Referenceable
from reference_config import *
from Products.CMFCore.interfaces.referenceable import IReferenceable

from Products.PageTemplates.PageTemplateFile import PageTemplateFile
import os

_www = os.path.join(os.path.dirname(__file__), 'www')

STRING_TYPES = (StringType, UnicodeType)


class Reference(Referenceable, SimpleItem):
    ## Added base level support for referencing References
    ## They respond to the UUID protocols, but are not
    ## catalog aware. This means that you can't move/rename
    ## reference objects and expect them to work, but you can't
    ## do this anyway. However they should fine the correct
    ## events when they are added/deleted, etc
    security = ClassSecurityInfo()
    protect = security.declareProtected
    
    manage_options = (
        (
        {'label':'View', 'action':'manage_view',
         },
        )+
        SimpleItem.manage_options
        )

    security.declareProtected(CMFCorePermissions.ManagePortal,
                              'manage_view')
    manage_view = PageTemplateFile('view_reference', _www)

    def __init__(self, id, sid, tid, relationship, **kwargs):
        self.id = id
        self.sourceUID = sid
        self.targetUID = tid
        self.relationship = relationship
        self.__dict__.update(kwargs)

    def __repr__(self):
        return "<Reference sid:%s tid:%s rel:%s>" %(self.sourceUID, self.targetUID, self.relationship)

    ###
    # Convience methods
    def getSourceObject(self):
        uc = getToolByName(self, UID_MANAGER)
        brains = uc(UID=self.sourceUID)
        if brains:
            return brains[0].getObject()
        return None 
    
    def getTargetObject(self):
        uc = getToolByName(self, UID_MANAGER)
        brains = uc(UID=self.targetUID)
        if brains:
            return brains[0].getObject()
        return None
    
    ###
    # Catalog support
    def targetId(self):
        target = self.getTargetObject()
        id = target.getId()
        return target.getId()

    def targetTitle(self):
        target = self.getTargetObject()
        return target.Title()

    def Type(self):
        return self.__class__.__name__

    ###
    # Policy hooks, subclass away
    def addHook(self, tool, sourceObject=None, targetObject=None):
        #to reject the reference being added raise a ReferenceException
        pass

    def delHook(self, tool, sourceObject=None, targetObject=None):
        #to reject the delete raise a ReferenceException
        pass

    ###
    # OFS Operations Policy Hooks
    # These Hooks are experimental and subject to change
    def beforeTargetDeleteInformSource(self):
        """called before target object is deleted so the source can have a say"""
        pass

    def beforeSourceDeleteInformTarget(self):
        """called when the refering source Object is about to be deleted"""
        pass

    ###
    # Non-catalog aware hook overrides
    def manage_afterAdd(self, item, container):
        Referenceable.manage_afterAdd(self, item, container)
        
        uc = getToolByName(self, UID_MANAGER)
        uc.catalog_object(self, '/'.join(self.getPhysicalPath()))



class ReferenceCatalog(UniqueObject, BTreeFolder2, ZCatalog):
    id = REFERENCE_MANAGER
    security = ClassSecurityInfo()
    protect = security.declareProtected
    
    manage_options = (
        (BTreeFolder2.manage_options[0],) +
        (ZCatalog.manage_options[1:])
        )

    def __init__(self, id, title, vocab_id, extra):
        BTreeFolder2.__init__(self, id)
        ZCatalog.__init__(self, id, title, vocab_id, extra)
        
    
    ###
    ## Public API
    def addReference(self, source, target, relationship=None, referenceClass=None, **kwargs):
        sID, sobj = self._uidFor(source)
        tID, tobj = self._uidFor(target)

        objects = self._resolveBrains(self._queryFor(sID, tID, relationship))
        if objects:
            #we want to update the existing reference
            #XXX we might need to being a subtransaction here to
            #    do this properly, and close it later
            existing = objects[0]
            if existing:
                self._delObject(existing.id)

        rID = self._makeName(sID, tID)
        if not referenceClass:
            referenceClass = Reference

        referenceObject = referenceClass(rID, sID, tID, relationship, **kwargs)

        try:
            referenceObject.addHook(self, sobj, tobj)
        except ReferenceException:
            pass
        else:
            self._setObject(rID, referenceObject)
            referenceObject = getattr(self, rID)
            self.catalog_object(referenceObject, rID)

    def deleteReference(self, source, target, relationship=None):
        sID, sobj = self._uidFor(source)
        tID, tobj = self._uidFor(target)

        objects = self._resolveBrains(self._queryFor(sID, tID, relationship))
        if objects:
            self._deleteReference(objects[0])

    def deleteReferences(self, object, relationship=None):
        """delete all the references held by an object"""
        [self._deleteReference(b) for b in
         (self.getReferences(object) or []) +
         (self.getBackReferences(object) or [])]

    def getReferences(self, object, relationship=None):
        """return a collection of reference objects"""
        sID, sobj = self._uidFor(object)
        brains = self._queryFor(sid=sID, relationship=relationship)
        return self._resolveBrains(brains)

    def getBackReferences(self, object, relationship=None):
        """return a collection of reference objects"""
        # Back refs would be anything that target this object
        sID, sobj = self._uidFor(object)
        brains = self._queryFor(tid=sID, relationship=relationship)
        return self._resolveBrains(brains)

    def hasRelationshipTo(self, source, target, relationship):
        sID, sobj = self._uidFor(source)
        tID, tobj = self._uidFor(target)

        brains = self._queryFor(sID, tID, relationship)
        result = False
        if brains:
            referenceObject = brains[0].getObject()
            result = True

        return result

    def getRelationships(self, object):
        """Get all relationship types this object has to other objects"""
        sID, sobj = self._uidFor(object)
        brains = self._queryFor(sid=sID)
        res = {}
        for b in brains:
            res[b.relationship]=1

        return res.keys()


    def isReferenceable(self, object):
        #Check for the catalogAware interface, there is no marker
        return IReferenceable.isImplementedBy(object) or hasattr(object, 'isReferenceable')


    def reference_url(self, object):
        """return a url to an object that will resolve by reference"""
        sID, sobj = self._uidFor(object)
        return "%s/lookupObject?uuid=%s" % (self.absolute_url(), sID)

    def lookupObject(self, uuid):
        """Lookup an object by its uuid"""
        return self._objectByUUID(uuid)


    #####
    ## UID register/unregister
    protect('registerObject', CMFCorePermissions.ModifyPortalContent)
    def registerObject(self, object):
        self._uidFor(object)

    protect('unregisterObject', CMFCorePermissions.ModifyPortalContent)
    def unregisterObject(self, object):
        self.deleteReferences(object)
        uc = getToolByName(self, UID_MANAGER)
        uc.uncatalog_object('/'.join(object.getPhysicalPath()))

        

    ######
    ## Private/Internal
    def _objectByUUID(self, uuid):
        uc = getToolByName(self, UID_MANAGER)
        brains = uc(UID=uuid)
        if not brains:
            return None
        return brains[0].getObject()

    def _queryFor(self, sid=None, tid=None, relationship=None, targetId=None, merge=1):
        """query reference catalog for object matching the info we are
        given, returns brains

        Note: targetId is the actual id of the target object, not its UID
        """

        q = {}
        if sid: q['sourceUID'] = sid
        if tid: q['targetUID'] = tid
        if relationship: q['relationship'] = relationship
        if targetId: q['targetId'] = targetId
        brains = self.searchResults(q, merge=merge)

        return brains


    def _uidFor(self, object):
        # We should really check for the interface but I have an idea
        # about simple annotated objects I want to play out
        if type(object) not in STRING_TYPES:
            if not self.isReferenceable(object):
                raise ReferenceException

            uobject = object.aq_base
            if not getattr(uobject, UUID_ATTR, None):
                uuid = self._getUUIDFor(object)
            else:
                uuid = getattr(uobject, UUID_ATTR)
        else:
            uuid = object
            #and we look up the object
            uc = getToolByName(self, UID_MANAGER)
            brains = uc(UID=uuid) ##XXX this said UUID?
            if brains:
                object = brains[0].getObject()
            else:
                object = None

        return uuid, object

    def _getUUIDFor(self, object):
        """generate and attach a new uid to the object returning it"""
        uuid = make_uuid(object.getId())
        setattr(object, UUID_ATTR, uuid)
        
        return uuid

    def _deleteReference(self, referenceObject):
        try:
            referenceObject.delHook(self, referenceObject.getSourceObject(), referenceObject.getTargetObject())
        except ReferenceException:
            pass
        else:
            self.uncatalog_object(referenceObject.getId())
            self._delObject(referenceObject.getId())

    def _resolveBrains(self, brains):
        objects = []
        if brains:
            objects = [b.getObject() for b in brains]
            objects = [b for b in objects if b]
        return objects

    def _makeName(self, *args):
        name = make_uuid(*args)
        name = "ref_%s" % name
        return name

def manage_addReferenceCatalog(self, id, title,
                               vocab_id=None, # Deprecated
                               REQUEST=None):
    """Add a ReferenceCatalog object
    """
    id=str(id)
    title=str(title)
    c=ReferenceCatalog(id, title, vocab_id, self)
    self._setObject(id, c)
    if REQUEST is not None:
        return self.manage_main(self, REQUEST,update_menu=1)


InitializeClass(ReferenceCatalog)


=== Added File CMF/CMFCore/Referenceable.py ===
from reference_config import *
from ExtensionClass import Base
from utils import getToolByName
from interfaces.referenceable import IReferenceable
from OFS.ObjectManager import BeforeDeleteException

class Referenceable(Base):

    __implements__ = IReferenceable
    isReferenceable = True
    
    # References
    # ----
    def UID(self):
        """return a uuid, getting one if absent"""
        return getattr(self, UUID_ATTR, self._uuid_register())
    
    def _uuid_register(self, reference_manager=None):
        """get a uuid that can be used for references"""
        uuid = getattr(self, UUID_ATTR, None)
        if uuid: return uuid
        
        if not reference_manager:
            reference_manager = getToolByName(self, REFERENCE_MANAGER, None)
           
        if reference_manager is not None:
            reference_manager.registerObject(self)

            uc = getattr(reference_manager, UID_MANAGER)
            uc.catalog_object(self, '/'.join(self.getPhysicalPath()))
        

    def _uuid_unregister(self, reference_manager=None):
        """remove all references"""
        if not reference_manager:
            reference_manager = getToolByName(self, REFERENCE_MANAGER, None)
            
        if reference_manager is not None:
            reference_manager.unregisterObject(self)

    # Reference Syntatic Sugar
    # ----
    def reference_url(self):
        """like absoluteURL, but return a link to the object with this UID"""
        tool = getToolByName(self, REFERENCE_MANAGER)
        return tool.reference_url(self)

    def hasRelationshipTo(self, target, relationship=None):
        tool = getToolByName(self, REFERENCE_MANAGER)
        return tool.hasRelationshipTo(self, target, relationship)

    def addReference(self, object, relationship=None, **kwargs):
        tool = getToolByName(self, REFERENCE_MANAGER)
        return tool.addReference(self, object, relationship, **kwargs)

    def deleteReference(self, target, relationship=None):
        tool = getToolByName(self, REFERENCE_MANAGER)
        return tool.deleteReference(self, target, relationship)

    def deleteReferences(self, relationship=None):
        tool = getToolByName(self, REFERENCE_MANAGER)
        return tool.deleteReferences(self, relationship)

    def getRelationships(self):
        """What kinds of relationships does this object have"""
        tool = getToolByName(self, REFERENCE_MANAGER)
        return tool.getRelationships(self)

    def getRefs(self, relationship=None):
        """get all the referenced objects for this object"""
        tool = getToolByName(self, REFERENCE_MANAGER)
        refs = tool.getReferences(self, relationship)
        if refs:
            return [ref.getTargetObject() for ref in refs]
        return []

    def getBRefs(self, relationship=None):
        """get all the back referenced objects for this object"""
        tool = getToolByName(self, REFERENCE_MANAGER)
        refs = tool.getBackReferences(self, relationship)
        if refs:
            return [ref.getSourceObject() for ref in refs]
        return []

    ## Hook Support
    def manage_afterAdd(self, item, container):
        """add hook"""
        self._uuid_register()

    def manage_afterClone(self, item):
        """copy hook"""
        setattr(self, UUID_ATTR, None)
        self._uuid_register()
    
    def manage_beforeDelete(self, item, container):
        """delete hook"""
        keepingRefsOnMove= getattr(self, KEEP_REFS_ON_MOVE, False)
        
        if keepingRefsOnMove is False:
            from ReferenceCatalog import ReferenceException
            ## This is an actual delete
            rc = getattr(self, REFERENCE_MANAGER)
            if not rc: return
            references = rc.getReferences(self)
            back_references = rc.getBackReferences(self)
            try:
                #First check the 'delete cascade' case
                if references:
                    for ref in references:
                        ref.beforeSourceDeleteInformTarget()
                        ref._uuid_unregister(rc)
                #Then check the 'holding/ref count' case
                if back_references:
                    for ref in back_references:
                        ref.beforeTargetDeleteInformSource()
                        ref._uuid_unregister(rc)
                self._uuid_unregister(rc)

            except ReferenceException, E:
                raise BeforeDeleteException(E)
            
        setattr(self, KEEP_REFS_ON_MOVE, False)

    def _notifyOfCopyTo(self, container, op=0):
        """keep reference info internally when op == 1 (move)
        because in those cases we need to keep refs"""
        if op==1:
            setattr(self, KEEP_REFS_ON_MOVE, True)






=== Added File CMF/CMFCore/UIDCatalog.py ===
from AccessControl import ClassSecurityInfo
from Products.ZCatalog.ZCatalog import ZCatalog

from OFS.SimpleItem import SimpleItem
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.utils import UniqueObject
from Products.CMFCore import CMFCorePermissions

from utils import unique, make_uuid
from types import StringType, UnicodeType

from Globals import InitializeClass

from Referenceable import Referenceable
from reference_config import *

from Products.PageTemplates.PageTemplateFile import PageTemplateFile
import os

_www = os.path.join(os.path.dirname(__file__), 'www')

STRING_TYPES = (StringType, UnicodeType)



class UIDCatalog(UniqueObject, ZCatalog):
    id = UID_MANAGER
    security = ClassSecurityInfo()
    protect = security.declareProtected
    
    manage_options = ZCatalog.manage_options

    
    ### ## Public API
    def catalog_object(self, object, uid=None, idxs=None, update_metadata=1):
        """
        The UID catalog has the special requirement that UUID's
        while unique occur multiple times within the scope of its
        index and really belong to a tuple of elements that constuct
        the primary key. (Minimally version and possibly things like
        language)
        """
    

    def deleteReference(self, source, target, relationship=None):
        sID, sobj = self._uidFor(source)
        tID, tobj = self._uidFor(target)

        objects = self._resolveBrains(self._queryFor(sID, tID, relationship))
        if objects:
            self._deleteReference(objects[0])

    def deleteReferences(self, object, relationship=None):
        """delete all the references held by an object"""
        [self._deleteReference(b) for b in
         (self.getReferences(object) or []) +
         (self.getBackReferences(object) or [])]

    def getReferences(self, object, relationship=None):
        """return a collection of reference objects"""
        sID, sobj = self._uidFor(object)
        brains = self._queryFor(sid=sID, relationship=relationship)
        return self._resolveBrains(brains)

    def getBackReferences(self, object, relationship=None):
        """return a collection of reference objects"""
        # Back refs would be anything that target this object
        sID, sobj = self._uidFor(object)
        brains = self._queryFor(tid=sID, relationship=relationship)
        return self._resolveBrains(brains)

    def hasRelationshipTo(self, source, target, relationship):
        sID, sobj = self._uidFor(source)
        tID, tobj = self._uidFor(target)

        brains = self._queryFor(sID, tID, relationship)
        result = False
        if brains:
            referenceObject = brains[0].getObject()
            result = True

        return result

    def getRelationships(self, object):
        """Get all relationship types this object has to other objects"""
        sID, sobj = self._uidFor(object)
        brains = self._queryFor(sid=sID)
        res = {}
        for b in brains:
            res[b.relationship]=1

        return res.keys()


    def isReferenceable(self, object):
        #Check for the catalogAware interface, there is no marker
        return IReferenceable.isImplementedBy(object) or hasattr(object, 'isReferenceable')


    def reference_url(self, object):
        """return a url to an object that will resolve by reference"""
        sID, sobj = self._uidFor(object)
        return "%s/lookupObject?uuid=%s" % (self.absolute_url(), sID)

    def lookupObject(self, uuid):
        """Lookup an object by its uuid"""
        return self._objectByUUID(uuid)


    #####
    ## UID register/unregister
    protect('registerObject', CMFCorePermissions.ModifyPortalContent)
    def registerObject(self, object):
        self._uidFor(object)

    protect('unregisterObject', CMFCorePermissions.ModifyPortalContent)
    def unregisterObject(self, object):
        self.deleteReferences(object)
        

    ######
    ## Private/Internal
    def _objectByUUID(self, uuid):
        tool = getToolByName(self, UID_MANAGER)
        brains = tool(UID=uuid)
        if not brains:
            return None
        return brains[0].getObject()

    def _queryFor(self, sid=None, tid=None, relationship=None, targetId=None, merge=1):
        """query reference catalog for object matching the info we are
        given, returns brains

        Note: targetId is the actual id of the target object, not its UID
        """

        q = {}
        if sid: q['sourceUID'] = sid
        if tid: q['targetUID'] = tid
        if relationship: q['relationship'] = relationship
        if targetId: q['targetId'] = targetId
        brains = self.searchResults(q, merge=merge)

        return brains


    def _uidFor(self, object):
        # We should really check for the interface but I have an idea
        # about simple annotated objects I want to play out
        if type(object) not in STRING_TYPES:
            uobject = object.aq_base
            if not self.isReferenceable(object):
                raise ReferenceException

            if not getattr(uobject, UUID_ATTR, None):
                uuid = self._getUUIDFor(object)
            else:
                uuid = getattr(uobject, UUID_ATTR)
        else:
            uuid = object
            #and we look up the object
            uid_catalog = getToolByName(self, UID_MANAGER)
            brains = uid_catalog(UUID=uuid)
            object = brains[0].getObject()

        return uuid, object

    def _getUUIDFor(self, object):
        """generate and attach a new uid to the object returning it"""
        uuid = make_uuid(object.getId())
        setattr(object, UUID_ATTR, uuid)
        
        uc = getToolByName(self, UID_MANAGER)
        uc.catalog_object(object, '/'.join(object.getPhysicalPath()))

        return uuid

    def _deleteReference(self, referenceObject):
        try:
            referenceObject.delHook(self, referenceObject.getSourceObject(), referenceObject.getTargetObject())
        except ReferenceException:
            pass
        else:
            self.uncatalog_object(referenceObject.getId())
            self._delObject(referenceObject.getId())
            #We actually deleted the reference, we special case kill its uuid
            referenceObject._ref_unregister()

    def _resolveBrains(self, brains):
        objects = []
        if brains:
            objects = [b.getObject() for b in brains]
            objects = [b for b in objects if b]
        return objects

    def _makeName(self, *args):
        name = make_uuid(*args)
        name = "ref_%s" % name
        return name

def manage_addReferenceCatalog(self, id, title,
                               vocab_id=None, # Deprecated
                               REQUEST=None):
    """Add a ReferenceCatalog object
    """
    id=str(id)
    title=str(title)
    c=ReferenceCatalog(id, title, vocab_id, self)
    self._setObject(id, c)
    if REQUEST is not None:
        return self.manage_main(self, REQUEST,update_menu=1)


InitializeClass(ReferenceCatalog)


=== Added File CMF/CMFCore/reference_config.py ===
# Reference Define
# ----------------
UUID_ATTR          = "_cmf_uuid"
REFERENCE_MANAGER  = "reference_catalog"
UID_MANAGER        = "uid_catalog"

KEEP_REFS_ON_MOVE  = "_v_keepReferences"


class ReferenceException(Exception):
    pass




=== CMF/CMFCore/CMFCatalogAware.py 1.15 => 1.15.32.1 ===
--- CMF/CMFCore/CMFCatalogAware.py:1.15	Sun Feb  2 11:06:17 2003
+++ CMF/CMFCore/CMFCatalogAware.py	Wed Mar 24 12:02:33 2004
@@ -19,11 +19,14 @@
 from CMFCorePermissions import ModifyPortalContent
 from CMFCorePermissions import AccessContentsInformation
 from CMFCorePermissions import ManagePortal
-from utils import getToolByName
 from utils import _dtmldir
+from utils import getToolByName
+
+from interfaces.IOpaqueItems import ICallableOpaqueItemWithHooks
 
+from Referenceable    import Referenceable
 
-class CMFCatalogAware(Base):
+class CMFCatalogAware(Referenceable):
     """Mix-in for notifying portal_catalog and portal_workflow
     """
 
@@ -143,6 +146,7 @@
             Add self to the catalog.
             (Called when the object is created or moved.)
         """
+        Referenceable.manage_afterAdd(item, item, container)
         if aq_base(container) is not aq_base(self):
             self.indexObject()
             self.__recurse('manage_afterAdd', item, container)
@@ -152,6 +156,7 @@
             Add self to the workflow.
             (Called when the object is cloned.)
         """
+        Referenceable.manage_afterClone(item, item)
         self.notifyWorkflowCreated()
         self.__recurse('manage_afterClone', item)
 
@@ -163,6 +168,9 @@
         if aq_base(container) is not aq_base(self):
             self.__recurse('manage_beforeDelete', item, container)
             self.unindexObject()
+
+        Referenceable.manage_beforeDelete(self, item, container)
+
 
     def __recurse(self, name, *args):
         """




More information about the CMF-checkins mailing list