diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py
index ca7558c77a..116255106c 100644
--- a/frappe/model/db_query.py
+++ b/frappe/model/db_query.py
@@ -409,6 +409,19 @@ class DatabaseQuery(object):
value = get_time(f.value).strftime("%H:%M:%S.%f")
fallback = "'00:00:00'"
+ elif f.operator.lower() == "is":
+ if f.value == 'set':
+ f.operator = '!='
+ elif f.value == 'not set':
+ f.operator = '='
+
+ value = ""
+ fallback = '""'
+ can_be_null = True
+
+ if 'ifnull' not in column_name:
+ column_name = 'ifnull({}, {})'.format(column_name, fallback)
+
elif f.operator.lower() in ("like", "not like") or (isinstance(f.value, string_types) and
(not df or df.fieldtype not in ["Float", "Int", "Currency", "Percent", "Check"])):
value = "" if f.value==None else f.value
@@ -417,7 +430,6 @@ class DatabaseQuery(object):
if f.operator.lower() in ("like", "not like") and isinstance(value, string_types):
# because "like" uses backslash (\) for escaping
value = value.replace("\\", "\\\\").replace("%", "%%")
-
else:
value = flt(f.value)
fallback = 0
diff --git a/frappe/public/js/frappe/ui/filters/edit_filter.html b/frappe/public/js/frappe/ui/filters/edit_filter.html
index 88f3a5b20c..50b1e1b4e6 100644
--- a/frappe/public/js/frappe/ui/filters/edit_filter.html
+++ b/frappe/public/js/frappe/ui/filters/edit_filter.html
@@ -3,21 +3,9 @@
diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js
index 8922bd9b0b..e8220bd07c 100644
--- a/frappe/public/js/frappe/ui/filters/filter.js
+++ b/frappe/public/js/frappe/ui/filters/filter.js
@@ -13,13 +13,16 @@ frappe.ui.Filter = class {
["not like", __("Not Like")],
["in", __("In")],
["not in", __("Not In")],
+ ["is", __("Is")],
[">", ">"],
["<", "<"],
[">=", ">="],
["<=", "<="],
["Between", __("Between")],
["descendants of", __("Descendants Of")],
- ["ancestors of", __("Ancestors Of")]
+ ["not descendants of", __("Not Descendants Of")],
+ ["ancestors of", __("Ancestors Of")],
+ ["not ancestors of", __("Not Ancestors Of")]
];
this.invalid_condition_map = {
Date: ['like', 'not like'],
@@ -37,7 +40,9 @@ frappe.ui.Filter = class {
}
make() {
- this.filter_edit_area = $(frappe.render_template("edit_filter", {}))
+ this.filter_edit_area = $(frappe.render_template("edit_filter", {
+ conditions: this.conditions
+ }))
.appendTo(this.parent.find('.filter-edit-area'));
}
@@ -397,5 +402,12 @@ frappe.ui.filter_utils = {
if(condition == "Between" && (df.fieldtype == 'Date' || df.fieldtype == 'Datetime')){
df.fieldtype = 'DateRange';
}
+ if (condition === 'is') {
+ df.fieldtype = 'Select';
+ df.options = [
+ { label: __('Set'), value: 'set' },
+ { label: __('Not Set'), value: 'not set' },
+ ];
+ }
}
};
diff --git a/frappe/tests/test_db_query.py b/frappe/tests/test_db_query.py
index 132fb0d01d..484b17e024 100644
--- a/frappe/tests/test_db_query.py
+++ b/frappe/tests/test_db_query.py
@@ -336,6 +336,17 @@ class TestReportview(unittest.TestCase):
self.assertTrue(len(frappe.get_all('File', {'name': ('not ancestors of', 'Home')})) == len(frappe.get_all('File')))
+ def test_is_set_is_not_set(self):
+ res = DatabaseQuery("DocType").execute(filters={"autoname": ["is", "not set"]})
+ self.assertTrue({'name': 'Integration Request'} in res)
+ self.assertTrue({'name': 'User'} in res)
+ self.assertFalse({'name': 'Blogger'} in res)
+
+ res = DatabaseQuery("DocType").execute(filters={"autoname": ["is", "set"]})
+ self.assertTrue({'name': 'DocField'} in res)
+ self.assertTrue({'name': 'Prepared Report'} in res)
+ self.assertFalse({'name': 'Property Setter'} in res)
+
def create_event(subject="_Test Event", starts_on=None):
""" create a test event """
diff --git a/frappe/utils/data.py b/frappe/utils/data.py
index dd3d9e3c94..3e92d3e5b9 100644
--- a/frappe/utils/data.py
+++ b/frappe/utils/data.py
@@ -835,7 +835,8 @@ def get_filter(doctype, f):
f.operator = "="
valid_operators = ("=", "!=", ">", "<", ">=", "<=", "like", "not like", "in", "not in",
- "between", "descendants of", "ancestors of", "not descendants of", "not ancestors of")
+ "between", "descendants of", "ancestors of", "not descendants of", "not ancestors of", "is")
+
if f.operator.lower() not in valid_operators:
frappe.throw(frappe._("Operator must be one of {0}").format(", ".join(valid_operators)))