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} = {
|
const ${grid.vue_component} = {
|
||||||
template: '#${grid.vue_tagname}-template',
|
template: '#${grid.vue_tagname}-template',
|
||||||
|
mixins: [WuttaRequestMixin],
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
recordCount() {
|
recordCount() {
|
||||||
|
|
@ -658,8 +659,13 @@
|
||||||
// fetch new data
|
// fetch new data
|
||||||
params.filter = true
|
params.filter = true
|
||||||
this.fetchData(params)
|
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() {
|
getFilterParams() {
|
||||||
const params = {}
|
const params = {}
|
||||||
for (let filter of this.filters) {
|
for (let filter of this.filters) {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,30 @@
|
||||||
${parent.modify_vue_vars()}
|
${parent.modify_vue_vars()}
|
||||||
<script>
|
<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'):
|
% if master.deletable_bulk and master.has_perm('delete_bulk'):
|
||||||
|
|
||||||
${grid.vue_component}Data.deleteResultsSubmitting = false
|
${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()`.
|
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
|
.. attribute:: checkable
|
||||||
|
|
||||||
Boolean indicating whether the grid should expose per-row
|
Boolean indicating whether the grid should expose per-row
|
||||||
|
|
@ -481,6 +488,7 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
||||||
# features
|
# features
|
||||||
listable = True
|
listable = True
|
||||||
has_grid = True
|
has_grid = True
|
||||||
|
has_grid_totals = False
|
||||||
checkable = False
|
checkable = False
|
||||||
filterable = True
|
filterable = True
|
||||||
filter_defaults = None
|
filter_defaults = None
|
||||||
|
|
@ -586,6 +594,42 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
||||||
|
|
||||||
return self.render_to_response("index", context)
|
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
|
# create methods
|
||||||
##############################
|
##############################
|
||||||
|
|
@ -2612,7 +2656,7 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
||||||
|
|
||||||
def make_model_grid(
|
def make_model_grid(
|
||||||
self, session=None, **kwargs
|
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`
|
Create and return a :class:`~wuttaweb.grids.base.Grid`
|
||||||
instance for use with the :meth:`index()` view.
|
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:
|
if "tools" not in kwargs:
|
||||||
tools = []
|
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
|
# delete-bulk
|
||||||
if self.deletable_bulk and self.has_perm("delete_bulk"):
|
if self.deletable_bulk and self.has_perm("delete_bulk"):
|
||||||
tools.append(("delete-results", self.delete_bulk_make_button()))
|
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}",
|
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
|
# create
|
||||||
if cls.creatable:
|
if cls.creatable:
|
||||||
config.add_route(f"{route_prefix}.create", f"{url_prefix}/new")
|
config.add_route(f"{route_prefix}.create", f"{url_prefix}/new")
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ class TestMasterView(WebTestCase):
|
||||||
model_key="uuid",
|
model_key="uuid",
|
||||||
deletable_bulk=True,
|
deletable_bulk=True,
|
||||||
has_autocomplete=True,
|
has_autocomplete=True,
|
||||||
|
has_grid_totals=True,
|
||||||
downloadable=True,
|
downloadable=True,
|
||||||
executable=True,
|
executable=True,
|
||||||
configurable=True,
|
configurable=True,
|
||||||
|
|
@ -694,6 +695,15 @@ class TestMasterView(WebTestCase):
|
||||||
grid = view.make_model_grid(session=self.session)
|
grid = view.make_model_grid(session=self.session)
|
||||||
self.assertEqual(grid.tools, {})
|
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
|
# delete-results tool added if master/perms allow
|
||||||
with patch.multiple(
|
with patch.multiple(
|
||||||
mod.MasterView,
|
mod.MasterView,
|
||||||
|
|
@ -763,6 +773,16 @@ class TestMasterView(WebTestCase):
|
||||||
self.assertEqual(len(data), 1)
|
self.assertEqual(len(data), 1)
|
||||||
self.assertIs(data[0], setting)
|
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):
|
def test_configure_grid(self):
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue