3
0
Fork 0

fix: add grid renderers for bool, currency, quantity

also set bool renderer by default when possible
This commit is contained in:
Lance Edgar 2025-01-02 20:13:04 -06:00
parent 08a895a07b
commit a612bf3846
2 changed files with 130 additions and 14 deletions

View file

@ -116,7 +116,8 @@ class Grid:
Dict of column (cell) value renderer overrides. Dict of column (cell) value renderer overrides.
See also :meth:`set_renderer()`. See also :meth:`set_renderer()` and
:meth:`set_default_renderers()`.
.. attribute:: row_class .. attribute:: row_class
@ -602,15 +603,18 @@ class Grid:
""" """
Set default column value renderers, where applicable. Set default column value renderers, where applicable.
This will add new entries to :attr:`renderers` for columns This is called automatically from the class constructor. It
whose data type implies a default renderer should be used. will add new entries to :attr:`renderers` for columns whose
This is generally only possible if :attr:`model_class` is set data type implies a default renderer. This is only possible
to a valid SQLAlchemy mapped class. if :attr:`model_class` is set to a SQLAlchemy mapped class.
This (for now?) only looks for This only looks for a couple of data types, and configures as
:class:`sqlalchemy:sqlalchemy.types.DateTime` columns and if follows:
any are found, they are configured to use
:meth:`render_datetime()`. * :class:`sqlalchemy:sqlalchemy.types.Boolean` ->
:meth:`render_boolean()`
* :class:`sqlalchemy:sqlalchemy.types.DateTime` ->
:meth:`render_datetime()`
""" """
if not self.model_class: if not self.model_class:
return return
@ -626,6 +630,8 @@ class Grid:
column = prop.columns[0] column = prop.columns[0]
if isinstance(column.type, sa.DateTime): if isinstance(column.type, sa.DateTime):
self.set_renderer(key, self.render_datetime) self.set_renderer(key, self.render_datetime)
elif isinstance(column.type, sa.Boolean):
self.set_renderer(key, self.render_boolean)
def set_link(self, key, link=True): def set_link(self, key, link=True):
""" """
@ -1753,23 +1759,65 @@ class Grid:
# rendering methods # rendering methods
############################## ##############################
def render_boolean(self, obj, key, value):
"""
Column renderer for boolean values.
This calls
:meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_boolean()`
for the return value.
This may be used automatically per
:meth:`set_default_renderers()` or you can use it explicitly::
grid.set_renderer('foo', grid.render_boolean)
"""
return self.app.render_boolean(value)
def render_currency(self, obj, key, value):
"""
Column renderer for currency values.
This calls
:meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_currency()`
for the return value.
This is not used automatically but you can use it explicitly::
grid.set_renderer('foo', grid.render_currency)
"""
return self.app.render_currency(value)
def render_datetime(self, obj, key, value): def render_datetime(self, obj, key, value):
""" """
Default cell value renderer for Column renderer for :class:`python:datetime.datetime` values.
:class:`sqlalchemy:sqlalchemy.types.DateTime` columns, which
calls This calls
:meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_datetime()` :meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_datetime()`
for the return value. for the return value.
This may be used automatically per This may be used automatically per
:meth:`set_default_renderers()` or you can use it explicitly :meth:`set_default_renderers()` or you can use it explicitly::
for any :class:`python:datetime.datetime` column with::
grid.set_renderer('foo', grid.render_datetime) grid.set_renderer('foo', grid.render_datetime)
""" """
dt = getattr(obj, key) dt = getattr(obj, key)
return self.app.render_datetime(dt) return self.app.render_datetime(dt)
def render_quantity(self, obj, key, value):
"""
Column renderer for quantity values.
This calls
:meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_quantity()`
for the return value.
This is not used automatically but you can use it explicitly::
grid.set_renderer('foo', grid.render_quantity)
"""
return self.app.render_quantity(value)
def render_table_element( def render_table_element(
self, self,
form=None, form=None,

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8; -*- # -*- coding: utf-8; -*-
import datetime import datetime
import decimal
from unittest import TestCase from unittest import TestCase
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
@ -232,6 +233,17 @@ class TestGrid(WebTestCase):
self.assertIn('created', grid.renderers) self.assertIn('created', grid.renderers)
self.assertIs(grid.renderers['created'], myrender) 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)
def test_linked_columns(self): def test_linked_columns(self):
grid = self.make_grid(columns=['foo', 'bar']) grid = self.make_grid(columns=['foo', 'bar'])
self.assertEqual(grid.linked_columns, []) self.assertEqual(grid.linked_columns, [])
@ -1331,6 +1343,62 @@ class TestGrid(WebTestCase):
# rendering methods # rendering methods
############################## ##############################
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_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_datetime(self): def test_render_datetime(self):
grid = self.make_grid(columns=['foo', 'bar']) grid = self.make_grid(columns=['foo', 'bar'])