Stop using sa-filters for basic grid sorting

this just breaks if we need to use "aliased" models e.g. when sorting
and/or filtering by Product "regular price" column and similar.  so
now sorting more like we always used to, except for multi-column.

nb. this still assumes callers use `Grid.make_sorter()` when declaring
the sorters.  if caller must specify more custom/explicit sort logic
then it likely will not work and we'll have to add a workaround to
allow avoiding the common logic..but that's another day
This commit is contained in:
Lance Edgar 2023-10-21 16:10:36 -05:00
parent 421266e70c
commit 6d79766b24
2 changed files with 37 additions and 36 deletions

View file

@ -30,7 +30,6 @@ import logging
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
@ -1235,29 +1234,29 @@ class Grid(object):
# TODO: is there a better way to check for SA sorting? # TODO: is there a better way to check for SA sorting?
if self.model_class: if self.model_class:
# convert sort settings into a 'sortspec' for use with sa-filters # collect actual column sorters for order_by clause
full_spec = [] sorters = []
for sorter in self.active_sorters: for sorter in self.active_sorters:
sortkey = sorter['field'] sortkey = sorter['field']
sortdir = sorter['order']
sortfunc = self.sorters.get(sortkey) sortfunc = self.sorters.get(sortkey)
if sortfunc: if not sortfunc:
spec = { log.warning("unknown sorter: %s", sorter)
'sortkey': sortkey, continue
'model': sortfunc._class.__name__,
'field': sortfunc._column.key,
'direction': sortdir or 'asc',
}
full_spec.append(spec)
# apply joins needed for this sort spec # join appropriate model if needed
for spec in full_spec:
sortkey = spec['sortkey']
if sortkey in self.joiners and sortkey not in self.joined: if sortkey in self.joiners and sortkey not in self.joined:
data = self.joiners[sortkey](data) data = self.joiners[sortkey](data)
self.joined.add(sortkey) self.joined.add(sortkey)
return apply_sort(data, full_spec) # add column/dir to collection
sortdir = sorter['order']
sorters.append(getattr(sortfunc._column, sortdir)())
# apply sorting to query
if sorters:
data = data.order_by(*sorters)
return data
else: else:
# not a SQLAlchemy grid, custom sorter # not a SQLAlchemy grid, custom sorter

View file

@ -160,12 +160,6 @@ class ProductView(MasterView):
'inventory_on_order', 'inventory_on_order',
] ]
# same, but for prices
RegularPrice = orm.aliased(model.ProductPrice)
CurrentPrice = orm.aliased(model.ProductPrice)
SalePrice = orm.aliased(model.ProductPrice)
TPRPrice = orm.aliased(model.ProductPrice)
def __init__(self, request): def __init__(self, request):
super().__init__(request) super().__init__(request)
self.expose_label_printing = self.rattail_config.getbool( self.expose_label_printing = self.rattail_config.getbool(
@ -332,28 +326,34 @@ class ProductView(MasterView):
g.set_joiner('family', lambda q: q.outerjoin(model.Family)) g.set_joiner('family', lambda q: q.outerjoin(model.Family))
g.set_filter('family', model.Family.name) g.set_filter('family', model.Family.name)
# regular_price
g.set_label('regular_price', "Reg. Price") g.set_label('regular_price', "Reg. Price")
RegularPrice = orm.aliased(model.ProductPrice)
g.set_joiner('regular_price', lambda q: q.outerjoin( g.set_joiner('regular_price', lambda q: q.outerjoin(
self.RegularPrice, self.RegularPrice.uuid == model.Product.regular_price_uuid)) RegularPrice, RegularPrice.uuid == model.Product.regular_price_uuid))
g.set_sorter('regular_price', self.RegularPrice.price) g.set_sorter('regular_price', RegularPrice.price)
g.set_filter('regular_price', self.RegularPrice.price, label="Regular Price") g.set_filter('regular_price', RegularPrice.price, label="Regular Price")
# current_price
g.set_label('current_price', "Cur. Price") g.set_label('current_price', "Cur. Price")
g.set_renderer('current_price', self.render_current_price_for_grid) g.set_renderer('current_price', self.render_current_price_for_grid)
CurrentPrice = orm.aliased(model.ProductPrice)
g.set_joiner('current_price', lambda q: q.outerjoin( g.set_joiner('current_price', lambda q: q.outerjoin(
self.CurrentPrice, self.CurrentPrice.uuid == model.Product.current_price_uuid)) CurrentPrice, CurrentPrice.uuid == model.Product.current_price_uuid))
g.set_sorter('current_price', self.CurrentPrice.price) g.set_sorter('current_price', CurrentPrice.price)
g.set_filter('current_price', self.CurrentPrice.price, label="Current Price") g.set_filter('current_price', CurrentPrice.price, label="Current Price")
# tpr_price # tpr_price
TPRPrice = orm.aliased(model.ProductPrice)
g.set_joiner('tpr_price', lambda q: q.outerjoin( g.set_joiner('tpr_price', lambda q: q.outerjoin(
self.TPRPrice, self.TPRPrice.uuid == model.Product.tpr_price_uuid)) TPRPrice, TPRPrice.uuid == model.Product.tpr_price_uuid))
g.set_filter('tpr_price', self.TPRPrice.price) g.set_filter('tpr_price', TPRPrice.price)
# sale_price # sale_price
SalePrice = orm.aliased(model.ProductPrice)
g.set_joiner('sale_price', lambda q: q.outerjoin( g.set_joiner('sale_price', lambda q: q.outerjoin(
self.SalePrice, self.SalePrice.uuid == model.Product.sale_price_uuid)) SalePrice, SalePrice.uuid == model.Product.sale_price_uuid))
g.set_filter('sale_price', self.SalePrice.price) g.set_filter('sale_price', SalePrice.price)
# suggested_price # suggested_price
g.set_renderer('suggested_price', self.render_grid_suggested_price) g.set_renderer('suggested_price', self.render_grid_suggested_price)
@ -402,10 +402,12 @@ class ProductView(MasterView):
return "${:0.2f}".format(cost.unit_cost) return "${:0.2f}".format(cost.unit_cost)
def render_price(self, product, field): def render_price(self, product, field):
if not product.not_for_sale: # TODO: previously this rendered null (empty string) if
price = product[field] # product was marked "not for sale" - but why? important?
if price: #if not product.not_for_sale:
return self.products_handler.render_price(price) price = product[field]
if price:
return self.products_handler.render_price(price)
def render_current_price_for_grid(self, product, field): def render_current_price_for_grid(self, product, field):
text = self.render_price(product, field) or "" text = self.render_price(product, field) or ""