3
0
Fork 0

feat: add initial filtering logic to grid class

still missing the actual filters, subclass must provide those for now
This commit is contained in:
Lance Edgar 2024-08-21 20:15:23 -05:00
parent a042d511fb
commit 9751bf4c2e
2 changed files with 633 additions and 20 deletions

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8; -*-
from unittest import TestCase
from unittest.mock import patch
from unittest.mock import patch, MagicMock
from sqlalchemy import orm
from paginate import Page
@ -86,6 +86,32 @@ class TestGrid(WebTestCase):
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')
@ -125,17 +151,28 @@ class TestGrid(WebTestCase):
self.assertEqual(grid.columns, ['one', 'four'])
def test_set_label(self):
grid = self.make_grid(columns=['foo', 'bar'])
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('foo', "Foo Fighters")
self.assertEqual(grid.labels['foo'], "Foo Fighters")
grid.set_label('name', "NAME COL")
self.assertEqual(grid.labels['name'], "NAME COL")
# can replace label
grid.set_label('foo', "Different")
self.assertEqual(grid.labels['foo'], "Different")
self.assertEqual(grid.get_label('foo'), "Different")
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'])
@ -342,20 +379,72 @@ class TestGrid(WebTestCase):
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):
grid = self.make_grid(key='foo')
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(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'))
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(self.request, 'GET', new={'sort1key': 'name'}):
self.assertTrue(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')
@ -400,6 +489,40 @@ class TestGrid(WebTestCase):
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
@ -510,6 +633,25 @@ class TestGrid(WebTestCase):
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
##############################
@ -629,6 +771,23 @@ class TestGrid(WebTestCase):
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
@ -726,6 +885,89 @@ class TestGrid(WebTestCase):
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
##############################
@ -751,14 +993,27 @@ class TestGrid(WebTestCase):
# 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()
visible = grid.get_visible_data()
# 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 = [
@ -827,6 +1082,21 @@ class TestGrid(WebTestCase):
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 = [