[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