this change was made for sake of sorting, when the backend is not responsible for that. in particular datetime values must be "rendered" somehow when passing to frontend, but depending on various factors the rendering may not preserve the "sensible" sort order behavior, e.g. if "weekday name" begins the rendered string. so in all cases now, rendered values will be given a distinct key in the record dict, while the original value stays in its original place. this should let grid sorting work off the original value, and hopefully all is well..fingers crossed
2206 lines
81 KiB
Python
2206 lines
81 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 wuttjamaican.util import get_timezone_by_name
|
|
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):
|
|
model = self.app.model
|
|
|
|
# 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"])
|
|
|
|
# sort_multiple is off by default with no model class
|
|
self.assertFalse(grid.sort_multiple)
|
|
|
|
# sort_multiple is *on* by default *with* model class
|
|
grid = self.make_grid(model_class=model.User)
|
|
self.assertTrue(grid.sort_multiple)
|
|
|
|
# ..but not if using oruga
|
|
with patch.object(self.request, "use_oruga", new=True):
|
|
grid = self.make_grid(model_class=model.User)
|
|
self.assertFalse(grid.sort_multiple)
|
|
|
|
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_filter_label("name"), "Different")
|
|
self.assertEqual(grid.get_column_label("name"), "Different")
|
|
|
|
# can update only column, not filter
|
|
self.assertEqual(grid.labels, {"name": "Different"})
|
|
self.assertEqual(grid.column_labels, {})
|
|
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_column_label("name"), "COLUMN ONLY")
|
|
self.assertEqual(grid.get_filter_label("name"), "Different")
|
|
|
|
def test_get_filter_label(self):
|
|
grid = self.make_grid(columns=["foo", "bar"])
|
|
self.assertEqual(grid.labels, {})
|
|
|
|
# default derived from key
|
|
self.assertEqual(grid.get_filter_label("foo"), "Foo")
|
|
|
|
# can override
|
|
grid.set_label("foo", "Different")
|
|
self.assertEqual(grid.get_filter_label("foo"), "Different")
|
|
|
|
def test_get_column_label(self):
|
|
grid = self.make_grid(columns=["foo", "bar"])
|
|
self.assertEqual(grid.labels, {})
|
|
|
|
# default derived from key
|
|
self.assertEqual(grid.get_column_label("foo"), "Foo")
|
|
|
|
# can override "globally"
|
|
grid.set_label("foo", "Different")
|
|
self.assertEqual(grid.get_column_label("foo"), "Different")
|
|
|
|
# can override for "just column"
|
|
grid.set_label("foo", "Col Lbl", column_only=True)
|
|
self.assertEqual(grid.get_column_label("foo"), "Col Lbl")
|
|
|
|
def test_get_label(self):
|
|
# nb. the get_label() method is deprecated; can remove this
|
|
# test eventually
|
|
|
|
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_centered(self):
|
|
model = self.app.model
|
|
|
|
grid = self.make_grid(model_class=model.Setting)
|
|
self.assertEqual(grid.centered, {})
|
|
|
|
# set flag
|
|
grid.set_centered("name")
|
|
self.assertIn("name", grid.centered)
|
|
self.assertTrue(grid.centered["name"])
|
|
|
|
# clear flag
|
|
grid.set_centered("name", False)
|
|
self.assertIn("name", grid.centered)
|
|
self.assertFalse(grid.centered["name"])
|
|
|
|
def test_is_centered(self):
|
|
model = self.app.model
|
|
|
|
grid = self.make_grid(model_class=model.Setting)
|
|
self.assertEqual(grid.centered, {})
|
|
self.assertFalse(grid.is_centered("name"))
|
|
|
|
# set flag
|
|
grid.set_centered("name")
|
|
self.assertIn("name", grid.centered)
|
|
self.assertTrue(grid.centered["name"])
|
|
self.assertTrue(grid.is_centered("name"))
|
|
|
|
# clear flag
|
|
grid.set_centered("name", False)
|
|
self.assertIn("name", grid.centered)
|
|
self.assertFalse(grid.centered["name"])
|
|
self.assertFalse(grid.is_centered("name"))
|
|
|
|
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)
|
|
|
|
# auto from filter factory
|
|
grid = self.make_grid(model_class=model.Setting)
|
|
self.assertEqual(grid.filters, {})
|
|
grid.set_filter(
|
|
"name", StringAlchemyFilter, model_property=model.Setting.name
|
|
)
|
|
self.assertIn("name", grid.filters)
|
|
|
|
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}
|
|
|
|
# true enum, null
|
|
value = grid.render_enum(obj, "status", None, enum=enum.UpgradeStatus)
|
|
self.assertIsNone(value)
|
|
|
|
# true enum, normal value
|
|
obj["status"] = enum.UpgradeStatus.SUCCESS
|
|
value = grid.render_enum(obj, "status", "SUCCESS", enum=enum.UpgradeStatus)
|
|
self.assertEqual(value, "success")
|
|
|
|
# dict enum
|
|
statuses = {
|
|
enum.UpgradeStatus.SUCCESS.name: "success",
|
|
enum.UpgradeStatus.FAILURE.name: "failure",
|
|
}
|
|
|
|
# dict enum, null
|
|
value = grid.render_enum(obj, "status", None, enum=statuses)
|
|
self.assertIsNone(value)
|
|
|
|
# true enum, normal value
|
|
obj["status"] = enum.UpgradeStatus.SUCCESS.value
|
|
value = grid.render_enum(obj, "status", "SUCCESS", enum=statuses)
|
|
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")
|
|
|
|
# object as dict
|
|
dt = datetime.date(2025, 1, 13)
|
|
obj = {"dt": dt}
|
|
result = grid.render_date(obj, "dt", str(dt))
|
|
self.assertEqual(result, "2025-01-13")
|
|
|
|
def test_render_datetime(self):
|
|
tzlocal = get_timezone_by_name("America/Los_Angeles")
|
|
with patch.object(self.app, "get_timezone", return_value=tzlocal):
|
|
grid = self.make_grid(columns=["foo", "bar"])
|
|
|
|
# null
|
|
obj = MagicMock(dt=None)
|
|
result = grid.render_datetime(obj, "dt", None)
|
|
self.assertEqual(result, "")
|
|
|
|
# normal (naive utc)
|
|
dt = datetime.datetime(2024, 12, 12, 13, 44)
|
|
obj = MagicMock(dt=dt)
|
|
result = grid.render_datetime(obj, "dt", str(dt))
|
|
self.assertTrue(result.startswith('<span title="'))
|
|
self.assertIn("2024-12-12 05:44-0800", result)
|
|
|
|
# object as dict
|
|
dt = datetime.datetime(2024, 12, 12, 13, 44)
|
|
obj = {"dt": dt}
|
|
result = grid.render_datetime(obj, "dt", str(dt))
|
|
self.assertTrue(result.startswith('<span title="'))
|
|
self.assertIn("2024-12-12 05:44-0800", result)
|
|
|
|
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)
|
|
|
|
def __getattr__(self, name):
|
|
if name == "something":
|
|
return "else"
|
|
raise AttributeError(f"attr not found: {name}")
|
|
|
|
mock = MockSetting(**setting)
|
|
dct = grid.object_to_dict(mock)
|
|
self.assertIsInstance(dct, dict)
|
|
self.assertEqual(dct, setting)
|
|
self.assertNotIn("something", dct)
|
|
|
|
# test (mock) association proxy behavior
|
|
grid.columns.append("something")
|
|
grid.columns.append("another")
|
|
dct = grid.object_to_dict(mock)
|
|
self.assertIn("something", dct)
|
|
self.assertEqual(dct["something"], "else")
|
|
self.assertNotIn("another", dct)
|
|
|
|
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", "baz": "zoo"},
|
|
]
|
|
grid = self.make_grid(columns=["foo", "baz"], data=mydata)
|
|
context = grid.get_vue_context()
|
|
self.assertEqual(
|
|
context, {"data": [{"foo": "bar", "baz": "zoo"}], "row_classes": {}}
|
|
)
|
|
|
|
# non-declared columns are discarded
|
|
mydata = [
|
|
{"foo": "a", "bar": "b", "baz": "c"},
|
|
]
|
|
grid = self.make_grid(columns=["bar"], data=mydata)
|
|
context = grid.get_vue_context()
|
|
self.assertEqual(context, {"data": [{"bar": "b"}], "row_classes": {}})
|
|
|
|
# if grid has actions, that list may be supplemented
|
|
mydata = [
|
|
{"foo": "bar", "baz": "zoo"},
|
|
]
|
|
grid = self.make_grid(columns=["foo", "baz"], data=mydata)
|
|
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", "baz": "zoo", "_action_url_view": "/blarg"}],
|
|
"row_classes": {},
|
|
},
|
|
)
|
|
|
|
# can override value rendering
|
|
renderer = MagicMock(return_value="blah blah")
|
|
grid.set_renderer("foo", renderer)
|
|
context = grid.get_vue_context()
|
|
self.assertEqual(
|
|
context,
|
|
{
|
|
"data": [
|
|
{
|
|
"foo": "bar",
|
|
"_rendered_foo": "blah blah",
|
|
"baz": "zoo",
|
|
"_action_url_view": "/blarg",
|
|
}
|
|
],
|
|
"row_classes": {},
|
|
},
|
|
)
|
|
renderer.assert_called_once_with({"foo": "bar", "baz": "zoo"}, "foo", "bar")
|
|
|
|
# custom rendering skipped if column not included
|
|
grid.remove("foo")
|
|
renderer.reset_mock()
|
|
context = grid.get_vue_context()
|
|
self.assertEqual(
|
|
context,
|
|
{
|
|
"data": [{"baz": "zoo", "_action_url_view": "/blarg"}],
|
|
"row_classes": {},
|
|
},
|
|
)
|
|
renderer.assert_not_called()
|
|
|
|
# can set row class
|
|
grid.row_class = "whatever"
|
|
context = grid.get_vue_context()
|
|
self.assertEqual(
|
|
context,
|
|
{
|
|
"data": [{"baz": "zoo", "_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": "bar",
|
|
"_rendered_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.assertTrue(html.startswith("<span "))
|
|
self.assertIn("ICON", html)
|
|
self.assertIn("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")
|