Convert User pages to use master view.
And of course make some more tweaks to new grids etc.
This commit is contained in:
parent
9cfbc918e7
commit
af07f477dc
11 changed files with 269 additions and 289 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue