How to Call a Python Function from a Client Script for set_query in ERPNext
Call Custom Function from JavascriptLearn to dynamically filter a Link Field in Frappe/ERPNext by calling a whitelisted Python server method from a Client Script using frappe.call and cur_frm.set_query. This is ideal for filters requiring complex server-side logic.
In Frappe and ERPNext, `cur_frm.set_query` is a powerful client-side method used to apply dynamic filters to Link and Dynamic Link fields. However, its query parameter can only point to a server method and cannot pass dynamic arguments directly from the form.
This code snippet demonstrates a common and effective pattern to overcome this limitation. It uses `frappe.call` to execute a preparatory server-side Python function with dynamic arguments (like the current document's name). This function processes the required logic and stores the results. The `set_query` then points to a second, simpler Python function that retrieves and returns these prepared results, effectively creating a dynamic, context-aware filter.
1 // On Parent Field 2 cur_frm.set_query("select_item", function(doc, cdt, cdn) { 3
4 frappe.call({ 5 method: "eie.api.get_item_code", 6 args: { 7 doc: cur_frm.doc.name 8 } 9 }); 10 return { 11 query: "eie.api.item_code_query" 12 13 }; 14 15 }); 16
17 // On Child Table Field. 'items' is the child table fieldname 18 cur_frm.set_query("main_item", "items", function(doc, cdt, cdn) { 19
20 frappe.call({ 21 method: "eie.api.get_item_code", 22 args: { 23 doc: cur_frm.doc.name 24 } 25 }); 26 return { 27 query: "eie.api.item_code_query" 28 29 }; 30 31 });
1 from __future__ import unicode_literals 2 import frappe 3 from frappe import _ 4
5
6 @frappe.whitelist() 7 def get_item_code(doc): 8 # Assuming the DocType is 'Quotation' 9 quote = frappe.get_doc("Quotation", doc) 10 11 # NOTE: Using a global variable is not recommended in production due to concurrency issues. 12 # See Troubleshooting section for a better approach using frappe.cache. 13 global item_list 14 item_list = [] 15 16 for row in quote.items: 17 # Example logic: add item_code to list if 'main_item' is not set 18 if not row.main_item: 19 item_list.append(row.item_code) 20 21
22 @frappe.whitelist() 23 def item_code_query(doctype, txt, searchfield, start, page_len, filters): 24 # This function simply returns the list prepared by get_item_code 25 # The 'filters' argument is passed by set_query but not used here. 26 # The global variable 'item_list' is accessed here. 27 if txt: 28 return [item for item in item_list if txt.lower() in item.lower()] 29 return item_list
Understanding This Code
What It Does
Applies a dynamic, server-side filter to a Link Field on both a parent document and within a child table by executing custom Python logic.
When To Use
Use this pattern when a Link Field's filter depends on complex logic that must be executed on the server, such as checking data in other documents or processing values from the current form's child table before displaying options.
Prerequisites
- •A custom Frappe app where you can add server-side Python scripts.
- •A DocType with a Link Field that requires dynamic filtering.
- •Basic understanding of Frappe Client Scripts and whitelisted server methods.
Key Concepts
Important ideas to understand in this code
cur_frm.set_query
A client-side API in Frappe used to define a dynamic query for Link or Dynamic Link fields. It returns an object containing the path to a whitelisted Python function that will provide the filtered list of options.
Learn morefrappe.call
The primary method for making synchronous or asynchronous calls from the client-side (Javascript) to the server-side (Python). It's essential for triggering server logic from form events.
Learn more@frappe.whitelist()
A Python decorator that exposes a server-side function, making it accessible from the client-side via `frappe.call` or other API endpoints. Any function called from the client must be whitelisted.
Learn moreClient-Server Interaction Pattern
This snippet uses a two-step pattern: `frappe.call` runs a function to prepare data with context (e.g., doc name), and `set_query` calls another function to retrieve that prepared data. This decouples data preparation from data retrieval, working around `set_query`'s limitations.
Learn moreStep-by-Step Tutorial
Follow along to understand how this code works
Create the Server-Side Python File
In your custom Frappe app (e.g., 'eie'), create a file named `api.py` inside the app's main module directory (e.g., `eie/api.py`). This file will house our whitelisted Python functions.
# eie/api.py
from __future__ import unicode_literals
import frappe
# Functions will be added hereDefine the Data Preparation Function
Add the `get_item_code` function to `api.py`. This function is whitelisted to be callable from the client. It takes the document name as an argument, fetches the full document, performs the filtering logic, and stores the result in a global variable.
@frappe.whitelist()
def get_item_code(doc):
quote = frappe.get_doc("Quotation", doc)
global item_list
item_list = []
for row in quote.items:
if not row.main_item:
item_list.append(row.item_code)Define the Query Function
Add the `item_code_query` function to `api.py`. This is the function that `set_query` will call directly. It takes standard arguments from `set_query` and simply returns the data prepared by `get_item_code`.
@frappe.whitelist()
def item_code_query(doctype, txt, searchfield, start, page_len, filters):
# This function simply returns the list prepared by get_item_code
# You can add search logic based on 'txt' for better UX
if txt:
return [item for item in item_list if txt.lower() in item.lower()]
return item_listImplement the Client Script
Create a new Client Script and apply it to your DocType. In the script, use `cur_frm.set_query` for the desired Link Field. The function will first use `frappe.call` to execute your data preparation function, then return the path to your query function.
// Attach to a form event like 'onload' or a field's 'change' event
frappe.ui.form.on('Quotation', {
refresh: function(frm) {
// Setup query for a parent field
frm.set_query("select_item", function(doc, cdt, cdn) {
frappe.call({
method: "eie.api.get_item_code",
args: { doc: doc.name }
});
return { query: "eie.api.item_code_query" };
});
// Setup query for a child table field
frm.set_query("main_item", "items", function(doc, cdt, cdn) {
frappe.call({
method: "eie.api.get_item_code",
args: { doc: doc.name }
});
return { query: "eie.api.item_code_query" };
});
}
});Common Issues & Solutions
Troubleshoot problems you might encounter