""" ZSyncerTool a CMF tool that wraps and provides a UI for ZSyncer. Work in progress, may not actually do much yet! """ # Std. lib imports. import copy # Zope imports. from AccessControl import ClassSecurityInfo, Permissions from Acquisition import aq_base from Globals import InitializeClass, DTMLFile from OFS.SimpleItem import SimpleItem # Third-party imports. from Products.CMFCore.utils import UniqueObject, getToolByName # Custom imports. from ZSyncer import ZSyncerObjNotFound try: # CMF >= 1.5 from Products.CMFCore.permissions import ManagePortal except ImportError: # CMF < 1.5 from Products.CMFCore.CMFCorePermissions import ManagePortal from Products.CMFCore.ActionProviderBase import ActionProviderBase from Products.CMFCore.ActionInformation import ActionInformation from Products.CMFCore.Expression import Expression from OFS.Folder import Folder from ZSyncer import ZSyncer, manage_addZSyncer, ZSYNC_PERMISSION from ZSyncer import MISSING, EXTRA, OK, OOD import Config from types import StringType _upgradePaths = {} class ZSyncerTool(UniqueObject, Folder, ActionProviderBase): """ A CMF tool that contains and provides a UI for a ZSyncer instance. XXX Example UI still in the early stages, it will go in skins/. """ id = 'portal_zsyncer' meta_type = 'Portal ZSyncer Tool' _default_zsyncer = 'Default' manage_options = ActionProviderBase.manage_options + Folder.manage_options security = ClassSecurityInfo() _actions = [ ActionInformation( id='zsyncer_fol_status', title='Sync Status', action=Expression(text='string: ${folder_url}/zsyncer_folderview'), condition=Expression(text='python: folder is object'), permissions=(ZSYNC_PERMISSION,), category='folder', visible=Config.show_tool_actions ), ActionInformation( id='zsyncer_obj_status', title='Sync Status', action=Expression(text='string: ${object_url}/zsyncer_objectview'), condition=Expression(text='python: not (folder is object)'), permissions=(ZSYNC_PERMISSION,), category='object', visible=Config.show_tool_actions ), ActionInformation( id='zsyncer_diff', title='Diff', action=Expression(text='string: ${object_url}/zsyncer_diff'), # In the expression text, I'd like to use getToolByName(), but how?? # Only very limited names are bound in the expression context. :-( condition=Expression( text='python:portal.portal_zsyncer.isObjectDiffable(object)' ), permissions=(ZSYNC_PERMISSION,), category='object', visible=Config.show_tool_actions ), ] def filtered_meta_types(self): """Filter meta types. Only ZSyncers can be added inside a ZSyncerTool. """ types = Folder.filtered_meta_types(self) l = [] for t in types: #print t['name'], ZSyncer.meta_type if t['name'] == ZSyncer.meta_type: l.append(t) return l security.declarePrivate('listActions') def listActions(self, info=None): """Return actions provided by tool. """ return self._actions def manage_afterAdd(self, *args, **kwargs): """Configuration after a ZSyncerTool is added. """ # add a default zSyncer if self._default_zsyncer not in self.objectIds(): manage_addZSyncer(self, self._default_zsyncer) # Set its path to the portal root path. portal_path = '/'.join(self.aq_parent.getPhysicalPath()) self[self._default_zsyncer].relative_path_base = portal_path ############################################################## # Wrappers for ZSyncer functionality ############################################################## security.declareProtected(ZSYNC_PERMISSION, 'getStatus') def getStatus(self, path, syncer=None, recurse=None): """Is this object (and subs, if any) in or out of sync? Result is a tuple of (info, subs), where info (and each item in the subs list) is a dictionary with all the useful keys. Also includes anything else useful for UI, e.g. actions we might want to link to. """ syncer = self.getZSyncer(syncer) try: info, subs = syncer.manage_compare(path, recurse) except ZSyncerObjNotFound: # No information found either remotely OR locally, ouch. raise info = self._setDefaults(info) info = self._setActions(info) subs = [self._setActions(self._setDefaults(s)) for s in subs] result = (info, subs) return result security.declareProtected(ZSYNC_PERMISSION, 'getDiff') def getDiff(self, obj_path, syncer=None): """Show differences for this object. """ syncer = self.getZSyncer(syncer) return syncer.manage_diffObject(obj_path) security.declareProtected(ZSYNC_PERMISSION, 'obj_sync') def doPush(self, obj_paths, syncer=None): """Sync object. Return a list of StatusMsgs. """ syncer = self.getZSyncer(syncer) msgs = syncer.manage_pushToRemote(obj_paths) return msgs security.declareProtected(ZSYNC_PERMISSION, 'doDelete') def doDelete(self, obj_paths, syncer=None): """Delete remote AND local object. """ syncer = self.getZSyncer(syncer) msgs = [] syncer.manage_syncDelete(obj_paths, msgs) return msgs security.declareProtected(ZSYNC_PERMISSION, 'getDestinations') def getDestinations(self, syncer=None): """Get a list of destinations for this syncer. """ syncer = self.getZSyncer(syncer) return syncer.dest_servers security.declareProtected(ZSYNC_PERMISSION, 'getPath') def getPath(self, obj, syncer=None): """ Get a path to obj that the syncer can work with. """ path = obj.getPhysicalPath() syncer = self.getZSyncer(syncer) path_info = syncer.getPathInfo(path) return path_info['relative_path'] security.declareProtected(ZSYNC_PERMISSION, 'getObjectFromPath') def getObjectFromPath(self, path, syncer=None): """ Get a path to obj that the syncer can work with. """ syncer = self.getZSyncer(syncer) return syncer._getObject(path) # sf bug #1469129: plone calls an action's condition expression before # checking its permission expression. So, don't restrict this method # too much. "View" is adequate protection, and the action # is still protected by its own permission anyway. security.declareProtected(Permissions.view, 'isObjectDiffable') def isObjectDiffable(self, obj): """ Is the object diffable? """ # Useful for determining whether to show the Diff action. base = aq_base(obj) syncer = self.getZSyncer() if syncer.is_diffable(meta_type=getattr(base, 'portal_type', None)): return 1 if syncer.is_diffable(meta_type=getattr(base, 'meta_type', None)): return 1 return 0 security.declareProtected(ZSYNC_PERMISSION, 'getZSyncer') def getZSyncer(self, name=None): """get the specified (or default) zsyncer. """ if name is None or not name.strip(): name = self._default_zsyncer obj = getattr(self, name, None) if obj is None: raise ValueError, "ZSyncer %s is not found" % name return obj security.declareProtected(ZSYNC_PERMISSION, 'callManyRemote') def callManyRemote(self, methodlist, syncer=None): """Call multiple remote methods and return a list of return values. methodlist should be a sequence of dictionaries with the following keys: 'path': path to the object on which to call the method. 'method_name': name of the method. 'args' (optional): list of positional arguments to pass. 'kwargs' (optional): dictionary of keyword arguments to pass. """ # XXX Other methods could be refactored to use this one. syncer = self.getZSyncer(syncer) return syncer.callManyRemote(methodlist) ####################################################### # Internal methods ####################################################### def _setDefaults(self, adict): # Make sure a status dict has everything the skins need to show it. status = adict.get('status', 'Not Found Anywhere!') adict.setdefault('status', status) adict.setdefault('icon', '') adict.setdefault('status_icon', self.getZSyncer().status_icon(status)) adict.setdefault('status_color', self.getZSyncer().status_colour(status)) return adict def _setActions(self, adict): # Add actions to status info. # Unfortunately this is impossible to do right: # we don't want to include ALL actions, # and we have no idea what types may be installed, # whether their edit/view links follow reasonable convention, # what other actions are 'important', etc. # So this is a stupid hack that works OK for at least some # some CMFDefault types. # XXX this needs better test coverage! default = {'diff': None, 'edit': None, 'view': None, 'sync_status': None, } adict['actions'] = default try: obj = self.getObjectFromPath(adict['relative_path']) except ZSyncerObjNotFound: return adict a_tool = getToolByName(self, 'portal_actions') all_actions = a_tool.listFilteredActionsFor(obj) actions = all_actions['folder'] + all_actions['object'] for a in actions: if a['id'] == 'zsyncer_diff': adict['actions']['diff'] = a elif a['id'] in ('zsyncer_obj_status', 'zsyncer_fol_status'): adict['actions']['sync_status'] = a elif a['name'].lower().strip() == 'view': adict['actions']['view'] = a elif a['name'].lower().strip() == 'edit': adict['actions']['edit'] = a else: pass #print "Useless action %s" % a['name'] return adict def manage_addZSyncerTool(self, id=None, REQUEST=None, submit=None): """add an instance to a folder """ # normally, the defaut id is used. if id is None: id = ZSyncerTool.id self._setObject(id, ZSyncerTool(id)) obj = getattr(self, id) if REQUEST is not None: obj.manage_changeProperties(REQUEST) try: url=self.DestinationURL() except: url=REQUEST['URL1'] url = url + "/manage_main" REQUEST.RESPONSE.redirect(url) constructors = (manage_addZSyncerTool,) InitializeClass(ZSyncerTool)