Back to index

plone3  3.1.7
PloneFolder.py
Go to the documentation of this file.
00001 from types import StringType
00002 from Globals import InitializeClass
00003 from zExceptions import NotFound
00004 from Acquisition import aq_base
00005 from Acquisition import aq_inner
00006 from Acquisition import aq_parent
00007 from AccessControl import Permissions
00008 from AccessControl import Unauthorized
00009 from AccessControl import ClassSecurityInfo
00010 from ComputedAttribute import ComputedAttribute
00011 
00012 from OFS.Folder import Folder
00013 from OFS.ObjectManager import REPLACEABLE
00014 from DocumentTemplate.sequence import sort
00015 from webdav.NullResource import NullResource
00016 from webdav.WriteLockInterface import WriteLockInterface
00017 from webdav.interfaces import IWriteLock
00018 
00019 from Products.CMFCore.utils import getToolByName
00020 from Products.CMFCore.CMFCatalogAware import CMFCatalogAware
00021 from Products.CMFCore.PortalFolder import PortalFolderBase
00022 from Products.CMFCore.permissions import AccessContentsInformation, \
00023                     AddPortalContent, AddPortalFolders, ListFolderContents, \
00024                     ManageProperties, ModifyPortalContent, View
00025 from Products.CMFDefault.DublinCore import DefaultDublinCoreImpl
00026 
00027 from zope.interface import implements
00028 from zope.app.container.contained import notifyContainerModified
00029 
00030 # ATM it's safer to define our own
00031 from interfaces.OrderedContainer import IOrderedContainer
00032 from OFS.IOrderSupport import IOrderedContainer as IZopeOrderedContainer
00033 
00034 class ReplaceableWrapper:
00035     """A wrapper around an object to make it replaceable."""
00036     def __init__(self, ob):
00037         self.__ob = ob
00038 
00039     def __getattr__(self, name):
00040         if name == '__replaceable__':
00041             return REPLACEABLE
00042         return getattr(self.__ob, name)
00043 
00044 # Portions of this class was copy/pasted from the OFS.Folder.OrderedFolder
00045 # from Zope2.7.  This class is licensed under the ZPL 2.0 as stated here:
00046 # http://www.zope.org/Resources/ZPL
00047 # Zope Public License (ZPL) Version 2.0
00048 # This software is Copyright (c) Zope Corporation (tm) and Contributors.
00049 # All rights reserved.
00050 
00051 class OrderedContainer(Folder):
00052     """Folder with subobject ordering support."""
00053 
00054     __implements__ = (IOrderedContainer, IZopeOrderedContainer)
00055 
00056     security = ClassSecurityInfo()
00057 
00058     security.declareProtected(ModifyPortalContent, 'moveObject')
00059     def moveObject(self, id, position):
00060         obj_idx  = self.getObjectPosition(id)
00061         if obj_idx == position:
00062             return None
00063         elif position < 0:
00064             position = 0
00065 
00066         metadata = list(self._objects)
00067         obj_meta = metadata.pop(obj_idx)
00068         metadata.insert(position, obj_meta)
00069         self._objects = tuple(metadata)
00070 
00071     # Here the implementation of IOrderedContainer starts
00072     # Once Plone depends on Zope 2.7 this should be replaced by mixing in
00073     # the 2.7 specific class OFS.OrderedContainer.OrderedContainer
00074 
00075     security.declareProtected(ModifyPortalContent, 'moveObjectsByDelta')
00076     def moveObjectsByDelta(self, ids, delta, subset_ids=None,
00077                            suppress_events=False):
00078         """Move specified sub-objects by delta."""
00079         if type(ids) is StringType:
00080             ids = (ids,)
00081         min_position = 0
00082         objects = list(self._objects)
00083         if subset_ids == None:
00084             subset_ids = self.getCMFObjectsSubsetIds(objects)
00085         else:
00086             subset_ids = list(subset_ids)
00087         # unify moving direction
00088         if delta > 0:
00089             ids = list(ids)
00090             ids.reverse()
00091             subset_ids.reverse()
00092         counter = 0
00093 
00094         for id in ids:
00095             try:
00096                 old_position = subset_ids.index(id)
00097             except ValueError:
00098                 continue
00099             new_position = max(old_position - abs(delta), min_position)
00100             if new_position == min_position:
00101                 min_position += 1
00102             if not old_position == new_position:
00103                 subset_ids.remove(id)
00104                 subset_ids.insert(new_position, id)
00105                 counter += 1
00106 
00107         if counter > 0:
00108             if delta > 0:
00109                 subset_ids.reverse()
00110             obj_dict = {}
00111             for obj in objects:
00112                 obj_dict[obj['id']] = obj
00113             pos = 0
00114             for i in range(len(objects)):
00115                 if objects[i]['id'] in subset_ids:
00116                     try:
00117                         objects[i] = obj_dict[subset_ids[pos]]
00118                         pos += 1
00119                     except KeyError:
00120                         raise ValueError('The object with the id "%s" does '
00121                                          'not exist.' % subset_ids[pos])
00122             self._objects = tuple(objects)
00123 
00124         if not suppress_events:
00125             notifyContainerModified(self)
00126 
00127         return counter
00128 
00129     security.declarePrivate('getCMFObjectsSubsetIds')
00130     def getCMFObjectsSubsetIds(self, objs):
00131         """Get the ids of only cmf objects (used for moveObjectsByDelta)."""
00132         ttool = getToolByName(self, 'portal_types')
00133         cmf_meta_types = [ti.Metatype() for ti in ttool.listTypeInfo()]
00134         return [obj['id'] for obj in objs if obj['meta_type'] in cmf_meta_types]
00135 
00136     security.declareProtected(ModifyPortalContent, 'getObjectPosition')
00137     def getObjectPosition(self, id):
00138 
00139         objs = list(self._objects)
00140         om = [objs.index(om) for om in objs if om['id']==id]
00141 
00142         if om: # only 1 in list if any
00143             return om[0]
00144 
00145         raise NotFound, 'Object %s was not found' % str(id)
00146 
00147     security.declareProtected(ModifyPortalContent, 'moveObjectsUp')
00148     def moveObjectsUp(self, ids, delta=1, RESPONSE=None):
00149         """Move an object up."""
00150         self.moveObjectsByDelta(ids, -delta)
00151         if RESPONSE is not None:
00152             RESPONSE.redirect('manage_workspace')
00153 
00154     security.declareProtected(ModifyPortalContent, 'moveObjectsDown')
00155     def moveObjectsDown(self, ids, delta=1, RESPONSE=None):
00156         """Move an object down."""
00157         self.moveObjectsByDelta(ids, delta)
00158         if RESPONSE is not None:
00159             RESPONSE.redirect('manage_workspace')
00160 
00161     security.declareProtected(ModifyPortalContent, 'moveObjectsToTop')
00162     def moveObjectsToTop(self, ids, RESPONSE=None):
00163         """Move an object to the top."""
00164         self.moveObjectsByDelta(ids, - len(self._objects))
00165         if RESPONSE is not None:
00166             RESPONSE.redirect('manage_workspace')
00167 
00168     security.declareProtected(ModifyPortalContent, 'moveObjectsToBottom')
00169     def moveObjectsToBottom(self, ids, RESPONSE=None):
00170         """Move an object to the bottom."""
00171         self.moveObjectsByDelta(ids, len(self._objects))
00172         if RESPONSE is not None:
00173             RESPONSE.redirect('manage_workspace')
00174 
00175     security.declareProtected(ModifyPortalContent, 'moveObjectToPosition')
00176     def moveObjectToPosition(self, id, position, suppress_events=False):
00177         """Move specified object to absolute position."""
00178         delta = position - self.getObjectPosition(id)
00179         return self.moveObjectsByDelta(id, delta,
00180                                        suppress_events=suppress_events)
00181 
00182     security.declareProtected(ModifyPortalContent, 'orderObjects')
00183     def orderObjects(self, key, reverse=None):
00184         """Order sub-objects by key and direction."""
00185         ids = [id for id, obj in sort(self.objectItems(),
00186                                       ((key, 'cmp', 'asc'),))]
00187         if reverse:
00188             ids.reverse()
00189         return self.moveObjectsByDelta(ids, -len(self._objects))
00190 
00191     # Here the implementation of IOrderedContainer ends
00192 
00193     def manage_renameObject(self, id, new_id, REQUEST=None):
00194         """Rename a particular sub-object."""
00195         objidx = self.getObjectPosition(id)
00196         method = OrderedContainer.inheritedAttribute('manage_renameObject')
00197         result = method(self, id, new_id, REQUEST)
00198         self.moveObject(new_id, objidx)
00199         putils = getToolByName(self, 'plone_utils')
00200         putils.reindexOnReorder(self)
00201         return result
00202 
00203 InitializeClass(OrderedContainer)
00204 
00205 class BasePloneFolder(CMFCatalogAware, PortalFolderBase, DefaultDublinCoreImpl):
00206     """Implements basic Plone folder functionality except ordering support.
00207     """
00208 
00209     security = ClassSecurityInfo()
00210 
00211     __implements__ = DefaultDublinCoreImpl.__implements__ + \
00212                      (PortalFolderBase.__implements__,WriteLockInterface)
00213 
00214     implements(IWriteLock)
00215 
00216     manage_options = Folder.manage_options + \
00217                      CMFCatalogAware.manage_options
00218 
00219     # Fix permissions set by CopySupport.py
00220     __ac_permissions__ = (
00221         ('Modify portal content',
00222          ('manage_cutObjects', 'manage_pasteObjects',
00223           'manage_renameForm', 'manage_renameObject',
00224           'manage_renameObjects',)),
00225         )
00226 
00227     security.declareProtected(Permissions.copy_or_move, 'manage_copyObjects')
00228 
00229     def __init__(self, id, title=''):
00230         DefaultDublinCoreImpl.__init__(self)
00231         self.id = id
00232         self.title = title
00233 
00234     def __call__(self):
00235         """Invokes the default view."""
00236         ti = self.getTypeInfo()
00237         method_id = ti and ti.queryMethodId('(Default)', context=self)
00238         if method_id:
00239             method = getattr(self, method_id)
00240             # XXX view is not defined!
00241             if getattr(aq_base(view), 'isDocTemp', 0):
00242                 return method(self, self.REQUEST, self.REQUEST['RESPONSE'])
00243             else:
00244                 return method()
00245         else:
00246             raise NotFound( 'Cannot find default view for "%s"' %
00247                             '/'.join( self.getPhysicalPath() ) )
00248 
00249     security.declareProtected(Permissions.view, 'view')
00250     view = __call__
00251 
00252     def index_html(self):
00253         """Acquire if not present."""
00254         request = getattr(self, 'REQUEST', None)
00255         if request and request.has_key('REQUEST_METHOD'):
00256             if request.maybe_webdav_client:
00257                 method = request['REQUEST_METHOD']
00258                 if method in ('PUT',):
00259                     # Very likely a WebDAV client trying to create something
00260                     return ReplaceableWrapper(NullResource(self, 'index_html'))
00261                 elif method in ('GET', 'HEAD', 'POST'):
00262                     # Do nothing, let it go and acquire.
00263                     pass
00264                 else:
00265                     raise AttributeError, 'index_html'
00266         # Acquire from parent
00267         _target = aq_parent(aq_inner(self)).aq_acquire('index_html')
00268         return ReplaceableWrapper(aq_base(_target).__of__(self))
00269 
00270     index_html = ComputedAttribute(index_html, 1)
00271 
00272     security.declareProtected(AddPortalFolders, 'manage_addPloneFolder')
00273     def manage_addPloneFolder(self, id, title='', REQUEST=None):
00274         """Adds a new PloneFolder."""
00275         ob = PloneFolder(id, title)
00276         self._setObject(id, ob)
00277         if REQUEST is not None:
00278             # TODO HARDCODED FIXME!
00279             return self.folder_contents(self, REQUEST)
00280 
00281     manage_addFolder = manage_addPloneFolder
00282     manage_renameObject = PortalFolderBase.manage_renameObject
00283 
00284     security.declareProtected(Permissions.delete_objects, 'manage_delObjects')
00285     def manage_delObjects(self, ids=[], REQUEST=None):
00286         """We need to enforce security."""
00287         mt = getToolByName(self, 'portal_membership')
00288         if type(ids) is StringType:
00289             ids = [ids]
00290         for id in ids:
00291             item = self._getOb(id)
00292             if not mt.checkPermission(Permissions.delete_objects, item):
00293                 raise Unauthorized, (
00294                     "Do not have permissions to remove this object")
00295         return PortalFolderBase.manage_delObjects(self, ids, REQUEST=REQUEST)
00296 
00297     def __browser_default__(self, request):
00298         """Set default so we can return whatever we want instead
00299         of index_html."""
00300         return getToolByName(self, 'plone_utils').browserDefault(self)
00301 
00302     security.declarePublic('contentValues')
00303     def contentValues(self, filter=None, sort_on=None, reverse=0):
00304         """Able to sort on field."""
00305         values = PortalFolderBase.contentValues(self, filter=filter)
00306         if sort_on is not None:
00307             values.sort(lambda x, y,
00308                         sort_on=sort_on: safe_cmp(getattr(x,sort_on),
00309                                                   getattr(y,sort_on)))
00310         if reverse:
00311             values.reverse()
00312 
00313         return values
00314 
00315     security.declareProtected(ListFolderContents, 'listFolderContents')
00316     def listFolderContents(self, contentFilter=None,
00317                            suppressHiddenFiles=0):
00318         """Optionally you can suppress "hidden" files, or files that
00319         begin with .
00320         """
00321         contents = PortalFolderBase.listFolderContents(self,
00322                                                   contentFilter=contentFilter)
00323         if suppressHiddenFiles:
00324             contents = [obj for obj in contents if obj.getId()[:1]!='.']
00325         return contents
00326 
00327     security.declareProtected(AccessContentsInformation,
00328                               'folderlistingFolderContents')
00329     def folderlistingFolderContents(self, contentFilter=None,
00330                                     suppressHiddenFiles=0):
00331         """Calls listFolderContents in protected only by ACI so that
00332         folder_listing can work without the List folder contents permission,
00333         as in CMFDefault.
00334         """
00335         return self.listFolderContents(contentFilter, suppressHiddenFiles)
00336 
00337     # Override CMFCore's invokeFactory to return the id returned by the
00338     # factory in case the factory modifies the id
00339     security.declareProtected(AddPortalContent, 'invokeFactory')
00340     def invokeFactory(self, type_name, id, RESPONSE=None, *args, **kw):
00341         """Invokes the portal_types tool."""
00342         pt = getToolByName(self, 'portal_types')
00343         myType = pt.getTypeInfo(self)
00344         if myType is not None:
00345             if not myType.allowType(type_name):
00346                 raise ValueError, 'Disallowed subobject type: %s' % type_name
00347         args = (type_name, self, id, RESPONSE) + args
00348         new_id = pt.constructContent(*args, **kw)
00349         if new_id is None or new_id == '':
00350             new_id = id
00351         return new_id
00352 
00353 InitializeClass(BasePloneFolder)
00354 
00355 class PloneFolder(BasePloneFolder, OrderedContainer):
00356     """A Plone Folder."""
00357     meta_type = 'Plone Folder'
00358     security=ClassSecurityInfo()
00359     __implements__ = BasePloneFolder.__implements__ + \
00360                      OrderedContainer.__implements__
00361 
00362     manage_renameObject = OrderedContainer.manage_renameObject
00363     security.declareProtected(Permissions.copy_or_move, 'manage_copyObjects')
00364 
00365 InitializeClass(PloneFolder)
00366 
00367 def safe_cmp(x, y):
00368     if callable(x): x=x()
00369     if callable(y): y=y()
00370     return cmp(x,y)
00371 
00372 def addPloneFolder(self, id, title='', description='', REQUEST=None):
00373     """Adds a Plone Folder."""
00374     sf = PloneFolder(id, title=title)
00375     sf.description=description
00376     self._setObject(id, sf)
00377     if REQUEST is not None:
00378         REQUEST['RESPONSE'].redirect(sf.absolute_url() + '/manage_main')