Bläddra i källkod

refactored code to add support in filters to get descendant or ancest… (#5847)

* 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 labels
version-14
sivankar 7 år sedan
committed by Rushabh Mehta
förälder
incheckning
a48b76b940
6 ändrade filer med 142 tillägg och 8 borttagningar
  1. +2
    -2
      frappe/boot.py
  2. +33
    -1
      frappe/model/db_query.py
  3. +4
    -0
      frappe/public/js/frappe/ui/filters/edit_filter.html
  4. +20
    -4
      frappe/public/js/frappe/ui/filters/filter.js
  5. +81
    -0
      frappe/tests/test_db_query.py
  6. +2
    -1
      frappe/utils/data.py

+ 2
- 2
frappe/boot.py Visa fil

@@ -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)


+ 33
- 1
frappe/model/db_query.py Visa fil

@@ -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(",")


+ 4
- 0
frappe/public/js/frappe/ui/filters/edit_filter.html Visa fil

@@ -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">


+ 20
- 4
frappe/public/js/frappe/ui/filters/filter.js Visa fil

@@ -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") {


+ 81
- 0
frappe/tests/test_db_query.py Visa fil

@@ -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 """



+ 2
- 1
frappe/utils/data.py Visa fil

@@ -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)))



Laddar…
Avbryt
Spara