From 6d79766b24e8873f568bcdac62793e5c9fc1abfa Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 21 Oct 2023 16:10:36 -0500 Subject: [PATCH] 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 --- tailbone/grids/core.py | 31 ++++++++++++++-------------- tailbone/views/products.py | 42 ++++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/tailbone/grids/core.py b/tailbone/grids/core.py index 6177d3d0..5f28fca0 100644 --- a/tailbone/grids/core.py +++ b/tailbone/grids/core.py @@ -30,7 +30,6 @@ import logging import sqlalchemy as sa from sqlalchemy import orm -from sa_filters import apply_sort from rattail.db.types import GPCType 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? if self.model_class: - # convert sort settings into a 'sortspec' for use with sa-filters - full_spec = [] + # collect actual column sorters for order_by clause + sorters = [] for sorter in self.active_sorters: sortkey = sorter['field'] - sortdir = sorter['order'] sortfunc = self.sorters.get(sortkey) - if sortfunc: - spec = { - 'sortkey': sortkey, - 'model': sortfunc._class.__name__, - 'field': sortfunc._column.key, - 'direction': sortdir or 'asc', - } - full_spec.append(spec) + if not sortfunc: + log.warning("unknown sorter: %s", sorter) + continue - # apply joins needed for this sort spec - for spec in full_spec: - sortkey = spec['sortkey'] + # join appropriate model if needed if sortkey in self.joiners and sortkey not in self.joined: data = self.joiners[sortkey](data) 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: # not a SQLAlchemy grid, custom sorter diff --git a/tailbone/views/products.py b/tailbone/views/products.py index 449e7473..e9e32a21 100644 --- a/tailbone/views/products.py +++ b/tailbone/views/products.py @@ -160,12 +160,6 @@ class ProductView(MasterView): '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): super().__init__(request) 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_filter('family', model.Family.name) + # regular_price g.set_label('regular_price', "Reg. Price") + RegularPrice = orm.aliased(model.ProductPrice) g.set_joiner('regular_price', lambda q: q.outerjoin( - self.RegularPrice, self.RegularPrice.uuid == model.Product.regular_price_uuid)) - g.set_sorter('regular_price', self.RegularPrice.price) - g.set_filter('regular_price', self.RegularPrice.price, label="Regular Price") + RegularPrice, RegularPrice.uuid == model.Product.regular_price_uuid)) + g.set_sorter('regular_price', RegularPrice.price) + g.set_filter('regular_price', RegularPrice.price, label="Regular Price") + # current_price g.set_label('current_price', "Cur. Price") 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( - self.CurrentPrice, self.CurrentPrice.uuid == model.Product.current_price_uuid)) - g.set_sorter('current_price', self.CurrentPrice.price) - g.set_filter('current_price', self.CurrentPrice.price, label="Current Price") + CurrentPrice, CurrentPrice.uuid == model.Product.current_price_uuid)) + g.set_sorter('current_price', CurrentPrice.price) + g.set_filter('current_price', CurrentPrice.price, label="Current Price") # tpr_price + TPRPrice = orm.aliased(model.ProductPrice) g.set_joiner('tpr_price', lambda q: q.outerjoin( - self.TPRPrice, self.TPRPrice.uuid == model.Product.tpr_price_uuid)) - g.set_filter('tpr_price', self.TPRPrice.price) + TPRPrice, TPRPrice.uuid == model.Product.tpr_price_uuid)) + g.set_filter('tpr_price', TPRPrice.price) # sale_price + SalePrice = orm.aliased(model.ProductPrice) g.set_joiner('sale_price', lambda q: q.outerjoin( - self.SalePrice, self.SalePrice.uuid == model.Product.sale_price_uuid)) - g.set_filter('sale_price', self.SalePrice.price) + SalePrice, SalePrice.uuid == model.Product.sale_price_uuid)) + g.set_filter('sale_price', SalePrice.price) # 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) def render_price(self, product, field): - if not product.not_for_sale: - price = product[field] - if price: - return self.products_handler.render_price(price) + # TODO: previously this rendered null (empty string) if + # product was marked "not for sale" - but why? important? + #if not product.not_for_sale: + price = product[field] + if price: + return self.products_handler.render_price(price) def render_current_price_for_grid(self, product, field): text = self.render_price(product, field) or ""