diff --git a/frappe/boot.py b/frappe/boot.py index 695a4d754b..92c81d808e 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -85,6 +85,7 @@ def get_bootinfo(): bootinfo.points = get_energy_points(frappe.session.user) bootinfo.frequently_visited_links = frequently_visited_links() bootinfo.link_preview_doctypes = get_link_preview_doctypes() + bootinfo.filters_config = get_filters_config() return bootinfo @@ -297,3 +298,10 @@ def get_link_preview_doctypes(): link_preview_doctypes.append(custom.doc_type) return link_preview_doctypes + +def get_filters_config(): + filter_config = frappe._dict() + filter_hooks = frappe.get_hooks('filters_config') + for hook in filter_hooks: + filter_config.update(frappe.get_attr(hook)()) + return filter_config diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 5317e15362..5ee4e21d34 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -355,7 +355,9 @@ class DatabaseQuery(object): ifnull(`tabDocType`.`fieldname`, fallback) operator "value" """ - f = get_filter(self.doctype, f) + from frappe.boot import get_filters_config + additional_filters_config = get_filters_config() + f = get_filter(self.doctype, f, additional_filters_config) tname = ('`tab' + f.doctype + '`') if not tname in self.tables: @@ -369,7 +371,9 @@ class DatabaseQuery(object): can_be_null = True - # prepare in condition + if f.operator.lower() in additional_filters_config: + f.update(get_additional_filter_field(additional_filters_config, f, f.value)) + if f.operator.lower() in ('ancestors of', 'descendants of', 'not ancestors of', 'not descendants of'): values = f.value or '' @@ -853,4 +857,14 @@ def get_between_date_filter(value, df=None): frappe.db.format_date(from_date), frappe.db.format_date(to_date)) - return data \ No newline at end of file + return data + +def get_additional_filter_field(additional_filters_config, f, value): + additional_filter = additional_filters_config[f.operator.lower()] + f = frappe._dict(frappe.get_attr(additional_filter['get_field'])()) + if f.query_value: + for option in f.options: + option = frappe._dict(option) + if option.value == value: + f.value = option.query_value + return f \ No newline at end of file diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index 15f77fada5..cb1d705018 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -338,6 +338,11 @@ frappe.views.BaseList = class BaseList { : []; } + get_filter_value(fieldname) { + return this.get_filters_for_args().filter(f=> f[1] == fieldname)[0] && + this.get_filters_for_args().filter(f=> f[1] == fieldname)[0][3]; + } + get_args() { return { doctype: this.doctype, diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index 25ef982c6e..2b4252cdca 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -6,6 +6,12 @@ frappe.ui.Filter = class { } this.utils = frappe.ui.filter_utils; + this.set_conditions(); + this.set_conditions_from_config(); + this.make(); + } + + set_conditions() { this.conditions = [ ["=", __("Equals")], ["!=", __("Not Equals")], @@ -43,10 +49,21 @@ frappe.ui.Filter = class { Color: ["Between", 'Previous', 'Current', 'Next'], Check: this.conditions.map(c => c[0]).filter(c => c !== '=') }; - this.make(); - this.make_select(); - this.set_events(); - this.setup(); + } + + set_conditions_from_config() { + if (frappe.boot.filters_config) { + this.filters_config = frappe.boot.filters_config; + for (let key of Object.keys(this.filters_config)) { + const filter = this.filters_config[key]; + this.conditions.push([key, __(`{0}`, [filter.label])]); + for (let fieldtype of Object.keys(this.invalid_condition_map)) { + if (!filter.fieldtypes.includes(fieldtype)) { + this.invalid_condition_map[fieldtype].push(filter.label); + } + } + } + } } make() { @@ -54,6 +71,10 @@ frappe.ui.Filter = class { conditions: this.conditions })) .appendTo(this.parent.find('.filter-edit-area')); + + this.make_select(); + this.set_events(); + this.setup(); } make_select() { @@ -121,6 +142,7 @@ frappe.ui.Filter = class { } freeze() { + console.log('freeze here') this.update_filter_tag(); } @@ -230,7 +252,22 @@ frappe.ui.Filter = class { ]; } - this.make_field(df, cur.fieldtype); + if (this.filters_config[condition] && this.filters_config[condition].fieldtypes.includes(this.field.df.fieldtype)) { + let args = {}; + if (this.filters_config[condition].depends_on) { + const field_name = this.filters_config[condition].depends_on; + const filter_value = this.base_list.get_filter_value(field_name); + args[field_name] = filter_value; + } + frappe.xcall(this.filters_config[condition].get_field, args).then(field => { + df.fieldtype = field.fieldtype; + df.options = field.options; + df.fieldname = fieldname; + this.make_field(df, cur.fieldtype); + }); + } else { + this.make_field(df, cur.fieldtype); + } } make_field(df, old_fieldtype) { diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index db6398ca78..759ae6fafa 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -103,7 +103,8 @@ frappe.ui.FilterGroup = class { }, filter_items: (doctype, fieldname) => { return !this.filter_exists([doctype, fieldname]); - } + }, + base_list: this.base_list }; let filter = new frappe.ui.Filter(args); this.filters.push(filter); @@ -132,7 +133,7 @@ frappe.ui.FilterGroup = class { get_filters() { return this.filters.filter(f => f.field).map(f => { - f.freeze(); + // f.freeze(); return f.get_value(); }); // {}: this.list.update_standard_filters(values); diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 4e85e599e0..b9b6c962e5 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -1011,7 +1011,7 @@ def compare(val1, condition, val2): return ret -def get_filter(doctype, f): +def get_filter(doctype, f, filters_config=None): """Returns a _dict like { @@ -1047,6 +1047,13 @@ def get_filter(doctype, f): valid_operators = ("=", "!=", ">", "<", ">=", "<=", "like", "not like", "in", "not in", "is", "between", "descendants of", "ancestors of", "not descendants of", "not ancestors of", "previous", "current", "next") + + if filters_config: + additional_operators = [] + for key in filters_config: + additional_operators.append(key.lower()) + valid_operators = tuple(set(valid_operators + tuple(additional_operators))) + if f.operator.lower() not in valid_operators: frappe.throw(frappe._("Operator must be one of {0}").format(", ".join(valid_operators)))