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'}