Merge branch 'develop' into css-for-web-form-mobile
This commit is contained in:
commit
498a51fbb5
18 changed files with 249 additions and 83 deletions
|
|
@ -1679,7 +1679,9 @@ def make_module_and_roles(doc, perm_fieldname="permissions"):
|
|||
|
||||
for role in list(set(roles)):
|
||||
if frappe.db.table_exists("Role", cached=False) and not frappe.db.exists("Role", role):
|
||||
r = frappe.get_doc(dict(doctype="Role", role_name=role, desk_access=1))
|
||||
r = frappe.new_doc("Role")
|
||||
r.role_name = role
|
||||
r.desk_access = 1
|
||||
r.flags.ignore_mandatory = r.flags.ignore_permissions = True
|
||||
r.insert()
|
||||
except frappe.DoesNotExistError as e:
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
"script_type",
|
||||
"reference_doctype",
|
||||
"event_frequency",
|
||||
"cron_format",
|
||||
"doctype_event",
|
||||
"api_method",
|
||||
"allow_guest",
|
||||
|
|
@ -99,7 +100,7 @@
|
|||
"fieldtype": "Select",
|
||||
"label": "Event Frequency",
|
||||
"mandatory_depends_on": "eval:doc.script_type == \"Scheduler Event\"",
|
||||
"options": "All\nHourly\nDaily\nWeekly\nMonthly\nYearly\nHourly Long\nDaily Long\nWeekly Long\nMonthly Long"
|
||||
"options": "All\nHourly\nDaily\nWeekly\nMonthly\nYearly\nHourly Long\nDaily Long\nWeekly Long\nMonthly Long\nCron"
|
||||
},
|
||||
{
|
||||
"fieldname": "module",
|
||||
|
|
@ -132,6 +133,12 @@
|
|||
"fieldname": "rate_limit_seconds",
|
||||
"fieldtype": "Int",
|
||||
"label": "Time Window (Seconds)"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.event_frequency==='Cron'",
|
||||
"fieldname": "cron_format",
|
||||
"fieldtype": "Data",
|
||||
"label": "Cron Format"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
|
|
@ -141,7 +148,7 @@
|
|||
"link_fieldname": "server_script"
|
||||
}
|
||||
],
|
||||
"modified": "2023-05-16 11:03:58.282680",
|
||||
"modified": "2023-05-27 16:33:16.595424",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Server Script",
|
||||
|
|
|
|||
|
|
@ -52,11 +52,16 @@ class ServerScript(Document):
|
|||
def sync_scheduler_events(self):
|
||||
"""Create or update Scheduled Job Type documents for Scheduler Event Server Scripts"""
|
||||
if not self.disabled and self.event_frequency and self.script_type == "Scheduler Event":
|
||||
setup_scheduler_events(script_name=self.name, frequency=self.event_frequency)
|
||||
cron_format = self.cron_format if self.event_frequency == "Cron" else None
|
||||
setup_scheduler_events(
|
||||
script_name=self.name, frequency=self.event_frequency, cron_format=cron_format
|
||||
)
|
||||
|
||||
def clear_scheduled_events(self):
|
||||
"""Deletes existing scheduled jobs by Server Script if self.event_frequency has changed"""
|
||||
if self.script_type == "Scheduler Event" and self.has_value_changed("event_frequency"):
|
||||
"""Deletes existing scheduled jobs by Server Script if self.event_frequency or self.cron_format has changed"""
|
||||
if self.script_type == "Scheduler Event" and (
|
||||
self.has_value_changed("event_frequency") or self.has_value_changed("cron_format")
|
||||
):
|
||||
for scheduled_job in self.scheduled_jobs:
|
||||
frappe.delete_doc("Scheduled Job Type", scheduled_job.name)
|
||||
|
||||
|
|
@ -171,7 +176,7 @@ class ServerScript(Document):
|
|||
return items
|
||||
|
||||
|
||||
def setup_scheduler_events(script_name, frequency):
|
||||
def setup_scheduler_events(script_name: str, frequency: str, cron_format: str | None = None):
|
||||
"""Creates or Updates Scheduled Job Type documents based on the specified script name and frequency
|
||||
|
||||
Args:
|
||||
|
|
@ -188,6 +193,7 @@ def setup_scheduler_events(script_name, frequency):
|
|||
"method": method,
|
||||
"frequency": frequency,
|
||||
"server_script": script_name,
|
||||
"cron_format": cron_format,
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
|
@ -200,6 +206,7 @@ def setup_scheduler_events(script_name, frequency):
|
|||
return
|
||||
|
||||
doc.frequency = frequency
|
||||
doc.cron_format = cron_format
|
||||
doc.save()
|
||||
|
||||
frappe.msgprint(_("Scheduled execution for script {0} has updated").format(script_name))
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import requests
|
||||
|
||||
import frappe
|
||||
from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs
|
||||
from frappe.frappeclient import FrappeClient, FrappeException
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import get_site_url
|
||||
|
|
@ -283,3 +284,37 @@ frappe.qb.from_(todo).select(todo.name).where(todo.name == "{todo.name}").run()
|
|||
script1.delete()
|
||||
script2.delete()
|
||||
frappe.db.commit()
|
||||
|
||||
def test_server_script_scheduled(self):
|
||||
scheduled_script = frappe.get_doc(
|
||||
doctype="Server Script",
|
||||
name="scheduled_script_wo_cron",
|
||||
script_type="Scheduler Event",
|
||||
script="""frappe.flags = {"test": True}""",
|
||||
event_frequency="Hourly",
|
||||
).insert()
|
||||
|
||||
cron_script = frappe.get_doc(
|
||||
doctype="Server Script",
|
||||
name="scheduled_script_w_cron",
|
||||
script_type="Scheduler Event",
|
||||
script="""frappe.flags = {"test": True}""",
|
||||
event_frequency="Cron",
|
||||
cron_format="0 0 1 1 *", # 1st january
|
||||
).insert()
|
||||
|
||||
# Ensure that jobs remain in DB after migrate
|
||||
sync_jobs()
|
||||
self.assertTrue(frappe.db.exists("Scheduled Job Type", {"server_script": scheduled_script.name}))
|
||||
|
||||
cron_job_name = frappe.db.get_value("Scheduled Job Type", {"server_script": cron_script.name})
|
||||
self.assertTrue(cron_job_name)
|
||||
|
||||
cron_job = frappe.get_doc("Scheduled Job Type", cron_job_name)
|
||||
self.assertEqual(cron_job.next_execution.day, 1)
|
||||
self.assertEqual(cron_job.next_execution.month, 1)
|
||||
|
||||
cron_script.cron_format = "0 0 2 1 *" # 2nd january
|
||||
cron_script.save()
|
||||
cron_job.reload()
|
||||
self.assertEqual(cron_job.next_execution.day, 2)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import itertools
|
||||
import re
|
||||
from ast import literal_eval
|
||||
from types import BuiltinFunctionType
|
||||
|
|
@ -190,15 +189,18 @@ class Engine:
|
|||
if _operator in OPERATOR_MAP["nested_set"]:
|
||||
hierarchy = _operator
|
||||
docname = _value
|
||||
result = get_nested_set_hierarchy_result(self.doctype, docname, hierarchy)
|
||||
|
||||
_df = frappe.get_meta(self.doctype).get_field(field)
|
||||
ref_doctype = _df.options if _df else self.doctype
|
||||
|
||||
nodes = get_nested_set_hierarchy_result(ref_doctype, docname, hierarchy)
|
||||
operator_fn = (
|
||||
OPERATOR_MAP["not in"]
|
||||
if hierarchy in ("not ancestors of", "not descendants of")
|
||||
else OPERATOR_MAP["in"]
|
||||
)
|
||||
if result:
|
||||
result = list(itertools.chain.from_iterable(result))
|
||||
self.query = self.query.where(operator_fn(_field, result))
|
||||
if nodes:
|
||||
self.query = self.query.where(operator_fn(_field, nodes))
|
||||
else:
|
||||
self.query = self.query.where(operator_fn(_field, ("",)))
|
||||
return
|
||||
|
|
@ -513,22 +515,25 @@ def has_function(field):
|
|||
return True
|
||||
|
||||
|
||||
def get_nested_set_hierarchy_result(doctype: str, name: str, hierarchy: str):
|
||||
def get_nested_set_hierarchy_result(doctype: str, name: str, hierarchy: str) -> list[str]:
|
||||
"""Get matching nodes based on operator."""
|
||||
table = frappe.qb.DocType(doctype)
|
||||
try:
|
||||
lft, rgt = frappe.qb.from_(table).select("lft", "rgt").where(table.name == name).run()[0]
|
||||
except IndexError:
|
||||
lft, rgt = None, None
|
||||
|
||||
if hierarchy in ("descendants of", "not descendants of"):
|
||||
if hierarchy in ("descendants of", "not descendants of", "descendants of (inclusive)"):
|
||||
result = (
|
||||
frappe.qb.from_(table)
|
||||
.select(table.name)
|
||||
.where(table.lft > lft)
|
||||
.where(table.rgt < rgt)
|
||||
.orderby(table.lft, order=Order.asc)
|
||||
.run()
|
||||
.run(pluck=True)
|
||||
)
|
||||
if hierarchy == "descendants of (inclusive)":
|
||||
result += [name]
|
||||
else:
|
||||
# Get ancestor elements of a DocType with a tree structure
|
||||
result = (
|
||||
|
|
@ -537,6 +542,6 @@ def get_nested_set_hierarchy_result(doctype: str, name: str, hierarchy: str):
|
|||
.where(table.lft < lft)
|
||||
.where(table.rgt > rgt)
|
||||
.orderby(table.lft, order=Order.desc)
|
||||
.run()
|
||||
.run(pluck=True)
|
||||
)
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ NestedSetHierarchy = (
|
|||
"descendants of",
|
||||
"not ancestors of",
|
||||
"not descendants of",
|
||||
"descendants of (inclusive)",
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,18 @@
|
|||
|
||||
frappe.ui.form.on("Custom HTML Block", {
|
||||
refresh(frm) {
|
||||
if (
|
||||
!has_common(frappe.user_roles, [
|
||||
"Administrator",
|
||||
"System Manager",
|
||||
"Workspace Manager",
|
||||
])
|
||||
) {
|
||||
frm.set_value("private", true);
|
||||
} else {
|
||||
frm.set_df_property("private", "read_only", false);
|
||||
}
|
||||
|
||||
let wrapper = frm.fields_dict["preview"].wrapper;
|
||||
wrapper.classList.add("mb-3");
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"private",
|
||||
"preview_section",
|
||||
"preview",
|
||||
"html_section",
|
||||
|
|
@ -80,11 +81,19 @@
|
|||
"fieldtype": "Table",
|
||||
"label": "Roles",
|
||||
"options": "Has Role"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.private || doc.__unsaved",
|
||||
"fieldname": "private",
|
||||
"fieldtype": "Check",
|
||||
"label": "Private",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-05-29 18:28:28.326843",
|
||||
"modified": "2023-05-30 14:33:31.994738",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Custom HTML Block",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,25 @@
|
|||
# Copyright (c) 2023, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.utils import DocType
|
||||
|
||||
|
||||
class CustomHTMLBlock(Document):
|
||||
pass
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_custom_blocks_for_user(doctype, txt, searchfield, start, page_len, filters):
|
||||
# return logged in users private blocks and all public blocks
|
||||
customHTMLBlock = DocType("Custom HTML Block")
|
||||
|
||||
condition_query = frappe.qb.get_query(customHTMLBlock)
|
||||
|
||||
return (
|
||||
condition_query.select(customHTMLBlock.name).where(
|
||||
(customHTMLBlock.private == 0)
|
||||
| ((customHTMLBlock.owner == frappe.session.user) & (customHTMLBlock.private == 1))
|
||||
)
|
||||
).run()
|
||||
|
|
|
|||
|
|
@ -730,18 +730,30 @@ class DatabaseQuery:
|
|||
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.get_all(
|
||||
ref_doctype, filters={"lft": [">", lft], "rgt": ["<", rgt]}, order_by="`lft` ASC"
|
||||
if f.operator.lower() in (
|
||||
"descendants of",
|
||||
"not descendants of",
|
||||
"descendants of (inclusive)",
|
||||
):
|
||||
nodes = frappe.get_all(
|
||||
ref_doctype,
|
||||
filters={"lft": [">", lft], "rgt": ["<", rgt]},
|
||||
order_by="`lft` ASC",
|
||||
pluck="name",
|
||||
)
|
||||
if f.operator.lower() == "descendants of (inclusive)":
|
||||
nodes += [f.value]
|
||||
else:
|
||||
# Get ancestor elements of a DocType with a tree structure
|
||||
result = frappe.get_all(
|
||||
ref_doctype, filters={"lft": ["<", lft], "rgt": [">", rgt]}, order_by="`lft` DESC"
|
||||
nodes = frappe.get_all(
|
||||
ref_doctype,
|
||||
filters={"lft": ["<", lft], "rgt": [">", rgt]},
|
||||
order_by="`lft` DESC",
|
||||
pluck="name",
|
||||
)
|
||||
|
||||
fallback = "''"
|
||||
value = [frappe.db.escape((cstr(v.name) or "").strip(), percent=False) for v in result]
|
||||
value = [frappe.db.escape((cstr(v)).strip(), percent=False) for v in nodes]
|
||||
if len(value):
|
||||
value = f"({', '.join(value)})"
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -73,9 +73,31 @@ frappe.ui.form.FormTour = class FormTour {
|
|||
if (!this.driver.hasNextStep()) {
|
||||
this.on_finish && this.on_finish();
|
||||
}
|
||||
let field = this.get_next_step()?.options.element.fieldobj;
|
||||
if (field?.tab && !field.tab.is_active()) {
|
||||
field.tab.set_active();
|
||||
this.driver.reset(true);
|
||||
frappe.utils.sleep(200).then(() => {
|
||||
this.start(step.idx);
|
||||
this.driver.overlay.refresh();
|
||||
});
|
||||
}
|
||||
};
|
||||
const on_prev = () => {
|
||||
if (!this.driver.hasPreviousStep()) return;
|
||||
let field =
|
||||
this.driver.steps[this.driver.currentStep - 1]?.options.element.fieldobj;
|
||||
if (field?.tab && !field.tab.is_active()) {
|
||||
field.tab.set_active();
|
||||
this.driver.reset(true);
|
||||
frappe.utils.sleep(200).then(() => {
|
||||
this.start(step.idx - 2);
|
||||
this.driver.overlay.refresh();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const driver_step = this.get_step(step, on_next);
|
||||
const driver_step = this.get_step(step, on_next, on_prev);
|
||||
this.driver_steps.push(driver_step);
|
||||
|
||||
if (step.fieldtype == "Table") this.handle_table_step(step);
|
||||
|
|
@ -93,7 +115,7 @@ frappe.ui.form.FormTour = class FormTour {
|
|||
return form.layout.evaluate_depends_on_value(step.next_step_condition || true);
|
||||
}
|
||||
|
||||
get_step(step_info, on_next) {
|
||||
get_step(step_info, on_next, on_prev) {
|
||||
const { name, fieldname, title, description, position, is_table_field } = step_info;
|
||||
let element = `.frappe-control[data-fieldname='${fieldname}']`;
|
||||
|
||||
|
|
@ -113,6 +135,7 @@ frappe.ui.form.FormTour = class FormTour {
|
|||
name,
|
||||
popover: { title, description, position: frappe.router.slug(position || "Bottom") },
|
||||
onNext: on_next,
|
||||
onPrevious: on_prev,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -678,7 +678,9 @@ class FilterArea {
|
|||
if (
|
||||
fields_dict[fieldname] &&
|
||||
(condition === "=" ||
|
||||
(condition === "like" && fields_dict[fieldname]?.df?.fieldtype != "Link"))
|
||||
(condition === "like" && fields_dict[fieldname]?.df?.fieldtype != "Link") ||
|
||||
(condition === "descendants of (inclusive)" &&
|
||||
fields_dict[fieldname]?.df?.fieldtype == "Link"))
|
||||
) {
|
||||
// standard filter
|
||||
out.promise = out.promise.then(() => fields_dict[fieldname].set_value(value));
|
||||
|
|
@ -788,6 +790,13 @@ class FilterArea {
|
|||
options = options.join("\n");
|
||||
}
|
||||
}
|
||||
if (
|
||||
df.fieldtype == "Link" &&
|
||||
df.options &&
|
||||
frappe.boot.treeviews.includes(df.options)
|
||||
) {
|
||||
condition = "descendants of (inclusive)";
|
||||
}
|
||||
|
||||
return {
|
||||
fieldtype: fieldtype,
|
||||
|
|
|
|||
|
|
@ -139,6 +139,12 @@ frappe.router = {
|
|||
if (!frappe.app) return;
|
||||
|
||||
let sub_path = this.get_sub_path();
|
||||
if (frappe.boot.setup_complete) {
|
||||
!frappe.re_route["setup-wizard"] && (frappe.re_route["setup-wizard"] = "app");
|
||||
} else if (!sub_path.startsWith("setup-wizard")) {
|
||||
frappe.re_route["setup-wizard"] && delete frappe.re_route["setup-wizard"];
|
||||
frappe.set_route(["setup-wizard"]);
|
||||
}
|
||||
if (this.re_route(sub_path)) return;
|
||||
|
||||
this.current_sub_path = sub_path;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ frappe.ui.Filter = class {
|
|||
|
||||
this.nested_set_conditions = [
|
||||
["descendants of", __("Descendants Of")],
|
||||
["descendants of (inclusive)", __("Descendants Of (inclusive)")],
|
||||
["not descendants of", __("Not Descendants Of")],
|
||||
["ancestors of", __("Ancestors Of")],
|
||||
["not ancestors of", __("Not Ancestors Of")],
|
||||
|
|
@ -524,6 +525,7 @@ frappe.ui.filter_utils = {
|
|||
"=",
|
||||
"!=",
|
||||
"descendants of",
|
||||
"descendants of (inclusive)",
|
||||
"ancestors of",
|
||||
"not descendants of",
|
||||
"not ancestors of",
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ frappe.ui.click_toggle_like = function () {
|
|||
return false;
|
||||
};
|
||||
|
||||
frappe.ui.setup_like_popover = ($parent, selector, check_not_liked = true) => {
|
||||
frappe.ui.setup_like_popover = ($parent, selector) => {
|
||||
if (frappe.dom.is_touchscreen()) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -109,20 +109,6 @@ frappe.ui.setup_like_popover = ($parent, selector, check_not_liked = true) => {
|
|||
liked_by = liked_by ? decodeURI(liked_by) : "[]";
|
||||
liked_by = JSON.parse(liked_by);
|
||||
|
||||
const user = frappe.session.user;
|
||||
// hack
|
||||
if (check_not_liked) {
|
||||
if (target_element.parents(".liked-by").find(".not-liked").length) {
|
||||
if (liked_by.indexOf(user) !== -1) {
|
||||
liked_by.splice(liked_by.indexOf(user), 1);
|
||||
}
|
||||
} else {
|
||||
if (liked_by.indexOf(user) === -1) {
|
||||
liked_by.push(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!liked_by.length) {
|
||||
return "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -715,6 +715,11 @@ class CustomBlockDialog extends WidgetDialog {
|
|||
label: "Custom Block Name",
|
||||
options: "Custom HTML Block",
|
||||
reqd: 1,
|
||||
get_query: () => {
|
||||
return {
|
||||
query: "frappe.desk.doctype.custom_html_block.custom_html_block.get_custom_blocks_for_user",
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,35 +51,35 @@ records = [
|
|||
},
|
||||
]
|
||||
|
||||
TEST_DOCTYPE = "Test Tree DocType"
|
||||
|
||||
|
||||
class NestedSetTestUtil:
|
||||
def setup_test_doctype(self):
|
||||
frappe.db.sql("delete from `tabDocType` where `name` = 'Test Tree DocType'")
|
||||
frappe.db.sql_ddl("drop table if exists `tabTest Tree DocType`")
|
||||
frappe.db.delete("DocType", TEST_DOCTYPE)
|
||||
frappe.db.sql_ddl(f"drop table if exists `tab{TEST_DOCTYPE}`")
|
||||
|
||||
self.tree_doctype = new_doctype(
|
||||
"Test Tree DocType", is_tree=True, autoname="field:some_fieldname"
|
||||
)
|
||||
self.tree_doctype = new_doctype(TEST_DOCTYPE, is_tree=True, autoname="field:some_fieldname")
|
||||
self.tree_doctype.insert()
|
||||
|
||||
for record in records:
|
||||
d = frappe.new_doc("Test Tree DocType")
|
||||
d = frappe.new_doc(TEST_DOCTYPE)
|
||||
d.update(record)
|
||||
d.insert()
|
||||
|
||||
def teardown_test_doctype(self):
|
||||
self.tree_doctype.delete()
|
||||
frappe.db.sql_ddl("drop table if exists `tabTest Tree DocType`")
|
||||
frappe.db.sql_ddl(f"drop table if exists `{TEST_DOCTYPE}`")
|
||||
|
||||
def move_it_back(self):
|
||||
parent_1 = frappe.get_doc("Test Tree DocType", "Parent 1")
|
||||
parent_1 = frappe.get_doc(TEST_DOCTYPE, "Parent 1")
|
||||
parent_1.parent_test_tree_doctype = "Root Node"
|
||||
parent_1.save()
|
||||
|
||||
def get_no_of_children(self, record_name: str) -> int:
|
||||
if not record_name:
|
||||
return frappe.db.count("Test Tree DocType")
|
||||
return len(get_descendants_of("Test Tree DocType", record_name, ignore_permissions=True))
|
||||
return frappe.db.count(TEST_DOCTYPE)
|
||||
return len(get_descendants_of(TEST_DOCTYPE, record_name, ignore_permissions=True))
|
||||
|
||||
|
||||
class TestNestedSet(FrappeTestCase):
|
||||
|
|
@ -101,18 +101,18 @@ class TestNestedSet(FrappeTestCase):
|
|||
global records
|
||||
|
||||
min_lft = 1
|
||||
max_rgt = frappe.qb.from_("Test Tree DocType").select(Max(Field("rgt"))).run(pluck=True)[0]
|
||||
max_rgt = frappe.qb.from_(TEST_DOCTYPE).select(Max(Field("rgt"))).run(pluck=True)[0]
|
||||
|
||||
for record in records:
|
||||
lft, rgt, parent_test_tree_doctype = frappe.db.get_value(
|
||||
"Test Tree DocType",
|
||||
TEST_DOCTYPE,
|
||||
record["some_fieldname"],
|
||||
["lft", "rgt", "parent_test_tree_doctype"],
|
||||
)
|
||||
|
||||
if parent_test_tree_doctype:
|
||||
parent_lft, parent_rgt = frappe.db.get_value(
|
||||
"Test Tree DocType", parent_test_tree_doctype, ["lft", "rgt"]
|
||||
TEST_DOCTYPE, parent_test_tree_doctype, ["lft", "rgt"]
|
||||
)
|
||||
else:
|
||||
# root
|
||||
|
|
@ -138,19 +138,19 @@ class TestNestedSet(FrappeTestCase):
|
|||
self.assertTrue(parent_rgt == (parent_lft + 1 + (2 * no_of_children)))
|
||||
|
||||
def test_recursion(self):
|
||||
leaf_node = frappe.get_doc("Test Tree DocType", {"some_fieldname": "Parent 2"})
|
||||
leaf_node = frappe.get_doc(TEST_DOCTYPE, {"some_fieldname": "Parent 2"})
|
||||
leaf_node.parent_test_tree_doctype = "Child 3"
|
||||
self.assertRaises(NestedSetRecursionError, leaf_node.save)
|
||||
leaf_node.reload()
|
||||
|
||||
def test_rebuild_tree(self):
|
||||
rebuild_tree("Test Tree DocType", "parent_test_tree_doctype")
|
||||
rebuild_tree(TEST_DOCTYPE, "parent_test_tree_doctype")
|
||||
self.test_basic_tree()
|
||||
|
||||
def test_move_group_into_another(self):
|
||||
old_lft, old_rgt = frappe.db.get_value("Test Tree DocType", "Parent 2", ["lft", "rgt"])
|
||||
old_lft, old_rgt = frappe.db.get_value(TEST_DOCTYPE, "Parent 2", ["lft", "rgt"])
|
||||
|
||||
parent_1 = frappe.get_doc("Test Tree DocType", "Parent 1")
|
||||
parent_1 = frappe.get_doc(TEST_DOCTYPE, "Parent 1")
|
||||
lft, rgt = parent_1.lft, parent_1.rgt
|
||||
|
||||
parent_1.parent_test_tree_doctype = "Parent 2"
|
||||
|
|
@ -158,7 +158,7 @@ class TestNestedSet(FrappeTestCase):
|
|||
self.test_basic_tree()
|
||||
|
||||
# after move
|
||||
new_lft, new_rgt = frappe.db.get_value("Test Tree DocType", "Parent 2", ["lft", "rgt"])
|
||||
new_lft, new_rgt = frappe.db.get_value(TEST_DOCTYPE, "Parent 2", ["lft", "rgt"])
|
||||
|
||||
# lft should reduce
|
||||
self.assertEqual(old_lft - new_lft, rgt - lft + 1)
|
||||
|
|
@ -170,12 +170,10 @@ class TestNestedSet(FrappeTestCase):
|
|||
self.test_basic_tree()
|
||||
|
||||
def test_move_leaf_into_another_group(self):
|
||||
child_2 = frappe.get_doc("Test Tree DocType", "Child 2")
|
||||
child_2 = frappe.get_doc(TEST_DOCTYPE, "Child 2")
|
||||
|
||||
# assert that child 2 is not already under parent 1
|
||||
parent_lft_old, parent_rgt_old = frappe.db.get_value(
|
||||
"Test Tree DocType", "Parent 2", ["lft", "rgt"]
|
||||
)
|
||||
parent_lft_old, parent_rgt_old = frappe.db.get_value(TEST_DOCTYPE, "Parent 2", ["lft", "rgt"])
|
||||
self.assertTrue((parent_lft_old > child_2.lft) and (parent_rgt_old > child_2.rgt))
|
||||
|
||||
child_2.parent_test_tree_doctype = "Parent 2"
|
||||
|
|
@ -183,22 +181,20 @@ class TestNestedSet(FrappeTestCase):
|
|||
self.test_basic_tree()
|
||||
|
||||
# assert that child 2 is under parent 1
|
||||
parent_lft_new, parent_rgt_new = frappe.db.get_value(
|
||||
"Test Tree DocType", "Parent 2", ["lft", "rgt"]
|
||||
)
|
||||
parent_lft_new, parent_rgt_new = frappe.db.get_value(TEST_DOCTYPE, "Parent 2", ["lft", "rgt"])
|
||||
self.assertFalse((parent_lft_new > child_2.lft) and (parent_rgt_new > child_2.rgt))
|
||||
|
||||
def test_delete_leaf(self):
|
||||
global records
|
||||
el = {"some_fieldname": "Child 1", "parent_test_tree_doctype": "Parent 1", "is_group": 0}
|
||||
|
||||
child_1 = frappe.get_doc("Test Tree DocType", "Child 1")
|
||||
child_1 = frappe.get_doc(TEST_DOCTYPE, "Child 1")
|
||||
child_1.delete()
|
||||
records.remove(el)
|
||||
|
||||
self.test_basic_tree()
|
||||
|
||||
n = frappe.new_doc("Test Tree DocType")
|
||||
n = frappe.new_doc(TEST_DOCTYPE)
|
||||
n.update(el)
|
||||
n.insert()
|
||||
records.append(el)
|
||||
|
|
@ -208,10 +204,10 @@ class TestNestedSet(FrappeTestCase):
|
|||
def test_delete_group(self):
|
||||
# cannot delete group with child, but can delete leaf
|
||||
with self.assertRaises(NestedSetChildExistsError):
|
||||
frappe.delete_doc("Test Tree DocType", "Parent 1")
|
||||
frappe.delete_doc(TEST_DOCTYPE, "Parent 1")
|
||||
|
||||
def test_remove_subtree(self):
|
||||
remove_subtree("Test Tree DocType", "Parent 2")
|
||||
remove_subtree(TEST_DOCTYPE, "Parent 2")
|
||||
self.test_basic_tree()
|
||||
|
||||
def test_rename_nestedset(self):
|
||||
|
|
@ -223,7 +219,7 @@ class TestNestedSet(FrappeTestCase):
|
|||
def test_merge_groups(self):
|
||||
global records
|
||||
el = {"some_fieldname": "Parent 2", "parent_test_tree_doctype": "Root Node", "is_group": 1}
|
||||
frappe.rename_doc("Test Tree DocType", "Parent 2", "Parent 1", merge=True)
|
||||
frappe.rename_doc(TEST_DOCTYPE, "Parent 2", "Parent 1", merge=True)
|
||||
records.remove(el)
|
||||
self.test_basic_tree()
|
||||
|
||||
|
|
@ -232,7 +228,7 @@ class TestNestedSet(FrappeTestCase):
|
|||
el = {"some_fieldname": "Child 3", "parent_test_tree_doctype": "Parent 2", "is_group": 0}
|
||||
|
||||
frappe.rename_doc(
|
||||
"Test Tree DocType",
|
||||
TEST_DOCTYPE,
|
||||
"Child 3",
|
||||
"Child 2",
|
||||
merge=True,
|
||||
|
|
@ -242,17 +238,17 @@ class TestNestedSet(FrappeTestCase):
|
|||
|
||||
def test_merge_leaf_into_group(self):
|
||||
with self.assertRaises(NestedSetInvalidMergeError):
|
||||
frappe.rename_doc("Test Tree DocType", "Child 1", "Parent 1", merge=True)
|
||||
frappe.rename_doc(TEST_DOCTYPE, "Child 1", "Parent 1", merge=True)
|
||||
|
||||
def test_merge_group_into_leaf(self):
|
||||
with self.assertRaises(NestedSetInvalidMergeError):
|
||||
frappe.rename_doc("Test Tree DocType", "Parent 1", "Child 1", merge=True)
|
||||
frappe.rename_doc(TEST_DOCTYPE, "Parent 1", "Child 1", merge=True)
|
||||
|
||||
def test_root_deletion(self):
|
||||
for doc in ["Child 3", "Child 2", "Child 1", "Parent 2", "Parent 1"]:
|
||||
frappe.delete_doc("Test Tree DocType", doc)
|
||||
frappe.delete_doc(TEST_DOCTYPE, doc)
|
||||
|
||||
root_node = frappe.get_doc("Test Tree DocType", "Root Node")
|
||||
root_node = frappe.get_doc(TEST_DOCTYPE, "Root Node")
|
||||
|
||||
# root deletion with allow_root_deletion
|
||||
# patched as delete_doc create a new instance of Root Node (using get_doc)
|
||||
|
|
@ -263,4 +259,40 @@ class TestNestedSet(FrappeTestCase):
|
|||
|
||||
# root deletion without allow_root_deletion
|
||||
root_node.delete()
|
||||
self.assertFalse(frappe.db.exists("Test Tree DocType", "Root Node"))
|
||||
self.assertFalse(frappe.db.exists(TEST_DOCTYPE, "Root Node"))
|
||||
|
||||
def test_desc_filters(self):
|
||||
|
||||
linked_doctype = (
|
||||
new_doctype(
|
||||
fields=[
|
||||
{
|
||||
"fieldname": "link_field",
|
||||
"fieldtype": "Link",
|
||||
"options": TEST_DOCTYPE,
|
||||
}
|
||||
]
|
||||
)
|
||||
.insert()
|
||||
.name
|
||||
)
|
||||
|
||||
record = "Child 1"
|
||||
|
||||
exclusive_filter = {"name": ("descendants of", record)}
|
||||
inclusive_filter = {"name": ("descendants of (inclusive)", record)}
|
||||
exclusive_link = {"link_field": ("descendants of", record)}
|
||||
inclusive_link = {"link_field": ("descendants of (inclusive)", record)}
|
||||
|
||||
# db_query
|
||||
self.assertNotIn(record, frappe.get_all(TEST_DOCTYPE, exclusive_filter, run=0))
|
||||
self.assertIn(record, frappe.get_all(TEST_DOCTYPE, inclusive_filter, run=0))
|
||||
self.assertNotIn(record, frappe.get_all(linked_doctype, exclusive_link, run=0))
|
||||
self.assertIn(record, frappe.get_all(linked_doctype, inclusive_link, run=0))
|
||||
|
||||
# QB
|
||||
self.assertNotIn(record, str(frappe.qb.get_query(TEST_DOCTYPE, filters=exclusive_filter)))
|
||||
self.assertIn(record, str(frappe.qb.get_query(TEST_DOCTYPE, filters=inclusive_filter)))
|
||||
|
||||
self.assertNotIn(record, str(frappe.qb.get_query(table=linked_doctype, filters=exclusive_link)))
|
||||
self.assertIn(record, str(frappe.qb.get_query(table=linked_doctype, filters=inclusive_link)))
|
||||
|
|
|
|||
|
|
@ -1766,6 +1766,7 @@ def get_filter(doctype: str, f: dict | list | tuple, filters_config=None) -> "fr
|
|||
"fieldtype":
|
||||
}
|
||||
"""
|
||||
from frappe.database.utils import NestedSetHierarchy
|
||||
from frappe.model import child_table_fields, default_fields, optional_fields
|
||||
|
||||
if isinstance(f, dict):
|
||||
|
|
@ -1805,14 +1806,10 @@ def get_filter(doctype: str, f: dict | list | tuple, filters_config=None) -> "fr
|
|||
"not in",
|
||||
"is",
|
||||
"between",
|
||||
"descendants of",
|
||||
"ancestors of",
|
||||
"not descendants of",
|
||||
"not ancestors of",
|
||||
"timespan",
|
||||
"previous",
|
||||
"next",
|
||||
)
|
||||
) + NestedSetHierarchy
|
||||
|
||||
if filters_config:
|
||||
additional_operators = []
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue