Add initial support for grid index URLs

Yay, been wanting this for some time now.
This commit is contained in:
Lance Edgar 2016-05-01 17:50:57 -05:00
parent 70a2f10c81
commit abb42e9f25
5 changed files with 80 additions and 25 deletions

View file

@ -590,24 +590,23 @@ class Grid(object):
""" """
return bool(self.main_actions or self.more_actions) return bool(self.main_actions or self.more_actions)
def render_actions(self, row): def render_actions(self, row, i):
""" """
Returns the rendered contents of the 'actions' column for a given row. Returns the rendered contents of the 'actions' column for a given row.
""" """
main_actions = filter(None, [self.render_action(a, row) for a in self.main_actions]) main_actions = filter(None, [self.render_action(a, row, i) for a in self.main_actions])
more_actions = filter(None, [self.render_action(a, row) for a in self.more_actions]) more_actions = filter(None, [self.render_action(a, row, i) for a in self.more_actions])
if more_actions: if more_actions:
icon = HTML.tag('span', class_='ui-icon ui-icon-carat-1-e') icon = HTML.tag('span', class_='ui-icon ui-icon-carat-1-e')
link = tags.link_to("More" + icon, '#', class_='more') link = tags.link_to("More" + icon, '#', class_='more')
main_actions.append(link + HTML.tag('div', class_='more', c=more_actions)) main_actions.append(link + HTML.tag('div', class_='more', c=more_actions))
# main_actions.append(tags.link_to("More" + icon + HTML.literal('').join(more_actions), '#', class_='more'))
return HTML.literal('').join(main_actions) return HTML.literal('').join(main_actions)
def render_action(self, action, row): def render_action(self, action, row, i):
""" """
Renders an action menu item (link) for the given row. Renders an action menu item (link) for the given row.
""" """
url = action.get_url(row) url = action.get_url(row, i)
if url: if url:
kwargs = {'class_': action.key} kwargs = {'class_': action.key}
if action.icon: if action.icon:
@ -711,10 +710,10 @@ class GridAction(object):
self.icon = icon self.icon = icon
self.url = url self.url = url
def get_url(self, row): def get_url(self, row, i):
""" """
Returns an action URL for the given row. Returns an action URL for the given row.
""" """
if callable(self.url): if callable(self.url):
return self.url(row) return self.url(row, i)
return self.url return self.url

View file

@ -112,6 +112,9 @@ $(function() {
$('input[type=submit]').button(); $('input[type=submit]').button();
$('input[type=reset]').button(); $('input[type=reset]').button();
/* Also automatically disable any buttons marked for that. */
$('a.button[disabled=disabled]').button('option', 'disabled', true);
/* /*
* Apply timepicker behavior to text inputs which are marked for it. * Apply timepicker behavior to text inputs which are marked for it.
*/ */

View file

@ -5,6 +5,7 @@
<%def name="context_menu_items()"> <%def name="context_menu_items()">
<li>${h.link_to("Back to {}".format(model_title_plural), index_url)}</li> <li>${h.link_to("Back to {}".format(model_title_plural), index_url)}</li>
<li>${h.link_to("Permalink for this {}".format(model_title), action_url('view', instance))}</li>
% if master.editable and instance_editable and request.has_perm('{}.edit'.format(permission_prefix)): % if master.editable and instance_editable and request.has_perm('{}.edit'.format(permission_prefix)):
<li>${h.link_to("Edit this {}".format(model_title), action_url('edit', instance))}</li> <li>${h.link_to("Edit this {}".format(model_title), action_url('edit', instance))}</li>
% endif % endif

View file

@ -25,7 +25,7 @@
% endfor % endfor
% if grid.show_actions_column: % if grid.show_actions_column:
<td class="actions"> <td class="actions">
${grid.render_actions(row)} ${grid.render_actions(row, i)}
</td> </td>
% endif % endif
</tr> </tr>

View file

@ -64,6 +64,9 @@ class MasterView(View):
row_attrs = {} row_attrs = {}
cell_attrs = {} cell_attrs = {}
grid_index = None
use_index_links = False
@property @property
def Session(self): def Session(self):
""" """
@ -93,10 +96,16 @@ class MasterView(View):
if self.request.GET.get('reset-to-default-filters') == 'true': if self.request.GET.get('reset-to-default-filters') == 'true':
return self.redirect(self.request.current_route_url(_query=None)) return self.redirect(self.request.current_route_url(_query=None))
# Stash some grid stats, for possible use when generating URLs.
if grid.pageable and grid.pager:
self.first_visible_grid_index = grid.pager.first_item
# Return grid only, if partial page was requested.
if self.request.params.get('partial'): if self.request.params.get('partial'):
self.request.response.content_type = b'text/html' self.request.response.content_type = b'text/html'
self.request.response.text = grid.render_grid() self.request.response.text = grid.render_grid()
return self.request.response return self.request.response
return self.render_to_response('index', {'grid': grid}) return self.render_to_response('index', {'grid': grid})
def create(self): def create(self):
@ -122,12 +131,13 @@ class MasterView(View):
def redirect_after_create(self, instance): def redirect_after_create(self, instance):
return self.redirect(self.get_action_url('view', instance)) return self.redirect(self.get_action_url('view', instance))
def view(self): def view(self, instance=None):
""" """
View for viewing details of an existing model record. View for viewing details of an existing model record.
""" """
self.viewing = True self.viewing = True
instance = self.get_instance() if instance is None:
instance = self.get_instance()
form = self.make_form(instance) form = self.make_form(instance)
return self.render_to_response('view', { return self.render_to_response('view', {
'instance': instance, 'instance': instance,
@ -136,6 +146,28 @@ class MasterView(View):
'instance_deletable': self.deletable_instance(instance), 'instance_deletable': self.deletable_instance(instance),
'form': form}) 'form': form})
def view_index(self):
"""
View a record according to its grid index.
"""
try:
index = int(self.request.GET['index'])
except KeyError, ValueError:
return self.redirect(self.get_index_url())
if index < 1:
return self.redirect(self.get_index_url())
data = self.get_effective_data()
try:
instance = data[index-1]
except IndexError:
return self.redirect(self.get_index_url())
self.grid_index = index
if hasattr(data, 'count'):
self.grid_count = data.count()
else:
self.grid_count = len(data)
return self.view(instance)
def edit(self): def edit(self):
""" """
View for editing an existing model record. View for editing an existing model record.
@ -327,7 +359,10 @@ class MasterView(View):
'index_title': self.get_index_title(), 'index_title': self.get_index_title(),
'index_url': self.get_index_url(), 'index_url': self.get_index_url(),
'action_url': self.get_action_url, 'action_url': self.get_action_url,
'grid_index': self.grid_index,
} }
if self.grid_index:
context['grid_count'] = self.grid_count
context.update(data) context.update(data)
context.update(self.template_kwargs(**context)) context.update(self.template_kwargs(**context))
if hasattr(self, 'template_kwargs_{}'.format(template)): if hasattr(self, 'template_kwargs_{}'.format(template)):
@ -445,9 +480,14 @@ class MasterView(View):
actions = [] actions = []
prefix = self.get_permission_prefix() prefix = self.get_permission_prefix()
if self.viewable and self.request.has_perm('{}.view'.format(prefix)): if self.viewable and self.request.has_perm('{}.view'.format(prefix)):
actions.append(self.make_action('view', icon='zoomin')) url = self.get_view_index_url if self.use_index_links else None
actions.append(self.make_action('view', icon='zoomin', url=url))
return actions return actions
def get_view_index_url(self, row, i):
route = '{}.view_index'.format(self.get_route_prefix())
return '{}?index={}'.format(self.request.route_url(route), self.first_visible_grid_index + i - 1)
def get_more_actions(self): def get_more_actions(self):
""" """
Return a list of 'more' actions for the grid. Return a list of 'more' actions for the grid.
@ -460,19 +500,19 @@ class MasterView(View):
actions.append(self.make_action('delete', icon='trash', url=self.default_delete_url)) actions.append(self.make_action('delete', icon='trash', url=self.default_delete_url))
return actions return actions
def default_delete_url(self, row): def default_delete_url(self, row, i=None):
if self.deletable_instance(row): if self.deletable_instance(row):
return self.request.route_url('{}.delete'.format(self.get_route_prefix()), return self.request.route_url('{}.delete'.format(self.get_route_prefix()),
**self.get_action_route_kwargs(row)) **self.get_action_route_kwargs(row))
def make_action(self, key, **kwargs): def make_action(self, key, url=None, **kwargs):
""" """
Make a new :class:`GridAction` instance for the current grid. Make a new :class:`GridAction` instance for the current grid.
""" """
kwargs.setdefault('url', lambda r: self.request.route_url( if url is None:
'{0}.{1}'.format(self.get_route_prefix(), key), route = '{}.{}'.format(self.get_route_prefix(), key)
**self.get_action_route_kwargs(r))) url = lambda r, i: self.request.route_url(route, **self.get_action_route_kwargs(r))
return GridAction(key, **kwargs) return GridAction(key, url=url, **kwargs)
def get_action_route_kwargs(self, row): def get_action_route_kwargs(self, row):
""" """
@ -542,16 +582,21 @@ class MasterView(View):
""" """
return session.query(self.get_model_class()) return session.query(self.get_model_class())
def get_effective_query(self, session): def get_effective_data(self, session=None):
""" """
Convenience method which returns the "effective" query for the master Convenience method which returns the "effective" data for the master
grid, filtered and sorted to match what would show on the UI, but not grid, filtered and sorted to match what would show on the UI, but not
paged etc. paged etc.
""" """
if session is None:
session = self.Session()
grid = self.make_grid(session=session, pageable=False, grid = self.make_grid(session=session, pageable=False,
main_actions=[], more_actions=[]) main_actions=[], more_actions=[])
return grid._fa_grid.rows return grid._fa_grid.rows
def get_effective_query(self, session):
return self.get_effective_data(session)
def checkbox(self, instance): def checkbox(self, instance):
""" """
Returns a boolean indicating whether ot not a checkbox should be Returns a boolean indicating whether ot not a checkbox should be
@ -738,11 +783,18 @@ class MasterView(View):
# view # view
if cls.viewable: if cls.viewable:
config.add_route('{0}.view'.format(route_prefix), '{0}/{{{1}}}'.format(url_prefix, model_key)) config.add_tailbone_permission(permission_prefix, '{}.view'.format(permission_prefix),
config.add_view(cls, attr='view', route_name='{0}.view'.format(route_prefix), "View {} details".format(model_title))
permission='{0}.view'.format(permission_prefix))
config.add_tailbone_permission(permission_prefix, '{0}.view'.format(permission_prefix), # view by grid index
"View {0} Details".format(model_title)) config.add_route('{}.view_index'.format(route_prefix), '{}/view'.format(url_prefix))
config.add_view(cls, attr='view_index', route_name='{}.view_index'.format(route_prefix),
permission='{}.view'.format(permission_prefix))
# view by record key
config.add_route('{}.view'.format(route_prefix), '{}/{{{}}}'.format(url_prefix, model_key))
config.add_view(cls, attr='view', route_name='{}.view'.format(route_prefix),
permission='{}.view'.format(permission_prefix))
# edit # edit
if cls.editable: if cls.editable: