Skip to content
Browse files

Merge remote-tracking branch 'origin' into ree-build-resources-allin

  • Loading branch information...
2 parents 6aecb50 + 875a7b5 commit a6f61c28d02f1db7dc1e65ba84095d60a00ce7e7 @reebalazs reebalazs committed May 9, 2013
View
24 TODO.txt
@@ -45,9 +45,6 @@ Can wait until after alpha 1
- Stamp created objects with creator information?
-- Allow second sort of a hypatia result set to specify that it wants a stable
- sort.
-
- Fix actions processor so it won't drop actions on the floor if
a keyboardinterrupt or another exception happens after a popall.
@@ -73,6 +70,25 @@ Can wait until after alpha 1
Docs
----
+Statsd
+++++++
+
+- Improve narrative documentation about how to view statistics from ``statsd``.
+
+Folder Contents
++++++++++++++++
+
+- Improve narrative documentation about column specifications (including
+ sorter).
+
+- Improve narrative documentation about custom buttons and associated views.
+
+Cataloging
+++++++++++
+
+- Improve current catalog docs with more info about: catalog index creation,
+ content indexing, query API
+
Misc
++++
@@ -81,7 +97,7 @@ Misc
Dump and Load
+++++++++++++
-- Add API docs for dump/load, and dump contexts.
+- Add API and narrative docs for dump/load, and dump contexts.
Can Wait Forever (Nice-to-Have)
View
10 docs/api.rst
@@ -317,8 +317,6 @@ Other Helpers
.. automodule:: substanced.objectmap
-.. autofunction:: find_objectmap
-
.. autoclass:: ObjectMap
:members:
@@ -461,6 +459,14 @@ Other Helpers
.. autofunction:: find_services
+.. autofunction:: find_objectmap
+
+.. autofunction:: find_catalogs
+
+.. autofunction:: find_catalog
+
+.. autofunction:: find_index
+
.. autofunction:: is_folder
.. autofunction:: is_service
View
51 docs/folder_contents.rst
@@ -36,27 +36,18 @@ mappings conforming to the datagrid's contract. For example:
modified = modified.isoformat()
return default_columnspec + [
{'name': 'Title',
- 'field': 'title',
'value': getattr(subobject, 'title', subobject_name),
- 'sortable': True,
- 'formatter': 'icon_label_url',
},
{'name': 'Created',
- 'field': 'created',
'value': created,
- 'sortable': True,
'formatter': 'date',
},
{'name': 'Last edited',
- 'field': 'modified',
'value': modified,
- 'sortable': True,
'formatter': 'date',
},
{'name': 'Creator',
- 'field': 'creator',
'value': user_name,
- 'sortable': True,
}
]
@@ -77,10 +68,33 @@ column headers, your callable is invoked on the first resource.
Later, this callable is used to get the value for the fields of each
column for each resource in a request's batch.
-The mappings returned can indicate whether a particular column should
-be sortable. In general, it is better if your sortable columns are
-hooked up to a catalog index, in case the folder contains a large set
-of resources.
+The mappings returned can indicate whether a particular column should be
+sorted. If you want your column to be sortable, you must provide a ``sorter``
+key in the mapping. If supplied, the ``sorter`` value must either be ``None``
+if the column is not sortable, or a function which accepts a resource (the
+folder), a "resultset", a ``limit`` keyword argument, and a ``reverse`` keyword
+argument and which must return a sorted result set. Here's an example sorter:
+
+.. code-block:: python
+
+ from substanced.util import find_index
+
+ def sorter(folder, resultset, reverse=False, limit=None):
+ index = find_index(folder, 'mycatalog', 'date')
+ if index is not None:
+ resultset = resultset.sort(index, reverse=reverse, limit=limit)
+ return resultset
+
+ def my_columns(folder, subobject, request, default_columnspec):
+ return default_columnspec + [
+ {'name': 'Date',
+ 'value': getattr(subobject, 'title', subobject_name),
+ 'sorter': 'sorter',
+ },
+
+Most often, sorting is done by passing a catalog index into the resultset.sort
+method as above (resultset.sort returns another resultset), but sorting can be
+performed manually, as long as the sorter returns a resultset.
Buttons
=======
@@ -306,7 +320,7 @@ The ``binder_columns`` points to a callable where we perform the work
to both add the column to the list of columns, but also specify how to
get the row data for that column:
-.. code-block: python
+.. code-block:: python
def binder_columns(folder, subobject, request, default_columnspec):
subobject_name = getattr(subobject, '__name__', str(subobject))
@@ -325,27 +339,18 @@ get the row data for that column:
modified = modified.isoformat()
return default_columnspec + [
{'name': 'Title',
- 'field': 'title',
'value': getattr(subobject, 'title', subobject_name),
- 'sortable': True,
- 'formatter': 'icon_label_url',
},
{'name': 'Created',
- 'field': 'created',
'value': created,
- 'sortable': True,
'formatter': 'date',
},
{'name': 'Last edited',
- 'field': 'modified',
'value': modified,
- 'sortable': True,
'formatter': 'date',
},
{'name': 'Creator',
- 'field': 'creator',
'value': user_name,
- 'sortable': True,
}
]
View
2 substanced/catalog/__init__.py
@@ -165,7 +165,7 @@ def unindex_resource(self, resource_or_oid, action_mode=None):
which explicitly indicates that you'd like to use the index's
action_mode value."""
oid = get_oid(resource_or_oid, resource_or_oid)
- if not isinstance(oid, int):
+ if not isinstance(oid, (int, long)):
raise ValueError(
'resource_or_oid must be a resource object with an __oid__ '
'attribute or an integer oid'
View
2 substanced/catalog/indexes.py
@@ -110,7 +110,7 @@ def reindex_resource(self, resource, oid=None, action_mode=None):
self.add_action(action)
def unindex_resource(self, resource_or_oid, action_mode=None):
- if isinstance(resource_or_oid, int):
+ if isinstance(resource_or_oid, (int, long)):
oid = resource_or_oid
else:
oid = oid_from_resource(resource_or_oid)
View
2 substanced/catalog/util.py
@@ -2,7 +2,7 @@
def oid_from_resource(resource):
oid = get_oid(resource, None)
- if not isinstance(oid, int):
+ if not isinstance(oid, (int, long)):
raise ValueError(
'Resource must be an object with an integer __oid__ attribute'
)
View
2 substanced/folder/__init__.py
@@ -226,7 +226,7 @@ def is_reorderable(self):
""" Return true if the folder can be reordered, false otherwise."""
return self._reorderable
- def sort(self, oids, reverse=False, limit=None):
+ def sort(self, oids, reverse=False, limit=None, **kw):
# used by the hypatia resultset "sort" method when the folder contents
# view uses us as a "sort index"
if self._order_oids is not None:
View
21 substanced/interfaces.py
@@ -586,27 +586,6 @@ def get_size(self):
""" Return the size in bytes of the data in the blob associated with
the file"""
-class IWorkflowState(Interface):
- """Represent a single logical state in the workflow state machine.
- """
- name = Attribute("Identifier (unique within a workflow)")
- title = Attribute("Display title")
-
- def __call__(content, request, transition, workflow):
- """ Callback when content enters the state.
- """
-
-class IWorkflowTransition(Interface):
- name = Attribute("Identifier (unique within a workflow)")
- title = Attribute("Display title")
- from_state = Attribute("Beginning state for the transition")
- to_state = Attribute("Ending state for the transition")
- permission = Attribute("Permission protecteing the transition")
-
- def __call__(content, request, transition, workflow):
- """ Callback when content begins a transition.
- """
-
class IWorkflow(Interface):
""""""
View
7 substanced/objectmap/__init__.py
@@ -20,8 +20,8 @@
from ..util import (
get_oid,
get_factory_type,
- acquire,
set_oid,
+ find_objectmap,
)
from ..interfaces import IObjectMap
@@ -911,11 +911,6 @@ def clear(self):
""" Clear all references in this relationship. """
self.disconnect(self.oids)
-def find_objectmap(context):
- """ Returns the object map for the root object in the lineage of the
- ``context`` or ``None`` if no objectmap can be found."""
- return acquire(context, '__objectmap__', None)
-
def has_references(context):
objectmap = find_objectmap(context)
if objectmap is None:
View
14 substanced/objectmap/tests.py
@@ -2017,20 +2017,6 @@ def test_clear(self):
[(-1, 1, 'reftype'), (-1, 2, 'reftype')]
)
-class Test_find_objectmap(unittest.TestCase):
- def _callFUT(self, context):
- from . import find_objectmap
- return find_objectmap(context)
-
- def test_found(self):
- inst = Dummy()
- inst.__objectmap__ = '123'
- self.assertEqual(self._callFUT(inst), '123')
-
- def test_unfound(self):
- inst = Dummy()
- self.assertEqual(self._callFUT(inst), None)
-
class Test_ReferencedPredicate(unittest.TestCase):
def _makeOne(self, val, config):
from . import _ReferencedPredicate
View
14 substanced/sdi/__init__.py
@@ -46,7 +46,10 @@
CENTER2 = Sentinel('CENTER2')
from ..objectmap import find_objectmap
-from ..util import acquire
+from ..util import (
+ acquire,
+ find_index,
+ )
MANAGE_ROUTE_NAME = 'substanced_manage'
@@ -351,15 +354,20 @@ def is_view(intr):
return manually_ordered + topo_ordered
+def name_sorter(resource, resultset, limit=None, reverse=False):
+ index = find_index(resource, 'system', 'name')
+ if index is not None:
+ resultset = resultset.sort(index, limit=limit, reverse=reverse)
+ return resultset
+
def default_sdi_columns(folder, subobject, request):
""" The default columns content-type hook """
name = getattr(subobject, '__name__', '')
return [
{'name': 'Name',
- 'field': 'name',
'value': name,
'formatter': 'icon_label_url',
- 'sortable': True}
+ 'sorter': name_sorter}
]
def default_sdi_buttons(folder, request):
View
1 substanced/sdi/static/js/slickgrid-config.js
@@ -289,3 +289,4 @@
});
})(window.jQuery);
+
View
8 substanced/sdi/tests/test_sdi.py
@@ -678,29 +678,29 @@ def _makeRequest(self, icon):
return request
def test_it(self):
+ from substanced.sdi import name_sorter
fred = testing.DummyResource()
fred.__name__ = 'fred'
request = self._makeRequest('icon')
result = self._callFUT(None, fred, request)
self.assertEqual(
result,
- [{'sortable': True,
+ [{'sorter': name_sorter,
'name': 'Name',
- 'field':'name',
'formatter':'icon_label_url',
'value': 'fred'}]
)
def test_it_with_callable_icon(self):
+ from substanced.sdi import name_sorter
fred = testing.DummyResource()
fred.__name__ = 'fred'
request = self._makeRequest(lambda *arg: 'icon')
result = self._callFUT(None, fred, request)
self.assertEqual(
result,
- [{'sortable': True,
+ [{'sorter': name_sorter,
'name': 'Name',
- 'field':'name',
'formatter':'icon_label_url',
'value': 'fred'}]
)
View
315 substanced/sdi/views/folder.py
@@ -3,7 +3,6 @@
import colander
-
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_defaults
from pyramid.security import has_permission
@@ -110,78 +109,112 @@ def _buttons(self):
request = self.request
buttons = default_sdi_buttons(context, request)
custom_buttons = request.registry.content.metadata(
- context, 'buttons', _marker)
+ context,
+ 'buttons',
+ _marker
+ )
if custom_buttons is None:
return []
if custom_buttons is not _marker:
buttons = custom_buttons(context, request, buttons)
return buttons
- def _column_headers(self):
+ def _columns(self):
context = self.context
request = self.request
-
- headers = []
-
- content_registry = request.registry.content
-
columns = default_sdi_columns(self, None, request)
-
- custom_columns = content_registry.metadata(context, 'columns', _marker)
-
+ custom_columns = request.registry.content.metadata(
+ context,
+ 'columns',
+ _marker
+ )
if custom_columns is None:
- return headers
-
+ return []
if custom_columns is not _marker:
columns = custom_columns(context, None, request, columns)
-
+ return columns
+
+ def _column_headers(self, columns):
+ is_ordered = self.context.is_ordered()
+
+ headers = []
+
for order, column in enumerate(columns):
name = column['name']
- field = column['field']
- sortable = column.get('sortable', True)
+ sortable = column.get('sorter', None) is not None
- if context.is_ordered():
+ if is_ordered:
+ # We don't currently allow ordered folders to be resorted by
+ # columns
sortable = False
formatter = column.get('formatter', '')
- cssClass = column.get('cssClass', '')
- cssClass= "cell-%s" % field + ((' ' + cssClass) if cssClass else '')
+ css_class = column.get('css_class', '')
+ css_name = name.replace(' ', '-')
+ css_class = ("cell-%s %s" % (css_name, css_class)).strip()
+
+ # XXX CM: Do we really need all of "id", "name", and "field" below?
+ # Ree?
headers.append({
- "id": field,
+ "id": name,
"name": name,
- "field": field,
+ "field": name,
"width": 120,
"minWidth": 120,
- "cssClass": cssClass,
+ "cssClass": css_class,
"sortable": sortable,
"formatterName": formatter,
})
return headers
- def _sort_index_from_index_name(self, index_name):
- if index_name is None:
- return None
- catalog = find_catalog(self.context, 'system')
- try:
- return catalog[index_name]
- except KeyError:
- keys = tuple(catalog.keys())
- raise KeyError(
- (
- 'Index %r is missing from the system catalog. '
- 'The name of the sorting column must match the '
- 'name of a catalog index. %r' % (index_name, keys)
- )
- )
+ def _sort_info(self, columns, sort_column_name=None):
+ context = self.context
+ sort_column = None
+ sorter = None
+
+ # Is the folder content ordered?
+ is_ordered = context.is_ordered()
+
+ if is_ordered:
+ # If the folder is ordered, use the folder itself as the sort
+ # index; ordered folders cannot currently be viewed reordered
+ # by anything except their explicit ordering.
+ def sorter(folder, resultset, reverse=False, limit=None):
+ return resultset.sort(folder, limit=limit, reverse=reverse)
+
+ elif sort_column_name is None:
+ # The default sort always uses the first column with a sorter.
+ for col in columns:
+ if col.get('sorter'):
+ sort_column_name = col['name']
+ sort_column = col
+ break
+
+ else:
+ # Nondefault sort column
+ for col in columns:
+ if col.get('name') == sort_column_name:
+ sort_column = col
+ break
+
+ if sort_column is not None:
+ sorter = sort_column['sorter']
+
+ return {
+ 'column':sort_column,
+ 'column_name':sort_column_name,
+ 'sorter':sorter,
+ }
+
def _folder_contents(
self,
start=None,
end=None,
reverse=False,
- sort_index=None,
+ sort_column_name=None,
filter_text=None,
):
@@ -353,20 +386,21 @@ def button1(context, request):
``request``, and a default column specification. It will be called once
for every object in the folder to obtain column representations for
each of its subobjects. It must return a list of dictionaries with at
- least a ``name`` key for the column header, a ``field`` key for the
- field name to use in javascript manipulations, and a ``value`` key with
+ least a ``name`` key for the column header and a ``value`` key with
the correct column value given the subobject. The callable **must** be
prepared to receive subobjects that will *not* have the desired
attributes (the subobject passed will be ``None`` at least once in
order for the system to compute headers).
- In addition to ``name``, ``field`` and ``value``, the column dictionary
- can contain the keys ``sortable`` and ``formatter``. The first one
- specifies whether the column will have buttons for sorting the
- rows. The default value is ``True``. The last key, ``formatter``, can
- give the name of a javascript method for formatting the
- ``value``. Currently, available formatters are ``icon_label_url`` and
- ``date``.
+ In addition to ``name`` and ``value``, the column dictionary may
+ contain the keys ``sorter`` and ``formatter``. The ``sorter`` will
+ either be ``None`` if the column is not sortable, or a callback which
+ accepts a resource (the folder), a resultset, a ``limit`` keyword
+ argument, and a ``reverse`` keyword argument and which must return a
+ sorted result set. The default ``sorter`` value is ``None``. The last
+ key, ``formatter``, can give the name of a javascript method for
+ formatting the ``value``. Currently, available formatters are
+ ``icon_label_url`` and ``date``.
The ``icon_label_url`` formatter gets the URL and icon (if any) of the
subobject and creates a link using ``value`` as link text. The ``date``
@@ -375,17 +409,23 @@ def button1(context, request):
Here's an example of using the ``columns`` content type hook::
+ from substanced.util import find_index
+
+ def sorter(folder, resultset, reverse=False, limit=None):
+ index = find_index(folder, 'mycatalog', 'date')
+ if index is not None:
+ resultset = resultset.sort(
+ index, reverse=reverse, limit=limit)
+ return resultset
+
def custom_columns(folder, subobject, request, default_columnspec):
return default_columnspec + [
- {'name': 'Review date',
- 'field': 'date',
+ {'name': 'Review Date',
'value': getattr(subobject, 'review_date', ''),
- 'sortable': True,
+ 'sorter': sorter,
'formatter': 'date'},
{'name': 'Rating',
- 'field': 'rating',
- 'value': getattr(subobject, 'rating', ''),
- 'sortable': True}
+ 'value': getattr(subobject, 'rating', '')}
]
@content(
@@ -407,23 +447,28 @@ class MyCustomFolder(Persistent):
from myapp import custom_user_columns
def main(global_config, **settings):
- config = Configurator(root_factory=root_factory, settings=settings)
+ config = Configurator(
+ root_factory=root_factory,
+ settings=settings
+ )
config.include('substanced')
- config.add_content_type(IUsers,
- factory=Users,
- icon='icon-list-alt',
- columns=custom_user_columns)
+ config.add_content_type(
+ IUsers,
+ factory=Users,
+ icon='icon-list-alt',
+ columns=custom_user_columns
+ )
config.scan()
- XXX TODO Document ``sort_index``, ``reverse``, ``filter_text``.
+ XXX TODO Document ``sort_column_name``, ``reverse``, ``filter_text``.
"""
folder = self.context
request = self.request
- catalog = find_catalog(folder, 'system')
+ system_catalog = find_catalog(folder, 'system')
objectmap = find_objectmap(folder)
- path = catalog['path']
- allowed = catalog['allowed']
+ path = system_catalog['path']
+ allowed = system_catalog['allowed']
if start is None:
start = 0
@@ -435,31 +480,41 @@ def main(global_config, **settings):
allowed.allows(request, 'sdi.view') )
if filter_text:
- if not filter_text.endswith('*'):
- filter_text = filter_text + '*' # glob (prefix) search
- text = catalog['text']
- q = q & text.eq(filter_text)
+ filter_text_globs = filter(None, filter_text.split())
+ if filter_text_globs:
+ text = system_catalog['text']
+ for filter_glob in filter_text_globs:
+ if not filter_glob.endswith('*'):
+ filter_glob = filter_glob + '*' # glob (prefix) search
+ q = q & text.eq(filter_glob)
- if not sort_index:
- # An empty sort index means no sorting.
- sort_index = None
+ resultset = q.execute()
+ # NB: must take snapshot of folder_length *before* limiting the length
+ # of the resultset via any search
+ folder_length = len(resultset)
- if folder.is_ordered() and sort_index is None:
- # hypatia resultset.sort will call IFolder.sort method
- sort_index = folder
+ columns = self._columns()
+
+ sort_info = self._sort_info(
+ columns,
+ sort_column_name=sort_column_name,
+ )
- if sort_index is None:
- sort_index = catalog['name']
+ sorter = sort_info['sorter']
+ sort_column_name = sort_info['column_name']
- resultset = q.execute()
- folder_length = len(resultset)
+ if sorter is not None:
+ resultset = sorter(folder, resultset, reverse=reverse, limit=end)
- resultset = resultset.sort(sort_index, reverse=reverse, limit=end)
ids = resultset.ids
can_manage = bool(has_permission('sdi.manage-contents', folder,request))
+
custom_columns = request.registry.content.metadata(
- folder, 'columns', _marker)
+ folder,
+ 'columns',
+ _marker
+ )
buttons = self._buttons()
@@ -468,6 +523,9 @@ def main(global_config, **settings):
for oid in itertools.islice(ids, start, end):
resource = objectmap.object_for(oid)
name = getattr(resource, '__name__', '')
+ # XXX CM: computation of deletable here should probably attached to
+ # the delete button as an ``enabled_for`` instead of being treated
+ # specially.
deletable = getattr(resource, '__sdi_deletable__', None)
if deletable is not None:
if callable(deletable):
@@ -486,17 +544,26 @@ def main(global_config, **settings):
# from the client, when a row is selected for an operation.)
deletable=deletable,
name=name,
- name_icon=icon,
- name_url=url,
)
columns = default_sdi_columns(folder, resource, request)
if custom_columns is None:
columns = []
elif custom_columns is not _marker:
columns = custom_columns(folder, resource, request, columns)
for column in columns:
- field = column['field']
- record[field] = column['value']
+ # XXX CM: adding arbitrary keys to the record based on
+ # configuration input is a bad idea here because we can't
+ # guarantee a column name won't override the "reserved" names
+ # (name, deletable, id) added to the record above. Ree?
+ cname = column['name']
+ record[cname] = column['value']
+ # XXX CM: we should document the fact that each column can have
+ # its own URL/icon if we keep this; although we should probably
+ # not send them if the formatter doesn't want them. Need a
+ # better formatter abstraction here, I think;
+ # maybe server-side? Opinions, Ree?
+ record[cname+'_icon'] = column.get('icon', icon)
+ record[cname+'_url'] = column.get('url', url)
disable = []
for button_group in buttons:
for button in button_group['buttons']:
@@ -510,7 +577,12 @@ def main(global_config, **settings):
record['disable'] = disable
records.append(record)
- return folder_length, records
+ return {
+ 'length':folder_length,
+ 'records':records,
+ 'sort_column_name':sort_column_name,
+ 'columns':columns,
+ }
@mgmt_view(
request_method='GET',
@@ -521,8 +593,6 @@ def show(self):
request = self.request
context = self.context
- columns = self._column_headers()
-
buttons = self._buttons()
addables = self.sdi_add_views(context, request)
@@ -537,40 +607,21 @@ def show(self):
rowHeight = 35,
)
- minimum_load = 40 # load at least this many records.
-
- # Is the folder content ordered?
- is_ordered = context.is_ordered()
is_reorderable = context.is_reorderable()
- if is_ordered:
- # Nothing is sortable if the content is ordered.
- sortCol = None
- else:
- # The sorted column is always the first sortable column in the row.
- # This gives the visual cue to the user who can see the sorted
- # column and its direction. It does _not_ affect the actual
- # sorting, which is done on the server and not on the grid.
- for col in columns:
- if col.get('sortable', True):
- # We found the first sortable column.
- sortCol = col['field']
- break
- else:
- # Nothing is sortable.
- sortCol = None
+ end = 40 # load at least this many records.
+ start = 0 # start at record number zero
- sortDir = True # True ascending, or False descending.
- reverse = (not sortDir)
+ folder_contents = self._folder_contents(start, end)
- folder_length, records = self._folder_contents(
- 0, minimum_load, reverse=reverse,
- sort_index=self._sort_index_from_index_name(sortCol),
- )
+ records = folder_contents['records']
+ folder_length = folder_contents['length']
+ sort_column_name = folder_contents['sort_column_name']
+ column_headers = self._column_headers(folder_contents['columns'])
items = {
- 'from':0,
- 'to':minimum_load,
+ 'from':start,
+ 'to':end,
'records':records,
'total':folder_length,
}
@@ -580,28 +631,28 @@ def show(self):
slickgrid_wrapper_options = JsonDict(
# below line refers to slickgrid-config.js
- configName='sdi-content-grid',
- columns=columns,
- slickgridOptions=slickgrid_options,
- items=items,
+ configName = 'sdi-content-grid',
+ columns = column_headers,
+ slickgridOptions = slickgrid_options,
+ items = items,
# initial sorting (The grid will really not sort the initial data,
# just display it in the order we provide it. It will use the
# information to just visually show in the headers the sorted
# column.)
- sortCol=sortCol,
- sortDir=sortDir,
+ sortCol = sort_column_name,
+ sortDir = True,
# is the grid reorderable?
- isReorderable=is_reorderable,
+ isReorderable = is_reorderable,
#
# Parameters for the remote data model
- url='', # use same url for ajax
- minimumLoad=minimum_load,
+ url = '', # use same url for ajax
+ minimumLoad = end,
)
result = dict(
- addables=addables,
- buttons=buttons,
- slickgrid_wrapper_options=slickgrid_wrapper_options,
+ addables = addables,
+ buttons = buttons,
+ slickgrid_wrapper_options = slickgrid_wrapper_options,
)
return result
@@ -620,17 +671,23 @@ def _get_json(self):
if 'from' in request.params:
start = int(request.params.get('from'))
end = int(request.params.get('to'))
- sort_col = request.params.get('sortCol')
+ sort_column_name = request.params.get('sortCol')
sort_dir = request.params.get('sortDir') in ('true', 'True')
filter_text = request.params.get('filter', '').strip()
reverse = (not sort_dir)
- folder_length, records = self._folder_contents(
- start, end, reverse=reverse, filter_text=filter_text,
- sort_index=self._sort_index_from_index_name(sort_col),
+ folder_contents = self._folder_contents(
+ start,
+ end,
+ reverse=reverse,
+ filter_text=filter_text,
+ sort_column_name=sort_column_name,
)
+ folder_length = folder_contents['length']
+ records = folder_contents['records']
+
items = {
'from': start,
'to': end,
View
1,066 substanced/sdi/views/tests/test_folder.py
@@ -103,344 +103,501 @@ def _makeRequest(self, **kw):
request = testing.DummyRequest()
request.sdiapi = DummySDIAPI()
request.sdiapi.flash_with_undo = request.session.flash
-
request.registry.content = DummyContent(**kw)
return request
- def test_show_no_columns(self):
- dummy_column_headers = []
+ def _makeCatalogs(self, oids=()):
+ catalogs = DummyCatalogs()
+ catalog = DummyCatalog(oids)
+ catalogs['system'] = catalog
+ return catalogs
+
+ def test__buttons_is_None(self):
context = testing.DummyResource()
- request = self._makeRequest(columns=None)
+ request = self._makeRequest(buttons=None)
inst = self._makeOne(context, request)
- inst._folder_contents = mock.Mock(
- return_value=dummy_folder_contents_0
- )
- inst._column_headers = mock.Mock(return_value=dummy_column_headers)
- inst.sdi_add_views = mock.Mock(return_value=('b',))
- context.is_reorderable = mock.Mock(return_value=False)
- context.is_ordered = mock.Mock(return_value=False)
- result = inst.show()
- self.assert_('slickgrid_wrapper_options' in result)
- slickgrid_wrapper_options = result['slickgrid_wrapper_options']
- self.assert_('slickgridOptions' in slickgrid_wrapper_options)
- self.assertEqual(
- slickgrid_wrapper_options['configName'],
- 'sdi-content-grid'
- )
- # None because it cannot be sorted.
- self.assertEqual(slickgrid_wrapper_options['isReorderable'], False)
- self.assertEqual(slickgrid_wrapper_options['sortCol'], None)
- self.assertEqual(slickgrid_wrapper_options['sortDir'], True)
- self.assertEqual(slickgrid_wrapper_options['url'], '')
- self.assert_('items' in slickgrid_wrapper_options)
- self.assertEqual(slickgrid_wrapper_options['items']['from'], 0)
- self.assertEqual(slickgrid_wrapper_options['items']['to'], 40)
- self.assertEqual(slickgrid_wrapper_options['items']['total'], 1)
- self.assert_('records' in slickgrid_wrapper_options['items'])
- records = slickgrid_wrapper_options['items']['records']
- self.assertEqual(len(records), 1)
- self.assertEqual(records[0], {
- 'name': 'the_name',
- 'deletable': True,
- 'name_url': 'http://foo.bar',
- 'id': 'the_name',
- 'name_icon': 'the_icon',
- })
- addables = result['addables']
- self.assertEqual(addables, ('b',))
- buttons = result['buttons']
- self.assertEqual(len(buttons), 2)
+ result = inst._buttons()
+ self.assertEqual(result, [])
- def test_show_with_columns(self):
- dummy_column_headers = [{
- 'field': 'col1',
- 'sortable': True,
- }, {
- 'field': 'col2',
- 'sortable': True,
- }]
+ def test__buttons_is_clbl(self):
context = testing.DummyResource()
- request = self._makeRequest()
+ def sdi_buttons(context, request, default_buttons):
+ return 'abc'
+ request = self._makeRequest(buttons=sdi_buttons)
inst = self._makeOne(context, request)
- inst._folder_contents = mock.Mock(
- return_value=dummy_folder_contents_2
- )
- inst._column_headers = mock.Mock(return_value=dummy_column_headers)
- inst.sdi_add_views = mock.Mock(return_value=('b',))
- context.is_reorderable = mock.Mock(return_value=False)
- context.is_ordered = mock.Mock(return_value=False)
- with mock.patch('substanced.sdi.views.folder.find_catalog') as find_catalog:
- find_catalog.return_value = {'col1': 'COL1', 'col2': 'COL2'}
- result = inst.show()
- self.assert_('slickgrid_wrapper_options' in result)
- slickgrid_wrapper_options = result['slickgrid_wrapper_options']
- self.assert_('slickgridOptions' in slickgrid_wrapper_options)
- self.assertEqual(
- slickgrid_wrapper_options['configName'], 'sdi-content-grid'
- )
- self.assertEqual(slickgrid_wrapper_options['isReorderable'], False)
- self.assertEqual(slickgrid_wrapper_options['sortCol'], 'col1')
- self.assertEqual(slickgrid_wrapper_options['sortDir'], True)
- self.assertEqual(slickgrid_wrapper_options['url'], '')
- self.assert_('items' in slickgrid_wrapper_options)
- self.assertEqual(slickgrid_wrapper_options['items']['from'], 0)
- self.assertEqual(slickgrid_wrapper_options['items']['to'], 40)
- self.assertEqual(slickgrid_wrapper_options['items']['total'], 1)
- self.assert_('records' in slickgrid_wrapper_options['items'])
- records = slickgrid_wrapper_options['items']['records']
- self.assertEqual(len(records), 1)
- self.assertEqual(records[0], {
- 'name': 'the_name',
- 'col1': 'value4col1',
- 'col2': 'value4col2',
- 'deletable': True,
- 'name_url': 'http://foo.bar',
- 'id': 'the_name',
- 'name_icon': 'the_icon',
- })
+ result = inst._buttons()
+ self.assertEqual(result, 'abc')
- addables = result['addables']
- self.assertEqual(addables, ('b',))
- buttons = result['buttons']
- self.assertEqual(len(buttons), 2)
+ def test__buttons_enabled_for_true(self):
+ from substanced.interfaces import IFolder
+ context = DummyFolder(__provides__=IFolder)
+ context['catalogs'] = self._makeCatalogs(oids=[1])
+ result = testing.DummyResource()
+ result.__name__ = 'fred'
+ context.__objectmap__ = DummyObjectMap(result)
+ def sdi_buttons(contexr, request, default_buttons):
+ return [{'type': 'single',
+ 'buttons': [{'enabled_for': lambda x,y,z: True,
+ 'id': 'Button'}]}]
+ request = self._makeRequest(buttons=sdi_buttons)
+ inst = self._makeOne(context, request)
+ folder_contents = inst._folder_contents()
+ length, records = folder_contents['length'], folder_contents['records']
+ self.assertEqual(length, 1)
+ self.assertEqual(records[0]['disable'], [])
- def test_show_non_sortable_columns(self):
- dummy_column_headers = [{
- 'field': 'col1',
- 'sortable': False,
- }, {
- 'field': 'col2',
- 'sortable': True,
- }]
- context = testing.DummyResource()
- request = self._makeRequest()
+ def test__buttons_enabled_for_false(self):
+ from substanced.interfaces import IFolder
+ context = DummyFolder(__provides__=IFolder)
+ context['catalogs'] = self._makeCatalogs(oids=[1])
+ result = testing.DummyResource()
+ result.__name__ = 'fred'
+ context.__objectmap__ = DummyObjectMap(result)
+ def sdi_buttons(context, request, default_buttons):
+ return [{'type': 'single',
+ 'buttons': [{'enabled_for': lambda x,y,z: False,
+ 'id': 'Button'}]}]
+ request = self._makeRequest(buttons=sdi_buttons)
inst = self._makeOne(context, request)
- inst._folder_contents = mock.Mock(
- return_value=dummy_folder_contents_2
- )
- inst._column_headers = mock.Mock(return_value=dummy_column_headers)
- inst.sdi_add_views = mock.Mock(return_value=('b',))
- context.is_reorderable = mock.Mock(return_value=False)
- context.is_ordered = mock.Mock(return_value=False)
- with mock.patch('substanced.sdi.views.folder.find_catalog') as find_catalog:
- find_catalog.return_value = {'col1': 'COL1', 'col2': 'COL2'}
- result = inst.show()
- self.assert_('slickgrid_wrapper_options' in result)
- slickgrid_wrapper_options = result['slickgrid_wrapper_options']
- self.assert_('slickgridOptions' in slickgrid_wrapper_options)
- self.assertEqual(
- slickgrid_wrapper_options['configName'],
- 'sdi-content-grid'
- )
+ folder_contents = inst._folder_contents()
+ length, records = folder_contents['length'], folder_contents['records']
+ self.assertEqual(length, 1)
+ self.assertEqual(records[0]['disable'], ['Button'])
- self.assertEqual(slickgrid_wrapper_options['isReorderable'], False)
- # col2 here, as col1 was not sortable.
- self.assertEqual(slickgrid_wrapper_options['sortCol'], 'col2')
-
- self.assertEqual(slickgrid_wrapper_options['sortDir'], True)
- self.assertEqual(slickgrid_wrapper_options['url'], '')
- self.assert_('items' in slickgrid_wrapper_options)
- self.assertEqual(slickgrid_wrapper_options['items']['from'], 0)
- self.assertEqual(slickgrid_wrapper_options['items']['to'], 40)
- self.assertEqual(slickgrid_wrapper_options['items']['total'], 1)
- self.assert_('records' in slickgrid_wrapper_options['items'])
- records = slickgrid_wrapper_options['items']['records']
- self.assertEqual(len(records), 1)
- self.assertEqual(records[0], {
- 'name': 'the_name',
- 'col1': 'value4col1',
- 'col2': 'value4col2',
- 'deletable': True,
- 'name_url': 'http://foo.bar',
- 'id': 'the_name',
- 'name_icon': 'the_icon',
- })
- addables = result['addables']
- self.assertEqual(addables, ('b',))
- buttons = result['buttons']
- self.assertEqual(len(buttons), 2)
- # We don't actually see the sortability in the records, because
- # this information is in the columns metadata. So, we test this
- # in test_metadata_for_non_sortable_columns.
-
- def test_wrong_index(self):
- dummy_column_headers = [{
- 'field': 'col1',
- 'sortable': False,
- }, {
- 'field': 'colNOSUCH',
- 'sortable': True,
- }]
- context = testing.DummyResource()
- request = self._makeRequest()
+ def test__buttons_enabled_for_non_callable(self):
+ from substanced.interfaces import IFolder
+ context = DummyFolder(__provides__=IFolder)
+ context['catalogs'] = self._makeCatalogs(oids=[1])
+ result = testing.DummyResource()
+ result.__name__ = 'fred'
+ context.__objectmap__ = DummyObjectMap(result)
+ def sdi_buttons(contexr, request, default_buttons):
+ return [{'type': 'single',
+ 'buttons': [{'enabled_for': 'not callable',
+ 'id': 'Button'}]}]
+ request = self._makeRequest(buttons=sdi_buttons)
inst = self._makeOne(context, request)
- inst._folder_contents = mock.Mock(
- return_value=dummy_folder_contents_2
- )
- inst._column_headers = mock.Mock(return_value=dummy_column_headers)
- inst.sdi_add_views = mock.Mock(return_value=('b',))
- context.is_reorderable = mock.Mock(return_value=False)
- context.is_ordered = mock.Mock(return_value=False)
- with mock.patch('substanced.sdi.views.folder.find_catalog') as find_catalog:
- find_catalog.return_value = {'col1': 'COL1', 'col2': 'COL2'}
- self.assertRaises(KeyError, inst.show)
+ folder_contents = inst._folder_contents()
+ length, records = folder_contents['length'], folder_contents['records']
+ self.assertEqual(length, 1)
+ self.assertEqual(records[0]['disable'], [])
- def test_show_json(self):
+ def test__columns_custom_columns_is_None(self):
context = testing.DummyResource()
- request = self._makeRequest()
- request.params['from'] = '1'
- request.params['to'] = '2'
+ request = self._makeRequest(columns=None)
inst = self._makeOne(context, request)
- inst._folder_contents = mock.Mock(
- return_value=dummy_folder_contents_0
- )
- result = inst.show_json()
- self.assertEqual(
- result,
- {'from':1, 'to':2, 'records':dummy_folder_contents_0[1], 'total':1}
- )
+ result = inst._columns()
+ self.assertEqual(result, [])
- def test_show_json_no_from(self):
+ def test__columns_custom_columns_doesnt_exist(self):
context = testing.DummyResource()
request = self._makeRequest()
inst = self._makeOne(context, request)
- result = inst.show_json()
- self.assertEqual(
- result,
- {}
- )
+ result = inst._columns()
+ self.assertEqual(len(result), 1)
+
+ def test__columns_custom_columns_exists(self):
+ context = testing.DummyResource()
+ def columns(context, subobject, request, columns):
+ self.assertEqual(len(columns), 1)
+ return ['abc', '123']
+ request = self._makeRequest(columns=columns)
+ inst = self._makeOne(context, request)
+ result = inst._columns()
+ self.assertEqual(len(result), 2)
def test__column_headers_for_non_sortable_columns(self):
- def sd_columns(folder, subobject, request, default_columns):
- self.assertEqual(len(default_columns), 1)
- return [
- {'name': 'Col 1', 'field': 'col1', 'value':
- 'col1', 'sortable': False},
- {'name': 'Col 2', 'field': 'col2', 'value': 'col2'}
- ]
context = testing.DummyResource(is_ordered=lambda: False)
- request = self._makeRequest(columns=sd_columns)
+ request = self._makeRequest()
inst = self._makeOne(context, request)
- result = inst._column_headers()
+ columns = [
+ {'name': 'Col 1',
+ 'value': 'col1',
+ 'sorter':True},
+ {'name': 'Col 2',
+ 'value': 'col2'}
+ ]
+ result = inst._column_headers(columns)
self.assertEqual(len(result), 2)
col = result[0]
- self.assertEqual(col['field'], 'col1')
- self.assertEqual(col['id'], 'col1')
+ self.assertEqual(col['id'], 'Col 1')
+ self.assertEqual(col['field'], 'Col 1')
self.assertEqual(col['name'], 'Col 1')
self.assertEqual(col['width'], 120)
self.assertEqual(col['formatterName'], '')
- self.assertEqual(col['cssClass'], 'cell-col1')
-
- self.assertEqual(col['sortable'], False)
+ self.assertEqual(col['cssClass'], 'cell-Col-1')
+ self.assertEqual(col['sortable'], True)
col = result[1]
- self.assertEqual(col['field'], 'col2')
- self.assertEqual(col['id'], 'col2')
+ self.assertEqual(col['id'], 'Col 2')
+ self.assertEqual(col['field'], 'Col 2')
self.assertEqual(col['name'], 'Col 2')
self.assertEqual(col['width'], 120)
self.assertEqual(col['formatterName'], '')
- self.assertEqual(col['cssClass'], 'cell-col2')
-
- self.assertEqual(col['sortable'], True)
-
+ self.assertEqual(col['cssClass'], 'cell-Col-2')
+ self.assertEqual(col['sortable'], False)
def test__column_headers_sortable_false_for_ordered_folder(self):
- def sd_columns(folder, subobject, request, default_columns):
- self.assertEqual(len(default_columns), 1)
- return [
- {'name': 'Col 1', 'field': 'col1', 'value':
- 'col1', 'sortable': True},
- {'name': 'Col 2', 'field': 'col2', 'value':
- 'col2', 'sortable': True}
- ]
context = testing.DummyResource(is_ordered=lambda: True)
- request = self._makeRequest(columns=sd_columns)
+ request = self._makeRequest()
inst = self._makeOne(context, request)
- result = inst._column_headers()
+ columns = [
+ {'name': 'Col 1',
+ 'value': 'col1',
+ 'sorter': True},
+ {'name': 'Col 2',
+ 'value': 'col2',
+ 'sorter': True}
+ ]
+ result = inst._column_headers(columns)
self.assertEqual(len(result), 2)
col = result[0]
- self.assertEqual(col['field'], 'col1')
+ self.assertEqual(col['field'], 'Col 1')
self.assertEqual(col['sortable'], False)
col = result[1]
- self.assertEqual(col['field'], 'col2')
+ self.assertEqual(col['field'], 'Col 2')
self.assertEqual(col['sortable'], False)
def test__column_headers_no_custom(self):
context = testing.DummyResource(is_ordered=lambda: False)
request = self._makeRequest()
inst = self._makeOne(context, request)
- result = inst._column_headers()
+ default_columns = [
+ {'name': 'Name',
+ 'value': 'myname',
+ 'formatter': 'icon_label_url',
+ 'sorter': True}
+ ]
+ result = inst._column_headers(default_columns)
self.assertEqual(len(result), 1)
self.assertEqual(
result[0],
{'minWidth': 120,
- 'field': 'name',
+ 'field': 'Name',
'sortable': True,
'name': 'Name',
'width': 120,
'formatterName': 'icon_label_url',
- 'cssClass': 'cell-name',
- 'id': 'name'}
+ 'cssClass': 'cell-Name',
+ 'id': 'Name'}
)
def test__column_headers_None(self):
context = testing.DummyResource()
- request = self._makeRequest(columns=None)
+ context.is_ordered = lambda: False
+ request = self._makeRequest()
inst = self._makeOne(context, request)
- result = inst._column_headers()
+ result = inst._column_headers([])
self.assertEqual(len(result), 0)
def test__column_headers_cssClass(self):
- def sd_columns(folder, subobject, request, default_columns):
- self.assertEqual(len(default_columns), 1)
- return [
- {'name': 'Col 1', 'field': 'col1', 'value':
- 'col1', 'sortable': True, 'cssClass': 'customClass'},
- {'name': 'Col 2', 'field': 'col2', 'value': 'col2',
- 'sortable': True},
- {'name': 'Col 3', 'field': 'col3', 'value': 'col3',
- 'sortable': True, 'cssClass': 'customClass1 customClass2'},
- ]
context = testing.DummyResource(is_ordered=lambda: False)
- request = self._makeRequest(columns=sd_columns)
+ request = self._makeRequest()
+
+ inst = self._makeOne(context, request)
+ columns = [
+ {'name': 'Col 1',
+ 'value': 'col1',
+ 'sorter': True,
+ 'css_class': 'customClass'},
+ {'name': 'Col 2',
+ 'value': 'col2',
+ 'sorter': True},
+ {'name': 'Col 3',
+ 'value': 'col3',
+ 'sorter': True,
+ 'css_class': 'customClass1 customClass2'},
+ ]
+ result = inst._column_headers(columns)
+ self.assertEqual(len(result), 3)
+ self.assertEqual(result[0]['cssClass'], 'cell-Col-1 customClass')
+ self.assertEqual(result[1]['cssClass'], 'cell-Col-2')
+ self.assertEqual(
+ result[2]['cssClass'], 'cell-Col-3 customClass1 customClass2')
+
+ def test__sort_info_context_is_ordered(self):
+ context = testing.DummyResource(
+ is_ordered=lambda: True,
+ sort=lambda resultset, **kw: resultset
+ )
+ request = self._makeRequest()
+ inst = self._makeOne(context, request)
+ result = inst._sort_info([])
+ self.assertEqual(result['column'], None)
+ sorter = result['sorter']
+ resultset = DummyResultSet([1])
+ self.assertEqual(
+ sorter(context, resultset, reverse=True, limit=1),
+ resultset
+ )
+ self.assertEqual(result['column_name'], None)
+
+ def test__sort_info_context_unordered_default_sort_column(self):
+ context = testing.DummyResource(
+ is_ordered=lambda: False,
+ )
+ request = self._makeRequest()
+ inst = self._makeOne(context, request)
+ columns = [{'name':'col1'}, {'name':'col2', 'sorter':True}]
+ result = inst._sort_info(columns)
+ self.assertEqual(result['column'], columns[1])
+ self.assertEqual(result['sorter'], True)
+ self.assertEqual(result['column_name'], 'col2')
+
+ def test__sort_info_context_unordered_nondefault_sort_column_exists(self):
+ context = testing.DummyResource(
+ is_ordered=lambda: False,
+ )
+ request = self._makeRequest()
+ inst = self._makeOne(context, request)
+ columns = [{'name':'col1', 'sorter':'a'}, {'name':'col2', 'sorter':'b'}]
+ result = inst._sort_info(columns, sort_column_name='col2')
+ self.assertEqual(result['column'], columns[1])
+ self.assertEqual(result['sorter'], 'b')
+ self.assertEqual(result['column_name'], 'col2')
+
+ def test__sort_info_context_unordered_nondefault_sort_column_notexist(self):
+ context = testing.DummyResource(
+ is_ordered=lambda: False,
+ )
+ request = self._makeRequest()
+ inst = self._makeOne(context, request)
+ columns = [{'name':'col1', 'sorter':'a'}, {'name':'col2', 'sorter':'b'}]
+ result = inst._sort_info(columns, sort_column_name='col3')
+ self.assertEqual(result['column'], None)
+ self.assertEqual(result['sorter'], None)
+ self.assertEqual(result['column_name'], 'col3')
+
+ def test__folder_contents_computable_icon(self):
+ from substanced.interfaces import IFolder
+ context = DummyFolder(__provides__=IFolder)
+ request = self._makeRequest()
+ context['catalogs'] = self._makeCatalogs(oids=[1])
+ result = testing.DummyResource()
+ result.__name__ = 'fred'
+ context.__objectmap__ = DummyObjectMap(result)
+ inst = self._makeOne(context, request)
+ def icon(subobject, _request):
+ self.assertEqual(subobject, result)
+ self.assertEqual(_request, request)
+ return 'anicon'
+ request.registry.content = DummyContent(icon=icon)
+ info = inst._folder_contents()
+ length, records = info['length'], info['records']
+ self.assertEqual(length, 1)
+ self.assertEqual(len(records), 1)
+ item = records[0]
+ self.assertTrue(item['deletable'])
+ self.assertEqual(item['name'], 'fred')
+ self.assertEqual(item['id'], 'fred')
+ self.assertEqual(item['Name_url'], '/mgmt_path')
+ self.assertEqual(item['Name_icon'], 'anicon')
+
+ def test__folder_contents_literal_icon(self):
+ from substanced.interfaces import IFolder
+ context = DummyFolder(__provides__=IFolder)
+ request = self._makeRequest()
+ context['catalogs'] = self._makeCatalogs(oids=[1])
+ result = testing.DummyResource()
+ result.__name__ = 'fred'
+ context.__objectmap__ = DummyObjectMap(result)
+ inst = self._makeOne(context, request)
+ request.registry.content = DummyContent(icon='anicon')
+ info = inst._folder_contents()
+ length, records = info['length'], info['records']
+ self.assertEqual(length, 1)
+ self.assertEqual(len(records), 1)
+ item = records[0]
+ self.assertTrue(item['deletable'])
+ self.assertEqual(item['name'], 'fred')
+ self.assertEqual(item['id'], 'fred')
+ self.assertEqual(item['Name_url'], '/mgmt_path')
+ self.assertEqual(item['Name_icon'], 'anicon')
+
+ def test__folder_contents_deletable_callable(self):
+ from substanced.interfaces import IFolder
+ context = DummyFolder(__provides__=IFolder)
+ request = self._makeRequest()
+ context['catalogs'] = self._makeCatalogs(oids=[1])
+ result = testing.DummyResource()
+ result.__name__ = 'fred'
+ context.__objectmap__ = DummyObjectMap(result)
+ inst = self._makeOne(context, request)
+ def deletable(subobject, _request):
+ self.assertEqual(subobject, result)
+ self.assertEqual(_request, request)
+ return False
+ result.__sdi_deletable__ = deletable
+ request.registry.content = DummyContent()
+ info = inst._folder_contents()
+ length, records = info['length'], info['records']
+ self.assertEqual(length, 1)
+ self.assertEqual(len(records), 1)
+ item = records[0]
+ self.assertFalse(item['deletable'])
+ self.assertEqual(item['name'], 'fred')
+ self.assertEqual(item['id'], 'fred')
+ self.assertEqual(item['Name_url'], '/mgmt_path')
+ self.assertEqual(item['Name_icon'], None)
+
+ def test__folder_contents_deletable_boolean(self):
+ from substanced.interfaces import IFolder
+ context = DummyFolder(__provides__=IFolder)
+ request = self._makeRequest()
+ context['catalogs'] = self._makeCatalogs(oids=[1])
+ result = testing.DummyResource()
+ result.__name__ = 'fred'
+ context.__objectmap__ = DummyObjectMap(result)
+ inst = self._makeOne(context, request)
+ result.__sdi_deletable__ = False
+ request.registry.content = DummyContent()
+ info = inst._folder_contents()
+ length, records = info['length'], info['records']
+ self.assertEqual(length, 1)
+ self.assertEqual(len(records), 1)
+ item = records[0]
+ self.assertFalse(item['deletable'])
+ self.assertEqual(item['name'], 'fred')
+ self.assertEqual(item['id'], 'fred')
+ self.assertEqual(item['Name_url'], '/mgmt_path')
+ self.assertEqual(item['Name_icon'], None)
+
+ def test__folder_contents_columns_callable(self):
+ from substanced.interfaces import IFolder
+ context = DummyFolder(__provides__=IFolder)
+ request = self._makeRequest()
+ context['catalogs'] = self._makeCatalogs(oids=[1])
+ result = testing.DummyResource()
+ result.col1 = 'val1'
+ result.col2 = 'val2'
+ result.__name__ = 'fred'
+ context.__objectmap__ = DummyObjectMap(result)
+ def get_columns(folder, subobject, request, default_columns):
+ self.assertEqual(len(default_columns), 1)
+ return [{'name': 'Col 1',
+ 'value': getattr(subobject, 'col1', None)},
+ {'name': 'Col 2',
+ 'value': getattr(subobject, 'col2', None)}]
+ inst = self._makeOne(context, request)
+ request.registry.content = DummyContent(columns=get_columns)
+ info = inst._folder_contents()
+ length, records = info['length'], info['records']
+ self.assertEqual(length, 1)
+ self.assertEqual(len(records), 1)
+ item = records[0]
+ self.assertEqual(item['Col 1'], 'val1')
+ self.assertEqual(item['Col 2'], 'val2')
+
+ def test__folder_contents_columns_None(self):
+ from substanced.interfaces import IFolder
+ context = DummyFolder(__provides__=IFolder)
+ request = self._makeRequest()
+ context['catalogs'] = self._makeCatalogs(oids=[1])
+ result = testing.DummyResource()
+ result.col1 = 'val1'
+ result.col2 = 'val2'
+ result.__name__ = 'fred'
+ context.__objectmap__ = DummyObjectMap(result)
+ inst = self._makeOne(context, request)
+ request.registry.content = DummyContent(columns=None)
+ info = inst._folder_contents()
+ length, records = info['length'], info['records']
+ self.assertEqual(length, 1)
+ self.assertEqual(len(records), 1)
+ item = records[0]
+ self.assertEqual(
+ item,
+ {'deletable': True,
+ 'name': 'fred',
+ 'disable': [],
+ 'id': 'fred'}
+ )
+
+ def test__folder_contents_with_filter_text(self):
+ from substanced.interfaces import IFolder
+ context = DummyFolder(__provides__=IFolder)
+ request = self._makeRequest()
+ context['catalogs'] = self._makeCatalogs(oids=[1])
+ result = testing.DummyResource()
+ result.__name__ = 'fred'
+ context.__objectmap__ = DummyObjectMap(result)
+ inst = self._makeOne(context, request)
+ request.registry.content = DummyContent()
+ info = inst._folder_contents(filter_text='abc')
+ length, records = info['length'], info['records']
+ self.assertEqual(length, 1)
+ self.assertEqual(len(records), 1)
+
+ def test__folder_contents_with_filter_text_multiple_words(self):
+ from substanced.interfaces import IFolder
+ context = DummyFolder(__provides__=IFolder)
+ request = self._makeRequest()
+ context['catalogs'] = self._makeCatalogs(oids=[1])
+ result = testing.DummyResource()
+ result.__name__ = 'fred'
+ context.__objectmap__ = DummyObjectMap(result)
+ inst = self._makeOne(context, request)
+ request.registry.content = DummyContent()
+ info = inst._folder_contents(filter_text='abc def')
+ length, records = info['length'], info['records']
+ self.assertEqual(length, 1)
+ self.assertEqual(len(records), 1)
+
+ def test__folder_contents_folder_is_ordered(self):
+ from substanced.interfaces import IFolder
+ context = DummyFolder(__provides__=IFolder)
+ context.sort = lambda resultset, **kw: resultset
+ request = self._makeRequest()
+ context['catalogs'] = self._makeCatalogs(oids=[1])
+ result = testing.DummyResource()
+ result.__name__ = 'fred'
+ context.__objectmap__ = DummyObjectMap(result)
+ inst = self._makeOne(context, request)
+ context.is_ordered = lambda *arg: True
+ context.oids = lambda *arg: [1, 2]
+ request.registry.content = DummyContent()
+ info = inst._folder_contents()
+ length, records = info['length'], info['records']
+ self.assertEqual(length, 1)
+ self.assertEqual(len(records), 1)
- inst = self._makeOne(context, request)
- result = inst._column_headers()
- self.assertEqual(len(result), 3)
- self.assertEqual(result[0]['cssClass'], 'cell-col1 customClass')
- self.assertEqual(result[1]['cssClass'], 'cell-col2')
- self.assertEqual(result[2]['cssClass'], 'cell-col3 customClass1 customClass2')
-
- def test_show_non_filterable_columns(self):
- dummy_column_headers = [{
- 'field': 'col1',
- 'sortable': True,
- }, {
- 'field': 'col2',
- 'sortable': True,
- }]
+ def test_show_no_columns(self):
+ folder_contents = {
+ 'length':1,
+ 'sort_column_name':None,
+ 'columns':[],
+ 'records': [{
+ 'name': 'the_name',
+ 'deletable': True,
+ 'name_url': 'http://foo.bar',
+ 'id': 'the_name',
+ 'name_icon': 'the_icon',
+ }]
+ }
context = testing.DummyResource()
- request = self._makeRequest()
+ request = self._makeRequest(columns=None)
inst = self._makeOne(context, request)
inst._folder_contents = mock.Mock(
- return_value=dummy_folder_contents_2
- )
- inst._column_headers = mock.Mock(return_value=dummy_column_headers)
+ return_value=folder_contents
+ )
inst.sdi_add_views = mock.Mock(return_value=('b',))
context.is_reorderable = mock.Mock(return_value=False)
context.is_ordered = mock.Mock(return_value=False)
- with mock.patch('substanced.sdi.views.folder.find_catalog') as find_catalog:
- find_catalog.return_value = {'col1': 'COL1', 'col2': 'COL2'}
- result = inst.show()
+ result = inst.show()
self.assert_('slickgrid_wrapper_options' in result)
slickgrid_wrapper_options = result['slickgrid_wrapper_options']
self.assert_('slickgridOptions' in slickgrid_wrapper_options)
- self.assertEqual(slickgrid_wrapper_options['configName'],
- 'sdi-content-grid')
+ self.assertEqual(
+ slickgrid_wrapper_options['configName'],
+ 'sdi-content-grid'
+ )
+ # None because it cannot be sorted.
+ self.assertEqual(slickgrid_wrapper_options['isReorderable'], False)
+ self.assertEqual(slickgrid_wrapper_options['sortCol'], None)
self.assertEqual(slickgrid_wrapper_options['sortDir'], True)
self.assertEqual(slickgrid_wrapper_options['url'], '')
self.assert_('items' in slickgrid_wrapper_options)
@@ -452,8 +609,6 @@ def test_show_non_filterable_columns(self):
self.assertEqual(len(records), 1)
self.assertEqual(records[0], {
'name': 'the_name',
- 'col1': 'value4col1',
- 'col2': 'value4col2',
'deletable': True,
'name_url': 'http://foo.bar',
'id': 'the_name',
@@ -464,33 +619,41 @@ def test_show_non_filterable_columns(self):
buttons = result['buttons']
self.assertEqual(len(buttons), 2)
- # We don't actually see the sortability in the records, because
- # the client need not know about it. It's in
- # the server's discretion to apply the fulltext filtering in
- # the designated fields.
- # XXX Perhaps one egde case requires some extra attention:
- # when a grid has no filterable columns at all.
-
-
- def test_show_ordered_columns(self):
- dummy_column_headers = [{
- 'field': 'col1',
- 'sortable': True, # Even if True, is_ordered it will make it False
- }, {
- 'field': 'col2',
- 'sortable': False,
- }]
+ def test_show_with_columns(self):
+ columns = [
+ {
+ 'name': 'col1',
+ 'sorter':True,
+ },
+ {
+ 'name': 'col2',
+ }
+ ]
+ folder_contents = {
+ 'length':1,
+ 'sort_column_name':'col1',
+ 'columns': columns,
+ 'records':[{
+ 'name': 'the_name',
+ 'col1': 'value4col1',
+ 'col2': 'value4col2',
+ 'deletable': True,
+ 'name_url': 'http://foo.bar',
+ 'id': 'the_name',
+ 'name_icon': 'the_icon',
+ }]
+ }
context = testing.DummyResource()
request = self._makeRequest()
inst = self._makeOne(context, request)
inst._folder_contents = mock.Mock(
- return_value=dummy_folder_contents_2
+ return_value=folder_contents
)
- inst._column_headers = mock.Mock(return_value=dummy_column_headers)
inst.sdi_add_views = mock.Mock(return_value=('b',))
- context.is_reorderable = mock.Mock(return_value=True)
- context.is_ordered = mock.Mock(return_value=True)
- with mock.patch('substanced.sdi.views.folder.find_catalog') as find_catalog:
+ context.is_reorderable = mock.Mock(return_value=False)
+ context.is_ordered = mock.Mock(return_value=False)
+ with mock.patch(
+ 'substanced.sdi.views.folder.find_catalog') as find_catalog:
find_catalog.return_value = {'col1': 'COL1', 'col2': 'COL2'}
result = inst.show()
self.assert_('slickgrid_wrapper_options' in result)
@@ -499,8 +662,8 @@ def test_show_ordered_columns(self):
self.assertEqual(
slickgrid_wrapper_options['configName'], 'sdi-content-grid'
)
- self.assertEqual(slickgrid_wrapper_options['isReorderable'], True)
- self.assertEqual(slickgrid_wrapper_options['sortCol'], None)
+ self.assertEqual(slickgrid_wrapper_options['isReorderable'], False)
+ self.assertEqual(slickgrid_wrapper_options['sortCol'], 'col1')
self.assertEqual(slickgrid_wrapper_options['sortDir'], True)
self.assertEqual(slickgrid_wrapper_options['url'], '')
self.assert_('items' in slickgrid_wrapper_options)
@@ -525,6 +688,42 @@ def test_show_ordered_columns(self):
buttons = result['buttons']
self.assertEqual(len(buttons), 2)
+ def test_show_json(self):
+ folder_contents = {
+ 'length':1,
+ 'sort_column_name':None,
+ 'records': [{
+ 'name': 'the_name',
+ 'deletable': True,
+ 'name_url': 'http://foo.bar',
+ 'id': 'the_name',
+ 'name_icon': 'the_icon',
+ }]
+ }
+
+ context = testing.DummyResource()
+ request = self._makeRequest()
+ request.params['from'] = '1'
+ request.params['to'] = '2'
+ inst = self._makeOne(context, request)
+ inst._folder_contents = mock.Mock(
+ return_value=folder_contents
+ )
+ result = inst.show_json()
+ self.assertEqual(
+ result,
+ {'from':1, 'to':2, 'records':folder_contents['records'], 'total':1}
+ )
+
+ def test_show_json_no_from(self):
+ context = testing.DummyResource()
+ request = self._makeRequest()
+ inst = self._makeOne(context, request)
+ result = inst.show_json()
+ self.assertEqual(
+ result,
+ {}
+ )
def test_delete_none_deleted(self):
context = testing.DummyResource()
@@ -998,257 +1197,6 @@ def test_move_finish_already_exists(self, mock_find_objectmap):
self.assertRaises(HTTPFound, inst.move_finish)
request.session.flash.assert_called_once_with(u'foobar', 'error')
- def test_buttons_is_None(self):
- context = testing.DummyResource()
- request = testing.DummyRequest()
- inst = self._makeOne(context, request)
- request.registry.content = DummyContent(buttons=None)
- result = inst._buttons()
- self.assertEqual(result, [])
-
- def test_buttons_is_clbl(self):
- context = testing.DummyResource()
- request = testing.DummyRequest()
- def sdi_buttons(contexr, request, default_buttons):
- return 'abc'
- inst = self._makeOne(context, request)
- request.registry.content = DummyContent(buttons=sdi_buttons)
- result = inst._buttons()
- self.assertEqual(result, 'abc')
-
- def test_buttons_enabled_for_true(self):
- from substanced.interfaces import IFolder
- context = DummyFolder(__provides__=IFolder)
- request = self._makeRequest()
- context['catalogs'] = self._makeCatalogs(oids=[1])
- result = testing.DummyResource()
- result.__name__ = 'fred'
- context.__objectmap__ = DummyObjectMap(result)
- inst = self._makeOne(context, request)
- def sdi_buttons(contexr, request, default_buttons):
- return [{'type': 'single',
- 'buttons': [{'enabled_for': lambda x,y,z: True,
- 'id': 'Button'}]}]
- request.registry.content = DummyContent(buttons=sdi_buttons)
- length, rows = inst._folder_contents()
- self.assertEqual(length, 1)
- self.assertEqual(rows[0]['disable'], [])
-
- def test_buttons_enabled_for_false(self):
- from substanced.interfaces import IFolder
- context = DummyFolder(__provides__=IFolder)
- request = self._makeRequest()
- context['catalogs'] = self._makeCatalogs(oids=[1])
- result = testing.DummyResource()
- result.__name__ = 'fred'
- context.__objectmap__ = DummyObjectMap(result)
- inst = self._makeOne(context, request)
- def sdi_buttons(contexr, request, default_buttons):
- return [{'type': 'single',
- 'buttons': [{'enabled_for': lambda x,y,z: False,
- 'id': 'Button'}]}]
- request.registry.content = DummyContent(buttons=sdi_buttons)
- length, rows = inst._folder_contents()
- self.assertEqual(length, 1)
- self.assertEqual(rows[0]['disable'], ['Button'])
-
- def test_buttons_enabled_for_non_callable(self):
- from substanced.interfaces import IFolder
- context = DummyFolder(__provides__=IFolder)
- request = self._makeRequest()
- context['catalogs'] = self._makeCatalogs(oids=[1])
- result = testing.DummyResource()
- result.__name__ = 'fred'
- context.__objectmap__ = DummyObjectMap(result)
- inst = self._makeOne(context, request)
- def sdi_buttons(contexr, request, default_buttons):
- return [{'type': 'single',
- 'buttons': [{'enabled_for': 'not callable',
- 'id': 'Button'}]}]
- request.registry.content = DummyContent(buttons=sdi_buttons)
- length, rows = inst._folder_contents()
- self.assertEqual(length, 1)
- self.assertEqual(rows[0]['disable'], [])
-
- def _makeCatalogs(self, oids=()):
- catalogs = DummyCatalogs()
- catalog = DummyCatalog(oids)
- catalogs['system'] = catalog
- return catalogs
-
- def test__folder_contents_computable_icon(self):
- from substanced.interfaces import IFolder
- context = DummyFolder(__provides__=IFolder)
- request = self._makeRequest()
- context['catalogs'] = self._makeCatalogs(oids=[1])
- result = testing.DummyResource()
- result.__name__ = 'fred'
- context.__objectmap__ = DummyObjectMap(result)
- inst = self._makeOne(context, request)
- def icon(subobject, _request):
- self.assertEqual(subobject, result)
- self.assertEqual(_request, request)
- return 'anicon'
- request.registry.content = DummyContent(icon=icon)
- length, results = inst._folder_contents()
- self.assertEqual(length, 1)
- self.assertEqual(len(results), 1)
- item = results[0]
- self.assertEqual(item['name_url'], '/mgmt_path')
- self.assertTrue(item['deletable'])
- self.assertEqual(item['name_icon'], 'anicon')
- self.assertEqual(item['name'], 'fred')
- self.assertEqual(item['id'], 'fred')
-
- def test__folder_contents_literal_icon(self):
- from substanced.interfaces import IFolder
- context = DummyFolder(__provides__=IFolder)
- request = self._makeRequest()
- context['catalogs'] = self._makeCatalogs(oids=[1])
- result = testing.DummyResource()
- result.__name__ = 'fred'
- context.__objectmap__ = DummyObjectMap(result)
- inst = self._makeOne(context, request)
- request.registry.content = DummyContent(icon='anicon')
- length, results = inst._folder_contents()
- self.assertEqual(length, 1)
- self.assertEqual(len(results), 1)
- item = results[0]
- self.assertEqual(item['name_url'], '/mgmt_path')
- self.assertTrue(item['deletable'])
- self.assertEqual(item['name_icon'], 'anicon')
- self.assertEqual(item['name'], 'fred')
- self.assertEqual(item['id'], 'fred')
-
- def test__folder_contents_deletable_callable(self):
- from substanced.interfaces import IFolder
- context = DummyFolder(__provides__=IFolder)
- request = self._makeRequest()
- context['catalogs'] = self._makeCatalogs(oids=[1])
- result = testing.DummyResource()
- result.__name__ = 'fred'
- context.__objectmap__ = DummyObjectMap(result)
- inst = self._makeOne(context, request)
- def deletable(subobject, _request):
- self.assertEqual(subobject, result)
- self.assertEqual(_request, request)
- return False
- result.__sdi_deletable__ = deletable
- request.registry.content = DummyContent()
- length, results = inst._folder_contents()
- self.assertEqual(length, 1)
- self.assertEqual(len(results), 1)
- item = results[0]
- self.assertEqual(item['name_url'], '/mgmt_path')
- self.assertFalse(item['deletable'])
- self.assertEqual(item['name_icon'], None)
- self.assertEqual(item['name'], 'fred')
- self.assertEqual(item['id'], 'fred')
-
- def test__folder_contents_deletable_boolean(self):
- from substanced.interfaces import IFolder
- context = DummyFolder(__provides__=IFolder)
- request = self._makeRequest()
- context['catalogs'] = self._makeCatalogs(oids=[1])
- result = testing.DummyResource()
- result.__name__ = 'fred'
- context.__objectmap__ = DummyObjectMap(result)
- inst = self._makeOne(context, request)
- result.__sdi_deletable__ = False
- request.registry.content = DummyContent()
- length, results = inst._folder_contents()
- self.assertEqual(length, 1)
- self.assertEqual(len(results), 1)
- item = results[0]
- self.assertEqual(item['name_url'], '/mgmt_path')
- self.assertFalse(item['deletable'])
- self.assertEqual(item['name_icon'], None)
- self.assertEqual(item['name'], 'fred')
- self.assertEqual(item['id'], 'fred')
-
- def test__folder_contents_columns_callable(self):
- from substanced.interfaces import IFolder
- context = DummyFolder(__provides__=IFolder)
- request = self._makeRequest()
- context['catalogs'] = self._makeCatalogs(oids=[1])
- result = testing.DummyResource()
- result.col1 = 'val1'
- result.col2 = 'val2'
- result.__name__ = 'fred'
- context.__objectmap__ = DummyObjectMap(result)
- def get_columns(folder, subobject, request, default_columns):
- self.assertEqual(len(default_columns), 1)
- return [{'name': 'Col 1',
- 'value': getattr(subobject, 'col1'),
- 'field':'col1'},
- {'name': 'Col 2',
- 'value': getattr(subobject, 'col2'),
- 'field':'col2'}]
- inst = self._makeOne(context, request)
- request.registry.content = DummyContent(columns=get_columns)
- length, results = inst._folder_contents()
- self.assertEqual(length, 1)
- self.assertEqual(len(results), 1)
- item = results[0]
- self.assertEqual(item['col1'], 'val1')
- self.assertEqual(item['col2'], 'val2')
-
- def test__folder_contents_columns_None(self):
- from substanced.interfaces import IFolder
- context = DummyFolder(__provides__=IFolder)
- request = self._makeRequest()
- context['catalogs'] = self._makeCatalogs(oids=[1])
- result = testing.DummyResource()
- result.col1 = 'val1'
- result.col2 = 'val2'
- result.__name__ = 'fred'
- context.__objectmap__ = DummyObjectMap(result)
- inst = self._makeOne(context, request)
- request.registry.content = DummyContent(columns=None)
- length, results = inst._folder_contents()
- self.assertEqual(length, 1)
- self.assertEqual(len(results), 1)
- item = results[0]
- self.assertEqual(
- item,
- {'name_icon': None,
- 'name_url': '/mgmt_path',
- 'deletable': True,
- 'name': 'fred',
- 'disable': [],
- 'id': 'fred'}
- )
-
- def test__folder_contents_with_filter_text(self):
- from substanced.interfaces import IFolder
- context = DummyFolder(__provides__=IFolder)
- request = self._makeRequest()
- context['catalogs'] = self._makeCatalogs(oids=[1])
- result = testing.DummyResource()
- result.__name__ = 'fred'
- context.__objectmap__ = DummyObjectMap(result)
- inst = self._makeOne(context, request)
- request.registry.content = DummyContent()
- length, results = inst._folder_contents(filter_text='abc')
- self.assertEqual(length, 1)
- self.assertEqual(len(results), 1)
-
- def test__folder_contents_folder_is_ordered(self):
- from substanced.interfaces import IFolder
- context = DummyFolder(__provides__=IFolder)
- request = self._makeRequest()
- context['catalogs'] = self._makeCatalogs(oids=[1])
- result = testing.DummyResource()
- result.__name__ = 'fred'
- context.__objectmap__ = DummyObjectMap(result)
- inst = self._makeOne(context, request)
- context.is_ordered = lambda *arg: True
- context.oids = lambda *arg: [1, 2]
- request.registry.content = DummyContent()
- length, results = inst._folder_contents()
- self.assertEqual(length, 1)
- self.assertEqual(len(results), 1)
-
def test_reorder_rows(self):
context = testing.DummyResource()
request = self._makeRequest()
@@ -1262,12 +1210,16 @@ def reorder(item_modify, insert_before):
def _get_json():
return {'foo':'bar'}
inst._get_json = _get_json
- mockundowrapper = request.sdiapi.get_flash_with_undo_snippet = mock.Mock(
+ mockundowrapper = mock.Mock(
return_value='STATUSMESSG<a>Undo</a>'
)
+ request.sdiapi.get_flash_with_undo_snippet = mockundowrapper