[Checkins] SVN: Products.CMFDefault/trunk/Products/CMFDefault/ - Folder views: Rebuilt the CMF folder views based on

Jens Vagelpohl jens at dataflake.org
Thu Nov 12 16:21:33 EST 2009


Log message for revision 105588:
  - Folder views: Rebuilt the CMF folder views based on
    zope.formlib.
  

Changed:
  U   Products.CMFDefault/trunk/Products/CMFDefault/CHANGES.txt
  U   Products.CMFDefault/trunk/Products/CMFDefault/browser/configure.zcml
  U   Products.CMFDefault/trunk/Products/CMFDefault/browser/folder.py
  A   Products.CMFDefault/trunk/Products/CMFDefault/browser/interfaces.py
  U   Products.CMFDefault/trunk/Products/CMFDefault/browser/templates/batch_widgets.pt
  U   Products.CMFDefault/trunk/Products/CMFDefault/browser/templates/folder_contents.pt
  U   Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/folder.txt
  U   Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/folder_utest.txt
  U   Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/test_btreefolder.py
  U   Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/test_document.py
  U   Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/test_folder.py
  A   Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/utils.py

-=-
Modified: Products.CMFDefault/trunk/Products/CMFDefault/CHANGES.txt
===================================================================
--- Products.CMFDefault/trunk/Products/CMFDefault/CHANGES.txt	2009-11-12 19:28:24 UTC (rev 105587)
+++ Products.CMFDefault/trunk/Products/CMFDefault/CHANGES.txt	2009-11-12 21:21:32 UTC (rev 105588)
@@ -4,6 +4,9 @@
 2.2.0 (unreleased)
 ------------------
 
+- Folder views: Rebuilt the CMF folder views based on 
+  zope.formlib.
+
 - SkinnedFolder: Adjusted implementation to PortalFolder changes.
 
 - moved the Zope dependency to version 2.12.0b3dev

Modified: Products.CMFDefault/trunk/Products/CMFDefault/browser/configure.zcml
===================================================================
--- Products.CMFDefault/trunk/Products/CMFDefault/browser/configure.zcml	2009-11-12 19:28:24 UTC (rev 105587)
+++ Products.CMFDefault/trunk/Products/CMFDefault/browser/configure.zcml	2009-11-12 21:21:32 UTC (rev 105588)
@@ -10,13 +10,18 @@
       template="templates/folder.pt"
       permission="zope2.View"
       />
-
+  
+  <utility
+      component=".folder.contents_delta_vocabulary"
+      name="cmf.contents delta vocabulary"
+      provides="zope.schema.interfaces.IVocabularyFactory"
+      />
+      
   <browser:page
       for="Products.CMFCore.interfaces.IFolderish"
       layer="..interfaces.ICMFDefaultSkin"
       name="edit.html"
-      class=".folder.FolderContentsView"
-      template="templates/folder_contents.pt"
+      class=".folder.ContentsView"
       permission="cmf.ListFolderContents"
       />
 

Modified: Products.CMFDefault/trunk/Products/CMFDefault/browser/folder.py
===================================================================
--- Products.CMFDefault/trunk/Products/CMFDefault/browser/folder.py	2009-11-12 19:28:24 UTC (rev 105587)
+++ Products.CMFDefault/trunk/Products/CMFDefault/browser/folder.py	2009-11-12 21:21:32 UTC (rev 105588)
@@ -15,155 +15,110 @@
 $Id$
 """
 
-from DocumentTemplate import sequence  # for sort()
-from Products.PythonScripts.standard import thousands_commas
+import urllib
+
+from DocumentTemplate import sequence
 from ZTUtils import Batch
 from ZTUtils import LazyFilter
-from ZTUtils import make_query
 
+from zope import schema
+from zope.schema.vocabulary import SimpleTerm
+from zope.schema.vocabulary import SimpleVocabulary
+
+from zope.formlib import form
+
+from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
+from Products.Five.formlib.formbase import PageForm
+
 from Products.CMFCore.interfaces import IDynamicType
+
+from Products.CMFDefault.browser.interfaces import IDeltaItem
+from Products.CMFDefault.browser.interfaces import IFolderItem
+from Products.CMFDefault.browser.interfaces import IHidden
 from Products.CMFDefault.browser.utils import decode
 from Products.CMFDefault.browser.utils import memoize
 from Products.CMFDefault.browser.utils import ViewBase
 from Products.CMFDefault.exceptions import CopyError
 from Products.CMFDefault.exceptions import zExceptions_Unauthorized
-from Products.CMFDefault.permissions import AddPortalContent
-from Products.CMFDefault.permissions import DeleteObjects
+from Products.CMFDefault.formlib.form import _EditFormMixin
 from Products.CMFDefault.permissions import ListFolderContents
 from Products.CMFDefault.permissions import ManageProperties
-from Products.CMFDefault.permissions import ViewManagementScreens
-from Products.CMFDefault.utils import html_marshal
 from Products.CMFDefault.utils import Message as _
-from Products.CMFDefault.utils import translate
 
 
-# XXX: This should be refactored using formlib. Please don't import from this
-#      module, things might be changed without further notice.
+def contents_delta_vocabulary(context):
+    """Vocabulary for the pulldown for moving objects up and down.
+    """
+    length = len(context.contentIds())
+    deltas = [SimpleTerm(i, str(i), str(i)) 
+            for i in range(1, min(5, length)) + range(5, length, 5)]
+    return SimpleVocabulary(deltas)
 
-class FormViewBase(ViewBase):
 
-    # helpers
-
-    def _setRedirect(self, provider_id, action_path, keys=''):
-        provider = self._getTool(provider_id)
-        try:
-            target = provider.getActionInfo(action_path, self.context)['url']
-        except ValueError:
-            target = self._getPortalURL()
-
-        kw = {}
-        message = self.request.other.get('portal_status_message', '')
-        if message:
-            if isinstance(message, unicode):
-                message = message.encode(self._getBrowserCharset())
-            kw['portal_status_message'] = message
-        for k in keys.split(','):
-            k = k.strip()
-            v = self.request.form.get(k, None)
-            if v:
-                kw[k] = v
-
-        query = kw and ( '?%s' % make_query(kw) ) or ''
-        self.request.RESPONSE.redirect( '%s%s' % (target, query) )
-
-        return True
-
-    # interface
-
-    def __call__(self, **kw):
-        form = self.request.form
-        for button in self._BUTTONS:
-            if button['id'] in form:
-                for permission in button.get('permissions', ()):
-                    if not self._checkPermission(permission):
-                        break
-                else:
-                    for transform in button.get('transform', ()):
-                        status = getattr(self, transform)(**form)
-                        if isinstance(status, bool):
-                            status = (status,)
-                        if len(status) > 1:
-                            message = translate(status[1], self.context)
-                            self.request.other['portal_status_message'] = message
-                        if not status[0]:
-                            return self.index()
-                    if self._setRedirect(*button['redirect']):
-                        return
-        return self.index()
-
-    @memoize
-    def form_action(self):
-        return self._getViewURL()
-
-    @memoize
-    def listButtonInfos(self):
-        form = self.request.form
-        buttons = []
-        for button in self._BUTTONS:
-            if button.get('title', None):
-                for permission in button.get('permissions', ()):
-                    if not self._checkPermission(permission):
-                        break
-                else:
-                    for condition in button.get('conditions', ()):
-                        if not getattr(self, condition)():
-                            break
-                    else:
-                        buttons.append({'name': button['id'],
-                                        'value': button['title']})
-        return tuple(buttons)
-
-    @memoize
-    @decode
-    def listHiddenVarInfos(self):
-        kw = self._getHiddenVars()
-        vars = [ {'name': name, 'value': value}
-                 for name, value in html_marshal(**kw) ]
-        return tuple(vars)
-
-
 class BatchViewBase(ViewBase):
+    """ Helper class for creating batch-based views.
+    """
 
-    # helpers
-
     _BATCH_SIZE = 25
+    hidden_fields = form.FormFields(IHidden)
+    prefix = ''
+    
+    @memoize
+    def setUpWidgets(self, ignore_request=False):
+        self.hidden_widgets = form.setUpWidgets(self.hidden_fields, self.prefix, 
+                                                self.context, self.request, 
+                                                ignore_request=ignore_request)
 
     @memoize
     def _getBatchStart(self):
-        return self.request.form.get('b_start', 0)
+        return self._getHiddenVars().get('b_start', 0)
 
     @memoize
     def _getBatchObj(self):
         b_start = self._getBatchStart()
-        items = self._getItems()
+        items = self._get_items()
         return Batch(items, self._BATCH_SIZE, b_start, orphan=0)
 
     @memoize
     def _getHiddenVars(self):
-        return {}
+        data = {}
+        if hasattr(self, 'hidden_widgets'):
+            form.getWidgetsData(self.hidden_widgets, self.prefix, data)
+        return data
 
     @memoize
     def _getNavigationVars(self):
         return self._getHiddenVars()
 
     @memoize
-    def _getNavigationURL(self, b_start):
+    def expand_prefix(self, key,):
+        """Return a form specific query key for use in GET strings"""
+        return "%s%s" % (form.expandPrefix(self.prefix), key)
+
+    @memoize
+    def _getNavigationURL(self, b_start=None):
         target = self._getViewURL()
         kw = self._getNavigationVars().copy()
+        if 'bstart' not in kw:
+            kw['b_start'] = b_start
 
-        kw['b_start'] = b_start
         for k, v in kw.items():
             if not v or k == 'portal_status_message':
-                del kw[k]
+                pass
+            else:
+                new_key = self.expand_prefix(k)
+                kw[new_key] = v
+            del kw[k]
 
-        query = kw and ('?%s' % make_query(kw)) or ''
+        query = kw and ('?%s' % urllib.urlencode(kw)) or ''
+
         return u'%s%s' % (target, query)
 
     # interface
 
     @memoize
     @decode
-    def listItemInfos(self):
+    def listBatchItems(self):
         batch_obj = self._getBatchObj()
         portal_url = self._getPortalURL()
 
@@ -220,7 +175,38 @@
             title = _(u'Next ${count} items', mapping={'count': length})
         return {'title': title, 'url': url}
 
+    def page_range(self):
+        """Create a range of up to ten pages around the current page"""
+        pages = [(idx + 1, b_start) for idx, b_start in enumerate(
+                    range(0, 
+                        self._getBatchObj().sequence_length, 
+                        self._BATCH_SIZE)
+                    )
+                ]
+        range_start = max(self.page_number() - 5, 0)
+        range_stop = min(max(self.page_number() + 5, 10), len(pages))
+        _page_range = []
+        for page, b_start in pages[range_start:range_stop]:
+            _page_range.append(
+                {'number':page, 
+                 'url':self._getNavigationURL(b_start)
+                }
+                              )
+        return _page_range
+
     @memoize
+    def page_count(self):
+        """Count total number of pages in the batch"""
+        batch_obj = self._getBatchObj()
+        count = (batch_obj.sequence_length - 1) / self._BATCH_SIZE + 1
+        return count
+
+    @memoize
+    def page_number(self):
+        """Get the number of the current page in the batch"""
+        return (self._getBatchStart() / self._BATCH_SIZE) + 1
+
+    @memoize
     def summary_length(self):
         length = self._getBatchObj().sequence_length
         return length and thousands_commas(length) or ''
@@ -233,138 +219,106 @@
     @memoize
     @decode
     def summary_match(self):
-        return self.request.form.get('SearchableText')
+        return self.request.form.get('SearchableText')       
 
 
-class FolderView(BatchViewBase):
+class ContentsView(BatchViewBase, _EditFormMixin, PageForm):
+    """Folder contents view"""
+    
+    template = ViewPageTemplateFile('templates/folder_contents.pt')
+    prefix = 'form'
+    
+    object_actions = form.Actions(
+        form.Action(
+            name='rename',
+            label=_(u'Rename'),
+            validator='validate_items',
+            condition='has_subobjects',
+            success='handle_rename'),
+        form.Action(
+            name='cut',
+            label=_(u'Cut'),
+            condition='has_subobjects',
+            validator='validate_items',
+            success='handle_cut'),
+        form.Action(
+            name='copy',
+            label=_(u'Copy'),
+            condition='has_subobjects',
+            validator='validate_items',
+            success='handle_copy'),
+        form.Action(
+            name='paste',
+            label=_(u'Paste'),
+            condition='check_clipboard_data',
+            success='handle_paste'),
+        form.Action(
+            name='delete',
+            label=_(u'Delete'),
+            condition='has_subobjects',
+            validator='validate_items',
+            success='handle_delete')
+            )
+            
+    delta_actions = form.Actions(
+        form.Action(
+            name='up',
+            label=_(u'Up'),
+            condition='is_orderable',
+            validator='validate_items',
+            success='handle_up'),
+        form.Action(
+            name='down',
+            label=_(u'Down'),
+            condition='is_orderable',
+            validator='validate_items',
+            success='handle_down')
+            )
+            
+    absolute_actions = form.Actions(
+        form.Action(
+            name='top',
+            label=_(u'Top'),
+            condition='is_orderable',
+            validator='validate_items',
+            success='handle_top'),
+        form.Action(
+            name='bottom',
+            label=_(u'Bottom'),
+            condition='is_orderable',
+            validator='validate_items',
+            success='handle_bottom')
+            )
 
-    """View for IFolderish.
-    """
+    sort_actions = form.Actions(
+        form.Action(
+            name='sort_order',
+            label=_(u'Set as Default Sort'),
+            condition='can_sort_be_changed',
+            validator='validate_items',
+            success='handle_top')
+            )
+            
+    actions = object_actions + delta_actions + absolute_actions + sort_actions
+    errors = ()
+    
+    def __init__(self, *args, **kw):
+        super(ContentsView, self).__init__(*args, **kw)
+        self.form_fields = form.FormFields()
+        self.delta_field = form.FormFields(IDeltaItem)
+        self.contents = self.context.contentValues()        
+                
+    def content_fields(self):
+        """Create content field objects only for batched items"""
+        for item in self._getBatchObj():
+            for name, field in schema.getFieldsInOrder(IFolderItem):
+                field = form.FormField(field, name, item.id)
+                self.form_fields += form.FormFields(field)
 
-    # helpers
-
     @memoize
-    def _getItems(self):
-        (key, reverse) = self.context.getDefaultSorting()
-        items = self.context.contentValues()
-        items = sequence.sort(items,
-                              ((key, 'cmp', reverse and 'desc' or 'asc'),))
-        return LazyFilter(items, skip='View')
-
-    # interface
-
-    @memoize
-    def has_local(self):
-        return 'local_pt' in self.context.objectIds()
-
-
-class FolderContentsView(BatchViewBase, FormViewBase):
-
-    """Contents view for IFolderish.
-    """
-
-    _BUTTONS = ({'id': 'items_rename',
-                 'title': _(u'Rename...'),
-                 'permissions': (ViewManagementScreens, AddPortalContent),
-                 'conditions': ('checkItems', 'checkAllowedContentTypes'),
-                 'transform': ('validateItemIds',),
-                 'redirect': ('portal_types', 'object/rename_items',
-                              'b_start, ids, key, reverse')},
-                {'id': 'items_cut',
-                 'title': _(u'Cut'),
-                 'permissions': (ViewManagementScreens,),
-                 'conditions': ('checkItems',),
-                 'transform': ('validateItemIds', 'cut_control'),
-                 'redirect': ('portal_types', 'object/folderContents',
-                              'b_start, key, reverse')},
-                {'id': 'items_copy',
-                 'title': _(u'Copy'),
-                 'permissions': (ViewManagementScreens,),
-                 'conditions': ('checkItems',),
-                 'transform': ('validateItemIds', 'copy_control'),
-                 'redirect': ('portal_types', 'object/folderContents',
-                              'b_start, key, reverse')},
-                {'id': 'items_paste',
-                 'title': _(u'Paste'),
-                 'permissions': (ViewManagementScreens, AddPortalContent),
-                 'conditions': ('checkClipboardData',),
-                 'transform': ('validateClipboardData', 'paste_control'),
-                 'redirect': ('portal_types', 'object/folderContents',
-                              'b_start, key, reverse')},
-                {'id': 'items_delete',
-                 'title': _(u'Delete'),
-                 'permissions': (ViewManagementScreens, DeleteObjects),
-                 'conditions': ('checkItems',),
-                 'transform': ('validateItemIds', 'delete_control'),
-                 'redirect': ('portal_types', 'object/folderContents',
-                              'b_start, key, reverse')},
-                {'id': 'items_sort',
-                 'permissions': (ManageProperties,),
-                 'transform': ('sort_control',),
-                 'redirect': ('portal_types', 'object/folderContents',
-                              'b_start')},
-                {'id': 'items_up',
-                 'permissions': (ManageProperties,),
-                 'transform': ('validateItemIds', 'up_control'),
-                 'redirect': ('portal_types', 'object/folderContents',
-                              'b_start, key, reverse')},
-                {'id': 'items_down',
-                 'permissions': (ManageProperties,),
-                 'transform': ('validateItemIds', 'down_control'),
-                 'redirect': ('portal_types', 'object/folderContents',
-                              'b_start, key, reverse')},
-                {'id': 'items_top',
-                 'permissions': (ManageProperties,),
-                 'transform': ('validateItemIds', 'top_control'),
-                 'redirect': ('portal_types', 'object/folderContents',
-                              'b_start, key, reverse')},
-                {'id': 'items_bottom',
-                 'permissions': (ManageProperties,),
-                 'transform': ('validateItemIds', 'bottom_control'),
-                 'redirect': ('portal_types', 'object/folderContents',
-                              'b_start, key, reverse')},
-                {'id': 'set_view_filter',
-                 'transform': ('set_filter_control',),
-                 'redirect': ('portal_types', 'object/folderContents')},
-                {'id': 'clear_view_filter',
-                 'transform': ('clear_filter_control',),
-                 'redirect': ('portal_types', 'object/folderContents')})
-
-    # helpers
-
-    @memoize
-    def _getSorting(self):
-        key = self.request.form.get('key', None)
-        if key:
-            return (key, self.request.form.get('reverse', 0))
-        else:
-            return self.context.getDefaultSorting()
-
-    @memoize
-    def _isDefaultSorting(self):
-        return self._getSorting() == self.context.getDefaultSorting()
-
-    @memoize
-    def _getHiddenVars(self):
-        b_start = self._getBatchStart()
-        is_default = self._isDefaultSorting()
-        (key, reverse) = is_default and ('', 0) or self._getSorting()
-        return {'b_start': b_start, 'key': key, 'reverse': reverse}
-
-    @memoize
-    def _getItems(self):
-        (key, reverse) = self._getSorting()
-        folderfilter = self.request.get('folderfilter', '')
-        filter = self.context.decodeFolderFilter(folderfilter)
-        items = self.context.listFolderContents(contentFilter=filter)
-        return sequence.sort(items,
-                             ((key, 'cmp', reverse and 'desc' or 'asc'),))
-
-    # interface
-
-    @memoize
     @decode
     def up_info(self):
+        """Link to the contens view of the parent object"""
         up_obj = self.context.aq_inner.aq_parent
         mtool = self._getTool('portal_membership')
         allowed = mtool.checkPermission(ListFolderContents, up_obj)
@@ -380,233 +334,289 @@
                         'url': ''}
         else:
             return {}
-
+        
+    def setUpWidgets(self, ignore_request=False):
+        """Create widgets for the folder contents."""
+        super(ContentsView, self).setUpWidgets(ignore_request)
+        data = {}
+        self.content_fields()
+        for i in self._getBatchObj():
+            data['%s.name' % i.id] = i.getId()
+        self.widgets = form.setUpDataWidgets(
+                self.form_fields, self.prefix, self.context,
+                self.request, data=data, ignore_request=ignore_request)
+        self.widgets += form.setUpWidgets(
+                self.delta_field, self.prefix, self.context,
+                self.request, ignore_request=ignore_request)
+                
     @memoize
-    def listColumnInfos(self):
-        (key, reverse) = self._getSorting()
-        columns = ( {'key': 'Type',
+    def _get_sorting(self):
+        """How should the contents be sorted"""
+        data = self._getHiddenVars()
+        key = data.get('sort_key')
+        if key:
+            return (key, data.get('reverse', 0))
+        else:
+            return self.context.getDefaultSorting()
+            
+    @memoize
+    def _is_default_sorting(self,):
+        return self._get_sorting() == self.context.getDefaultSorting()
+        
+    @memoize
+    def column_headings(self):
+        key, reverse = self._get_sorting()
+        columns = ( {'sort_key': 'Type',
                      'title': _(u'Type'),
-                     'width': '20',
                      'colspan': '2'}
-                  , {'key': 'getId',
-                     'title': _(u'Name'),
-                     'width': '360',
-                     'colspan': None}
-                  , {'key': 'modified',
-                     'title': _(u'Last Modified'),
-                     'width': '180',
-                     'colspan': None}
-                  , {'key': 'position',
-                     'title': _(u'Position'),
-                     'width': '80',
-                     'colspan': None }
+                  , {'sort_key': 'getId',
+                     'title': _(u'Name')}
+                  , {'sort_key': 'modified',
+                     'title': _(u'Last Modified')}
+                  , {'sort_key': 'position',
+                     'title': _(u'Position')}
                   )
         for column in columns:
-            if key == column['key'] and not reverse and key != 'position':
-                query = make_query(key=column['key'], reverse=1)
-            else:
-                query = make_query(key=column['key'])
+            paras = {'form.sort_key':column['sort_key']}
+            if key == column['sort_key'] \
+            and not reverse and key != 'position':
+                paras['form.reverse'] = 1
+            query = urllib.urlencode(paras)
             column['url'] = '%s?%s' % (self._getViewURL(), query)
         return tuple(columns)
-
+        
     @memoize
-    @decode
-    def listItemInfos(self):
+    def _get_items(self):
+        key, reverse = self._get_sorting()
+        items = self.contents
+        return sequence.sort(items,
+                             ((key, 'cmp', reverse and 'desc' or 'asc'),))
+    
+    @memoize
+    def listBatchItems(self):
+        """Return the widgets for the form in the interface field order"""
+        batch_obj = self._getBatchObj()
         b_start = self._getBatchStart()
-        (key, reverse) = self._getSorting()
-        batch_obj = self._getBatchObj()
-        items_manage_allowed = self._checkPermission(ViewManagementScreens)
-        portal_url = self._getPortalURL()
+        key, reverse = self._get_sorting()
+        fields = []
 
-        items = []
-        i = 1
-        for item in batch_obj:
-            item_icon = item.getIcon(1)
-            item_id = item.getId()
-            item_position = (key == 'position') and str(b_start + i) or '...'
-            i += 1
-            item_url = item.getActionInfo(('object/folderContents',
-                                           'object/view'))['url']
-            items.append({'checkbox': items_manage_allowed and ('cb_%s' %
-                                                               item_id) or '',
-                          'icon': item_icon and ('%s/%s' %
-                                               (portal_url, item_icon)) or '',
-                          'id': item_id,
-                          'modified': item.ModificationDate(),
-                          'position': item_position,
-                          'title': item.Title(),
-                          'type': item.Type() or None,
-                          'url': item_url})
-        return tuple(items)
+        for idx, item in enumerate(batch_obj):
+            field = {'ModificationDate':item.ModificationDate()}
+            field['select'] = self.widgets['%s.select' % item.getId()]
+            field['name'] = self.widgets['%s.name' % item.getId()]
+            field['url'] = item.absolute_url()
+            field['title'] = item.TitleOrId()
+            field['icon'] = item.icon
+            field['position'] = (key == 'position') \
+                                and str(b_start + idx + 1) \
+                                or '...'
+            field['type'] = item.Type() or None
+            fields.append(field.copy())
+        return fields
+                
+    def _get_ids(self, data):
+        """Identify objects that have been selected"""
+        ids = [k[:-7] for k, v in data.items()
+                 if v is True and k.endswith('.select')]
+        return ids
 
+    #Action conditions
     @memoize
-    def listDeltas(self):
-        length = self._getBatchObj().sequence_length
-        deltas = range(1, min(5, length)) + range(5, length, 5)
-        return tuple(deltas)
-
+    def has_subobjects(self, action=None):
+        """Return false if the user cannot rename subobjects"""
+        return bool(self.contents)
+    
     @memoize
-    def is_orderable(self):
-        length = len(self._getBatchObj())
+    def check_clipboard_data(self, action=None):
+        """Any data in the clipboard"""
+        return bool(self.context.cb_dataValid())
+    
+    @memoize
+    def can_sort_be_changed(self, action=None):
+        """Returns true if the default sort key may be changed 
+            may be sorted for display"""
         items_move_allowed = self._checkPermission(ManageProperties)
-        (key, reverse) = self._getSorting()
-        return items_move_allowed and (key == 'position') and length > 1
+        return items_move_allowed and not \
+            self._get_sorting() == self.context.getDefaultSorting()
 
     @memoize
-    def is_sortable(self):
-        items_move_allowed = self._checkPermission(ManageProperties)
-        return items_move_allowed and not self._isDefaultSorting()
-
-    # checkers
-
-    def checkAllowedContentTypes(self):
-        return bool(self.context.allowedContentTypes())
-
-    def checkClipboardData(self):
-        return bool(self.context.cb_dataValid())
-
-    def checkItems(self):
-        return bool(self._getItems())
-
-    # validators
-
-    def validateItemIds(self, ids=(), **kw):
-        if ids:
-            return True
+    def is_orderable(self, action=None):
+        """Returns true if the displayed contents can be
+            reorded."""
+        (key, reverse) = self._get_sorting()        
+        return key == 'position' and len(self.contents) > 1
+    
+    #Action validators
+    def validate_items(self, action=None, data=None):
+        """Check whether any items have been selected for 
+        the requested action."""
+        super(ContentsView, self).validate(action, data)
+        if data is None or data == {}:
+            return [_(u"Please select one or more items first.")]
         else:
-            return False, _(u'Please select one or more items first.')
-
-    def validateClipboardData(self, **kw):
-        if self.context.cb_dataValid():
-            return True
-        else:
-            return False, _(u'Please copy or cut one or more items to paste '
-                            u'first.')
-
-    # controllers
-
-    def cut_control(self, ids, **kw):
-        """Cut objects from a folder and copy to the clipboard.
-        """
+            return []
+            
+    #Action handlers
+    def handle_rename(self, action, data):
+        """Redirect to rename view passing the ids of objects to be renamed"""
+        # currently redirects to a PythonScript
+        # should be replaced with a dedicated form
+        self.request.form['ids'] = self._get_ids(data)
+        keys = ",".join(self._getHiddenVars().keys() + ['ids'])
+        # keys = 'b_start, ids, key, reverse'
+        return self._setRedirect('portal_types', 'object/rename_items', keys)
+        
+    def handle_cut(self, action, data):
+        """Cut the selected objects and put them in the clipboard"""
+        ids = self._get_ids(data)
         try:
             self.context.manage_cutObjects(ids, self.request)
             if len(ids) == 1:
-                return True, _(u'Item cut.')
+                self.status = _(u'Item cut.')
             else:
-                return True, _(u'Items cut.')
+                self.status = _(u'Items cut.')
         except CopyError:
-            return False, _(u'CopyError: Cut failed.')
+            self.status = _(u'CopyError: Cut failed.')
         except zExceptions_Unauthorized:
-            return False, _(u'Unauthorized: Cut failed.')
+            self.status = _(u'Unauthorized: Cut failed.')
+        return self._setRedirect('portal_types', 'object/new_contents')    
 
-    def copy_control(self, ids, **kw):
-        """Copy objects from a folder to the clipboard.
-        """
+    def handle_copy(self, action, data):
+        """Copy the selected objects to the clipboard"""
+        ids = self._get_ids(data)
         try:
             self.context.manage_copyObjects(ids, self.request)
             if len(ids) == 1:
-                return True, _(u'Item copied.')
+                self.status = _(u'Item copied.')
             else:
-                return True, _(u'Items copied.')
+                self.status = _(u'Items copied.')
         except CopyError:
-            return False, _(u'CopyError: Copy failed.')
-
-    def paste_control(self, **kw):
-        """Paste objects to a folder from the clipboard.
-        """
+            self.status = _(u'CopyError: Copy failed.')
+        return self._setRedirect('portal_types', 'object/new_contents')
+    
+    def handle_paste(self, action, data):
+        """Paste the objects from the clipboard into the folder"""
         try:
             result = self.context.manage_pasteObjects(self.request['__cp'])
             if len(result) == 1:
-                return True, _(u'Item pasted.')
+                self.status = _(u'Item pasted.')
             else:
-                return True, _(u'Items pasted.')
-        except CopyError:
-            return False, _(u'CopyError: Paste failed.')
+                self.status = _(u'Items pasted.')
+        except CopyError, error:
+            self.status = _(u'CopyError: Paste failed.')
+            self.request['RESPONSE'].expireCookie('__cp', 
+                    path='%s' % (self.request['BASEPATH1'] or "/"))
+
         except zExceptions_Unauthorized:
-            return False, _(u'Unauthorized: Paste failed.')
+            self.status = _(u'Unauthorized: Paste failed.')
+        return self._setRedirect('portal_types', 'object/new_contents')
 
-    def delete_control(self, ids, **kw):
-        """Delete objects from a folder.
-        """
+    def handle_delete(self, action, data):
+        """Delete the selected objects"""
+        ids = self._get_ids(data)
         self.context.manage_delObjects(list(ids))
         if len(ids) == 1:
-            return True, _(u'Item deleted.')
+            self.status = _(u'Item deleted.')
         else:
-            return True, _(u'Items deleted.')
-
-    def sort_control(self, key='position', reverse=0, **kw):
-        """Sort objects in a folder.
-        """
-        self.context.setDefaultSorting(key, reverse)
-        return True
-
-    def up_control(self, ids, delta, **kw):
-        subset_ids = [ obj.getId()
-                       for obj in self.context.listFolderContents() ]
+            self.status = _(u'Items deleted.')
+        return self._setRedirect('portal_types', 'object/new_contents')
+    
+    def handle_up(self, action, data):
+        """Move the selected objects up the selected number of places"""
+        ids = self._get_ids(data)
+        delta = data.get('delta', 1)
+        subset_ids = [obj.getId()
+                       for obj in self.context.listFolderContents()]
         try:
             attempt = self.context.moveObjectsUp(ids, delta,
                                                  subset_ids=subset_ids)
             if attempt == 1:
-                return True, _(u'Item moved up.')
+                self.status = _(u'Item moved up.')
             elif attempt > 1:
-                return True, _(u'Items moved up.')
+                self.status = _(u'Items moved up.')
             else:
-                return False, _(u'Nothing to change.')
+                self.status = _(u'Nothing to change.')
         except ValueError:
-            return False, _(u'ValueError: Move failed.')
+            self.status = _(u'ValueError: Move failed.')
+        return self._setRedirect('portal_types', 'object/new_contents')
 
-    def down_control(self, ids, delta, **kw):
-        subset_ids = [ obj.getId()
-                       for obj in self.context.listFolderContents() ]
+    def handle_down(self, action, data):
+        """Move the selected objects down the selected number of places"""
+        ids = self._get_ids(data)
+        delta = data.get('delta', 1)
+        subset_ids = [obj.getId()
+                       for obj in self.context.listFolderContents()]
         try:
             attempt = self.context.moveObjectsDown(ids, delta,
-                                                   subset_ids=subset_ids)
+                                                 subset_ids=subset_ids)
             if attempt == 1:
-                return True, _(u'Item moved down.')
+                self.status = _(u'Item moved down.')
             elif attempt > 1:
-                return True, _(u'Items moved down.')
+                self.status = _(u'Items moved down.')
             else:
-                return False, _(u'Nothing to change.')
+                self.status = _(u'Nothing to change.')
         except ValueError:
-            return False, _(u'ValueError: Move failed.')
-
-    def top_control(self, ids, **kw):
-        subset_ids = [ obj.getId()
-                       for obj in self.context.listFolderContents() ]
+            self.status = _(u'ValueError: Move failed.')
+        return self._setRedirect('portal_types', 'object/new_contents')
+            
+    def handle_top(self, action, data):
+        """Move the selected objects to the top of the page"""
+        ids = self._get_ids(data)
+        subset_ids = [obj.getId()
+                       for obj in self.context.listFolderContents()]
         try:
             attempt = self.context.moveObjectsToTop(ids,
                                                     subset_ids=subset_ids)
             if attempt == 1:
-                return True, _(u'Item moved to top.')
+                self.status = _(u'Item moved to top.')
             elif attempt > 1:
-                return True, _(u'Items moved to top.')
+                self.status = _(u'Items moved to top.')
             else:
-                return False, _(u'Nothing to change.')
+                self.status = _(u'Nothing to change.')
         except ValueError:
-            return False, _(u'ValueError: Move failed.')
+            self.status = _(u'ValueError: Move failed.')
+        return self._setRedirect('portal_types', 'object/new_contents')
 
-    def bottom_control(self, ids, **kw):
-        subset_ids = [ obj.getId()
-                       for obj in self.context.listFolderContents() ]
+    def handle_bottom(self, action, data):
+        """Move the selected objects to the bottom of the page"""
+        ids = self._get_ids(data)
+        subset_ids = [obj.getId()
+                       for obj in self.context.listFolderContents()]
         try:
             attempt = self.context.moveObjectsToBottom(ids,
                                                        subset_ids=subset_ids)
             if attempt == 1:
-                return True, _(u'Item moved to bottom.')
+                self.status = _(u'Item moved to bottom.')
             elif attempt > 1:
-                return True, _(u'Items moved to bottom.')
+                self.status = _(u'Items moved to bottom.')
             else:
-                return False, _(u'Nothing to change.')
+                self.status = _(u'Nothing to change.')
         except ValueError:
-            return False, _(u'ValueError: Move failed.')
+            self.status = _(u'ValueError: Move failed.')
+        return self._setRedirect('portal_types', 'object/new_contents')
+        
+    def handle_sort_order(self, action, data):
+        """Set the sort options for the folder."""
+        key = data['position']
+        reverse = data.get('reverse', 0)
+        self.context.setDefaultSorting(key, reverse)
+        self.status = _(u"Sort order changed")
+        return self._setRedirect('portal_types', 'object/new_contents')
+        
 
-    def set_filter_control(self, **kw):
-        filter = self.context.encodeFolderFilter(self.request)
-        self.request.RESPONSE.setCookie('folderfilter', filter, path='/',
-                                      expires='Wed, 19 Feb 2020 14:28:00 GMT')
-        return True, _(u'Filter applied.')
+class FolderView(BatchViewBase):
 
-    def clear_filter_control(self, **kw):
-        self.request.RESPONSE.expireCookie('folderfilter', path='/')
-        self.request.RESPONSE.expireCookie('show_filter_form', path='/')
-        return True, _(u'Filter cleared.')
+    """View for IFolderish.
+    """
+
+    @memoize
+    def _get_items(self):
+        (key, reverse) = self.context.getDefaultSorting()
+        items = self.context.contentValues()
+        items = sequence.sort(items,
+                              ((key, 'cmp', reverse and 'desc' or 'asc'),))
+        return LazyFilter(items, skip='View')
+
+    @memoize
+    def has_local(self):
+        return 'local_pt' in self.context.objectIds()

Added: Products.CMFDefault/trunk/Products/CMFDefault/browser/interfaces.py
===================================================================
--- Products.CMFDefault/trunk/Products/CMFDefault/browser/interfaces.py	                        (rev 0)
+++ Products.CMFDefault/trunk/Products/CMFDefault/browser/interfaces.py	2009-11-12 21:21:32 UTC (rev 105588)
@@ -0,0 +1,61 @@
+##############################################################################
+#
+# Copyright (c) 2009 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser view interfaces.
+
+$Id$
+"""
+
+from zope.interface import Interface
+from zope.schema import Bool
+from zope.schema import Choice
+from zope.schema import Int
+from zope.schema import TextLine
+
+
+class IFolderItem(Interface):
+    """Schema for folderish objects contents."""
+    
+    select = Bool(
+        required=False)
+        
+    name = TextLine(
+        title=u"Name",
+        required=False,
+        readonly=True)
+
+
+class IDeltaItem(Interface):
+    """Schema for delta"""    
+    delta = Choice(
+        title=u"By",
+        description=u"Move an object up or down the chosen number of places.",
+        required=False,
+        vocabulary=u'cmf.contents delta vocabulary',
+        default=1)
+
+        
+class IHidden(Interface):
+    """Schema for hidden items"""
+    
+    b_start = Int(
+        title=u"Batch start",
+        required=False,
+        default=0)
+        
+    sort_key = TextLine(
+        title=u"Sort key",
+        required=False)
+        
+    reverse = Int(
+        title=u"Reverse sort order",
+        required=False)


Property changes on: Products.CMFDefault/trunk/Products/CMFDefault/browser/interfaces.py
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Modified: Products.CMFDefault/trunk/Products/CMFDefault/browser/templates/batch_widgets.pt
===================================================================
--- Products.CMFDefault/trunk/Products/CMFDefault/browser/templates/batch_widgets.pt	2009-11-12 19:28:24 UTC (rev 105587)
+++ Products.CMFDefault/trunk/Products/CMFDefault/browser/templates/batch_widgets.pt	2009-11-12 21:21:32 UTC (rev 105588)
@@ -14,7 +14,7 @@
 ></metal:macro>
 
  <metal:macro metal:define-macro="listing" i18n:domain="cmf_default">
- <p class="BatchListing" tal:repeat="item_info view/listItemInfos"
+ <p class="BatchListing" tal:repeat="item_info view/listBatchItems"
  ><a href="" tal:attributes="href item_info/url"
   ><img src="" alt="" title="" border="0" width="16" height="16"
       tal:attributes="src item_info/icon; alt item_info/type;
@@ -50,5 +50,27 @@
  ></p
 ></metal:macro>
 
+<metal:macro metal:define-macro="pagination"
+  tal:define="current_page view/page_number;
+              prev_info view/navigation_previous;
+              next_info view/navigation_next">
+	<a href="" 
+	   tal:condition="prev_info"
+		 tal:attributes="href prev_info/url">&lt;&lt;</a>
+  <tal:repeat condition="python: prev_info or next_info"
+                        repeat="page view/page_range">
+		<a tal:condition="python: not page['number'] == current_page" 
+		  tal:attributes="href page/url"
+		  tal:content="page/number">Page number</a>
+	  <span tal:condition="python: page['number'] == current_page"
+	        tal:content="page/number">Page number</span>
+		<tal:condition condition="not: repeat/page/end">|</tal:condition> 
+	</tal:repeat>
+	
+	<a href="" 
+	   tal:condition="python: current_page != view.page_count() and next_info" 
+	   tal:attributes="href next_info/url">&gt;&gt;</a>
+</metal:macro>
+
 </body>
 </html>

Modified: Products.CMFDefault/trunk/Products/CMFDefault/browser/templates/folder_contents.pt
===================================================================
--- Products.CMFDefault/trunk/Products/CMFDefault/browser/templates/folder_contents.pt	2009-11-12 19:28:24 UTC (rev 105587)
+++ Products.CMFDefault/trunk/Products/CMFDefault/browser/templates/folder_contents.pt	2009-11-12 21:21:32 UTC (rev 105588)
@@ -16,75 +16,67 @@
 ><tal:case tal:condition="not: up_info/url"
  ><span class="mild" i18n:translate="">Root</span></tal:case></p>
 
-<form action="folder_contents" method="post"
-   tal:attributes="action view/form_action"
-><metal:macro metal:use-macro="context/@@form_widget/hidden_vars" />
- <table class="BatchTable"
-    tal:condition="view/listItemInfos">
-  <thead>
-   <tr class="list-header">
-    <th width="80" tal:repeat="column_info view/listColumnInfos"
-       tal:attributes="width column_info/width; colspan column_info/colspan"
-    ><a href="" tal:attributes="href column_info/url"
-        tal:content="column_info/title">COLUMN TITLE</a></th>
-   </tr>
-  </thead>
-  <tbody tal:repeat="item_info view/listItemInfos">
-   <tr class="" tal:define="even repeat/item_info/even"
-      tal:attributes="class python: (even and 'row-hilite') or 'row-normal'">
-      <td width="5"
-      ><input type="checkbox" name="ids:list" value="" id=""
-          tal:attributes="value item_info/id; id item_info/checkbox"
-          tal:condition="item_info/checkbox" /></td>
-      <td
-      ><a href="" tal:attributes="href item_info/url"
-          tal:condition="item_info/icon"
-       ><img src="" alt="" border="0"
-           tal:attributes="src item_info/icon; alt item_info/type"
-           i18n:attributes="alt" /></a></td>
-      <td
-      ><a href="" tal:attributes="href item_info/url"
-       ><tal:span tal:content="item_info/id">ID</tal:span>
-        <tal:case tal:condition="item_info/title"
-           tal:content="string:(${item_info/title})">(Title)</tal:case
-      ></a></td>
-      <td
-      ><tal:span tal:content="item_info/modified">2001</tal:span></td>
-      <td
-      ><tal:span tal:content="item_info/position">1</tal:span></td>
-   </tr>
-  </tbody>
- </table>
- <metal:macro metal:use-macro="context/@@batch_widget/navigation" />
- <metal:macro metal:use-macro="context/@@form_widget/buttons" />
-<tal:case tal:condition="python: view.is_orderable() or view.is_sortable()"
-> <div class="FormButtons"
- ><tal:case tal:condition="view/is_orderable">
-  <input type="submit" name="items_up" value="Up"
-     i18n:attributes="value" />
-  /
-  <input type="submit" name="items_down" value="Down"
-     i18n:attributes="value" />
-  by
-  <select name="delta:int">
-   <option value=""
-      tal:repeat="delta view/listDeltas"
-      tal:attributes="value delta"
-      tal:content="delta">
-   </option>
-  </select>
-  <input type="submit" name="items_top" value="Top"
-     i18n:attributes="value" />
-  <input type="submit" name="items_bottom" value="Bottom"
-     i18n:attributes="value" /></tal:case
- ><tal:case tal:condition="view/is_sortable">
-  <input type="submit" name="items_sort" value="Set Sorting as Default"
-     i18n:attributes="value" /></tal:case
-></div>
-</tal:case></form>
+<ul class="errors" tal:condition="view/errors">
+ <li tal:repeat="error view/error_views"><tal:span
+     tal:replace="structure error" /></li>
+</ul>
 
