Convert User pages to use master view.

And of course make some more tweaks to new grids etc.
This commit is contained in:
Lance Edgar 2015-08-11 23:19:41 -05:00
parent 9cfbc918e7
commit af07f477dc
11 changed files with 269 additions and 289 deletions

View file

@ -78,26 +78,22 @@ class AlchemyGrid(Grid):
filtrs = filters.GridFilterSet()
mapper = orm.class_mapper(self.model_class)
for prop in mapper.iterate_properties:
if isinstance(prop, orm.ColumnProperty) and prop.key != 'uuid':
filtrs[prop.key] = self.make_filter(prop)
if isinstance(prop, orm.ColumnProperty) and not prop.key.endswith('uuid'):
filtrs[prop.key] = self.make_filter(prop.key, prop.columns[0])
return filtrs
def make_filter(self, model_property, **kwargs):
def make_filter(self, key, column, **kwargs):
"""
Make a filter suitable for use with the given model property.
Make a filter suitable for use with the given column.
"""
if len(model_property.columns) > 1:
log.debug("ignoring secondary columns for sake of type detection")
coltype = model_property.columns[0].type
factory = filters.AlchemyGridFilter
if isinstance(coltype, sa.String):
if isinstance(column.type, sa.String):
factory = filters.AlchemyStringFilter
elif isinstance(coltype, sa.Numeric):
elif isinstance(column.type, sa.Numeric):
factory = filters.AlchemyNumericFilter
kwargs['model_property'] = model_property
return factory(model_property.key, **kwargs)
elif isinstance(column.type, sa.Boolean):
factory = filters.AlchemyBooleanFilter
return factory(key, column=column, **kwargs)
def iter_filters(self):
"""
@ -112,19 +108,20 @@ class AlchemyGrid(Grid):
"""
sorters, updates = {}, sorters
mapper = orm.class_mapper(self.model_class)
for key, column in mapper.columns.items():
if key != 'uuid':
sorters[key] = self.make_sorter(column)
for prop in mapper.iterate_properties:
if isinstance(prop, orm.ColumnProperty) and not prop.key.endswith('uuid'):
sorters[prop.key] = self.make_sorter(prop)
if updates:
sorters.update(updates)
return sorters
def make_sorter(self, field):
def make_sorter(self, model_property):
"""
Returns a function suitable for a sort map callable, with typical logic
built in for sorting applied to ``field``.
"""
return lambda q, d: q.order_by(getattr(field, d)())
column = getattr(self.model_class, model_property.key)
return lambda q, d: q.order_by(getattr(column, d)())
def load_settings(self):
"""
@ -136,23 +133,6 @@ class AlchemyGrid(Grid):
self._fa_grid.rebind(self.make_visible_data(), session=Session(),
request=self.request)
def sort_data(self, query):
"""
Sort the given query according to current settings, and return the result.
"""
# Cannot sort unless we know which column to sort by.
if not self.sortkey:
return query
# Cannot sort unless we have a sort function.
sortfunc = self.sorters.get(self.sortkey)
if not sortfunc:
return query
# We can provide a default sort direction though.
sortdir = getattr(self, 'sortdir', 'asc')
return sortfunc(query, sortdir)
def paginate_data(self, query):
"""
Paginate the given data set according to current settings, and return
@ -170,7 +150,7 @@ class AlchemyGrid(Grid):
for field in self._fa_grid.render_fields.itervalues():
column = GridColumn()
column.field = field
column.key = field.name
column.key = field.key
column.label = field.label()
yield column

View file

@ -39,7 +39,7 @@ class Grid(object):
"""
def __init__(self, key, request, columns=[], data=[], main_actions=[], more_actions=[],
filterable=False, filters={},
joiners={}, filterable=False, filters={},
sortable=False, sorters={}, default_sortkey=None, default_sortdir='asc',
pageable=False, default_pagesize=20, default_page=1,
width='auto', checkboxes=False, **kwargs):
@ -49,6 +49,7 @@ class Grid(object):
self.data = data
self.main_actions = main_actions
self.more_actions = more_actions
self.joiners = joiners
# Set extra attributes first, in case other init logic depends on any
# of them (i.e. in subclasses).
@ -378,15 +379,31 @@ class Grid(object):
Filter and return the given data set, according to current settings.
"""
for filtr in self.iter_active_filters():
if filtr.key in self.joiners and filtr.key not in self.joined:
data = self.joiners[filtr.key](data)
self.joined.add(filtr.key)
data = filtr.filter(data)
return data
def sort_data(self, data):
"""
Sort the given data set according to current settings, and return the
result. Note that the default implementation does nothing.
Sort the given query according to current settings, and return the result.
"""
return data
# Cannot sort unless we know which column to sort by.
if not self.sortkey:
return data
# Cannot sort unless we have a sort function.
sortfunc = self.sorters.get(self.sortkey)
if not sortfunc:
return data
# We can provide a default sort direction though.
sortdir = getattr(self, 'sortdir', 'asc')
if self.sortkey in self.joiners and self.sortkey not in self.joined:
data = self.joiners[self.sortkey](data)
self.joined.add(self.sortkey)
return sortfunc(data, sortdir)
def paginate_data(self, data):
"""
@ -401,6 +418,7 @@ class Grid(object):
set. This will page / sort / filter as necessary, according to the
grid's defaults and the current request etc.
"""
self.joined = set()
data = self.data
if self.filterable:
data = self.filter_data(data)

View file

