# -*- coding: utf-8; -*- from unittest import TestCase from unittest.mock import patch, MagicMock from sqlalchemy import orm from paginate import Page from paginate_sqlalchemy import SqlalchemyOrmPage from pyramid import testing from wuttjamaican.conf import WuttaConfig from wuttaweb.grids import base as mod from wuttaweb.util import FieldList from wuttaweb.forms import Form from tests.util import WebTestCase class TestGrid(WebTestCase): def make_grid(self, request=None, **kwargs): return mod.Grid(request or self.request, **kwargs) def test_constructor(self): # empty grid = self.make_grid() self.assertIsNone(grid.key) self.assertEqual(grid.columns, []) self.assertIsNone(grid.data) # now with columns grid = self.make_grid(columns=['foo', 'bar']) self.assertIsInstance(grid.columns, FieldList) self.assertEqual(grid.columns, ['foo', 'bar']) def test_constructor_sorting(self): model = self.app.model # defaults, not sortable grid = self.make_grid() self.assertFalse(grid.sortable) self.assertTrue(grid.sort_on_backend) self.assertEqual(grid.sorters, {}) self.assertEqual(grid.sort_defaults, []) # defaults, sortable grid = self.make_grid(sortable=True) self.assertTrue(grid.sortable) self.assertTrue(grid.sort_on_backend) self.assertEqual(grid.sorters, {}) self.assertEqual(grid.sort_defaults, []) # sorters may be pre-populated grid = self.make_grid(model_class=model.Setting, sortable=True) self.assertEqual(len(grid.sorters), 2) self.assertIn('name', grid.sorters) self.assertIn('value', grid.sorters) self.assertEqual(grid.sort_defaults, []) # sort defaults as str grid = self.make_grid(model_class=model.Setting, sortable=True, sort_defaults='name') self.assertEqual(grid.sort_defaults, [mod.SortInfo('name', 'asc')]) # sort defaults as tuple grid = self.make_grid(model_class=model.Setting, sortable=True, sort_defaults=('name', 'desc')) self.assertEqual(grid.sort_defaults, [mod.SortInfo('name', 'desc')]) # sort defaults as list w/ single tuple grid = self.make_grid(model_class=model.Setting, sortable=True, sort_defaults=[('name', 'desc')]) self.assertEqual(grid.sort_defaults, [mod.SortInfo('name', 'desc')]) # multi-column defaults grid = self.make_grid(model_class=model.Setting, sortable=True, sort_multiple=True, sort_defaults=[('name', 'desc'), ('value', 'asc')]) self.assertTrue(grid.sort_multiple) self.assertEqual(grid.sort_defaults, [mod.SortInfo('name', 'desc'), mod.SortInfo('value', 'asc')]) # multi-column sort disabled for oruga self.request.use_oruga = True grid = self.make_grid(model_class=model.Setting, sortable=True, sort_multiple=True) self.assertFalse(grid.sort_multiple) def test_constructor_filtering(self): model = self.app.model # defaults, not filterable grid = self.make_grid() self.assertFalse(grid.filterable) self.assertEqual(grid.filters, {}) # defaults, filterable grid = self.make_grid(filterable=True) self.assertTrue(grid.filterable) self.assertEqual(grid.filters, {}) # filters may be pre-populated with patch.object(mod.Grid, 'make_filter', return_value=42): grid = self.make_grid(model_class=model.Setting, filterable=True) self.assertEqual(len(grid.filters), 2) self.assertIn('name', grid.filters) self.assertIn('value', grid.filters) # can specify filters grid = self.make_grid(model_class=model.Setting, filterable=True, filters={'name': 42}) self.assertTrue(grid.filterable) self.assertEqual(grid.filters, {'name': 42}) def test_vue_tagname(self): grid = self.make_grid() self.assertEqual(grid.vue_tagname, 'wutta-grid') def test_vue_component(self): grid = self.make_grid() self.assertEqual(grid.vue_component, 'WuttaGrid') def test_get_columns(self): model = self.app.model # empty grid = self.make_grid() self.assertEqual(grid.columns, []) self.assertEqual(grid.get_columns(), []) # explicit grid = self.make_grid(columns=['foo', 'bar']) self.assertEqual(grid.columns, ['foo', 'bar']) self.assertEqual(grid.get_columns(), ['foo', 'bar']) # derived from model grid = self.make_grid(model_class=model.Setting) self.assertEqual(grid.columns, ['name', 'value']) self.assertEqual(grid.get_columns(), ['name', 'value']) def test_append(self): grid = self.make_grid(columns=['one', 'two']) self.assertEqual(grid.columns, ['one', 'two']) grid.append('one', 'two', 'three') self.assertEqual(grid.columns, ['one', 'two', 'three']) def test_remove(self): grid = self.make_grid(columns=['one', 'two', 'three', 'four']) self.assertEqual(grid.columns, ['one', 'two', 'three', 'four']) grid.remove('two', 'three') self.assertEqual(grid.columns, ['one', 'four']) def test_set_label(self): model = self.app.model with patch.object(mod.Grid, 'make_filter'): # nb. filters are MagicMock instances grid = self.make_grid(model_class=model.Setting, filterable=True) self.assertEqual(grid.labels, {}) # basic grid.set_label('name', "NAME COL") self.assertEqual(grid.labels['name'], "NAME COL") # can replace label grid.set_label('name', "Different") self.assertEqual(grid.labels['name'], "Different") self.assertEqual(grid.get_label('name'), "Different") # can update only column, not filter self.assertEqual(grid.labels, {'name': "Different"}) self.assertIn('name', grid.filters) self.assertEqual(grid.filters['name'].label, "Different") grid.set_label('name', "COLUMN ONLY", column_only=True) self.assertEqual(grid.get_label('name'), "COLUMN ONLY") self.assertEqual(grid.filters['name'].label, "Different") def test_get_label(self): grid = self.make_grid(columns=['foo', 'bar']) self.assertEqual(grid.labels, {}) # default derived from key self.assertEqual(grid.get_label('foo'), "Foo") # can override grid.set_label('foo', "Different") self.assertEqual(grid.get_label('foo'), "Different") def test_set_renderer(self): grid = self.make_grid(columns=['foo', 'bar']) self.assertEqual(grid.renderers, {}) def render1(record, key, value): pass # basic grid.set_renderer('foo', render1) self.assertIs(grid.renderers['foo'], render1) def render2(record, key, value, extra=None): return extra # can pass kwargs to get a partial grid.set_renderer('foo', render2, extra=42) self.assertIsNot(grid.renderers['foo'], render2) self.assertEqual(grid.renderers['foo'](None, None, None), 42) def test_linked_columns(self): grid = self.make_grid(columns=['foo', 'bar']) self.assertEqual(grid.linked_columns, []) self.assertFalse(grid.is_linked('foo')) grid.set_link('foo') self.assertEqual(grid.linked_columns, ['foo']) self.assertTrue(grid.is_linked('foo')) self.assertFalse(grid.is_linked('bar')) grid.set_link('bar') self.assertEqual(grid.linked_columns, ['foo', 'bar']) self.assertTrue(grid.is_linked('foo')) self.assertTrue(grid.is_linked('bar')) grid.set_link('foo', False) self.assertEqual(grid.linked_columns, ['bar']) self.assertFalse(grid.is_linked('foo')) self.assertTrue(grid.is_linked('bar')) def test_searchable_columns(self): grid = self.make_grid(columns=['foo', 'bar']) self.assertEqual(grid.searchable_columns, set()) self.assertFalse(grid.is_searchable('foo')) grid.set_searchable('foo') self.assertEqual(grid.searchable_columns, {'foo'}) self.assertTrue(grid.is_searchable('foo')) self.assertFalse(grid.is_searchable('bar')) grid.set_searchable('bar') self.assertEqual(grid.searchable_columns, {'foo', 'bar'}) self.assertTrue(grid.is_searchable('foo')) self.assertTrue(grid.is_searchable('bar')) grid.set_searchable('foo', False) self.assertEqual(grid.searchable_columns, {'bar'}) self.assertFalse(grid.is_searchable('foo')) self.assertTrue(grid.is_searchable('bar')) def test_add_action(self): grid = self.make_grid() self.assertEqual(len(grid.actions), 0) grid.add_action('view') self.assertEqual(len(grid.actions), 1) self.assertIsInstance(grid.actions[0], mod.GridAction) def test_get_pagesize_options(self): grid = self.make_grid() # default options = grid.get_pagesize_options() self.assertEqual(options, [5, 10, 20, 50, 100, 200]) # override default options = grid.get_pagesize_options(default=[42]) self.assertEqual(options, [42]) # from config self.config.setdefault('wuttaweb.grids.default_pagesize_options', '1 2 3') options = grid.get_pagesize_options() self.assertEqual(options, [1, 2, 3]) def test_get_pagesize(self): grid = self.make_grid() # default size = grid.get_pagesize() self.assertEqual(size, 20) # override default size = grid.get_pagesize(default=42) self.assertEqual(size, 42) # override default options self.config.setdefault('wuttaweb.grids.default_pagesize_options', '10 15 30') grid = self.make_grid() size = grid.get_pagesize() self.assertEqual(size, 10) # from config self.config.setdefault('wuttaweb.grids.default_pagesize', '15') size = grid.get_pagesize() self.assertEqual(size, 15) ############################## # configuration methods ############################## def test_load_settings(self): model = self.app.model # nb. first use a paging grid grid = self.make_grid(key='foo', paginated=True, paginate_on_backend=True, pagesize=20, page=1) # settings are loaded, applied, saved self.assertEqual(grid.page, 1) self.assertNotIn('grid.foo.page', self.request.session) self.request.GET = {'pagesize': '10', 'page': '2'} grid.load_settings() self.assertEqual(grid.page, 2) self.assertEqual(self.request.session['grid.foo.page'], 2) # can skip the saving step self.request.GET = {'pagesize': '10', 'page': '3'} grid.load_settings(persist=False) self.assertEqual(grid.page, 3) self.assertEqual(self.request.session['grid.foo.page'], 2) # no error for non-paginated grid grid = self.make_grid(key='foo', paginated=False) grid.load_settings() self.assertFalse(grid.paginated) # nb. next use a sorting grid grid = self.make_grid(key='settings', model_class=model.Setting, sortable=True, sort_on_backend=True) # settings are loaded, applied, saved self.assertEqual(grid.sort_defaults, []) self.assertFalse(hasattr(grid, 'active_sorters')) self.request.GET = {'sort1key': 'name', 'sort1dir': 'desc'} grid.load_settings() self.assertEqual(grid.active_sorters, [{'key': 'name', 'dir': 'desc'}]) self.assertEqual(self.request.session['grid.settings.sorters.length'], 1) self.assertEqual(self.request.session['grid.settings.sorters.1.key'], 'name') self.assertEqual(self.request.session['grid.settings.sorters.1.dir'], 'desc') # can skip the saving step self.request.GET = {'sort1key': 'name', 'sort1dir': 'asc'} grid.load_settings(persist=False) self.assertEqual(grid.active_sorters, [{'key': 'name', 'dir': 'asc'}]) self.assertEqual(self.request.session['grid.settings.sorters.length'], 1) self.assertEqual(self.request.session['grid.settings.sorters.1.key'], 'name') self.assertEqual(self.request.session['grid.settings.sorters.1.dir'], 'desc') # no error for non-sortable grid grid = self.make_grid(key='foo', sortable=False) grid.load_settings() self.assertFalse(grid.sortable) # with sort defaults grid = self.make_grid(model_class=model.Setting, sortable=True, sort_on_backend=True, sort_defaults='name') self.assertFalse(hasattr(grid, 'active_sorters')) grid.load_settings() self.assertEqual(grid.active_sorters, [{'key': 'name', 'dir': 'asc'}]) # with multi-column sort defaults grid = self.make_grid(model_class=model.Setting, sortable=True, sort_on_backend=True) grid.sort_defaults = [ mod.SortInfo('name', 'asc'), mod.SortInfo('value', 'desc'), ] self.assertFalse(hasattr(grid, 'active_sorters')) grid.load_settings() self.assertEqual(grid.active_sorters, [{'key': 'name', 'dir': 'asc'}]) # load settings from session when nothing is in request self.request.GET = {} self.request.session.invalidate() self.assertNotIn('grid.settings.sorters.length', self.request.session) self.request.session['grid.settings.sorters.length'] = 1 self.request.session['grid.settings.sorters.1.key'] = 'name' self.request.session['grid.settings.sorters.1.dir'] = 'desc' grid = self.make_grid(key='settings', model_class=model.Setting, sortable=True, sort_on_backend=True, paginated=True, paginate_on_backend=True) self.assertFalse(hasattr(grid, 'active_sorters')) grid.load_settings() self.assertEqual(grid.active_sorters, [{'key': 'name', 'dir': 'desc'}]) # filter settings are loaded, applied, saved grid = self.make_grid(key='settings', model_class=model.Setting, filterable=True) self.assertEqual(len(grid.filters), 2) self.assertFalse(hasattr(grid.filters['name'], 'active')) self.assertFalse(hasattr(grid.filters['value'], 'active')) self.assertNotIn('grid.settings.filter.name.active', self.request.session) self.assertNotIn('grid.settings.filter.value.active', self.request.session) self.request.GET = {'name': 'john', 'name.verb': 'contains'} grid.load_settings() self.assertTrue(grid.filters['name'].active) self.assertEqual(grid.filters['name'].verb, 'contains') self.assertEqual(grid.filters['name'].value, 'john') self.assertTrue(self.request.session['grid.settings.filter.name.active']) self.assertEqual(self.request.session['grid.settings.filter.name.verb'], 'contains') self.assertEqual(self.request.session['grid.settings.filter.name.value'], 'john') # filter + sort settings are loaded, applied, saved self.request.session.invalidate() grid = self.make_grid(key='settings', model_class=model.Setting, sortable=True, filterable=True) self.assertEqual(len(grid.filters), 2) self.assertFalse(hasattr(grid.filters['name'], 'active')) self.assertFalse(hasattr(grid.filters['value'], 'active')) self.assertNotIn('grid.settings.filter.name.active', self.request.session) self.assertNotIn('grid.settings.filter.value.active', self.request.session) self.assertNotIn('grid.settings.sorters.length', self.request.session) self.request.GET = {'name': 'john', 'name.verb': 'contains', 'sort1key': 'name', 'sort1dir': 'asc'} grid.load_settings() self.assertTrue(grid.filters['name'].active) self.assertEqual(grid.filters['name'].verb, 'contains') self.assertEqual(grid.filters['name'].value, 'john') self.assertTrue(self.request.session['grid.settings.filter.name.active']) self.assertEqual(self.request.session['grid.settings.filter.name.verb'], 'contains') self.assertEqual(self.request.session['grid.settings.filter.name.value'], 'john') self.assertEqual(self.request.session['grid.settings.sorters.length'], 1) self.assertEqual(self.request.session['grid.settings.sorters.1.key'], 'name') self.assertEqual(self.request.session['grid.settings.sorters.1.dir'], 'asc') def test_request_has_settings(self): model = self.app.model grid = self.make_grid(key='settings', model_class=model.Setting) # paging self.assertFalse(grid.request_has_settings('page')) with patch.object(grid, 'paginated', new=True): with patch.object(self.request, 'GET', new={'pagesize': '20'}): self.assertTrue(grid.request_has_settings('page')) with patch.object(self.request, 'GET', new={'page': '1'}): self.assertTrue(grid.request_has_settings('page')) # sorting self.assertFalse(grid.request_has_settings('sort')) with patch.object(grid, 'sortable', new=True): with patch.object(self.request, 'GET', new={'sort1key': 'name'}): self.assertTrue(grid.request_has_settings('sort')) # filtering grid = self.make_grid(key='settings', model_class=model.Setting, filterable=True) self.assertFalse(grid.request_has_settings('filter')) with patch.object(grid, 'filterable', new=True): with patch.object(self.request, 'GET', new={'name': 'john', 'name.verb': 'contains'}): self.assertTrue(grid.request_has_settings('filter')) with patch.object(self.request, 'GET', new={'filter': '1'}): self.assertTrue(grid.request_has_settings('filter')) def test_get_setting(self): grid = self.make_grid(key='foo') settings = {} # default is null value = grid.get_setting(settings, 'pagesize') self.assertIsNone(value) # can read value from user session self.request.session['grid.foo.pagesize'] = 15 value = grid.get_setting(settings, 'pagesize', src='session') self.assertEqual(value, 15) # string value not normalized self.request.session['grid.foo.pagesize'] = '15' value = grid.get_setting(settings, 'pagesize', src='session') self.assertEqual(value, '15') self.assertNotEqual(value, 15) # but can be normalized self.request.session['grid.foo.pagesize'] = '15' value = grid.get_setting(settings, 'pagesize', src='session', normalize=int) self.assertEqual(value, 15) # can read value from request self.request.GET = {'pagesize': '25'} value = grid.get_setting(settings, 'pagesize', src='request', normalize=int) self.assertEqual(value, 25) # null when normalization fails self.request.GET = {'pagesize': 'invalid'} value = grid.get_setting(settings, 'pagesize', src='request', normalize=int) self.assertIsNone(value) # reset del self.request.session['grid.foo.pagesize'] self.request.GET = {} # value can come from provided settings settings['pagesize'] = '35' value = grid.get_setting(settings, 'pagesize', src='session', normalize=int) self.assertEqual(value, 35) def test_update_filter_settings(self): model = self.app.model # nothing happens if not filterable grid = self.make_grid(key='settings', model_class=model.Setting) settings = {} self.request.session['grid.settings.filter.name.active'] = True self.request.session['grid.settings.filter.name.verb'] = 'contains' self.request.session['grid.settings.filter.name.value'] = 'john' grid.update_filter_settings(settings, src='session') self.assertEqual(settings, {}) # nb. now use a filterable grid grid = self.make_grid(key='settings', model_class=model.Setting, filterable=True) # settings are updated from session settings = {} self.request.session['grid.settings.filter.name.active'] = True self.request.session['grid.settings.filter.name.verb'] = 'contains' self.request.session['grid.settings.filter.name.value'] = 'john' grid.update_filter_settings(settings, src='session') self.assertTrue(settings['filter.name.active']) self.assertEqual(settings['filter.name.verb'], 'contains') self.assertEqual(settings['filter.name.value'], 'john') # settings are updated from request self.request.GET = {'value': 'sally', 'value.verb': 'contains'} grid.update_filter_settings(settings, src='request') self.assertFalse(settings['filter.name.active']) self.assertTrue(settings['filter.value.active']) self.assertEqual(settings['filter.value.verb'], 'contains') self.assertEqual(settings['filter.value.value'], 'sally') def test_update_sort_settings(self): model = self.app.model # nothing happens if not sortable grid = self.make_grid(key='settings', model_class=model.Setting) settings = {'sorters.length': 0} self.request.session['grid.settings.sorters.length'] = 1 self.request.session['grid.settings.sorters.1.key'] = 'name' self.request.session['grid.settings.sorters.1.dir'] = 'asc' grid.update_sort_settings(settings, src='session') self.assertEqual(settings['sorters.length'], 0) # nb. now use a sortable grid grid = self.make_grid(key='settings', model_class=model.Setting, sortable=True, sort_on_backend=True) # settings are updated from session settings = {'sorters.length': 1, 'sorters.1.key': 'name', 'sorters.1.dir': 'asc'} self.request.session['grid.settings.sorters.length'] = 1 self.request.session['grid.settings.sorters.1.key'] = 'name' self.request.session['grid.settings.sorters.1.dir'] = 'asc' grid.update_sort_settings(settings, src='session') self.assertEqual(settings['sorters.length'], 1) self.assertEqual(settings['sorters.1.key'], 'name') self.assertEqual(settings['sorters.1.dir'], 'asc') # settings are updated from request self.request.GET = {'sort1key': 'value', 'sort1dir': 'desc'} grid.update_sort_settings(settings, src='request') self.assertEqual(settings['sorters.length'], 1) self.assertEqual(settings['sorters.1.key'], 'value') self.assertEqual(settings['sorters.1.dir'], 'desc') def test_update_page_settings(self): # nothing happens if not paginated grid = self.make_grid(key='foo') settings = {'pagesize': 20, 'page': 1} self.request.session['grid.foo.pagesize'] = 10 self.request.session['grid.foo.page'] = 2 grid.update_page_settings(settings) self.assertEqual(settings['pagesize'], 20) self.assertEqual(settings['page'], 1) # nb. now use a paginated grid grid = self.make_grid(key='foo', paginated=True, paginate_on_backend=True) # settings are updated from session settings = {'pagesize': 20, 'page': 1} self.request.session['grid.foo.pagesize'] = 10 self.request.session['grid.foo.page'] = 2 grid.update_page_settings(settings) self.assertEqual(settings['pagesize'], 10) self.assertEqual(settings['page'], 2) # settings are updated from request self.request.GET = {'pagesize': '15', 'page': '4'} grid.update_page_settings(settings) self.assertEqual(settings['pagesize'], 15) self.assertEqual(settings['page'], 4) def test_persist_settings(self): model = self.app.model # nb. start out with paginated-only grid grid = self.make_grid(key='foo', paginated=True, paginate_on_backend=True) # invalid dest self.assertRaises(ValueError, grid.persist_settings, {}, dest='doesnotexist') # nb. no error if empty settings, but it saves null values grid.persist_settings({}, dest='session') self.assertIsNone(self.request.session['grid.foo.page']) # provided values are saved grid.persist_settings({'pagesize': 15, 'page': 3}, dest='session') self.assertEqual(self.request.session['grid.foo.page'], 3) # nb. now switch to sortable-only grid grid = self.make_grid(key='settings', model_class=model.Setting, sortable=True, sort_on_backend=True) # no error if empty settings; does not save values grid.persist_settings({}, dest='session') self.assertNotIn('grid.settings.sorters.length', self.request.session) # provided values are saved grid.persist_settings({'sorters.length': 2, 'sorters.1.key': 'name', 'sorters.1.dir': 'desc', 'sorters.2.key': 'value', 'sorters.2.dir': 'asc'}, dest='session') self.assertEqual(self.request.session['grid.settings.sorters.length'], 2) self.assertEqual(self.request.session['grid.settings.sorters.1.key'], 'name') self.assertEqual(self.request.session['grid.settings.sorters.1.dir'], 'desc') self.assertEqual(self.request.session['grid.settings.sorters.2.key'], 'value') self.assertEqual(self.request.session['grid.settings.sorters.2.dir'], 'asc') # old values removed when new are saved grid.persist_settings({'sorters.length': 1, 'sorters.1.key': 'name', 'sorters.1.dir': 'desc'}, dest='session') self.assertEqual(self.request.session['grid.settings.sorters.length'], 1) self.assertEqual(self.request.session['grid.settings.sorters.1.key'], 'name') self.assertEqual(self.request.session['grid.settings.sorters.1.dir'], 'desc') self.assertNotIn('grid.settings.sorters.2.key', self.request.session) self.assertNotIn('grid.settings.sorters.2.dir', self.request.session) # nb. now switch to filterable-only grid grid = self.make_grid(key='settings', model_class=model.Setting, filterable=True) self.assertIn('name', grid.filters) self.assertEqual(grid.filters['name'].key, 'name') # no error if empty settings; does not save values grid.persist_settings({}, dest='session') self.assertNotIn('grid.settings.filters.name', self.request.session) # provided values are saved grid.persist_settings({'filter.name.active': True, 'filter.name.verb': 'contains', 'filter.name.value': 'john'}, dest='session') self.assertTrue(self.request.session['grid.settings.filter.name.active']) self.assertEqual(self.request.session['grid.settings.filter.name.verb'], 'contains') self.assertEqual(self.request.session['grid.settings.filter.name.value'], 'john') ############################## # sorting methods ############################## def test_make_backend_sorters(self): model = self.app.model # default is empty grid = self.make_grid() sorters = grid.make_backend_sorters() self.assertEqual(sorters, {}) # makes sorters if model class grid = self.make_grid(model_class=model.Setting) sorters = grid.make_backend_sorters() self.assertEqual(len(sorters), 2) self.assertIn('name', sorters) self.assertIn('value', sorters) # does not replace supplied sorters grid = self.make_grid(model_class=model.Setting) mysorters = {'value': 42} sorters = grid.make_backend_sorters(mysorters) self.assertEqual(len(sorters), 2) self.assertIn('name', sorters) self.assertIn('value', sorters) self.assertEqual(sorters['value'], 42) self.assertEqual(mysorters['value'], 42) def test_make_sorter(self): model = self.app.model sample_data = [ {'name': 'foo1', 'value': 'ONE'}, {'name': 'foo2', 'value': 'two'}, {'name': 'foo3', 'value': 'three'}, {'name': 'foo4', 'value': 'four'}, {'name': 'foo5', 'value': 'five'}, {'name': 'foo6', 'value': 'six'}, {'name': 'foo7', 'value': 'seven'}, {'name': 'foo8', 'value': 'eight'}, {'name': 'foo9', 'value': 'nine'}, ] for setting in sample_data: self.app.save_setting(self.session, setting['name'], setting['value']) self.session.commit() sample_query = self.session.query(model.Setting) # plain data grid = self.make_grid(columns=['name', 'value']) sorter = grid.make_sorter('name') sorted_data = sorter(sample_data, 'desc') self.assertEqual(sorted_data[0], {'name': 'foo9', 'value': 'nine'}) sorted_data = sorter(sample_data, 'asc') self.assertEqual(sorted_data[0], {'name': 'foo1', 'value': 'ONE'}) # model class, but still plain data grid = self.make_grid(model_class=model.Setting) sorter = grid.make_sorter('name') sorted_data = sorter(sample_data, 'desc') self.assertEqual(sorted_data[0], {'name': 'foo9', 'value': 'nine'}) sorted_data = sorter(sample_data, 'asc') self.assertEqual(sorted_data[0], {'name': 'foo1', 'value': 'ONE'}) # repeat previous test, w/ model property grid = self.make_grid(model_class=model.Setting) sorter = grid.make_sorter(model.Setting.name) sorted_data = sorter(sample_data, 'desc') self.assertEqual(sorted_data[0], {'name': 'foo9', 'value': 'nine'}) sorted_data = sorter(sample_data, 'asc') self.assertEqual(sorted_data[0], {'name': 'foo1', 'value': 'ONE'}) # sqlalchemy query grid = self.make_grid(model_class=model.Setting) sorter = grid.make_sorter('name') sorted_query = sorter(sample_query, 'desc') sorted_data = sorted_query.all() self.assertEqual(dict(sorted_data[0]), {'name': 'foo9', 'value': 'nine'}) sorted_query = sorter(sample_query, 'asc') sorted_data = sorted_query.all() self.assertEqual(dict(sorted_data[0]), {'name': 'foo1', 'value': 'ONE'}) # repeat previous test, w/ model property grid = self.make_grid(model_class=model.Setting) sorter = grid.make_sorter(model.Setting.name) sorted_query = sorter(sample_query, 'desc') sorted_data = sorted_query.all() self.assertEqual(dict(sorted_data[0]), {'name': 'foo9', 'value': 'nine'}) sorted_query = sorter(sample_query, 'asc') sorted_data = sorted_query.all() self.assertEqual(dict(sorted_data[0]), {'name': 'foo1', 'value': 'ONE'}) # sortfunc for "invalid" column will fail when called; however # it can work for manual sort w/ custom keyfunc grid = self.make_grid(model_class=model.Setting) sorter = grid.make_sorter('doesnotexist') self.assertRaises(TypeError, sorter, sample_query, 'desc') self.assertRaises(KeyError, sorter, sample_data, 'desc') sorter = grid.make_sorter('doesnotexist', keyfunc=lambda obj: obj['name']) sorted_data = sorter(sample_data, 'desc') self.assertEqual(len(sorted_data), 9) sorted_data = sorter(sample_data, 'asc') self.assertEqual(len(sorted_data), 9) # case folding is on by default grid = self.make_grid(model_class=model.Setting) sorter = grid.make_sorter('value') sorted_data = sorter(sample_data, 'desc') self.assertEqual(dict(sorted_data[0]), {'name': 'foo2', 'value': 'two'}) sorted_data = sorter(sample_data, 'asc') self.assertEqual(dict(sorted_data[0]), {'name': 'foo8', 'value': 'eight'}) # results are different with case folding off grid = self.make_grid(model_class=model.Setting) sorter = grid.make_sorter('value', foldcase=False) sorted_data = sorter(sample_data, 'desc') self.assertEqual(dict(sorted_data[0]), {'name': 'foo2', 'value': 'two'}) sorted_data = sorter(sample_data, 'asc') self.assertEqual(dict(sorted_data[0]), {'name': 'foo1', 'value': 'ONE'}) def test_set_joiner(self): # basic grid = self.make_grid(columns=['foo', 'bar'], sortable=True, sort_on_backend=True) self.assertEqual(grid.joiners, {}) grid.set_joiner('foo', 42) self.assertEqual(grid.joiners, {'foo': 42}) def test_remove_joiner(self): # basic grid = self.make_grid(columns=['foo', 'bar'], sortable=True, sort_on_backend=True, joiners={'foo': 42}) self.assertEqual(grid.joiners, {'foo': 42}) grid.remove_joiner('foo') self.assertEqual(grid.joiners, {}) def test_set_sorter(self): model = self.app.model # explicit sortfunc grid = self.make_grid() self.assertEqual(grid.sorters, {}) sortfunc = lambda data, direction: data grid.set_sorter('foo', sortfunc) self.assertIs(grid.sorters['foo'], sortfunc) # auto from model property grid = self.make_grid(model_class=model.Setting, sortable=True, sorters={}) self.assertEqual(grid.sorters, {}) grid.set_sorter('name', model.Setting.name) self.assertTrue(callable(grid.sorters['name'])) # auto from column name grid = self.make_grid(model_class=model.Setting, sortable=True, sorters={}) self.assertEqual(grid.sorters, {}) grid.set_sorter('name', 'name') self.assertTrue(callable(grid.sorters['name'])) # auto from key grid = self.make_grid(model_class=model.Setting, sortable=True, sorters={}) self.assertEqual(grid.sorters, {}) grid.set_sorter('name') self.assertTrue(callable(grid.sorters['name'])) def test_remove_sorter(self): model = self.app.model # basics grid = self.make_grid(model_class=model.Setting, sortable=True) self.assertEqual(len(grid.sorters), 2) self.assertIn('name', grid.sorters) self.assertIn('value', grid.sorters) grid.remove_sorter('value') self.assertNotIn('value', grid.sorters) def test_set_sort_defaults(self): model = self.app.model grid = self.make_grid(model_class=model.Setting, sortable=True) self.assertEqual(grid.sort_defaults, []) # can set just sortkey grid.set_sort_defaults('name') self.assertEqual(grid.sort_defaults, [mod.SortInfo('name', 'asc')]) # can set sortkey, sortdir grid.set_sort_defaults('name', 'desc') self.assertEqual(grid.sort_defaults, [mod.SortInfo('name', 'desc')]) # can set sortkey, sortdir as tuple grid.set_sort_defaults(('value', 'asc')) self.assertEqual(grid.sort_defaults, [mod.SortInfo('value', 'asc')]) # can set as list grid.sort_multiple = True grid.set_sort_defaults([('value', 'asc'), ('name', 'desc')]) self.assertEqual(grid.sort_defaults, [mod.SortInfo('value', 'asc'), mod.SortInfo('name', 'desc')]) # list is pruned if multi-sort disabled grid.sort_multiple = False grid.set_sort_defaults([('value', 'asc'), ('name', 'desc')]) self.assertEqual(grid.sort_defaults, [mod.SortInfo('value', 'asc')]) # error if any other single arg self.assertRaises(ValueError, grid.set_sort_defaults, 42) # error if more than 2 args self.assertRaises(ValueError, grid.set_sort_defaults, 'name', 'asc', 'value', 'desc') def test_is_sortable(self): model = self.app.model # basics, frontend sorting grid = self.make_grid(model_class=model.Setting, sortable=True, sort_on_backend=False) self.assertTrue(grid.is_sortable('name')) self.assertTrue(grid.is_sortable('value')) grid.remove_sorter('value') # nb. columns are always sortable for frontend, despite remove_sorter() self.assertTrue(grid.is_sortable('value')) # nb. when grid is not sortable, no column is either grid.sortable = False self.assertFalse(grid.is_sortable('name')) # same test but with backend sorting grid = self.make_grid(model_class=model.Setting, sortable=True, sort_on_backend=True) self.assertTrue(grid.is_sortable('name')) self.assertTrue(grid.is_sortable('value')) grid.remove_sorter('value') self.assertFalse(grid.is_sortable('value')) # nb. when grid is not sortable, no column is either grid.sortable = False self.assertFalse(grid.is_sortable('name')) def test_make_backend_filters(self): model = self.app.model # default is empty grid = self.make_grid() filters = grid.make_backend_filters() self.assertEqual(filters, {}) # makes filters if model class with patch.object(mod.Grid, 'make_filter'): # nb. filters are MagicMock instances grid = self.make_grid(model_class=model.Setting) filters = grid.make_backend_filters() self.assertEqual(len(filters), 2) self.assertIn('name', filters) self.assertIn('value', filters) # does not replace supplied filters myfilters = {'value': 42} with patch.object(mod.Grid, 'make_filter'): # nb. filters are MagicMock instances grid = self.make_grid(model_class=model.Setting) filters = grid.make_backend_filters(myfilters) self.assertEqual(len(filters), 2) self.assertIn('name', filters) self.assertIn('value', filters) self.assertEqual(filters['value'], 42) self.assertEqual(myfilters['value'], 42) def test_make_filter(self): model = self.app.model # basic grid = self.make_grid(model_class=model.Setting) filtr = grid.make_filter('name') self.assertIsInstance(filtr, mod.GridFilter) # property grid = self.make_grid(model_class=model.Setting) filtr = grid.make_filter(model.Setting.name) self.assertIsInstance(filtr, mod.GridFilter) def test_set_filter(self): model = self.app.model with patch.object(mod.Grid, 'make_filter', return_value=42): # auto from model property grid = self.make_grid(model_class=model.Setting) self.assertEqual(grid.filters, {}) grid.set_filter('name', model.Setting.name) self.assertIn('name', grid.filters) # auto from column name grid = self.make_grid(model_class=model.Setting) self.assertEqual(grid.filters, {}) grid.set_filter('name', 'name') self.assertIn('name', grid.filters) # auto from key grid = self.make_grid(model_class=model.Setting) self.assertEqual(grid.filters, {}) grid.set_filter('name') self.assertIn('name', grid.filters) # explicit is not yet implemented grid = self.make_grid(model_class=model.Setting) self.assertEqual(grid.filters, {}) self.assertRaises(NotImplementedError, grid.set_filter, 'name', lambda q: q) def test_remove_filter(self): model = self.app.model # basics with patch.object(mod.Grid, 'make_filter'): # nb. filters are MagicMock instances grid = self.make_grid(model_class=model.Setting, filterable=True) self.assertEqual(len(grid.filters), 2) self.assertIn('name', grid.filters) self.assertIn('value', grid.filters) grid.remove_filter('value') self.assertNotIn('value', grid.filters) ############################## # data methods ############################## def test_get_visible_data(self): model = self.app.model sample_data = [ {'name': 'foo1', 'value': 'ONE'}, {'name': 'foo2', 'value': 'two'}, {'name': 'foo3', 'value': 'three'}, {'name': 'foo4', 'value': 'four'}, {'name': 'foo5', 'value': 'five'}, {'name': 'foo6', 'value': 'six'}, {'name': 'foo7', 'value': 'seven'}, {'name': 'foo8', 'value': 'eight'}, {'name': 'foo9', 'value': 'nine'}, ] for setting in sample_data: self.app.save_setting(self.session, setting['name'], setting['value']) self.session.commit() sample_query = self.session.query(model.Setting) # data is sorted and paginated grid = self.make_grid(model_class=model.Setting, data=sample_query, filterable=True, sortable=True, sort_on_backend=True, sort_defaults=('name', 'desc'), paginated=True, paginate_on_backend=True, pagesize=4, page=2) grid.load_settings() # nb. for now the filtering is mocked with patch.object(grid, 'filter_data') as filter_data: filter_data.side_effect = lambda q: q visible = grid.get_visible_data() filter_data.assert_called_once_with(sample_query) self.assertEqual([s.name for s in visible], ['foo5', 'foo4', 'foo3', 'foo2']) def test_filter_data(self): model = self.app.model query = self.session.query(model.Setting) grid = self.make_grid(model_class=model.Setting, filterable=True) grid.load_settings() self.assertRaises(NotImplementedError, grid.filter_data, query) def test_sort_data(self): model = self.app.model sample_data = [ {'name': 'foo1', 'value': 'ONE'}, {'name': 'foo2', 'value': 'two'}, {'name': 'foo3', 'value': 'ggg'}, {'name': 'foo4', 'value': 'ggg'}, {'name': 'foo5', 'value': 'ggg'}, {'name': 'foo6', 'value': 'six'}, {'name': 'foo7', 'value': 'seven'}, {'name': 'foo8', 'value': 'eight'}, {'name': 'foo9', 'value': 'nine'}, ] for setting in sample_data: self.app.save_setting(self.session, setting['name'], setting['value']) self.session.commit() sample_query = self.session.query(model.Setting) grid = self.make_grid(model_class=model.Setting, sortable=True, sort_on_backend=True, sort_defaults=('name', 'desc')) grid.load_settings() # can sort a simple list of data sorted_data = grid.sort_data(sample_data) self.assertIsInstance(sorted_data, list) self.assertEqual(len(sorted_data), 9) self.assertEqual(sorted_data[0]['name'], 'foo9') self.assertEqual(sorted_data[-1]['name'], 'foo1') # can also sort a data query sorted_query = grid.sort_data(sample_query) self.assertIsInstance(sorted_query, orm.Query) sorted_data = sorted_query.all() self.assertEqual(len(sorted_data), 9) self.assertEqual(sorted_data[0]['name'], 'foo9') self.assertEqual(sorted_data[-1]['name'], 'foo1') # cannot sort data if sorter missing in overrides sorted_data = grid.sort_data(sample_data, sorters=[]) # nb. sorted data is in same order as original sample (not sorted) self.assertEqual(sorted_data[0]['name'], 'foo1') self.assertEqual(sorted_data[-1]['name'], 'foo9') # multi-column sorting for list data sorted_data = grid.sort_data(sample_data, sorters=[{'key': 'value', 'dir': 'asc'}, {'key': 'name', 'dir': 'asc'}]) self.assertEqual(dict(sorted_data[0]), {'name': 'foo8', 'value': 'eight'}) self.assertEqual(dict(sorted_data[1]), {'name': 'foo3', 'value': 'ggg'}) self.assertEqual(dict(sorted_data[3]), {'name': 'foo5', 'value': 'ggg'}) self.assertEqual(dict(sorted_data[-1]), {'name': 'foo2', 'value': 'two'}) # multi-column sorting for query sorted_query = grid.sort_data(sample_query, sorters=[{'key': 'value', 'dir': 'asc'}, {'key': 'name', 'dir': 'asc'}]) self.assertEqual(dict(sorted_data[0]), {'name': 'foo8', 'value': 'eight'}) self.assertEqual(dict(sorted_data[1]), {'name': 'foo3', 'value': 'ggg'}) self.assertEqual(dict(sorted_data[3]), {'name': 'foo5', 'value': 'ggg'}) self.assertEqual(dict(sorted_data[-1]), {'name': 'foo2', 'value': 'two'}) # cannot sort data if sortfunc is missing for column grid.remove_sorter('name') sorted_data = grid.sort_data(sample_data, sorters=[{'key': 'value', 'dir': 'asc'}, {'key': 'name', 'dir': 'asc'}]) # nb. sorted data is in same order as original sample (not sorted) self.assertEqual(sorted_data[0]['name'], 'foo1') self.assertEqual(sorted_data[-1]['name'], 'foo9') # now try with a joiner query = self.session.query(model.User) grid = self.make_grid(model_class=model.User, data=query, columns=['username', 'full_name'], sortable=True, sort_on_backend=True, sort_defaults='full_name', joiners={ 'full_name': lambda q: q.join(model.Person), }) grid.set_sorter('full_name', model.Person.full_name) grid.load_settings() data = grid.get_visible_data() self.assertIsInstance(data, orm.Query) def test_paginate_data(self): model = self.app.model sample_data = [ {'name': 'foo1', 'value': 'ONE'}, {'name': 'foo2', 'value': 'two'}, {'name': 'foo3', 'value': 'three'}, {'name': 'foo4', 'value': 'four'}, {'name': 'foo5', 'value': 'five'}, {'name': 'foo6', 'value': 'six'}, {'name': 'foo7', 'value': 'seven'}, {'name': 'foo8', 'value': 'eight'}, {'name': 'foo9', 'value': 'nine'}, ] for setting in sample_data: self.app.save_setting(self.session, setting['name'], setting['value']) self.session.commit() sample_query = self.session.query(model.Setting) # basic list pager grid = self.make_grid(paginated=True, paginate_on_backend=True) pager = grid.paginate_data(sample_data) self.assertIsInstance(pager, Page) # basic query pager grid = self.make_grid(paginated=True, paginate_on_backend=True) pager = grid.paginate_data(sample_query) self.assertIsInstance(pager, SqlalchemyOrmPage) # page is reset to 1 for empty data self.request.session['grid.foo.page'] = 2 grid = self.make_grid(key='foo', paginated=True, paginate_on_backend=True) grid.load_settings() self.assertEqual(grid.page, 2) self.assertEqual(self.request.session['grid.foo.page'], 2) pager = grid.paginate_data(sample_data) self.assertEqual(pager.page, 1) self.assertEqual(grid.page, 1) self.assertEqual(self.request.session['grid.foo.page'], 1) ############################## # rendering methods ############################## def test_render_vue_tag(self): grid = self.make_grid(columns=['foo', 'bar']) html = grid.render_vue_tag() self.assertEqual(html, '') def test_render_vue_template(self): self.pyramid_config.include('pyramid_mako') self.pyramid_config.add_subscriber('wuttaweb.subscribers.before_render', 'pyramid.events.BeforeRender') grid = self.make_grid(columns=['foo', 'bar']) html = grid.render_vue_template() self.assertIn('