refactor: create same perm type for multiple doctypes
This commit is contained in:
parent
3eb53bf012
commit
50dd9f31c0
12 changed files with 137 additions and 85 deletions
|
|
@ -23,18 +23,13 @@ frappe.ui.form.on("Permission Inspector", {
|
|||
}
|
||||
},
|
||||
add_custom_perm_types(frm) {
|
||||
const custom_perm_types = frm.doc.__onload.custom_perm_types
|
||||
if (!custom_perm_types?.length) return
|
||||
if (!frm.doc.ref_doctype) return
|
||||
|
||||
const standard_options = frm.meta.fields.find(f => f.fieldname === "permission_type").options;
|
||||
const doctype_ptype_map = frm.doc.__onload.doctype_ptype_map
|
||||
if (!Object.keys(doctype_ptype_map).length) return
|
||||
|
||||
const custom_options = (
|
||||
custom_perm_types
|
||||
.filter(pt => pt.applicable_doctype != frm.doc.ref_doctype)
|
||||
.map(pt => pt.label || pt.name)
|
||||
.join("\n")
|
||||
);
|
||||
const standard_options = frm.meta.fields.find(f => f.fieldname === "permission_type").options;
|
||||
const custom_options = doctype_ptype_map[frm.doc.ref_doctype].join("\n");
|
||||
|
||||
frm.set_df_property("permission_type", "options", `${standard_options}\n${custom_options}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,10 +38,9 @@ class PermissionInspector(Document):
|
|||
# end: auto-generated types
|
||||
|
||||
def onload(self):
|
||||
self.set_onload("custom_perm_types", frappe.get_all(
|
||||
"Permission Type",
|
||||
fields=["name", "label", "applicable_for"],
|
||||
))
|
||||
from frappe.core.doctype.permission_type.permission_type import get_doctype_ptype_map
|
||||
|
||||
self.set_onload("doctype_ptype_map", get_doctype_ptype_map())
|
||||
|
||||
@frappe.whitelist()
|
||||
def debug(self):
|
||||
|
|
|
|||
|
|
@ -5,33 +5,30 @@
|
|||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"applicable_for",
|
||||
"column_break_fyaj",
|
||||
"label"
|
||||
"module",
|
||||
"doc_types"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "applicable_for",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Applicable for",
|
||||
"options": "DocType",
|
||||
"fieldname": "doc_types",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Applies To",
|
||||
"options": "Permission Type DocType",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"label": "Label"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_fyaj",
|
||||
"fieldtype": "Column Break"
|
||||
"fieldname": "module",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Module",
|
||||
"options": "Module Def",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-10-30 18:52:03.750433",
|
||||
"modified": "2025-11-03 15:53:10.367326",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Permission Type",
|
||||
|
|
|
|||
|
|
@ -21,10 +21,11 @@ class PermissionType(Document):
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.core.doctype.permission_type_doctype.permission_type_doctype import PermissionTypeDocType
|
||||
from frappe.types import DF
|
||||
|
||||
applicable_for: DF.Link
|
||||
label: DF.Data | None
|
||||
doc_types: DF.TableMultiSelect[PermissionTypeDocType]
|
||||
module: DF.Link
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
|
|
@ -36,11 +37,7 @@ class PermissionType(Document):
|
|||
)
|
||||
|
||||
def can_write(self):
|
||||
return (
|
||||
frappe.conf.developer_mode
|
||||
or frappe.flags.in_migrate
|
||||
or frappe.flags.in_install
|
||||
)
|
||||
return frappe.conf.developer_mode or frappe.flags.in_migrate or frappe.flags.in_install
|
||||
|
||||
def on_update(self):
|
||||
if not self.can_write():
|
||||
|
|
@ -48,22 +45,21 @@ class PermissionType(Document):
|
|||
|
||||
from frappe.modules.export_file import export_to_files
|
||||
|
||||
module = get_doctype_module(self.applicable_for)
|
||||
export_to_files(record_list=[["Permission Type", self.name]], record_module=module)
|
||||
export_to_files(record_list=[["Permission Type", self.name]], record_module=self.module)
|
||||
|
||||
for doctype in CUSTOM_FIELD_TARGET:
|
||||
self.create_custom_field(doctype)
|
||||
for target in CUSTOM_FIELD_TARGET:
|
||||
self.create_custom_field(target)
|
||||
|
||||
def create_custom_field(self, doctype):
|
||||
def create_custom_field(self, target):
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
|
||||
if not self.custom_field_exists(doctype):
|
||||
depends_on = f"eval:doc.parent == '{self.applicable_for}'"
|
||||
if doctype == "DocShare":
|
||||
depends_on = f"eval:doc.share_doctype == '{self.applicable_for}'"
|
||||
if not self.custom_field_exists(target):
|
||||
field = "share_doctype" if target == "DocShare" else "parent"
|
||||
doc_types = [dt.doc_type for dt in self.doc_types if dt.doc_type]
|
||||
depends_on = f"eval:{frappe.as_json(doc_types)}.includes(doc.{field})"
|
||||
|
||||
create_custom_field(
|
||||
doctype,
|
||||
target,
|
||||
{
|
||||
"fieldname": self.name,
|
||||
"label": self.name.replace("_", " ").title(),
|
||||
|
|
@ -77,34 +73,39 @@ class PermissionType(Document):
|
|||
if not self.can_write():
|
||||
frappe.throw(_("Deletion of this document is only permitted in developer mode."))
|
||||
|
||||
for doctype in CUSTOM_FIELD_TARGET:
|
||||
self.delete_custom_field(doctype)
|
||||
for target in CUSTOM_FIELD_TARGET:
|
||||
self.delete_custom_field(target)
|
||||
|
||||
module = get_doctype_module(self.applicable_for)
|
||||
delete_folder(module, "Permission Type", self.name)
|
||||
delete_folder(self.module, "Permission Type", self.name)
|
||||
|
||||
def delete_custom_field(self, doctype):
|
||||
if name := self.custom_field_exists(doctype):
|
||||
def delete_custom_field(self, target):
|
||||
if name := self.custom_field_exists(target):
|
||||
frappe.delete_doc("Custom Field", name)
|
||||
|
||||
def custom_field_exists(self, doctype):
|
||||
def custom_field_exists(self, target):
|
||||
return frappe.db.exists(
|
||||
"Custom Field",
|
||||
{
|
||||
"fieldname": self.name,
|
||||
"dt": doctype,
|
||||
"dt": target,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@site_cache
|
||||
def get_custom_ptype_map():
|
||||
ptypes = frappe.get_all(
|
||||
def get_doctype_ptype_map():
|
||||
ptypes = frappe.qb.get_query(
|
||||
"Permission Type",
|
||||
fields=["name", "label", "applicable_for"],
|
||||
fields=[
|
||||
"name",
|
||||
{"doc_types": ["doc_type"]},
|
||||
],
|
||||
order_by="name",
|
||||
)
|
||||
custom_ptype_map = defaultdict(list)
|
||||
ptypes = ptypes.run(as_dict=True)
|
||||
|
||||
doctype_ptype_map = defaultdict(list)
|
||||
for pt in ptypes:
|
||||
custom_ptype_map[pt["applicable_for"]].append(pt["label"] or pt["name"])
|
||||
return dict(custom_ptype_map)
|
||||
for dt in pt.doc_types:
|
||||
doctype_ptype_map[dt.doc_type].append(pt.name)
|
||||
return dict(doctype_ptype_map)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
|
|
@ -12,11 +11,20 @@ EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
|||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
|
||||
class IntegrationTestPermissionType(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for PermissionType.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
def test_permission_type_creation_deletion(self): ...
|
||||
|
||||
def test_permission_type_creation_reserved_name(self): ...
|
||||
|
||||
def test_role_permission_with_custom_permission_type(self): ...
|
||||
|
||||
def test_share_permission_with_custom_permission_type(self): ...
|
||||
|
||||
def test_role_permission_mapping_with_custom_permission_type(self): ...
|
||||
|
||||
def test_export_import_of_permission_type(self): ...
|
||||
|
|
|
|||
0
frappe/core/doctype/permission_type_doctype/__init__.py
Normal file
0
frappe/core/doctype/permission_type_doctype/__init__.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-11-03 15:51:52.422122",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"doc_type"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "doc_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "DocType",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-11-03 15:52:16.161580",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Permission Type DocType",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"rows_threshold_for_grid_search": 20,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Copyright (c) 2025, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class PermissionTypeDocType(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
|
||||
|
||||
doc_type: DF.Link
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
|
|
@ -132,19 +132,14 @@ frappe.PermissionEngine = class PermissionEngine {
|
|||
.append(`<hr><h5>${__("Standard Permissions")}:</h5><br>`);
|
||||
let $wrapper = $("<p></p>").appendTo($d);
|
||||
data.message.forEach((d) => {
|
||||
let rights = this.rights
|
||||
let custom_rights = this.options.doctype_ptype_map[doctype] || [];
|
||||
d.rights = this.rights
|
||||
.filter((r) => d[r])
|
||||
.concat(custom_rights)
|
||||
.map((r) => {
|
||||
return __(toTitle(frappe.unscrub(r)));
|
||||
});
|
||||
|
||||
this.options.custom_rights.forEach((r) => {
|
||||
if (r.applicable_for !== doctype || !d[r.name]) return;
|
||||
rights.push(__(toTitle(frappe.unscrub(r.label || r.name))));
|
||||
});
|
||||
|
||||
d.rights = rights.join(", ");
|
||||
|
||||
$wrapper.append(`<div class="row">\
|
||||
<div class="col-xs-5"><b>${__(d.role)}</b>, ${__("Level")} ${d.permlevel || 0}</div>\
|
||||
<div class="col-xs-7">${d.rights}</div>\
|
||||
|
|
@ -270,9 +265,8 @@ frappe.PermissionEngine = class PermissionEngine {
|
|||
}
|
||||
});
|
||||
|
||||
this.options.custom_rights.forEach((r) => {
|
||||
if (r.applicable_for !== d.parent) return;
|
||||
this.add_check(perm_container, d, r.name);
|
||||
this.options.doctype_ptype_map[d.parent]?.forEach((r) => {
|
||||
this.add_check(perm_container, d, r);
|
||||
});
|
||||
|
||||
// buttons
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from frappe.core.doctype.doctype.doctype import (
|
|||
clear_permissions_cache,
|
||||
validate_permissions_for_doctype,
|
||||
)
|
||||
from frappe.core.doctype.permission_type.permission_type import get_doctype_ptype_map
|
||||
from frappe.exceptions import DoesNotExistError
|
||||
from frappe.modules.import_file import get_file_path, read_doc_from_file
|
||||
from frappe.permissions import (
|
||||
|
|
@ -57,18 +58,13 @@ def get_roles_and_doctypes():
|
|||
fields=["name"],
|
||||
)
|
||||
|
||||
custom_rights = frappe.get_all(
|
||||
"Permission Type",
|
||||
fields=["name", "label", "applicable_for"],
|
||||
)
|
||||
|
||||
doctypes_list = [{"label": _(d.get("name")), "value": d.get("name")} for d in doctypes]
|
||||
roles_list = [{"label": _(d.get("name")), "value": d.get("name")} for d in roles]
|
||||
|
||||
return {
|
||||
"doctypes": sorted(doctypes_list, key=lambda d: d["label"].casefold()),
|
||||
"roles": sorted(roles_list, key=lambda d: d["label"].casefold()),
|
||||
"custom_rights": custom_rights,
|
||||
"doctype_ptype_map": get_doctype_ptype_map(),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
{
|
||||
"applicable_for": "User",
|
||||
"creation": "2025-10-30 19:17:39.833189",
|
||||
"doc_types": [
|
||||
{
|
||||
"doc_type": "User"
|
||||
}
|
||||
],
|
||||
"docstatus": 0,
|
||||
"doctype": "Permission Type",
|
||||
"idx": 0,
|
||||
"label": "impersonate",
|
||||
"modified": "2025-10-30 19:17:39.833189",
|
||||
"modified": "2025-11-03 16:58:56.819971",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "impersonate",
|
||||
"owner": "Administrator"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import functools
|
|||
import frappe
|
||||
import frappe.share
|
||||
from frappe import _, msgprint
|
||||
from frappe.core.doctype.permission_type.permission_type import get_custom_ptype_map
|
||||
from frappe.core.doctype.permission_type.permission_type import get_doctype_ptype_map
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.utils import cint, cstr
|
||||
|
||||
|
|
@ -168,7 +168,7 @@ def has_permission(
|
|||
|
||||
def false_if_not_shared():
|
||||
std_rights = ["read", "write", "share", "submit", "email", "print"]
|
||||
custom_rights = get_custom_ptype_map().get(doctype, [])
|
||||
custom_rights = get_doctype_ptype_map().get(doctype, [])
|
||||
|
||||
if ptype not in std_rights + custom_rights:
|
||||
debug and _debug_log(f"Permission type {ptype} can not be shared")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue