[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"><<</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">>></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&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&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&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&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