-<div tal:replace="structure context/folder_filter_form">Filter Form Here</div>
+<p class="status" 
+  tal:condition="exists: request/portal_status_message"
+  tal:content="request/portal_status_message"></p>
+  
+<form class="form" action="." method="post" enctype="multipart/form-data"
+   tal:attributes="action request/ACTUAL_URL">
+   <tal:block repeat="widget view/hidden_widgets"
+              replace="structure widget/hidden" />
+   <table tal:condition="view/has_subobjects">
+     <tr>
+       <th tal:repeat="column view/column_headings"
+            tal:attributes="colspan column/colspan | nothing"><a href="column"
+         tal:content="column/title"
+         tal:attributes="href column/url"
+         >Column Title</a></th>
+     </tr>
+   <tr tal:repeat="item view/listBatchItems" 
+      tal:attributes="class python: (repeat['item'].even() and 'row-hilite') or ''">
+     <td tal:content="structure item/select">Checkbox</td>
+     <td><a href="" tal:attributes="href item/url"
+         tal:condition="item/icon"
+      ><img src="" alt="" border="0"
+          tal:attributes="src item/icon; alt item/type"
+          i18n:attributes="alt" /></a></td>
+     <td><a tal:attributes="href string:${item/url}/edit.html" tal:content="string:${item/name} (${item/title})"></a></td>
+     <td tal:content="item/ModificationDate"></td>
+    <td tal:content="item/position"></td>
+   </tr>
+   </table>
+<div class="buttons">
+  <tal:loop tal:repeat="action view/object_actions" 
+   tal:replace="structure action/render" />
+</div>
+<div class="buttons">
+  <tal:loop tal:repeat="action view/delta_actions" 
+   tal:replace="structure action/render" />
+   <div tal:condition="view/is_orderable"
+        tal:define="widget python:view.widgets.get('delta');
+                    hint widget/hint | nothing">
+     <label tal:attributes="for widget/name; title python: hint or None"
+       tal:content="widget/label">Move By</label>
+     <tal:block tal:replace="structure view/widgets/delta" />
+   </div>
+</div>
+<div class="buttons">
+  <tal:loop tal:repeat="action view/absolute_actions" 
+   tal:replace="structure action/render" />
+</div>
+<div class="buttons">
+  <tal:loop tal:repeat="action view/sort_actions" 
+   tal:replace="structure action/render" />
+</div>
+</form>
+<metal:macro metal:use-macro="context/@@batch_widget/navigation" />
+<metal:macro metal:use-macro="context/@@batch_widget/pagination" />
 </metal:slot>
 
 </body>
-</html>
+</html>
\ No newline at end of file

Modified: Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/folder.txt
===================================================================
--- Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/folder.txt	2009-11-12 19:28:24 UTC (rev 105587)
+++ Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/folder.txt	2009-11-12 21:21:32 UTC (rev 105588)
@@ -1,10 +1,12 @@
-Folder Views
-------------
+Form Views
+--------------
 
 Set up user.
 
     >>> uf = app.site.acl_users
     >>> uf._doAddUser('mgr', 'mgrpw', ['Manager'], [])
+    >>> from zope.site.hooks import setSite
+    >>> setSite(app.site)
 
 Create the browser object we'll be using.
 
@@ -13,41 +15,48 @@
     >>> browser.handleErrors = False
     >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
 
-Use the add form without input.
+Open the contents view with the various options selected.
+All sort options apart from position are reversible.
 
-    >>> browser.open('http://localhost/site/++add++Folder')
-    >>> '[[cmf_default][Add [[cmf_default][Folder]]]]' in browser.contents
+    >>> browser.open('http://localhost/site/@@edit.html?form.b_start=25')
+    >>> 'name="form.b_start" type="hidden" value="25"' in browser.contents
     True
-    >>> browser.getControl('[[zope][Add]]').click()
-    >>> '[[zope][There were errors]]' in browser.contents
+    >>> browser.open('http://localhost/site/@@edit.html?form.sort_key=Type')
+    >>> 'name="form.sort_key" type="hidden" value="Type"' in browser.contents
     True
-    >>> '[[zope][Required input is missing.]]' in browser.contents
+    >>> '<a href="http://localhost/site/@@edit.html?form.sort_key=Type&amp;form.reverse=1">' \
+    ... in browser.contents
     True
-
-Use the add form with valid input.
-
-    >>> from StringIO import StringIO
-    >>> browser.open('http://localhost/site/++add++Folder')
-    >>> '[[cmf_default][Add [[cmf_default][Folder]]]]' in browser.contents
+    >>> browser.open('http://localhost/site/@@edit.html?form.sort_key=getId')
+    >>> 'name="form.sort_key" type="hidden" value="getId"' in browser.contents
     True
-    >>> browser.getControl(name='form.id').value = 'myFolder'
-    >>> browser.getControl('[[zope][Add]]').click()
-    >>> '[[cmf_default][[[cmf_default][Folder]] added.]]' in browser.contents
+    >>> '<a href="http://localhost/site/@@edit.html?form.sort_key=getId&amp;form.reverse=1">' \
+    ... in browser.contents
     True
+    >>> browser.open('http://localhost/site/@@edit.html?form.sort_key=modified')
+    >>> 'name="form.sort_key" type="hidden" value="modified"' in browser.contents
+    True
+    >>> '<a href="http://localhost/site/@@edit.html?form.sort_key=modified&amp;form.reverse=1">' \
+    ... in browser.contents
+    True
+    >>> browser.open('http://localhost/site/@@edit.html?form.sort_key=position')
+    >>> 'name="form.sort_key" type="hidden" value="position"' in browser.contents
+    True
+    >>> '<a href="http://localhost/site/@@edit.html?form.sort_key=position&amp;form.reverse=1">' \
+    ... in browser.contents
+    False
 
-Use the folder contents form without input.
 
-    >>> browser.open('http://localhost/site/myFolder/@@edit.html')
-    >>> '[[cmf_default][Folder Contents: ]]' in browser.contents
-    True
 
-Try to add something to this folder.
-
-    >>> browser.open('http://localhost/site/myFolder/++add++Document')
-    >>> '[[cmf_default][Add [[cmf_default][Document]]]]' in browser.contents
+    >>> browser.open('http://localhost/site/@@edit.html?form.sort_key=Type&form.reverse=1')
+    >>> 'name="form.reverse" type="hidden" value="1"' in browser.contents
     True
-    >>> browser.getControl(name='form.id').value = 'myDocument'
-    >>> browser.getControl('[[zope][Add]]').click()
-    >>> '[[cmf_default][[[cmf_default][Document]] added.]]' in browser.contents
+    >>> browser.open('http://localhost/site/@@edit.html?form.sort_key=getId&form.reverse=1')
+    >>> 'name="form.reverse" type="hidden" value="1"' in browser.contents
     True
-
+    >>> browser.open('http://localhost/site/@@edit.html?form.sort_key=modified&form.reverse=1')
+    >>> 'name="form.reverse" type="hidden" value="1"' in browser.contents
+    True
+    >>> browser.open('http://localhost/site/@@edit.html?form.sort_key=position&form.reverse=1')
+    >>> 'name="form.reverse" type="hidden" value="1"' in browser.contents
+    True
\ No newline at end of file

Modified: Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/folder_utest.txt
===================================================================
--- Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/folder_utest.txt	2009-11-12 19:28:24 UTC (rev 105587)
+++ Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/folder_utest.txt	2009-11-12 21:21:32 UTC (rev 105588)
@@ -1,109 +0,0 @@
-Browser Views for IFolderish
-
-
-  The required environment:
-
-    Setting up a dummy site with required tools::
-
-      >>> from Products.CMFCore.tests.base.dummy import DummySite
-      >>> site = DummySite('site')
-
-      >>> from Products.CMFCore.tests.base.dummy import DummyTool
-      >>> from zope.component import getSiteManager
-      >>> from Products.CMFCore.interfaces import IPropertiesTool
-      >>> sm = getSiteManager()
-      >>> mtool = site._setObject('portal_membership', DummyTool())
-      >>> ptool = site._setObject('portal_properties', DummyTool())
-      >>> sm.registerUtility(ptool, IPropertiesTool)
-      >>> ttool = site._setObject('portal_types', DummyTool())
-      >>> utool = site._setObject('portal_url', DummyTool())
-
-
-  Basic functionality without security setup:
-
-    Setting up a simple request and an empty context object::
-
-      >>> class DummyRequest(dict):
-      ...     def __init__(self):
-      ...         self['ACTUAL_URL'] = 'actual_url'
-      ...         self.form = {}
-      >>> request = DummyRequest()
-
-      >>> from Products.CMFCore.PortalFolder import PortalFolder
-      >>> context = PortalFolder('foo').__of__(site)
-
-    The FolderView interface used by templates::
-
-      >>> from Products.CMFDefault.browser.folder import FolderView
-      >>> view = FolderView(context, request)
-
-      >>> view.title()
-      u''
-
-      >>> view.description()
-      u''
-
-      >>> view.has_local()
-      False
-
-    The FolderContentsView interface used by templates::
-
-      >>> from Products.CMFDefault.browser.folder import FolderContentsView
-      >>> view = FolderContentsView(context, request)
-
-      >>> view.title()
-      u''
-
-      >>> view.description()
-      u''
-
-      >>> view.up_info()
-      {'url': u'', 'id': u'Root', 'icon': u''}
-
-      >>> view.listColumnInfos()
-      ({'url': 'actual_url?key=Type', 'width': '20', 'colspan': '2',
-        'key': 'Type', 'title': u'Type'},
-       {'url': 'actual_url?key=getId', 'width': '360', 'colspan': None,
-        'key': 'getId', 'title': u'Name'},
-       {'url': 'actual_url?key=modified', 'width': '180', 'colspan': None,
-        'key': 'modified', 'title': u'Last Modified'},
-       {'url': 'actual_url?key=position', 'width': '80', 'colspan': None,
-        'key': 'position', 'title': u'Position'})
-
-      >>> view.listItemInfos()
-      ()
-
-      >>> view.listDeltas()
-      ()
-
-      >>> view.is_orderable()
-      False
-
-      >>> view.is_sortable()
-      False
-
-    The FolderContentsView checkers used by button actions::
-
-      >>> view.checkAllowedContentTypes()
-      True
-
-      >>> view.checkClipboardData()
-      False
-
-      >>> view.checkItems()
-      False
-
-    The FolderContentsView validators used by button actions::
-
-      >>> view.validateItemIds()
-      (False, u'Please select one or more items first.')
-      >>> view.validateItemIds(('foo',))
-      True
-
-      >>> view.validateClipboardData()
-      (False, u'Please copy or cut one or more items to paste first.')
-
-    Finally we have to clean up::
-
-      >>> from zope.testing.cleanup import cleanUp
-      >>> cleanUp()

Modified: Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/test_btreefolder.py
===================================================================
--- Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/test_btreefolder.py	2009-11-12 19:28:24 UTC (rev 105587)
+++ Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/test_btreefolder.py	2009-11-12 21:21:32 UTC (rev 105588)
@@ -18,10 +18,15 @@
 import unittest
 from Testing import ZopeTestCase
 
+from Products.CMFDefault.browser.tests.utils import clearVocabulary
+from Products.CMFDefault.browser.tests.utils import setupVocabulary
 from Products.CMFDefault.testing import FunctionalLayer
 
 
