fix: add basic "Show Totals" feature for main index grids
subclass must define what to display
This commit is contained in:
parent
184aa0630d
commit
bbcc17b064
4 changed files with 133 additions and 1 deletions
|
|
@ -328,6 +328,7 @@
|
|||
|
||||
const ${grid.vue_component} = {
|
||||
template: '#${grid.vue_tagname}-template',
|
||||
mixins: [WuttaRequestMixin],
|
||||
computed: {
|
||||
|
||||
recordCount() {
|
||||
|
|
@ -658,8 +659,13 @@
|
|||
// fetch new data
|
||||
params.filter = true
|
||||
this.fetchData(params)
|
||||
## nb. this is used for "show totals" feature in master/index template
|
||||
this.appliedFiltersHook()
|
||||
},
|
||||
|
||||
## nb. for now each grid can have "just one" (or no) hook
|
||||
appliedFiltersHook() {},
|
||||
|
||||
getFilterParams() {
|
||||
const params = {}
|
||||
for (let filter of this.filters) {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,30 @@
|
|||
${parent.modify_vue_vars()}
|
||||
<script>
|
||||
|
||||
% if master.has_grid_totals:
|
||||
|
||||
${grid.vue_component}Data.gridTotalsHTML = ""
|
||||
${grid.vue_component}Data.gridTotalsFetching = false
|
||||
|
||||
${grid.vue_component}.methods.gridTotalsFetch = function() {
|
||||
this.gridTotalsFetching = true
|
||||
|
||||
const url = "${url(f'{route_prefix}.fetch_grid_totals')}"
|
||||
this.wuttaGET(url, {}, response => {
|
||||
this.gridTotalsHTML = response.data.totals_html
|
||||
this.gridTotalsFetching = false
|
||||
}, response => {
|
||||
this.gridTotalsFetching = false
|
||||
})
|
||||
}
|
||||
|
||||
${grid.vue_component}.methods.appliedFiltersHook = function() {
|
||||
this.gridTotalsHTML = ""
|
||||
this.gridTotalsFetching = false
|
||||
}
|
||||
|
||||
% endif
|
||||
|
||||
% if master.deletable_bulk and master.has_perm('delete_bulk'):
|
||||
|
||||
${grid.vue_component}Data.deleteResultsSubmitting = false
|
||||
|
|
|
|||
|
|
@ -193,6 +193,13 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
|||
|
||||
This is optional; see also :meth:`get_grid_columns()`.
|
||||
|
||||
.. attribute:: has_grid_totals
|
||||
|
||||
Boolean indicating whether the main grid supports a "Show
|
||||
Totals" feature; this is false by default.
|
||||
|
||||
See also :meth:`fetch_grid_totals()`.
|
||||
|
||||
.. attribute:: checkable
|
||||
|
||||
Boolean indicating whether the grid should expose per-row
|
||||
|
|
@ -481,6 +488,7 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
|||
# features
|
||||
listable = True
|
||||
has_grid = True
|
||||
has_grid_totals = False
|
||||
checkable = False
|
||||
filterable = True
|
||||
filter_defaults = None
|
||||
|
|
@ -586,6 +594,42 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
|||
|
||||
return self.render_to_response("index", context)
|
||||
|
||||
def fetch_grid_totals(self):
|
||||
"""
|
||||
Should return the "totals info" for the main grid, if
|
||||
applicable. Only relevant when :attr:`has_grid_totals` is
|
||||
true.
|
||||
|
||||
This method is called "on demand" from the client side; totals
|
||||
are not calculated / shown by default when a grid is first
|
||||
displayed on the page.
|
||||
|
||||
Subclass should override this method to calculate and return
|
||||
the customized info. Default logic within the template is
|
||||
expecting a ``totals_html`` key within the dict; this will be
|
||||
rendered as-is on the page. For instance::
|
||||
|
||||
def fetch_grid_totals(self):
|
||||
|
||||
from webhelpers2.html import HTML
|
||||
|
||||
# get current data set from grid
|
||||
# nb. this will be filtered and sorted but *not*
|
||||
# paginated; we want to include all results.
|
||||
grid = self.make_model_grid(paginated=False)
|
||||
rows = grid.get_visible_data()
|
||||
|
||||
# calculate total
|
||||
foo_total = sum([row.foo_amount for row in rows])
|
||||
|
||||
# render as <span> tag
|
||||
html = HTML.tag("span", c=f"Foo Total: {foo_total:0.2f}")
|
||||
return {"totals_html": html}
|
||||
|
||||
:returns: Dict of totals info.
|
||||
"""
|
||||
return {"totals_html": "TODO: totals go here"}
|
||||
|
||||
##############################
|
||||
# create methods
|
||||
##############################
|
||||
|
|
@ -2612,7 +2656,7 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
|||
|
||||
def make_model_grid(
|
||||
self, session=None, **kwargs
|
||||
): # pylint: disable=too-many-branches
|
||||
): # pylint: disable=too-many-branches,too-many-statements
|
||||
"""
|
||||
Create and return a :class:`~wuttaweb.grids.base.Grid`
|
||||
instance for use with the :meth:`index()` view.
|
||||
|
|
@ -2678,6 +2722,29 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
|||
if "tools" not in kwargs:
|
||||
tools = []
|
||||
|
||||
# show totals
|
||||
if self.has_grid_totals:
|
||||
button = self.make_button(
|
||||
"{{ gridTotalsFetching ? 'Working, please wait...' : 'Show Totals' }}",
|
||||
icon_left="calculator",
|
||||
**{
|
||||
"v-if": "!gridTotalsHTML.length",
|
||||
":disabled": "gridTotalsFetching",
|
||||
"@click": "gridTotalsFetch()",
|
||||
},
|
||||
)
|
||||
display = HTML.tag(
|
||||
"div",
|
||||
class_="control",
|
||||
style="margin: 0 0.5rem;",
|
||||
**{
|
||||
"v-if": "gridTotalsHTML.length",
|
||||
"v-html": "gridTotalsHTML",
|
||||
},
|
||||
)
|
||||
wrapper = HTML.tag("div", c=[button, display])
|
||||
tools.append(("show-totals", wrapper))
|
||||
|
||||
# delete-bulk
|
||||
if self.deletable_bulk and self.has_perm("delete_bulk"):
|
||||
tools.append(("delete-results", self.delete_bulk_make_button()))
|
||||
|
|
@ -4310,6 +4377,21 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
|||
f"Browse / search {model_title_plural}",
|
||||
)
|
||||
|
||||
# grid totals
|
||||
if cls.has_grid_totals:
|
||||
config.add_route(
|
||||
f"{route_prefix}.fetch_grid_totals",
|
||||
f"{url_prefix}/fetch-grid-totals",
|
||||
request_method="GET",
|
||||
)
|
||||
config.add_view(
|
||||
cls,
|
||||
attr="fetch_grid_totals",
|
||||
route_name=f"{route_prefix}.fetch_grid_totals",
|
||||
permission=f"{permission_prefix}.list",
|
||||
renderer="json",
|
||||
)
|
||||
|
||||
# create
|
||||
if cls.creatable:
|
||||
config.add_route(f"{route_prefix}.create", f"{url_prefix}/new")
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ class TestMasterView(WebTestCase):
|
|||
model_key="uuid",
|
||||
deletable_bulk=True,
|
||||
has_autocomplete=True,
|
||||
has_grid_totals=True,
|
||||
downloadable=True,
|
||||
executable=True,
|
||||
configurable=True,
|
||||
|
|
@ -694,6 +695,15 @@ class TestMasterView(WebTestCase):
|
|||
grid = view.make_model_grid(session=self.session)
|
||||
self.assertEqual(grid.tools, {})
|
||||
|
||||
# "show totals" tool added when applicable
|
||||
with patch.multiple(
|
||||
mod.MasterView, model_class=model.Setting, has_grid_totals=True
|
||||
):
|
||||
view = self.make_view()
|
||||
grid = view.make_model_grid(session=self.session)
|
||||
self.assertEqual(len(grid.tools), 1)
|
||||
self.assertIn("show-totals", grid.tools)
|
||||
|
||||
# delete-results tool added if master/perms allow
|
||||
with patch.multiple(
|
||||
mod.MasterView,
|
||||
|
|
@ -763,6 +773,16 @@ class TestMasterView(WebTestCase):
|
|||
self.assertEqual(len(data), 1)
|
||||
self.assertIs(data[0], setting)
|
||||
|
||||
def test_fetch_grid_totals(self):
|
||||
view = self.make_view()
|
||||
# with patch.object(view, "has_grid_totals", new=True):
|
||||
|
||||
# nb. default logic just returns generic stub info
|
||||
totals = view.fetch_grid_totals()
|
||||
self.assertIsInstance(totals, dict)
|
||||
self.assertIn("totals_html", totals)
|
||||
self.assertEqual(totals["totals_html"], "TODO: totals go here")
|
||||
|
||||
def test_configure_grid(self):
|
||||
model = self.app.model
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue