2026 lines
		
	
	
	
		
			75 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			2026 lines
		
	
	
	
		
			75 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8; -*-
 | |
| 
 | |
| import datetime
 | |
| import decimal
 | |
| from collections import OrderedDict
 | |
| from enum import Enum
 | |
| from unittest import TestCase
 | |
| from unittest.mock import patch, MagicMock
 | |
| 
 | |
| import sqlalchemy as sa
 | |
| 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.grids.filters import (
 | |
|     GridFilter,
 | |
|     StringAlchemyFilter,
 | |
|     default_sqlalchemy_filters,
 | |
| )
 | |
| from wuttaweb.util import FieldList
 | |
| from wuttaweb.forms import Form
 | |
| from wuttaweb.testing import WebTestCase
 | |
| 
 | |
| 
 | |
| class TestGrid(WebTestCase):
 | |
| 
 | |
|     def make_grid(self, request=None, **kwargs):
 | |
|         return mod.Grid(request or self.request, **kwargs)
 | |
| 
 | |
|     def test_constructor(self):
 | |
| 
 | |
|         # empty
 | |
|         grid = self.make_grid()
 | |
|         self.assertIsNone(grid.key)
 | |
|         self.assertEqual(grid.columns, [])
 | |
|         self.assertIsNone(grid.data)
 | |
| 
 | |
|         # now with columns
 | |
|         grid = self.make_grid(columns=["foo", "bar"])
 | |
|         self.assertIsInstance(grid.columns, FieldList)
 | |
|         self.assertEqual(grid.columns, ["foo", "bar"])
 | |
| 
 | |
|     def test_constructor_sorting(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         # defaults, not sortable
 | |
|         grid = self.make_grid()
 | |
|         self.assertFalse(grid.sortable)
 | |
|         self.assertTrue(grid.sort_on_backend)
 | |
|         self.assertEqual(grid.sorters, {})
 | |
|         self.assertEqual(grid.sort_defaults, [])
 | |
| 
 | |
|         # defaults, sortable
 | |
|         grid = self.make_grid(sortable=True)
 | |
|         self.assertTrue(grid.sortable)
 | |
|         self.assertTrue(grid.sort_on_backend)
 | |
|         self.assertEqual(grid.sorters, {})
 | |
|         self.assertEqual(grid.sort_defaults, [])
 | |
| 
 | |
|         # sorters may be pre-populated
 | |
|         grid = self.make_grid(model_class=model.Setting, sortable=True)
 | |
|         self.assertEqual(len(grid.sorters), 2)
 | |
|         self.assertIn("name", grid.sorters)
 | |
|         self.assertIn("value", grid.sorters)
 | |
|         self.assertEqual(grid.sort_defaults, [])
 | |
| 
 | |
|         # sort defaults as str
 | |
|         grid = self.make_grid(
 | |
|             model_class=model.Setting, sortable=True, sort_defaults="name"
 | |
|         )
 | |
|         self.assertEqual(grid.sort_defaults, [mod.SortInfo("name", "asc")])
 | |
| 
 | |
|         # sort defaults as tuple
 | |
|         grid = self.make_grid(
 | |
|             model_class=model.Setting, sortable=True, sort_defaults=("name", "desc")
 | |
|         )
 | |
|         self.assertEqual(grid.sort_defaults, [mod.SortInfo("name", "desc")])
 | |
| 
 | |
|         # sort defaults as list w/ single tuple
 | |
|         grid = self.make_grid(
 | |
|             model_class=model.Setting, sortable=True, sort_defaults=[("name", "desc")]
 | |
|         )
 | |
|         self.assertEqual(grid.sort_defaults, [mod.SortInfo("name", "desc")])
 | |
| 
 | |
|         # multi-column defaults
 | |
|         grid = self.make_grid(
 | |
|             model_class=model.Setting,
 | |
|             sortable=True,
 | |
|             sort_multiple=True,
 | |
|             sort_defaults=[("name", "desc"), ("value", "asc")],
 | |
|         )
 | |
|         self.assertTrue(grid.sort_multiple)
 | |
|         self.assertEqual(
 | |
|             grid.sort_defaults,
 | |
|             [mod.SortInfo("name", "desc"), mod.SortInfo("value", "asc")],
 | |
|         )
 | |
| 
 | |
|         # multi-column sort disabled for oruga
 | |
|         self.request.use_oruga = True
 | |
|         grid = self.make_grid(
 | |
|             model_class=model.Setting, sortable=True, sort_multiple=True
 | |
|         )
 | |
|         self.assertFalse(grid.sort_multiple)
 | |
| 
 | |
|     def test_constructor_filtering(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         # defaults, not filterable
 | |
|         grid = self.make_grid()
 | |
|         self.assertFalse(grid.filterable)
 | |
|         self.assertEqual(grid.filters, {})
 | |
| 
 | |
|         # defaults, filterable
 | |
|         grid = self.make_grid(filterable=True)
 | |
|         self.assertTrue(grid.filterable)
 | |
|         self.assertEqual(grid.filters, {})
 | |
| 
 | |
|         # filters may be pre-populated
 | |
|         with patch.object(mod.Grid, "make_filter", return_value=42):
 | |
|             grid = self.make_grid(model_class=model.Setting, filterable=True)
 | |
|             self.assertEqual(len(grid.filters), 2)
 | |
|             self.assertIn("name", grid.filters)
 | |
|             self.assertIn("value", grid.filters)
 | |
| 
 | |
|         # can specify filters
 | |
|         grid = self.make_grid(
 | |
|             model_class=model.Setting, filterable=True, filters={"name": 42}
 | |
|         )
 | |
|         self.assertTrue(grid.filterable)
 | |
|         self.assertEqual(grid.filters, {"name": 42})
 | |
| 
 | |
|     def test_vue_tagname(self):
 | |
|         grid = self.make_grid()
 | |
|         self.assertEqual(grid.vue_tagname, "wutta-grid")
 | |
| 
 | |
|     def test_vue_component(self):
 | |
|         grid = self.make_grid()
 | |
|         self.assertEqual(grid.vue_component, "WuttaGrid")
 | |
| 
 | |
|     def test_get_columns(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         # empty
 | |
|         grid = self.make_grid()
 | |
|         self.assertEqual(grid.columns, [])
 | |
|         self.assertEqual(grid.get_columns(), [])
 | |
| 
 | |
|         # explicit
 | |
|         grid = self.make_grid(columns=["foo", "bar"])
 | |
|         self.assertEqual(grid.columns, ["foo", "bar"])
 | |
|         self.assertEqual(grid.get_columns(), ["foo", "bar"])
 | |
| 
 | |
|         # derived from model
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         self.assertEqual(grid.columns, ["name", "value"])
 | |
|         self.assertEqual(grid.get_columns(), ["name", "value"])
 | |
| 
 | |
|     def test_append(self):
 | |
|         grid = self.make_grid(columns=["one", "two"])
 | |
|         self.assertEqual(grid.columns, ["one", "two"])
 | |
|         grid.append("one", "two", "three")
 | |
|         self.assertEqual(grid.columns, ["one", "two", "three"])
 | |
| 
 | |
|     def test_remove(self):
 | |
|         grid = self.make_grid(columns=["one", "two", "three", "four"])
 | |
|         self.assertEqual(grid.columns, ["one", "two", "three", "four"])
 | |
|         grid.remove("two", "three")
 | |
|         self.assertEqual(grid.columns, ["one", "four"])
 | |
| 
 | |
|     def test_set_label(self):
 | |
|         model = self.app.model
 | |
|         with patch.object(mod.Grid, "make_filter"):
 | |
|             # nb. filters are MagicMock instances
 | |
|             grid = self.make_grid(model_class=model.Setting, filterable=True)
 | |
|         self.assertEqual(grid.labels, {})
 | |
| 
 | |
|         # basic
 | |
|         grid.set_label("name", "NAME COL")
 | |
|         self.assertEqual(grid.labels["name"], "NAME COL")
 | |
| 
 | |
|         # can replace label
 | |
|         grid.set_label("name", "Different")
 | |
|         self.assertEqual(grid.labels["name"], "Different")
 | |
|         self.assertEqual(grid.get_label("name"), "Different")
 | |
| 
 | |
|         # can update only column, not filter
 | |
|         self.assertEqual(grid.labels, {"name": "Different"})
 | |
|         self.assertIn("name", grid.filters)
 | |
|         self.assertEqual(grid.filters["name"].label, "Different")
 | |
|         grid.set_label("name", "COLUMN ONLY", column_only=True)
 | |
|         self.assertEqual(grid.get_label("name"), "COLUMN ONLY")
 | |
|         self.assertEqual(grid.filters["name"].label, "Different")
 | |
| 
 | |
|     def test_get_label(self):
 | |
|         grid = self.make_grid(columns=["foo", "bar"])
 | |
|         self.assertEqual(grid.labels, {})
 | |
| 
 | |
|         # default derived from key
 | |
|         self.assertEqual(grid.get_label("foo"), "Foo")
 | |
| 
 | |
|         # can override
 | |
|         grid.set_label("foo", "Different")
 | |
|         self.assertEqual(grid.get_label("foo"), "Different")
 | |
| 
 | |
|     def test_set_renderer(self):
 | |
|         grid = self.make_grid(columns=["foo", "bar"])
 | |
|         self.assertEqual(grid.renderers, {})
 | |
| 
 | |
|         def render1(record, key, value):
 | |
|             pass
 | |
| 
 | |
|         # basic
 | |
|         grid.set_renderer("foo", render1)
 | |
|         self.assertIs(grid.renderers["foo"], render1)
 | |
| 
 | |
|         def render2(record, key, value, extra=None):
 | |
|             return extra
 | |
| 
 | |
|         # can pass kwargs to get a partial
 | |
|         grid.set_renderer("foo", render2, extra=42)
 | |
|         self.assertIsNot(grid.renderers["foo"], render2)
 | |
|         self.assertEqual(grid.renderers["foo"](None, None, None), 42)
 | |
| 
 | |
|         # can use built-in string shortcut
 | |
|         grid.set_renderer("foo", "quantity")
 | |
|         obj = MagicMock(foo=42.00)
 | |
|         self.assertEqual(grid.renderers["foo"](obj, "foo", 42.00), "42")
 | |
| 
 | |
|     def test_set_default_renderers(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         # no defaults for "plain" schema
 | |
|         grid = self.make_grid(columns=["foo", "bar"])
 | |
|         self.assertEqual(grid.renderers, {})
 | |
| 
 | |
|         # no defaults for "plain" mapped class
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         self.assertEqual(grid.renderers, {})
 | |
| 
 | |
|         def myrender(obj, key, value):
 | |
|             return value
 | |
| 
 | |
|         # renderer set for datetime mapped field
 | |
|         grid = self.make_grid(model_class=model.Upgrade)
 | |
|         self.assertIn("created", grid.renderers)
 | |
|         self.assertIsNot(grid.renderers["created"], myrender)
 | |
| 
 | |
|         # renderer *not* set for datetime, if override present
 | |
|         grid = self.make_grid(
 | |
|             model_class=model.Upgrade, renderers={"created": myrender}
 | |
|         )
 | |
|         self.assertIn("created", grid.renderers)
 | |
|         self.assertIs(grid.renderers["created"], myrender)
 | |
| 
 | |
|         # renderer set for boolean mapped field
 | |
|         grid = self.make_grid(model_class=model.Upgrade)
 | |
|         self.assertIn("executing", grid.renderers)
 | |
|         self.assertIsNot(grid.renderers["executing"], myrender)
 | |
| 
 | |
|         # renderer *not* set for boolean, if override present
 | |
|         grid = self.make_grid(
 | |
|             model_class=model.Upgrade, renderers={"executing": myrender}
 | |
|         )
 | |
|         self.assertIn("executing", grid.renderers)
 | |
|         self.assertIs(grid.renderers["executing"], myrender)
 | |
| 
 | |
|         # nb. as of writing we have no Date columns in default schema,
 | |
|         # so must invent one to test that type
 | |
|         class SomeFoolery(model.Base):
 | |
|             __tablename__ = "somefoolery"
 | |
|             id = sa.Column(sa.Integer(), primary_key=True)
 | |
|             created = sa.Column(sa.Date())
 | |
| 
 | |
|         # renderer set for date mapped field
 | |
|         grid = self.make_grid(model_class=SomeFoolery)
 | |
|         self.assertIn("created", grid.renderers)
 | |
|         self.assertIsNot(grid.renderers["created"], myrender)
 | |
| 
 | |
|     def test_set_enum(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         class MockEnum(Enum):
 | |
|             FOO = "foo"
 | |
|             BAR = "bar"
 | |
| 
 | |
|         # no enums by default
 | |
|         grid = self.make_grid(columns=["foo", "bar"])
 | |
|         self.assertEqual(grid.enums, {})
 | |
| 
 | |
|         # enum is set, but not filter choices
 | |
|         grid = self.make_grid(
 | |
|             columns=["foo", "bar"], filterable=False, enums={"foo": MockEnum}
 | |
|         )
 | |
|         self.assertIs(grid.enums["foo"], MockEnum)
 | |
|         self.assertEqual(grid.filters, {})
 | |
| 
 | |
|         # both enum and filter choices are set
 | |
|         grid = self.make_grid(
 | |
|             model_class=model.Setting, filterable=True, enums={"name": MockEnum}
 | |
|         )
 | |
|         self.assertIs(grid.enums["name"], MockEnum)
 | |
|         self.assertIn("name", grid.filters)
 | |
|         self.assertIn("value", grid.filters)
 | |
|         self.assertEqual(
 | |
|             grid.filters["name"].choices,
 | |
|             OrderedDict(
 | |
|                 [
 | |
|                     ("FOO", "foo"),
 | |
|                     ("BAR", "bar"),
 | |
|                 ]
 | |
|             ),
 | |
|         )
 | |
| 
 | |
|     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_hidden_columns(self):
 | |
|         grid = self.make_grid(columns=["foo", "bar"])
 | |
|         self.assertEqual(grid.hidden_columns, [])
 | |
|         self.assertFalse(grid.is_hidden("foo"))
 | |
| 
 | |
|         grid.set_hidden("foo")
 | |
|         self.assertEqual(grid.hidden_columns, ["foo"])
 | |
|         self.assertTrue(grid.is_hidden("foo"))
 | |
|         self.assertFalse(grid.is_hidden("bar"))
 | |
| 
 | |
|         grid.set_hidden("bar")
 | |
|         self.assertEqual(grid.hidden_columns, ["foo", "bar"])
 | |
|         self.assertTrue(grid.is_hidden("foo"))
 | |
|         self.assertTrue(grid.is_hidden("bar"))
 | |
| 
 | |
|         grid.set_hidden("foo", False)
 | |
|         self.assertEqual(grid.hidden_columns, ["bar"])
 | |
|         self.assertFalse(grid.is_hidden("foo"))
 | |
|         self.assertTrue(grid.is_hidden("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_set_tools(self):
 | |
|         grid = self.make_grid()
 | |
|         self.assertEqual(grid.tools, {})
 | |
| 
 | |
|         # null
 | |
|         grid.set_tools(None)
 | |
|         self.assertEqual(grid.tools, {})
 | |
| 
 | |
|         # empty
 | |
|         grid.set_tools({})
 | |
|         self.assertEqual(grid.tools, {})
 | |
| 
 | |
|         # full dict is replaced
 | |
|         grid.tools = {"foo": "bar"}
 | |
|         self.assertEqual(grid.tools, {"foo": "bar"})
 | |
|         grid.set_tools({"bar": "baz"})
 | |
|         self.assertEqual(grid.tools, {"bar": "baz"})
 | |
| 
 | |
|         # can specify as list of html elements
 | |
|         grid.set_tools(["foo", "bar"])
 | |
|         self.assertEqual(len(grid.tools), 2)
 | |
|         self.assertEqual(list(grid.tools.values()), ["foo", "bar"])
 | |
| 
 | |
|     def test_add_tool(self):
 | |
|         grid = self.make_grid()
 | |
|         self.assertEqual(grid.tools, {})
 | |
| 
 | |
|         # with key
 | |
|         grid.add_tool("foo", key="foo")
 | |
|         self.assertEqual(grid.tools, {"foo": "foo"})
 | |
| 
 | |
|         # without key
 | |
|         grid.add_tool("bar")
 | |
|         self.assertEqual(len(grid.tools), 2)
 | |
|         self.assertEqual(list(grid.tools.values()), ["foo", "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.assertIsNone(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.assertIsNone(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.assertIsNone(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.assertIsNone(grid.active_sorters)
 | |
|         grid.load_settings()
 | |
|         self.assertEqual(grid.active_sorters, [{"key": "name", "dir": "desc"}])
 | |
| 
 | |
|         # filter settings are loaded, applied, saved
 | |
|         grid = self.make_grid(
 | |
|             key="settings", model_class=model.Setting, filterable=True
 | |
|         )
 | |
|         self.assertEqual(len(grid.filters), 2)
 | |
|         self.assertEqual(len(grid.active_filters), 0)
 | |
|         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.assertEqual(len(grid.active_filters), 0)
 | |
|         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")
 | |
| 
 | |
|         # can reset view to defaults
 | |
|         self.request.GET = {"reset-view": "true"}
 | |
|         grid.load_settings()
 | |
|         self.assertEqual(grid.active_filters, [])
 | |
|         self.assertIsNone(grid.filters["name"].value)
 | |
| 
 | |
|     def test_request_has_settings(self):
 | |
|         model = self.app.model
 | |
|         grid = self.make_grid(key="settings", model_class=model.Setting)
 | |
| 
 | |
|         # paging
 | |
|         self.assertFalse(grid.request_has_settings("page"))
 | |
|         with patch.object(grid, "paginated", new=True):
 | |
|             with patch.object(self.request, "GET", new={"pagesize": "20"}):
 | |
|                 self.assertTrue(grid.request_has_settings("page"))
 | |
|             with patch.object(self.request, "GET", new={"page": "1"}):
 | |
|                 self.assertTrue(grid.request_has_settings("page"))
 | |
| 
 | |
|         # sorting
 | |
|         self.assertFalse(grid.request_has_settings("sort"))
 | |
|         with patch.object(grid, "sortable", new=True):
 | |
|             with patch.object(self.request, "GET", new={"sort1key": "name"}):
 | |
|                 self.assertTrue(grid.request_has_settings("sort"))
 | |
| 
 | |
|         # filtering
 | |
|         grid = self.make_grid(
 | |
|             key="settings", model_class=model.Setting, filterable=True
 | |
|         )
 | |
|         self.assertFalse(grid.request_has_settings("filter"))
 | |
|         with patch.object(grid, "filterable", new=True):
 | |
|             with patch.object(
 | |
|                 self.request, "GET", new={"name": "john", "name.verb": "contains"}
 | |
|             ):
 | |
|                 self.assertTrue(grid.request_has_settings("filter"))
 | |
|         with patch.object(self.request, "GET", new={"filter": "1"}):
 | |
|             self.assertTrue(grid.request_has_settings("filter"))
 | |
| 
 | |
|     def test_get_setting(self):
 | |
|         grid = self.make_grid(key="foo")
 | |
|         settings = {}
 | |
| 
 | |
|         # default is null
 | |
|         value = grid.get_setting(settings, "pagesize")
 | |
|         self.assertIsNone(value)
 | |
| 
 | |
|         # can read value from user session
 | |
|         self.request.session["grid.foo.pagesize"] = 15
 | |
|         value = grid.get_setting(settings, "pagesize", src="session")
 | |
|         self.assertEqual(value, 15)
 | |
| 
 | |
|         # string value not normalized
 | |
|         self.request.session["grid.foo.pagesize"] = "15"
 | |
|         value = grid.get_setting(settings, "pagesize", src="session")
 | |
|         self.assertEqual(value, "15")
 | |
|         self.assertNotEqual(value, 15)
 | |
| 
 | |
|         # but can be normalized
 | |
|         self.request.session["grid.foo.pagesize"] = "15"
 | |
|         value = grid.get_setting(settings, "pagesize", src="session", normalize=int)
 | |
|         self.assertEqual(value, 15)
 | |
| 
 | |
|         # can read value from request
 | |
|         self.request.GET = {"pagesize": "25"}
 | |
|         value = grid.get_setting(settings, "pagesize", src="request", normalize=int)
 | |
|         self.assertEqual(value, 25)
 | |
| 
 | |
|         # null when normalization fails
 | |
|         self.request.GET = {"pagesize": "invalid"}
 | |
|         value = grid.get_setting(settings, "pagesize", src="request", normalize=int)
 | |
|         self.assertIsNone(value)
 | |
| 
 | |
|         # reset
 | |
|         del self.request.session["grid.foo.pagesize"]
 | |
|         self.request.GET = {}
 | |
| 
 | |
|         # value can come from provided settings
 | |
|         settings["pagesize"] = "35"
 | |
|         value = grid.get_setting(settings, "pagesize", src="session", normalize=int)
 | |
|         self.assertEqual(value, 35)
 | |
| 
 | |
|     def test_update_filter_settings(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         # nothing happens if not filterable
 | |
|         grid = self.make_grid(key="settings", model_class=model.Setting)
 | |
|         settings = {}
 | |
|         self.request.session["grid.settings.filter.name.active"] = True
 | |
|         self.request.session["grid.settings.filter.name.verb"] = "contains"
 | |
|         self.request.session["grid.settings.filter.name.value"] = "john"
 | |
|         grid.update_filter_settings(settings, src="session")
 | |
|         self.assertEqual(settings, {})
 | |
| 
 | |
|         # nb. now use a filterable grid
 | |
|         grid = self.make_grid(
 | |
|             key="settings", model_class=model.Setting, filterable=True
 | |
|         )
 | |
| 
 | |
|         # settings are updated from session
 | |
|         settings = {}
 | |
|         self.request.session["grid.settings.filter.name.active"] = True
 | |
|         self.request.session["grid.settings.filter.name.verb"] = "contains"
 | |
|         self.request.session["grid.settings.filter.name.value"] = "john"
 | |
|         grid.update_filter_settings(settings, src="session")
 | |
|         self.assertTrue(settings["filter.name.active"])
 | |
|         self.assertEqual(settings["filter.name.verb"], "contains")
 | |
|         self.assertEqual(settings["filter.name.value"], "john")
 | |
| 
 | |
|         # settings are updated from request
 | |
|         self.request.GET = {"value": "sally", "value.verb": "contains"}
 | |
|         grid.update_filter_settings(settings, src="request")
 | |
|         self.assertFalse(settings["filter.name.active"])
 | |
|         self.assertTrue(settings["filter.value.active"])
 | |
|         self.assertEqual(settings["filter.value.verb"], "contains")
 | |
|         self.assertEqual(settings["filter.value.value"], "sally")
 | |
| 
 | |
|     def test_update_sort_settings(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         # nothing happens if not sortable
 | |
|         grid = self.make_grid(key="settings", model_class=model.Setting)
 | |
|         settings = {"sorters.length": 0}
 | |
|         self.request.session["grid.settings.sorters.length"] = 1
 | |
|         self.request.session["grid.settings.sorters.1.key"] = "name"
 | |
|         self.request.session["grid.settings.sorters.1.dir"] = "asc"
 | |
|         grid.update_sort_settings(settings, src="session")
 | |
|         self.assertEqual(settings["sorters.length"], 0)
 | |
| 
 | |
|         # nb. now use a sortable grid
 | |
|         grid = self.make_grid(
 | |
|             key="settings",
 | |
|             model_class=model.Setting,
 | |
|             sortable=True,
 | |
|             sort_on_backend=True,
 | |
|         )
 | |
| 
 | |
|         # settings are updated from session
 | |
|         settings = {
 | |
|             "sorters.length": 1,
 | |
|             "sorters.1.key": "name",
 | |
|             "sorters.1.dir": "asc",
 | |
|         }
 | |
|         self.request.session["grid.settings.sorters.length"] = 1
 | |
|         self.request.session["grid.settings.sorters.1.key"] = "name"
 | |
|         self.request.session["grid.settings.sorters.1.dir"] = "asc"
 | |
|         grid.update_sort_settings(settings, src="session")
 | |
|         self.assertEqual(settings["sorters.length"], 1)
 | |
|         self.assertEqual(settings["sorters.1.key"], "name")
 | |
|         self.assertEqual(settings["sorters.1.dir"], "asc")
 | |
| 
 | |
|         # settings are updated from request
 | |
|         self.request.GET = {"sort1key": "value", "sort1dir": "desc"}
 | |
|         grid.update_sort_settings(settings, src="request")
 | |
|         self.assertEqual(settings["sorters.length"], 1)
 | |
|         self.assertEqual(settings["sorters.1.key"], "value")
 | |
|         self.assertEqual(settings["sorters.1.dir"], "desc")
 | |
| 
 | |
|     def test_update_page_settings(self):
 | |
| 
 | |
|         # nothing happens if not paginated
 | |
|         grid = self.make_grid(key="foo")
 | |
|         settings = {"pagesize": 20, "page": 1}
 | |
|         self.request.session["grid.foo.pagesize"] = 10
 | |
|         self.request.session["grid.foo.page"] = 2
 | |
|         grid.update_page_settings(settings)
 | |
|         self.assertEqual(settings["pagesize"], 20)
 | |
|         self.assertEqual(settings["page"], 1)
 | |
| 
 | |
|         # nb. now use a paginated grid
 | |
|         grid = self.make_grid(key="foo", paginated=True, paginate_on_backend=True)
 | |
| 
 | |
|         # settings are updated from session
 | |
|         settings = {"pagesize": 20, "page": 1}
 | |
|         self.request.session["grid.foo.pagesize"] = 10
 | |
|         self.request.session["grid.foo.page"] = 2
 | |
|         grid.update_page_settings(settings)
 | |
|         self.assertEqual(settings["pagesize"], 10)
 | |
|         self.assertEqual(settings["page"], 2)
 | |
| 
 | |
|         # settings are updated from request
 | |
|         self.request.GET = {"pagesize": "15", "page": "4"}
 | |
|         grid.update_page_settings(settings)
 | |
|         self.assertEqual(settings["pagesize"], 15)
 | |
|         self.assertEqual(settings["page"], 4)
 | |
| 
 | |
|     def test_persist_settings(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         # nb. start out with paginated-only grid
 | |
|         grid = self.make_grid(key="foo", paginated=True, paginate_on_backend=True)
 | |
| 
 | |
|         # invalid dest
 | |
|         self.assertRaises(ValueError, grid.persist_settings, {}, dest="doesnotexist")
 | |
| 
 | |
|         # nb. no error if empty settings, but it saves null values
 | |
|         grid.persist_settings({}, dest="session")
 | |
|         self.assertIsNone(self.request.session["grid.foo.page"])
 | |
| 
 | |
|         # provided values are saved
 | |
|         grid.persist_settings({"pagesize": 15, "page": 3}, dest="session")
 | |
|         self.assertEqual(self.request.session["grid.foo.page"], 3)
 | |
| 
 | |
|         # nb. now switch to sortable-only grid
 | |
|         grid = self.make_grid(
 | |
|             key="settings",
 | |
|             model_class=model.Setting,
 | |
|             sortable=True,
 | |
|             sort_on_backend=True,
 | |
|         )
 | |
| 
 | |
|         # no error if empty settings; does not save values
 | |
|         grid.persist_settings({}, dest="session")
 | |
|         self.assertNotIn("grid.settings.sorters.length", self.request.session)
 | |
| 
 | |
|         # provided values are saved
 | |
|         grid.persist_settings(
 | |
|             {
 | |
|                 "sorters.length": 2,
 | |
|                 "sorters.1.key": "name",
 | |
|                 "sorters.1.dir": "desc",
 | |
|                 "sorters.2.key": "value",
 | |
|                 "sorters.2.dir": "asc",
 | |
|             },
 | |
|             dest="session",
 | |
|         )
 | |
|         self.assertEqual(self.request.session["grid.settings.sorters.length"], 2)
 | |
|         self.assertEqual(self.request.session["grid.settings.sorters.1.key"], "name")
 | |
|         self.assertEqual(self.request.session["grid.settings.sorters.1.dir"], "desc")
 | |
|         self.assertEqual(self.request.session["grid.settings.sorters.2.key"], "value")
 | |
|         self.assertEqual(self.request.session["grid.settings.sorters.2.dir"], "asc")
 | |
| 
 | |
|         # old values removed when new are saved
 | |
|         grid.persist_settings(
 | |
|             {"sorters.length": 1, "sorters.1.key": "name", "sorters.1.dir": "desc"},
 | |
|             dest="session",
 | |
|         )
 | |
|         self.assertEqual(self.request.session["grid.settings.sorters.length"], 1)
 | |
|         self.assertEqual(self.request.session["grid.settings.sorters.1.key"], "name")
 | |
|         self.assertEqual(self.request.session["grid.settings.sorters.1.dir"], "desc")
 | |
|         self.assertNotIn("grid.settings.sorters.2.key", self.request.session)
 | |
|         self.assertNotIn("grid.settings.sorters.2.dir", self.request.session)
 | |
| 
 | |
|         # nb. now switch to filterable-only grid
 | |
|         grid = self.make_grid(
 | |
|             key="settings", model_class=model.Setting, filterable=True
 | |
|         )
 | |
|         self.assertIn("name", grid.filters)
 | |
|         self.assertEqual(grid.filters["name"].key, "name")
 | |
| 
 | |
|         # no error if empty settings; does not save values
 | |
|         grid.persist_settings({}, dest="session")
 | |
|         self.assertNotIn("grid.settings.filters.name", self.request.session)
 | |
| 
 | |
|         # provided values are saved
 | |
|         grid.persist_settings(
 | |
|             {
 | |
|                 "filter.name.active": True,
 | |
|                 "filter.name.verb": "contains",
 | |
|                 "filter.name.value": "john",
 | |
|             },
 | |
|             dest="session",
 | |
|         )
 | |
|         self.assertTrue(self.request.session["grid.settings.filter.name.active"])
 | |
|         self.assertEqual(
 | |
|             self.request.session["grid.settings.filter.name.verb"], "contains"
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             self.request.session["grid.settings.filter.name.value"], "john"
 | |
|         )
 | |
| 
 | |
|     ##############################
 | |
|     # sorting methods
 | |
|     ##############################
 | |
| 
 | |
|     def test_make_backend_sorters(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         # default is empty
 | |
|         grid = self.make_grid()
 | |
|         sorters = grid.make_backend_sorters()
 | |
|         self.assertEqual(sorters, {})
 | |
| 
 | |
|         # makes sorters if model class
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         sorters = grid.make_backend_sorters()
 | |
|         self.assertEqual(len(sorters), 2)
 | |
|         self.assertIn("name", sorters)
 | |
|         self.assertIn("value", sorters)
 | |
| 
 | |
|         # does not replace supplied sorters
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         mysorters = {"value": 42}
 | |
|         sorters = grid.make_backend_sorters(mysorters)
 | |
|         self.assertEqual(len(sorters), 2)
 | |
|         self.assertIn("name", sorters)
 | |
|         self.assertIn("value", sorters)
 | |
|         self.assertEqual(sorters["value"], 42)
 | |
|         self.assertEqual(mysorters["value"], 42)
 | |
| 
 | |
|     def test_make_sorter(self):
 | |
|         model = self.app.model
 | |
|         sample_data = [
 | |
|             {"name": "foo1", "value": "ONE"},
 | |
|             {"name": "foo2", "value": "two"},
 | |
|             {"name": "foo3", "value": "three"},
 | |
|             {"name": "foo4", "value": "four"},
 | |
|             {"name": "foo5", "value": "five"},
 | |
|             {"name": "foo6", "value": "six"},
 | |
|             {"name": "foo7", "value": "seven"},
 | |
|             {"name": "foo8", "value": "eight"},
 | |
|             {"name": "foo9", "value": "nine"},
 | |
|         ]
 | |
|         for setting in sample_data:
 | |
|             self.app.save_setting(self.session, setting["name"], setting["value"])
 | |
|         self.session.commit()
 | |
|         sample_query = self.session.query(model.Setting)
 | |
| 
 | |
|         # plain data
 | |
|         grid = self.make_grid(columns=["name", "value"])
 | |
|         sorter = grid.make_sorter("name")
 | |
|         sorted_data = sorter(sample_data, "desc")
 | |
|         self.assertEqual(sorted_data[0], {"name": "foo9", "value": "nine"})
 | |
|         sorted_data = sorter(sample_data, "asc")
 | |
|         self.assertEqual(sorted_data[0], {"name": "foo1", "value": "ONE"})
 | |
| 
 | |
|         # model class, but still plain data
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         sorter = grid.make_sorter("name")
 | |
|         sorted_data = sorter(sample_data, "desc")
 | |
|         self.assertEqual(sorted_data[0], {"name": "foo9", "value": "nine"})
 | |
|         sorted_data = sorter(sample_data, "asc")
 | |
|         self.assertEqual(sorted_data[0], {"name": "foo1", "value": "ONE"})
 | |
| 
 | |
|         # repeat previous test, w/ model property
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         sorter = grid.make_sorter(model.Setting.name)
 | |
|         sorted_data = sorter(sample_data, "desc")
 | |
|         self.assertEqual(sorted_data[0], {"name": "foo9", "value": "nine"})
 | |
|         sorted_data = sorter(sample_data, "asc")
 | |
|         self.assertEqual(sorted_data[0], {"name": "foo1", "value": "ONE"})
 | |
| 
 | |
|         # sqlalchemy query
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         sorter = grid.make_sorter("name")
 | |
|         sorted_query = sorter(sample_query, "desc")
 | |
|         sorted_data = sorted_query.all()
 | |
|         self.assertEqual(dict(sorted_data[0]), {"name": "foo9", "value": "nine"})
 | |
|         sorted_query = sorter(sample_query, "asc")
 | |
|         sorted_data = sorted_query.all()
 | |
|         self.assertEqual(dict(sorted_data[0]), {"name": "foo1", "value": "ONE"})
 | |
| 
 | |
|         # repeat previous test, w/ model property
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         sorter = grid.make_sorter(model.Setting.name)
 | |
|         sorted_query = sorter(sample_query, "desc")
 | |
|         sorted_data = sorted_query.all()
 | |
|         self.assertEqual(dict(sorted_data[0]), {"name": "foo9", "value": "nine"})
 | |
|         sorted_query = sorter(sample_query, "asc")
 | |
|         sorted_data = sorted_query.all()
 | |
|         self.assertEqual(dict(sorted_data[0]), {"name": "foo1", "value": "ONE"})
 | |
| 
 | |
|         # sortfunc for "invalid" column will fail when called; however
 | |
|         # it can work for manual sort w/ custom keyfunc
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         sorter = grid.make_sorter("doesnotexist")
 | |
|         self.assertRaises(TypeError, sorter, sample_query, "desc")
 | |
|         self.assertRaises(KeyError, sorter, sample_data, "desc")
 | |
|         sorter = grid.make_sorter("doesnotexist", keyfunc=lambda obj: obj["name"])
 | |
|         sorted_data = sorter(sample_data, "desc")
 | |
|         self.assertEqual(len(sorted_data), 9)
 | |
|         sorted_data = sorter(sample_data, "asc")
 | |
|         self.assertEqual(len(sorted_data), 9)
 | |
| 
 | |
|         # case folding is on by default
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         sorter = grid.make_sorter("value")
 | |
|         sorted_data = sorter(sample_data, "desc")
 | |
|         self.assertEqual(dict(sorted_data[0]), {"name": "foo2", "value": "two"})
 | |
|         sorted_data = sorter(sample_data, "asc")
 | |
|         self.assertEqual(dict(sorted_data[0]), {"name": "foo8", "value": "eight"})
 | |
| 
 | |
|         # results are different with case folding off
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         sorter = grid.make_sorter("value", foldcase=False)
 | |
|         sorted_data = sorter(sample_data, "desc")
 | |
|         self.assertEqual(dict(sorted_data[0]), {"name": "foo2", "value": "two"})
 | |
|         sorted_data = sorter(sample_data, "asc")
 | |
|         self.assertEqual(dict(sorted_data[0]), {"name": "foo1", "value": "ONE"})
 | |
| 
 | |
|     def test_set_joiner(self):
 | |
| 
 | |
|         # basic
 | |
|         grid = self.make_grid(
 | |
|             columns=["foo", "bar"], sortable=True, sort_on_backend=True
 | |
|         )
 | |
|         self.assertEqual(grid.joiners, {})
 | |
|         grid.set_joiner("foo", 42)
 | |
|         self.assertEqual(grid.joiners, {"foo": 42})
 | |
| 
 | |
|     def test_remove_joiner(self):
 | |
| 
 | |
|         # basic
 | |
|         grid = self.make_grid(
 | |
|             columns=["foo", "bar"],
 | |
|             sortable=True,
 | |
|             sort_on_backend=True,
 | |
|             joiners={"foo": 42},
 | |
|         )
 | |
|         self.assertEqual(grid.joiners, {"foo": 42})
 | |
|         grid.remove_joiner("foo")
 | |
|         self.assertEqual(grid.joiners, {})
 | |
| 
 | |
|     def test_set_sorter(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         # explicit sortfunc
 | |
|         grid = self.make_grid()
 | |
|         self.assertEqual(grid.sorters, {})
 | |
|         sortfunc = lambda data, direction: data
 | |
|         grid.set_sorter("foo", sortfunc)
 | |
|         self.assertIs(grid.sorters["foo"], sortfunc)
 | |
| 
 | |
|         # auto from model property
 | |
|         grid = self.make_grid(model_class=model.Setting, sortable=True, sorters={})
 | |
|         self.assertEqual(grid.sorters, {})
 | |
|         grid.set_sorter("name", model.Setting.name)
 | |
|         self.assertTrue(callable(grid.sorters["name"]))
 | |
| 
 | |
|         # auto from column name
 | |
|         grid = self.make_grid(model_class=model.Setting, sortable=True, sorters={})
 | |
|         self.assertEqual(grid.sorters, {})
 | |
|         grid.set_sorter("name", "name")
 | |
|         self.assertTrue(callable(grid.sorters["name"]))
 | |
| 
 | |
|         # auto from key
 | |
|         grid = self.make_grid(model_class=model.Setting, sortable=True, sorters={})
 | |
|         self.assertEqual(grid.sorters, {})
 | |
|         grid.set_sorter("name")
 | |
|         self.assertTrue(callable(grid.sorters["name"]))
 | |
| 
 | |
|     def test_remove_sorter(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         # basics
 | |
|         grid = self.make_grid(model_class=model.Setting, sortable=True)
 | |
|         self.assertEqual(len(grid.sorters), 2)
 | |
|         self.assertIn("name", grid.sorters)
 | |
|         self.assertIn("value", grid.sorters)
 | |
|         grid.remove_sorter("value")
 | |
|         self.assertNotIn("value", grid.sorters)
 | |
| 
 | |
|     def test_set_sort_defaults(self):
 | |
|         model = self.app.model
 | |
|         grid = self.make_grid(model_class=model.Setting, sortable=True)
 | |
|         self.assertEqual(grid.sort_defaults, [])
 | |
| 
 | |
|         # can set just sortkey
 | |
|         grid.set_sort_defaults("name")
 | |
|         self.assertEqual(grid.sort_defaults, [mod.SortInfo("name", "asc")])
 | |
| 
 | |
|         # can set sortkey, sortdir
 | |
|         grid.set_sort_defaults("name", "desc")
 | |
|         self.assertEqual(grid.sort_defaults, [mod.SortInfo("name", "desc")])
 | |
| 
 | |
|         # can set sortkey, sortdir as tuple
 | |
|         grid.set_sort_defaults(("value", "asc"))
 | |
|         self.assertEqual(grid.sort_defaults, [mod.SortInfo("value", "asc")])
 | |
| 
 | |
|         # can set as list
 | |
|         grid.sort_multiple = True
 | |
|         grid.set_sort_defaults([("value", "asc"), ("name", "desc")])
 | |
|         self.assertEqual(
 | |
|             grid.sort_defaults,
 | |
|             [mod.SortInfo("value", "asc"), mod.SortInfo("name", "desc")],
 | |
|         )
 | |
| 
 | |
|         # list is pruned if multi-sort disabled
 | |
|         grid.sort_multiple = False
 | |
|         grid.set_sort_defaults([("value", "asc"), ("name", "desc")])
 | |
|         self.assertEqual(grid.sort_defaults, [mod.SortInfo("value", "asc")])
 | |
| 
 | |
|         # error if any other single arg
 | |
|         self.assertRaises(ValueError, grid.set_sort_defaults, 42)
 | |
| 
 | |
|         # error if more than 2 args
 | |
|         self.assertRaises(
 | |
|             ValueError, grid.set_sort_defaults, "name", "asc", "value", "desc"
 | |
|         )
 | |
| 
 | |
|     def test_is_sortable(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         # basics, frontend sorting
 | |
|         grid = self.make_grid(
 | |
|             model_class=model.Setting, sortable=True, sort_on_backend=False
 | |
|         )
 | |
|         self.assertTrue(grid.is_sortable("name"))
 | |
|         self.assertTrue(grid.is_sortable("value"))
 | |
|         grid.remove_sorter("value")
 | |
|         # nb. columns are always sortable for frontend, despite remove_sorter()
 | |
|         self.assertTrue(grid.is_sortable("value"))
 | |
|         # nb. when grid is not sortable, no column is either
 | |
|         grid.sortable = False
 | |
|         self.assertFalse(grid.is_sortable("name"))
 | |
| 
 | |
|         # same test but with backend sorting
 | |
|         grid = self.make_grid(
 | |
|             model_class=model.Setting, sortable=True, sort_on_backend=True
 | |
|         )
 | |
|         self.assertTrue(grid.is_sortable("name"))
 | |
|         self.assertTrue(grid.is_sortable("value"))
 | |
|         grid.remove_sorter("value")
 | |
|         self.assertFalse(grid.is_sortable("value"))
 | |
|         # nb. when grid is not sortable, no column is either
 | |
|         grid.sortable = False
 | |
|         self.assertFalse(grid.is_sortable("name"))
 | |
| 
 | |
|     def test_make_backend_filters(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         # default is empty
 | |
|         grid = self.make_grid()
 | |
|         filters = grid.make_backend_filters()
 | |
|         self.assertEqual(filters, {})
 | |
| 
 | |
|         # makes filters if model class
 | |
|         with patch.object(mod.Grid, "make_filter"):
 | |
|             # nb. filters are MagicMock instances
 | |
|             grid = self.make_grid(model_class=model.Setting)
 | |
|             filters = grid.make_backend_filters()
 | |
|         self.assertEqual(len(filters), 2)
 | |
|         self.assertIn("name", filters)
 | |
|         self.assertIn("value", filters)
 | |
| 
 | |
|         # does not replace supplied filters
 | |
|         myfilters = {"value": 42}
 | |
|         with patch.object(mod.Grid, "make_filter"):
 | |
|             # nb. filters are MagicMock instances
 | |
|             grid = self.make_grid(model_class=model.Setting)
 | |
|             filters = grid.make_backend_filters(myfilters)
 | |
|         self.assertEqual(len(filters), 2)
 | |
|         self.assertIn("name", filters)
 | |
|         self.assertIn("value", filters)
 | |
|         self.assertEqual(filters["value"], 42)
 | |
|         self.assertEqual(myfilters["value"], 42)
 | |
| 
 | |
|         # filters for all *true* columns by default, despite grid.columns
 | |
|         with patch.object(mod.Grid, "make_filter"):
 | |
|             # nb. filters are MagicMock instances
 | |
|             grid = self.make_grid(
 | |
|                 model_class=model.User, columns=["username", "person"]
 | |
|             )
 | |
|             filters = grid.make_backend_filters()
 | |
|             self.assertIn("username", filters)
 | |
|             self.assertIn("active", filters)
 | |
|             # nb. relationship not included by default
 | |
|             self.assertNotIn("person", filters)
 | |
|             # nb. uuid fields not included by default
 | |
|             self.assertNotIn("uuid", filters)
 | |
|             self.assertNotIn("person_uuid", filters)
 | |
| 
 | |
|     def test_make_filter(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         # arg is column name
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         filtr = grid.make_filter("name")
 | |
|         self.assertIsInstance(filtr, StringAlchemyFilter)
 | |
| 
 | |
|         # arg is column name, but model class is invalid
 | |
|         grid = self.make_grid(model_class=42)
 | |
|         self.assertRaises(ValueError, grid.make_filter, "name")
 | |
| 
 | |
|         # arg is model property
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         filtr = grid.make_filter(model.Setting.name)
 | |
|         self.assertIsInstance(filtr, StringAlchemyFilter)
 | |
| 
 | |
|         # model property as kwarg
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         filtr = grid.make_filter(None, model_property=model.Setting.name)
 | |
|         self.assertIsInstance(filtr, StringAlchemyFilter)
 | |
| 
 | |
|         # default factory
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         with patch.dict(default_sqlalchemy_filters, {None: GridFilter}, clear=True):
 | |
|             filtr = grid.make_filter(model.Setting.name)
 | |
|         self.assertIsInstance(filtr, GridFilter)
 | |
|         self.assertNotIsInstance(filtr, StringAlchemyFilter)
 | |
| 
 | |
|         # factory override
 | |
|         grid = self.make_grid(model_class=model.Setting)
 | |
|         filtr = grid.make_filter(model.Setting.name, factory=GridFilter)
 | |
|         self.assertIsInstance(filtr, GridFilter)
 | |
|         self.assertNotIsInstance(filtr, StringAlchemyFilter)
 | |
| 
 | |
|     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)
 | |
| 
 | |
|     def test_set_filter_defaults(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         # empty by default
 | |
|         grid = self.make_grid(model_class=model.Setting, filterable=True)
 | |
|         self.assertEqual(grid.filter_defaults, {})
 | |
| 
 | |
|         # can specify via method call
 | |
|         grid.set_filter_defaults(name={"active": True})
 | |
|         self.assertEqual(grid.filter_defaults, {"name": {"active": True}})
 | |
| 
 | |
|         # can specify via constructor
 | |
|         grid = self.make_grid(
 | |
|             model_class=model.Setting,
 | |
|             filterable=True,
 | |
|             filter_defaults={"name": {"active": True}},
 | |
|         )
 | |
|         self.assertEqual(grid.filter_defaults, {"name": {"active": True}})
 | |
| 
 | |
|     ##############################
 | |
|     # data methods
 | |
|     ##############################
 | |
| 
 | |
|     def test_get_visible_data(self):
 | |
|         model = self.app.model
 | |
|         sample_data = [
 | |
|             {"name": "foo1", "value": "ONE"},
 | |
|             {"name": "foo2", "value": "two"},
 | |
|             {"name": "foo3", "value": "three"},
 | |
|             {"name": "foo4", "value": "four"},
 | |
|             {"name": "foo5", "value": "five"},
 | |
|             {"name": "foo6", "value": "six"},
 | |
|             {"name": "foo7", "value": "seven"},
 | |
|             {"name": "foo8", "value": "eight"},
 | |
|             {"name": "foo9", "value": "nine"},
 | |
|         ]
 | |
|         for setting in sample_data:
 | |
|             self.app.save_setting(self.session, setting["name"], setting["value"])
 | |
|         self.session.commit()
 | |
|         sample_query = self.session.query(model.Setting)
 | |
| 
 | |
|         # data is sorted and paginated
 | |
|         grid = self.make_grid(
 | |
|             model_class=model.Setting,
 | |
|             data=sample_query,
 | |
|             filterable=True,
 | |
|             sortable=True,
 | |
|             sort_on_backend=True,
 | |
|             sort_defaults=("name", "desc"),
 | |
|             paginated=True,
 | |
|             paginate_on_backend=True,
 | |
|             pagesize=4,
 | |
|             page=2,
 | |
|         )
 | |
|         grid.load_settings()
 | |
|         # nb. for now the filtering is mocked
 | |
|         with patch.object(grid, "filter_data") as filter_data:
 | |
|             filter_data.side_effect = lambda q: q
 | |
|             visible = grid.get_visible_data()
 | |
|             filter_data.assert_called_once_with(sample_query)
 | |
|         self.assertEqual([s.name for s in visible], ["foo5", "foo4", "foo3", "foo2"])
 | |
| 
 | |
|     def test_filter_data(self):
 | |
|         model = self.app.model
 | |
|         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(
 | |
|             key="settings", model_class=model.Setting, filterable=True
 | |
|         )
 | |
|         self.assertEqual(list(grid.filters), ["name", "value"])
 | |
|         self.assertIsInstance(grid.filters["name"], StringAlchemyFilter)
 | |
|         self.assertIsInstance(grid.filters["value"], StringAlchemyFilter)
 | |
| 
 | |
|         # not filtered by default
 | |
|         grid.load_settings()
 | |
|         self.assertEqual(grid.active_filters, [])
 | |
|         filtered_query = grid.filter_data(sample_query)
 | |
|         self.assertIs(filtered_query, sample_query)
 | |
| 
 | |
|         # can be filtered per session settings
 | |
|         self.request.session["grid.settings.filter.value.active"] = True
 | |
|         self.request.session["grid.settings.filter.value.verb"] = "contains"
 | |
|         self.request.session["grid.settings.filter.value.value"] = "ggg"
 | |
|         grid.load_settings()
 | |
|         self.assertEqual(len(grid.active_filters), 1)
 | |
|         self.assertEqual(grid.active_filters[0].key, "value")
 | |
|         filtered_query = grid.filter_data(sample_query)
 | |
|         self.assertIsInstance(filtered_query, orm.Query)
 | |
|         self.assertIsNot(filtered_query, sample_query)
 | |
|         self.assertEqual(filtered_query.count(), 3)
 | |
| 
 | |
|         # can be filtered per request settings
 | |
|         self.request.GET = {"value": "s", "value.verb": "contains"}
 | |
|         grid.load_settings()
 | |
|         self.assertEqual(len(grid.active_filters), 1)
 | |
|         self.assertEqual(grid.active_filters[0].key, "value")
 | |
|         filtered_query = grid.filter_data(sample_query)
 | |
|         self.assertIsInstance(filtered_query, orm.Query)
 | |
|         self.assertEqual(filtered_query.count(), 2)
 | |
| 
 | |
|         # not filtered if verb is invalid
 | |
|         self.request.GET = {"value": "ggg", "value.verb": "doesnotexist"}
 | |
|         grid.load_settings()
 | |
|         self.assertEqual(len(grid.active_filters), 1)
 | |
|         self.assertEqual(grid.active_filters[0].verb, "doesnotexist")
 | |
|         filtered_query = grid.filter_data(sample_query)
 | |
|         self.assertIs(filtered_query, sample_query)
 | |
|         self.assertEqual(filtered_query.count(), 9)
 | |
| 
 | |
|         # not filtered if error
 | |
|         self.request.GET = {"value": "ggg", "value.verb": "contains"}
 | |
|         grid.load_settings()
 | |
|         self.assertEqual(len(grid.active_filters), 1)
 | |
|         self.assertEqual(grid.active_filters[0].verb, "contains")
 | |
|         filtered_query = grid.filter_data(sample_query)
 | |
|         self.assertIsNot(filtered_query, sample_query)
 | |
|         self.assertEqual(filtered_query.count(), 3)
 | |
|         with patch.object(
 | |
|             grid.active_filters[0], "filter_contains", side_effect=RuntimeError
 | |
|         ):
 | |
|             filtered_query = grid.filter_data(sample_query)
 | |
|         self.assertIs(filtered_query, sample_query)
 | |
|         self.assertEqual(filtered_query.count(), 9)
 | |
| 
 | |
|         # joiner is invoked
 | |
|         self.assertEqual(len(grid.active_filters), 1)
 | |
|         self.assertEqual(grid.active_filters[0].key, "value")
 | |
|         joiner = MagicMock(side_effect=lambda q: q)
 | |
|         grid.joiners = {"value": joiner}
 | |
|         grid.joined = set()
 | |
|         filtered_query = grid.filter_data(sample_query)
 | |
|         joiner.assert_called_once_with(sample_query)
 | |
|         self.assertEqual(filtered_query.count(), 3)
 | |
| 
 | |
|     def test_sort_data(self):
 | |
|         model = self.app.model
 | |
|         sample_data = [
 | |
|             {"name": "foo1", "value": "ONE"},
 | |
|             {"name": "foo2", "value": "two"},
 | |
|             {"name": "foo3", "value": "ggg"},
 | |
|             {"name": "foo4", "value": "ggg"},
 | |
|             {"name": "foo5", "value": "ggg"},
 | |
|             {"name": "foo6", "value": "six"},
 | |
|             {"name": "foo7", "value": "seven"},
 | |
|             {"name": "foo8", "value": "eight"},
 | |
|             {"name": "foo9", "value": "nine"},
 | |
|         ]
 | |
|         for setting in sample_data:
 | |
|             self.app.save_setting(self.session, setting["name"], setting["value"])
 | |
|         self.session.commit()
 | |
|         sample_query = self.session.query(model.Setting)
 | |
| 
 | |
|         grid = self.make_grid(
 | |
|             model_class=model.Setting,
 | |
|             sortable=True,
 | |
|             sort_on_backend=True,
 | |
|             sort_defaults=("name", "desc"),
 | |
|         )
 | |
|         grid.load_settings()
 | |
| 
 | |
|         # can sort a simple list of data
 | |
|         sorted_data = grid.sort_data(sample_data)
 | |
|         self.assertIsInstance(sorted_data, list)
 | |
|         self.assertEqual(len(sorted_data), 9)
 | |
|         self.assertEqual(sorted_data[0]["name"], "foo9")
 | |
|         self.assertEqual(sorted_data[-1]["name"], "foo1")
 | |
| 
 | |
|         # can also sort a data query
 | |
|         sorted_query = grid.sort_data(sample_query)
 | |
|         self.assertIsInstance(sorted_query, orm.Query)
 | |
|         sorted_data = sorted_query.all()
 | |
|         self.assertEqual(len(sorted_data), 9)
 | |
|         self.assertEqual(sorted_data[0]["name"], "foo9")
 | |
|         self.assertEqual(sorted_data[-1]["name"], "foo1")
 | |
| 
 | |
|         # cannot sort data if sorter missing in overrides
 | |
|         sorted_data = grid.sort_data(sample_data, sorters=[])
 | |
|         # nb. sorted data is in same order as original sample (not sorted)
 | |
|         self.assertEqual(sorted_data[0]["name"], "foo1")
 | |
|         self.assertEqual(sorted_data[-1]["name"], "foo9")
 | |
| 
 | |
|         # multi-column sorting for list data
 | |
|         sorted_data = grid.sort_data(
 | |
|             sample_data,
 | |
|             sorters=[{"key": "value", "dir": "asc"}, {"key": "name", "dir": "asc"}],
 | |
|         )
 | |
|         self.assertEqual(dict(sorted_data[0]), {"name": "foo8", "value": "eight"})
 | |
|         self.assertEqual(dict(sorted_data[1]), {"name": "foo3", "value": "ggg"})
 | |
|         self.assertEqual(dict(sorted_data[3]), {"name": "foo5", "value": "ggg"})
 | |
|         self.assertEqual(dict(sorted_data[-1]), {"name": "foo2", "value": "two"})
 | |
| 
 | |
|         # multi-column sorting for query
 | |
|         sorted_query = grid.sort_data(
 | |
|             sample_query,
 | |
|             sorters=[{"key": "value", "dir": "asc"}, {"key": "name", "dir": "asc"}],
 | |
|         )
 | |
|         self.assertEqual(dict(sorted_data[0]), {"name": "foo8", "value": "eight"})
 | |
|         self.assertEqual(dict(sorted_data[1]), {"name": "foo3", "value": "ggg"})
 | |
|         self.assertEqual(dict(sorted_data[3]), {"name": "foo5", "value": "ggg"})
 | |
|         self.assertEqual(dict(sorted_data[-1]), {"name": "foo2", "value": "two"})
 | |
| 
 | |
|         # cannot sort data if sortfunc is missing for column
 | |
|         grid.remove_sorter("name")
 | |
|         sorted_data = grid.sort_data(
 | |
|             sample_data,
 | |
|             sorters=[{"key": "value", "dir": "asc"}, {"key": "name", "dir": "asc"}],
 | |
|         )
 | |
|         # nb. sorted data is in same order as original sample (not sorted)
 | |
|         self.assertEqual(sorted_data[0]["name"], "foo1")
 | |
|         self.assertEqual(sorted_data[-1]["name"], "foo9")
 | |
| 
 | |
|         # now try with a joiner
 | |
|         query = self.session.query(model.User)
 | |
|         grid = self.make_grid(
 | |
|             model_class=model.User,
 | |
|             data=query,
 | |
|             columns=["username", "full_name"],
 | |
|             sortable=True,
 | |
|             sort_on_backend=True,
 | |
|             sort_defaults="full_name",
 | |
|             joiners={
 | |
|                 "full_name": lambda q: q.join(model.Person),
 | |
|             },
 | |
|         )
 | |
|         grid.set_sorter("full_name", model.Person.full_name)
 | |
|         grid.load_settings()
 | |
|         data = grid.get_visible_data()
 | |
|         self.assertIsInstance(data, orm.Query)
 | |
| 
 | |
|     def test_paginate_data(self):
 | |
|         model = self.app.model
 | |
|         sample_data = [
 | |
|             {"name": "foo1", "value": "ONE"},
 | |
|             {"name": "foo2", "value": "two"},
 | |
|             {"name": "foo3", "value": "three"},
 | |
|             {"name": "foo4", "value": "four"},
 | |
|             {"name": "foo5", "value": "five"},
 | |
|             {"name": "foo6", "value": "six"},
 | |
|             {"name": "foo7", "value": "seven"},
 | |
|             {"name": "foo8", "value": "eight"},
 | |
|             {"name": "foo9", "value": "nine"},
 | |
|         ]
 | |
|         for setting in sample_data:
 | |
|             self.app.save_setting(self.session, setting["name"], setting["value"])
 | |
|         self.session.commit()
 | |
|         sample_query = self.session.query(model.Setting)
 | |
| 
 | |
|         # basic list pager
 | |
|         grid = self.make_grid(paginated=True, paginate_on_backend=True)
 | |
|         pager = grid.paginate_data(sample_data)
 | |
|         self.assertIsInstance(pager, Page)
 | |
| 
 | |
|         # basic query pager
 | |
|         grid = self.make_grid(paginated=True, paginate_on_backend=True)
 | |
|         pager = grid.paginate_data(sample_query)
 | |
|         self.assertIsInstance(pager, SqlalchemyOrmPage)
 | |
| 
 | |
|         # page is reset to 1 for empty data
 | |
|         self.request.session["grid.foo.page"] = 2
 | |
|         grid = self.make_grid(key="foo", paginated=True, paginate_on_backend=True)
 | |
|         grid.load_settings()
 | |
|         self.assertEqual(grid.page, 2)
 | |
|         self.assertEqual(self.request.session["grid.foo.page"], 2)
 | |
|         pager = grid.paginate_data(sample_data)
 | |
|         self.assertEqual(pager.page, 1)
 | |
|         self.assertEqual(grid.page, 1)
 | |
|         self.assertEqual(self.request.session["grid.foo.page"], 1)
 | |
| 
 | |
|     ##############################
 | |
|     # rendering methods
 | |
|     ##############################
 | |
| 
 | |
|     def test_render_batch_id(self):
 | |
|         grid = self.make_grid(columns=["foo", "bar"])
 | |
| 
 | |
|         # null
 | |
|         obj = MagicMock(foo=None)
 | |
|         self.assertEqual(grid.render_batch_id(obj, "foo", None), "")
 | |
| 
 | |
|         # int
 | |
|         obj = MagicMock(foo=42)
 | |
|         self.assertEqual(grid.render_batch_id(obj, "foo", 42), "00000042")
 | |
| 
 | |
|     def test_render_boolean(self):
 | |
|         grid = self.make_grid(columns=["foo", "bar"])
 | |
| 
 | |
|         # null
 | |
|         obj = MagicMock(foo=None)
 | |
|         self.assertEqual(grid.render_boolean(obj, "foo", None), "")
 | |
| 
 | |
|         # true
 | |
|         obj = MagicMock(foo=True)
 | |
|         self.assertEqual(grid.render_boolean(obj, "foo", True), "Yes")
 | |
| 
 | |
|         # false
 | |
|         obj = MagicMock(foo=False)
 | |
|         self.assertEqual(grid.render_boolean(obj, "foo", False), "No")
 | |
| 
 | |
|     def test_render_currency(self):
 | |
|         grid = self.make_grid(columns=["foo", "bar"])
 | |
|         obj = MagicMock()
 | |
| 
 | |
|         # null
 | |
|         self.assertEqual(grid.render_currency(obj, "foo", None), "")
 | |
| 
 | |
|         # basic decimal example
 | |
|         value = decimal.Decimal("42.00")
 | |
|         self.assertEqual(grid.render_currency(obj, "foo", value), "$42.00")
 | |
| 
 | |
|         # basic float example
 | |
|         value = 42.00
 | |
|         self.assertEqual(grid.render_currency(obj, "foo", value), "$42.00")
 | |
| 
 | |
|         # decimal places will be rounded
 | |
|         value = decimal.Decimal("42.12345")
 | |
|         self.assertEqual(grid.render_currency(obj, "foo", value), "$42.12")
 | |
| 
 | |
|         # negative numbers get parens
 | |
|         value = decimal.Decimal("-42.42")
 | |
|         self.assertEqual(grid.render_currency(obj, "foo", value), "($42.42)")
 | |
| 
 | |
|     def test_render_enum(self):
 | |
|         enum = self.app.enum
 | |
|         grid = self.make_grid(columns=["foo", "bar"])
 | |
|         obj = {"status": None}
 | |
| 
 | |
|         # null
 | |
|         value = grid.render_enum(obj, "status", None, enum=enum.UpgradeStatus)
 | |
|         self.assertIsNone(value)
 | |
| 
 | |
|         # normal
 | |
|         obj["status"] = enum.UpgradeStatus.SUCCESS
 | |
|         value = grid.render_enum(obj, "status", "SUCCESS", enum=enum.UpgradeStatus)
 | |
|         self.assertEqual(value, "success")
 | |
| 
 | |
|     def test_render_percent(self):
 | |
|         grid = self.make_grid(columns=["foo", "bar"])
 | |
|         obj = MagicMock()
 | |
| 
 | |
|         # null
 | |
|         self.assertEqual(grid.render_percent(obj, "foo", None), "")
 | |
| 
 | |
|         # typical
 | |
|         self.assertEqual(grid.render_percent(obj, "foo", 12.3419), "12.34 %")
 | |
| 
 | |
|         # more decimal places
 | |
|         self.assertEqual(
 | |
|             grid.render_percent(obj, "foo", 12.3419, decimals=3), "12.342 %"
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             grid.render_percent(obj, "foo", 12.3419, decimals=4), "12.3419 %"
 | |
|         )
 | |
| 
 | |
|         # negative
 | |
|         self.assertEqual(grid.render_percent(obj, "foo", -12.3419), "(12.34 %)")
 | |
|         self.assertEqual(
 | |
|             grid.render_percent(obj, "foo", -12.3419, decimals=3), "(12.342 %)"
 | |
|         )
 | |
| 
 | |
|     def test_render_quantity(self):
 | |
|         grid = self.make_grid(columns=["foo", "bar"])
 | |
|         obj = MagicMock()
 | |
| 
 | |
|         # null
 | |
|         self.assertEqual(grid.render_quantity(obj, "foo", None), "")
 | |
| 
 | |
|         # integer decimals become integers
 | |
|         value = decimal.Decimal("1.000")
 | |
|         self.assertEqual(grid.render_quantity(obj, "foo", value), "1")
 | |
| 
 | |
|         # but decimal places are preserved
 | |
|         value = decimal.Decimal("1.234")
 | |
|         self.assertEqual(grid.render_quantity(obj, "foo", value), "1.234")
 | |
| 
 | |
|         # zero is *not* empty string (with this renderer)
 | |
|         self.assertEqual(grid.render_quantity(obj, "foo", 0), "0")
 | |
| 
 | |
|     def test_render_date(self):
 | |
|         grid = self.make_grid(columns=["foo", "bar"])
 | |
| 
 | |
|         # null
 | |
|         obj = MagicMock(dt=None)
 | |
|         result = grid.render_date(obj, "dt", None)
 | |
|         self.assertEqual(result, "")
 | |
| 
 | |
|         # typical
 | |
|         dt = datetime.date(2025, 1, 13)
 | |
|         obj = MagicMock(dt=dt)
 | |
|         result = grid.render_date(obj, "dt", str(dt))
 | |
|         self.assertEqual(result, "2025-01-13")
 | |
| 
 | |
|     def test_render_datetime(self):
 | |
|         grid = self.make_grid(columns=["foo", "bar"])
 | |
| 
 | |
|         obj = MagicMock(dt=None)
 | |
|         result = grid.render_datetime(obj, "dt", None)
 | |
|         self.assertEqual(result, "")
 | |
| 
 | |
|         dt = datetime.datetime(2024, 12, 12, 13, 44, tzinfo=datetime.timezone.utc)
 | |
|         obj = MagicMock(dt=dt)
 | |
|         result = grid.render_datetime(obj, "dt", str(dt))
 | |
|         self.assertEqual(result, "2024-12-12 13:44+0000")
 | |
| 
 | |
|     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_context), 0)
 | |
|         html = grid.render_table_element(form)
 | |
|         self.assertEqual(len(form.grid_vue_context), 1)
 | |
|         self.assertIn("foobar", form.grid_vue_context)
 | |
| 
 | |
|     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_first_sorter(self):
 | |
| 
 | |
|         # empty by default
 | |
|         grid = self.make_grid(key="foo", sortable=True)
 | |
|         sorter = grid.get_vue_first_sorter()
 | |
|         self.assertIsNone(sorter)
 | |
| 
 | |
|         # will use first element from sort_defaults when applicable...
 | |
| 
 | |
|         # basic
 | |
|         grid = self.make_grid(key="foo", sortable=True, sort_defaults="name")
 | |
|         sorter = grid.get_vue_first_sorter()
 | |
|         self.assertEqual(sorter, ["name", "asc"])
 | |
| 
 | |
|         # descending
 | |
|         grid = self.make_grid(key="foo", sortable=True, sort_defaults=("name", "desc"))
 | |
|         sorter = grid.get_vue_first_sorter()
 | |
|         self.assertEqual(sorter, ["name", "desc"])
 | |
| 
 | |
|         # multiple
 | |
|         grid = self.make_grid(
 | |
|             key="foo", sortable=True, sort_defaults=[("key", "asc"), ("name", "asc")]
 | |
|         )
 | |
|         sorter = grid.get_vue_first_sorter()
 | |
|         self.assertEqual(sorter, ["key", "asc"])
 | |
| 
 | |
|         # will use first element from active_sorters when applicable...
 | |
| 
 | |
|         # basic
 | |
|         grid = self.make_grid(key="foo", sortable=True)
 | |
|         grid.active_sorters = [{"key": "name", "dir": "asc"}]
 | |
|         sorter = grid.get_vue_first_sorter()
 | |
|         self.assertEqual(sorter, ["name", "asc"])
 | |
| 
 | |
|         # descending
 | |
|         grid = self.make_grid(key="foo", sortable=True)
 | |
|         grid.active_sorters = [{"key": "name", "dir": "desc"}]
 | |
|         sorter = grid.get_vue_first_sorter()
 | |
|         self.assertEqual(sorter, ["name", "desc"])
 | |
| 
 | |
|         # multiple
 | |
|         grid = self.make_grid(key="foo", sortable=True)
 | |
|         grid.active_sorters = [
 | |
|             {"key": "key", "dir": "asc"},
 | |
|             {"key": "name", "dir": "asc"},
 | |
|         ]
 | |
|         sorter = grid.get_vue_first_sorter()
 | |
|         self.assertEqual(sorter, ["key", "asc"])
 | |
| 
 | |
|     def test_get_vue_filters(self):
 | |
|         model = self.app.model
 | |
| 
 | |
|         # basic
 | |
|         grid = self.make_grid(
 | |
|             key="settings", model_class=model.Setting, filterable=True
 | |
|         )
 | |
|         grid.load_settings()
 | |
|         filters = grid.get_vue_filters()
 | |
|         self.assertEqual(len(filters), 2)
 | |
|         name, value = filters
 | |
|         self.assertEqual(name["choices"], [])
 | |
|         self.assertEqual(name["choice_labels"], {})
 | |
|         self.assertEqual(value["choices"], [])
 | |
|         self.assertEqual(value["choice_labels"], {})
 | |
| 
 | |
|         class MockEnum(Enum):
 | |
|             FOO = "foo"
 | |
|             BAR = "bar"
 | |
| 
 | |
|         # with filter choices
 | |
|         grid = self.make_grid(
 | |
|             key="settings",
 | |
|             model_class=model.Setting,
 | |
|             filterable=True,
 | |
|             enums={"name": MockEnum},
 | |
|         )
 | |
|         grid.load_settings()
 | |
|         filters = grid.get_vue_filters()
 | |
|         self.assertEqual(len(filters), 2)
 | |
|         name, value = filters
 | |
|         self.assertEqual(name["choices"], ["FOO", "BAR"])
 | |
|         self.assertEqual(name["choice_labels"], {"FOO": "foo", "BAR": "bar"})
 | |
|         self.assertEqual(value["choices"], [])
 | |
|         self.assertEqual(value["choice_labels"], {})
 | |
| 
 | |
|     def test_object_to_dict(self):
 | |
|         grid = self.make_grid()
 | |
|         setting = {"name": "foo", "value": "bar"}
 | |
| 
 | |
|         # new dict but with same values
 | |
|         dct = grid.object_to_dict(setting)
 | |
|         self.assertIsInstance(dct, dict)
 | |
|         self.assertIsNot(dct, setting)
 | |
|         self.assertEqual(dct, setting)
 | |
| 
 | |
|         # random object, not iterable
 | |
|         class MockSetting:
 | |
|             def __init__(self, **kw):
 | |
|                 self.__dict__.update(kw)
 | |
| 
 | |
|         mock = MockSetting(**setting)
 | |
|         dct = grid.object_to_dict(mock)
 | |
|         self.assertIsInstance(dct, dict)
 | |
|         self.assertEqual(dct, setting)
 | |
| 
 | |
|     def test_get_vue_context(self):
 | |
| 
 | |
|         # empty if no columns defined
 | |
|         grid = self.make_grid()
 | |
|         context = grid.get_vue_context()
 | |
|         self.assertEqual(context, {"data": [], "row_classes": {}})
 | |
| 
 | |
|         # typical data is a list
 | |
|         mydata = [
 | |
|             {"foo": "bar"},
 | |
|         ]
 | |
|         grid = self.make_grid(columns=["foo"], data=mydata)
 | |
|         context = grid.get_vue_context()
 | |
|         self.assertEqual(context, {"data": [{"foo": "bar"}], "row_classes": {}})
 | |
| 
 | |
|         # if grid has actions, that list may be supplemented
 | |
|         grid.actions.append(mod.GridAction(self.request, "view", url="/blarg"))
 | |
|         context = grid.get_vue_context()
 | |
|         self.assertIsNot(context["data"], mydata)
 | |
|         self.assertEqual(
 | |
|             context,
 | |
|             {"data": [{"foo": "bar", "_action_url_view": "/blarg"}], "row_classes": {}},
 | |
|         )
 | |
| 
 | |
|         # can override value rendering
 | |
|         grid.set_renderer("foo", lambda record, key, value: "blah blah")
 | |
|         context = grid.get_vue_context()
 | |
|         self.assertEqual(
 | |
|             context,
 | |
|             {
 | |
|                 "data": [{"foo": "blah blah", "_action_url_view": "/blarg"}],
 | |
|                 "row_classes": {},
 | |
|             },
 | |
|         )
 | |
| 
 | |
|         # can set row class
 | |
|         grid.row_class = "whatever"
 | |
|         context = grid.get_vue_context()
 | |
|         self.assertEqual(
 | |
|             context,
 | |
|             {
 | |
|                 "data": [{"foo": "blah blah", "_action_url_view": "/blarg"}],
 | |
|                 "row_classes": {"0": "whatever"},
 | |
|             },
 | |
|         )
 | |
| 
 | |
|     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"}])
 | |
| 
 | |
|         # 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_row_class(self):
 | |
|         model = self.app.model
 | |
|         user = model.User(username="barney", active=True)
 | |
|         self.session.add(user)
 | |
|         self.session.commit()
 | |
|         data = dict(user)
 | |
| 
 | |
|         # null by default
 | |
|         grid = self.make_grid()
 | |
|         self.assertIsNone(grid.get_row_class(user, data, 1))
 | |
| 
 | |
|         # can use static class
 | |
|         grid.row_class = "foo"
 | |
|         self.assertEqual(grid.get_row_class(user, data, 1), "foo")
 | |
| 
 | |
|         # can use callable
 | |
|         def status(u, d, i):
 | |
|             if not u.active:
 | |
|                 return "inactive"
 | |
| 
 | |
|         grid.row_class = status
 | |
|         self.assertIsNone(grid.get_row_class(user, data, 1))
 | |
|         user.active = False
 | |
|         self.assertEqual(grid.get_row_class(user, data, 1), "inactive")
 | |
| 
 | |
|     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")
 |