refactor: single perm type for multiple doctypes
problem: if same perm type is defined in two different apps, we'd have to handle the merging of `doc_types` multi-select field
This commit is contained in:
parent
c459c7414b
commit
010351cee2
9 changed files with 70 additions and 131 deletions
|
|
@ -1,27 +1,26 @@
|
|||
{
|
||||
"actions": [],
|
||||
"autoname": "prompt",
|
||||
"creation": "2025-07-28 13:12:03.573433",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"module",
|
||||
"doc_types"
|
||||
"perm_type",
|
||||
"doc_type"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "doc_types",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Applies To",
|
||||
"options": "Permission Type DocType",
|
||||
"fieldname": "perm_type",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Permission Type",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "module",
|
||||
"fieldname": "doc_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Module",
|
||||
"options": "Module Def",
|
||||
"label": "Applies To (DocType)",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
|
|
@ -29,11 +28,10 @@
|
|||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-11-10 12:48:16.863229",
|
||||
"modified": "2025-11-13 16:17:58.536849",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Permission Type",
|
||||
"naming_rule": "Set by user",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,34 +20,26 @@ 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
|
||||
|
||||
doc_types: DF.TableMultiSelect[PermissionTypeDocType]
|
||||
module: DF.Link
|
||||
doc_type: DF.Link
|
||||
perm_type: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
def autoname(self):
|
||||
self.name = f"{frappe.scrub(self.doc_type)}_{frappe.scrub(self.perm_type)}"
|
||||
|
||||
def before_insert(self):
|
||||
self.name = frappe.scrub(self.name)
|
||||
self.perm_type = frappe.scrub(self.perm_type)
|
||||
|
||||
def validate(self):
|
||||
from frappe.permissions import std_rights
|
||||
|
||||
if self.name in std_rights:
|
||||
if self.perm_type in std_rights:
|
||||
frappe.throw(
|
||||
_("Permission Type '{0}' is reserved. Please choose another name.").format(self.name)
|
||||
_("Permission Type '{0}' is reserved. Please choose another name.").format(self.perm_type)
|
||||
)
|
||||
|
||||
# remove duplicate doc types
|
||||
seen = set()
|
||||
unique_doc_types = []
|
||||
for dt in self.doc_types:
|
||||
if dt.doc_type not in seen:
|
||||
seen.add(dt.doc_type)
|
||||
unique_doc_types.append(dt)
|
||||
|
||||
self.doc_types = unique_doc_types
|
||||
|
||||
def can_write(self):
|
||||
return (
|
||||
frappe.conf.developer_mode
|
||||
|
|
@ -56,30 +48,51 @@ class PermissionType(Document):
|
|||
or frappe.flags.in_test
|
||||
)
|
||||
|
||||
def should_export(self):
|
||||
return (
|
||||
frappe.conf.developer_mode
|
||||
and not frappe.flags.in_migrate
|
||||
and not frappe.flags.in_install
|
||||
and not frappe.flags.in_test
|
||||
)
|
||||
|
||||
def get_folder_path(self):
|
||||
app = frappe.get_doctype_app(self.doc_type)
|
||||
folder = frappe.get_app_source_path(app, app, "permission_types")
|
||||
return folder
|
||||
|
||||
def on_update(self):
|
||||
if not self.can_write():
|
||||
frappe.throw(_("Creation of this document is only permitted in developer mode."))
|
||||
|
||||
from frappe.modules.export_file import export_to_files
|
||||
|
||||
export_to_files(record_list=[["Permission Type", self.name]], record_module=self.module)
|
||||
|
||||
for target in CUSTOM_FIELD_TARGET:
|
||||
self.create_custom_field(target)
|
||||
|
||||
if self.should_export():
|
||||
from frappe.modules.export_file import export_to_files
|
||||
|
||||
module = frappe.db.get_value("DocType", self.doc_type, "module")
|
||||
export_to_files(record_list=[["Permission Type", self.name]], record_module=module)
|
||||
|
||||
def before_export(self, export_doc):
|
||||
del export_doc["idx"]
|
||||
del export_doc["docstatus"]
|
||||
for key in list(export_doc.keys()):
|
||||
if key.startswith("_"):
|
||||
del export_doc[key]
|
||||
|
||||
def create_custom_field(self, target):
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
|
||||
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})"
|
||||
depends_on = f"eval:doc.{field} == '{self.doc_type}'"
|
||||
|
||||
create_custom_field(
|
||||
target,
|
||||
{
|
||||
"fieldname": self.name,
|
||||
"label": self.name.replace("_", " ").title(),
|
||||
"fieldname": self.perm_type,
|
||||
"label": frappe.unscrub(self.perm_type),
|
||||
"fieldtype": "Check",
|
||||
"insert_after": "append",
|
||||
"depends_on": depends_on,
|
||||
|
|
@ -93,7 +106,9 @@ class PermissionType(Document):
|
|||
for target in CUSTOM_FIELD_TARGET:
|
||||
self.delete_custom_field(target)
|
||||
|
||||
delete_folder(self.module, "Permission Type", self.name)
|
||||
if self.should_export():
|
||||
module = frappe.db.get_value("DocType", self.doc_type, "module")
|
||||
delete_folder(module, "Permission Type", self.name)
|
||||
|
||||
def delete_custom_field(self, target):
|
||||
if name := self.custom_field_exists(target):
|
||||
|
|
@ -103,7 +118,7 @@ class PermissionType(Document):
|
|||
return frappe.db.exists(
|
||||
"Custom Field",
|
||||
{
|
||||
"fieldname": self.name,
|
||||
"fieldname": self.perm_type,
|
||||
"dt": target,
|
||||
},
|
||||
)
|
||||
|
|
@ -111,19 +126,10 @@ class PermissionType(Document):
|
|||
|
||||
@site_cache
|
||||
def get_doctype_ptype_map():
|
||||
ptypes = frappe.qb.get_query(
|
||||
"Permission Type",
|
||||
fields=[
|
||||
"name",
|
||||
{"doc_types": ["doc_type"]},
|
||||
],
|
||||
order_by="name",
|
||||
)
|
||||
ptypes = ptypes.run(as_dict=True)
|
||||
ptypes = frappe.get_all("Permission Type", fields=["perm_type", "doc_type"], order_by="perm_type")
|
||||
|
||||
doctype_ptype_map = defaultdict(list)
|
||||
for pt in ptypes:
|
||||
for dt in pt.doc_types:
|
||||
if pt.name not in doctype_ptype_map[dt.doc_type]:
|
||||
doctype_ptype_map[dt.doc_type].append(pt.name)
|
||||
if pt.perm_type not in doctype_ptype_map[pt.doc_type]:
|
||||
doctype_ptype_map[pt.doc_type].append(pt.perm_type)
|
||||
return dict(doctype_ptype_map)
|
||||
|
|
|
|||
|
|
@ -52,9 +52,9 @@ class IntegrationTestPermissionType(IntegrationTestCase):
|
|||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Permission Type",
|
||||
"name": "read",
|
||||
"perm_type": "read",
|
||||
"doc_type": "ToDo",
|
||||
"module": "Core",
|
||||
"doc_types": [{"doc_type": "ToDo"}],
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -76,9 +76,9 @@ class IntegrationTestPermissionType(IntegrationTestCase):
|
|||
ptype_doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Permission Type",
|
||||
"name": name,
|
||||
"perm_type": name,
|
||||
"doc_type": doc_type,
|
||||
"module": "Core",
|
||||
"doc_types": [{"doc_type": doc_type}],
|
||||
}
|
||||
)
|
||||
ptype_doc.insert(ignore_if_duplicate=True)
|
||||
|
|
@ -88,9 +88,9 @@ class IntegrationTestPermissionType(IntegrationTestCase):
|
|||
def _verify_custom_fields_created(self, ptype_doc, doc_type):
|
||||
"""Verify that custom fields are created for the permission type."""
|
||||
for target in ["Custom DocPerm", "DocPerm", "DocShare"]:
|
||||
custom_field = frappe.get_doc("Custom Field", {"dt": target, "fieldname": ptype_doc.name})
|
||||
custom_field = frappe.get_doc("Custom Field", {"dt": target, "fieldname": ptype_doc.perm_type})
|
||||
self.assertEqual(custom_field.dt, target)
|
||||
self.assertEqual(custom_field.fieldname, ptype_doc.name)
|
||||
self.assertEqual(custom_field.fieldname, ptype_doc.perm_type)
|
||||
self.assertEqual(custom_field.fieldtype, "Check")
|
||||
self.assertIn(doc_type, custom_field.depends_on)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"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": []
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
# 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
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"creation": "2025-10-30 19:17:39.833189",
|
||||
"doc_types": [
|
||||
{
|
||||
"doc_type": "User"
|
||||
}
|
||||
],
|
||||
"docstatus": 0,
|
||||
"doctype": "Permission Type",
|
||||
"idx": 0,
|
||||
"modified": "2025-11-03 16:58:56.819971",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "impersonate",
|
||||
"owner": "Administrator"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"creation": "2025-11-13 16:34:50.584738",
|
||||
"doc_type": "User",
|
||||
"doctype": "Permission Type",
|
||||
"modified": "2025-11-14 14:21:45.603237",
|
||||
"modified_by": "Administrator",
|
||||
"name": "user_impersonate",
|
||||
"owner": "Administrator",
|
||||
"perm_type": "impersonate"
|
||||
}
|
||||
|
|
@ -66,7 +66,6 @@ def sync_for(app_name, force=0, reset_permissions=False):
|
|||
"role",
|
||||
"has_role",
|
||||
"doctype",
|
||||
"permission_type_doctype",
|
||||
"permission_type",
|
||||
]:
|
||||
files.append(os.path.join(FRAPPE_PATH, "core", "doctype", core_module, f"{core_module}.json"))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue