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: