3
0
Fork 0

Compare commits

..

3 commits

Author SHA1 Message Date
Lance Edgar dcdc0e7dab fix: improve support for random objects with grid, master view
thus far we expected either dict or "native" ORM object which can
essentially behave like a dict when needed.  but a "non-native" object
may not behave like a dict and this hopefully fixes the logic to allow
for those anyway..
2024-11-25 19:11:41 -06:00
Lance Edgar ba9021c990 feat: add get_template_context() method for master view
allow override / supplement context for any view template
2024-11-25 19:10:58 -06:00
Lance Edgar 6085ea78ec fix: hide CRUD header buttons if master view does not allow 2024-11-25 18:45:51 -06:00
5 changed files with 97 additions and 7 deletions

View file

@ -1940,6 +1940,15 @@ class Grid:
})
return filters
def object_to_dict(self, obj):
""" """
try:
dct = dict(obj)
except TypeError:
dct = dict(obj.__dict__)
dct.pop('_sa_instance_state', None)
return dct
def get_vue_context(self):
"""
Returns a dict of context for the grid, for use with the Vue
@ -1976,7 +1985,7 @@ class Grid:
original_record = record
# convert record to new dict
record = dict(record)
record = self.object_to_dict(record)
# make all values safe for json
record = make_json_safe(record, warn=False)

View file

@ -682,13 +682,13 @@
<%def name="render_crud_header_buttons()">
% if master:
% if master.viewing:
% if instance_editable and master.has_perm('edit'):
% if master.editable and instance_editable and master.has_perm('edit'):
<wutta-button once
tag="a" href="${master.get_action_url('edit', instance)}"
icon-left="edit"
label="Edit This" />
% endif
% if instance_deletable and master.has_perm('delete'):
% if master.deletable and instance_deletable and master.has_perm('delete'):
<wutta-button once type="is-danger"
tag="a" href="${master.get_action_url('delete', instance)}"
icon-left="trash"
@ -701,7 +701,7 @@
icon-left="eye"
label="View This" />
% endif
% if instance_deletable and master.has_perm('delete'):
% if master.deletable and instance_deletable and master.has_perm('delete'):
<wutta-button once type="is-danger"
tag="a" href="${master.get_action_url('delete', instance)}"
icon-left="trash"
@ -714,7 +714,7 @@
icon-left="eye"
label="View This" />
% endif
% if instance_editable and master.has_perm('edit'):
% if master.editable and instance_editable and master.has_perm('edit'):
<wutta-button once
tag="a" href="${master.get_action_url('edit', instance)}"
icon-left="edit"

View file

@ -1629,6 +1629,9 @@ class MasterView(View):
if 'instance_deletable' not in context:
context['instance_deletable'] = self.is_deletable(instance)
# supplement context further if needed
context = self.get_template_context(context)
# first try the template path most specific to this view
template_prefix = self.get_template_prefix()
mako_path = f'{template_prefix}/{template}.mako'
@ -1648,6 +1651,26 @@ class MasterView(View):
# let that error raise on up
return render_to_response(mako_path, context, request=self.request)
def get_template_context(self, context):
"""
This method should return the "complete" context for rendering
the current view template.
Default logic for this method returns the given context
unchanged.
You may wish to override to pass extra context to the view
template. Check :attr:`viewing` and similar, or
``request.current_route_name`` etc. in order to add extra
context only for certain view templates.
:params: context: The context dict we have so far,
auto-provided by the master view logic.
:returns: Final context dict for the template.
"""
return context
def get_fallback_templates(self, template):
"""
Returns a list of "fallback" template paths which may be
@ -1995,8 +2018,12 @@ class MasterView(View):
:param obj: Model instance object.
"""
route_prefix = self.get_route_prefix()
kw = dict([(key, obj[key])
for key in self.get_model_key()])
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)
return self.request.route_url(f'{route_prefix}.{action}', **kw)

View file

@ -1373,6 +1373,25 @@ class TestGrid(WebTestCase):
filters = grid.get_vue_filters()
self.assertEqual(len(filters), 2)
def test_object_to_dict(self):
grid = self.make_grid()
setting = {'name': 'foo', 'value': 'bar'}
# new dict but with same values
dct = grid.object_to_dict(setting)
self.assertIsInstance(dct, dict)
self.assertIsNot(dct, setting)
self.assertEqual(dct, setting)
# random object, not iterable
class MockSetting:
def __init__(self, **kw):
self.__dict__.update(kw)
mock = MockSetting(**setting)
dct = grid.object_to_dict(mock)
self.assertIsInstance(dct, dict)
self.assertEqual(dct, setting)
def test_get_vue_context(self):
# empty if no columns defined

View file

@ -734,6 +734,41 @@ class TestMasterView(WebTestCase):
self.request.matchdict = {'name': 'blarg'}
self.assertRaises(HTTPNotFound, view.get_instance, session=self.session)
def test_get_action_url_for_dict(self):
model = self.app.model
setting = {'name': 'foo', 'value': 'bar'}
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting):
mod.MasterView.defaults(self.pyramid_config)
view = self.make_view()
url = view.get_action_url_view(setting, 0)
self.assertEqual(url, self.request.route_url('settings.view', name='foo'))
def test_get_action_url_for_orm_object(self):
model = self.app.model
setting = model.Setting(name='foo', value='bar')
self.session.add(setting)
self.session.commit()
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting):
mod.MasterView.defaults(self.pyramid_config)
view = self.make_view()
url = view.get_action_url_view(setting, 0)
self.assertEqual(url, self.request.route_url('settings.view', name='foo'))
def test_get_action_url_for_adhoc_object(self):
model = self.app.model
class MockSetting:
def __init__(self, **kw):
self.__dict__.update(kw)
setting = MockSetting(name='foo', value='bar')
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting):
mod.MasterView.defaults(self.pyramid_config)
view = self.make_view()
url = view.get_action_url_view(setting, 0)
self.assertEqual(url, self.request.route_url('settings.view', name='foo'))
def test_get_action_url_view(self):
model = self.app.model
setting = model.Setting(name='foo', value='bar')