3
0
Fork 0

fix: improve support for composite model_key in MasterView

in particular, had a table (Catapult) with composite primary key,
where both prop keys are named differently than columns.

this also splits out the route kwargs logic for action urls, because
of another situation where i wanted to use non-primary field as model
key, but it also needed to be stripped of whitespace.  this allows for
such an override but in the end i did not pursue that method and just
wound up using default model key anyway..
This commit is contained in:
Lance Edgar 2025-01-14 11:49:47 -06:00
parent 72a663a80b
commit ecb1dce590
2 changed files with 54 additions and 10 deletions

View file

@ -2176,6 +2176,28 @@ class MasterView(View):
""" """
return str(instance) or "(no title)" 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): def get_action_url(self, action, obj, **kwargs):
""" """
Generate an "action" URL for the given model instance. 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 This is a shortcut which generates a route name based on
:meth:`get_route_prefix()` and the ``action`` param. :meth:`get_route_prefix()` and the ``action`` param.
It returns the URL based on generated route name and object's It calls :meth:`get_action_route_kwargs()` and then passes
model key values. those along with route name to ``request.route_url()``, and
returns the result.
:param action: String name for the action, which corresponds :param action: String name for the action, which corresponds
to part of some named route, e.g. ``'view'`` or ``'edit'``. to part of some named route, e.g. ``'view'`` or ``'edit'``.
:param obj: Model instance object. :param obj: Model instance object.
:param \**kwargs: Additional kwargs to be passed to
``request.route_url()``, if needed.
""" """
route_prefix = self.get_route_prefix() kw = self.get_action_route_kwargs(obj)
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.update(kwargs) kw.update(kwargs)
route_prefix = self.get_route_prefix()
return self.request.route_url(f'{route_prefix}.{action}', **kw) return self.request.route_url(f'{route_prefix}.{action}', **kw)
def get_action_url_view(self, obj, i): def get_action_url_view(self, obj, i):
@ -2729,7 +2750,7 @@ class MasterView(View):
inspector = sa.inspect(model_class) inspector = sa.inspect(model_class)
keys = [col.name for col in inspector.primary_key] keys = [col.name for col in inspector.primary_key]
return tuple([prop.key for prop in inspector.column_attrs 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}") raise AttributeError(f"you must define model_key for view class: {cls}")

View file

@ -750,6 +750,29 @@ class TestMasterView(WebTestCase):
self.request.matchdict = {'name': 'blarg'} self.request.matchdict = {'name': 'blarg'}
self.assertRaises(HTTPNotFound, view.get_instance, session=self.session) 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): def test_get_action_url_for_dict(self):
model = self.app.model model = self.app.model
setting = {'name': 'foo', 'value': 'bar'} setting = {'name': 'foo', 'value': 'bar'}