feat: move single-column grid sorting logic to wuttaweb

This commit is contained in:
Lance Edgar 2024-08-18 14:05:52 -05:00
parent c95e42bf82
commit ec36df4a34
7 changed files with 475 additions and 200 deletions

View file

@ -1,6 +1,8 @@
# -*- coding: utf-8; -*-
from unittest.mock import MagicMock
from unittest.mock import MagicMock, patch
from sqlalchemy import orm
from tailbone.grids import core as mod
from tests.util import WebTestCase
@ -27,6 +29,16 @@ class TestGrid(WebTestCase):
grid = self.make_grid(component='blarg')
self.assertEqual(grid.vue_tagname, 'blarg')
# default_sortkey, default_sortdir
grid = self.make_grid()
self.assertEqual(grid.sort_defaults, [])
grid = self.make_grid(default_sortkey='name')
self.assertEqual(grid.sort_defaults, [mod.SortInfo('name', 'asc')])
grid = self.make_grid(default_sortdir='desc')
self.assertEqual(grid.sort_defaults, [])
grid = self.make_grid(default_sortkey='name', default_sortdir='desc')
self.assertEqual(grid.sort_defaults, [mod.SortInfo('name', 'desc')])
# pageable
grid = self.make_grid()
self.assertFalse(grid.paginated)
@ -159,6 +171,27 @@ class TestGrid(WebTestCase):
grid.set_action_urls(setting, setting, 0)
self.assertEqual(setting['_action_url_view'], '/blarg')
def test_default_sortkey(self):
grid = self.make_grid()
self.assertEqual(grid.sort_defaults, [])
self.assertIsNone(grid.default_sortkey)
grid.default_sortkey = 'name'
self.assertEqual(grid.sort_defaults, [mod.SortInfo('name', 'asc')])
self.assertEqual(grid.default_sortkey, 'name')
grid.default_sortkey = 'value'
self.assertEqual(grid.sort_defaults, [mod.SortInfo('value', 'asc')])
self.assertEqual(grid.default_sortkey, 'value')
def test_default_sortdir(self):
grid = self.make_grid()
self.assertEqual(grid.sort_defaults, [])
self.assertIsNone(grid.default_sortdir)
self.assertRaises(ValueError, setattr, grid, 'default_sortdir', 'asc')
grid.sort_defaults = [mod.SortInfo('name', 'asc')]
grid.default_sortdir = 'desc'
self.assertEqual(grid.sort_defaults, [mod.SortInfo('name', 'desc')])
self.assertEqual(grid.default_sortdir, 'desc')
def test_pageable(self):
grid = self.make_grid()
self.assertFalse(grid.paginated)
@ -219,6 +252,212 @@ class TestGrid(WebTestCase):
size = grid.get_pagesize()
self.assertEqual(size, 15)
def test_set_sorter(self):
model = self.app.model
grid = self.make_grid(model_class=model.Setting,
sortable=True, sort_on_backend=True)
# passing None will remove sorter
self.assertIn('name', grid.sorters)
grid.set_sorter('name', None)
self.assertNotIn('name', grid.sorters)
# can recreate sorter with just column name
grid.set_sorter('name')
self.assertIn('name', grid.sorters)
grid.remove_sorter('name')
self.assertNotIn('name', grid.sorters)
grid.set_sorter('name', 'name')
self.assertIn('name', grid.sorters)
# can recreate sorter with model property
grid.remove_sorter('name')
self.assertNotIn('name', grid.sorters)
grid.set_sorter('name', model.Setting.name)
self.assertIn('name', grid.sorters)
# extra kwargs are ignored
grid.remove_sorter('name')
self.assertNotIn('name', grid.sorters)
grid.set_sorter('name', model.Setting.name, foo='bar')
self.assertIn('name', grid.sorters)
# passing multiple args will invoke make_filter() directly
grid.remove_sorter('name')
self.assertNotIn('name', grid.sorters)
with patch.object(grid, 'make_sorter') as make_sorter:
make_sorter.return_value = 42
grid.set_sorter('name', 'foo', 'bar')
make_sorter.assert_called_once_with('foo', 'bar')
self.assertEqual(grid.sorters['name'], 42)
def test_make_simple_sorter(self):
model = self.app.model
grid = self.make_grid(model_class=model.Setting,
sortable=True, sort_on_backend=True)
# delegates to grid.make_sorter()
with patch.object(grid, 'make_sorter') as make_sorter:
make_sorter.return_value = 42
sorter = grid.make_simple_sorter('name', foldcase=True)
make_sorter.assert_called_once_with('name', foldcase=True)
self.assertEqual(sorter, 42)
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(store=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(store=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_sort_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)
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')
# error if mult-column sort attempted
self.assertRaises(NotImplementedError, grid.sort_data, sample_data, sorters=[
{'key': 'name', 'dir': 'desc'},
{'key': 'value', 'dir': 'asc'},
])
# cannot sort data if sortfunc is missing for column
grid.remove_sorter('name')
sorted_data = grid.sort_data(sample_data)
# 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')
# cannot sort data if sortfunc is missing for column
grid.remove_sorter('name')
# nb. attempting multi-column sort, but only one sorter exists
self.assertEqual(list(grid.sorters), ['value'])
grid.active_sorters = [{'key': 'name', 'dir': 'asc'},
{'key': 'value', 'dir': 'asc'}]
with patch.object(sample_query, 'order_by') as order_by:
order_by.return_value = 42
sorted_query = grid.sort_data(sample_query)
order_by.assert_called_once()
self.assertEqual(len(order_by.call_args.args), 1)
self.assertEqual(sorted_query, 42)
def test_render_vue_tag(self):
model = self.app.model
@ -249,11 +488,13 @@ class TestGrid(WebTestCase):
model = self.app.model
# sanity check
grid = self.make_grid('settings', model_class=model.Setting)
grid = self.make_grid('settings', model_class=model.Setting, sortable=True)
columns = grid.get_vue_columns()
self.assertEqual(len(columns), 2)
self.assertEqual(columns[0]['field'], 'name')
self.assertTrue(columns[0]['sortable'])
self.assertEqual(columns[1]['field'], 'value')
self.assertTrue(columns[1]['sortable'])
def test_get_vue_data(self):
model = self.app.model