-ftest_suite = ZopeTestCase.FunctionalDocFileSuite('btreefolder.txt')
+ftest_suite = ZopeTestCase.FunctionalDocFileSuite('btreefolder.txt',
+                setUp=setupVocabulary,
+                tearDown=clearVocabulary
+                )
 ftest_suite.layer = FunctionalLayer
 
 def test_suite():

Modified: Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/test_document.py
===================================================================
--- Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/test_document.py	2009-11-12 19:28:24 UTC (rev 105587)
+++ Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/test_document.py	2009-11-12 21:21:32 UTC (rev 105588)
@@ -17,23 +17,16 @@
 
 import unittest
 from Testing import ZopeTestCase
-from Products.Five.schema import Zope2VocabularyRegistry
 
+from Products.CMFDefault.browser.tests.utils import clearVocabulary
+from Products.CMFDefault.browser.tests.utils import setupVocabulary
 from Products.CMFDefault.testing import FunctionalLayer
 
-def _setupVocabulary(ztc):
-    from zope.schema.vocabulary import setVocabularyRegistry
-    setVocabularyRegistry(Zope2VocabularyRegistry())
 
-def _clearVocabulary(ztc):
-    from zope.schema.vocabulary import _clear
-    _clear()
-
-
 ftest_suite = ZopeTestCase.FunctionalDocFileSuite(
                 'document.txt',
-                setUp=_setupVocabulary,
-                tearDown=_clearVocabulary,
+                setUp=setupVocabulary,
+                tearDown=clearVocabulary,
                )
 ftest_suite.layer = FunctionalLayer
 

Modified: Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/test_folder.py
===================================================================
--- Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/test_folder.py	2009-11-12 19:28:24 UTC (rev 105587)
+++ Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/test_folder.py	2009-11-12 21:21:32 UTC (rev 105588)
@@ -12,24 +12,128 @@
 ##############################################################################
 """ Test Products.CMFDefault.browser.folder
 
-$Id: test_folder.py 100397 2009-05-26 11:51:22Z jens $
+$Id$
 """
 
 import unittest
+
+from AccessControl.SecurityManagement import newSecurityManager
+from AccessControl.User import UnrestrictedUser
 from Testing import ZopeTestCase
-from zope.testing import doctest
 
+from zope.component import getSiteManager
+from zope.publisher.browser import TestRequest
+from zope.publisher.interfaces.browser import IBrowserPublisher
+
+from Products.CMFCore.PortalFolder import PortalFolder
+from Products.CMFCore.tests.base.dummy import DummySite, DummyTool
+from Products.CMFCore.tests.base.dummy import DummyUserFolder, DummyContent
+from Products.CMFCore.interfaces import IPropertiesTool
+
+from Products.CMFDefault.browser.folder import ContentsView
+from Products.CMFDefault.browser.tests.utils import clearVocabulary
+from Products.CMFDefault.browser.tests.utils import setupVocabulary
 from Products.CMFDefault.testing import FunctionalLayer
 
 
-utest_suite = doctest.DocFileSuite( 'folder_utest.txt'
-                                  , optionflags=doctest.NORMALIZE_WHITESPACE
-                                  )
-ftest_suite = ZopeTestCase.FunctionalDocFileSuite('folder.txt')
+class FolderBrowserViewTests(unittest.TestCase):
+
+    def setUp(self):
+        """Setup a site"""
+        # maybe there is a base class for this?
+        self.site = site = DummySite('site')
+        self.sm = getSiteManager()
+        mtool = site._setObject('portal_membership', DummyTool())
+        ptool = site._setObject('portal_properties', DummyTool())
+        self.sm.registerUtility(ptool, IPropertiesTool)
+        ttool = site._setObject('portal_types', DummyTool())
+        utool = site._setObject('portal_url', DummyTool())
+        folder = PortalFolder('test_folder')
+        self.folder = site._setObject('test_folder', folder)
+        self.uf = self.site._setObject('acl_users', DummyUserFolder())
+        
+    def _make_one(self, name="DummyItem"):
+        content = DummyContent(name)
+        content.portal_type = "Dummy Content"
+        self.folder._setObject(name, content)
+
+    def _make_batch(self):
+        """Add enough objects to force pagination"""
+        batch_size = ContentsView._BATCH_SIZE
+        for i in range(batch_size + 2):
+            content_id = "Dummy%s" % i
+            self._make_one(content_id)
+
+    def site_login(self):
+        newSecurityManager(None, 
+                    UnrestrictedUser('god', '', ['Manager'], ''))
+    
+    def test_view(self):
+        view = ContentsView(self.folder, TestRequest())
+        self.failUnless(IBrowserPublisher.providedBy(view))
+        
+    def test_up_info(self):
+        view = ContentsView(self.folder, TestRequest())
+        self.assertEquals({'url':u'', 'id':u'Root', 'icon':u''},
+                            view.up_info())
+        
+    def test_list_batch_items(self):
+        view = ContentsView(self.folder, TestRequest())
+        self.assertEquals(view.listBatchItems(), [])
+    
+    def test_is_orderable(self):
+        view = ContentsView(self.folder, TestRequest())
+        self.failIf(view.is_orderable())
+        
+    def test_sort_can_be_changed(self):
+        view = ContentsView(self.folder, TestRequest())
+        self.failIf(view.can_sort_be_changed())
+    
+    def test_empty_has_subobjects(self):
+        view = ContentsView(self.folder, TestRequest())
+        self.failIf(view.has_subobjects())
+        
+    def test_has_subobjects(self):
+        self._make_one()
+        view = ContentsView(self.folder, TestRequest())
+        self.failUnless(view.has_subobjects())
+        
+    def test_check_clipboard_data(self):
+        view = ContentsView(self.folder, TestRequest())
+        self.failIf(view.check_clipboard_data())
+    
+    def test_validate_items(self):
+        """Cannot validate forms without widgets"""
+        view = ContentsView(self.folder, TestRequest())
+        self.assertRaises(AttributeError, 
+                            view.validate_items, "", {'foo':'bar'})
+                            
+    def test_get_ids(self):
+        view = ContentsView(self.folder, TestRequest())
+        self.assertEquals(
+                        view._get_ids({'foo':'bar'}),
+                        [])
+        self.assertEquals(
+                        view._get_ids({'DummyItem1.select':True,
+                                       'DummyItem2.select':False,
+                                       'DummyItem3.select':True}),
+                        ['DummyItem1', 'DummyItem3'])
+        self.assertEquals(
+                        view._get_ids({'delta':True,
+                                       'delta':1}),
+                        []
+                        )
+
+
+ftest_suite = ZopeTestCase.FunctionalDocFileSuite('folder.txt',
+                        setUp=setupVocabulary,
+                        tearDown=clearVocabulary,
+                        )
+                        
 ftest_suite.layer = FunctionalLayer
 
 def test_suite():
-    return unittest.TestSuite((
-        utest_suite,
-        ftest_suite,
-    ))
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(FolderBrowserViewTests))
+    suite.addTest(unittest.TestSuite((ftest_suite,)))
+    return suite

Added: Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/utils.py
===================================================================
--- Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/utils.py	                        (rev 0)
+++ Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/utils.py	2009-11-12 21:21:32 UTC (rev 105588)
@@ -0,0 +1,27 @@
+##############################################################################
+#
+# Copyright (c) 2009 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+""" Shared utility functions for browser view tests
+
+$Id$
+"""
+
+from Products.Five.schema import Zope2VocabularyRegistry
+
+
+def setupVocabulary(testcase):
+    from zope.schema.vocabulary import setVocabularyRegistry
+    setVocabularyRegistry(Zope2VocabularyRegistry())
+
+def clearVocabulary(testcase):
+    from zope.schema.vocabulary import _clear
+    _clear()


Property changes on: Products.CMFDefault/trunk/Products/CMFDefault/browser/tests/utils.py
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native



More information about the checkins mailing list