f7e371d21d
to make the templates just that much cleaner
1002 lines
40 KiB
Python
1002 lines
40 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.forms import FieldList
|
|
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_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_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')
|