├── doctype_permission ├── config │ └── __init__.py ├── public │ └── .gitkeep ├── templates │ ├── __init__.py │ └── pages │ │ └── __init__.py ├── doctype_permission │ ├── __init__.py │ ├── doctype │ │ ├── __init__.py │ │ ├── doctype_permission │ │ │ ├── __init__.py │ │ │ ├── doctype_permission.js │ │ │ ├── doctype_permission.py │ │ │ ├── doctype_permission.json │ │ │ └── test_doctype_permission.py │ │ ├── doctype_permission_level │ │ │ ├── __init__.py │ │ │ ├── doctype_permission_level.js │ │ │ ├── test_doctype_permission_level.py │ │ │ ├── doctype_permission_level.py │ │ │ └── doctype_permission_level.json │ │ └── doctype_permission_condition │ │ │ ├── __init__.py │ │ │ ├── doctype_permission_condition.py │ │ │ └── doctype_permission_condition.json │ └── utils.py ├── modules.txt ├── __init__.py ├── patches.txt ├── fixtures │ └── doctype_permission_level.json └── hooks.py ├── .gitignore ├── .pre-commit-config.yaml ├── pyproject.toml ├── license.txt └── README.md /doctype_permission/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doctype_permission/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doctype_permission/templates/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doctype_permission/templates/pages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doctype_permission/modules.txt: -------------------------------------------------------------------------------- 1 | DocType Permission 2 | -------------------------------------------------------------------------------- /doctype_permission/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.0.1" 2 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/doctype/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/doctype/doctype_permission/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/doctype/doctype_permission_level/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/doctype/doctype_permission_condition/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.egg-info 4 | *.swp 5 | tags 6 | node_modules 7 | __pycache__ 8 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v3.2.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-yaml 8 | - id: check-added-large-files 9 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/doctype/doctype_permission/doctype_permission.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Kitti U. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("DocType Permission", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/doctype/doctype_permission_level/doctype_permission_level.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Ecosoft and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("DocType Permission Level", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/doctype/doctype_permission_level/test_doctype_permission_level.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Ecosoft and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests.utils import FrappeTestCase 6 | 7 | 8 | class TestDocTypePermissionLevel(FrappeTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /doctype_permission/patches.txt: -------------------------------------------------------------------------------- 1 | [pre_model_sync] 2 | # Patches added in this section will be executed before doctypes are migrated 3 | # Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations 4 | 5 | [post_model_sync] 6 | # Patches added in this section will be executed after doctypes are migrated 7 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/doctype/doctype_permission_level/doctype_permission_level.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Ecosoft and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class DocTypePermissionLevel(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "doctype_permission" 3 | authors = [ 4 | { name = "Ecosoft", email = "kittiu@ecosoft.co.th"} 5 | ] 6 | description = "Allow easier setup of permission query" 7 | requires-python = ">=3.10" 8 | readme = "README.md" 9 | dynamic = ["version"] 10 | dependencies = [ 11 | # "frappe~=15.0.0" # Installed and managed by bench. 12 | ] 13 | 14 | [build-system] 15 | requires = ["flit_core >=3.4,<4"] 16 | build-backend = "flit_core.buildapi" 17 | 18 | # These dependencies are only installed when developer mode is enabled 19 | [tool.bench.dev-dependencies] 20 | # package_name = "~=1.1.0" 21 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/doctype/doctype_permission_condition/doctype_permission_condition.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Ecosoft and contributors 2 | # For license information, please see license.txt 3 | 4 | from frappe.model.document import Document 5 | 6 | 7 | class DocTypePermissionCondition(Document): 8 | pass 9 | 10 | def validate(self): 11 | if self.perm_level == "Employee Docs Only": 12 | self.script = """ 13 | employee = frappe.db.get_value("Employee", {"user_id": user}) 14 | conditions = "employee = %s" % frappe.db.escape(employee) 15 | """ 16 | if self.perm_level == "Owner Docs Only": 17 | self.script = 'conditions = "owner = %s" % frappe.db.escape(user)' 18 | if self.perm_level == "Full Access": 19 | self.script = 'conditions = "true"' 20 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/doctype/doctype_permission/doctype_permission.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Ecosoft and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | from frappe.model.document import Document 6 | from frappe.utils.safe_exec import safe_exec 7 | 8 | 9 | class DocTypePermission(Document): 10 | def validate(self): 11 | if self.docstatus in (1, 2): 12 | self.testing = 0 13 | 14 | def clear_cache(self): 15 | frappe.cache.delete_value("doctype_permission_map") 16 | return super().clear_cache() 17 | 18 | def on_trash(self): 19 | frappe.cache.delete_value("doctype_permission_map") 20 | 21 | def get_doctype_permission_conditions(self, user: str, script: str) -> list[str]: 22 | locals = {"user": user, "conditions": ""} 23 | safe_exec(script, None, locals, script_filename=self.name) 24 | if locals["conditions"]: 25 | return locals["conditions"] 26 | -------------------------------------------------------------------------------- /doctype_permission/fixtures/doctype_permission_level.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "docstatus": 0, 4 | "doctype": "DocType Permission Level", 5 | "modified": "2024-09-08 21:57:01.986104", 6 | "name": "Custom Query", 7 | "script": "conditions = \"\"", 8 | "title": "Custom Query" 9 | }, 10 | { 11 | "docstatus": 0, 12 | "doctype": "DocType Permission Level", 13 | "modified": "2024-09-08 21:59:47.636896", 14 | "name": "Employee's Docs", 15 | "script": "employee = frappe.db.get_value(\"Employee\", {\"user_id\": user})\nconditions = \"employee = %s\" % frappe.db.escape(employee)", 16 | "title": "Employee's Docs" 17 | }, 18 | { 19 | "docstatus": 0, 20 | "doctype": "DocType Permission Level", 21 | "modified": "2024-09-08 22:00:08.776045", 22 | "name": "Owner's Docs", 23 | "script": "conditions = \"owner = %s\" % frappe.db.escape(user)", 24 | "title": "Owner's Docs" 25 | }, 26 | { 27 | "docstatus": 0, 28 | "doctype": "DocType Permission Level", 29 | "modified": "2024-09-08 22:07:15.653912", 30 | "name": "Full Access", 31 | "script": "conditions = \"true\"", 32 | "title": "Full Access" 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/doctype/doctype_permission_level/doctype_permission_level.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "autoname": "field:title", 5 | "creation": "2024-09-08 21:50:15.191218", 6 | "doctype": "DocType", 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "title", 10 | "script" 11 | ], 12 | "fields": [ 13 | { 14 | "fieldname": "title", 15 | "fieldtype": "Data", 16 | "in_list_view": 1, 17 | "label": "Title", 18 | "reqd": 1, 19 | "unique": 1 20 | }, 21 | { 22 | "fieldname": "script", 23 | "fieldtype": "Code", 24 | "label": "Permission Query Script", 25 | "options": "Python", 26 | "reqd": 1 27 | } 28 | ], 29 | "index_web_pages_for_search": 1, 30 | "links": [], 31 | "modified": "2024-09-08 22:08:25.343580", 32 | "modified_by": "Administrator", 33 | "module": "DocType Permission", 34 | "name": "DocType Permission Level", 35 | "naming_rule": "By fieldname", 36 | "owner": "Administrator", 37 | "permissions": [ 38 | { 39 | "create": 1, 40 | "delete": 1, 41 | "email": 1, 42 | "export": 1, 43 | "print": 1, 44 | "read": 1, 45 | "report": 1, 46 | "role": "System Manager", 47 | "share": 1, 48 | "write": 1 49 | } 50 | ], 51 | "sort_field": "modified", 52 | "sort_order": "DESC", 53 | "states": [] 54 | } 55 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/doctype/doctype_permission_condition/doctype_permission_condition.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2024-09-08 12:33:45.246497", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "role", 10 | "perm_level", 11 | "script" 12 | ], 13 | "fields": [ 14 | { 15 | "columns": 2, 16 | "fieldname": "role", 17 | "fieldtype": "Link", 18 | "in_list_view": 1, 19 | "label": "Role", 20 | "options": "Role" 21 | }, 22 | { 23 | "columns": 6, 24 | "fetch_from": "perm_level.script", 25 | "fetch_if_empty": 1, 26 | "fieldname": "script", 27 | "fieldtype": "Code", 28 | "in_list_view": 1, 29 | "label": "Permission Query", 30 | "options": "Python" 31 | }, 32 | { 33 | "columns": 2, 34 | "fieldname": "perm_level", 35 | "fieldtype": "Link", 36 | "in_list_view": 1, 37 | "label": "Permission Level", 38 | "options": "DocType Permission Level", 39 | "reqd": 1 40 | } 41 | ], 42 | "index_web_pages_for_search": 1, 43 | "istable": 1, 44 | "links": [], 45 | "modified": "2024-09-08 22:10:43.188750", 46 | "modified_by": "Administrator", 47 | "module": "DocType Permission", 48 | "name": "DocType Permission Condition", 49 | "owner": "Administrator", 50 | "permissions": [], 51 | "sort_field": "modified", 52 | "sort_order": "DESC", 53 | "states": [] 54 | } 55 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/utils.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | 3 | 4 | def get_doctype_permission(user, doctype): 5 | user_roles = frappe.get_roles(user) 6 | if doctype_permission_names := get_doctype_permission_map().get(doctype): 7 | doc_perm_conditions = ["false"] 8 | for name in doctype_permission_names: 9 | dt_perm = frappe.get_doc("DocType Permission", name) 10 | for c in dt_perm.conditions: 11 | if c.role not in user_roles: # Matched user role for this condition 12 | continue 13 | if condition := dt_perm.get_doctype_permission_conditions(user, c.script): 14 | doc_perm_conditions.append(condition) 15 | return f"({' or '.join(doc_perm_conditions)})" 16 | return None 17 | 18 | 19 | def get_doctype_permission_map(): 20 | if frappe.flags.in_patch and not frappe.db.table_exists("DocType Permission"): 21 | return {} 22 | 23 | permission_map = frappe.cache.get_value("doctype_permission_map") 24 | if permission_map is None: 25 | permission_map = {} 26 | doc_perms = frappe.get_all( 27 | "DocType Permission", 28 | fields=("name", "ref_doctype"), 29 | or_filters=[ 30 | ["testing", "=", 1], 31 | ["docstatus", "=", 1], 32 | ], 33 | ) 34 | for perm in doc_perms: 35 | if not permission_map.get(perm.ref_doctype): 36 | permission_map[perm.ref_doctype] = [] 37 | permission_map[perm.ref_doctype] += [perm.name] 38 | frappe.cache.set_value("doctype_permission_map", permission_map) 39 | return permission_map 40 | 41 | 42 | def has_permission(doc): 43 | if get_doctype_permission_map().get(doc.doctype): 44 | if doc.is_new(): 45 | return True 46 | doc = frappe.get_list(doc.doctype, filters={"name": doc.name}, pluck="name") 47 | if not doc: 48 | return False 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## DocType Permission 2 | 3 | This app is the answer to the topic **[Design of User Permissions is Dangerous](https://discuss.frappe.io/t/the-design-of-user-permissions-is-dangerous/109103)**, where data is grant first and then add more restriction later. 4 | 5 | The DocType Permission is the opposite, it allow system admin to restrict first, and then grant more permission later. 6 | 7 | Behind the scene, it is using **permission_query_conditions** and **has_permission** in hooks.py and so, it works as additional security measurement with existing User Permission and Permission query. 8 | 9 | #### Key DocTypes: 10 | 11 | 1. **DocType Permission**, a submittable document that restrict and then grant different permission to different role 12 | 2. **DocType Permission Level**, master data for pre-built permission query script 13 | 14 | #### Benefit: 15 | 16 | * Intuitive and easy to us. Restrict first permit later. 17 | * Can work along side with standard user permissions, permission queries. 18 | 19 | #### Problem with Current System: 20 | 21 | Given example of a sensitive document such as Salary Slip. 22 | 23 | * Employees to see only his/her own Salary Slip 24 | * Payroll Users to see all Salary Slips 25 | 26 | Without this app, following setup is required. 27 | 28 | * For Employees, create each User Permission for each employee. If there are 1,000 employees then 1,000 User Permissions is required. 29 | * For Payroll Users, make sure there is no User Permissions created for them. 30 | 31 | As you can see it is now difficult to keep track of the amount of User Permissions. And if for some reason either system or human, a User Permission is missing for someone, this can be a very serious data breach! 32 | 33 | #### Solution with DocType Permission: 34 | 35 | Given the same example, no 1,000 User Pemissions is needed, just create 1 DocType Permission as following, 36 | 37 | ![doctype_permission](https://github.com/user-attachments/assets/4e3a483f-49e9-4793-8690-3f6b5dbc6aa2) 38 | 39 | As soon as this document is created for Salary Slip, all documents will be restrictred. And then by adding each Role’s Additional Permission, the permission will be granted (using OR condition). 40 | 41 | #### Notes: 42 | 43 | * The combined SQL condition would be, (false **OR** Role-1 **OR** Role-2) 44 | * This consition will then be **AND** with other SQL condition from User Permissiona and/or Permission Query (if used) 45 | * DocType Prmission Level are canned permission query which can be exteded by ourself 46 | 47 | ![doctype_permission_level](https://github.com/user-attachments/assets/73520437-6cad-4396-86bd-dee6f19b9769) 48 | 49 | 50 | #### License 51 | 52 | MIT 53 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/doctype/doctype_permission/doctype_permission.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "autoname": "field:title", 5 | "creation": "2024-09-06 14:30:11.957791", 6 | "doctype": "DocType", 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "title", 10 | "ref_doctype", 11 | "testing", 12 | "column_break_rqbq", 13 | "description", 14 | "section_break_zmxh", 15 | "conditions", 16 | "section_break_drte", 17 | "amended_from" 18 | ], 19 | "fields": [ 20 | { 21 | "fieldname": "amended_from", 22 | "fieldtype": "Link", 23 | "label": "Amended From", 24 | "no_copy": 1, 25 | "options": "DocType Permission", 26 | "print_hide": 1, 27 | "read_only": 1, 28 | "search_index": 1 29 | }, 30 | { 31 | "fieldname": "section_break_drte", 32 | "fieldtype": "Section Break" 33 | }, 34 | { 35 | "fieldname": "column_break_rqbq", 36 | "fieldtype": "Column Break" 37 | }, 38 | { 39 | "fieldname": "title", 40 | "fieldtype": "Data", 41 | "label": "Title", 42 | "reqd": 1, 43 | "unique": 1 44 | }, 45 | { 46 | "default": "0", 47 | "description": "During test, permission is applied without submission", 48 | "fieldname": "testing", 49 | "fieldtype": "Check", 50 | "label": "Testing" 51 | }, 52 | { 53 | "fieldname": "description", 54 | "fieldtype": "Small Text", 55 | "label": "Description" 56 | }, 57 | { 58 | "fieldname": "ref_doctype", 59 | "fieldtype": "Link", 60 | "in_list_view": 1, 61 | "in_standard_filter": 1, 62 | "label": "DocType", 63 | "options": "DocType", 64 | "reqd": 1 65 | }, 66 | { 67 | "description": "DocType Permission will first restrict access to all docs. Then additional permission can be given for each role.", 68 | "fieldname": "section_break_zmxh", 69 | "fieldtype": "Section Break", 70 | "label": "Addition Permission" 71 | }, 72 | { 73 | "fieldname": "conditions", 74 | "fieldtype": "Table", 75 | "options": "DocType Permission Condition" 76 | } 77 | ], 78 | "index_web_pages_for_search": 1, 79 | "is_submittable": 1, 80 | "links": [], 81 | "modified": "2024-09-08 21:48:02.850194", 82 | "modified_by": "Administrator", 83 | "module": "DocType Permission", 84 | "name": "DocType Permission", 85 | "naming_rule": "By fieldname", 86 | "owner": "Administrator", 87 | "permissions": [ 88 | { 89 | "amend": 1, 90 | "cancel": 1, 91 | "create": 1, 92 | "delete": 1, 93 | "email": 1, 94 | "export": 1, 95 | "print": 1, 96 | "read": 1, 97 | "report": 1, 98 | "role": "System Manager", 99 | "share": 1, 100 | "submit": 1, 101 | "write": 1 102 | } 103 | ], 104 | "sort_field": "modified", 105 | "sort_order": "DESC", 106 | "states": [] 107 | } 108 | -------------------------------------------------------------------------------- /doctype_permission/doctype_permission/doctype/doctype_permission/test_doctype_permission.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Kitti U. and Contributors 2 | # See license.txt 3 | 4 | import frappe 5 | from frappe.tests.utils import FrappeTestCase 6 | 7 | 8 | class TestDocTypePermission(FrappeTestCase): 9 | 10 | def setUp(self): 11 | frappe.set_user("Administrator") 12 | self.create_doctype_permission() 13 | 14 | def tearDown(self): 15 | frappe.db.rollback() 16 | 17 | def test_employee_see_own_docs(self): 18 | # As Admin, create new user/employee 19 | frappe.set_user("Administrator") 20 | (user, employee) = make_employee("employee@company.com", "EMP01") 21 | user.add_roles(["Employee"]) 22 | # Change to Employee 23 | frappe.set_user("employee@company.com") 24 | count = len(frappe.get_list("Employee", pluck="name")) 25 | # Test See Own Doc 26 | self.assertEqual(count, 1) 27 | 28 | def test_manager_see_all_docs(self): 29 | # As Admin, create new user/employee 30 | frappe.set_user("Administrator") 31 | (user, employee) = make_employee("manager@company.com", "EMP02") 32 | user.add_roles(["System Manager"]) 33 | # Change to System Manager 34 | frappe.set_user("manager@company.com") 35 | count = len(frappe.get_list("Employee", pluck="name")) 36 | # Test See All Docs 37 | self.assertGreater(count, 1) 38 | 39 | def create_doctype_permission(self): 40 | """ 41 | - Employee - See Employee's Docs 42 | - System Manager - See All Docs 43 | """ 44 | perm_levels = dict(frappe.get_all( 45 | "DocType Permission Level", 46 | fields=["name", "script"], 47 | as_list=1 48 | )) 49 | doc_perm = frappe.get_doc( 50 | { 51 | "doctype": "DocType Permission", 52 | "title": "Employee Permission", 53 | "ref_doctype": "Employee", 54 | "conditions": [ 55 | { 56 | "role": "Employee", 57 | "permlevel": "Employee's Docs", 58 | "script": perm_levels["Employee's Docs"], 59 | }, 60 | { 61 | "role": "System Manager", 62 | "permlevel": "Full Access", 63 | "script": perm_levels["Full Access"], 64 | }, 65 | ] 66 | } 67 | ).insert(ignore_if_duplicate=True, ignore_mandatory=True) 68 | doc_perm.reload() 69 | doc_perm.submit() 70 | return doc_perm 71 | 72 | 73 | def make_employee(user, emp): 74 | new_user = frappe.get_doc( 75 | { 76 | "doctype": "User", 77 | "email": user, 78 | "first_name": user, 79 | "new_password": "password", 80 | "send_welcome_email": 0, 81 | "roles": [{"doctype": "Has Role", "role": "Employee"}], 82 | } 83 | ).insert(ignore_if_duplicate=True, ignore_mandatory=True) 84 | new_employee = frappe.get_doc( 85 | { 86 | "doctype": "Employee", 87 | "name": emp, 88 | "first_name": user, 89 | "user_id": user, 90 | "status": "Active", 91 | "create_user_permission": 0, # Do not create user permission 92 | } 93 | ).insert(ignore_if_duplicate=True, ignore_mandatory=True) 94 | new_user.reload() 95 | new_employee.reload() 96 | return (new_user, new_employee) 97 | -------------------------------------------------------------------------------- /doctype_permission/hooks.py: -------------------------------------------------------------------------------- 1 | app_name = "doctype_permission" 2 | app_title = "DocType Permission" 3 | app_publisher = "Ecosoft" 4 | app_description = "Allow easier setup of permission query" 5 | app_email = "kittiu@ecosoft.co.th" 6 | app_license = "mit" 7 | 8 | fixtures = [ 9 | { 10 | "doctype": "DocType Permission Level", 11 | "filters": [ 12 | [ 13 | "name", 14 | "in", 15 | ( 16 | "Full Access", 17 | "Owner's Docs", 18 | "Employee's Docs", 19 | "Custom Query", 20 | ), 21 | ] 22 | ], 23 | } 24 | ] 25 | 26 | 27 | # Apps 28 | # ------------------ 29 | 30 | # required_apps = [] 31 | 32 | # Each item in the list will be shown as an app in the apps page 33 | # add_to_apps_screen = [ 34 | # { 35 | # "name": "doctype_permission", 36 | # "logo": "/assets/doctype_permission/logo.png", 37 | # "title": "DocType Permission", 38 | # "route": "/doctype_permission", 39 | # "has_permission": "doctype_permission.api.permission.has_app_permission" 40 | # } 41 | # ] 42 | 43 | # Includes in 44 | # ------------------ 45 | 46 | # include js, css files in header of desk.html 47 | # app_include_css = "/assets/doctype_permission/css/doctype_permission.css" 48 | # app_include_js = "/assets/doctype_permission/js/doctype_permission.js" 49 | 50 | # include js, css files in header of web template 51 | # web_include_css = "/assets/doctype_permission/css/doctype_permission.css" 52 | # web_include_js = "/assets/doctype_permission/js/doctype_permission.js" 53 | 54 | # include custom scss in every website theme (without file extension ".scss") 55 | # website_theme_scss = "doctype_permission/public/scss/website" 56 | 57 | # include js, css files in header of web form 58 | # webform_include_js = {"doctype": "public/js/doctype.js"} 59 | # webform_include_css = {"doctype": "public/css/doctype.css"} 60 | 61 | # include js in page 62 | # page_js = {"page" : "public/js/file.js"} 63 | 64 | # include js in doctype views 65 | # doctype_js = {"doctype" : "public/js/doctype.js"} 66 | # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} 67 | # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} 68 | # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} 69 | 70 | # Svg Icons 71 | # ------------------ 72 | # include app icons in desk 73 | # app_include_icons = "doctype_permission/public/icons.svg" 74 | 75 | # Home Pages 76 | # ---------- 77 | 78 | # application home page (will override Website Settings) 79 | # home_page = "login" 80 | 81 | # website user home page (by Role) 82 | # role_home_page = { 83 | # "Role": "home_page" 84 | # } 85 | 86 | # Generators 87 | # ---------- 88 | 89 | # automatically create page for each record of this doctype 90 | # website_generators = ["Web Page"] 91 | 92 | # Jinja 93 | # ---------- 94 | 95 | # add methods and filters to jinja environment 96 | # jinja = { 97 | # "methods": "doctype_permission.utils.jinja_methods", 98 | # "filters": "doctype_permission.utils.jinja_filters" 99 | # } 100 | 101 | # Installation 102 | # ------------ 103 | 104 | # before_install = "doctype_permission.install.before_install" 105 | # after_install = "doctype_permission.install.after_install" 106 | 107 | # Uninstallation 108 | # ------------ 109 | 110 | # before_uninstall = "doctype_permission.uninstall.before_uninstall" 111 | # after_uninstall = "doctype_permission.uninstall.after_uninstall" 112 | 113 | # Integration Setup 114 | # ------------------ 115 | # To set up dependencies/integrations with other apps 116 | # Name of the app being installed is passed as an argument 117 | 118 | # before_app_install = "doctype_permission.utils.before_app_install" 119 | # after_app_install = "doctype_permission.utils.after_app_install" 120 | 121 | # Integration Cleanup 122 | # ------------------- 123 | # To clean up dependencies/integrations with other apps 124 | # Name of the app being uninstalled is passed as an argument 125 | 126 | # before_app_uninstall = "doctype_permission.utils.before_app_uninstall" 127 | # after_app_uninstall = "doctype_permission.utils.after_app_uninstall" 128 | 129 | # Desk Notifications 130 | # ------------------ 131 | # See frappe.core.notifications.get_notification_config 132 | 133 | # notification_config = "doctype_permission.notifications.get_notification_config" 134 | 135 | # Permissions 136 | # ----------- 137 | # Permissions evaluated in scripted ways 138 | 139 | permission_query_conditions = { 140 | "*": "doctype_permission.doctype_permission.utils.get_doctype_permission", 141 | } 142 | 143 | has_permission = { 144 | "*": "doctype_permission.doctype_permission.utils.has_permission", 145 | } 146 | 147 | # DocType Class 148 | # --------------- 149 | # Override standard doctype classes 150 | 151 | # override_doctype_class = { 152 | # "ToDo": "custom_app.overrides.CustomToDo" 153 | # } 154 | 155 | # Document Events 156 | # --------------- 157 | # Hook on document methods and events 158 | 159 | # doc_events = { 160 | # "*": { 161 | # "on_update": "method", 162 | # "on_cancel": "method", 163 | # "on_trash": "method" 164 | # } 165 | # } 166 | 167 | # Scheduled Tasks 168 | # --------------- 169 | 170 | # scheduler_events = { 171 | # "all": [ 172 | # "doctype_permission.tasks.all" 173 | # ], 174 | # "daily": [ 175 | # "doctype_permission.tasks.daily" 176 | # ], 177 | # "hourly": [ 178 | # "doctype_permission.tasks.hourly" 179 | # ], 180 | # "weekly": [ 181 | # "doctype_permission.tasks.weekly" 182 | # ], 183 | # "monthly": [ 184 | # "doctype_permission.tasks.monthly" 185 | # ], 186 | # } 187 | 188 | # Testing 189 | # ------- 190 | 191 | # before_tests = "doctype_permission.install.before_tests" 192 | 193 | # Overriding Methods 194 | # ------------------------------ 195 | # 196 | # override_whitelisted_methods = { 197 | # "frappe.desk.doctype.event.event.get_events": "doctype_permission.event.get_events" 198 | # } 199 | # 200 | # each overriding function accepts a `data` argument; 201 | # generated from the base implementation of the doctype dashboard, 202 | # along with any modifications made in other Frappe apps 203 | # override_doctype_dashboards = { 204 | # "Task": "doctype_permission.task.get_dashboard_data" 205 | # } 206 | 207 | # exempt linked doctypes from being automatically cancelled 208 | # 209 | # auto_cancel_exempted_doctypes = ["Auto Repeat"] 210 | 211 | # Ignore links to specified DocTypes when deleting documents 212 | # ----------------------------------------------------------- 213 | 214 | # ignore_links_on_delete = ["Communication", "ToDo"] 215 | 216 | # Request Events 217 | # ---------------- 218 | # before_request = ["doctype_permission.utils.before_request"] 219 | # after_request = ["doctype_permission.utils.after_request"] 220 | 221 | # Job Events 222 | # ---------- 223 | # before_job = ["doctype_permission.utils.before_job"] 224 | # after_job = ["doctype_permission.utils.after_job"] 225 | 226 | # User Data Protection 227 | # -------------------- 228 | 229 | # user_data_fields = [ 230 | # { 231 | # "doctype": "{doctype_1}", 232 | # "filter_by": "{filter_by}", 233 | # "redact_fields": ["{field_1}", "{field_2}"], 234 | # "partial": 1, 235 | # }, 236 | # { 237 | # "doctype": "{doctype_2}", 238 | # "filter_by": "{filter_by}", 239 | # "partial": 1, 240 | # }, 241 | # { 242 | # "doctype": "{doctype_3}", 243 | # "strict": False, 244 | # }, 245 | # { 246 | # "doctype": "{doctype_4}" 247 | # } 248 | # ] 249 | 250 | # Authentication and authorization 251 | # -------------------------------- 252 | 253 | # auth_hooks = [ 254 | # "doctype_permission.auth.validate" 255 | # ] 256 | 257 | # Automatically update python controller files with type annotations for this app. 258 | # export_python_type_annotations = True 259 | 260 | # default_log_clearing_doctypes = { 261 | # "Logging DocType Name": 30 # days to retain logs 262 | # } 263 | --------------------------------------------------------------------------------