diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js
index 3e2a423b06..2b06d86553 100644
--- a/frappe/core/doctype/doctype/doctype.js
+++ b/frappe/core/doctype/doctype/doctype.js
@@ -13,6 +13,17 @@
frappe.ui.form.on('DocType', {
refresh: function(frm) {
+ frm.set_query('role', 'permissions', function(doc) {
+ if (doc.custom && frappe.session.user != 'Administrator') {
+ return {
+ query: "frappe.core.doctype.role.role.role_query",
+ filters: {
+ name: ['not in', ['All']]
+ }
+ };
+ }
+ });
+
if(frappe.session.user !== "Administrator" || !frappe.boot.developer_mode) {
if(frm.is_new()) {
frm.set_value("custom", 1);
diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py
index c0a82c594a..db9ae19b03 100644
--- a/frappe/core/doctype/doctype/doctype.py
+++ b/frappe/core/doctype/doctype/doctype.py
@@ -1112,6 +1112,20 @@ def validate_permissions(doctype, for_remove=False, alert=False):
if d.get("import") and not isimportable:
frappe.throw(_("{0}: Cannot set import as {1} is not importable").format(get_txt(d), doctype))
+ def validate_permission_for_all_role(d):
+ if frappe.session.user == 'Administrator': return
+
+ if doctype.custom:
+ if d.role == 'All':
+ frappe.throw(_('Row # {0}: Non administrator user can not set the role {1} to the custom doctype')
+ .format(d.idx, frappe.bold(_('All'))), title=_('Permissions Error'))
+
+ roles = [row.name for row in frappe.get_all('Role', filters={'is_custom': 1})]
+
+ if d.role in roles:
+ frappe.throw(_('Row # {0}: Non administrator user can not set the role {1} to the custom doctype')
+ .format(d.idx, frappe.bold(_(d.role))), title=_('Permissions Error'))
+
for d in permissions:
if not d.permlevel:
d.permlevel=0
@@ -1123,6 +1137,7 @@ def validate_permissions(doctype, for_remove=False, alert=False):
check_if_importable(d)
check_level_zero_is_set(d)
remove_rights_for_single(d)
+ validate_permission_for_all_role(d)
def make_module_and_roles(doc, perm_fieldname="permissions"):
"""Make `Module Def` and `Role` records if already not made. Called while installing."""
diff --git a/frappe/core/doctype/role/role.js b/frappe/core/doctype/role/role.js
index 6968607008..47b2207c9a 100644
--- a/frappe/core/doctype/role/role.js
+++ b/frappe/core/doctype/role/role.js
@@ -3,6 +3,9 @@
frappe.ui.form.on('Role', {
refresh: function(frm) {
+ frm.set_df_property('is_custom', 'read_only',
+ frappe.session.user == 'Administrator' ? false : true);
+
frm.add_custom_button("Role Permissions Manager", function() {
frappe.route_options = {"role": frm.doc.name};
frappe.set_route("permission-manager");
diff --git a/frappe/core/doctype/role/role.json b/frappe/core/doctype/role/role.json
index e47dc7194b..0135cbf9e8 100644
--- a/frappe/core/doctype/role/role.json
+++ b/frappe/core/doctype/role/role.json
@@ -25,7 +25,8 @@
"form_settings_section",
"form_sidebar",
"timeline",
- "dashboard"
+ "dashboard",
+ "is_custom"
],
"fields": [
{
@@ -141,13 +142,20 @@
"fieldname": "notifications",
"fieldtype": "Check",
"label": "Notifications"
+ },
+ {
+ "default": "0",
+ "fieldname": "is_custom",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Is Custom"
}
],
"icon": "fa fa-bookmark",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2020-12-03 14:08:38.181035",
+ "modified": "2021-01-27 10:35:37.638350",
"modified_by": "Administrator",
"module": "Core",
"name": "Role",
diff --git a/frappe/core/doctype/role/role.py b/frappe/core/doctype/role/role.py
index 7adfeba8d9..bacd2e5274 100644
--- a/frappe/core/doctype/role/role.py
+++ b/frappe/core/doctype/role/role.py
@@ -53,7 +53,6 @@ class Role(Document):
if user_type != user.user_type:
user.save()
-
def get_info_based_on_role(role, field='email'):
''' Get information of all users that have been assigned this role '''
users = frappe.get_list("Has Role", filters={"role": role, "parenttype": "User"},
@@ -73,3 +72,15 @@ def get_user_info(users, field='email'):
def get_users(role):
return [d.parent for d in frappe.get_all("Has Role", filters={"role": role, "parenttype": "User"},
fields=["parent"])]
+
+
+# searches for active employees
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def role_query(doctype, txt, searchfield, start, page_len, filters):
+ filters.update({
+ 'is_custom': 0, 'name': ('like', '%{0}%'.format(txt))
+ })
+
+ return frappe.get_all('Role', limit_start=start, limit_page_length=page_len,
+ filters=filters, as_list=1)
\ No newline at end of file
diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js
index 3548b4c913..081d0788f3 100644
--- a/frappe/core/doctype/user/user.js
+++ b/frappe/core/doctype/user/user.js
@@ -59,10 +59,11 @@ frappe.ui.form.on('User', {
onload: function(frm) {
frm.can_edit_roles = has_access_to_edit_user();
- if (frm.can_edit_roles && !frm.is_new()) {
- if (!frm.roles_editor) {
+ if (frm.can_edit_roles && !frm.is_new() && frm.doc.user_type == 'System User') {
+ if(!frm.roles_editor) {
const role_area = $('
')
.appendTo(frm.fields_dict.roles_html.wrapper);
+
frm.roles_editor = new frappe.RoleEditor(role_area, frm, frm.doc.role_profile_name ? 1 : 0);
var module_area = $('
')
@@ -75,7 +76,7 @@ frappe.ui.form.on('User', {
},
refresh: function(frm) {
var doc = frm.doc;
- if(!frm.is_new() && !frm.roles_editor && frm.can_edit_roles) {
+ if (frm.doc.user_type == 'System User' && !frm.is_new() && !frm.roles_editor && frm.can_edit_roles) {
frm.reload_doc();
return;
}
diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json
index 747ace5de6..66902732cd 100644
--- a/frappe/core/doctype/user/user.json
+++ b/frappe/core/doctype/user/user.json
@@ -191,7 +191,7 @@
"print_hide": 1
},
{
- "depends_on": "enabled",
+ "depends_on": "eval:doc.user_type == 'System User' && doc.enabled == 1",
"fieldname": "sb1",
"fieldtype": "Section Break",
"label": "Roles",
@@ -391,6 +391,7 @@
},
{
"collapsible": 1,
+ "depends_on": "eval:doc.user_type == 'System User'",
"fieldname": "sb_allow_modules",
"fieldtype": "Section Break",
"label": "Allow Modules",
@@ -453,18 +454,18 @@
"label": "Simultaneous Sessions"
},
{
+ "bold": 1,
"default": "System User",
"description": "If the user has any role checked, then the user becomes a \"System User\". \"System User\" has access to the desktop",
"fieldname": "user_type",
- "fieldtype": "Select",
+ "fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "User Type",
"oldfieldname": "user_type",
"oldfieldtype": "Select",
- "options": "System User\nWebsite User",
- "permlevel": 1,
- "read_only": 1
+ "options": "User Type",
+ "permlevel": 1
},
{
"description": "Allow user to login only after this hour (0-24)",
@@ -669,7 +670,7 @@
}
],
"max_attachments": 5,
- "modified": "2021-02-01 16:11:06.037543",
+ "modified": "2021-02-02 16:11:06.037543",
"modified_by": "Administrator",
"module": "Core",
"name": "User",
diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py
index 64ac7f5bca..04d087e82a 100644
--- a/frappe/core/doctype/user/user.py
+++ b/frappe/core/doctype/user/user.py
@@ -10,7 +10,8 @@ import frappe.share
import frappe.defaults
import frappe.permissions
from frappe.model.document import Document
-from frappe.utils import cint, flt, has_gravatar, escape_html, format_datetime, now_datetime, get_formatted_email, today
+from frappe.utils import (cint, flt, has_gravatar, escape_html, format_datetime,
+ now_datetime, get_formatted_email, today)
from frappe import throw, msgprint, _
from frappe.utils.password import update_password as _update_password, check_password, get_password_reset_limit
from frappe.desk.notifications import clear_notifications
@@ -19,6 +20,7 @@ from frappe.utils.user import get_system_managers
from frappe.website.utils import is_signup_enabled
from frappe.rate_limiter import rate_limit
from frappe.utils.background_jobs import enqueue
+from frappe.core.doctype.user_type.user_type import user_linked_with_permission_on_doctype
STANDARD_USERS = ("Guest", "Administrator")
@@ -186,11 +188,36 @@ class User(Document):
_update_password(user=self.name, pwd=new_password, logout_all_sessions=self.logout_all_sessions)
def set_system_user(self):
- '''Set as System User if any of the given roles has desk_access'''
- if self.has_desk_access() or self.name == 'Administrator':
- self.user_type = 'System User'
+ '''For the standard users like admin and guest, the user type is fixed.'''
+ user_type_mapper = {
+ 'Administrator': 'System User',
+ 'Guest': 'Website User'
+ }
+
+ if self.user_type and not frappe.get_cached_value('User Type', self.user_type, 'is_standard'):
+ if user_type_mapper.get(self.name):
+ self.user_type = user_type_mapper.get(self.name)
+ else:
+ self.set_roles_and_modules_based_on_user_type()
else:
- self.user_type = 'Website User'
+ '''Set as System User if any of the given roles has desk_access'''
+ self.user_type = 'System User' if self.has_desk_access() else 'Website User'
+
+ def set_roles_and_modules_based_on_user_type(self):
+ user_type_doc = frappe.get_cached_doc('User Type', self.user_type)
+ if user_type_doc.role:
+ self.roles = []
+
+ # Check whether User has linked with the 'Apply User Permission On' doctype or not
+ if user_linked_with_permission_on_doctype(user_type_doc, self.name):
+ self.append('roles', {
+ 'role': user_type_doc.role
+ })
+
+ frappe.msgprint(_('Role has been set as per the user type {0}')
+ .format(self.user_type), alert=True)
+
+ user_type_doc.update_modules_in_user(self)
def has_desk_access(self):
'''Return true if any of the set roles has desk access'''
@@ -877,7 +904,8 @@ def reset_password(user):
def user_query(doctype, txt, searchfield, start, page_len, filters):
from frappe.desk.reportview import get_match_cond, get_filters_cond
conditions=[]
- user_type_condition = "and user_type = 'System User'"
+
+ user_type_condition = "and user_type != 'Website User'"
if filters and filters.get('ignore_user_type'):
user_type_condition = ''
filters.pop('ignore_user_type')
diff --git a/frappe/core/doctype/user_document_type/__init__.py b/frappe/core/doctype/user_document_type/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/core/doctype/user_document_type/user_document_type.json b/frappe/core/doctype/user_document_type/user_document_type.json
new file mode 100644
index 0000000000..66ba7cd12e
--- /dev/null
+++ b/frappe/core/doctype/user_document_type/user_document_type.json
@@ -0,0 +1,76 @@
+{
+ "actions": [],
+ "creation": "2021-01-13 01:51:40.158521",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "document_type",
+ "column_break_2",
+ "is_custom",
+ "permissions_section",
+ "read",
+ "write",
+ "create"
+ ],
+ "fields": [
+ {
+ "fieldname": "document_type",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Document Type",
+ "options": "DocType",
+ "reqd": 1
+ },
+ {
+ "fieldname": "permissions_section",
+ "fieldtype": "Section Break",
+ "label": "Role Permissions"
+ },
+ {
+ "default": "1",
+ "fieldname": "read",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Read"
+ },
+ {
+ "default": "0",
+ "fieldname": "write",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Write"
+ },
+ {
+ "default": "0",
+ "fieldname": "create",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Create"
+ },
+ {
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "fetch_from": "document_type.custom",
+ "fieldname": "is_custom",
+ "fieldtype": "Check",
+ "label": "Is Custom",
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-01-19 01:25:34.773430",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "User Document Type",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/user_document_type/user_document_type.py b/frappe/core/doctype/user_document_type/user_document_type.py
new file mode 100644
index 0000000000..979bfcb250
--- /dev/null
+++ b/frappe/core/doctype/user_document_type/user_document_type.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class UserDocumentType(Document):
+ pass
diff --git a/frappe/core/doctype/user_select_document_type/__init__.py b/frappe/core/doctype/user_select_document_type/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/core/doctype/user_select_document_type/user_select_document_type.json b/frappe/core/doctype/user_select_document_type/user_select_document_type.json
new file mode 100644
index 0000000000..86e19422c3
--- /dev/null
+++ b/frappe/core/doctype/user_select_document_type/user_select_document_type.json
@@ -0,0 +1,33 @@
+{
+ "actions": [],
+ "creation": "2021-01-17 18:28:14.208576",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "document_type"
+ ],
+ "fields": [
+ {
+ "fieldname": "document_type",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Document Type",
+ "options": "DocType",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-01-17 18:45:44.993190",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "User Select Document Type",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/user_select_document_type/user_select_document_type.py b/frappe/core/doctype/user_select_document_type/user_select_document_type.py
new file mode 100644
index 0000000000..373eaf7aa3
--- /dev/null
+++ b/frappe/core/doctype/user_select_document_type/user_select_document_type.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class UserSelectDocumentType(Document):
+ pass
diff --git a/frappe/core/doctype/user_type/__init__.py b/frappe/core/doctype/user_type/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/core/doctype/user_type/test_user_type.py b/frappe/core/doctype/user_type/test_user_type.py
new file mode 100644
index 0000000000..de61e0f476
--- /dev/null
+++ b/frappe/core/doctype/user_type/test_user_type.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestUserType(unittest.TestCase):
+ pass
diff --git a/frappe/core/doctype/user_type/user_type.js b/frappe/core/doctype/user_type/user_type.js
new file mode 100644
index 0000000000..6ae95fb7df
--- /dev/null
+++ b/frappe/core/doctype/user_type/user_type.js
@@ -0,0 +1,70 @@
+// Copyright (c) 2021, Frappe Technologies and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('User Type', {
+ refresh: function(frm) {
+ frm.toggle_display('is_standard', frappe.boot.developer_mode ? true : false);
+ frm.set_df_property('is_standard', 'read_only', frappe.boot.developer_mode ? false : true);
+
+ const fields = ['role', 'apply_user_permission_on', 'user_id_field',
+ 'user_doctypes', 'select_doctypes', 'user_type_modules'];
+
+ frm.toggle_display(fields, frm.doc.is_standard ? false : true);
+
+ frm.set_query('document_type', 'user_doctypes', function() {
+ return {
+ filters: {
+ istable: 0
+ }
+ };
+ });
+
+ frm.set_query('document_type', 'select_doctypes', function() {
+ return {
+ filters: {
+ istable: 0,
+ is_submittable: 0
+ }
+ };
+ });
+
+ frm.set_query('role', function() {
+ return {
+ filters: {
+ is_custom: 1,
+ disabled: 0,
+ desk_access: 1
+ }
+ };
+ });
+
+ frm.set_query('apply_user_permission_on', function() {
+ return {
+ query: "frappe.core.doctype.user_type.user_type.get_user_linked_doctypes"
+ };
+ });
+ },
+
+ onload: function(frm) {
+ frm.trigger('get_user_id_fields');
+ },
+
+ apply_user_permission_on: function(frm) {
+ frm.set_value('user_id_field', '');
+ frm.trigger('get_user_id_fields');
+ },
+
+ get_user_id_fields: function(frm) {
+ if (frm.doc.apply_user_permission_on) {
+ frappe.call({
+ method: 'frappe.core.doctype.user_type.user_type.get_user_id',
+ args: {
+ parent: frm.doc.apply_user_permission_on
+ },
+ callback: function(r) {
+ set_field_options('user_id_field', [""].concat(r.message));
+ }
+ });
+ }
+ }
+});
diff --git a/frappe/core/doctype/user_type/user_type.json b/frappe/core/doctype/user_type/user_type.json
new file mode 100644
index 0000000000..6b4a13b2e6
--- /dev/null
+++ b/frappe/core/doctype/user_type/user_type.json
@@ -0,0 +1,128 @@
+{
+ "actions": [],
+ "autoname": "Prompt",
+ "creation": "2021-01-13 01:48:02.378548",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "is_standard",
+ "section_break_2",
+ "role",
+ "column_break_4",
+ "apply_user_permission_on",
+ "user_id_field",
+ "section_break_6",
+ "user_doctypes",
+ "select_doctypes",
+ "allowed_modules_section",
+ "user_type_modules"
+ ],
+ "fields": [
+ {
+ "default": "0",
+ "fieldname": "is_standard",
+ "fieldtype": "Check",
+ "label": "Is Standard"
+ },
+ {
+ "depends_on": "eval: !doc.is_standard",
+ "fieldname": "section_break_2",
+ "fieldtype": "Section Break",
+ "label": "Document Types and Permissions"
+ },
+ {
+ "fieldname": "user_doctypes",
+ "fieldtype": "Table",
+ "label": "Document Types",
+ "mandatory_depends_on": "eval: !doc.is_standard",
+ "options": "User Document Type"
+ },
+ {
+ "fieldname": "role",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Role",
+ "mandatory_depends_on": "eval: !doc.is_standard",
+ "options": "Role"
+ },
+ {
+ "fieldname": "select_doctypes",
+ "fieldtype": "Table",
+ "label": "Document Types (Select Permissions Only)",
+ "options": "User Select Document Type"
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "description": "Can only list down the document types which has been linked to the User document type.",
+ "fieldname": "apply_user_permission_on",
+ "fieldtype": "Link",
+ "label": "Apply User Permission On",
+ "mandatory_depends_on": "eval: !doc.is_standard",
+ "options": "DocType"
+ },
+ {
+ "depends_on": "eval: !doc.is_standard",
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break",
+ "hide_border": 1
+ },
+ {
+ "depends_on": "apply_user_permission_on",
+ "fieldname": "user_id_field",
+ "fieldtype": "Select",
+ "label": "User Id Field",
+ "mandatory_depends_on": "eval: !doc.is_standard"
+ },
+ {
+ "depends_on": "eval: !doc.is_standard",
+ "fieldname": "allowed_modules_section",
+ "fieldtype": "Section Break",
+ "label": "Allowed Modules"
+ },
+ {
+ "fieldname": "user_type_modules",
+ "fieldtype": "Table",
+ "no_copy": 1,
+ "options": "User Type Module",
+ "print_hide": 1,
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-01-24 04:47:08.243320",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "User Type",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Administrator",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/user_type/user_type.py b/frappe/core/doctype/user_type/user_type.py
new file mode 100644
index 0000000000..62a20cf937
--- /dev/null
+++ b/frappe/core/doctype/user_type/user_type.py
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from six import iteritems
+from frappe.utils import get_link_to_form
+from frappe.config import get_modules_from_app
+from frappe.permissions import add_permission, add_user_permission
+from frappe.model.document import Document
+
+class UserType(Document):
+ def validate(self):
+ self.set_modules()
+
+ def on_update(self):
+ if self.is_standard: return
+
+ self.validate_document_type_limit()
+ self.validate_role()
+ self.add_role_permissions_for_user_doctypes()
+ self.add_role_permissions_for_select_doctypes()
+ self.update_users()
+ get_non_standard_user_type_details()
+ self.remove_permission_for_deleted_doctypes()
+
+ def on_trash(self):
+ if self.is_standard:
+ frappe.throw(_('Standard user type {0} can not be deleted.')
+ .format(frappe.bold(self.name)))
+
+ def set_modules(self):
+ if not self.user_doctypes: return
+
+ modules = frappe.get_all('DocType', fields=['distinct module as module'],
+ filters={'name': ('in', [d.document_type for d in self.user_doctypes])})
+
+ self.set('user_type_modules', [])
+ for row in modules:
+ self.append('user_type_modules', {
+ 'module': row.module
+ })
+
+ def validate_document_type_limit(self):
+ limit = frappe.conf.get('user_type_doctype_limit').get(frappe.scrub(self.name)) or 10
+
+ if self.user_doctypes and len(self.user_doctypes) > limit:
+ frappe.throw(_('The total number of user document types limit has been crossed.'),
+ title=_('User Document Types Limit Exceeded'))
+
+ custom_doctypes = [row.document_type for row in self.user_doctypes if row.is_custom]
+ if custom_doctypes and len(custom_doctypes) > 3:
+ frappe.throw(_('You can only set the 3 custom doctypes in the Document Types table.'),
+ title=_('Custom Document Types Limit Exceeded'))
+
+ def validate_role(self):
+ if not self.role:
+ frappe.throw(_("The field {0} is mandatory")
+ .format(frappe.bold(_('Role'))))
+
+ if not frappe.db.get_value('Role', self.role, 'is_custom'):
+ frappe.throw(_("The role {0} should be a custom role.")
+ .format(frappe.bold(get_link_to_form('Role', self.role))))
+
+ def update_users(self):
+ for row in frappe.get_all('User', filters = {'user_type': self.name}):
+ user = frappe.get_cached_doc('User', row.name)
+ self.update_roles_in_user(user)
+ self.update_modules_in_user(user)
+ user.update_children()
+
+ def update_roles_in_user(self, user):
+ user.set('roles', [])
+ user.append('roles', {
+ 'role': self.role
+ })
+
+ def update_modules_in_user(self, user):
+ block_modules = frappe.get_all('Module Def', fields = ['name as module'],
+ filters={'name': ['not in', [d.module for d in self.user_type_modules]]})
+
+ if block_modules:
+ user.set('block_modules', block_modules)
+
+ def add_role_permissions_for_user_doctypes(self):
+ perms = ['read', 'write', 'create']
+ for row in self.user_doctypes:
+ docperm = add_role_permissions(row.document_type, self.role)
+
+ values = {perm:row.get(perm) for perm in perms}
+ for perm in ['print', 'email', 'share']:
+ values[perm] = 1
+
+ frappe.db.set_value('Custom DocPerm', docperm, values)
+
+ def add_role_permissions_for_select_doctypes(self):
+ for row in self.select_doctypes:
+ docperm = add_role_permissions(row.document_type, self.role)
+ frappe.db.set_value('Custom DocPerm', docperm,
+ {'select': 1, 'read': 0, 'create': 0, 'write': 0})
+
+ def remove_permission_for_deleted_doctypes(self):
+ doctypes = [d.document_type for d in self.user_doctypes]
+
+ for dt in self.select_doctypes:
+ doctypes.append(dt.document_type)
+
+ for perm in frappe.get_all('Custom DocPerm',
+ filters = {'role': self.role, 'parent': ['not in', doctypes]}):
+ frappe.delete_doc('Custom DocPerm', perm.name)
+
+def add_role_permissions(doctype, role):
+ name = frappe.get_value('Custom DocPerm', dict(parent=doctype,
+ role=role, permlevel=0))
+
+ if not name:
+ name = add_permission(doctype, role, 0)
+
+ return name
+
+def get_non_standard_user_type_details():
+ user_types = frappe.get_all('User Type',
+ fields=['apply_user_permission_on', 'name', 'user_id_field'],
+ filters={'is_standard': 0})
+
+ if user_types:
+ user_type_details = {d.name: [d.apply_user_permission_on, d.user_id_field] for d in user_types}
+
+ frappe.cache().set_value('non_standard_user_types', user_type_details)
+
+ return user_type_details
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_user_linked_doctypes(doctype, txt, searchfield, start, page_len, filters):
+ modules = [d.get('module_name') for d in get_modules_from_app('frappe')]
+
+ filters = [['DocField', 'options', '=', 'User'], ['DocType', 'is_submittable', '=', 0],
+ ['DocType', 'issingle', '=', 0], ['DocType', 'module', 'not in', modules],
+ ['DocType', 'read_only', '=', 0], ['DocType', 'name', 'like', '%{0}%'.format(txt)]]
+
+ doctypes = frappe.get_all('DocType', fields = ['`tabDocType`.`name`'], filters=filters,
+ order_by = '`tabDocType`.`idx` desc', limit_start=start, limit_page_length=page_len, as_list=1, debug=1)
+
+ custom_dt_filters = [['Custom Field', 'dt', 'like', '%{0}%'.format(txt)],
+ ['Custom Field', 'options', '=', 'User'], ['Custom Field', 'fieldtype', '=', 'Link']]
+
+ custom_doctypes = frappe.get_all('Custom Field', fields = ['dt as name'],
+ filters= custom_dt_filters, as_list=1)
+
+ return doctypes + custom_doctypes
+
+@frappe.whitelist()
+def get_user_id(parent):
+ data = frappe.get_all('DocField', fields = ['label', 'fieldname as value'],
+ filters= {'options': 'User', 'fieldtype': 'Link', 'parent': parent}) or []
+
+ data.extend(frappe.get_all('Custom Field', fields = ['label', 'fieldname as value'],
+ filters= {'options': 'User', 'fieldtype': 'Link', 'dt': parent}))
+
+ return data
+
+def user_linked_with_permission_on_doctype(doc, user):
+ if not doc.apply_user_permission_on: return True
+
+ if not doc.user_id_field:
+ frappe.throw(_('User Id Field is mandatory in the user type {0}')
+ .format(frappe.bold(doc.name)))
+
+ if frappe.db.get_value(doc.apply_user_permission_on,
+ {doc.user_id_field: user}, 'name'):
+ return True
+ else:
+ label = frappe.get_meta(doc.apply_user_permission_on).get_field(doc.user_id_field).label
+
+ frappe.msgprint(_("To set the role {0} in the user {1}, kindly set the {2} field as {3} in one of the {4} record.")
+ .format(frappe.bold(doc.role), frappe.bold(user), frappe.bold(label),
+ frappe.bold(user), frappe.bold(doc.apply_user_permission_on)))
+
+ return False
+
+def apply_permissions_for_non_standard_user_type(doc, method=None):
+ '''Create user permission for the non standard user type'''
+ user_types = frappe.cache().get_value('non_standard_user_types')
+
+ if not user_types:
+ user_types = get_non_standard_user_type_details()
+
+ for user_type, data in iteritems(user_types):
+ if doc.doctype != data[0]: continue
+ if frappe.get_cached_value('User', doc.get(data[1]), 'user_type') != user_type:
+ return
+
+ if (doc.get(data[1]) and (doc.get(data[1]) != doc._doc_before_save.get(data[1])
+ or not frappe.db.get_value('User Permission',
+ {'user': doc.get(data[1]), 'allow': data[0], 'for_value': doc.name}, 'name'))):
+
+ perm_data = frappe.db.get_value('User Permission',
+ {'allow': doc.doctype, 'for_value': doc.name}, ['name', 'user'])
+
+ if not perm_data:
+ user_doc = frappe.get_cached_doc('User', doc.get(data[1]))
+ user_doc.set_roles_and_modules_based_on_user_type()
+ 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]))
\ No newline at end of file
diff --git a/frappe/core/doctype/user_type/user_type_dashboard.py b/frappe/core/doctype/user_type/user_type_dashboard.py
new file mode 100644
index 0000000000..7e14198bca
--- /dev/null
+++ b/frappe/core/doctype/user_type/user_type_dashboard.py
@@ -0,0 +1,13 @@
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'user_type',
+ 'transactions': [
+ {
+ 'label': _('Reference'),
+ 'items': ['User']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/frappe/core/doctype/user_type/user_type_list.js b/frappe/core/doctype/user_type/user_type_list.js
new file mode 100644
index 0000000000..9a9ef417ac
--- /dev/null
+++ b/frappe/core/doctype/user_type/user_type_list.js
@@ -0,0 +1,10 @@
+frappe.listview_settings['User Type'] = {
+ add_fields: ["is_standard"],
+ get_indicator: function (doc) {
+ if (doc.is_standard) {
+ return [__("Standard"), "green", "is_standard,=,1"];
+ } else {
+ return [__("Custom"), "blue", "is_standard,=,0"];
+ }
+ }
+};
diff --git a/frappe/core/doctype/user_type_module/__init__.py b/frappe/core/doctype/user_type_module/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/core/doctype/user_type_module/user_type_module.json b/frappe/core/doctype/user_type_module/user_type_module.json
new file mode 100644
index 0000000000..0f9cbefc25
--- /dev/null
+++ b/frappe/core/doctype/user_type_module/user_type_module.json
@@ -0,0 +1,33 @@
+{
+ "actions": [],
+ "creation": "2021-01-24 03:05:24.634719",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "module"
+ ],
+ "fields": [
+ {
+ "fieldname": "module",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Module",
+ "options": "Module Def",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-01-24 03:07:43.602927",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "User Type Module",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/user_type_module/user_type_module.py b/frappe/core/doctype/user_type_module/user_type_module.py
new file mode 100644
index 0000000000..6cd2cbacdb
--- /dev/null
+++ b/frappe/core/doctype/user_type_module/user_type_module.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class UserTypeModule(Document):
+ pass
diff --git a/frappe/core/page/permission_manager/permission_manager.py b/frappe/core/page/permission_manager/permission_manager.py
index be8921e2ff..1c215eb6e1 100644
--- a/frappe/core/page/permission_manager/permission_manager.py
+++ b/frappe/core/page/permission_manager/permission_manager.py
@@ -30,8 +30,16 @@ def get_roles_and_doctypes():
"restrict_to_domain": ("in", active_domains)
}, fields=["name"])
+ restricted_roles = ['Administrator']
+ if frappe.session.user != 'Administrator':
+ custom_user_type_roles = frappe.get_all('User Type', filters = {'is_standard': 0}, fields=['role'])
+ for row in custom_user_type_roles:
+ restricted_roles.append(row.role)
+
+ restricted_roles.append('All')
+
roles = frappe.get_all("Role", filters={
- "name": ("not in", "Administrator"),
+ "name": ("not in", restricted_roles),
"disabled": 0,
}, or_filters={
"ifnull(restrict_to_domain, '')": "",
@@ -54,9 +62,14 @@ def get_permissions(doctype=None, role=None):
if doctype:
out = [p for p in out if p.parent == doctype]
else:
- out = frappe.get_all('Custom DocPerm', fields='*', filters=dict(parent = doctype), order_by="permlevel")
+ filters=dict(parent = doctype)
+ if frappe.session.user != 'Administrator':
+ custom_roles = frappe.get_all('Role', filters={'is_custom': 1})
+ filters['role'] = ['not in', [row.name for row in custom_roles]]
+
+ out = frappe.get_all('Custom DocPerm', fields='*', filters=filters, order_by="permlevel")
if not out:
- out = frappe.get_all('DocPerm', fields='*', filters=dict(parent = doctype), order_by="permlevel")
+ out = frappe.get_all('DocPerm', fields='*', filters=filters, order_by="permlevel")
linked_doctypes = {}
for d in out:
@@ -78,14 +91,14 @@ def add(parent, role, permlevel):
@frappe.whitelist()
def update(doctype, role, permlevel, ptype, value=None):
"""Update role permission params
-
+
Args:
doctype (str): Name of the DocType to update params for
role (str): Role to be updated for, eg "Website Manager".
permlevel (int): perm level the provided rule applies to
ptype (str): permission type, example "read", "delete", etc.
value (None, optional): value for ptype, None indicates False
-
+
Returns:
str: Refresh flag is permission is updated successfully
"""
diff --git a/frappe/hooks.py b/frappe/hooks.py
index c06930afd8..74c538c5df 100644
--- a/frappe/hooks.py
+++ b/frappe/hooks.py
@@ -147,6 +147,7 @@ doc_events = {
"frappe.core.doctype.file.file.attach_files_to_document",
"frappe.event_streaming.doctype.event_update_log.event_update_log.notify_consumers",
"frappe.automation.doctype.assignment_rule.assignment_rule.update_due_date",
+ "frappe.core.doctype.user_type.user_type.apply_permissions_for_non_standard_user_type"
],
"after_rename": "frappe.desk.notifications.clear_doctype_notifications",
"on_cancel": [
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 6e94bf0adc..a056df0915 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -333,3 +333,4 @@ frappe.patches.v13_0.delete_package_publish_tool
frappe.patches.v13_0.rename_list_view_setting_to_list_view_settings
frappe.patches.v13_0.remove_twilio_settings
frappe.patches.v12_0.rename_uploaded_files_with_proper_name
+frappe.patches.v13_0.make_user_type
diff --git a/frappe/patches/v13_0/make_user_type.py b/frappe/patches/v13_0/make_user_type.py
new file mode 100644
index 0000000000..dac21c7eec
--- /dev/null
+++ b/frappe/patches/v13_0/make_user_type.py
@@ -0,0 +1,10 @@
+import frappe
+from frappe.utils.install import create_user_type
+
+def execute():
+ frappe.reload_doc('core', 'doctype', 'role')
+ frappe.reload_doc('core', 'doctype', 'user_document_type')
+ frappe.reload_doc('core', 'doctype', 'user_select_document_type')
+ frappe.reload_doc('core', 'doctype', 'user_type')
+
+ create_user_type()
diff --git a/frappe/permissions.py b/frappe/permissions.py
index abb1f6653a..a718ed7169 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -471,7 +471,7 @@ def setup_custom_perms(parent):
copy_perms(parent)
return True
-def add_permission(doctype, role, permlevel=0):
+def add_permission(doctype, role, permlevel=0, ptype=None):
'''Add a new permission rule to the given doctype
for the given Role and Permission Level'''
from frappe.core.doctype.doctype.doctype import validate_permissions_for_doctype
@@ -481,6 +481,9 @@ def add_permission(doctype, role, permlevel=0):
permlevel=permlevel, if_owner=0)):
return
+ if not ptype:
+ ptype = 'read'
+
custom_docperm = frappe.get_doc({
"doctype":"Custom DocPerm",
"__islocal": 1,
@@ -488,13 +491,14 @@ def add_permission(doctype, role, permlevel=0):
"parenttype": "DocType",
"parentfield": "permissions",
"role": role,
- 'read': 1,
"permlevel": permlevel,
+ ptype: 1,
})
custom_docperm.save()
validate_permissions_for_doctype(doctype)
+ return custom_docperm.name
def copy_perms(parent):
'''Copy all DocPerm in to Custom DocPerm for the given document'''
diff --git a/frappe/utils/install.py b/frappe/utils/install.py
index 4ecae440cb..93f46a2a16 100644
--- a/frappe/utils/install.py
+++ b/frappe/utils/install.py
@@ -18,6 +18,7 @@ def after_install():
# reset installed apps for re-install
frappe.db.set_global("installed_apps", '["frappe"]')
+ create_user_type()
install_basic_docs()
from frappe.core.doctype.file.file import make_home_folder
@@ -49,6 +50,15 @@ def after_install():
frappe.db.commit()
+def create_user_type():
+ for user_type in ['System User', 'Website User']:
+ if not frappe.db.exists('User Type', user_type):
+ frappe.get_doc({
+ 'doctype': 'User Type',
+ 'name': user_type,
+ 'is_standard': 1
+ }).insert(ignore_permissions=True)
+
def install_basic_docs():
# core users / roles
install_docs = [