From 6ce30724cb801151b2fe72f3c3ee0472d2a7b598 Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Fri, 15 Sep 2023 18:33:18 +0530
Subject: [PATCH 32/47] chore: linter fix
---
frappe/public/js/workflow_builder/store.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/workflow_builder/store.js b/frappe/public/js/workflow_builder/store.js
index b7e4973a4f..781e53e555 100644
--- a/frappe/public/js/workflow_builder/store.js
+++ b/frappe/public/js/workflow_builder/store.js
@@ -144,7 +144,7 @@ export const useStore = defineStore("workflow-builder-store", () => {
let docfield = "Workflow Transition";
let df = frappe.model.get_new_doc(docfield);
df.name = frappe.utils.get_random(8);
- Object.assign(df, data)
+ Object.assign(df, data);
return df;
}
From 4ae3aaf6acbbdfdcd3c9891337c1eca5d7d9e071 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 15 Sep 2023 19:04:55 +0530
Subject: [PATCH 33/47] Revert "fix: defer first tour until awesomebar is
triggered"
This reverts commit 60615a126c07f8cd30843292ce9e1cab41052591.
---
.../form_tour/main_workspace_tour/main_workspace_tour.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/desk/form_tour/main_workspace_tour/main_workspace_tour.json b/frappe/desk/form_tour/main_workspace_tour/main_workspace_tour.json
index 0f936abae0..afd0583cfb 100644
--- a/frappe/desk/form_tour/main_workspace_tour/main_workspace_tour.json
+++ b/frappe/desk/form_tour/main_workspace_tour/main_workspace_tour.json
@@ -8,7 +8,7 @@
"include_name_field": 0,
"is_standard": 1,
"list_name": "",
- "modified": "2023-08-24 11:01:18.688875",
+ "modified": "2023-05-24 12:43:43.741781",
"modified_by": "Administrator",
"module": "Desk",
"name": "Main Workspace Tour",
@@ -22,7 +22,7 @@
"steps": [
{
"description": "This is Awesomebar, it helps you to navigate anywhere in the system, find documents, reports, settings, create new records and many more things.",
- "element_selector": "#navbar-search[aria-expanded=\"true\"]",
+ "element_selector": "#navbar-search",
"fieldtype": "0",
"has_next_condition": 0,
"hide_buttons": 0,
From ea7b080e70095cd250020a31903fd54284c50213 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 15 Sep 2023 19:14:24 +0530
Subject: [PATCH 34/47] chore: nudge towards properly skipping onboarding
Escape doesn't totally skip
---
frappe/public/js/onboarding_tours/onboarding_tours.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/public/js/onboarding_tours/onboarding_tours.js b/frappe/public/js/onboarding_tours/onboarding_tours.js
index 3100329f16..3f6202d068 100644
--- a/frappe/public/js/onboarding_tours/onboarding_tours.js
+++ b/frappe/public/js/onboarding_tours/onboarding_tours.js
@@ -11,11 +11,11 @@ frappe.ui.OnboardingTour = class OnboardingTour {
allowClose: false,
padding: 10,
overlayClickNext: false,
- keyboardControl: true,
+ keyboardControl: false,
nextBtnText: __("Next"),
prevBtnText: __("Previous"),
doneBtnText: __("Done"),
- closeBtnText: __("Close"),
+ closeBtnText: __("Skip"),
opacity: 0.5,
onHighlighted: (step) => {
frappe.ui.next_form_tour = step.options.step_info?.next_form_tour;
From 8f34d6fc3ebaf293d26c37372f617acce4f342a3 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Sun, 17 Sep 2023 14:10:09 +0530
Subject: [PATCH 35/47] perf: docinfo
- Dont query versions if disabled
---
frappe/desk/form/load.py | 51 +++++++++++++++++++++-------------------
frappe/types/exporter.py | 1 +
2 files changed, 28 insertions(+), 24 deletions(-)
diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py
index e48157db11..abb88c6c0a 100644
--- a/frappe/desk/form/load.py
+++ b/frappe/desk/form/load.py
@@ -2,6 +2,7 @@
# License: MIT. See LICENSE
import json
+import typing
from urllib.parse import quote
import frappe
@@ -14,6 +15,9 @@ from frappe.model.utils.user_settings import get_user_settings
from frappe.permissions import get_doc_permissions
from frappe.utils.data import cstr
+if typing.TYPE_CHECKING:
+ from frappe.model.document import Document
+
@frappe.whitelist()
def getdoc(doctype, name, user=None):
@@ -117,7 +121,7 @@ def get_docinfo(doc=None, doctype=None, name=None):
"assignments": get_assignments(doc.doctype, doc.name),
"permissions": get_doc_permissions(doc),
"shared": get_docshares(doc),
- "views": get_view_logs(doc.doctype, doc.name),
+ "views": get_view_logs(doc),
"energy_point_logs": get_point_logs(doc.doctype, doc.name),
"additional_timeline_content": get_additional_timeline_content(doc.doctype, doc.name),
"milestones": get_milestones(doc.doctype, doc.name),
@@ -192,7 +196,9 @@ def get_attachments(dt, dn):
)
-def get_versions(doc):
+def get_versions(doc: "Document") -> list[dict]:
+ if not doc.meta.track_changes:
+ return []
return frappe.get_all(
"Version",
filters=dict(ref_doctype=doc.doctype, docname=doc.name),
@@ -362,32 +368,29 @@ def run_onload(doc):
doc.run_method("onload")
-def get_view_logs(doctype, docname):
+def get_view_logs(doc: "Document") -> list[dict]:
"""get and return the latest view logs if available"""
- logs = []
- if getattr(frappe.get_meta(doctype), "track_views", None):
- view_logs = frappe.get_all(
- "View Log",
- filters={
- "reference_doctype": doctype,
- "reference_name": docname,
- },
- fields=["name", "creation", "owner"],
- order_by="creation desc",
- )
+ if not doc.meta.track_views:
+ return []
- if view_logs:
- logs = view_logs
- return logs
+ return frappe.get_all(
+ "View Log",
+ filters={
+ "reference_doctype": doc.doctype,
+ "reference_name": doc.name,
+ },
+ fields=["name", "creation", "owner"],
+ order_by="creation desc",
+ )
-def get_tags(doctype, name):
- tags = [
- tag.tag
- for tag in frappe.get_all(
- "Tag Link", filters={"document_type": doctype, "document_name": name}, fields=["tag"]
- )
- ]
+def get_tags(doctype: str, name: str) -> str:
+ tags = frappe.get_all(
+ "Tag Link",
+ filters={"document_type": doctype, "document_name": name},
+ fields=["tag"],
+ pluck="tag",
+ )
return ",".join(tags)
diff --git a/frappe/types/exporter.py b/frappe/types/exporter.py
index 0ec99af365..a62c8931eb 100644
--- a/frappe/types/exporter.py
+++ b/frappe/types/exporter.py
@@ -206,3 +206,4 @@ class TypeExporter:
# Ideally this should be longest common substring but I don't l33tc0de.
# If someone really needs it, add support via hooks.
self.indent = " " * 4
+ break
From 9f0c8be183b770f450d2794e1ed400e63394858c Mon Sep 17 00:00:00 2001
From: David Arnold
Date: Sun, 17 Sep 2023 14:44:41 +0200
Subject: [PATCH 36/47] test: add failing test case for js flt
---
cypress/integration/control_float.js | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/cypress/integration/control_float.js b/cypress/integration/control_float.js
index 65aa21ed69..50452c6fee 100644
--- a/cypress/integration/control_float.js
+++ b/cypress/integration/control_float.js
@@ -83,6 +83,23 @@ context("Control Float", () => {
},
],
},
+ {
+ // '.' is the parseFloat's decimal separator
+ number_format: "#.###,##",
+ values: [
+ {
+ input: "12.345",
+ blur_expected: "12.345",
+ focus_expected: "12345",
+ },
+ {
+ // parseFloat would reduce 12,340 to 12,34 if this string was ever to be parsed
+ input: "12.340",
+ blur_expected: "12.340",
+ focus_expected: "12340",
+ },
+ ],
+ },
];
}
});
From 0568795eddb49e5bfb45c5a205da0d3ebd23c379 Mon Sep 17 00:00:00 2001
From: David Arnold
Date: Sun, 17 Sep 2023 14:39:39 +0200
Subject: [PATCH 37/47] Revert "fix: make flt value idempotent"
This reverts commit 4d0b0d35efa64a1692e075b71ae70c4969b81cda.
---
frappe/public/js/frappe/utils/number_format.js | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/frappe/public/js/frappe/utils/number_format.js b/frappe/public/js/frappe/utils/number_format.js
index 649353f654..9352733186 100644
--- a/frappe/public/js/frappe/utils/number_format.js
+++ b/frappe/public/js/frappe/utils/number_format.js
@@ -8,12 +8,7 @@ if (!window.frappe) window.frappe = {};
function flt(v, decimals, number_format, rounding_method) {
if (v == null || v == "") return 0;
- if (!(typeof v === "number" || String(parseFloat(v)) == v)) {
- // cases in which this block should not run
- // 1. 'v' is already a number
- // 2. v is already parsed but in string form
- // if (typeof v !== "number") {
-
+ if (typeof v !== "number") {
v = v + "";
// strip currency symbol if exists
@@ -29,7 +24,6 @@ function flt(v, decimals, number_format, rounding_method) {
if (isNaN(v)) v = 0;
}
- v = parseFloat(v);
if (decimals != null) return _round(v, decimals, rounding_method);
return v;
}
From 2ddd808b588fefb10fb2e741463bbbd90cc1fb7c Mon Sep 17 00:00:00 2001
From: David Arnold
Date: Sun, 17 Sep 2023 16:44:13 +0200
Subject: [PATCH 38/47] test: add more test cases with number & currency
formatting
---
cypress/integration/control_currency.js | 12 ++++++++++++
cypress/integration/control_float.js | 4 ++--
2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/cypress/integration/control_currency.js b/cypress/integration/control_currency.js
index 5e6db86036..1fb912d9ff 100644
--- a/cypress/integration/control_currency.js
+++ b/cypress/integration/control_currency.js
@@ -47,6 +47,17 @@ context("Control Currency", () => {
df_options: { precision: 0 },
blur_expected: "10",
},
+ {
+ input: "10.000",
+ number_format: "#.###,##",
+ df_options: { precision: 0 },
+ blur_expected: "10.000",
+ },
+ {
+ input: "10.000",
+ number_format: "#.###,##",
+ blur_expected: "10.000,00",
+ },
{
input: "10.101",
df_options: { precision: "" },
@@ -61,6 +72,7 @@ context("Control Currency", () => {
.then((frappe) => {
frappe.boot.sysdefaults.currency = test_case.currency;
frappe.boot.sysdefaults.currency_precision = test_case.default_precision ?? 2;
+ frappe.boot.sysdefaults.number_format = test_case.number_format ?? "#,###.##";
});
get_dialog_with_currency(test_case.df_options).as("dialog");
diff --git a/cypress/integration/control_float.js b/cypress/integration/control_float.js
index 50452c6fee..e7d6b398f1 100644
--- a/cypress/integration/control_float.js
+++ b/cypress/integration/control_float.js
@@ -89,13 +89,13 @@ context("Control Float", () => {
values: [
{
input: "12.345",
- blur_expected: "12.345",
+ blur_expected: "12.345,000",
focus_expected: "12345",
},
{
// parseFloat would reduce 12,340 to 12,34 if this string was ever to be parsed
input: "12.340",
- blur_expected: "12.340",
+ blur_expected: "12.340,000",
focus_expected: "12340",
},
],
From d20e436e0f49659a2ad3cb512c8386ac07884731 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 18 Sep 2023 11:00:43 +0530
Subject: [PATCH 39/47] perf: fetch user info in one query
---
frappe/desk/form/load.py | 60 +++++++++++++++++++---------------------
frappe/utils/__init__.py | 41 ++++++++++++++++++++-------
2 files changed, 59 insertions(+), 42 deletions(-)
diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py
index abb88c6c0a..77767f589c 100644
--- a/frappe/desk/form/load.py
+++ b/frappe/desk/form/load.py
@@ -153,29 +153,22 @@ def add_comments(doc, docinfo):
)
for c in comments:
- if c.comment_type == "Comment":
- c.content = frappe.utils.markdown(c.content)
- docinfo.comments.append(c)
-
- elif c.comment_type in ("Shared", "Unshared"):
- docinfo.shared.append(c)
-
- elif c.comment_type in ("Assignment Completed", "Assigned"):
- docinfo.assignment_logs.append(c)
-
- elif c.comment_type in ("Attachment", "Attachment Removed"):
- docinfo.attachment_logs.append(c)
-
- elif c.comment_type in ("Info", "Edit", "Label"):
- docinfo.info_logs.append(c)
-
- elif c.comment_type == "Like":
- docinfo.like_logs.append(c)
-
- elif c.comment_type == "Workflow":
- docinfo.workflow_logs.append(c)
-
- frappe.utils.add_user_info(c.owner, docinfo.user_info)
+ match c.comment_type:
+ case "Comment":
+ c.content = frappe.utils.markdown(c.content)
+ docinfo.comments.append(c)
+ case "Shared" | "Unshared":
+ docinfo.shared.append(c)
+ case "Assignment Completed" | "Assigned":
+ docinfo.assignment_logs.append(c)
+ case "Attachment" | "Attachment Removed":
+ docinfo.attachment_logs.append(c)
+ case "Info" | "Edit" | "Label":
+ docinfo.info_logs.append(c)
+ case "Like":
+ docinfo.like_logs.append(c)
+ case "Workflow":
+ docinfo.workflow_logs.append(c)
return comments
@@ -481,17 +474,20 @@ def send_link_titles(link_titles):
def update_user_info(docinfo):
- for d in docinfo.communications:
- frappe.utils.add_user_info(d.sender, docinfo.user_info)
+ users = set()
- for d in docinfo.shared:
- frappe.utils.add_user_info(d.user, docinfo.user_info)
+ users.update(d.sender for d in docinfo.communications)
+ users.update(d.user for d in docinfo.shared)
+ users.update(d.owner for d in docinfo.assignments)
+ users.update(d.owner for d in docinfo.views)
+ users.update(d.owner for d in docinfo.workflow_logs)
+ users.update(d.owner for d in docinfo.like_logs)
+ users.update(d.owner for d in docinfo.info_logs)
+ users.update(d.owner for d in docinfo.attachment_logs)
+ users.update(d.owner for d in docinfo.assignment_logs)
+ users.update(d.owner for d in docinfo.comments)
- for d in docinfo.assignments:
- frappe.utils.add_user_info(d.owner, docinfo.user_info)
-
- for d in docinfo.views:
- frappe.utils.add_user_info(d.owner, docinfo.user_info)
+ frappe.utils.add_user_info(users, docinfo.user_info)
@frappe.whitelist()
diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py
index f5c6320aee..77fe2b0f8d 100644
--- a/frappe/utils/__init__.py
+++ b/frappe/utils/__init__.py
@@ -21,7 +21,7 @@ from collections.abc import (
)
from email.header import decode_header, make_header
from email.utils import formataddr, parseaddr
-from typing import Any, Literal
+from typing import Any, Literal, TypedDict
from urllib.parse import quote, urlparse
from redis.exceptions import ConnectionError
@@ -1082,15 +1082,36 @@ def dictify(arg):
return arg
-def add_user_info(user, user_info):
- if user not in user_info:
- info = (
- frappe.db.get_value(
- "User", user, ["full_name", "user_image", "name", "email", "time_zone"], as_dict=True
- )
- or frappe._dict()
- )
- user_info[user] = frappe._dict(
+class _UserInfo(TypedDict):
+ fullname: str
+ image: str
+ name: str
+ email: str
+ time_zone: str
+
+
+def add_user_info(user: str | list[str] | set[str], user_info: dict[str, _UserInfo]) -> None:
+ if not user:
+ return
+
+ if isinstance(user, str):
+ user = [user]
+
+ missing_users = [u for u in user if u not in user_info]
+ if not missing_users:
+ return
+
+ for missing_user in missing_users:
+ user_info[missing_user] = frappe._dict()
+
+ missing_info = frappe.get_all(
+ "User",
+ {"name": ("in", missing_users)},
+ ["full_name", "user_image", "name", "email", "time_zone"],
+ )
+
+ for info in missing_info:
+ user_info[info.name].update(
fullname=info.full_name or user,
image=info.user_image,
name=user,
From f1ffc3fd39179b1799946bbf880ae143d9d7ef94 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 18 Sep 2023 11:42:21 +0530
Subject: [PATCH 40/47] test: fix bankers rounding test
One more `null` needs to be passed for "not passing precision"
---
cypress/integration/rounding.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cypress/integration/rounding.js b/cypress/integration/rounding.js
index 1a9cfa685b..f778e009bb 100644
--- a/cypress/integration/rounding.js
+++ b/cypress/integration/rounding.js
@@ -49,7 +49,7 @@ context("Rounding behaviour", () => {
let rounding_method = "Banker's Rounding";
expect(flt("0.5", 0, null, rounding_method)).eq(0);
- expect(flt("0.3", null, rounding_method)).eq(0.3);
+ expect(flt("0.3", null, null, rounding_method)).eq(0.3);
expect(flt("1.5", 0, null, rounding_method)).eq(2);
From fb65ab1a4e277bc14523d334f1ce1e3fe4c64269 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 18 Sep 2023 12:28:14 +0530
Subject: [PATCH 41/47] fix: notify when rule already exists
---
frappe/permissions.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/frappe/permissions.py b/frappe/permissions.py
index a9f1b3e464..d902c02334 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -579,6 +579,12 @@ def add_permission(doctype, role, permlevel=0, ptype=None):
if frappe.db.get_value(
"Custom DocPerm", dict(parent=doctype, role=role, permlevel=permlevel, if_owner=0)
):
+ frappe.msgprint(
+ _("Rule for this doctype, role, permlevel and if-owner combination already exists.").format(
+ doctype,
+ ),
+ alert=True,
+ )
return
if not ptype:
From b484c65283c0b8c1a523b37f19b70e02e29beb6a Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 18 Sep 2023 12:33:26 +0530
Subject: [PATCH 42/47] fix: check if owner while deleting permission
If doctype has 2 rules: 1 with if-owner and one without then deleting one deletes both.
---
frappe/core/page/permission_manager/permission_manager.js | 1 +
frappe/core/page/permission_manager/permission_manager.py | 7 +++++--
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/frappe/core/page/permission_manager/permission_manager.js b/frappe/core/page/permission_manager/permission_manager.js
index bbd2dda7b3..5c756b4240 100644
--- a/frappe/core/page/permission_manager/permission_manager.js
+++ b/frappe/core/page/permission_manager/permission_manager.js
@@ -370,6 +370,7 @@ frappe.PermissionEngine = class PermissionEngine {
doctype: d.parent,
role: d.role,
permlevel: d.permlevel,
+ if_owner: d.if_owner,
},
callback: (r) => {
if (r.exc) {
diff --git a/frappe/core/page/permission_manager/permission_manager.py b/frappe/core/page/permission_manager/permission_manager.py
index 45dd5d7162..5f5a0a6429 100644
--- a/frappe/core/page/permission_manager/permission_manager.py
+++ b/frappe/core/page/permission_manager/permission_manager.py
@@ -135,11 +135,14 @@ def update(doctype, role, permlevel, ptype, value=None):
@frappe.whitelist()
-def remove(doctype, role, permlevel):
+def remove(doctype, role, permlevel, if_owner):
frappe.only_for("System Manager")
setup_custom_perms(doctype)
- frappe.db.delete("Custom DocPerm", {"parent": doctype, "role": role, "permlevel": permlevel})
+ frappe.db.delete(
+ "Custom DocPerm",
+ {"parent": doctype, "role": role, "permlevel": permlevel, "if_owner": if_owner},
+ )
if not frappe.get_all("Custom DocPerm", {"parent": doctype}):
frappe.throw(_("There must be atleast one permission rule."), title=_("Cannot Remove"))
From 0381f836d6cce11ec5d6aa01f8a1d82127a02583 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 18 Sep 2023 12:36:59 +0530
Subject: [PATCH 43/47] fix: update correct role permission rule
Right now if you have if-owner rule on doctype then whatever you change
will only apply to last inserted rule because we don't check if-owner
value
Long term better fix: Identify with perm rule name instead of arbitrary
"primary keys" defined in code.
---
.../page/permission_manager/permission_manager.js | 2 ++
.../page/permission_manager/permission_manager.py | 6 +++---
frappe/permissions.py | 14 ++++++++++++--
frappe/tests/test_permissions.py | 14 +++++++-------
4 files changed, 24 insertions(+), 12 deletions(-)
diff --git a/frappe/core/page/permission_manager/permission_manager.js b/frappe/core/page/permission_manager/permission_manager.js
index 5c756b4240..f06e583bee 100644
--- a/frappe/core/page/permission_manager/permission_manager.js
+++ b/frappe/core/page/permission_manager/permission_manager.js
@@ -291,6 +291,7 @@ frappe.PermissionEngine = class PermissionEngine {
.attr("data-ptype", fieldname)
.attr("data-role", d.role)
.attr("data-permlevel", d.permlevel)
+ .attr("data-if_owner", d.if_owner)
.attr("data-doctype", d.parent);
checkbox.find("label").css("text-transform", "capitalize");
@@ -399,6 +400,7 @@ frappe.PermissionEngine = class PermissionEngine {
doctype: chk.attr("data-doctype"),
ptype: chk.attr("data-ptype"),
value: chk.prop("checked") ? 1 : 0,
+ if_owner: chk.attr("data-if_owner"),
};
return frappe.call({
module: "frappe.core",
diff --git a/frappe/core/page/permission_manager/permission_manager.py b/frappe/core/page/permission_manager/permission_manager.py
index 5f5a0a6429..1368ced6eb 100644
--- a/frappe/core/page/permission_manager/permission_manager.py
+++ b/frappe/core/page/permission_manager/permission_manager.py
@@ -109,7 +109,7 @@ def add(parent, role, permlevel):
@frappe.whitelist()
-def update(doctype, role, permlevel, ptype, value=None):
+def update(doctype, role, permlevel, ptype, value=None, if_owner=0):
"""Update role permission params
Args:
@@ -127,7 +127,7 @@ def update(doctype, role, permlevel, ptype, value=None):
frappe.clear_cache(doctype=doctype)
frappe.only_for("System Manager")
- out = update_permission_property(doctype, role, permlevel, ptype, value)
+ out = update_permission_property(doctype, role, permlevel, ptype, value, if_owner=if_owner)
frappe.db.after_commit.add(clear_cache)
@@ -135,7 +135,7 @@ def update(doctype, role, permlevel, ptype, value=None):
@frappe.whitelist()
-def remove(doctype, role, permlevel, if_owner):
+def remove(doctype, role, permlevel, if_owner=0):
frappe.only_for("System Manager")
setup_custom_perms(doctype)
diff --git a/frappe/permissions.py b/frappe/permissions.py
index d902c02334..0e7c0d6480 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -546,13 +546,23 @@ def can_export(doctype, raise_exception=False):
return has_access
-def update_permission_property(doctype, role, permlevel, ptype, value=None, validate=True):
+def update_permission_property(
+ doctype,
+ role,
+ permlevel,
+ ptype,
+ value=None,
+ validate=True,
+ if_owner=0,
+):
"""Update a property in Custom Perm"""
from frappe.core.doctype.doctype.doctype import validate_permissions_for_doctype
out = setup_custom_perms(doctype)
- name = frappe.db.get_value("Custom DocPerm", dict(parent=doctype, role=role, permlevel=permlevel))
+ name = frappe.db.get_value(
+ "Custom DocPerm", dict(parent=doctype, role=role, permlevel=permlevel, if_owner=if_owner)
+ )
table = DocType("Custom DocPerm")
frappe.qb.update(table).set(ptype, value).where(table.name == name).run()
diff --git a/frappe/tests/test_permissions.py b/frappe/tests/test_permissions.py
index e9cf3aaa2f..1b05c28d2a 100644
--- a/frappe/tests/test_permissions.py
+++ b/frappe/tests/test_permissions.py
@@ -477,9 +477,9 @@ class TestPermissions(FrappeTestCase):
# check if user is not granted access if the user is not the owner of the doc
# Blogger has only read access on the blog post unless he is the owner of the blog
update("Blog Post", "Blogger", 0, "if_owner", 1)
- update("Blog Post", "Blogger", 0, "read", 1)
- update("Blog Post", "Blogger", 0, "write", 1)
- update("Blog Post", "Blogger", 0, "delete", 1)
+ update("Blog Post", "Blogger", 0, "read", 1, 1)
+ update("Blog Post", "Blogger", 0, "write", 1, 1)
+ update("Blog Post", "Blogger", 0, "delete", 1, 1)
# currently test2 user has not created any document
# still he should be able to do get_list query which should
@@ -588,9 +588,9 @@ class TestPermissions(FrappeTestCase):
def test_if_owner_permission_on_delete(self):
update("Blog Post", "Blogger", 0, "if_owner", 1)
- update("Blog Post", "Blogger", 0, "read", 1)
- update("Blog Post", "Blogger", 0, "write", 1)
- update("Blog Post", "Blogger", 0, "delete", 1)
+ update("Blog Post", "Blogger", 0, "read", 1, 1)
+ update("Blog Post", "Blogger", 0, "write", 1, 1)
+ update("Blog Post", "Blogger", 0, "delete", 1, 1)
# Remove delete perm
update("Blog Post", "Website Manager", 0, "delete", 0)
@@ -627,7 +627,7 @@ class TestPermissions(FrappeTestCase):
frappe.set_user("test2@example.com")
frappe.delete_doc("Blog Post", "-test-blog-post-title-new-1")
- update("Blog Post", "Website Manager", 0, "delete", 1)
+ update("Blog Post", "Website Manager", 0, "delete", 1, 1)
def test_clear_user_permissions(self):
current_user = frappe.session.user
From 6a2bfbf61dcdc4f66e33f4686c3939570062c91d Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 18 Sep 2023 14:30:53 +0530
Subject: [PATCH 44/47] fix: Ignore perm while updating module onboarding
(#22448)
* fix: Ignore perm while updating module onboarding
* fix: incorrect error message
---
frappe/desk/doctype/module_onboarding/module_onboarding.py | 2 +-
frappe/model/document.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/desk/doctype/module_onboarding/module_onboarding.py b/frappe/desk/doctype/module_onboarding/module_onboarding.py
index adb23074fb..1c7a2adeaf 100644
--- a/frappe/desk/doctype/module_onboarding/module_onboarding.py
+++ b/frappe/desk/doctype/module_onboarding/module_onboarding.py
@@ -52,7 +52,7 @@ class ModuleOnboarding(Document):
is_complete = [bool(step.is_complete or step.is_skipped) for step in steps]
if all(is_complete):
self.is_complete = True
- self.save()
+ self.save(ignore_permissions=True)
return True
return False
diff --git a/frappe/model/document.py b/frappe/model/document.py
index c23e78cd38..d419abc727 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -216,7 +216,7 @@ class Document(BaseDocument):
def check_permission(self, permtype="read", permlevel=None):
"""Raise `frappe.PermissionError` if not permitted"""
if not self.has_permission(permtype):
- self.raise_no_permission_to(permlevel or permtype)
+ self.raise_no_permission_to(permtype)
def has_permission(self, permtype="read") -> bool:
"""
From 8cb13f922abe50ebd8201753fe20925d8704871b Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 5 Sep 2023 11:38:26 +0530
Subject: [PATCH 45/47] chore(DX): Annotate all local proxies
---
frappe/__init__.py | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/frappe/__init__.py b/frappe/__init__.py
index dd15329106..b676a6abf7 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -170,6 +170,8 @@ lang = local("lang")
# This if block is never executed when running the code. It is only used for
# telling static code analyzer where to find dynamically defined attributes.
if TYPE_CHECKING:
+ from werkzeug.wrappers import Request
+
from frappe.database.mariadb.database import MariaDBDatabase
from frappe.database.postgres.database import PostgresDatabase
from frappe.model.document import Document
@@ -179,6 +181,15 @@ if TYPE_CHECKING:
db: MariaDBDatabase | PostgresDatabase
qb: MariaDB | Postgres
cache: RedisWrapper
+ response: _dict
+ conf: _dict
+ form_dict: _dict
+ flags: _dict
+ request: Request
+ session: _dict
+ user: str
+ flags: _dict
+ lang: str
# end: static analysis hack
From 8779be9b49f05713d21485ea87a2816438526497 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 18 Sep 2023 16:00:12 +0530
Subject: [PATCH 46/47] fix: propogate correct HTTP status code (#22450)
---
frappe/website/path_resolver.py | 9 +++++----
frappe/website/serve.py | 2 +-
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py
index 50ef2afcb4..a91a51c0da 100644
--- a/frappe/website/path_resolver.py
+++ b/frappe/website/path_resolver.py
@@ -17,10 +17,11 @@ from frappe.website.utils import can_cache, get_home_page
class PathResolver:
- __slots__ = ("path",)
+ __slots__ = ("path", "http_status_code")
- def __init__(self, path):
+ def __init__(self, path, http_status_code=None):
self.path = path.strip("/ ")
+ self.http_status_code = http_status_code
def resolve(self):
"""Returns endpoint and a renderer instance that can render the endpoint"""
@@ -41,7 +42,7 @@ class PathResolver:
# WARN: Hardcoded for better performance
if endpoint == "app":
- return endpoint, TemplatePage(endpoint, 200)
+ return endpoint, TemplatePage(endpoint, self.http_status_code)
custom_renderers = self.get_custom_page_renderers()
renderers = custom_renderers + [
@@ -54,7 +55,7 @@ class PathResolver:
]
for renderer in renderers:
- renderer_instance = renderer(endpoint, 200)
+ renderer_instance = renderer(endpoint, self.http_status_code)
if renderer_instance.can_render():
return endpoint, renderer_instance
diff --git a/frappe/website/serve.py b/frappe/website/serve.py
index 2bbaf9137c..acae44940e 100644
--- a/frappe/website/serve.py
+++ b/frappe/website/serve.py
@@ -13,7 +13,7 @@ def get_response(path=None, http_status_code=200):
endpoint = path
try:
- path_resolver = PathResolver(path)
+ path_resolver = PathResolver(path, http_status_code)
endpoint, renderer_instance = path_resolver.resolve()
response = renderer_instance.render()
except frappe.Redirect:
From e152ebc0a334f434a5648c01fb4153d8872c0161 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 18 Sep 2023 20:16:27 +0530
Subject: [PATCH 47/47] chore: typo
---
frappe/utils/safe_exec.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py
index 7375d328fd..0b8435b100 100644
--- a/frappe/utils/safe_exec.py
+++ b/frappe/utils/safe_exec.py
@@ -69,7 +69,7 @@ def safe_exec(script, _globals=None, _locals=None, restrict_commit_rollback=Fals
if not is_safe_exec_enabled():
msg = _("Server Scripts are disabled. Please enable server scripts from bench configuration.")
- docs_cta = _("Read the documentation to know")
+ docs_cta = _("Read the documentation to know more")
msg += f"
{docs_cta}"
frappe.throw(msg, ServerScriptNotEnabled, title="Server Scripts Disabled")