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: