* refactored code to add support in filters to get descendant or ancestor for tree type documents * added semicolon and comments to imporve code quality * refactored code to add not ancestor and not descendants of filters * added test cases for ancestors of and descendants of * Translate labelsversion-14
@@ -45,8 +45,8 @@ def get_bootinfo(): | |||
bootinfo.all_domains = [d.get("name") for d in frappe.get_all("Domain")] | |||
bootinfo.module_app = frappe.local.module_app | |||
bootinfo.single_types = frappe.db.sql_list("""select name from tabDocType | |||
where issingle=1""") | |||
bootinfo.single_types = [d.name for d in frappe.get_all('DocType', {'issingle': 1})] | |||
bootinfo.nested_set_doctypes = [d.parent for d in frappe.get_all('DocField', {'fieldname': 'lft'}, ['parent'])] | |||
add_home_page(bootinfo, doclist) | |||
bootinfo.page_info = get_allowed_pages() | |||
load_translations(bootinfo) | |||
@@ -328,7 +328,39 @@ class DatabaseQuery(object): | |||
can_be_null = True | |||
# prepare in condition | |||
if f.operator.lower() in ('in', 'not in'): | |||
if f.operator.lower() in ('ancestors of', 'descendants of', 'not ancestors of', 'not descendants of'): | |||
values = f.value or '' | |||
# TODO: handle list and tuple | |||
# if not isinstance(values, (list, tuple)): | |||
# values = values.split(",") | |||
ref_doctype = f.doctype | |||
if frappe.get_meta(f.doctype).get_field(f.fieldname) is not None : | |||
ref_doctype = frappe.get_meta(f.doctype).get_field(f.fieldname).options | |||
result=[] | |||
lft, rgt = frappe.db.get_value(ref_doctype, f.value, ["lft", "rgt"]) | |||
# Get descendants elements of a DocType with a tree structure | |||
if f.operator.lower() in ('descendants of', 'not descendants of') : | |||
result = frappe.db.sql_list("""select name from `tab{0}` | |||
where lft>%s and rgt<%s order by lft asc""".format(ref_doctype), (lft, rgt)) | |||
else : | |||
# Get ancestor elements of a DocType with a tree structure | |||
result = frappe.db.sql_list("""select name from `tab{0}` | |||
where lft<%s and rgt>%s order by lft desc""".format(ref_doctype), (lft, rgt)) | |||
fallback = "''" | |||
value = (frappe.db.escape((v or '').strip(), percent=False) for v in result) | |||
value = '("{0}")'.format('", "'.join(value)) | |||
# changing operator to IN as the above code fetches all the parent / child values and convert into tuple | |||
# which can be directly used with IN operator to query. | |||
f.operator = 'not in' if f.operator.lower() in ('not ancestors of', 'not descendants of') else 'in' | |||
elif f.operator.lower() in ('in', 'not in'): | |||
values = f.value or '' | |||
if not isinstance(values, (list, tuple)): | |||
values = values.split(",") | |||
@@ -14,6 +14,10 @@ | |||
<option value=">=">{%= __(">=") %}</option> | |||
<option value="<=">{%= __("<=") %}</option> | |||
<option value="Between">{%= __("Between") %}</option> | |||
<option value="descendants of">{%= __("Descendants Of") %}</option> | |||
<option value="not descendants of">{%= __("Not Descendants Of") %}</option> | |||
<option value="ancestors of">{%= __("Ancestors Of") %}</option> | |||
<option value="not ancestors of">{%= __("Not Ancestors Of") %}</option> | |||
</select> | |||
</div> | |||
<div class="col-sm-6 col-xs-12"> | |||
@@ -17,7 +17,9 @@ frappe.ui.Filter = class { | |||
["<", "<"], | |||
[">=", ">="], | |||
["<=", "<="], | |||
["Between", __("Between")] | |||
["Between", __("Between")], | |||
["descendants of", __("Descendants Of")], | |||
["ancestors of", __("Ancestors Of")] | |||
]; | |||
this.invalid_condition_map = { | |||
Date: ['like', 'not like'], | |||
@@ -185,7 +187,7 @@ frappe.ui.Filter = class { | |||
make_field(df, old_fieldtype) { | |||
let old_text = this.field ? this.field.get_value() : null; | |||
this.hide_invalid_conditions(df.fieldtype, df.original_type); | |||
this.hide_nested_set_conditions(df); | |||
let field_area = this.filter_edit_area.find('.filter-field').empty().get(0); | |||
let f = frappe.ui.form.make_control({ | |||
df: df, | |||
@@ -216,7 +218,6 @@ frappe.ui.Filter = class { | |||
this.hidden | |||
]; | |||
} | |||
get_selected_value() { | |||
return this.utils.get_selected_value(this.field, this.get_condition()); | |||
} | |||
@@ -229,6 +230,7 @@ frappe.ui.Filter = class { | |||
let $condition_field = this.filter_edit_area.find('.condition'); | |||
$condition_field.val(condition); | |||
if(trigger_change) $condition_field.change(); | |||
} | |||
make_tag() { | |||
@@ -290,6 +292,20 @@ frappe.ui.Filter = class { | |||
); | |||
} | |||
} | |||
hide_nested_set_conditions(df) { | |||
if ( !( df.fieldtype == "Link" && frappe.boot.nested_set_doctypes.includes(df.options))) { | |||
this.filter_edit_area.find(`.condition option[value="descendants of"]`).hide(); | |||
this.filter_edit_area.find(`.condition option[value="not descendants of"]`).hide(); | |||
this.filter_edit_area.find(`.condition option[value="ancestors of"]`).hide(); | |||
this.filter_edit_area.find(`.condition option[value="not ancestors of"]`).hide(); | |||
}else { | |||
this.filter_edit_area.find(`.condition option[value="descendants of"]`).show(); | |||
this.filter_edit_area.find(`.condition option[value="not descendants of"]`).show(); | |||
this.filter_edit_area.find(`.condition option[value="ancestors of"]`).show(); | |||
this.filter_edit_area.find(`.condition option[value="not ancestors of"]`).show(); | |||
} | |||
} | |||
}; | |||
frappe.ui.filter_utils = { | |||
@@ -374,7 +390,7 @@ frappe.ui.filter_utils = { | |||
} else if(['Text','Small Text','Text Editor','Code','Tag','Comments', | |||
'Dynamic Link','Read Only','Assign'].indexOf(df.fieldtype)!=-1) { | |||
df.fieldtype = 'Data'; | |||
} else if(df.fieldtype=='Link' && ['=', '!='].indexOf(condition)==-1) { | |||
} else if(df.fieldtype=='Link' && ['=', '!=', 'descendants of', 'ancestors of', 'not descendants of', 'not ancestors of'].indexOf(condition)==-1) { | |||
df.fieldtype = 'Data'; | |||
} | |||
if(df.fieldtype==="Data" && (df.options || "").toLowerCase()==="email") { | |||
@@ -214,6 +214,87 @@ class TestReportview(unittest.TestCase): | |||
], order_by='creation') | |||
self.assertTrue('DefaultValue' in [d['name'] for d in out]) | |||
def test_of_not_of_descendant_ancestors(self): | |||
clear_user_permissions_for_doctype("File") | |||
delete_test_file_hierarchy() # delete already existing folders | |||
from frappe.core.doctype.file.file import create_new_folder | |||
create_new_folder('level1-A', 'Home') | |||
create_new_folder('level2-A', 'Home/level1-A') | |||
create_new_folder('level2-B', 'Home/level1-A') | |||
create_new_folder('level3-A', 'Home/level1-A/level2-A') | |||
create_new_folder('level1-B', 'Home') | |||
create_new_folder('level2-A', 'Home/level1-B') | |||
# in descendants filter | |||
data = frappe.get_all('File', {'name': ('descendants of', 'Home/level1-A/level2-A')}) | |||
self.assertTrue({"name": "Home/level1-A/level2-A/level3-A"} in data) | |||
data = frappe.get_all('File', {'name': ('descendants of', 'Home/level1-A')}) | |||
self.assertTrue({"name": "Home/level1-A/level2-A/level3-A"} in data) | |||
self.assertTrue({"name": "Home/level1-A/level2-A"} in data) | |||
self.assertTrue({"name": "Home/level1-A/level2-B"} in data) | |||
self.assertFalse({"name": "Home/level1-B"} in data) | |||
self.assertFalse({"name": "Home/level1-A"} in data) | |||
self.assertFalse({"name": "Home"} in data) | |||
# in ancestors of filter | |||
data = frappe.get_all('File', {'name': ('ancestors of', 'Home/level1-A/level2-A')}) | |||
self.assertFalse({"name": "Home/level1-A/level2-A/level3-A"} in data) | |||
self.assertFalse({"name": "Home/level1-A/level2-A"} in data) | |||
self.assertFalse({"name": "Home/level1-A/level2-B"} in data) | |||
self.assertFalse({"name": "Home/level1-B"} in data) | |||
self.assertTrue({"name": "Home/level1-A"} in data) | |||
self.assertTrue({"name": "Home"} in data) | |||
data = frappe.get_all('File', {'name': ('ancestors of', 'Home/level1-A')}) | |||
self.assertFalse({"name": "Home/level1-A/level2-A/level3-A"} in data) | |||
self.assertFalse({"name": "Home/level1-A/level2-A"} in data) | |||
self.assertFalse({"name": "Home/level1-A/level2-B"} in data) | |||
self.assertFalse({"name": "Home/level1-B"} in data) | |||
self.assertFalse({"name": "Home/level1-A"} in data) | |||
self.assertTrue({"name": "Home"} in data) | |||
# not descendants filter | |||
data = frappe.get_all('File', {'name': ('not descendants of', 'Home/level1-A/level2-A')}) | |||
self.assertFalse({"name": "Home/level1-A/level2-A/level3-A"} in data) | |||
self.assertTrue({"name": "Home/level1-A/level2-A"} in data) | |||
self.assertTrue({"name": "Home/level1-A/level2-B"} in data) | |||
self.assertTrue({"name": "Home/level1-A"} in data) | |||
self.assertTrue({"name": "Home"} in data) | |||
data = frappe.get_all('File', {'name': ('not descendants of', 'Home/level1-A')}) | |||
self.assertFalse({"name": "Home/level1-A/level2-A/level3-A"} in data) | |||
self.assertFalse({"name": "Home/level1-A/level2-A"} in data) | |||
self.assertFalse({"name": "Home/level1-A/level2-B"} in data) | |||
self.assertTrue({"name": "Home/level1-B"} in data) | |||
self.assertTrue({"name": "Home/level1-A"} in data) | |||
self.assertTrue({"name": "Home"} in data) | |||
# not ancestors of filter | |||
data = frappe.get_all('File', {'name': ('not ancestors of', 'Home/level1-A/level2-A')}) | |||
self.assertTrue({"name": "Home/level1-A/level2-A/level3-A"} in data) | |||
self.assertTrue({"name": "Home/level1-A/level2-A"} in data) | |||
self.assertTrue({"name": "Home/level1-A/level2-B"} in data) | |||
self.assertTrue({"name": "Home/level1-B"} in data) | |||
self.assertTrue({"name": "Home/level1-A"} not in data) | |||
self.assertTrue({"name": "Home"} not in data) | |||
data = frappe.get_all('File', {'name': ('not ancestors of', 'Home/level1-A')}) | |||
self.assertTrue({"name": "Home/level1-A/level2-A/level3-A"} in data) | |||
self.assertTrue({"name": "Home/level1-A/level2-A"} in data) | |||
self.assertTrue({"name": "Home/level1-A/level2-B"} in data) | |||
self.assertTrue({"name": "Home/level1-B"} in data) | |||
self.assertTrue({"name": "Home/level1-A"} in data) | |||
self.assertFalse({"name": "Home"} in data) | |||
data = frappe.get_all('File', {'name': ('ancestors of', 'Home')}) | |||
self.assertTrue(len(data) == 0) | |||
self.assertTrue(len(frappe.get_all('File', {'name': ('not ancestors of', 'Home')})) == len(frappe.get_all('File'))) | |||
def create_event(subject="_Test Event", starts_on=None): | |||
""" create a test event """ | |||
@@ -818,7 +818,8 @@ def get_filter(doctype, f): | |||
# if operator is missing | |||
f.operator = "=" | |||
valid_operators = ("=", "!=", ">", "<", ">=", "<=", "like", "not like", "in", "not in", "between") | |||
valid_operators = ("=", "!=", ">", "<", ">=", "<=", "like", "not like", "in", "not in", | |||
"between", "descendants of", "ancestors of", "not descendants of", "not ancestors of") | |||
if f.operator.lower() not in valid_operators: | |||
frappe.throw(frappe._("Operator must be one of {0}").format(", ".join(valid_operators))) | |||