@ -70,6 +70,7 @@ class GridFilter(object):
'filters' section when rendering the index page template.
"""
verbmap = {
'is_any': "is any",
'equal': "equal to",
'not_equal': "not equal to",
'greater_than': "greater than",
@ -78,6 +79,8 @@ class GridFilter(object):
'less_equal': "less than or equal to",
'is_null': "is null",
'is_not_null': "is not null",
'is_true': "is true",
'is_false': "is false",
'contains': "contains",
'does_not_contain': "does not contain",
}
@ -86,7 +89,7 @@ class GridFilter(object):
default_active=False, default_verb=None, default_value=None):
self.key = key
self.label = label or prettify(key)
self.verbs = verbs or self.default_verbs()
self.verbs = verbs or self.get_default_verbs()
self.renderer = renderer or DefaultRenderer()
self.renderer.filter = self
self.default_active = default_active
@ -96,11 +99,16 @@ class GridFilter(object):
def __repr__(self):
return "GridFilter({0})".format(repr(self.key))
def default_verbs(self):
def get_default_verbs(self):
"""
Returns the set of verbs which will be used by default, i.e. unless
overridden by constructor args etc.
"""
verbs = getattr(self, 'default_verbs', None)
if verbs:
if callable(verbs):
return verbs()
return verbs
return ['equal', 'not_equal', 'is_null', 'is_not_null']
def filter(self, data, verb=None, value=UNSPECIFIED):
@ -116,6 +124,14 @@ class GridFilter(object):
raise ValueError("Unknown filter verb: {0}".format(repr(verb)))
return filtr(data, value)
def filter_is_any(self, data, value):
"""
Special no-op filter which does no actual filtering. Useful in some
cases to add an "ineffective" option to the verb list for a given grid
filter.
"""
return data
def render(self, **kwargs):
kwargs['filter'] = self
return self.renderer.render(**kwargs)
@ -127,9 +143,7 @@ class AlchemyGridFilter(GridFilter):
"""
def __init__(self, *args, **kwargs):
self.model_property = kwargs.pop('model_property')
self.model_class = self.model_property.parent.class_
self.model_column = getattr(self.model_class, self.model_property.key)
self.column = kwargs.pop('column')
super(AlchemyGridFilter, self).__init__(*args, **kwargs)
def filter_equal(self, query, value):
@ -138,7 +152,7 @@ class AlchemyGridFilter(GridFilter):
"""
if value is None or value == '':
return query
return query.filter(self.model_column == value)
return query.filter(self.column == value)
def filter_not_equal(self, query, value):
"""
@ -150,8 +164,8 @@ class AlchemyGridFilter(GridFilter):
# When saying something is 'not equal' to something else, we must also
# include things which are nothing at all, in our result set.
return query.filter(sa.or_(
self.model_column == None,
self.model_column != value,
self.column == None,
self.column != value,
))
def filter_is_null(self, query, value):
@ -159,14 +173,14 @@ class AlchemyGridFilter(GridFilter):
Filter data with an 'IS NULL' query. Note that this filter does not
use the value for anything.
"""
return query.filter(self.model_column == None)
return query.filter(self.column == None)
def filter_is_not_null(self, query, value):
"""
Filter data with an 'IS NOT NULL' query. Note that this filter does
not use the value for anything.
"""
return query.filter(self.model_column != None)
return query.filter(self.column != None)
def filter_greater_than(self, query, value):
"""
@ -174,7 +188,7 @@ class AlchemyGridFilter(GridFilter):
"""
if value is None or value == '':
return query
return query.filter(self.model_column > value)
return query.filter(self.column > value)
def filter_greater_equal(self, query, value):
"""
@ -182,7 +196,7 @@ class AlchemyGridFilter(GridFilter):
"""
if value is None or value == '':
return query
return query.filter(self.model_column >= value)
return query.filter(self.column >= value)
def filter_less_than(self, query, value):
"""
@ -190,7 +204,7 @@ class AlchemyGridFilter(GridFilter):
"""
if value is None or value == '':
return query
return query.filter(self.model_column < value)
return query.filter(self.column < value)
def filter_less_equal(self, query, value):
"""
@ -198,7 +212,7 @@ class AlchemyGridFilter(GridFilter):
"""
if value is None or value == '':
return query
return query.filter(self.model_column <= value)
return query.filter(self.column <= value)
class AlchemyStringFilter(AlchemyGridFilter):
@ -219,7 +233,7 @@ class AlchemyStringFilter(AlchemyGridFilter):
"""
if value is None or value == '':
return query
return query.filter(self.model_column.ilike('%{0}%'.format(value)))
return query.filter(self.column.ilike('%{0}%'.format(value)))
def filter_does_not_contain(self, query, value):
"""
@ -231,8 +245,8 @@ class AlchemyStringFilter(AlchemyGridFilter):
# When saying something is 'not like' something else, we must also
# include things which are nothing at all, in our result set.
return query.filter(sa.or_(
self.model_column == None,
~self.model_column.ilike('%{0}%'.format(value)),
self.column == None,
~self.column.ilike('%{0}%'.format(value)),
))
@ -249,6 +263,27 @@ class AlchemyNumericFilter(AlchemyGridFilter):
'less_than', 'less_equal', 'is_null', 'is_not_null']
class AlchemyBooleanFilter(AlchemyGridFilter):
"""
Boolean filter for SQLAlchemy.
"""
default_verbs = ['is_true', 'is_false', 'is_null', 'is_not_null', 'is_any']
def filter_is_true(self, query, value):
"""
Filter data with an "is true" query. Note that this filter does not
use the value for anything.
"""
return query.filter(self.column == True)
def filter_is_false(self, query, value):
"""
Filter data with an "is false" query. Note that this filter does not
use the value for anything.
"""
return query.filter(self.column == False)
class GridFilterSet(OrderedDict):
"""
Collection class for :class:`GridFilter` instances.