diff --git a/frappe/core/doctype/custom_docperm/custom_docperm.py b/frappe/core/doctype/custom_docperm/custom_docperm.py
index bf3a9b06da..77f2524159 100644
--- a/frappe/core/doctype/custom_docperm/custom_docperm.py
+++ b/frappe/core/doctype/custom_docperm/custom_docperm.py
@@ -35,3 +35,12 @@ class CustomDocPerm(Document):
def on_update(self):
frappe.clear_cache(doctype=self.parent)
+
+ def get_permission_log_options(self, event=None):
+ return {"for_doctype": "DocType", "for_document": self.parent}
+
+
+def update_custom_docperm(docperm, values):
+ custom_docperm = frappe.get_doc("Custom DocPerm", docperm)
+ custom_docperm.update(values)
+ custom_docperm.save(ignore_permissions=True)
diff --git a/frappe/core/doctype/custom_role/custom_role.py b/frappe/core/doctype/custom_role/custom_role.py
index 34879fcbba..ca2ff7c54d 100644
--- a/frappe/core/doctype/custom_role/custom_role.py
+++ b/frappe/core/doctype/custom_role/custom_role.py
@@ -25,6 +25,11 @@ class CustomRole(Document):
if self.report and not self.ref_doctype:
self.ref_doctype = frappe.db.get_value("Report", self.report, "ref_doctype")
+ def get_permission_log_options(self, event=None):
+ if self.report:
+ return {"for_doctype": "Report", "for_document": self.report, "fields": ["roles"]}
+ return {"for_doctype": "Page", "for_document": self.page, "fields": ["roles"]}
+
def get_custom_allowed_roles(field, name):
allowed_roles = []
diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py
index b35bdf27da..df17676760 100644
--- a/frappe/core/doctype/doctype/doctype.py
+++ b/frappe/core/doctype/doctype/doctype.py
@@ -505,6 +505,14 @@ class DocType(Document):
if d.unique:
d.search_index = 0
+ def get_permission_log_options(self, event=None):
+ if self.custom and event != "after_delete":
+ return {
+ "fields": ("permissions", {"fields": ("fieldname", "ignore_user_permissions", "permlevel")})
+ }
+
+ self._no_perm_log = True
+
def on_update(self):
"""Update database schema, make controller templates if `custom` is not set and clear cache."""
diff --git a/frappe/core/doctype/module_profile/module_profile.py b/frappe/core/doctype/module_profile/module_profile.py
index 85744b351e..04b245e33f 100644
--- a/frappe/core/doctype/module_profile/module_profile.py
+++ b/frappe/core/doctype/module_profile/module_profile.py
@@ -22,3 +22,6 @@ class ModuleProfile(Document):
from frappe.utils.modules import get_modules_from_all_apps
self.set_onload("all_modules", sorted(m.get("module_name") for m in get_modules_from_all_apps()))
+
+ def get_permission_log_options(self, event=None):
+ return {"fields": ["block_modules"]}
diff --git a/frappe/core/doctype/page/page.py b/frappe/core/doctype/page/page.py
index 48d783f14e..9b9361d13b 100644
--- a/frappe/core/doctype/page/page.py
+++ b/frappe/core/doctype/page/page.py
@@ -66,6 +66,9 @@ class Page(Document):
if frappe.session.user != "Administrator" and not self.flags.ignore_permissions:
frappe.throw(_("Only Administrator can edit"))
+ def get_permission_log_options(self, event=None):
+ return {"fields": ["roles"]}
+
# export
def on_update(self):
"""
@@ -97,8 +100,8 @@ class Page(Document):
}}"""
)
- def as_dict(self, no_nulls=False):
- d = super().as_dict(no_nulls=no_nulls)
+ def as_dict(self, **kwargs):
+ d = super().as_dict(**kwargs)
for key in ("script", "style", "content"):
d[key] = self.get(key)
return d
diff --git a/frappe/core/doctype/permission_log/__init__.py b/frappe/core/doctype/permission_log/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/core/doctype/permission_log/permission_log.js b/frappe/core/doctype/permission_log/permission_log.js
new file mode 100644
index 0000000000..9cb400645b
--- /dev/null
+++ b/frappe/core/doctype/permission_log/permission_log.js
@@ -0,0 +1,75 @@
+// Copyright (c) 2022, Frappe Technologies and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on("Permission Log", {
+ refresh: function (frm) {
+ frm.events.render_changed_values(frm);
+ },
+
+ render_changed_values: function (frm) {
+ let wrapper = $(frm.fields_dict["changed_values"].wrapper).empty();
+ const changes = JSON.parse(frm.doc.changes);
+ let changes_table = $(`
+
+
+ | ${__("Field")} |
+ ${__("From")} |
+ ${__("To")} |
+
+
+
+
`);
+
+ Object.entries(changes["from"]).forEach(([key, value]) => {
+ if (Array.isArray(value || changes["to"][key])) {
+ changes_table
+ .find(".main-body")
+ .append(frm.events.get_child_changes(key, value, changes["to"][key]));
+ } else {
+ changes_table.find("tbody").append(
+ $(`
+ | ${frappe.model.unscrub(key)} |
+ ${changes["from"][key]} |
+ ${changes["to"][key]} |
+
`)
+ );
+ }
+ });
+
+ wrapper.append(changes_table);
+ },
+
+ get_child_changes: function (field_key, from, to) {
+ let child_main = $(`
+ | ${frappe.model.unscrub(field_key)} |
+ |
+ |
+
`);
+
+ [from, to].forEach((val, index) => {
+ if (!val) return;
+
+ let for_value = index > 0 ? "to" : "frfromom";
+ let child_table = $(``);
+
+ val.forEach((child_row) => {
+ for (const [key, value] of Object.entries(child_row)) {
+ child_table.find("tbody").append(
+ $(
+ `
+ | ${frappe.model.unscrub(key)} |
+ ${value} |
+
`
+ )
+ );
+ }
+ });
+
+ child_main.find(`.${for_value}`).append(child_table);
+ });
+
+ return child_main;
+ },
+});
diff --git a/frappe/core/doctype/permission_log/permission_log.json b/frappe/core/doctype/permission_log/permission_log.json
new file mode 100644
index 0000000000..61c2a8b1b5
--- /dev/null
+++ b/frappe/core/doctype/permission_log/permission_log.json
@@ -0,0 +1,156 @@
+{
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2022-11-23 14:13:55.437321",
+ "default_view": "List",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "changed_by",
+ "column_break_gvuy",
+ "changed_at",
+ "status",
+ "section_break_ttv7",
+ "for_doctype",
+ "column_break_acow",
+ "for_document",
+ "section_break_6gd5",
+ "reference_type",
+ "column_break_8nk0",
+ "reference",
+ "section_break_mt9x",
+ "changes",
+ "changed_values"
+ ],
+ "fields": [
+ {
+ "fieldname": "changed_by",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Changed by",
+ "options": "User",
+ "read_only": 1
+ },
+ {
+ "fieldname": "changed_at",
+ "fieldtype": "Datetime",
+ "is_virtual": 1,
+ "label": "Changed at",
+ "read_only": 1
+ },
+ {
+ "fieldname": "changes",
+ "fieldtype": "Text",
+ "hidden": 1
+ },
+ {
+ "fieldname": "changed_values",
+ "fieldtype": "HTML",
+ "label": "Changes"
+ },
+ {
+ "fieldname": "column_break_gvuy",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_ttv7",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_acow",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_mt9x",
+ "fieldtype": "Section Break"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "section_break_6gd5",
+ "fieldtype": "Section Break",
+ "label": "More Info"
+ },
+ {
+ "fieldname": "column_break_8nk0",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "for_doctype",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "For DocType",
+ "options": "DocType",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "for_document",
+ "fieldtype": "Dynamic Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "For Document",
+ "options": "for_doctype",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "reference_type",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Reference Type",
+ "options": "DocType",
+ "read_only": 1
+ },
+ {
+ "fieldname": "reference",
+ "fieldtype": "Dynamic Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Reference",
+ "options": "reference_type",
+ "read_only": 1
+ },
+ {
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "label": "Status",
+ "options": "Updated\nRemoved\nAdded"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2024-09-25 17:23:31.599897",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Permission Log",
+ "naming_rule": "Random",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "read": 1,
+ "report": 1,
+ "role": "System Manager"
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [
+ {
+ "color": "Yellow",
+ "title": "Updated"
+ },
+ {
+ "color": "Red",
+ "title": "Removed"
+ },
+ {
+ "color": "Green",
+ "title": "Added"
+ }
+ ],
+ "title_field": "changed_by"
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/permission_log/permission_log.py b/frappe/core/doctype/permission_log/permission_log.py
new file mode 100644
index 0000000000..ca0e005b9c
--- /dev/null
+++ b/frappe/core/doctype/permission_log/permission_log.py
@@ -0,0 +1,150 @@
+# Copyright (c) 2022, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from typing import Optional
+
+import frappe
+from frappe.model.document import Document
+
+
+class PermissionLog(Document):
+ # begin: auto-generated types
+ # This code is auto-generated. Do not modify anything in this block.
+
+ from typing import TYPE_CHECKING
+
+ if TYPE_CHECKING:
+ from frappe.types import DF
+
+ changed_at: DF.Datetime | None
+ changed_by: DF.Link | None
+ changes: DF.Text | None
+ for_doctype: DF.Link
+ for_document: DF.DynamicLink
+ reference: DF.DynamicLink | None
+ reference_type: DF.Link | None
+ status: DF.Literal["Updated", "Removed", "Added"]
+ # end: auto-generated types
+
+ @property
+ def changed_at(self):
+ return self.creation
+
+
+def make_perm_log(doc, method=None):
+ if not hasattr(doc, "get_permission_log_options"):
+ return
+
+ params = doc.get_permission_log_options(method) or {}
+ if not getattr(doc, "_no_perm_log", False):
+ insert_perm_log(doc, doc.get_doc_before_save(), **params)
+
+
+def insert_perm_log(
+ doc: Document,
+ doc_before_save: Document = None,
+ for_doctype: Optional["str"] = None,
+ for_document: Optional["str"] = None,
+ fields: Optional["list | tuple"] = None,
+):
+ if frappe.flags.in_install or frappe.flags.in_migrate:
+ # no need to log changes when migrating or installing app/site
+ return
+
+ current, previous = get_changes(doc, doc_before_save, fields)
+ if not previous and not current:
+ return
+
+ status = "Updated" if doc_before_save else ("Added" if doc.flags.in_insert else "Removed")
+
+ frappe.get_doc(
+ {
+ "doctype": "Permission Log",
+ "owner": frappe.session.user,
+ "changed_by": frappe.session.user,
+ "reference_type": for_doctype and doc.doctype,
+ "reference": for_document and doc.name,
+ "for_doctype": for_doctype or doc.doctype,
+ "for_document": for_document or doc.name,
+ "status": status,
+ "changes": frappe.as_json({"from": previous, "to": current}, indent=0),
+ }
+ ).db_insert()
+
+
+def get_changes(doc: Document, doc_before_save=None, fields=None):
+ current_changes = get_filtered_changes(
+ doc.as_dict(
+ no_default_fields=True,
+ no_child_table_fields=True,
+ no_private_properties=True,
+ ),
+ fields,
+ )
+
+ if not doc_before_save:
+ empty_changes = dict.fromkeys(current_changes, "")
+ return (current_changes, empty_changes) if doc.flags.in_insert else (empty_changes, current_changes)
+
+ previous_changes = get_filtered_changes(
+ doc_before_save.as_dict(
+ no_default_fields=True,
+ no_child_table_fields=True,
+ no_private_properties=True,
+ ),
+ fields,
+ )
+
+ return get_changes_diff(current_changes, previous_changes)
+
+
+def get_changes_diff(current_changes, previous_changes):
+ # TODO: track, added, removed and changed rows in child tables
+
+ current_values = {}
+ previous_values = {}
+
+ for k, current_val in current_changes.items():
+ if isinstance(current_val, list):
+ # for child table docs
+ current = {frozenset(row.items()) for row in current_val}
+ previous = {frozenset(row.items()) for row in previous_changes[k]}
+ if not current.symmetric_difference(previous):
+ continue
+
+ previous_values[k] = [dict(i) for i in previous - current]
+ current_val = [dict(i) for i in current - previous]
+
+ elif previous_changes.get(k, None) == current_val:
+ continue
+
+ previous_values[k] = previous_values[k] if k in previous_values else previous_changes[k]
+ current_values[k] = current_val
+
+ return current_values, previous_values
+
+
+def get_filtered_changes(changes, filters=None):
+ def filter_child_docs(child_docs, filter_keys):
+ changes = []
+ for child in child_docs:
+ temp = {}
+ for key in filter_keys:
+ temp[key] = child[key]
+ changes.append(temp)
+
+ return changes
+
+ if not filters:
+ return changes
+
+ filtered_changes = {}
+ for f in filters:
+ if isinstance(f, dict):
+ # filtered child docs
+ for field, cf in f.items():
+ filtered_changes[field] = filter_child_docs(changes.get(field, []), cf)
+ else:
+ filtered_changes[f] = changes.get(f, None)
+
+ return filtered_changes
diff --git a/frappe/core/doctype/permission_log/permission_log_list.js b/frappe/core/doctype/permission_log/permission_log_list.js
new file mode 100644
index 0000000000..5d4715697b
--- /dev/null
+++ b/frappe/core/doctype/permission_log/permission_log_list.js
@@ -0,0 +1,9 @@
+frappe.listview_settings["Permission Log"] = {
+ hide_name_column: true,
+
+ onload(listview) {
+ if (listview.list_view_settings) {
+ listview.list_view_settings.disable_comment_count = 1;
+ }
+ },
+};
diff --git a/frappe/core/doctype/permission_log/test_permission_log.py b/frappe/core/doctype/permission_log/test_permission_log.py
new file mode 100644
index 0000000000..46d1ad71a5
--- /dev/null
+++ b/frappe/core/doctype/permission_log/test_permission_log.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2022, Frappe Technologies and Contributors
+# See license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestPermissionLog(FrappeTestCase):
+ pass
diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py
index 47be2247c7..b5d4419467 100644
--- a/frappe/core/doctype/report/report.py
+++ b/frappe/core/doctype/report/report.py
@@ -95,6 +95,9 @@ class Report(Document):
frappe.throw(_("You are not allowed to delete Standard Report"))
delete_custom_role("report", self.name)
+ def get_permission_log_options(self, event=None):
+ return {"fields": ["roles"]}
+
def get_columns(self):
return [d.as_dict(no_default_fields=True, no_child_table_fields=True) for d in self.columns]
diff --git a/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py b/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py
index 226afb5ec9..78dc5987ef 100644
--- a/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py
+++ b/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py
@@ -5,7 +5,6 @@ import frappe
from frappe.core.doctype.report.report import is_prepared_report_enabled
from frappe.model.document import Document
from frappe.permissions import ALL_USER_ROLE
-from frappe.utils import cint
class RolePermissionforPageandReport(Document):
@@ -67,16 +66,17 @@ class RolePermissionforPageandReport(Document):
def update_custom_roles(self):
args = self.get_args()
+ roles = self.get_roles()
name = frappe.db.get_value("Custom Role", args, "name")
- args.update({"doctype": "Custom Role", "roles": self.get_roles()})
+ args.update({"doctype": "Custom Role", "roles": roles})
if self.report:
args.update({"ref_doctype": frappe.db.get_value("Report", self.report, "ref_doctype")})
if name:
custom_role = frappe.get_doc("Custom Role", name)
- custom_role.set("roles", self.get_roles())
+ custom_role.set("roles", roles)
custom_role.save()
else:
frappe.get_doc(args).insert()
diff --git a/frappe/core/doctype/role_profile/role_profile.py b/frappe/core/doctype/role_profile/role_profile.py
index 59ddeca898..f156c87038 100644
--- a/frappe/core/doctype/role_profile/role_profile.py
+++ b/frappe/core/doctype/role_profile/role_profile.py
@@ -39,3 +39,6 @@ class RoleProfile(Document):
for user in users:
user = frappe.get_doc("User", user)
user.save() # resaving syncs roles
+
+ def get_permission_log_options(self, event=None):
+ return {"fields": ["roles"]}
diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py
index a1f0b05ed2..940cec9dda 100644
--- a/frappe/core/doctype/user/user.py
+++ b/frappe/core/doctype/user/user.py
@@ -778,6 +778,9 @@ class User(Document):
if not self.time_zone:
self.time_zone = get_system_timezone()
+ def get_permission_log_options(self, event=None):
+ return {"fields": ("role_profile_name", "roles", "module_profile", "block_modules")}
+
def check_roles_added(self):
if self.user_type != "System User" or self.roles or not self.is_new():
return
diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py
index 02e0304e4c..f3541865ca 100644
--- a/frappe/core/doctype/user_permission/user_permission.py
+++ b/frappe/core/doctype/user_permission/user_permission.py
@@ -76,6 +76,9 @@ class UserPermission(Document):
ref_link = frappe.get_desk_link(self.doctype, overlap_exists[0].name)
frappe.throw(_("{0} has already assigned default value for {1}.").format(ref_link, self.allow))
+ def get_permission_log_options(self, event=None):
+ pass
+
def send_user_permissions(bootinfo):
bootinfo.user["user_permissions"] = get_user_permissions()
diff --git a/frappe/core/doctype/user_type/user_type.py b/frappe/core/doctype/user_type/user_type.py
index eaad70c69b..0104b6119e 100644
--- a/frappe/core/doctype/user_type/user_type.py
+++ b/frappe/core/doctype/user_type/user_type.py
@@ -3,6 +3,7 @@
import frappe
from frappe import _
+from frappe.core.doctype.custom_docperm.custom_docperm import update_custom_docperm
from frappe.model.document import Document
from frappe.permissions import add_permission, add_user_permission
from frappe.utils import get_link_to_form
@@ -142,7 +143,7 @@ class UserType(Document):
docperm = add_role_permissions(row.document_type, self.role)
values = {perm: row.get(perm, default=0) for perm in perms}
- frappe.db.set_value("Custom DocPerm", docperm, values)
+ update_custom_docperm(docperm, values)
def add_select_perm_doctypes(self):
if frappe.flags.ignore_select_perm:
@@ -176,13 +177,11 @@ class UserType(Document):
for doctype in ["select_doctypes", "custom_select_doctypes"]:
for row in self.get(doctype):
docperm = add_role_permissions(row.document_type, self.role)
- frappe.db.set_value(
- "Custom DocPerm", docperm, {"select": 1, "read": 0, "create": 0, "write": 0}
- )
+ update_custom_docperm(docperm, {"select": 1, "read": 0, "create": 0, "write": 0})
def add_role_permissions_for_file(self):
docperm = add_role_permissions("File", self.role)
- frappe.db.set_value("Custom DocPerm", docperm, {"read": 1, "create": 1, "write": 1})
+ update_custom_docperm(docperm, {"read": 1, "create": 1, "write": 1})
def remove_permission_for_deleted_doctypes(self):
doctypes = [d.document_type for d in self.user_doctypes]
@@ -340,4 +339,6 @@ def apply_permissions_for_non_standard_user_type(doc, method=None):
user_doc.update_children()
add_user_permission(doc.doctype, doc.name, doc.get(data[1]))
else:
- frappe.db.set_value("User Permission", perm_data[0], "user", doc.get(data[1]))
+ user_perm = frappe.get_doc("User Permission", perm_data[0])
+ user_perm.user = doc.get(data[1])
+ user_perm.save(ignore_permissions=True)
diff --git a/frappe/core/page/permission_manager/permission_manager.py b/frappe/core/page/permission_manager/permission_manager.py
index d19d42d2af..77ebc1e024 100644
--- a/frappe/core/page/permission_manager/permission_manager.py
+++ b/frappe/core/page/permission_manager/permission_manager.py
@@ -146,10 +146,11 @@ def remove(doctype, role, permlevel, if_owner=0):
frappe.only_for("System Manager")
setup_custom_perms(doctype)
- frappe.db.delete(
- "Custom DocPerm",
- {"parent": doctype, "role": role, "permlevel": permlevel, "if_owner": if_owner},
+ custom_docperms = frappe.db.get_values(
+ "Custom DocPerm", {"parent": doctype, "role": role, "permlevel": permlevel, "if_owner": if_owner}
)
+ for name in custom_docperms:
+ frappe.delete_doc("Custom DocPerm", name, ignore_permissions=True)
if not frappe.get_all("Custom DocPerm", {"parent": doctype}):
frappe.throw(_("There must be atleast one permission rule."), title=_("Cannot Remove"))
diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py
index 4a873523e7..52d85eb710 100644
--- a/frappe/custom/doctype/custom_field/custom_field.py
+++ b/frappe/custom/doctype/custom_field/custom_field.py
@@ -5,6 +5,7 @@ import json
import frappe
from frappe import _
+from frappe.custom.doctype.property_setter.property_setter import delete_property_setter
from frappe.model import core_doctypes_list
from frappe.model.docfield import supports_translation
from frappe.model.document import Document
@@ -221,7 +222,7 @@ class CustomField(Document):
)
# delete property setter entries
- frappe.db.delete("Property Setter", {"doc_type": self.dt, "field_name": self.fieldname})
+ delete_property_setter(self.dt, field_name=self.fieldname)
# update doctype layouts
doctype_layouts = frappe.get_all("DocType Layout", filters={"document_type": self.dt}, pluck="name")
@@ -248,6 +249,21 @@ class CustomField(Document):
if self.fieldname == self.insert_after:
frappe.throw(_("Insert After cannot be set as {0}").format(meta.get_label(self.insert_after)))
+ def get_permission_log_options(self, event=None):
+ if event != "after_delete" and self.fieldtype not in (
+ "Section Break",
+ "Column Break",
+ "Tab Break",
+ "Fold",
+ ):
+ return {
+ "fields": ("ignore_user_permissions", "permlevel"),
+ "for_doctype": "DocType",
+ "for_document": self.dt,
+ }
+
+ self._no_perm_log = True
+
@frappe.whitelist()
def get_fields_label(doctype=None):
diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py
index 43b3d10c34..b5016ed276 100644
--- a/frappe/custom/doctype/customize_form/customize_form.py
+++ b/frappe/custom/doctype/customize_form/customize_form.py
@@ -440,7 +440,7 @@ class CustomizeForm(Document):
property_name, json.dumps([d.name for d in self.get(fieldname)]), "Small Text"
)
else:
- frappe.db.delete("Property Setter", dict(property=property_name, doc_type=self.doc_type))
+ delete_property_setter(self.doc_type, property=property_name)
def clear_removed_items(self, doctype, items):
"""
diff --git a/frappe/custom/doctype/property_setter/property_setter.py b/frappe/custom/doctype/property_setter/property_setter.py
index 9b1ce34414..fb155c2c34 100644
--- a/frappe/custom/doctype/property_setter/property_setter.py
+++ b/frappe/custom/doctype/property_setter/property_setter.py
@@ -59,6 +59,16 @@ class PropertySetter(Document):
validate_fields_for_doctype(self.doc_type)
+ def get_permission_log_options(self, event=None):
+ if self.property in ("ignore_user_permissions", "permlevel"):
+ return {
+ "for_doctype": "DocType",
+ "for_document": self.doc_type,
+ "fields": ("value", "property", "field_name"),
+ }
+
+ self._no_perm_log = True
+
def make_property_setter(
doctype,
@@ -87,12 +97,17 @@ def make_property_setter(
return property_setter
-def delete_property_setter(doc_type, property, field_name=None, row_name=None):
+def delete_property_setter(doc_type, property=None, field_name=None, row_name=None):
"""delete other property setters on this, if this is new"""
- filters = dict(doc_type=doc_type, property=property)
+ filters = {"doc_type": doc_type}
+ if property:
+ filters["property"] = property
+
if field_name:
filters["field_name"] = field_name
if row_name:
filters["row_name"] = row_name
- frappe.db.delete("Property Setter", filters)
+ property_setters = frappe.db.get_values("Property Setter", filters)
+ for ps in property_setters:
+ frappe.get_doc("Property Setter", ps).delete(ignore_permissions=True)
diff --git a/frappe/hooks.py b/frappe/hooks.py
index 04bd710213..94ce9b220c 100644
--- a/frappe/hooks.py
+++ b/frappe/hooks.py
@@ -157,6 +157,7 @@ doc_events = {
"frappe.automation.doctype.assignment_rule.assignment_rule.apply",
"frappe.automation.doctype.assignment_rule.assignment_rule.update_due_date",
"frappe.core.doctype.user_type.user_type.apply_permissions_for_non_standard_user_type",
+ "frappe.core.doctype.permission_log.permission_log.make_perm_log",
],
"after_rename": "frappe.desk.notifications.clear_doctype_notifications",
"on_cancel": [
@@ -178,6 +179,7 @@ doc_events = {
"frappe.social.doctype.energy_point_rule.energy_point_rule.process_energy_points",
"frappe.automation.doctype.milestone_tracker.milestone_tracker.evaluate_milestone",
],
+ "after_delete": ["frappe.core.doctype.permission_log.permission_log.make_perm_log"],
},
"Event": {
"after_insert": "frappe.integrations.doctype.google_calendar.google_calendar.insert_event_in_google_calendar",
@@ -422,6 +424,7 @@ ignore_links_on_delete = [
"Workspace",
"Route History",
"Access Log",
+ "Permission Log",
]
# Request Hooks
diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py
index 84cb85e9a5..f949fda568 100644
--- a/frappe/model/base_document.py
+++ b/frappe/model/base_document.py
@@ -490,6 +490,7 @@ class BaseDocument:
no_default_fields=False,
convert_dates_to_str=False,
no_child_table_fields=False,
+ no_private_properties=False,
) -> dict:
doc = self.get_valid_dict(convert_dates_to_str=convert_dates_to_str, ignore_nulls=no_nulls)
doc["doctype"] = self.doctype
@@ -502,6 +503,7 @@ class BaseDocument:
no_nulls=no_nulls,
no_default_fields=no_default_fields,
no_child_table_fields=no_child_table_fields,
+ no_private_properties=no_private_properties,
)
for d in children
]
@@ -516,16 +518,17 @@ class BaseDocument:
if key in doc:
del doc[key]
- for key in (
- "_user_tags",
- "__islocal",
- "__onload",
- "_liked_by",
- "__run_link_triggers",
- "__unsaved",
- ):
- if value := getattr(self, key, None):
- doc[key] = value
+ if not no_private_properties:
+ for key in (
+ "_user_tags",
+ "__islocal",
+ "__onload",
+ "_liked_by",
+ "__run_link_triggers",
+ "__unsaved",
+ ):
+ if value := getattr(self, key, None):
+ doc[key] = value
return doc
diff --git a/frappe/permissions.py b/frappe/permissions.py
index bf41b431d3..7fb189064d 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -614,15 +614,16 @@ def update_permission_property(
if_owner=0,
):
"""Update a property in Custom Perm"""
+ from frappe.core.doctype.custom_docperm.custom_docperm import update_custom_docperm
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, if_owner=if_owner)
+ custom_docperm = frappe.db.get_value(
+ "Custom DocPerm", dict(parent=doctype, role=role, permlevel=permlevel)
)
- table = DocType("Custom DocPerm")
- frappe.qb.update(table).set(ptype, value).where(table.name == name).run()
+ if custom_docperm:
+ update_custom_docperm(custom_docperm, {ptype: value})
if validate:
validate_permissions_for_doctype(doctype)
@@ -690,7 +691,8 @@ def reset_perms(doctype):
from frappe.desk.notifications import delete_notification_count_for
delete_notification_count_for(doctype)
- frappe.db.delete("Custom DocPerm", {"parent": doctype})
+ for custom_docperm in frappe.get_all("Custom DocPerm", filters={"parent": doctype}, pluck="name"):
+ frappe.delete_doc("Custom DocPerm", custom_docperm, ignore_permissions=True)
def get_linked_doctypes(dt: str) -> list: