Back to index

plone3  3.1.7
folder.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2006 Zope Corporation and Contributors. All Rights Reserved.
00004 #
00005 # This software is subject to the provisions of the Zope Public License,
00006 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
00007 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00008 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00009 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00010 # FOR A PARTICULAR PURPOSE.
00011 #
00012 ##############################################################################
00013 """Browser views for folders.
00014 
00015 $Id: folder.py 77366 2007-07-03 16:14:33Z yuppie $
00016 """
00017 
00018 from DocumentTemplate import sequence
00019 from Products.PythonScripts.standard import thousands_commas
00020 from ZTUtils import Batch
00021 from ZTUtils import LazyFilter
00022 from ZTUtils import make_query
00023 
00024 from Products.CMFCore.interfaces import IDynamicType
00025 from Products.CMFDefault.exceptions import CopyError
00026 from Products.CMFDefault.exceptions import zExceptions_Unauthorized
00027 from Products.CMFDefault.permissions import AddPortalContent
00028 from Products.CMFDefault.permissions import DeleteObjects
00029 from Products.CMFDefault.permissions import ListFolderContents
00030 from Products.CMFDefault.permissions import ManageProperties
00031 from Products.CMFDefault.permissions import ViewManagementScreens
00032 from Products.CMFDefault.utils import html_marshal
00033 from Products.CMFDefault.utils import translate
00034 from Products.CMFDefault.utils import Message as _
00035 
00036 from utils import decode
00037 from utils import memoize
00038 from utils import ViewBase
00039 
00040 
00041 # XXX: This should be refactored using formlib. Please don't import from this
00042 #      module, things might be changed without further notice.
00043 
00044 class FormViewBase(ViewBase):
00045 
00046     # helpers
00047 
00048     def _setRedirect(self, provider_id, action_path, keys=''):
00049         provider = self._getTool(provider_id)
00050         try:
00051             target = provider.getActionInfo(action_path, self.context)['url']
00052         except ValueError:
00053             target = self._getPortalURL()
00054 
00055         kw = {}
00056         message = self.request.other.get('portal_status_message', '')
00057         if message:
00058             if isinstance(message, unicode):
00059                 message = message.encode(self._getBrowserCharset())
00060             kw['portal_status_message'] = message
00061         for k in keys.split(','):
00062             k = k.strip()
00063             v = self.request.form.get(k, None)
00064             if v:
00065                 kw[k] = v
00066 
00067         query = kw and ( '?%s' % make_query(kw) ) or ''
00068         self.request.RESPONSE.redirect( '%s%s' % (target, query) )
00069 
00070         return True
00071 
00072     # interface
00073 
00074     def __call__(self, **kw):
00075         form = self.request.form
00076         for button in self._BUTTONS:
00077             if button['id'] in form:
00078                 for permission in button.get('permissions', ()):
00079                     if not self._checkPermission(permission):
00080                         break
00081                 else:
00082                     for transform in button.get('transform', ()):
00083                         status = getattr(self, transform)(**form)
00084                         if isinstance(status, bool):
00085                             status = (status,)
00086                         if len(status) > 1:
00087                             message = translate(status[1], self.context)
00088                             self.request.other['portal_status_message'] = message
00089                         if not status[0]:
00090                             return self.index()
00091                     if self._setRedirect(*button['redirect']):
00092                         return
00093         return self.index()
00094 
00095     @memoize
00096     def form_action(self):
00097         return self._getViewURL()
00098 
00099     @memoize
00100     def listButtonInfos(self):
00101         form = self.request.form
00102         buttons = []
00103         for button in self._BUTTONS:
00104             if button.get('title', None):
00105                 for permission in button.get('permissions', ()):
00106                     if not self._checkPermission(permission):
00107                         break
00108                 else:
00109                     for condition in button.get('conditions', ()):
00110                         if not getattr(self, condition)():
00111                             break
00112                     else:
00113                         buttons.append({'name': button['id'],
00114                                         'value': button['title']})
00115         return tuple(buttons)
00116 
00117     @memoize
00118     @decode
00119     def listHiddenVarInfos(self):
00120         kw = self._getHiddenVars()
00121         vars = [ {'name': name, 'value': value}
00122                  for name, value in html_marshal(**kw) ]
00123         return tuple(vars)
00124 
00125 
00126 class BatchViewBase(ViewBase):
00127 
00128     # helpers
00129 
00130     _BATCH_SIZE = 25
00131 
00132     @memoize
00133     def _getBatchStart(self):
00134         return self.request.form.get('b_start', 0)
00135 
00136     @memoize
00137     def _getBatchObj(self):
00138         b_start = self._getBatchStart()
00139         items = self._getItems()
00140         return Batch(items, self._BATCH_SIZE, b_start, orphan=0)
00141 
00142     @memoize
00143     def _getHiddenVars(self):
00144         return {}
00145 
00146     @memoize
00147     def _getNavigationVars(self):
00148         return self._getHiddenVars()
00149 
00150     @memoize
00151     def _getNavigationURL(self, b_start):
00152         target = self._getViewURL()
00153         kw = self._getNavigationVars().copy()
00154 
00155         kw['b_start'] = b_start
00156         for k, v in kw.items():
00157             if not v or k == 'portal_status_message':
00158                 del kw[k]
00159 
00160         query = kw and ('?%s' % make_query(kw)) or ''
00161         return u'%s%s' % (target, query)
00162 
00163     # interface
00164 
00165     @memoize
00166     @decode
00167     def listItemInfos(self):
00168         batch_obj = self._getBatchObj()
00169         portal_url = self._getPortalURL()
00170 
00171         items = []
00172         for item in batch_obj:
00173             item_description = item.Description()
00174             item_icon = item.getIcon(1)
00175             item_title = item.Title()
00176             item_type = remote_type = item.Type()
00177             if item_type == 'Favorite' and not item_icon == 'p_/broken':
00178                 item = item.getObject()
00179                 item_description = item_description or item.Description()
00180                 item_title = item_title or item.Title()
00181                 remote_type = item.Type()
00182             is_file = remote_type in ('File', 'Image')
00183             is_link = remote_type == 'Link'
00184             items.append({'description': item_description,
00185                           'format': is_file and item.Format() or '',
00186                           'icon': item_icon and ('%s/%s' %
00187                                                (portal_url, item_icon)) or '',
00188                           'size': is_file and ('%0.0f kb' %
00189                                             (item.get_size() / 1024.0)) or '',
00190                           'title': item_title,
00191                           'type': item_type,
00192                           'url': is_link and item.getRemoteUrl() or
00193                                  item.absolute_url()})
00194         return tuple(items)
00195 
00196     @memoize
00197     def navigation_previous(self):
00198         batch_obj = self._getBatchObj().previous
00199         if batch_obj is None:
00200             return None
00201 
00202         length = len(batch_obj)
00203         url = self._getNavigationURL(batch_obj.first)
00204         if length == 1:
00205             title = _(u'Previous item')
00206         else:
00207             title = _(u'Previous ${count} items', mapping={'count': length})
00208         return {'title': title, 'url': url}
00209 
00210     @memoize
00211     def navigation_next(self):
00212         batch_obj = self._getBatchObj().next
00213         if batch_obj is None:
00214             return None
00215 
00216         length = len(batch_obj)
00217         url = self._getNavigationURL(batch_obj.first)
00218         if length == 1:
00219             title = _(u'Next item')
00220         else:
00221             title = _(u'Next ${count} items', mapping={'count': length})
00222         return {'title': title, 'url': url}
00223 
00224     @memoize
00225     def summary_length(self):
00226         length = self._getBatchObj().sequence_length
00227         return length and thousands_commas(length) or ''
00228 
00229     @memoize
00230     def summary_type(self):
00231         length = self._getBatchObj().sequence_length
00232         return (length == 1) and _(u'item') or _(u'items')
00233 
00234     @memoize
00235     @decode
00236     def summary_match(self):
00237         return self.request.form.get('SearchableText')
00238 
00239 
00240 class FolderView(BatchViewBase):
00241 
00242     """View for IFolderish.
00243     """
00244 
00245     # helpers
00246 
00247     @memoize
00248     def _getItems(self):
00249         (key, reverse) = self.context.getDefaultSorting()
00250         items = self.context.contentValues()
00251         items = sequence.sort(items,
00252                               ((key, 'cmp', reverse and 'desc' or 'asc'),))
00253         return LazyFilter(items, skip='View')
00254 
00255     # interface
00256 
00257     @memoize
00258     def has_local(self):
00259         return 'local_pt' in self.context.objectIds()
00260 
00261 
00262 class FolderContentsView(BatchViewBase, FormViewBase):
00263 
00264     """Contents view for IFolderish.
00265     """
00266 
00267     _BUTTONS = ({'id': 'items_new',
00268                  'title': _(u'New...'),
00269                  'permissions': (ViewManagementScreens, AddPortalContent),
00270                  'conditions': ('checkAllowedContentTypes',),
00271                  'redirect': ('portal_types', 'object/new')},
00272                 {'id': 'items_rename',
00273                  'title': _(u'Rename...'),
00274                  'permissions': (ViewManagementScreens, AddPortalContent),
00275                  'conditions': ('checkItems', 'checkAllowedContentTypes'),
00276                  'transform': ('validateItemIds',),
00277                  'redirect': ('portal_types', 'object/rename_items',
00278                               'b_start, ids, key, reverse')},
00279                 {'id': 'items_cut',
00280                  'title': _(u'Cut'),
00281                  'permissions': (ViewManagementScreens,),
00282                  'conditions': ('checkItems',),
00283                  'transform': ('validateItemIds', 'cut_control'),
00284                  'redirect': ('portal_types', 'object/folderContents',
00285                               'b_start, key, reverse')},
00286                 {'id': 'items_copy',
00287                  'title': _(u'Copy'),
00288                  'permissions': (ViewManagementScreens,),
00289                  'conditions': ('checkItems',),
00290                  'transform': ('validateItemIds', 'copy_control'),
00291                  'redirect': ('portal_types', 'object/folderContents',
00292                               'b_start, key, reverse')},
00293                 {'id': 'items_paste',
00294                  'title': _(u'Paste'),
00295                  'permissions': (ViewManagementScreens, AddPortalContent),
00296                  'conditions': ('checkClipboardData',),
00297                  'transform': ('validateClipboardData', 'paste_control'),
00298                  'redirect': ('portal_types', 'object/folderContents',
00299                               'b_start, key, reverse')},
00300                 {'id': 'items_delete',
00301                  'title': _(u'Delete'),
00302                  'permissions': (ViewManagementScreens, DeleteObjects),
00303                  'conditions': ('checkItems',),
00304                  'transform': ('validateItemIds', 'delete_control'),
00305                  'redirect': ('portal_types', 'object/folderContents',
00306                               'b_start, key, reverse')},
00307                 {'id': 'items_sort',
00308                  'permissions': (ManageProperties,),
00309                  'transform': ('sort_control',),
00310                  'redirect': ('portal_types', 'object/folderContents',
00311                               'b_start')},
00312                 {'id': 'items_up',
00313                  'permissions': (ManageProperties,),
00314                  'transform': ('validateItemIds', 'up_control'),
00315                  'redirect': ('portal_types', 'object/folderContents',
00316                               'b_start, key, reverse')},
00317                 {'id': 'items_down',
00318                  'permissions': (ManageProperties,),
00319                  'transform': ('validateItemIds', 'down_control'),
00320                  'redirect': ('portal_types', 'object/folderContents',
00321                               'b_start, key, reverse')},
00322                 {'id': 'items_top',
00323                  'permissions': (ManageProperties,),
00324                  'transform': ('validateItemIds', 'top_control'),
00325                  'redirect': ('portal_types', 'object/folderContents',
00326                               'b_start, key, reverse')},
00327                 {'id': 'items_bottom',
00328                  'permissions': (ManageProperties,),
00329                  'transform': ('validateItemIds', 'bottom_control'),
00330                  'redirect': ('portal_types', 'object/folderContents',
00331                               'b_start, key, reverse')},
00332                 {'id': 'set_view_filter',
00333                  'transform': ('set_filter_control',),
00334                  'redirect': ('portal_types', 'object/folderContents')},
00335                 {'id': 'clear_view_filter',
00336                  'transform': ('clear_filter_control',),
00337                  'redirect': ('portal_types', 'object/folderContents')})
00338 
00339     # helpers
00340 
00341     @memoize
00342     def _getSorting(self):
00343         key = self.request.form.get('key', None)
00344         if key:
00345             return (key, self.request.form.get('reverse', 0))
00346         else:
00347             return self.context.getDefaultSorting()
00348 
00349     @memoize
00350     def _isDefaultSorting(self):
00351         return self._getSorting() == self.context.getDefaultSorting()
00352 
00353     @memoize
00354     def _getHiddenVars(self):
00355         b_start = self._getBatchStart()
00356         is_default = self._isDefaultSorting()
00357         (key, reverse) = is_default and ('', 0) or self._getSorting()
00358         return {'b_start': b_start, 'key': key, 'reverse': reverse}
00359 
00360     @memoize
00361     def _getItems(self):
00362         (key, reverse) = self._getSorting()
00363         folderfilter = self.request.get('folderfilter', '')
00364         filter = self.context.decodeFolderFilter(folderfilter)
00365         items = self.context.listFolderContents(contentFilter=filter)
00366         return sequence.sort(items,
00367                              ((key, 'cmp', reverse and 'desc' or 'asc'),))
00368 
00369     # interface
00370 
00371     @memoize
00372     @decode
00373     def up_info(self):
00374         up_obj = self.context.aq_inner.aq_parent
00375         mtool = self._getTool('portal_membership')
00376         allowed = mtool.checkPermission(ListFolderContents, up_obj)
00377         if allowed:
00378             if IDynamicType.providedBy(up_obj):
00379                 up_url = up_obj.getActionInfo('object/folderContents')['url']
00380                 return {'icon': '%s/UpFolder_icon.gif' % self._getPortalURL(),
00381                         'id': up_obj.getId(),
00382                         'url': up_url}
00383             else:
00384                 return {'icon': '',
00385                         'id': 'Root',
00386                         'url': ''}
00387         else:
00388             return {}
00389 
00390     @memoize
00391     def listColumnInfos(self):
00392         (key, reverse) = self._getSorting()
00393         columns = ( {'key': 'Type',
00394                      'title': _(u'Type'),
00395                      'width': '20',
00396                      'colspan': '2'}
00397                   , {'key': 'getId',
00398                      'title': _(u'Name'),
00399                      'width': '360',
00400                      'colspan': None}
00401                   , {'key': 'modified',
00402                      'title': _(u'Last Modified'),
00403                      'width': '180',
00404                      'colspan': None}
00405                   , {'key': 'position',
00406                      'title': _(u'Position'),
00407                      'width': '80',
00408                      'colspan': None }
00409                   )
00410         for column in columns:
00411             if key == column['key'] and not reverse and key != 'position':
00412                 query = make_query(key=column['key'], reverse=1)
00413             else:
00414                 query = make_query(key=column['key'])
00415             column['url'] = '%s?%s' % (self._getViewURL(), query)
00416         return tuple(columns)
00417 
00418     @memoize
00419     @decode
00420     def listItemInfos(self):
00421         b_start = self._getBatchStart()
00422         (key, reverse) = self._getSorting()
00423         batch_obj = self._getBatchObj()
00424         items_manage_allowed = self._checkPermission(ViewManagementScreens)
00425         portal_url = self._getPortalURL()
00426 
00427         items = []
00428         i = 1
00429         for item in batch_obj:
00430             item_icon = item.getIcon(1)
00431             item_id = item.getId()
00432             item_position = (key == 'position') and str(b_start + i) or '...'
00433             i += 1
00434             item_url = item.getActionInfo(('object/folderContents',
00435                                            'object/view'))['url']
00436             items.append({'checkbox': items_manage_allowed and ('cb_%s' %
00437                                                                item_id) or '',
00438                           'icon': item_icon and ('%s/%s' %
00439                                                (portal_url, item_icon)) or '',
00440                           'id': item_id,
00441                           'modified': item.ModificationDate(),
00442                           'position': item_position,
00443                           'title': item.Title(),
00444                           'type': item.Type() or None,
00445                           'url': item_url})
00446         return tuple(items)
00447 
00448     @memoize
00449     def listDeltas(self):
00450         length = self._getBatchObj().sequence_length
00451         deltas = range(1, min(5, length)) + range(5, length, 5)
00452         return tuple(deltas)
00453 
00454     @memoize
00455     def is_orderable(self):
00456         length = len(self._getBatchObj())
00457         items_move_allowed = self._checkPermission(ManageProperties)
00458         (key, reverse) = self._getSorting()
00459         return items_move_allowed and (key == 'position') and length > 1
00460 
00461     @memoize
00462     def is_sortable(self):
00463         items_move_allowed = self._checkPermission(ManageProperties)
00464         return items_move_allowed and not self._isDefaultSorting()
00465 
00466     # checkers
00467 
00468     def checkAllowedContentTypes(self):
00469         return bool(self.context.allowedContentTypes())
00470 
00471     def checkClipboardData(self):
00472         return bool(self.context.cb_dataValid())
00473 
00474     def checkItems(self):
00475         return bool(self._getItems())
00476 
00477     # validators
00478 
00479     def validateItemIds(self, ids=(), **kw):
00480         if ids:
00481             return True
00482         else:
00483             return False, _(u'Please select one or more items first.')
00484 
00485     def validateClipboardData(self, **kw):
00486         if self.context.cb_dataValid():
00487             return True
00488         else:
00489             return False, _(u'Please copy or cut one or more items to paste '
00490                             u'first.')
00491 
00492     # controllers
00493 
00494     def cut_control(self, ids, **kw):
00495         """Cut objects from a folder and copy to the clipboard.
00496         """
00497         try:
00498             self.context.manage_cutObjects(ids, self.request)
00499             if len(ids) == 1:
00500                 return True, _(u'Item cut.')
00501             else:
00502                 return True, _(u'Items cut.')
00503         except CopyError:
00504             return False, _(u'CopyError: Cut failed.')
00505         except zExceptions_Unauthorized:
00506             return False, _(u'Unauthorized: Cut failed.')
00507 
00508     def copy_control(self, ids, **kw):
00509         """Copy objects from a folder to the clipboard.
00510         """
00511         try:
00512             self.context.manage_copyObjects(ids, self.request)
00513             if len(ids) == 1:
00514                 return True, _(u'Item copied.')
00515             else:
00516                 return True, _(u'Items copied.')
00517         except CopyError:
00518             return False, _(u'CopyError: Copy failed.')
00519 
00520     def paste_control(self, **kw):
00521         """Paste objects to a folder from the clipboard.
00522         """
00523         try:
00524             result = self.context.manage_pasteObjects(self.request['__cp'])
00525             if len(result) == 1:
00526                 return True, _(u'Item pasted.')
00527             else:
00528                 return True, _(u'Items pasted.')
00529         except CopyError:
00530             return False, _(u'CopyError: Paste failed.')
00531         except zExceptions_Unauthorized:
00532             return False, _(u'Unauthorized: Paste failed.')
00533 
00534     def delete_control(self, ids, **kw):
00535         """Delete objects from a folder.
00536         """
00537         self.context.manage_delObjects(list(ids))
00538         if len(ids) == 1:
00539             return True, _(u'Item deleted.')
00540         else:
00541             return True, _(u'Items deleted.')
00542 
00543     def sort_control(self, key='position', reverse=0, **kw):
00544         """Sort objects in a folder.
00545         """
00546         self.context.setDefaultSorting(key, reverse)
00547         return True
00548 
00549     def up_control(self, ids, delta, **kw):
00550         subset_ids = [ obj.getId()
00551                        for obj in self.context.listFolderContents() ]
00552         try:
00553             attempt = self.context.moveObjectsUp(ids, delta,
00554                                                  subset_ids=subset_ids)
00555             if attempt == 1:
00556                 return True, _(u'Item moved up.')
00557             elif attempt > 1:
00558                 return True, _(u'Items moved up.')
00559             else:
00560                 return False, _(u'Nothing to change.')
00561         except ValueError:
00562             return False, _(u'ValueError: Move failed.')
00563 
00564     def down_control(self, ids, delta, **kw):
00565         subset_ids = [ obj.getId()
00566                        for obj in self.context.listFolderContents() ]
00567         try:
00568             attempt = self.context.moveObjectsDown(ids, delta,
00569                                                    subset_ids=subset_ids)
00570             if attempt == 1:
00571                 return True, _(u'Item moved down.')
00572             elif attempt > 1:
00573                 return True, _(u'Items moved down.')
00574             else:
00575                 return False, _(u'Nothing to change.')
00576         except ValueError:
00577             return False, _(u'ValueError: Move failed.')
00578 
00579     def top_control(self, ids, **kw):
00580         subset_ids = [ obj.getId()
00581                        for obj in self.context.listFolderContents() ]
00582         try:
00583             attempt = self.context.moveObjectsToTop(ids,
00584                                                     subset_ids=subset_ids)
00585             if attempt == 1:
00586                 return True, _(u'Item moved to top.')
00587             elif attempt > 1:
00588                 return True, _(u'Items moved to top.')
00589             else:
00590                 return False, _(u'Nothing to change.')
00591         except ValueError:
00592             return False, _(u'ValueError: Move failed.')
00593 
00594     def bottom_control(self, ids, **kw):
00595         subset_ids = [ obj.getId()
00596                        for obj in self.context.listFolderContents() ]
00597         try:
00598             attempt = self.context.moveObjectsToBottom(ids,
00599                                                        subset_ids=subset_ids)
00600             if attempt == 1:
00601                 return True, _(u'Item moved to bottom.')
00602             elif attempt > 1:
00603                 return True, _(u'Items moved to bottom.')
00604             else:
00605                 return False, _(u'Nothing to change.')
00606         except ValueError:
00607             return False, _(u'ValueError: Move failed.')
00608 
00609     def set_filter_control(self, **kw):
00610         filter = self.context.encodeFolderFilter(self.request)
00611         self.request.RESPONSE.setCookie('folderfilter', filter, path='/',
00612                                       expires='Wed, 19 Feb 2020 14:28:00 GMT')
00613         return True, _(u'Filter applied.')
00614 
00615     def clear_filter_control(self, **kw):
00616         self.request.RESPONSE.expireCookie('folderfilter', path='/')
00617         self.request.RESPONSE.expireCookie('show_filter_form', path='/')
00618         return True, _(u'Filter cleared.')