Back to index

plone3  3.1.7
ChangeSet.py
Go to the documentation of this file.
00001 #
00002 # ChangeSet.py - Zope object representing the differences between
00003 # objects
00004 #
00005 # Code by Brent Hendricks
00006 #
00007 # (C) 2003 Brent Hendricks - licensed under the terms of the
00008 # GNU General Public License (GPL).  See LICENSE.txt for details
00009 
00010 import logging
00011 import transaction
00012 from zope.interface import implements
00013 
00014 from AccessControl import ClassSecurityInfo
00015 from Acquisition import Implicit
00016 from Acquisition import aq_base
00017 from ComputedAttribute import ComputedAttribute
00018 from Globals import InitializeClass
00019 from OFS.CopySupport import CopyError
00020 from Products.CMFCore.utils import getToolByName
00021 from Products.CMFCore.permissions import View, ModifyPortalContent
00022 from Products.CMFDefault.SkinnedFolder import SkinnedFolder
00023 from Products.CMFDefault.DublinCore import DefaultDublinCoreImpl
00024 from Products.CMFDiffTool.interfaces import IChangeSet
00025 from Products.CMFDiffTool.interfaces.IChangeSet import IChangeSet as IChangeSetZ2
00026 
00027 logger = logging.getLogger('CMFDiffTool')
00028 
00029 def manage_addChangeSet(self, id, title='', REQUEST=None):
00030     """Creates a new ChangeSet object """
00031     id=str(id)
00032     if not id:
00033         raise "Bad Request", "Please specify an ID."
00034 
00035     self=self.this()
00036     cs = ChangeSet(id, title)
00037     self._setObject(id, cs)
00038 
00039     if REQUEST is not None:
00040         REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
00041 
00042 
00043 factory_type_information = (
00044     {'id': 'ChangeSet',
00045      'content_icon': 'changeset.png',
00046      'meta_type': 'Change Set',
00047      'description': ('A collection of changes between two objects'),
00048      'product': 'CMFDiffTool',
00049      'global_allow':0,
00050      'factory': 'manage_addChangeSet',
00051      'filter_content_types' : 0,
00052      'immediate_view': 'changeset_edit_form',
00053      'actions': ({'id': 'view',
00054                   'name': 'View Changes',
00055                   'action': 'changeset_view',
00056                   'permissions': (View,),
00057                   'visible':1},
00058                  {'id': 'edit',
00059                   'name': 'Edit Change set',
00060                   'action': 'changeset_edit_form',
00061                   'permissions': (ModifyPortalContent,),
00062                   'visible':1},
00063                  )
00064      },
00065     )
00066 
00067 class BaseChangeSet(Implicit):
00068     """A ChangeSet represents the set of differences between two objects"""
00069 
00070     __implements__ = (IChangeSetZ2,)
00071     implements(IChangeSet)
00072     # This should really not be needed just for same, we should use a method
00073     __allow_access_to_unprotected_subobjects__ = 1
00074     security = ClassSecurityInfo()
00075 
00076     def __init__(self, id, title=''):
00077         """ChangeSet constructor"""
00078         self.id = id
00079         self.title = title
00080         self._diffs = []
00081         self._added = []
00082         self._removed = []
00083         self.ob1_path = []
00084         self.ob2_path = []
00085         self._changesets = {}
00086         self.recursive = 0
00087 
00088     security.declarePublic('getId')
00089     def getId(self):
00090         """ChangeSet id"""
00091         return self.id
00092 
00093     def __getitem__(self, key):
00094         return self._changesets[key]
00095 
00096     def _isSame(self):
00097         """Returns true if there are no differences between the two objects"""
00098         return reduce(lambda x, y: x and y, [d.same for d in self._diffs], 1)
00099 
00100     security.declarePublic('same')
00101     same = ComputedAttribute(_isSame)
00102 
00103     security.declarePublic('computeDiff')
00104     def computeDiff(self, ob1, ob2, recursive=1, exclude=[], id1=None, id2=None):
00105         """Compute the differences from ob1 to ob2 (ie. ob2 - ob1).
00106 
00107         The results can be accessed through getDiffs()"""
00108 
00109         # Reset state
00110         self._diffs = []
00111         self._added = []
00112         self._removed = []
00113         self._changed = []
00114         self._changesets = {}
00115 
00116         purl = getToolByName(self, 'portal_url', None)
00117         if purl is not None:
00118             try:
00119                 self.ob1_path = purl.getRelativeContentPath(ob1)
00120                 self.ob2_path = purl.getRelativeContentPath(ob2)
00121             except AttributeError:
00122                 # one or both of the objects may not have a path
00123                 return
00124         diff_tool = getToolByName(self, "portal_diff")
00125         self._diffs = diff_tool.computeDiff(ob1, ob2, id1=id1, id2=id2)
00126 
00127         if recursive and ob1.isPrincipiaFolderish and \
00128                                                      ob2.isPrincipiaFolderish:
00129             self.recursive = 1
00130             ids1 = set(ob1.objectIds())
00131             ids2 = set(ob2.objectIds())
00132             self._changed = ids1.intersection(ids2)
00133             self._removed = ids1.difference(ids2)
00134             self._added = ids2.difference(ids1)
00135 
00136             # Ignore any excluded items
00137             for id in exclude:
00138                 try:
00139                     self._added.remove(id)
00140                 except ValueError:
00141                     pass
00142                 try:
00143                     self._removed.remove(id)
00144                 except ValueError:
00145                     pass
00146                 try:
00147                     self._changed.remove(id)
00148                 except ValueError:
00149                     pass
00150 
00151             # Calculate a ChangeSet for every subobject that has changed
00152             # XXX this is a little strange as self._changed doesn't appear
00153             # to list changed objects, but rather objects which have been
00154             # reordered or renamed.
00155             for id in self._changed:
00156                 self._addSubSet(id, ob1, ob2, exclude, id1, id2)
00157 
00158     def _addSubSet(self, id, ob1, ob2, exclude, id1, id2):
00159         cs = BaseChangeSet(id, title='Changes to: %s' % id)
00160         cs = cs.__of__(self)
00161         cs.computeDiff(ob1[id], ob2[id], exclude=exclude, id1=id1, id2=id2)
00162         self._changesets[id] = aq_base(cs)
00163 
00164     security.declarePublic('testChanges')
00165     def testChanges(self, ob):
00166         """Test the specified object to determine if the change set will apply without errors"""
00167         for d in self._diffs:
00168             d.testChanges(ob)
00169 
00170         for id in self._changed:
00171             cs = self[id]
00172             child = ob[id]
00173             cs.testChanges(child)
00174 
00175     security.declarePublic('applyChanges')
00176     def applyChanges(self, ob):
00177         """Apply the change set to the specified object"""
00178         for d in self._diffs:
00179             d.applyChanges(ob)
00180 
00181         if self._removed:
00182             ob.manage_delObjects(self._removed)
00183 
00184         for id in self._added:
00185             child = self[id]
00186             ob.manage_clone(child, id)
00187 
00188         for id in self._changed:
00189             cs = self[id]
00190             child = ob[id]
00191             cs.applyChanges(child)
00192 
00193     security.declarePublic('getDiffs')
00194     def getDiffs(self):
00195         """Returns the list differences between the two objects.
00196 
00197         Each difference is a single object implementing the IDifference interface"""
00198         return self._diffs
00199 
00200     security.declarePublic('getSubDiffs')
00201     def getSubDiffs(self):
00202         """If the ChangeSet was computed recursively, returns a list
00203            of ChangeSet objects representing subjects differences
00204 
00205            Each ChangeSet will have the same ID as the objects whose
00206            difference it represents.
00207            """
00208         return [self[id] for id in self._changed]
00209 
00210     security.declarePublic('getAddedItems')
00211     def getAddedItems(self):
00212         """If the ChangeSet was computed recursively, returns the list
00213         of IDs of items that were added.
00214 
00215         A copy of these items is available as a cubject of the ChangeSet
00216         """
00217         return list(self._added)
00218 
00219     security.declarePublic('getRemovedItems')
00220     def getRemovedItems(self):
00221         """If the ChangeSet was computed recursively, returns the list
00222         of IDs of items that were removed"""
00223         return list(self._removed)
00224 
00225 
00226 class ChangeSet(BaseChangeSet, SkinnedFolder, DefaultDublinCoreImpl):
00227     """A persistent skinnable contentish Change Set"""
00228     meta_type = "Change Set"
00229     portal_type = "ChangeSet"
00230     security = ClassSecurityInfo()
00231 
00232     try:
00233         __implements__ = (BaseChangeSet.__implements__ +
00234                             SkinnedFolder.__implements__ +
00235                             DefaultDublinCoreImpl.__implements__)
00236     except TypeError:
00237         # FFF for CMF trunk
00238         __implements__ = (BaseChangeSet.__implements__ +
00239                             (SkinnedFolder.__implements__,))
00240 
00241     def __init__(self, id, title=''):
00242         BaseChangeSet.__init__(self, id, title='')
00243         DefaultDublinCoreImpl.__init__(self)
00244 
00245     def __getitem__(self, key):
00246         SkinnedFolder.__getitem__(self, key)
00247 
00248     def computeDiff(self, ob1, ob2, recursive=1, exclude=[], id1=None, id2=None):
00249         self.manage_delObjects(self.objectIds())
00250         BaseChangeSet.computeDiff(self, ob1, ob2, recursive, exclude, id1, id2)
00251         if recursive and ob1.isPrincipiaFolderish:
00252             # Clone any added subobjects
00253             for id in self._added:
00254                 ob = ob2[id]
00255                 logger.log(logging.DEBUG, "ChangeSet: cloning %s (%s)" % (id, ob))
00256                 try:
00257                     self.manage_clone(ob, id)
00258                 except CopyError:
00259                     # If one of the objects isn't actually local to the ZODB
00260                     # (i.e. it is a version in some other repository), this
00261                     # will fail
00262                     pass
00263 
00264         self._p_changed = 1
00265 
00266     # Override _addSubSet to add persistent sub changesets
00267     def _addSubSet(self, id, ob1, ob2, exclude, id1, id2):
00268         self.manage_addProduct['CMFDiffTool'].manage_addChangeSet(id,
00269                                                   title='Changes to: %s' % id)
00270         transaction.savepoint(optimistic=True)
00271         self[id].computeDiff(ob1[id], ob2[id], exclude=exclude, id1=id1, id2=id2)
00272 
00273 
00274 InitializeClass(ChangeSet)