Compare commits
No commits in common. "8af4d741727a942352087020a44107ee5e73d9d3" and "184aa0630d43804bdda19cfbee4c02ec4405bca5" have entirely different histories.
8af4d74172
...
184aa0630d
6 changed files with 2 additions and 140 deletions
|
|
@ -5,12 +5,6 @@ All notable changes to wuttaweb will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## v0.30.3 (2026-05-06)
|
|
||||||
|
|
||||||
### Fix
|
|
||||||
|
|
||||||
- add basic "Show Totals" feature for main index grids
|
|
||||||
|
|
||||||
## v0.30.2 (2026-03-22)
|
## v0.30.2 (2026-03-22)
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "WuttaWeb"
|
name = "WuttaWeb"
|
||||||
version = "0.30.3"
|
version = "0.30.2"
|
||||||
description = "Web App for Wutta Framework"
|
description = "Web App for Wutta Framework"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
|
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
|
||||||
|
|
|
||||||
|
|
@ -328,7 +328,6 @@
|
||||||
|
|
||||||
const ${grid.vue_component} = {
|
const ${grid.vue_component} = {
|
||||||
template: '#${grid.vue_tagname}-template',
|
template: '#${grid.vue_tagname}-template',
|
||||||
mixins: [WuttaRequestMixin],
|
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
recordCount() {
|
recordCount() {
|
||||||
|
|
@ -659,13 +658,8 @@
|
||||||
// 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,30 +31,6 @@
|
||||||
${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,13 +193,6 @@ 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
|
||||||
|
|
@ -488,7 +481,6 @@ 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
|
||||||
|
|
@ -594,42 +586,6 @@ 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
|
||||||
##############################
|
##############################
|
||||||
|
|
@ -2656,7 +2612,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,too-many-statements
|
): # pylint: disable=too-many-branches
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
|
|
@ -2722,29 +2678,6 @@ 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()))
|
||||||
|
|
@ -4377,21 +4310,6 @@ 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,7 +34,6 @@ 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,
|
||||||
|
|
@ -695,15 +694,6 @@ 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,
|
||||||
|
|
@ -773,16 +763,6 @@ 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