Merge pull request #37026 from UmakanthKaspa/feat/web-form-dynamic-filters
feat: add dynamic filters to Web Form list view
This commit is contained in:
commit
a5d65c1b3f
4 changed files with 160 additions and 1 deletions
|
|
@ -46,6 +46,7 @@ frappe.ui.form.on("Web Form", {
|
||||||
frm.trigger("add_get_fields_button");
|
frm.trigger("add_get_fields_button");
|
||||||
frm.trigger("add_publish_button");
|
frm.trigger("add_publish_button");
|
||||||
frm.trigger("render_condition_table");
|
frm.trigger("render_condition_table");
|
||||||
|
frm.trigger("render_dynamic_filters_table");
|
||||||
},
|
},
|
||||||
|
|
||||||
login_required: function (frm) {
|
login_required: function (frm) {
|
||||||
|
|
@ -203,9 +204,15 @@ frappe.ui.form.on("Web Form", {
|
||||||
},
|
},
|
||||||
|
|
||||||
before_save: function (frm) {
|
before_save: function (frm) {
|
||||||
|
let dynamic_filters = JSON.parse(frm.doc.dynamic_filters_json || "null");
|
||||||
let static_filters = JSON.parse(frm.doc.condition_json || "[]");
|
let static_filters = JSON.parse(frm.doc.condition_json || "[]");
|
||||||
|
static_filters = frappe.dashboard_utils.remove_common_static_filter_values(
|
||||||
|
static_filters,
|
||||||
|
dynamic_filters
|
||||||
|
);
|
||||||
frm.set_value("condition_json", JSON.stringify(static_filters));
|
frm.set_value("condition_json", JSON.stringify(static_filters));
|
||||||
frm.trigger("render_condition_table");
|
frm.trigger("render_condition_table");
|
||||||
|
frm.trigger("render_dynamic_filters_table");
|
||||||
},
|
},
|
||||||
|
|
||||||
render_condition_table: function (frm) {
|
render_condition_table: function (frm) {
|
||||||
|
|
@ -308,6 +315,106 @@ frappe.ui.form.on("Web Form", {
|
||||||
dialog.set_values(filters);
|
dialog.set_values(filters);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
render_dynamic_filters_table(frm) {
|
||||||
|
let wrapper = $(frm.get_field("dynamic_filters_json").wrapper).empty();
|
||||||
|
|
||||||
|
frm.dynamic_filter_table = $(`<table class="table table-bordered" style="cursor:${
|
||||||
|
frm.has_perm("write") ? "pointer" : "default"
|
||||||
|
}; margin:0px;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 20%">${__("Filter")}</th>
|
||||||
|
<th style="width: 20%">${__("Condition")}</th>
|
||||||
|
<th>${__("Value")}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>`).appendTo(wrapper);
|
||||||
|
|
||||||
|
frm.dynamic_filters =
|
||||||
|
frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
|
||||||
|
? JSON.parse(frm.doc.dynamic_filters_json)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
frm.trigger("set_dynamic_filters_in_table");
|
||||||
|
|
||||||
|
let filters = JSON.parse(frm.doc.condition_json || "[]");
|
||||||
|
|
||||||
|
let fields = frappe.dashboard_utils.get_fields_for_dynamic_filter_dialog(
|
||||||
|
true,
|
||||||
|
filters,
|
||||||
|
frm.dynamic_filters
|
||||||
|
);
|
||||||
|
|
||||||
|
// Override description to show Python expressions (evaluated server-side)
|
||||||
|
let desc_field = fields.find((f) => f.fieldname === "description");
|
||||||
|
if (desc_field) {
|
||||||
|
desc_field.options = `<div>
|
||||||
|
<p>${__("Set dynamic filter values as Python expressions.")}</p>
|
||||||
|
<p>${__("For example:")}
|
||||||
|
<code>frappe.session.user</code> ${__("or")}
|
||||||
|
<code>frappe.utils.now()</code>
|
||||||
|
</p>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
frm.dynamic_filter_table.on("click", () => {
|
||||||
|
if (!frm.has_perm("write")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frappe.boot.developer_mode && frm.doc.is_standard) {
|
||||||
|
frappe.throw(__("Cannot edit filters for standard Web Forms"));
|
||||||
|
}
|
||||||
|
let dialog = new frappe.ui.Dialog({
|
||||||
|
title: __("Set Dynamic Filters"),
|
||||||
|
fields: fields,
|
||||||
|
primary_action: () => {
|
||||||
|
let values = dialog.get_values();
|
||||||
|
dialog.hide();
|
||||||
|
let dynamic_filters = [];
|
||||||
|
for (let key of Object.keys(values)) {
|
||||||
|
let [doctype, fieldname] = key.split(":");
|
||||||
|
dynamic_filters.push([doctype, fieldname, "=", values[key]]);
|
||||||
|
}
|
||||||
|
frm.set_value("dynamic_filters_json", JSON.stringify(dynamic_filters));
|
||||||
|
frm.trigger("set_dynamic_filters_in_table");
|
||||||
|
},
|
||||||
|
primary_action_label: __("Set"),
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
if (frm.dynamic_filters) {
|
||||||
|
let filter_values = {};
|
||||||
|
frm.dynamic_filters.forEach((f) => {
|
||||||
|
filter_values[f[0] + ":" + f[1]] = f[3];
|
||||||
|
});
|
||||||
|
dialog.set_values(filter_values);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
set_dynamic_filters_in_table: function (frm) {
|
||||||
|
frm.dynamic_filters =
|
||||||
|
frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
|
||||||
|
? JSON.parse(frm.doc.dynamic_filters_json)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!frm.dynamic_filters) {
|
||||||
|
const filter_row = $(`<tr><td colspan="3" class="text-muted text-center">
|
||||||
|
${__("Click to Set Dynamic Filters")}</td></tr>`);
|
||||||
|
frm.dynamic_filter_table.find("tbody").html(filter_row);
|
||||||
|
} else {
|
||||||
|
let filter_rows = "";
|
||||||
|
frm.dynamic_filters.forEach((filter) => {
|
||||||
|
filter_rows += `<tr>
|
||||||
|
<td>${filter[1]}</td>
|
||||||
|
<td>${filter[2] || ""}</td>
|
||||||
|
<td>${filter[3]}</td>
|
||||||
|
</tr>`;
|
||||||
|
});
|
||||||
|
frm.dynamic_filter_table.find("tbody").html(filter_rows);
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on("Web Form List Column", {
|
frappe.ui.form.on("Web Form List Column", {
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,8 @@
|
||||||
"condition_section",
|
"condition_section",
|
||||||
"condition_description",
|
"condition_description",
|
||||||
"condition_json",
|
"condition_json",
|
||||||
|
"dynamic_filters_section",
|
||||||
|
"dynamic_filters_json",
|
||||||
"section_break_3",
|
"section_break_3",
|
||||||
"list_setting_message",
|
"list_setting_message",
|
||||||
"show_list",
|
"show_list",
|
||||||
|
|
@ -424,12 +426,22 @@
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_hhec",
|
"fieldname": "column_break_hhec",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dynamic_filters_json",
|
||||||
|
"fieldtype": "JSON",
|
||||||
|
"label": "Dynamic Filters JSON"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dynamic_filters_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Dynamic Filters"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-edit",
|
"icon": "icon-edit",
|
||||||
"is_published_field": "published",
|
"is_published_field": "published",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-12-12 16:29:35.806107",
|
"modified": "2026-02-13 19:59:19.807594",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Website",
|
"module": "Website",
|
||||||
"name": "Web Form",
|
"name": "Web Form",
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ class WebForm(WebsiteGenerator):
|
||||||
condition_json: DF.JSON | None
|
condition_json: DF.JSON | None
|
||||||
custom_css: DF.Code | None
|
custom_css: DF.Code | None
|
||||||
doc_type: DF.Link
|
doc_type: DF.Link
|
||||||
|
dynamic_filters_json: DF.JSON | None
|
||||||
hide_footer: DF.Check
|
hide_footer: DF.Check
|
||||||
hide_navbar: DF.Check
|
hide_navbar: DF.Check
|
||||||
introduction_text: DF.TextEditor | None
|
introduction_text: DF.TextEditor | None
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,11 @@ def get_list_data(
|
||||||
if list_context.filters:
|
if list_context.filters:
|
||||||
filters.update(list_context.filters)
|
filters.update(list_context.filters)
|
||||||
|
|
||||||
|
if web_form_name:
|
||||||
|
dynamic_filters = get_dynamic_filters(web_form_name)
|
||||||
|
if dynamic_filters:
|
||||||
|
filters.update(dynamic_filters)
|
||||||
|
|
||||||
_get_list = list_context.get_list or get_list
|
_get_list = list_context.get_list or get_list
|
||||||
|
|
||||||
kwargs = dict(
|
kwargs = dict(
|
||||||
|
|
@ -194,3 +199,37 @@ def get_list(
|
||||||
order_by=order_by,
|
order_by=order_by,
|
||||||
distinct=distinct,
|
distinct=distinct,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_dynamic_filters(web_form_name):
|
||||||
|
"""Evaluate dynamic filter expressions from Web Form.
|
||||||
|
Uses same safe_eval + get_workflow_safe_globals pattern as Workflow."""
|
||||||
|
from frappe.model.workflow import get_workflow_safe_globals
|
||||||
|
|
||||||
|
web_form = frappe.get_cached_doc("Web Form", web_form_name)
|
||||||
|
|
||||||
|
if not web_form.dynamic_filters_json:
|
||||||
|
return None
|
||||||
|
|
||||||
|
dynamic_filters = json.loads(web_form.dynamic_filters_json)
|
||||||
|
if not dynamic_filters:
|
||||||
|
return None
|
||||||
|
|
||||||
|
safe_globals = get_workflow_safe_globals()
|
||||||
|
safe_globals["frappe"]["defaults"] = frappe._dict(
|
||||||
|
get_user_default=frappe.defaults.get_user_default,
|
||||||
|
get_global_default=frappe.defaults.get_global_default,
|
||||||
|
)
|
||||||
|
|
||||||
|
evaluated = {}
|
||||||
|
for f in dynamic_filters:
|
||||||
|
try:
|
||||||
|
value = frappe.safe_eval(f[3], safe_globals)
|
||||||
|
evaluated[f[1]] = [f[2], value]
|
||||||
|
except Exception as e:
|
||||||
|
frappe.throw(
|
||||||
|
_("Invalid expression in Web Form Dynamic Filter for {0}: {1}").format(f[1], e),
|
||||||
|
title=_("Dynamic Filter Error"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return evaluated
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue