diff --git a/cypress/integration/list_paging.js b/cypress/integration/list_paging.js
index cd4e2bc68d..494ca6ae74 100644
--- a/cypress/integration/list_paging.js
+++ b/cypress/integration/list_paging.js
@@ -37,6 +37,6 @@ context("List Paging", () => {
cy.get(".list-paging-area .list-count").should("contain.text", "500 of");
cy.get(".list-paging-area .btn-more").click();
- cy.get(".list-paging-area .list-count").should("contain.text", "1000 of");
+ cy.get(".list-paging-area .list-count").should("contain.text", "1,000 of");
});
});
diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py
index 598648cc4c..3891b78f43 100644
--- a/frappe/desk/reportview.py
+++ b/frappe/desk/reportview.py
@@ -14,6 +14,7 @@ from frappe.model.base_document import get_controller
from frappe.model.db_query import DatabaseQuery
from frappe.model.utils import is_virtual_doctype
from frappe.utils import add_user_info, cint, format_duration
+from frappe.utils.data import sbool
@frappe.whitelist()
@@ -51,13 +52,22 @@ def get_count() -> int:
if is_virtual_doctype(args.doctype):
controller = get_controller(args.doctype)
- data = frappe.call(controller.get_count, args=args, **args)
+ count = frappe.call(controller.get_count, args=args, **args)
else:
- distinct = "distinct " if args.distinct == "true" else ""
- args.fields = [f"count({distinct}`tab{args.doctype}`.name) as total_count"]
- data = execute(**args)[0].get("total_count")
+ args.distinct = sbool(args.distinct)
+ distinct = "distinct " if args.distinct else ""
+ args.limit = cint(args.limit)
+ fieldname = f"{distinct}`tab{args.doctype}`.name"
- return data
+ if args.limit:
+ args.fields = [fieldname]
+ partial_query = execute(**args, run=0)
+ count = frappe.db.sql(f"""select count(*) from ( {partial_query} ) p""")[0][0]
+ else:
+ args.fields = [f"count({fieldname}) as total_count"]
+ count = execute(**args)[0].get("total_count")
+
+ return count
def execute(doctype, *args, **kwargs):
diff --git a/frappe/public/js/frappe/db.js b/frappe/public/js/frappe/db.js
index 58329f16d7..b569205aa9 100644
--- a/frappe/public/js/frappe/db.js
+++ b/frappe/public/js/frappe/db.js
@@ -96,6 +96,7 @@ frappe.db = {
},
count: function (doctype, args = {}) {
let filters = args.filters || {};
+ let limit = args.limit;
// has a filter with childtable?
const distinct =
@@ -111,6 +112,7 @@ frappe.db = {
filters,
fields,
distinct,
+ limit,
});
},
get_link_options(doctype, txt = "", filters = {}) {
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 6adf057391..5c2cabfe54 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -28,6 +28,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
this.process_document_refreshes.bind(this),
2000
);
+ this.count_upper_bound = 1001;
}
has_permissions() {
@@ -500,9 +501,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
freeze() {
if (this.list_view_settings && !this.list_view_settings.disable_count) {
- this.$result
- .find(".list-count")
- .html(`${__("Refreshing", null, "Document count in list view")}...`);
+ this.get_count_element().html(
+ `${__("Refreshing", null, "Document count in list view")}...`
+ );
}
}
@@ -616,11 +617,33 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
}
render_count() {
- if (!this.list_view_settings.disable_count) {
- this.get_count_str().then((str) => {
- this.$result.find(".list-count").html(`${str}`);
- });
- }
+ if (this.list_view_settings.disable_count) return;
+
+ let me = this;
+ let $count = this.get_count_element();
+ this.get_count_str().then((count) => {
+ $count.html(`${count}`);
+ if (this.count_upper_bound) {
+ $count.attr(
+ "title",
+ __(
+ "The count shown is an estimated count. Click here to see the accurate count."
+ )
+ );
+ $count.tooltip({ delay: { show: 600, hide: 100 }, trigger: "hover" });
+ $count.on("click", () => {
+ me.count_upper_bound = 0;
+ $count.off("click");
+ $count.tooltip("disable");
+ me.freeze();
+ me.render_count();
+ });
+ }
+ });
+ }
+
+ get_count_element() {
+ return this.$result.find(".list-count");
}
get_header_html() {
@@ -946,12 +969,21 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
return frappe.db
.count(this.doctype, {
filters: this.get_filters_for_args(),
+ limit: this.count_upper_bound,
})
.then((total_count) => {
this.total_count = total_count || current_count;
this.count_without_children =
count_without_children !== current_count ? count_without_children : undefined;
- let str = __("{0} of {1}", [current_count, this.total_count]);
+
+ let count_str;
+ if (this.total_count === this.count_upper_bound) {
+ count_str = `${format_number(this.total_count - 1, null, 0)}+`;
+ } else {
+ count_str = format_number(this.total_count, null, 0);
+ }
+
+ let str = __("{0} of {1}", [format_number(current_count, null, 0), count_str]);
if (this.count_without_children) {
str = __("{0} of {1} ({2} rows with children)", [
count_without_children,
diff --git a/frappe/public/js/frappe/views/file/file_view.js b/frappe/public/js/frappe/views/file/file_view.js
index fb2a12a268..de787e10c6 100644
--- a/frappe/public/js/frappe/views/file/file_view.js
+++ b/frappe/public/js/frappe/views/file/file_view.js
@@ -231,6 +231,7 @@ frappe.views.FileView = class FileView extends frappe.views.ListView {
} else {
super.render();
this.render_header();
+ this.render_count();
}
}
diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js
index 725ebd06a0..8e54c43c97 100644
--- a/frappe/public/js/frappe/views/reports/report_view.js
+++ b/frappe/public/js/frappe/views/reports/report_view.js
@@ -220,19 +220,14 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
this.setup_datatable(this.data);
}
- render_count() {
- if (this.list_view_settings?.disable_count) {
- return;
- }
- let $list_count = this.$paging_area.find(".list-count");
- if (!$list_count.length) {
- $list_count = $("")
+ get_count_element() {
+ let $count = this.$paging_area.find(".list-count");
+ if (!$count.length) {
+ $count = $("")
.addClass("text-muted list-count")
.prependTo(this.$paging_area.find(".level-right"));
}
- this.get_count_str().then((str) => {
- $list_count.text(str);
- });
+ return $count;
}
on_update(data) {
diff --git a/frappe/tests/test_db_query.py b/frappe/tests/test_db_query.py
index 078bb55c42..f80f73c410 100644
--- a/frappe/tests/test_db_query.py
+++ b/frappe/tests/test_db_query.py
@@ -1187,7 +1187,7 @@ class TestReportView(FrappeTestCase):
"distinct": "false",
}
)
- list_filter_response = execute_cmd("frappe.desk.reportview.get_count")
+ count = execute_cmd("frappe.desk.reportview.get_count")
frappe.local.form_dict = frappe._dict(
{
"doctype": "DocType",
@@ -1196,8 +1196,8 @@ class TestReportView(FrappeTestCase):
}
)
dict_filter_response = execute_cmd("frappe.desk.reportview.get_count")
- self.assertIsInstance(list_filter_response, int)
- self.assertEqual(list_filter_response, dict_filter_response)
+ self.assertIsInstance(count, int)
+ self.assertEqual(count, dict_filter_response)
# test with child table filter
frappe.local.form_dict = frappe._dict(
@@ -1218,6 +1218,21 @@ class TestReportView(FrappeTestCase):
)[0][0]
self.assertEqual(child_filter_response, current_value)
+ # test with limit
+ limit = 2
+ frappe.local.form_dict = frappe._dict(
+ {
+ "doctype": "DocType",
+ "filters": [["DocType", "is_virtual", "=", 1]],
+ "fields": [],
+ "distinct": "false",
+ "limit": limit,
+ }
+ )
+ count = execute_cmd("frappe.desk.reportview.get_count")
+ self.assertIsInstance(count, int)
+ self.assertLessEqual(count, limit)
+
def test_reportview_get(self):
user = frappe.get_doc("User", "test@example.com")
add_child_table_to_blog_post()