diff --git a/CHANGELOG.md b/CHANGELOG.md
index ef4a5e4..cc241e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,15 @@ All notable changes to wuttaweb will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
+## v0.20.2 (2025-01-14)
+
+### Fix
+
+- improve support for composite `model_key` in MasterView
+- let content header text be a bit longer
+- add optional `target` attr for GridAction
+- add `render_date()` method for grids
+
## v0.20.1 (2025-01-13)
### Fix
diff --git a/pyproject.toml b/pyproject.toml
index 8ec3f7c..a7a756e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
[project]
name = "WuttaWeb"
-version = "0.20.1"
+version = "0.20.2"
description = "Web App for Wutta Framework"
readme = "README.md"
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
diff --git a/src/wuttaweb/grids/base.py b/src/wuttaweb/grids/base.py
index fb797a4..e65a22e 100644
--- a/src/wuttaweb/grids/base.py
+++ b/src/wuttaweb/grids/base.py
@@ -2280,6 +2280,10 @@ class GridAction:
See also :meth:`get_url()`.
+ .. attribute:: target
+
+ Optional ``target`` attribute for the ```` tag.
+
.. attribute:: icon
Name of icon to be shown for the action link.
@@ -2297,6 +2301,7 @@ class GridAction:
key,
label=None,
url=None,
+ target=None,
icon=None,
link_class=None,
):
@@ -2305,6 +2310,7 @@ class GridAction:
self.app = self.config.get_app()
self.key = key
self.url = url
+ self.target = target
self.label = label or self.app.make_title(key)
self.icon = icon or key
self.link_class = link_class or ''
diff --git a/src/wuttaweb/templates/base.mako b/src/wuttaweb/templates/base.mako
index 429e9be..486b496 100644
--- a/src/wuttaweb/templates/base.mako
+++ b/src/wuttaweb/templates/base.mako
@@ -155,7 +155,7 @@
}
#content-title h1 {
- max-width: 80%;
+ max-width: 85%;
overflow: hidden;
padding-left: 0.5rem;
text-overflow: ellipsis;
diff --git a/src/wuttaweb/templates/grids/vue_template.mako b/src/wuttaweb/templates/grids/vue_template.mako
index 746a939..ac0a2a9 100644
--- a/src/wuttaweb/templates/grids/vue_template.mako
+++ b/src/wuttaweb/templates/grids/vue_template.mako
@@ -180,6 +180,9 @@
% for action in grid.actions:
${action.render_icon_and_label()}
diff --git a/src/wuttaweb/views/master.py b/src/wuttaweb/views/master.py
index cb32ef9..61d4d14 100644
--- a/src/wuttaweb/views/master.py
+++ b/src/wuttaweb/views/master.py
@@ -2176,6 +2176,28 @@ class MasterView(View):
"""
return str(instance) or "(no title)"
+ def get_action_route_kwargs(self, obj):
+ """
+ Get a dict of route kwargs for the given object.
+
+ This is called from :meth:`get_action_url()` and must return
+ kwargs suitable for use with ``request.route_url()``.
+
+ In practice this should return a dict which has keys for each
+ field from :meth:`get_model_key()` and values which come from
+ the object.
+
+ :param obj: Model instance object.
+
+ :returns: The dict of route kwargs for the object.
+ """
+ try:
+ return dict([(key, obj[key])
+ for key in self.get_model_key()])
+ except TypeError:
+ return dict([(key, getattr(obj, key))
+ for key in self.get_model_key()])
+
def get_action_url(self, action, obj, **kwargs):
"""
Generate an "action" URL for the given model instance.
@@ -2183,22 +2205,21 @@ class MasterView(View):
This is a shortcut which generates a route name based on
:meth:`get_route_prefix()` and the ``action`` param.
- It returns the URL based on generated route name and object's
- model key values.
+ It calls :meth:`get_action_route_kwargs()` and then passes
+ those along with route name to ``request.route_url()``, and
+ returns the result.
:param action: String name for the action, which corresponds
to part of some named route, e.g. ``'view'`` or ``'edit'``.
:param obj: Model instance object.
+
+ :param \**kwargs: Additional kwargs to be passed to
+ ``request.route_url()``, if needed.
"""
- route_prefix = self.get_route_prefix()
- try:
- kw = dict([(key, obj[key])
- for key in self.get_model_key()])
- except TypeError:
- kw = dict([(key, getattr(obj, key))
- for key in self.get_model_key()])
+ kw = self.get_action_route_kwargs(obj)
kw.update(kwargs)
+ route_prefix = self.get_route_prefix()
return self.request.route_url(f'{route_prefix}.{action}', **kw)
def get_action_url_view(self, obj, i):
@@ -2729,7 +2750,7 @@ class MasterView(View):
inspector = sa.inspect(model_class)
keys = [col.name for col in inspector.primary_key]
return tuple([prop.key for prop in inspector.column_attrs
- if [col.name for col in prop.columns] == keys])
+ if all([col.name in keys for col in prop.columns])])
raise AttributeError(f"you must define model_key for view class: {cls}")
diff --git a/tests/views/test_master.py b/tests/views/test_master.py
index d77e2b8..c259034 100644
--- a/tests/views/test_master.py
+++ b/tests/views/test_master.py
@@ -750,6 +750,29 @@ class TestMasterView(WebTestCase):
self.request.matchdict = {'name': 'blarg'}
self.assertRaises(HTTPNotFound, view.get_instance, session=self.session)
+ def test_get_action_route_kwargs(self):
+ model = self.app.model
+ with patch.object(mod.MasterView, 'model_class', new=model.Setting, create=True):
+ view = self.make_view()
+
+ # dict object
+ setting = {'name': 'foo', 'value': 'bar'}
+ kw = view.get_action_route_kwargs(setting)
+ self.assertEqual(kw, {'name': 'foo'})
+
+ # mapped object
+ setting = model.Setting(name='foo', value='bar')
+ kw = view.get_action_route_kwargs(setting)
+ self.assertEqual(kw, {'name': 'foo'})
+
+ # non-standard object
+ class MySetting:
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
+ setting = MySetting(name='foo', value='bar')
+ kw = view.get_action_route_kwargs(setting)
+ self.assertEqual(kw, {'name': 'foo'})
+
def test_get_action_url_for_dict(self):
model = self.app.model
setting = {'name': 'foo', 'value': 'bar'}