1
0
Fork 0
wuttaweb/tests/grids/test_base.py
2024-08-21 15:50:36 -05:00

1050 lines
42 KiB
Python

# -*- coding: utf-8; -*-
from unittest import TestCase
from unittest.mock import patch
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_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):
grid = self.make_grid(columns=['foo', 'bar'])
self.assertEqual(grid.labels, {})
# basic
grid.set_label('foo', "Foo Fighters")
self.assertEqual(grid.labels['foo'], "Foo Fighters")
# can replace label
grid.set_label('foo', "Different")
self.assertEqual(grid.labels['foo'], "Different")
self.assertEqual(grid.get_label('foo'), "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'}])
def test_request_has_settings(self):
grid = self.make_grid(key='foo')
# paging
self.assertFalse(grid.request_has_settings('page'))
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(self.request, 'GET', new={'sort1key': 'name'}):
self.assertTrue(grid.request_has_settings('sort'))
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_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)
##############################
# 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_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'))
##############################
# 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,
sortable=True, sort_on_backend=True,
sort_defaults=('name', 'desc'),
paginated=True, paginate_on_backend=True,
pagesize=4, page=2)
grid.load_settings()
visible = grid.get_visible_data()
self.assertEqual([s.name for s in visible], ['foo5', 'foo4', 'foo3', 'foo2'])
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')
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, '<wutta-grid></wutta-grid>')
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('<script type="text/x-template" id="wutta-grid-template">', html)
def test_render_table_element(self):
self.pyramid_config.include('pyramid_mako')
self.pyramid_config.add_subscriber('wuttaweb.subscribers.before_render',
'pyramid.events.BeforeRender')
grid = self.make_grid(key='foobar', columns=['foo', 'bar'])
# form not required
html = grid.render_table_element()
self.assertNotIn('<script ', html)
self.assertIn('<b-table ', html)
# form will register grid data
form = Form(self.request)
self.assertEqual(len(form.grid_vue_data), 0)
html = grid.render_table_element(form)
self.assertEqual(len(form.grid_vue_data), 1)
self.assertIn('foobar', form.grid_vue_data)
def test_render_vue_finalize(self):
grid = self.make_grid()
html = grid.render_vue_finalize()
self.assertIn('<script>', html)
self.assertIn("Vue.component('wutta-grid', WuttaGrid)", html)
def test_get_vue_columns(self):
# error if no columns are set
grid = self.make_grid()
self.assertRaises(ValueError, grid.get_vue_columns)
# otherwise get back field/label dicts
grid = self.make_grid(columns=['foo', 'bar'])
columns = grid.get_vue_columns()
first = columns[0]
self.assertEqual(first['field'], 'foo')
self.assertEqual(first['label'], 'Foo')
def test_get_vue_active_sorters(self):
model = self.app.model
# empty
grid = self.make_grid(key='foo', sortable=True, sort_on_backend=True)
grid.load_settings()
sorters = grid.get_vue_active_sorters()
self.assertEqual(sorters, [])
# format is different
grid = self.make_grid(key='settings', model_class=model.Setting,
sortable=True, sort_on_backend=True,
sort_defaults='name')
grid.load_settings()
self.assertEqual(grid.active_sorters, [{'key': 'name', 'dir': 'asc'}])
sorters = grid.get_vue_active_sorters()
self.assertEqual(sorters, [{'field': 'name', 'order': 'asc'}])
def test_get_vue_data(self):
# empty if no columns defined
grid = self.make_grid()
data = grid.get_vue_data()
self.assertEqual(data, [])
# typical data is a list
mydata = [
{'foo': 'bar'},
]
grid = self.make_grid(columns=['foo'], data=mydata)
data = grid.get_vue_data()
self.assertEqual(data, [{'foo': 'bar'}])
# if grid has actions, that list may be supplemented
grid.actions.append(mod.GridAction(self.request, 'view', url='/blarg'))
data = grid.get_vue_data()
self.assertIsNot(data, mydata)
self.assertEqual(data, [{'foo': 'bar', '_action_url_view': '/blarg'}])
# also can override value rendering
grid.set_renderer('foo', lambda record, key, value: "blah blah")
data = grid.get_vue_data()
self.assertEqual(data, [{'foo': 'blah blah', '_action_url_view': '/blarg'}])
def test_get_vue_pager_stats(self):
data = [
{'foo': 1, 'bar': 1},
{'foo': 2, 'bar': 2},
{'foo': 3, 'bar': 3},
{'foo': 4, 'bar': 4},
{'foo': 5, 'bar': 5},
{'foo': 6, 'bar': 6},
{'foo': 7, 'bar': 7},
{'foo': 8, 'bar': 8},
{'foo': 9, 'bar': 9},
]
grid = self.make_grid(columns=['foo', 'bar'], pagesize=4, page=2)
grid.pager = grid.paginate_data(data)
stats = grid.get_vue_pager_stats()
self.assertEqual(stats['item_count'], 9)
self.assertEqual(stats['items_per_page'], 4)
self.assertEqual(stats['page'], 2)
self.assertEqual(stats['first_item'], 5)
self.assertEqual(stats['last_item'], 8)
class TestGridAction(TestCase):
def setUp(self):
self.config = WuttaConfig()
self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False)
def make_action(self, key, **kwargs):
return mod.GridAction(self.request, key, **kwargs)
def test_render_icon(self):
# icon is derived from key by default
action = self.make_action('blarg')
html = action.render_icon()
self.assertIn('<i class="fas fa-blarg">', html)
# oruga has different output
self.request.use_oruga = True
html = action.render_icon()
self.assertIn('<o-icon icon="blarg">', html)
def test_render_label(self):
# label is derived from key by default
action = self.make_action('blarg')
label = action.render_label()
self.assertEqual(label, "Blarg")
# otherwise use what caller provides
action = self.make_action('foo', label="Bar")
label = action.render_label()
self.assertEqual(label, "Bar")
def test_render_icon_and_label(self):
action = self.make_action('blarg')
with patch.multiple(action,
render_icon=lambda: 'ICON',
render_label=lambda: 'LABEL'):
html = action.render_icon_and_label()
self.assertEqual('ICON LABEL', html)
def test_get_url(self):
obj = {'foo': 'bar'}
# null by default
action = self.make_action('blarg')
url = action.get_url(obj)
self.assertIsNone(url)
# or can be "static"
action = self.make_action('blarg', url='/foo')
url = action.get_url(obj)
self.assertEqual(url, '/foo')
# or can be "dynamic"
action = self.make_action('blarg', url=lambda o, i: '/yeehaw')
url = action.get_url(obj)
self.assertEqual(url, '/yeehaw')