Back to index

plone3  3.1.7
StandardModifiers.py
Go to the documentation of this file.
00001 #########################################################################
00002 # Copyright (c) 2005 Alberto Berti, Gregoire Weber,
00003 # Reflab(Vincenzo Di Somma, Francesco Ciriaci, Riccardo Lemmi),
00004 # Duncan Booth
00005 #
00006 # All Rights Reserved.
00007 #
00008 # This file is part of CMFEditions.
00009 #
00010 # CMFEditions is free software; you can redistribute it and/or modify
00011 # it under the terms of the GNU General Public License as published by
00012 # the Free Software Foundation; either version 2 of the License, or
00013 # (at your option) any later version.
00014 #
00015 # CMFEditions is distributed in the hope that it will be useful,
00016 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00017 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018 # GNU General Public License for more details.
00019 #
00020 # You should have received a copy of the GNU General Public License
00021 # along with CMFEditions; if not, write to the Free Software
00022 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
00023 #########################################################################
00024 """Standard modifiers
00025 
00026 $Id: StandardModifiers.py,v 1.13 2005/06/26 13:28:36 gregweb Exp $
00027 """
00028 
00029 import sys
00030 from Globals import InitializeClass
00031 
00032 from Acquisition import aq_base
00033 
00034 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
00035 
00036 from Products.CMFCore.utils import getToolByName
00037 from Products.CMFCore.permissions import ManagePortal
00038 from Products.CMFCore.Expression import Expression
00039 
00040 from Products.CMFEditions.interfaces.IModifier import IAttributeModifier
00041 from Products.CMFEditions.interfaces.IModifier import ICloneModifier
00042 from Products.CMFEditions.interfaces.IModifier import ISaveRetrieveModifier
00043 from Products.CMFEditions.interfaces.IModifier import IConditionalTalesModifier
00044 from Products.CMFEditions.interfaces.IModifier import IReferenceAdapter
00045 from Products.CMFEditions.interfaces.IModifier import FileTooLargeToVersionError
00046 from Products.CMFEditions.Modifiers import ConditionalModifier
00047 from Products.CMFEditions.Modifiers import ConditionalTalesModifier
00048 
00049 try:
00050     from Products.Archetypes.interfaces.referenceable import IReferenceable
00051     from Products.Archetypes.config import UUID_ATTR, REFERENCE_ANNOTATION
00052     WRONG_AT=False
00053 except ImportError:
00054     WRONG_AT=True
00055     UUID_ATTR = None
00056     REFERENCE_ANNOTATION = None
00057 
00058 
00059 #----------------------------------------------------------------------
00060 # Product initialziation, installation and factory stuff
00061 #----------------------------------------------------------------------
00062 
00063 def initialize(context):
00064     """Registers modifiers with zope (on zope startup).
00065     """
00066     for m in modifiers:
00067         context.registerClass(
00068             m['wrapper'], m['id'],
00069             permission = ManagePortal,
00070             constructors = (m['form'], m['factory']),
00071             icon = m['icon'],
00072         )
00073 
00074 def install(portal_modifier):
00075     """Registers modifiers in the modifier registry (at tool install time).
00076     """
00077     for m in modifiers:
00078         id = m['id']
00079         if id in portal_modifier.objectIds():
00080             continue
00081         title = m['title']
00082         modifier = m['modifier']()
00083         wrapper = m['wrapper'](id, modifier, title)
00084         enabled = m['enabled']
00085         if IConditionalTalesModifier.isImplementedBy(wrapper):
00086             wrapper.edit(enabled, m['condition'])
00087         else:
00088             wrapper.edit(enabled)
00089 
00090         portal_modifier.register(m['id'], wrapper)
00091 
00092 
00093 manage_OMOutsideChildrensModifierAddForm = PageTemplateFile('www/OMOutsideChildrensModifierAddForm.pt',
00094                                           globals(),
00095                                           __name__='manage_OMOutsideChildrensModifierAddForm')
00096 
00097 def manage_addOMOutsideChildrensModifier(self, id, title=None, REQUEST=None):
00098     """Add an object manager modifier treating childrens as outside refs
00099     """
00100     modifier = OMOutsideChildrensModifier()
00101     self._setObject(id, ConditionalTalesModifier(id, modifier, title))
00102 
00103     if REQUEST is not None:
00104         REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
00105 
00106 
00107 manage_OMInsideChildrensModifierAddForm = PageTemplateFile('www/OMInsideChildrensModifierAddForm.pt',
00108                                           globals(),
00109                                           __name__='manage_OMInsideChildrensModifierAddForm')
00110 
00111 def manage_addOMInsideChildrensModifier(self, id, title=None,
00112                                         REQUEST=None):
00113     """Add an object manager modifier treating children as inside refs
00114     """
00115     modifier = OMInsideChildrensModifier()
00116     self._setObject(id, ConditionalTalesModifier(id, modifier, title))
00117 
00118     if REQUEST is not None:
00119         REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
00120 
00121 
00122 manage_RetainUIDsModifierAddForm =  \
00123                          PageTemplateFile('www/RetainUIDsModifierAddForm.pt',
00124                                           globals(),
00125                                           __name__='manage_RetainUIDsModifierAddForm')
00126 
00127 def manage_addRetainUIDs(self, id, title=None, REQUEST=None):
00128     """Add a modifier retaining UIDs upon retrieve.
00129     """
00130     modifier = RetainUIDs()
00131     self._setObject(id, ConditionalModifier(id, modifier, title))
00132 
00133     if REQUEST is not None:
00134         REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
00135 
00136 
00137 manage_RetainATRefsModifierAddForm =  \
00138                PageTemplateFile('www/RetainATRefsModifierAddForm.pt',
00139                                 globals(),
00140                                 __name__='manage_RetainUIDsModifierAddForm')
00141 
00142 def manage_addRetainATRefs(self, id, title=None, REQUEST=None):
00143     """Add a modifier retaining AT References upon retrieve.
00144     """
00145     modifier = RetainATRefs()
00146     self._setObject(id, ConditionalTalesModifier(id, modifier, title))
00147 
00148     if REQUEST is not None:
00149         REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
00150 
00151 manage_NotRetainATRefsModifierAddForm =  \
00152                PageTemplateFile('www/NotRetainATRefsModifierAddForm.pt',
00153                                 globals(),
00154                                 __name__='manage_NotRetainUIDsModifierAddForm')
00155 
00156 def manage_addNotRetainATRefs(self, id, title=None, REQUEST=None):
00157     """Add a modifier that removes Archetypes references of the working
00158        copy when reverting to a previous version without those references.
00159     """
00160     modifier = NotRetainATRefs()
00161     self._setObject(id, ConditionalTalesModifier(id, modifier, title))
00162 
00163     if REQUEST is not None:
00164         REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
00165 
00166 
00167 manage_RetainWorkflowStateAndHistoryModifierAddForm =  \
00168                          PageTemplateFile('www/RetainWorkflowStateAndHistoryModifierAddForm.pt',
00169                                           globals(),
00170                                           __name__='manage_RetainWorkflowStateAndHistoryModifierAddForm')
00171 
00172 def manage_addRetainWorkflowStateAndHistory(self, id, title=None,
00173                                             REQUEST=None):
00174     """Add a modifier retaining workflow state upon retrieve.
00175     """
00176     modifier = RetainWorkflowStateAndHistory()
00177     self._setObject(id, ConditionalModifier(id, modifier, title))
00178 
00179     if REQUEST is not None:
00180         REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
00181 
00182 
00183 manage_RetainPermissionsSettingsAddForm =  \
00184                          PageTemplateFile('www/RetainPermissionsSettingsModifierAddForm.pt',
00185                                           globals(),
00186                                           __name__='manage_RetainPermissionsSettingsModifierAddForm')
00187 
00188 def manage_addRetainPermissionsSettings(self, id, title=None,
00189                                             REQUEST=None):
00190     """Add a modifier retaining permissions upon retrieve.
00191     """
00192     modifier = RetainPermissionsSettings()
00193     self._setObject(id, ConditionalModifier(id, modifier, title))
00194 
00195     if REQUEST is not None:
00196         REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
00197 
00198 
00199 manage_SaveFileDataInFileTypeByReferenceModifierAddForm =  \
00200                          PageTemplateFile('www/SaveFileDataInFileTypeByReferenceModifierAddForm.pt',
00201                                           globals(),
00202                                           __name__='manage_SaveFileDataInFileTypeByReferenceModifierAddForm')
00203 
00204 def manage_addSaveFileDataInFileTypeByReference(self, id, title=None,
00205                                                 REQUEST=None):
00206     """Add a modifier avoiding unnecessary cloning of file data.
00207     """
00208     modifier = SaveFileDataInFileTypeByReference()
00209     self._setObject(id, ConditionalTalesModifier(id, modifier, title))
00210 
00211     if REQUEST is not None:
00212         REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
00213 
00214 # silly modifier just for demos
00215 manage_SillyDemoRetrieveModifierAddForm =  \
00216     PageTemplateFile('www/SillyDemoRetrieveModifierAddForm.pt', globals(),
00217                      __name__='manage_SillyDemoRetrieveModifierAddForm')
00218 
00219 def manage_addSillyDemoRetrieveModifier(self, id, title=None,
00220                                             REQUEST=None):
00221     """Add a silly demo retrieve modifier
00222     """
00223     modifier = SillyDemoRetrieveModifier()
00224     self._setObject(id, ConditionalTalesModifier(id, modifier, title))
00225 
00226     if REQUEST is not None:
00227         REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
00228 
00229 
00230 manage_AbortVersioningOfLargeFilesAndImagesAddForm =  \
00231     PageTemplateFile('www/AbortVersioningOfLargeFilesAndImagesAddForm.pt',
00232                   globals(),
00233                   __name__='manage_AbortVersioningOfLargeFilesAndImagesAddForm')
00234 
00235 def manage_addAbortVersioningOfLargeFilesAndImages(self, id, title=None,
00236                                             REQUEST=None):
00237     """Add a silly demo retrieve modifier
00238     """
00239     modifier = AbortVersioningOfLargeFilesAndImages(id, title)
00240     self._setObject(id, modifier)
00241 
00242     if REQUEST is not None:
00243         REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
00244 
00245 
00246 manage_SkipVersioningOfLargeFilesAndImagesAddForm =  \
00247     PageTemplateFile('www/SkipVersioningOfLargeFilesAndImagesAddForm.pt',
00248                    globals(),
00249                    __name__='manage_SkipVersioningOfLargeFilesAndImagesAddForm')
00250 
00251 def manage_addSkipVersioningOfLargeFilesAndImages(self, id, title=None,
00252                                             REQUEST=None):
00253     """Add a silly demo retrieve modifier
00254     """
00255     modifier = SkipVersioningOfLargeFilesAndImages(id, title)
00256     self._setObject(id, modifier)
00257 
00258     if REQUEST is not None:
00259         REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
00260 
00261 
00262 
00263 
00264 #----------------------------------------------------------------------
00265 # Standard modifier implementation
00266 #----------------------------------------------------------------------
00267 
00268 class OMBaseModifier:
00269     """Base class for ObjectManager modifiers.
00270     """
00271 
00272     def _getOnCloneModifiers(self, obj):
00273         """Removes all childrens and returns them as references.
00274         """
00275         portal_archivist = getToolByName(obj, 'portal_archivist')
00276         VersionAwareReference = portal_archivist.classes.VersionAwareReference
00277 
00278         # do not pickle the object managers subobjects
00279         refs = {}
00280         result_refs = []
00281         for sub in obj.objectValues():
00282             result_refs.append(sub)
00283             refs[id(aq_base(sub))] = True
00284 
00285         def persistent_id(obj):
00286             try:
00287                 # return a non None value if it is one of the object
00288                 # managers subobjects or raise an KeyError exception
00289                 return refs[id(obj)]
00290             except KeyError:
00291                 # signalize the pickler to just pickle the 'obj' as
00292                 # usual
00293                 return None
00294 
00295         def persistent_load(ignored):
00296             return VersionAwareReference()
00297 
00298         return persistent_id, persistent_load, result_refs
00299 
00300     def _beforeSaveModifier(self, obj, clone):
00301         """Returns all unititialized 'IVersionAwareReference' objects.
00302         """
00303         portal_archivist = getToolByName(obj, 'portal_archivist')
00304         AttributeAdapter = portal_archivist.classes.AttributeAdapter
00305 
00306         # just return adapters to the attributes that were replaced by
00307         # a uninitialzed 'IVersionAwareReference' object
00308         result_refs = []
00309         for name in clone.objectIds():
00310             result_refs.append(AttributeAdapter(clone, name, type="ObjectManager"))
00311 
00312         return result_refs
00313 
00314     def _getAttributeNamesHandlingSubObjects(self, obj, repo_clone):
00315         attrs = ['_objects']
00316         attrs.extend(repo_clone.objectIds())
00317         attrs.extend(obj.objectIds())
00318         return attrs
00319 
00320 class OMOutsideChildrensModifier(OMBaseModifier):
00321     """ObjectManager modifier treating all childrens as outside refs
00322 
00323     Treats all childrens as outside references (the repository layer
00324     knows what to do with that fact).
00325     """
00326 
00327     __implements__ = (ICloneModifier, ISaveRetrieveModifier)
00328 
00329     def getOnCloneModifiers(self, obj):
00330         """Removes all childrens and returns them as references.
00331         """
00332         pers_id, pers_load, outside_refs = self._getOnCloneModifiers(obj)
00333         return pers_id, pers_load, [], outside_refs, ''
00334 
00335     def beforeSaveModifier(self, obj, clone):
00336         """Returns all unititialized 'IVersionAwareReference' objects.
00337 
00338         This allways goes in conjunction with 'getOnCloneModifiers'.
00339         """
00340         outside_refs = self._beforeSaveModifier(obj, clone)
00341         return {}, [], outside_refs
00342 
00343     def afterRetrieveModifier(self, obj, repo_clone, preserve=()):
00344         ref_names = self._getAttributeNamesHandlingSubObjects(obj, repo_clone)
00345 
00346         # Add objects that have been added to the working copy
00347         clone_ids = repo_clone.objectIds()
00348         orig_ids = obj.objectIds()
00349         for attr_name in orig_ids:
00350             if attr_name not in clone_ids:
00351                 new_ob = getattr(obj, attr_name, None)
00352                 if new_ob is not None:
00353                     repo_clone._setOb(attr_name, new_ob)
00354 
00355         # Delete references that are no longer relevant
00356         for attr_name in clone_ids:
00357             if attr_name not in orig_ids:
00358                 try:
00359                     repo_clone._delOb(attr_name)
00360                 except AttributeError:
00361                     pass
00362 
00363         # Restore _objects, so that order is preserved and ids are consistent
00364         orig_objects = getattr(obj, '_objects', None)
00365         if orig_objects is not None:
00366             repo_clone._objects = orig_objects
00367 
00368         return [], ref_names, {}
00369 
00370 InitializeClass(OMOutsideChildrensModifier)
00371 
00372 
00373 class OMInsideChildrensModifier(OMBaseModifier):
00374     """ObjectManager modifier treating all childrens as inside refs
00375 
00376     Treats all childrens as inside references (the repository layer
00377     knows what to do with that fact).
00378     """
00379 
00380     __implements__ = (ICloneModifier, ISaveRetrieveModifier)
00381 
00382     def getOnCloneModifiers(self, obj):
00383         """Removes all childrens and returns them as references.
00384         """
00385         pers_id, pers_load, inside_refs = self._getOnCloneModifiers(obj)
00386         return pers_id, pers_load, inside_refs, [], ''
00387 
00388     def beforeSaveModifier(self, obj, clone):
00389         """Returns all unititialized 'IVersionAwareReference' objects.
00390 
00391         This allways goes in conjunction with 'getOnCloneModifiers'.
00392         """
00393         inside_refs = self._beforeSaveModifier(obj, clone)
00394         return {}, inside_refs, []
00395 
00396     def afterRetrieveModifier(self, obj, repo_clone, preserve=()):
00397         # check if the modifier is called with a valid working copy
00398         if obj is None:
00399             return [], [], {}
00400 
00401         hidhandler = getToolByName(obj, 'portal_historyidhandler')
00402         queryUid = hidhandler.queryUid
00403 
00404         # Inside refs from the original object that have no counterpart
00405         # in the repositories clone have to be deleted from the original.
00406         # The following steps have to be carried out:
00407         #
00408         # 1. list all inside references of the original
00409         # 2. remove from the list the inside references that just will be
00410         #    reverted from the repository
00411         # 3. Return the remaining inside objects that have to be removed
00412         #    from the original.
00413 
00414         # (1) list originals inside references
00415         orig_histids = {}
00416         for id, o in obj.objectItems():
00417             histid = queryUid(o, None)
00418             # there may be objects without history_id
00419             # We want to make sure to delete these on revert
00420             if histid is not None:
00421                 orig_histids[histid] = id
00422             else:
00423                 orig_histids['no_history'+id]=id
00424 
00425         # (2) evaluate the refs that get replaced anyway
00426         for varef in repo_clone.objectValues():
00427             histid = varef.history_id
00428             if histid in orig_histids:
00429                 del orig_histids[histid]
00430 
00431         # (3) build the list of adapters to the references to be removed
00432         refs_to_be_deleted = \
00433             [OMSubObjectAdapter(obj, name) for name in orig_histids.values()]
00434 
00435         # return all attribute names that have something to do with
00436         # referencing
00437         ref_names = self._getAttributeNamesHandlingSubObjects(obj, repo_clone)
00438         return refs_to_be_deleted, ref_names, {}
00439 
00440 InitializeClass(OMInsideChildrensModifier)
00441 
00442 class OMSubObjectAdapter:
00443     """Adapter to an object manager children.
00444     """
00445 
00446     __implements__ = (IReferenceAdapter, )
00447 
00448     def __init__(self, obj, name):
00449         """Initialize the adapter.
00450         """
00451         self._obj = obj
00452         self._name = name
00453 
00454     def save(self, dict):
00455         """See interface
00456         """
00457         dict[self._name] = getattr(aq_base(self._obj), self._name)
00458 
00459     def remove(self, permanent=False):
00460         """See interface
00461         """
00462         # XXX do we want there is the ``manage_afterDelete`` hook called?
00463         # The decision has to go into the interface documentation.
00464         # IM(alecm)O, we should update the catalog if the change is permanent
00465         # and not if otherwise, this forces this Adapter to know a bit about
00466         # implementation details, but it's an optional arg to a specific
00467         # implemetnation of this interface, so I think this is acceptable.
00468         # The only other option I see is to do the deletion in the
00469         # CopyModifyMerge tool which is aware of the distinction between
00470         # retrieve and revert.
00471         if permanent:
00472             self._obj.manage_delObjects(ids=[self._name])
00473         else:
00474             self._obj._delOb(self._name)
00475 
00476 
00477 _marker = []
00478 
00479 class RetainWorkflowStateAndHistory:
00480     """Standard modifier retaining the working copies workflow state
00481 
00482     Avoids the objects workflow state from beeing retrieved also.
00483     """
00484 
00485     __implements__ = (ISaveRetrieveModifier, )
00486 
00487     def beforeSaveModifier(self, obj, clone):
00488         # Saving the ``review_state`` as this is hard to achieve at retreive
00489         # (or because I'm dumb). What happened is that ``getInfoFor`` always
00490         # returned the state of the working copy although the retrieved 
00491         # temporary object was passed to it.
00492         #
00493         # Anyway the review state may be a very interesting piece of 
00494         # information for a hypothetic purge policy ...
00495         wflow = getToolByName(obj, "portal_workflow", None)
00496         if wflow is not None:
00497             review_state = wflow.getInfoFor(obj, "review_state", None)
00498         else:
00499             review_state = None
00500         
00501         return {"review_state": review_state}, [], []
00502 
00503     def afterRetrieveModifier(self, obj, repo_clone, preserve=()):
00504         # check if the modifier is called with a valid working copy
00505         if obj is None:
00506             return [], [], {}
00507 
00508         # replace the workflow stuff of the repository clone by the
00509         # one of the working copy or delete it
00510         if getattr(aq_base(obj), 'review_state', _marker) is not _marker:
00511             repo_clone.review_state = obj.review_state
00512         elif (getattr(aq_base(repo_clone), 'review_state', _marker)
00513               is not _marker):
00514             del repo_clone.review_state
00515 
00516         if getattr(aq_base(obj), 'workflow_history', _marker) is not _marker:
00517             repo_clone.workflow_history = obj.workflow_history
00518         elif (getattr(aq_base(repo_clone), 'workflow_history', _marker)
00519               is not _marker):
00520             del repo_clone.workflow_history
00521 
00522         return [], [], {}
00523 
00524 InitializeClass(RetainWorkflowStateAndHistory)
00525 
00526 class RetainPermissionsSettings:
00527     """Standard modifier retaining permissions settings
00528 
00529     This is nearly essential if we are going to be retaining workflow.
00530     """
00531 
00532     __implements__ = (ISaveRetrieveModifier, )
00533 
00534     def beforeSaveModifier(self, obj, clone):
00535         return {}, [], []
00536 
00537     def afterRetrieveModifier(self, obj, repo_clone, preserve=()):
00538         # check if the modifier is called with a valid working copy
00539         if obj is None:
00540             return [], [], {}
00541 
00542         # replace the permission stuff of the repository clone by the
00543         # one of the working copy or delete it
00544         for key, val in obj.__dict__.items():
00545             # Find permission settings
00546             if key.startswith('_') and key.endswith('_Permission'):
00547                 setattr(repo_clone, key, val)
00548 
00549         return [], [], {}
00550 
00551 InitializeClass(RetainPermissionsSettings)
00552 
00553 class RetainUIDs:
00554     """Modifier which ensures uid consistency by retaining the uid from the working copy.  Ensuring
00555        that newly created objects are assigned an appropriate uid is a job for the repository tool.
00556     """
00557 
00558     __implements__ = (ISaveRetrieveModifier, )
00559 
00560     def beforeSaveModifier(self, obj, clone):
00561         return {}, [], []
00562 
00563     def afterRetrieveModifier(self, obj, repo_clone, preserve=()):
00564         # check if the modifier is called with a valid working copy
00565         if obj is None:
00566             return [], [], {}
00567 
00568         #Preserve CMFUid
00569         uid_tool = getToolByName(obj, 'portal_historyidhandler', None)
00570         if uid_tool is not None:
00571             working_uid = uid_tool.queryUid(obj)
00572             copy_uid = uid_tool.queryUid(repo_clone)
00573             anno_tool = getToolByName(obj, 'portal_uidannotation')
00574             annotation = anno_tool(repo_clone, uid_tool.UID_ATTRIBUTE_NAME)
00575             annotation.setUid(working_uid)
00576 
00577         #Preserve ATUID
00578         uid = getattr(aq_base(obj), 'UID', None)
00579         if UUID_ATTR is not None and uid is not None and callable(obj.UID):
00580             working_atuid = obj.UID()
00581             repo_uid = repo_clone.UID()
00582             setattr(repo_clone, UUID_ATTR, working_atuid)
00583             if working_atuid != repo_uid:
00584                 # XXX: We need to do something with forward references
00585                 annotations = repo_clone._getReferenceAnnotations()
00586                 for ref in annotations.objectValues():
00587                     ref.sourceUID = working_atuid
00588 
00589         return [], [], {}
00590 
00591 InitializeClass(RetainUIDs)
00592 
00593 class RetainATRefs:
00594     """Modifier which ensures the Archetypes references of the working
00595        copy are preserved when reverting to a previous version
00596     """
00597 
00598     __implements__ = (ISaveRetrieveModifier, )
00599 
00600     def beforeSaveModifier(self, obj, clone):
00601         return {}, [], []
00602 
00603     def afterRetrieveModifier(self, obj, repo_clone, preserve=()):
00604         # check if the modifier is called with a valid working copy
00605         if obj is None:
00606             return [], [], {}
00607 
00608         if not WRONG_AT:
00609             if IReferenceable.isImplementedBy(obj) \
00610                 and hasattr(aq_base(obj), REFERENCE_ANNOTATION):
00611                 #Preserve AT references
00612                 orig_refs_container = getattr(aq_base(obj), REFERENCE_ANNOTATION)
00613                 setattr(repo_clone, REFERENCE_ANNOTATION, orig_refs_container)
00614         return [], [], {}
00615 
00616 InitializeClass(RetainATRefs)
00617 
00618 class NotRetainATRefs:
00619     """Modifier which removes Archetypes references of the working
00620        copy when reverting to a previous version without those references.
00621        We need to remove them explicitly by calling deleteReference() to
00622        keep the reference_catalog in sync, and to call the delHook().
00623     """
00624 
00625     __implements__ = (ISaveRetrieveModifier, )
00626 
00627     def beforeSaveModifier(self, obj, clone):
00628         return {}, [], []
00629 
00630     def afterRetrieveModifier(self, obj, repo_clone, preserve=()):
00631         # check if the modifier is called with a valid working copy
00632         if obj is None:
00633             return [], [], {}
00634 
00635         if not WRONG_AT:
00636             if IReferenceable.isImplementedBy(obj) and hasattr(aq_base(obj), REFERENCE_ANNOTATION) \
00637             and hasattr(aq_base(repo_clone), REFERENCE_ANNOTATION):
00638                 #Remove AT references that no longer exists in the retrived version
00639                 orig_refs_container = getattr(aq_base(obj), REFERENCE_ANNOTATION)
00640                 repo_clone_refs_container = getattr(aq_base(repo_clone), REFERENCE_ANNOTATION)
00641                 ref_objs = orig_refs_container.objectValues()
00642                 repo_clone_ref_ids = repo_clone_refs_container.objectIds()
00643 
00644                 reference_catalog = getToolByName(obj, 'reference_catalog')
00645                 if reference_catalog:
00646                     for ref in ref_objs:
00647                         if ref.getId() not in repo_clone_ref_ids:
00648                             reference_catalog.deleteReference(ref.sourceUID, ref.targetUID,
00649                                                               ref.relationship)
00650                 
00651         return [], [], {}
00652 
00653 InitializeClass(NotRetainATRefs)
00654 
00655 class SaveFileDataInFileTypeByReference:
00656     """Standard modifier avoiding unnecessary cloning of the file data.
00657 
00658     Called on 'Portal File' objects.
00659     """
00660 
00661     __implements__ = (IAttributeModifier, )
00662 
00663     def getReferencedAttributes(self, obj):
00664         return {'data': getattr(aq_base(obj),'data', None)}
00665 
00666     def reattachReferencedAttributes(self, obj, attrs_dict):
00667 
00668         obj = aq_base(obj)
00669         for name, attr_value in attrs_dict.items():
00670             setattr(obj, name, attr_value)
00671 
00672 
00673 InitializeClass(SaveFileDataInFileTypeByReference)
00674 
00675 class SillyDemoRetrieveModifier:
00676     """Silly Retrieve Modifier for Demos
00677 
00678     Disabled by default and if enabled only effective if the 
00679     username is ``gregweb``.
00680 
00681     This is really just as silly example though for demo purposes!!!
00682     """
00683 
00684     __implements__ = (ISaveRetrieveModifier, )
00685 
00686     def beforeSaveModifier(self, obj, clone):
00687         return {}, [], []
00688 
00689     def afterRetrieveModifier(self, obj, repo_clone, preserve=()):
00690         from AccessControl import getSecurityManager
00691         if getSecurityManager().getUser().getUserName() != "gregweb":
00692             return [], [], {}
00693 
00694         if repo_clone.portal_type != "Document":
00695             return [], [], {}
00696 
00697         # sorry: hack
00698         clone = repo_clone.__of__(obj.aq_inner.aq_parent)
00699 
00700         # replace all occurences of DeMo with Demo and deMo with demo
00701         text = clone.EditableBody()
00702         text = text.replace("DeMo", "Demo").replace("deMo", "demo")
00703         clone.setText(text)
00704 
00705         return [], [], {}
00706 
00707 InitializeClass(SillyDemoRetrieveModifier)
00708 
00709 
00710 ANNOTATION_PREFIX = 'Archetypes.storage.AnnotationStorage-'
00711 class AbortVersioningOfLargeFilesAndImages(ConditionalTalesModifier):
00712     """Raises an error if a file or image attribute stored on the
00713     object in a specified field is larger than a fixed default"""
00714 
00715     field_names = ('file', 'image')
00716     max_size = 26214400 # This represents a 400 element long Pdata list
00717 
00718     __implements__ = (IConditionalTalesModifier, ICloneModifier,)
00719 
00720     modifierEditForm = PageTemplateFile('www/fieldModifierEditForm.pt',
00721                                         globals(),
00722                                         __name__='modifierEditForm')
00723 
00724     _condition = Expression("python: portal_type in ('Image', 'File')")
00725 
00726     def __init__(self, id='AbortVersioningOfLargeFilesAndImages', modifier=None,
00727                  title=''):
00728         self.id = str(id)
00729         self.title = str(title)
00730         self.meta_type = 'edmod_%s' % id
00731         self._enabled = False
00732 
00733     def edit(self, enabled=None, condition=None, title='', field_names=None,
00734              max_size=None, REQUEST=None):
00735         """See IConditionalTalesModifier.
00736         """
00737         if max_size is not None:
00738             self.max_size = int(max_size)
00739         if field_names is not None:
00740             field_names = tuple(s.strip() for s in field_names.split('\n') if s)
00741             if field_names != self.field_names:
00742                 self.field_names = field_names
00743         return ConditionalTalesModifier.edit(self, enabled, condition, title)
00744 
00745     def getFieldNames(self):
00746         """For the edit form"""
00747         return '\n'.join(self.field_names)
00748 
00749     def getModifier(self):
00750         """We are the modifier, not some silly wrapper. """
00751         return self
00752 
00753     def _getFieldValues(self, obj):
00754         """Finds the specified field values and returns them if
00755         they contain file objects which are too large.  Specifically,
00756         it returns an iterator of tuples containing the type of storage,
00757         the field name, and the value stored"""
00758         max_size  = self.max_size
00759 
00760         # Search for fields stored via AnnotationStorage
00761         annotations = getattr(obj, '__annotations__', None)
00762         if annotations is not None:
00763             annotation_names = (ANNOTATION_PREFIX + name for name in
00764                                                               self.field_names)
00765             for name in annotation_names:
00766                 val = annotations.get(name, None)
00767                 # Skip linked Pdata chains too long for the pickler
00768                 if hasattr(aq_base(val), 'getSize') and callable(val.getSize):
00769                     size = val.getSize()
00770                     if isinstance(size, (int, long)) and size >= max_size:
00771                         yield 'annotation', name, val
00772 
00773         # Search for fields stored via AttributeStorage
00774         for name in self.field_names:
00775             val = getattr(obj, name, None)
00776             # Skip linked Pdata chains too long for the pickler
00777             if hasattr(aq_base(val), 'getSize') and callable(val.getSize):
00778                 size = val.getSize()
00779                 if isinstance(size, int) and size >= max_size:
00780                     yield 'attribute', name, val
00781 
00782     def getOnCloneModifiers(self, obj):
00783         """Detects large file objects and raises an error
00784         """
00785         for storage, name, val in self._getFieldValues(obj):
00786             # if we find a file that's too big, abort
00787             raise FileTooLargeToVersionError
00788         return None # no effect otherwise
00789 
00790 InitializeClass(AbortVersioningOfLargeFilesAndImages)
00791 
00792 _empty_marker =[]
00793 class LargeFilePlaceHolder(object):
00794     """PlaceHolder for a large object"""
00795     @staticmethod
00796     def getSize():
00797         return sys.maxint
00798 
00799 class SkipVersioningOfLargeFilesAndImages(AbortVersioningOfLargeFilesAndImages):
00800     """Replaces any excessively large file and images stored as
00801     annotations or attributes on the object with a marker.  On
00802     retrieve, the marker will be replaced with the current value.."""
00803 
00804     __implements__ = (IConditionalTalesModifier, ICloneModifier,
00805                       ISaveRetrieveModifier)
00806 
00807     def getOnCloneModifiers(self, obj):
00808         """Removes large file objects and returns them as references
00809         """
00810         refs = {}
00811         ref_list = []
00812         for storage, name, val in self._getFieldValues(obj):
00813             ref_list.append(val)
00814             refs[id(val)] = True
00815 
00816         if not refs:
00817             return None # don't do anything
00818 
00819         def persistent_id(obj):
00820             return refs.get(id(obj), None)
00821 
00822         def persistent_load(ignored):
00823             return LargeFilePlaceHolder()
00824 
00825         return persistent_id, persistent_load, [], []
00826 
00827     def beforeSaveModifier(self, obj, clone):
00828         """Does nothing, the pickler does the work"""
00829         return {}, [], []
00830 
00831     def afterRetrieveModifier(self, obj, repo_clone, preserve=()):
00832         """If we find any LargeFilePlaceHolders, replace them with the
00833         values from the current working copy.  If the values are missing
00834         from the working copy, remove them from the retrieved object."""
00835         # Search for fields stored via AnnotationStorage
00836         annotations = getattr(obj, '__annotations__', None)
00837         orig_annotations = getattr(repo_clone, '__annotations__', None)
00838         for storage, name, orig_val in self._getFieldValues(repo_clone):
00839             if isinstance(orig_val, LargeFilePlaceHolder):
00840                 if storage == 'annotation':
00841                     val = _empty_marker
00842                     if annotations is not None:
00843                         val = annotations.get(name, _empty_marker)
00844                     if val is not _empty_marker:
00845                         orig_annotations[name] = val
00846                     else:
00847                         # remove the annotation if not present on the
00848                         # working copy, or if annotations are missing
00849                         # entirely
00850                         del orig_annotations[name]
00851                 else: # attribute storage
00852                     val = getattr(obj, name, _empty_marker)
00853                     if val is not _empty_marker:
00854                         setattr(repo_clone, name, val)
00855                     else:
00856                         delattr(repo_clone, name)
00857         return [], [], {}
00858 
00859 InitializeClass(SkipVersioningOfLargeFilesAndImages)
00860 
00861 
00862 #----------------------------------------------------------------------
00863 # Standard modifier configuration
00864 #----------------------------------------------------------------------
00865 
00866 modifiers = (
00867     {
00868         'id': 'OMInsideChildrensModifier',
00869         'title': "Modifier for object managers treating children as inside objects.",
00870         'enabled': False,
00871         'condition': 'python: False',
00872         'wrapper': ConditionalTalesModifier,
00873         'modifier': OMInsideChildrensModifier,
00874         'form': manage_OMInsideChildrensModifierAddForm,
00875         'factory': manage_addOMInsideChildrensModifier,
00876         'icon': 'www/modifier.gif',
00877     },
00878     {
00879         'id': 'OMOutsideChildrensModifier',
00880         'title': "Modifier for object managers (like standard folders) treating children as outside objects.",
00881         'enabled': True,
00882         'condition': "python: portal_type=='Folder'",
00883         'wrapper': ConditionalTalesModifier,
00884         'modifier': OMOutsideChildrensModifier,
00885         'form': manage_OMOutsideChildrensModifierAddForm,
00886         'factory': manage_addOMOutsideChildrensModifier,
00887         'icon': 'www/modifier.gif',
00888     },
00889     {
00890         'id': 'RetainUIDs',
00891         'title': "Retains the CMF and AT UIDs from the working copy",
00892         'enabled': True,
00893         'wrapper': ConditionalModifier,
00894         'modifier': RetainUIDs,
00895         'form': manage_RetainUIDsModifierAddForm,
00896         'factory': manage_addRetainUIDs,
00897         'icon': 'www/modifier.gif',
00898     },
00899     {
00900         'id': 'RetainATRefs',
00901         'title': "Retains AT refs",
00902         'enabled': False,
00903         'condition': 'python: False',
00904         'wrapper': ConditionalTalesModifier,
00905         'modifier': RetainATRefs,
00906         'form': manage_RetainATRefsModifierAddForm,
00907         'factory': manage_addRetainATRefs,
00908         'icon': 'www/modifier.gif',
00909     },
00910     {
00911         'id': 'NotRetainATRefs',
00912         'title': "Handles removal of AT refs that no longer exists when reverting",
00913         'enabled': True,
00914         'condition': 'python: True',
00915         'wrapper': ConditionalTalesModifier,
00916         'modifier': NotRetainATRefs,
00917         'form': manage_NotRetainATRefsModifierAddForm,
00918         'factory': manage_addNotRetainATRefs,
00919         'icon': 'www/modifier.gif',
00920     },
00921     {
00922         'id': 'RetainWorkflowStateAndHistory',
00923         'title': "Retains the working copies workflow state upon retrieval/revertion.",
00924         'enabled': True,
00925         'wrapper': ConditionalModifier,
00926         'modifier': RetainWorkflowStateAndHistory,
00927         'form': manage_RetainWorkflowStateAndHistoryModifierAddForm,
00928         'factory': manage_addRetainWorkflowStateAndHistory,
00929         'icon': 'www/modifier.gif',
00930     },
00931     {
00932         'id': 'RetainPermissionsSettings',
00933         'title': "Retains the permission settings upon retrieval/revertion.",
00934         'enabled': True,
00935         'wrapper': ConditionalModifier,
00936         'modifier': RetainPermissionsSettings,
00937         'form': manage_RetainPermissionsSettingsAddForm ,
00938         'factory': manage_addRetainPermissionsSettings,
00939         'icon': 'www/modifier.gif',
00940     },
00941     {
00942         'id': 'SaveFileDataInFileTypeByReference',
00943         'title': "Let's the storage optimize cloning of file data.",
00944         'enabled': True,
00945         'condition': "python: meta_type=='Portal File'",
00946         'wrapper': ConditionalTalesModifier,
00947         'modifier': SaveFileDataInFileTypeByReference,
00948         'form': manage_SaveFileDataInFileTypeByReferenceModifierAddForm,
00949         'factory': manage_addSaveFileDataInFileTypeByReference,
00950         'icon': 'www/modifier.gif',
00951     },
00952     {
00953         'id': 'SillyDemoRetrieveModifier',
00954         'title': "Silly retrive modifier for demos only.",
00955         'enabled': False,
00956         'condition': "python: True",
00957         'wrapper': ConditionalTalesModifier,
00958         'modifier': SillyDemoRetrieveModifier,
00959         'form': manage_SillyDemoRetrieveModifierAddForm,
00960         'factory': manage_addSillyDemoRetrieveModifier,
00961         'icon': 'www/modifier.gif',
00962     },
00963     {
00964         'id': 'AbortVersioningOfLargeFilesAndImages',
00965         'title': "Abort versioning of objects if file data if it's too large",
00966         'enabled': True,
00967         'condition': "python: portal_type in ('Image', 'File')",
00968         'wrapper': AbortVersioningOfLargeFilesAndImages,
00969         'modifier': AbortVersioningOfLargeFilesAndImages,
00970         'form': manage_AbortVersioningOfLargeFilesAndImagesAddForm,
00971         'factory': manage_addAbortVersioningOfLargeFilesAndImages,
00972         'icon': 'www/modifier.gif',
00973     },
00974     {
00975         'id': 'SkipVersioningOfLargeFilesAndImages',
00976         'title': "Skip versioning of objects if file data if it's too large",
00977         'enabled': False,
00978         'condition': "python: portal_type in ('Image', 'File')",
00979         'wrapper': SkipVersioningOfLargeFilesAndImages,
00980         'modifier': SkipVersioningOfLargeFilesAndImages,
00981         'form': manage_SkipVersioningOfLargeFilesAndImagesAddForm,
00982         'factory': manage_addSkipVersioningOfLargeFilesAndImages,
00983         'icon': 'www/modifier.gif',
00984     },
00985 )