From f9bfbfec9843cf3c38c3dbc8b203cc2fcaeb69ca Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 5 Aug 2022 06:22:37 +0000 Subject: [PATCH] perf: reduce DB calls made in `get_fetch_values` (#17671) * perf: reduce DB calls made in `get_fetch_values` * fix: ensure return value is same as before * test: add test for `frappe.model.utils.get_fetch_values` --- frappe/model/utils/__init__.py | 43 ++++++++++++++++++++++++++------ frappe/tests/test_model_utils.py | 28 +++++++++++++++++++++ 2 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 frappe/tests/test_model_utils.py diff --git a/frappe/model/utils/__init__.py b/frappe/model/utils/__init__.py index 1445999639..2220b3904f 100644 --- a/frappe/model/utils/__init__.py +++ b/frappe/model/utils/__init__.py @@ -90,15 +90,42 @@ def get_fetch_values(doctype, fieldname, value): :param fieldname: Link fieldname selected :param value: Value selected """ - out = {} - meta = frappe.get_meta(doctype) - link_df = meta.get_field(fieldname) - for df in meta.get_fields_to_fetch(fieldname): - # example shipping_address.gistin - link_field, source_fieldname = df.fetch_from.split(".", 1) - out[df.fieldname] = frappe.db.get_value(link_df.options, value, source_fieldname) - return out + result = frappe._dict() + meta = frappe.get_meta(doctype) + + # fieldname in target doctype: fieldname in source doctype + fields_to_fetch = { + df.fieldname: df.fetch_from.split(".", 1)[1] for df in meta.get_fields_to_fetch(fieldname) + } + + # nothing to fetch + if not fields_to_fetch: + return result + + # initialise empty values for target fields + for target_fieldname in fields_to_fetch: + result[target_fieldname] = None + + # fetch only if Link field has a truthy value + if not value: + return result + + db_values = frappe.db.get_value( + meta.get_options(fieldname), # source doctype + value, + tuple(set(fields_to_fetch.values())), # unique source fieldnames + as_dict=True, + ) + + # if value doesn't exist in source doctype, get_value returns None + if not db_values: + return result + + for target_fieldname, source_fieldname in fields_to_fetch.items(): + result[target_fieldname] = db_values.get(source_fieldname) + + return result @site_cache(maxsize=128) diff --git a/frappe/tests/test_model_utils.py b/frappe/tests/test_model_utils.py new file mode 100644 index 0000000000..95e3e714a7 --- /dev/null +++ b/frappe/tests/test_model_utils.py @@ -0,0 +1,28 @@ +import unittest + +import frappe +from frappe.model.utils import get_fetch_values + + +class TestModelUtils(unittest.TestCase): + def test_get_fetch_values(self): + doctype = "ToDo" + + # no fields to fetch + self.assertEqual(get_fetch_values(doctype, "role", "System Manager"), {}) + + # no value + self.assertEqual(get_fetch_values(doctype, "assigned_by", None), {"assigned_by_full_name": None}) + + # no db values + self.assertEqual( + get_fetch_values(doctype, "assigned_by", "~not-a-user~"), {"assigned_by_full_name": None} + ) + + # valid db values + user = "test@example.com" + full_name = frappe.db.get_value("User", user, "full_name") + + self.assertEqual( + get_fetch_values(doctype, "assigned_by", user), {"assigned_by_full_name": full_name} + )