Add back-end support for multi-column grid sorting
or very nearly, anyway. front-end still just supports 1 column yet
This commit is contained in:
parent
4beca7af20
commit
6d7754cf2a
|
@ -33,13 +33,7 @@ from cornice import resource, Service
|
||||||
|
|
||||||
from tailbone.api import APIView, api
|
from tailbone.api import APIView, api
|
||||||
from tailbone.db import Session
|
from tailbone.db import Session
|
||||||
|
from tailbone.util import SortColumn
|
||||||
|
|
||||||
class SortColumn(object):
|
|
||||||
|
|
||||||
def __init__(self, field_name, model_name=None):
|
|
||||||
self.field_name = field_name
|
|
||||||
self.model_name = model_name
|
|
||||||
|
|
||||||
|
|
||||||
class APIMasterView(APIView):
|
class APIMasterView(APIView):
|
||||||
|
|
|
@ -24,12 +24,13 @@
|
||||||
Core Grid Classes
|
Core Grid Classes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from urllib.parse import urlencode
|
||||||
import warnings
|
import warnings
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from six.moves import urllib
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
|
from sa_filters import apply_sort
|
||||||
|
|
||||||
from rattail.db.types import GPCType
|
from rattail.db.types import GPCType
|
||||||
from rattail.util import prettify, pretty_boolean, pretty_quantity
|
from rattail.util import prettify, pretty_boolean, pretty_quantity
|
||||||
|
@ -552,48 +553,6 @@ class Grid(object):
|
||||||
return self.url(obj)
|
return self.url(obj)
|
||||||
return self.url
|
return self.url
|
||||||
|
|
||||||
def make_webhelpers_grid(self):
|
|
||||||
kwargs = dict(self._whgrid_kwargs)
|
|
||||||
kwargs['request'] = self.request
|
|
||||||
kwargs['url'] = self.make_url
|
|
||||||
|
|
||||||
columns = list(self.columns)
|
|
||||||
column_labels = kwargs.setdefault('column_labels', {})
|
|
||||||
column_formats = kwargs.setdefault('column_formats', {})
|
|
||||||
|
|
||||||
for key, value in self.labels.items():
|
|
||||||
column_labels.setdefault(key, value)
|
|
||||||
|
|
||||||
if self.checkboxes:
|
|
||||||
columns.insert(0, 'checkbox')
|
|
||||||
column_labels['checkbox'] = tags.checkbox('check-all')
|
|
||||||
column_formats['checkbox'] = self.checkbox_column_format
|
|
||||||
|
|
||||||
if self.renderers:
|
|
||||||
kwargs['renderers'] = self.renderers
|
|
||||||
if self.extra_row_class:
|
|
||||||
kwargs['extra_record_class'] = self.extra_row_class
|
|
||||||
if self.linked_columns:
|
|
||||||
kwargs['linked_columns'] = list(self.linked_columns)
|
|
||||||
|
|
||||||
if self.main_actions or self.more_actions:
|
|
||||||
columns.append('actions')
|
|
||||||
column_formats['actions'] = self.actions_column_format
|
|
||||||
|
|
||||||
# TODO: pretty sure this factory doesn't serve all use cases yet?
|
|
||||||
factory = CustomWebhelpersGrid
|
|
||||||
# factory = webhelpers2_grid.Grid
|
|
||||||
if self.sortable:
|
|
||||||
# factory = CustomWebhelpersGrid
|
|
||||||
kwargs['order_column'] = self.sortkey
|
|
||||||
kwargs['order_direction'] = 'dsc' if self.sortdir == 'desc' else 'asc'
|
|
||||||
|
|
||||||
grid = factory(self.make_visible_data(), columns, **kwargs)
|
|
||||||
if self.sortable:
|
|
||||||
grid.exclude_ordering = list([key for key in grid.exclude_ordering
|
|
||||||
if key not in self.sorters])
|
|
||||||
return grid
|
|
||||||
|
|
||||||
def make_default_renderers(self, renderers):
|
def make_default_renderers(self, renderers):
|
||||||
"""
|
"""
|
||||||
Make the default set of column renderers for the grid.
|
Make the default set of column renderers for the grid.
|
||||||
|
@ -638,19 +597,6 @@ class Grid(object):
|
||||||
def actions_column_format(self, column_number, row_number, item):
|
def actions_column_format(self, column_number, row_number, item):
|
||||||
return HTML.td(self.render_actions(item, row_number), class_='actions')
|
return HTML.td(self.render_actions(item, row_number), class_='actions')
|
||||||
|
|
||||||
def render_grid(self, template='/grids/grid.mako', **kwargs):
|
|
||||||
context = kwargs
|
|
||||||
context['grid'] = self
|
|
||||||
context['request'] = self.request
|
|
||||||
grid_class = ''
|
|
||||||
if self.width == 'full':
|
|
||||||
grid_class = 'full'
|
|
||||||
elif self.width == 'half':
|
|
||||||
grid_class = 'half'
|
|
||||||
context['grid_class'] = '{} {}'.format(grid_class, context.get('grid_class', ''))
|
|
||||||
context.setdefault('grid_attrs', {})
|
|
||||||
return render(template, context)
|
|
||||||
|
|
||||||
def get_default_filters(self):
|
def get_default_filters(self):
|
||||||
"""
|
"""
|
||||||
Returns the default set of filters provided by the grid.
|
Returns the default set of filters provided by the grid.
|
||||||
|
@ -761,6 +707,9 @@ class Grid(object):
|
||||||
return query
|
return query
|
||||||
return query.order_by(getattr(column, direction)())
|
return query.order_by(getattr(column, direction)())
|
||||||
|
|
||||||
|
sorter._class = class_
|
||||||
|
sorter._column = column
|
||||||
|
|
||||||
return sorter
|
return sorter
|
||||||
|
|
||||||
def make_simple_sorter(self, key, foldcase=False):
|
def make_simple_sorter(self, key, foldcase=False):
|
||||||
|
@ -801,8 +750,12 @@ class Grid(object):
|
||||||
# initial default settings
|
# initial default settings
|
||||||
settings = {}
|
settings = {}
|
||||||
if self.sortable:
|
if self.sortable:
|
||||||
settings['sortkey'] = self.default_sortkey
|
if self.default_sortkey:
|
||||||
settings['sortdir'] = self.default_sortdir
|
settings['sorters.length'] = 1
|
||||||
|
settings['sorters.1.key'] = self.default_sortkey
|
||||||
|
settings['sorters.1.dir'] = self.default_sortdir
|
||||||
|
else:
|
||||||
|
settings['sorters.length'] = 0
|
||||||
if self.pageable:
|
if self.pageable:
|
||||||
settings['pagesize'] = self.get_default_pagesize()
|
settings['pagesize'] = self.get_default_pagesize()
|
||||||
settings['page'] = self.default_page
|
settings['page'] = self.default_page
|
||||||
|
@ -875,8 +828,12 @@ class Grid(object):
|
||||||
filtr.verb = settings['filter.{}.verb'.format(filtr.key)]
|
filtr.verb = settings['filter.{}.verb'.format(filtr.key)]
|
||||||
filtr.value = settings['filter.{}.value'.format(filtr.key)]
|
filtr.value = settings['filter.{}.value'.format(filtr.key)]
|
||||||
if self.sortable:
|
if self.sortable:
|
||||||
self.sortkey = settings['sortkey']
|
self.active_sorters = []
|
||||||
self.sortdir = settings['sortdir']
|
for i in range(1, settings['sorters.length'] + 1):
|
||||||
|
self.active_sorters.append((
|
||||||
|
settings[f'sorters.{i}.key'],
|
||||||
|
settings[f'sorters.{i}.dir'],
|
||||||
|
))
|
||||||
if self.pageable:
|
if self.pageable:
|
||||||
self.pagesize = settings['pagesize']
|
self.pagesize = settings['pagesize']
|
||||||
self.page = settings['page']
|
self.page = settings['page']
|
||||||
|
@ -895,21 +852,36 @@ class Grid(object):
|
||||||
# anything...
|
# anything...
|
||||||
session = Session()
|
session = Session()
|
||||||
if user not in session:
|
if user not in session:
|
||||||
user = session.merge(user)
|
# TODO: pretty sure there is no need to *merge* here..
|
||||||
|
# but we shall see if any breakage happens maybe
|
||||||
|
#user = session.merge(user)
|
||||||
|
user = session.get(user.__class__, user.uuid)
|
||||||
|
|
||||||
# User defaults should have all or nothing, so just check one key.
|
|
||||||
key = 'tailbone.{}.grid.{}.sortkey'.format(user.uuid, self.key)
|
|
||||||
app = self.request.rattail_config.get_app()
|
app = self.request.rattail_config.get_app()
|
||||||
return app.get_setting(Session(), key) is not None
|
|
||||||
|
# user defaults should be all or nothing, so just check one key
|
||||||
|
key = f'tailbone.{user.uuid}.grid.{self.key}.sorters.length'
|
||||||
|
if app.get_setting(session, key) is not None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# TODO: this is deprecated but should work its way out of the
|
||||||
|
# system in a little while (?)..then can remove this entirely
|
||||||
|
key = f'tailbone.{user.uuid}.grid.{self.key}.sortkey'
|
||||||
|
if app.get_setting(session, key) is not None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def apply_user_defaults(self, settings):
|
def apply_user_defaults(self, settings):
|
||||||
"""
|
"""
|
||||||
Update the given settings dict with user defaults, if any exist.
|
Update the given settings dict with user defaults, if any exist.
|
||||||
"""
|
"""
|
||||||
def merge(key, normalize=lambda v: v):
|
|
||||||
skey = 'tailbone.{}.grid.{}.{}'.format(self.request.user.uuid, self.key, key)
|
|
||||||
app = self.request.rattail_config.get_app()
|
app = self.request.rattail_config.get_app()
|
||||||
value = app.get_setting(Session(), skey)
|
session = Session()
|
||||||
|
prefix = f'tailbone.{self.request.user.uuid}.grid.{self.key}'
|
||||||
|
|
||||||
|
def merge(key, normalize=lambda v: v):
|
||||||
|
value = app.get_setting(session, f'{prefix}.{key}')
|
||||||
settings[key] = normalize(value)
|
settings[key] = normalize(value)
|
||||||
|
|
||||||
if self.filterable:
|
if self.filterable:
|
||||||
|
@ -919,8 +891,52 @@ class Grid(object):
|
||||||
merge('filter.{}.value'.format(filtr.key))
|
merge('filter.{}.value'.format(filtr.key))
|
||||||
|
|
||||||
if self.sortable:
|
if self.sortable:
|
||||||
merge('sortkey')
|
|
||||||
merge('sortdir')
|
# first clear existing settings for *sorting* only
|
||||||
|
# nb. this is because number of sort settings will vary
|
||||||
|
for key in list(settings):
|
||||||
|
if key.startswith('sorters.'):
|
||||||
|
del settings[key]
|
||||||
|
|
||||||
|
# check for *deprecated* settings, and use those if present
|
||||||
|
# TODO: obviously should stop this, but must wait until
|
||||||
|
# all old settings have been flushed out. which in the
|
||||||
|
# case of user-persisted settings, could be a while...
|
||||||
|
sortkey = app.get_setting(session, f'{prefix}.sortkey')
|
||||||
|
if sortkey:
|
||||||
|
settings['sorters.length'] = 1
|
||||||
|
settings['sorters.1.key'] = sortkey
|
||||||
|
settings['sorters.1.dir'] = app.get_setting(session, f'{prefix}.sortdir')
|
||||||
|
|
||||||
|
# nb. re-persist these user settings per new
|
||||||
|
# convention, so deprecated settings go away and we
|
||||||
|
# can remove this logic after a while..
|
||||||
|
app = self.request.rattail_config.get_app()
|
||||||
|
model = app.model
|
||||||
|
prefix = f'tailbone.{self.request.user.uuid}.grid.{self.key}'
|
||||||
|
query = Session.query(model.Setting)\
|
||||||
|
.filter(sa.or_(
|
||||||
|
model.Setting.name.like(f'{prefix}.sorters.%'),
|
||||||
|
model.Setting.name == f'{prefix}.sortkey',
|
||||||
|
model.Setting.name == f'{prefix}.sortdir'))
|
||||||
|
for setting in query.all():
|
||||||
|
Session.delete(setting)
|
||||||
|
Session.flush()
|
||||||
|
|
||||||
|
def persist(key):
|
||||||
|
app.save_setting(Session(),
|
||||||
|
f'tailbone.{self.request.user.uuid}.grid.{self.key}.{key}',
|
||||||
|
settings[key])
|
||||||
|
|
||||||
|
persist('sorters.length')
|
||||||
|
persist('sorters.1.key')
|
||||||
|
persist('sorters.1.dir')
|
||||||
|
|
||||||
|
else: # the future
|
||||||
|
merge('sorters.length', int)
|
||||||
|
for i in range(1, settings['sorters.length'] + 1):
|
||||||
|
merge(f'sorters.{i}.key')
|
||||||
|
merge(f'sorters.{i}.dir')
|
||||||
|
|
||||||
if self.pageable:
|
if self.pageable:
|
||||||
merge('pagesize', int)
|
merge('pagesize', int)
|
||||||
|
@ -939,10 +955,16 @@ class Grid(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
elif type_ == 'sort':
|
elif type_ == 'sort':
|
||||||
|
|
||||||
|
# TODO: remove this eventually, but some links in the wild
|
||||||
|
# may still include these params, so leave it for now
|
||||||
for key in ['sortkey', 'sortdir']:
|
for key in ['sortkey', 'sortdir']:
|
||||||
if key in self.request.GET:
|
if key in self.request.GET:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if 'sort1key' in self.request.GET:
|
||||||
|
return True
|
||||||
|
|
||||||
elif type_ == 'page':
|
elif type_ == 'page':
|
||||||
for key in ['pagesize', 'page']:
|
for key in ['pagesize', 'page']:
|
||||||
if key in self.request.GET:
|
if key in self.request.GET:
|
||||||
|
@ -956,10 +978,12 @@ class Grid(object):
|
||||||
"""
|
"""
|
||||||
# session should have all or nothing, so just check a few keys which
|
# session should have all or nothing, so just check a few keys which
|
||||||
# should be guaranteed present if anything has been stashed
|
# should be guaranteed present if anything has been stashed
|
||||||
for key in ['page', 'sortkey']:
|
prefix = f'grid.{self.key}'
|
||||||
if 'grid.{}.{}'.format(self.key, key) in self.request.session:
|
for key in ['page', 'sorters.length']:
|
||||||
|
if f'{prefix}.{key}' in self.request.session:
|
||||||
return True
|
return True
|
||||||
return any([key.startswith('grid.{}.filter'.format(self.key)) for key in self.request.session])
|
return any([key.startswith(f'{prefix}.filter')
|
||||||
|
for key in self.request.session])
|
||||||
|
|
||||||
def get_setting(self, source, settings, key, normalize=lambda v: v, default=None):
|
def get_setting(self, source, settings, key, normalize=lambda v: v, default=None):
|
||||||
"""
|
"""
|
||||||
|
@ -1044,8 +1068,46 @@ class Grid(object):
|
||||||
"""
|
"""
|
||||||
if not self.sortable:
|
if not self.sortable:
|
||||||
return
|
return
|
||||||
settings['sortkey'] = self.get_setting(source, settings, 'sortkey')
|
|
||||||
settings['sortdir'] = self.get_setting(source, settings, 'sortdir')
|
if source == 'request':
|
||||||
|
|
||||||
|
# TODO: remove this eventually, but some links in the wild
|
||||||
|
# may still include these params, so leave it for now
|
||||||
|
if 'sortkey' in self.request.GET:
|
||||||
|
settings['sorters.length'] = 1
|
||||||
|
settings['sorters.1.key'] = self.get_setting(source, settings, 'sortkey')
|
||||||
|
settings['sorters.1.dir'] = self.get_setting(source, settings, 'sortdir')
|
||||||
|
|
||||||
|
else: # the future
|
||||||
|
i = 1
|
||||||
|
while True:
|
||||||
|
skey = f'sort{i}key'
|
||||||
|
if skey in self.request.GET:
|
||||||
|
settings[f'sorters.{i}.key'] = self.get_setting(source, settings, skey)
|
||||||
|
settings[f'sorters.{i}.dir'] = self.get_setting(source, settings, f'sort{i}dir')
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
i += 1
|
||||||
|
settings['sorters.length'] = i - 1
|
||||||
|
|
||||||
|
else: # session
|
||||||
|
|
||||||
|
# TODO: definitely will remove this, but leave it for now
|
||||||
|
# so it doesn't monkey with current user sessions when
|
||||||
|
# next upgrade happens. so, remove after all are upgraded
|
||||||
|
sortkey = self.get_setting(source, settings, 'sortkey')
|
||||||
|
if sortkey:
|
||||||
|
settings['sorters.length'] = 1
|
||||||
|
settings['sorters.1.key'] = sortkey
|
||||||
|
settings['sorters.1.dir'] = self.get_setting(source, settings, 'sortdir')
|
||||||
|
|
||||||
|
else: # the future
|
||||||
|
settings['sorters.length'] = self.get_setting(source, settings,
|
||||||
|
'sorters.length', int)
|
||||||
|
for i in range(1, settings['sorters.length'] + 1):
|
||||||
|
for key in ('key', 'dir'):
|
||||||
|
skey = f'sorters.{i}.{key}'
|
||||||
|
settings[skey] = self.get_setting(source, settings, skey)
|
||||||
|
|
||||||
def update_page_settings(self, settings):
|
def update_page_settings(self, settings):
|
||||||
"""
|
"""
|
||||||
|
@ -1100,8 +1162,40 @@ class Grid(object):
|
||||||
persist('filter.{}.value'.format(filtr.key))
|
persist('filter.{}.value'.format(filtr.key))
|
||||||
|
|
||||||
if self.sortable:
|
if self.sortable:
|
||||||
persist('sortkey')
|
|
||||||
persist('sortdir')
|
# first clear existing settings for *sorting* only
|
||||||
|
# nb. this is because number of sort settings will vary
|
||||||
|
if to == 'defaults':
|
||||||
|
model = self.request.rattail_config.get_model()
|
||||||
|
prefix = f'tailbone.{self.request.user.uuid}.grid.{self.key}'
|
||||||
|
query = Session.query(model.Setting)\
|
||||||
|
.filter(sa.or_(
|
||||||
|
model.Setting.name.like(f'{prefix}.sorters.%'),
|
||||||
|
# TODO: remove these eventually,
|
||||||
|
# but probably should wait until
|
||||||
|
# all nodes have been upgraded for
|
||||||
|
# (quite) a while?
|
||||||
|
model.Setting.name == f'{prefix}.sortkey',
|
||||||
|
model.Setting.name == f'{prefix}.sortdir'))
|
||||||
|
for setting in query.all():
|
||||||
|
Session.delete(setting)
|
||||||
|
Session.flush()
|
||||||
|
else: # session
|
||||||
|
prefix = f'grid.{self.key}'
|
||||||
|
for key in list(self.request.session):
|
||||||
|
if key.startswith(f'{prefix}.sorters.'):
|
||||||
|
del self.request.session[key]
|
||||||
|
# TODO: definitely will remove these, but leave for
|
||||||
|
# now so they don't monkey with current user sessions
|
||||||
|
# when next upgrade happens. so, remove after all are
|
||||||
|
# upgraded
|
||||||
|
self.request.session.pop(f'{prefix}.sortkey', None)
|
||||||
|
self.request.session.pop(f'{prefix}.sortdir', None)
|
||||||
|
|
||||||
|
persist('sorters.length')
|
||||||
|
for i in range(1, settings['sorters.length'] + 1):
|
||||||
|
persist(f'sorters.{i}.key')
|
||||||
|
persist(f'sorters.{i}.dir')
|
||||||
|
|
||||||
if self.pageable:
|
if self.pageable:
|
||||||
persist('pagesize')
|
persist('pagesize')
|
||||||
|
@ -1131,21 +1225,32 @@ class Grid(object):
|
||||||
"""
|
"""
|
||||||
Sort the given query according to current settings, and return the result.
|
Sort the given query according to current settings, and return the result.
|
||||||
"""
|
"""
|
||||||
# Cannot sort unless we know which column to sort by.
|
# bail if no sort settings
|
||||||
if not self.sortkey:
|
if not self.active_sorters:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
# Cannot sort unless we have a sort function.
|
# convert sort settings into a 'sortspec' for use with sa-filters
|
||||||
sortfunc = self.sorters.get(self.sortkey)
|
full_spec = []
|
||||||
if not sortfunc:
|
for sortkey, sortdir in self.active_sorters:
|
||||||
return data
|
sortfunc = self.sorters.get(sortkey)
|
||||||
|
if sortfunc:
|
||||||
|
spec = {
|
||||||
|
'sortkey': sortkey,
|
||||||
|
'model': sortfunc._class.__name__,
|
||||||
|
'field': sortfunc._column.name,
|
||||||
|
'direction': sortdir or 'asc',
|
||||||
|
}
|
||||||
|
# spec.sortkey = sortkey
|
||||||
|
full_spec.append(spec)
|
||||||
|
|
||||||
# We can provide a default sort direction though.
|
# apply joins needed for this sort spec
|
||||||
sortdir = getattr(self, 'sortdir', 'asc')
|
for spec in full_spec:
|
||||||
if self.sortkey in self.joiners and self.sortkey not in self.joined:
|
sortkey = spec['sortkey']
|
||||||
data = self.joiners[self.sortkey](data)
|
if sortkey in self.joiners and sortkey not in self.joined:
|
||||||
self.joined.add(self.sortkey)
|
data = self.joiners[sortkey](data)
|
||||||
return sortfunc(data, sortdir)
|
self.joined.add(sortkey)
|
||||||
|
|
||||||
|
return apply_sort(data, full_spec)
|
||||||
|
|
||||||
def paginate_data(self, data):
|
def paginate_data(self, data):
|
||||||
"""
|
"""
|
||||||
|
@ -1197,7 +1302,7 @@ class Grid(object):
|
||||||
data = self.pager
|
data = self.pager
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def render_complete(self, template='/grids/complete.mako', **kwargs):
|
def render_complete(self, template='/grids/buefy.mako', **kwargs):
|
||||||
"""
|
"""
|
||||||
Render the complete grid, including filters.
|
Render the complete grid, including filters.
|
||||||
"""
|
"""
|
||||||
|
@ -1717,5 +1822,5 @@ class URLMaker(object):
|
||||||
params = self.request.GET.copy()
|
params = self.request.GET.copy()
|
||||||
params["page"] = page
|
params["page"] = page
|
||||||
params["partial"] = "1"
|
params["partial"] = "1"
|
||||||
qs = urllib.parse.urlencode(params, True)
|
qs = urlencode(params, True)
|
||||||
return '{}?{}'.format(self.request.path, qs)
|
return '{}?{}'.format(self.request.path, qs)
|
||||||
|
|
|
@ -202,7 +202,7 @@
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
% if grid.sortable:
|
% if grid.sortable:
|
||||||
:default-sort="[sortField, sortOrder]"
|
:default-sort="sortingPriority[0]"
|
||||||
backend-sorting
|
backend-sorting
|
||||||
@sort="onSort"
|
@sort="onSort"
|
||||||
% endif
|
% endif
|
||||||
|
@ -352,8 +352,9 @@
|
||||||
firstItem: ${json.dumps(grid_data['first_item'] if grid.pageable else None)|n},
|
firstItem: ${json.dumps(grid_data['first_item'] if grid.pageable else None)|n},
|
||||||
lastItem: ${json.dumps(grid_data['last_item'] if grid.pageable else None)|n},
|
lastItem: ${json.dumps(grid_data['last_item'] if grid.pageable else None)|n},
|
||||||
|
|
||||||
sortField: ${json.dumps(grid.sortkey if grid.sortable else None)|n},
|
% if grid.sortable:
|
||||||
sortOrder: ${json.dumps(grid.sortdir if grid.sortable else None)|n},
|
sortingPriority: ${json.dumps(grid.active_sorters)|n},
|
||||||
|
% endif
|
||||||
|
|
||||||
## filterable: ${json.dumps(grid.filterable)|n},
|
## filterable: ${json.dumps(grid.filterable)|n},
|
||||||
filters: ${json.dumps(filters_data if grid.filterable else None)|n},
|
filters: ${json.dumps(filters_data if grid.filterable else None)|n},
|
||||||
|
@ -454,8 +455,10 @@
|
||||||
getBasicParams() {
|
getBasicParams() {
|
||||||
let params = {}
|
let params = {}
|
||||||
% if grid.sortable:
|
% if grid.sortable:
|
||||||
params.sortkey = this.sortField
|
for (let i = 1; i <= this.sortingPriority.length; i++) {
|
||||||
params.sortdir = this.sortOrder
|
params['sort'+i+'key'] = this.sortingPriority[i-1][0]
|
||||||
|
params['sort'+i+'dir'] = this.sortingPriority[i-1][1]
|
||||||
|
}
|
||||||
% endif
|
% endif
|
||||||
% if grid.pageable:
|
% if grid.pageable:
|
||||||
params.pagesize = this.perPage
|
params.pagesize = this.perPage
|
||||||
|
@ -535,8 +538,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
onSort(field, order) {
|
onSort(field, order) {
|
||||||
this.sortField = field
|
this.sortingPriority = [[field, order]]
|
||||||
this.sortOrder = order
|
|
||||||
// always reset to first page when changing sort options
|
// always reset to first page when changing sort options
|
||||||
// TODO: i mean..right? would we ever not want that?
|
// TODO: i mean..right? would we ever not want that?
|
||||||
this.currentPage = 1
|
this.currentPage = 1
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
## -*- coding: utf-8 -*-
|
|
||||||
<div class="grid-wrapper">
|
|
||||||
|
|
||||||
<table class="grid-header">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
|
|
||||||
<td class="filters" rowspan="2">
|
|
||||||
% if grid.filterable:
|
|
||||||
${grid.render_filters(allow_save_defaults=allow_save_defaults)|n}
|
|
||||||
% endif
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="menu">
|
|
||||||
% if context_menu:
|
|
||||||
<ul id="context-menu">
|
|
||||||
${context_menu|n}
|
|
||||||
</ul>
|
|
||||||
% endif
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td class="tools">
|
|
||||||
% if tools:
|
|
||||||
<div class="grid-tools">
|
|
||||||
${tools|n}
|
|
||||||
</div><!-- grid-tools -->
|
|
||||||
% endif
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
</table><!-- grid-header -->
|
|
||||||
|
|
||||||
${grid.render_grid()|n}
|
|
||||||
|
|
||||||
</div><!-- grid-wrapper -->
|
|
|
@ -1,21 +0,0 @@
|
||||||
## -*- coding: utf-8; -*-
|
|
||||||
<div class="grid ${grid_class}" data-delete-speedbump="${'true' if grid.delete_speedbump else 'false'}" ${h.HTML.render_attrs(grid_attrs)}>
|
|
||||||
<table>
|
|
||||||
${grid.make_webhelpers_grid()}
|
|
||||||
</table>
|
|
||||||
% if grid.pageable and grid.pager:
|
|
||||||
<div class="pager">
|
|
||||||
<p class="showing">
|
|
||||||
${"showing {} thru {} of {:,d}".format(grid.pager.first_item, grid.pager.last_item, grid.pager.item_count)}
|
|
||||||
% if grid.pager.page_count > 1:
|
|
||||||
${"(page {} of {:,d})".format(grid.pager.page, grid.pager.page_count)}
|
|
||||||
% endif
|
|
||||||
</p>
|
|
||||||
<p class="page-links">
|
|
||||||
${h.select('pagesize', grid.pager.items_per_page, grid.get_pagesize_options())}
|
|
||||||
per page
|
|
||||||
${grid.pager.pager('$link_first $link_previous ~1~ $link_next $link_last', symbol_next='next', symbol_previous='prev')|n}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
% endif
|
|
||||||
</div>
|
|
|
@ -44,6 +44,17 @@ from webhelpers2.html import HTML, tags
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SortColumn(object):
|
||||||
|
"""
|
||||||
|
Generic representation of a sort column, for use with sorting grid
|
||||||
|
data as well as with API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, field_name, model_name=None):
|
||||||
|
self.field_name = field_name
|
||||||
|
self.model_name = model_name
|
||||||
|
|
||||||
|
|
||||||
def get_csrf_token(request):
|
def get_csrf_token(request):
|
||||||
"""
|
"""
|
||||||
Convenience function to retrieve the effective CSRF token for the given
|
Convenience function to retrieve the effective CSRF token for the given
|
||||||
|
|
|
@ -476,36 +476,6 @@ class CustomerView(MasterView):
|
||||||
items.append(HTML.tag('li', c=[link]))
|
items.append(HTML.tag('li', c=[link]))
|
||||||
return HTML.tag('ul', c=items)
|
return HTML.tag('ul', c=items)
|
||||||
|
|
||||||
# TODO: remove if no longer used
|
|
||||||
def render_people_removable(self, customer, field):
|
|
||||||
people = customer.people
|
|
||||||
if not people:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
route_prefix = self.get_route_prefix()
|
|
||||||
permission_prefix = self.get_permission_prefix()
|
|
||||||
|
|
||||||
view_url = lambda p, i: self.request.route_url('people.view', uuid=p.uuid)
|
|
||||||
actions = [
|
|
||||||
grids.GridAction('view', icon='zoomin', url=view_url),
|
|
||||||
]
|
|
||||||
if self.people_detachable and self.request.has_perm('{}.detach_person'.format(permission_prefix)):
|
|
||||||
url = lambda p, i: self.request.route_url('{}.detach_person'.format(route_prefix),
|
|
||||||
uuid=customer.uuid, person_uuid=p.uuid)
|
|
||||||
actions.append(
|
|
||||||
grids.GridAction('detach', icon='trash', url=url))
|
|
||||||
|
|
||||||
columns = ['first_name', 'last_name', 'display_name']
|
|
||||||
g = grids.Grid(
|
|
||||||
key='{}.people'.format(route_prefix),
|
|
||||||
data=customer.people,
|
|
||||||
columns=columns,
|
|
||||||
labels={'display_name': "Full Name"},
|
|
||||||
url=lambda p: self.request.route_url('people.view', uuid=p.uuid),
|
|
||||||
linked_columns=columns,
|
|
||||||
main_actions=actions)
|
|
||||||
return HTML.literal(g.render_grid())
|
|
||||||
|
|
||||||
def render_shoppers(self, customer, field):
|
def render_shoppers(self, customer, field):
|
||||||
route_prefix = self.get_route_prefix()
|
route_prefix = self.get_route_prefix()
|
||||||
permission_prefix = self.get_permission_prefix()
|
permission_prefix = self.get_permission_prefix()
|
||||||
|
|
|
@ -340,11 +340,9 @@ class MasterView(View):
|
||||||
if grid.pageable and hasattr(grid, 'pager'):
|
if grid.pageable and hasattr(grid, 'pager'):
|
||||||
self.first_visible_grid_index = grid.pager.first_item
|
self.first_visible_grid_index = grid.pager.first_item
|
||||||
|
|
||||||
# return grid only, if partial page was requested
|
# return grid data only, if partial page was requested
|
||||||
if self.request.params.get('partial'):
|
if self.request.params.get('partial'):
|
||||||
# render grid data only, as JSON
|
return self.json_response(grid.get_buefy_data())
|
||||||
return render_to_response('json', grid.get_buefy_data(),
|
|
||||||
request=self.request)
|
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'grid': grid,
|
'grid': grid,
|
||||||
|
@ -1156,8 +1154,7 @@ class MasterView(View):
|
||||||
# return grid only, if partial page was requested
|
# return grid only, if partial page was requested
|
||||||
if self.request.params.get('partial'):
|
if self.request.params.get('partial'):
|
||||||
# render grid data only, as JSON
|
# render grid data only, as JSON
|
||||||
return render_to_response('json', grid.get_buefy_data(),
|
return self.json_response(grid.get_buefy_data())
|
||||||
request=self.request)
|
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'instance': instance,
|
'instance': instance,
|
||||||
|
@ -1284,8 +1281,7 @@ class MasterView(View):
|
||||||
# return grid only, if partial page was requested
|
# return grid only, if partial page was requested
|
||||||
if self.request.params.get('partial'):
|
if self.request.params.get('partial'):
|
||||||
# render grid data only, as JSON
|
# render grid data only, as JSON
|
||||||
return render_to_response('json', grid.get_buefy_data(),
|
return self.json_response(grid.get_buefy_data())
|
||||||
request=self.request)
|
|
||||||
|
|
||||||
return self.render_to_response('versions', {
|
return self.render_to_response('versions', {
|
||||||
'instance': instance,
|
'instance': instance,
|
||||||
|
|
|
@ -461,7 +461,8 @@ class MemberEquityPaymentView(MasterView):
|
||||||
g.set_renderer(field, self.render_member_key)
|
g.set_renderer(field, self.render_member_key)
|
||||||
g.set_filter(field, attr,
|
g.set_filter(field, attr,
|
||||||
label=self.get_member_key_label(),
|
label=self.get_member_key_label(),
|
||||||
default_active=True)
|
default_active=True,
|
||||||
|
default_verb='equal')
|
||||||
g.set_sorter(field, attr)
|
g.set_sorter(field, attr)
|
||||||
|
|
||||||
# member (name)
|
# member (name)
|
||||||
|
|
Loading…
Reference in a new issue