From abb42e9f2566578ea893fa4827f82d86b3adba7c Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 1 May 2016 17:50:57 -0500 Subject: [PATCH] Add initial support for grid index URLs Yay, been wanting this for some time now. --- tailbone/newgrids/core.py | 15 +++-- tailbone/static/js/tailbone.js | 3 + tailbone/templates/master/view.mako | 1 + tailbone/templates/newgrids/grid.mako | 2 +- tailbone/views/master.py | 84 ++++++++++++++++++++++----- 5 files changed, 80 insertions(+), 25 deletions(-) diff --git a/tailbone/newgrids/core.py b/tailbone/newgrids/core.py index cab60f4b..b36590c9 100644 --- a/tailbone/newgrids/core.py +++ b/tailbone/newgrids/core.py @@ -590,24 +590,23 @@ class Grid(object): """ 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. """ - main_actions = filter(None, [self.render_action(a, row) for a in self.main_actions]) - more_actions = filter(None, [self.render_action(a, row) for a in self.more_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, i) for a in self.more_actions]) if more_actions: icon = HTML.tag('span', class_='ui-icon ui-icon-carat-1-e') link = tags.link_to("More" + icon, '#', class_='more') 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) - def render_action(self, action, row): + def render_action(self, action, row, i): """ Renders an action menu item (link) for the given row. """ - url = action.get_url(row) + url = action.get_url(row, i) if url: kwargs = {'class_': action.key} if action.icon: @@ -711,10 +710,10 @@ class GridAction(object): self.icon = icon self.url = url - def get_url(self, row): + def get_url(self, row, i): """ Returns an action URL for the given row. """ if callable(self.url): - return self.url(row) + return self.url(row, i) return self.url diff --git a/tailbone/static/js/tailbone.js b/tailbone/static/js/tailbone.js index 6613c172..7e3b952c 100644 --- a/tailbone/static/js/tailbone.js +++ b/tailbone/static/js/tailbone.js @@ -112,6 +112,9 @@ $(function() { $('input[type=submit]').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. */ diff --git a/tailbone/templates/master/view.mako b/tailbone/templates/master/view.mako index d17c7b28..22bdfb00 100644 --- a/tailbone/templates/master/view.mako +++ b/tailbone/templates/master/view.mako @@ -5,6 +5,7 @@ <%def name="context_menu_items()">
  • ${h.link_to("Back to {}".format(model_title_plural), index_url)}
  • +
  • ${h.link_to("Permalink for this {}".format(model_title), action_url('view', instance))}
  • % if master.editable and instance_editable and request.has_perm('{}.edit'.format(permission_prefix)):
  • ${h.link_to("Edit this {}".format(model_title), action_url('edit', instance))}
  • % endif diff --git a/tailbone/templates/newgrids/grid.mako b/tailbone/templates/newgrids/grid.mako index 88bbbd43..61b02a47 100644 --- a/tailbone/templates/newgrids/grid.mako +++ b/tailbone/templates/newgrids/grid.mako @@ -25,7 +25,7 @@ % endfor % if grid.show_actions_column: - ${grid.render_actions(row)} + ${grid.render_actions(row, i)} % endif diff --git a/tailbone/views/master.py b/tailbone/views/master.py index 1b4f5e04..2eb7e778 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -64,6 +64,9 @@ class MasterView(View): row_attrs = {} cell_attrs = {} + grid_index = None + use_index_links = False + @property def Session(self): """ @@ -93,10 +96,16 @@ class MasterView(View): if self.request.GET.get('reset-to-default-filters') == 'true': 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'): self.request.response.content_type = b'text/html' self.request.response.text = grid.render_grid() return self.request.response + return self.render_to_response('index', {'grid': grid}) def create(self): @@ -122,12 +131,13 @@ class MasterView(View): def redirect_after_create(self, 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. """ self.viewing = True - instance = self.get_instance() + if instance is None: + instance = self.get_instance() form = self.make_form(instance) return self.render_to_response('view', { 'instance': instance, @@ -136,6 +146,28 @@ class MasterView(View): 'instance_deletable': self.deletable_instance(instance), '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): """ View for editing an existing model record. @@ -327,7 +359,10 @@ class MasterView(View): 'index_title': self.get_index_title(), 'index_url': self.get_index_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(self.template_kwargs(**context)) if hasattr(self, 'template_kwargs_{}'.format(template)): @@ -445,9 +480,14 @@ class MasterView(View): actions = [] prefix = self.get_permission_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 + 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): """ 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)) return actions - def default_delete_url(self, row): + def default_delete_url(self, row, i=None): if self.deletable_instance(row): return self.request.route_url('{}.delete'.format(self.get_route_prefix()), **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. """ - kwargs.setdefault('url', lambda r: self.request.route_url( - '{0}.{1}'.format(self.get_route_prefix(), key), - **self.get_action_route_kwargs(r))) - return GridAction(key, **kwargs) + if url is None: + route = '{}.{}'.format(self.get_route_prefix(), key) + url = lambda r, i: self.request.route_url(route, **self.get_action_route_kwargs(r)) + return GridAction(key, url=url, **kwargs) def get_action_route_kwargs(self, row): """ @@ -542,16 +582,21 @@ class MasterView(View): """ 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 paged etc. """ + if session is None: + session = self.Session() grid = self.make_grid(session=session, pageable=False, main_actions=[], more_actions=[]) return grid._fa_grid.rows + def get_effective_query(self, session): + return self.get_effective_data(session) + def checkbox(self, instance): """ Returns a boolean indicating whether ot not a checkbox should be @@ -738,11 +783,18 @@ class MasterView(View): # view if cls.viewable: - config.add_route('{0}.view'.format(route_prefix), '{0}/{{{1}}}'.format(url_prefix, model_key)) - config.add_view(cls, attr='view', route_name='{0}.view'.format(route_prefix), - permission='{0}.view'.format(permission_prefix)) - config.add_tailbone_permission(permission_prefix, '{0}.view'.format(permission_prefix), - "View {0} Details".format(model_title)) + config.add_tailbone_permission(permission_prefix, '{}.view'.format(permission_prefix), + "View {} details".format(model_title)) + + # view by grid index + 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 if cls.editable: