diff --git a/tailbone/grids/core.py b/tailbone/grids/core.py index 3f1769cf..b9254c18 100644 --- a/tailbone/grids/core.py +++ b/tailbone/grids/core.py @@ -38,6 +38,7 @@ from pyramid.renderers import render from webhelpers2.html import HTML, tags from paginate_sqlalchemy import SqlalchemyOrmPage +from wuttaweb.grids import GridAction as WuttaGridAction from . import filters as gridfilters from tailbone.db import Session from tailbone.util import raw_datetime @@ -1801,18 +1802,20 @@ class Grid: return False -class GridAction(object): +class GridAction(WuttaGridAction): """ - Represents an action available to a grid. This is used to construct the - 'actions' column when rendering the grid. + Represents a "row action" hyperlink within a grid context. - :param key: Key for the action (e.g. ``'edit'``), unique within - the grid. + This is a subclass of + :class:`wuttaweb:wuttaweb.grids.base.GridAction`. - :param label: Label to be displayed for the action. If not set, - will be a capitalized version of ``key``. + .. warning:: - :param icon: Icon name for the action. + This class remains for now, to retain compatibility with + existing code. But at some point the WuttaWeb class will + supersede this one entirely. + + :param target: HTML "target" attribute for the ```` tag. :param click_handler: Optional JS click handler for the action. This value will be rendered as-is within the final grid @@ -1824,41 +1827,23 @@ class GridAction(object): * ``$emit('do-something', props.row)`` """ - def __init__(self, key, label=None, url='#', icon=None, target=None, - link_class=None, click_handler=None): - self.key = key - self.label = label or prettify(key) - self.icon = icon - self.url = url + def __init__( + self, + request, + key, + target=None, + click_handler=None, + **kwargs, + ): + # TODO: previously url default was '#' - but i don't think we + # need that anymore? guess we'll see.. + #kwargs.setdefault('url', '#') + + super().__init__(request, key, **kwargs) + self.target = target - self.link_class = link_class self.click_handler = click_handler - def get_url(self, row, i): - """ - Returns an action URL for the given row. - """ - if callable(self.url): - return self.url(row, i) - return self.url - - def render_icon(self): - """ - Render the HTML snippet for the action link icon. - """ - return HTML.tag('i', class_='fas fa-{}'.format(self.icon)) - - def render_label(self): - """ - Render the label "text" within the actions column of a grid - row. Most actions have a static label that never varies, but - you can override this to add e.g. HTML content. Note that the - return value will be treated / rendered as HTML whether or not - it contains any, so perhaps be careful that it is trusted - content. - """ - return self.label - class URLMaker(object): """ diff --git a/tailbone/templates/grids/b-table.mako b/tailbone/templates/grids/b-table.mako index 632193b5..da9f2aae 100644 --- a/tailbone/templates/grids/b-table.mako +++ b/tailbone/templates/grids/b-table.mako @@ -53,11 +53,11 @@ % endfor - % if grid.main_actions or grid.more_actions: + % if grid.actions: <${b}-table-column field="actions" label="Actions" v-slot="props"> - % for action in grid.main_actions: + % for action in grid.actions: - % if request.use_oruga: - - % else: - - % endif - ${action.label} + ${action.render_icon_and_label()}   % endfor diff --git a/tailbone/templates/grids/complete.mako b/tailbone/templates/grids/complete.mako index fc48916b..93bb6c26 100644 --- a/tailbone/templates/grids/complete.mako +++ b/tailbone/templates/grids/complete.mako @@ -163,13 +163,7 @@ target="${action.target}" % endif > - % if request.use_oruga: - - ${action.render_label()|n} - % else: - ${action.render_icon()|n} - ${action.render_label()|n} - % endif + ${action.render_icon_and_label()}   % endfor diff --git a/tailbone/views/master.py b/tailbone/views/master.py index 0d322da3..097cb229 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -3220,14 +3220,18 @@ class MasterView(View): def make_action(self, key, url=None, factory=None, **kwargs): """ - Make a new :class:`GridAction` instance for the current grid. + Make and return a new :class:`~tailbone.grids.core.GridAction` + instance. + + This can be called to make actions for any grid, not just the + one from :meth:`index()`. """ if url is None: route = '{}.{}'.format(self.get_route_prefix(), key) url = lambda r, i: self.request.route_url(route, **self.get_action_route_kwargs(r)) if not factory: factory = grids.GridAction - return factory(key, url=url, **kwargs) + return factory(self.request, key, url=url, **kwargs) def get_action_route_kwargs(self, obj): """ diff --git a/tailbone/views/people.py b/tailbone/views/people.py index 94c85821..163a9a52 100644 --- a/tailbone/views/people.py +++ b/tailbone/views/people.py @@ -552,7 +552,7 @@ class PersonView(MasterView): if self.request.has_perm('trainwreck.transactions.view'): url = lambda row, i: self.request.route_url('trainwreck.transactions.view', uuid=row.uuid) - g.main_actions.append(grids.GridAction('view', icon='eye', url=url)) + g.main_actions.append(self.make_action('view', icon='eye', url=url)) g.load_settings() g.set_enum('system', self.enum.TRAINWRECK_SYSTEM) diff --git a/tailbone/views/purchasing/receiving.py b/tailbone/views/purchasing/receiving.py index 55936184..0a305f0a 100644 --- a/tailbone/views/purchasing/receiving.py +++ b/tailbone/views/purchasing/receiving.py @@ -40,7 +40,7 @@ from webhelpers2.html import tags, HTML from wuttaweb.util import get_form_data -from tailbone import forms, grids +from tailbone import forms from tailbone.views.purchasing import PurchasingBatchView @@ -1031,7 +1031,7 @@ class ReceivingBatchView(PurchasingBatchView): if batch.is_truck_dump_parent(): permission_prefix = self.get_permission_prefix() if self.request.has_perm('{}.edit_row'.format(permission_prefix)): - transform = grids.GridAction('transform', + transform = self.make_action('transform', icon='shuffle', label="Transform to Unit", url=self.transform_unit_url) diff --git a/tailbone/views/roles.py b/tailbone/views/roles.py index b34b3673..fb834479 100644 --- a/tailbone/views/roles.py +++ b/tailbone/views/roles.py @@ -363,7 +363,7 @@ class RoleView(PrincipalMasterView): if role.users: users = sorted(role.users, key=lambda u: u.username) actions = [ - grids.GridAction('view', icon='zoomin', + self.make_action('view', icon='zoomin', url=lambda r, i: self.request.route_url('users.view', uuid=r.uuid)) ] kwargs['users'] = grids.Grid(None, users, ['username', 'active'], diff --git a/tests/grids/test_core.py b/tests/grids/test_core.py index e6f9d675..0a8d5d66 100644 --- a/tests/grids/test_core.py +++ b/tests/grids/test_core.py @@ -137,3 +137,20 @@ class TestGrid(WebTestCase): # calling again returns same data data2 = grid.get_vue_data() self.assertIs(data2, data) + + +class TestGridAction(WebTestCase): + + def test_constructor(self): + + # null by default + action = mod.GridAction(self.request, 'view') + self.assertIsNone(action.target) + self.assertIsNone(action.click_handler) + + # but can set them + action = mod.GridAction(self.request, 'view', + target='_blank', + click_handler='doSomething(props.row)') + self.assertEqual(action.target, '_blank') + self.assertEqual(action.click_handler, 'doSomething(props.row)') diff --git a/tests/views/test_master.py b/tests/views/test_master.py index 19321496..572875a0 100644 --- a/tests/views/test_master.py +++ b/tests/views/test_master.py @@ -3,6 +3,7 @@ from unittest.mock import patch from tailbone.views import master as mod +from wuttaweb.grids import GridAction from tests.util import WebTestCase @@ -24,3 +25,11 @@ class TestMasterView(WebTestCase): # sanity / coverage check kw = view.make_form_kwargs(model_instance=setting) self.assertIsNotNone(kw['action_url']) + + def test_make_action(self): + model = self.app.model + with patch.multiple(mod.MasterView, create=True, + model_class=model.Setting): + view = self.make_view() + action = view.make_action('view') + self.assertIsInstance(action, GridAction)