Merge pull request #7303 from Mangesh-Khairnar/default-user-permission

feat: Default user permission
This commit is contained in:
Suraj Shetty 2019-04-24 15:51:47 +05:30 committed by GitHub
commit 794f3d08b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 232 additions and 72 deletions

View file

@ -8,17 +8,26 @@ import frappe
import unittest
class TestUserPermission(unittest.TestCase):
def test_default_user_permission_validation(self):
user = create_user('test_default_permission@example.com')
param = get_params(user, 'User', user.name, is_default=1)
add_user_permissions(param)
#create a duplicate entry with default
perm_user = create_user('test_user_perm@example.com')
param = get_params(user, 'User', perm_user.name, is_default=1)
self.assertRaises(frappe.ValidationError, add_user_permissions, param)
def test_apply_to_all(self):
''' Create User permission for User having access to all applicable Doctypes'''
user = get_user()
param = get_params(user, apply = 1)
user = create_user('test_bulk_creation_update@example.com')
param = get_params(user, 'User', user.name)
created = add_user_permissions(param)
self.assertEquals(created, 1)
def test_for_applicable_on_update_from_apply_to_all(self):
''' Update User Permission from all to some applicable Doctypes'''
user = get_user()
param = get_params(user, applicable = ["Chat Room", "Chat Message"])
user = create_user('test_bulk_creation_update@example.com')
param = get_params(user, 'User', user.name , applicable = ["Chat Room", "Chat Message"])
create = add_user_permissions(param)
frappe.db.commit()
@ -33,8 +42,8 @@ class TestUserPermission(unittest.TestCase):
def test_for_apply_to_all_on_update_from_applicable(self):
''' Update User Permission from some to all applicable Doctypes'''
user = get_user()
param = get_params(user, apply = 1)
user = create_user('test_bulk_creation_update@example.com')
param = get_params(user, 'User', user.name)
created = add_user_permissions(param)
created_apply_to_all = frappe.db.exists("User Permission", get_exists_param(user))
removed_applicable_first = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Room"))
@ -46,26 +55,27 @@ class TestUserPermission(unittest.TestCase):
self.assertIsNone(removed_applicable_second)
self.assertEquals(created, 1)
def get_user():
if frappe.db.exists('User', 'test_bulk_creation_update@example.com'):
return frappe.get_doc('User', 'test_bulk_creation_update@example.com')
def create_user(email):
''' create user with role system manager '''
if frappe.db.exists('User', email):
return frappe.get_doc('User', email)
else:
user = frappe.new_doc('User')
user.email = 'test_bulk_creation_update@example.com'
user.first_name = 'Test_Bulk_Creation'
user.email = email
user.first_name = email.split("@")[0]
user.add_roles("System Manager")
return user
def get_params(user, apply = None , applicable = None):
def get_params(user, doctype, docname, is_default=0, applicable=None):
''' Return param to insert '''
param = {
"user": user.name,
"doctype":"User",
"docname":user.name
"doctype":doctype,
"docname":docname,
"is_default": is_default,
"apply_to_all_doctypes": 1,
"applicable_doctypes": []
}
if apply:
param.update({"apply_to_all_doctypes": 1})
param.update({"applicable_doctypes": []})
if applicable:
param.update({"apply_to_all_doctypes": 0})
param.update({"applicable_doctypes": applicable})

View file

@ -20,6 +20,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "user",
"fieldtype": "Link",
"hidden": 0,
@ -53,6 +54,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "allow",
"fieldtype": "Link",
"hidden": 0,
@ -86,6 +88,39 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "for_value",
"fieldtype": "Dynamic Link",
"hidden": 0,
@ -119,6 +154,40 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "is_default",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Default",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "advanced_control_section",
"fieldtype": "Section Break",
"hidden": 0,
@ -152,6 +221,7 @@
"collapsible": 0,
"columns": 0,
"default": "1",
"fetch_if_empty": 0,
"fieldname": "apply_to_all_doctypes",
"fieldtype": "Check",
"hidden": 0,
@ -185,6 +255,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.apply_to_all_doctypes",
"fetch_if_empty": 0,
"fieldname": "applicable_for",
"fieldtype": "Link",
"hidden": 0,
@ -213,16 +284,14 @@
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-02-13 22:58:27.428741",
"modified": "2019-04-16 19:17:23.644724",
"modified_by": "Administrator",
"module": "Core",
"name": "User Permission",
@ -251,7 +320,6 @@
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",

View file

@ -7,21 +7,14 @@ import frappe, json
from frappe.model.document import Document
from frappe.permissions import (get_valid_perms, update_permission_property)
from frappe import _
from frappe.utils import cstr
from frappe.core.utils import find
from frappe.desk.form.linked_with import get_linked_doctypes
class UserPermission(Document):
def validate(self):
duplicate_exists = frappe.db.get_all(self.doctype, filters={
'allow': self.allow,
'for_value': self.for_value,
'user': self.user,
'applicable_for': self.applicable_for,
'apply_to_all_doctypes': self.apply_to_all_doctypes,
'name': ['!=', self.name]
}, limit=1)
if duplicate_exists:
frappe.throw(_("User permission already exists"), frappe.DuplicateEntryError)
self.validate_user_permission()
self.validate_default_permission()
def on_update(self):
frappe.cache().delete_value('user_permissions')
@ -31,6 +24,37 @@ class UserPermission(Document):
frappe.cache().delete_value('user_permissions')
frappe.publish_realtime('update_user_permissions')
def validate_user_permission(self):
''' checks for duplicate user permission records'''
duplicate_exists = frappe.db.get_all(self.doctype, filters={
'allow': self.allow,
'for_value': self.for_value,
'user': self.user,
'applicable_for': cstr(self.applicable_for),
'apply_to_all_doctypes': self.apply_to_all_doctypes,
'name': ['!=', self.name]
}, limit=1)
if duplicate_exists:
frappe.throw(_("User permission already exists"), frappe.DuplicateEntryError)
def validate_default_permission(self):
''' validate user permission overlap for default value of a particular doctype '''
overlap_exists = []
if self.is_default:
overlap_exists = frappe.get_all(self.doctype, filters={
'allow': self.allow,
'user': self.user,
'is_default': 1,
'name': ['!=', self.name]
}, or_filters={
'applicable_for': cstr(self.applicable_for),
'apply_to_all_doctypes': 1
}, limit=1)
if overlap_exists:
ref_link = frappe.get_desk_link(self.doctype, overlap_exists[0].name)
frappe.throw(_("{0} has already assigned default vaue for {1}.".format(ref_link, self.allow)))
@frappe.whitelist()
def get_user_permissions(user=None):
'''Get all users permissions for the user as a dict of doctype'''
@ -52,7 +76,7 @@ def get_user_permissions(user=None):
out = {}
def add_doc_to_perm(perm, doc_name):
def add_doc_to_perm(perm, doc_name, is_default):
# group rules for each type
# for example if allow is "Customer", then build all allowed customers
# in a list
@ -61,21 +85,22 @@ def get_user_permissions(user=None):
out[perm.allow].append(frappe._dict({
'doc': doc_name,
'applicable_for': perm.get('applicable_for')
'applicable_for': perm.get('applicable_for'),
'is_default': is_default
}))
try:
for perm in frappe.get_all('User Permission',
fields=['allow', 'for_value', 'applicable_for'],
fields=['allow', 'for_value', 'applicable_for', 'is_default'],
filters=dict(user=user)):
meta = frappe.get_meta(perm.allow)
add_doc_to_perm(perm, perm.for_value)
add_doc_to_perm(perm, perm.for_value, perm.is_default)
if meta.is_nested_set():
decendants = frappe.db.get_descendants(perm.allow, perm.for_value)
for doc in decendants:
add_doc_to_perm(perm, doc)
add_doc_to_perm(perm, doc, False)
out = frappe._dict(out)
frappe.cache().hset("user_permissions", user, out)
@ -160,24 +185,25 @@ def add_user_permissions(data):
exists = frappe.db.exists("User Permission", {"user": data.user, "allow": data.doctype, "for_value": data.docname, "apply_to_all_doctypes": 1})
if data.apply_to_all_doctypes == 1 and not exists:
remove_applicable(d, data.user, data.doctype, data.docname)
insert_user_perm(data.user, data.doctype, data.docname, apply_to_all = 1)
insert_user_perm(data.user, data.doctype, data.docname, data.is_default, apply_to_all = 1)
return 1
else:
remove_apply_to_all(data.user, data.doctype, data.docname)
update_applicable(d, data.applicable_doctypes, data.user, data.doctype, data.docname)
for applicable in data.applicable_doctypes :
if applicable not in d:
insert_user_perm(data.user, data.doctype, data.docname, applicable = applicable)
insert_user_perm(data.user, data.doctype, data.docname, data.is_default, applicable = applicable)
elif exists:
insert_user_perm(data.user, data.doctype, data.docname, applicable = applicable)
insert_user_perm(data.user, data.doctype, data.docname, data.is_default, applicable = applicable)
return 1
return 0
def insert_user_perm(user, doctype, docname, apply_to_all=None, applicable=None):
def insert_user_perm(user, doctype, docname, is_default=0, apply_to_all=None, applicable=None):
user_perm = frappe.new_doc("User Permission")
user_perm.user = user
user_perm.allow = doctype
user_perm.for_value = docname
user_perm.is_default = is_default
if applicable:
user_perm.applicable_for = applicable
user_perm.apply_to_all_doctypes = 0

View file

@ -16,6 +16,7 @@ frappe.listview_settings['User Permission'] = {
dialog.fields_dict.doctype.set_input(undefined);
dialog.fields_dict.docname.set_input(undefined);
dialog.set_df_property("docname", "hidden", 1);
dialog.set_df_property("is_default", "hidden", 1);
dialog.set_df_property("apply_to_all_doctypes", "hidden", 1);
dialog.set_df_property("applicable_doctypes", "hidden", 1);
}
@ -53,11 +54,16 @@ frappe.listview_settings['User Permission'] = {
}
}
},
{
fieldname: 'is_default',
label: __('Is Default'),
fieldtype: 'Check',
hidden: 1
},
{
fieldname: 'apply_to_all_doctypes',
label: __('Apply to all Documents Types'),
fieldtype: 'Check',
checked: 1,
hidden: 1,
onchange: function() {
if(dialog.fields_dict.doctype.value && dialog.fields_dict.docname.value && dialog.fields_dict.user.value){
@ -205,8 +211,9 @@ frappe.listview_settings['User Permission'] = {
on_doctype_change: function(dialog) {
dialog.set_df_property("docname", "hidden", 0);
dialog.set_df_property("docname", "reqd", 1);
dialog.set_df_property("is_default", "hidden", 0);
dialog.set_df_property("apply_to_all_doctypes", "hidden", 0);
dialog.set_value("apply_to_all_doctypes","checked",1);
dialog.set_value("apply_to_all_doctypes", "checked", 1);
},
on_docname_change: function(dialog, options, applicable) {

View file

@ -12,7 +12,7 @@ import frappe.defaults
from frappe.model import data_fieldtypes
from frappe.utils import nowdate, nowtime, now_datetime
from frappe.core.doctype.user_permission.user_permission import get_user_permissions
from frappe.permissions import get_allowed_docs_for_doctype
from frappe.permissions import filter_allowed_docs_for_doctype
def get_new_doc(doctype, parent_doc = None, parentfield = None, as_dict=False):
if doctype not in frappe.local.new_doc_templates:
@ -56,12 +56,12 @@ def set_user_and_static_default_values(doc):
if df.fieldtype in data_fieldtypes:
# user permissions for link options
doctype_user_permissions = user_permissions.get(df.options, [])
# Allowed records for the reference doctype (link field)
allowed_records = get_allowed_docs_for_doctype(doctype_user_permissions, df.parent)
# Allowed records for the reference doctype (link field) along with default doc
allowed_records, default_doc = filter_allowed_docs_for_doctype(doctype_user_permissions, df.parent, with_default_doc=True)
user_default_value = get_user_default_value(df, defaults, doctype_user_permissions, allowed_records)
user_default_value = get_user_default_value(df, defaults, doctype_user_permissions, allowed_records, default_doc)
if user_default_value != None:
# do not set default if the field on which current field is dependent is not set
# do not set default if the field on which current field is dependent is not set
if is_dependent_field_set(df.depends_on, doc):
doc.set(df.fieldname, user_default_value)
else:
@ -78,14 +78,14 @@ def is_dependent_field_set(fieldname, doc):
if fieldname not in value_dict: return True
return value_dict[fieldname]
def get_user_default_value(df, defaults, doctype_user_permissions, allowed_records):
def get_user_default_value(df, defaults, doctype_user_permissions, allowed_records, default_doc):
# don't set defaults for "User" link field using User Permissions!
if df.fieldtype == "Link" and df.options != "User":
# 1 - look in user permissions only for document_type==Setup
# We don't want to include permissions of transactions to be used for defaults.
if (frappe.get_meta(df.options).document_type=="Setup"
and len(allowed_records)==1 and not df.ignore_user_permissions):
return allowed_records[0]
and not df.ignore_user_permissions and default_doc):
return default_doc
# 2 - Look in user defaults
user_default = defaults.get(df.fieldname)

View file

@ -395,7 +395,7 @@ def set_user_permission_if_allowed(doctype, name, user, with_message=False):
if get_role_permissions(frappe.get_meta(doctype), user).set_user_permissions!=1:
add_user_permission(doctype, name, user)
def add_user_permission(doctype, name, user, ignore_permissions=False, applicable_for=None):
def add_user_permission(doctype, name, user, ignore_permissions=False, applicable_for=None, is_default=0):
'''Add user permission'''
from frappe.core.doctype.user_permission.user_permission import user_permission_exists
@ -408,6 +408,7 @@ def add_user_permission(doctype, name, user, ignore_permissions=False, applicabl
user=user,
allow=doctype,
for_value=name,
is_default=is_default,
applicable_for=applicable_for,
)).insert(ignore_permissions=ignore_permissions)
@ -523,9 +524,22 @@ def allow_everything():
return perm
def get_allowed_docs_for_doctype(user_permissions, doctype):
'''Returns all the docs from the passed user_permission
that are allowed under provide doctype'''
return [d.get('doc') for d in user_permissions if not d.get('applicable_for') or d.get('applicable_for') == doctype]
''' Returns all the docs from the passed user_permissions that are
allowed under provided doctype '''
return filter_allowed_docs_for_doctype(user_permissions, doctype, with_default_doc=False)
def filter_allowed_docs_for_doctype(user_permissions, doctype, with_default_doc=True):
''' Returns all the docs from the passed user_permissions that are
allowed under provided doctype along with default doc value if with_default_doc is set '''
allowed_doc = []
default_doc = None
for doc in user_permissions:
if not doc.get('applicable_for') or doc.get('applicable_for') == doctype:
allowed_doc.append(doc.get('doc'))
if doc.get('is_default') or len(user_permissions) == 1 and with_default_doc:
default_doc = doc.get('doc')
return (allowed_doc, default_doc) if with_default_doc else allowed_doc
def push_perm_check_log(log):
if frappe.flags.get('has_permission_check_logs') == None: return

View file

@ -125,8 +125,9 @@ $.extend(frappe.model, {
var user_default = "";
var user_permissions = frappe.defaults.get_user_permissions();
let allowed_records = [];
let default_doc = null;
if(user_permissions) {
allowed_records = frappe.perm.get_allowed_docs_for_doctype(user_permissions[df.options], doc.doctype);
({allowed_records, default_doc} = frappe.perm.filter_allowed_docs_for_doctype(user_permissions[df.options], doc.doctype));
}
var meta = frappe.get_meta(doc.doctype);
var has_user_permissions = (df.fieldtype==="Link"
@ -139,8 +140,8 @@ $.extend(frappe.model, {
// 1 - look in user permissions for document_type=="Setup".
// We don't want to include permissions of transactions to be used for defaults.
if (df.linked_document_type==="Setup"
&& has_user_permissions && allowed_records.length===1) {
return allowed_records[0];
&& has_user_permissions && default_doc) {
return default_doc;
}
if(!df.ignore_user_permissions) {

View file

@ -254,8 +254,30 @@ $.extend(frappe.perm, {
},
get_allowed_docs_for_doctype: (user_permissions, doctype) => {
return (user_permissions || []).filter(perm => {
// returns docs from the list of user permissions that are allowed under provided doctype
return frappe.perm.filter_allowed_docs_for_doctype(user_permissions, doctype, false);
},
filter_allowed_docs_for_doctype: (user_permissions, doctype, with_default_doc=true) => {
// returns docs from the list of user permissions that are allowed under provided doctype
// also returns default doc when with_default_doc is set
const filtered_perms = (user_permissions || []).filter(perm => {
return (perm.applicable_for === doctype || !perm.applicable_for);
}).map(perm => perm.doc);
});
const allowed_docs = (filtered_perms).map(perm => perm.doc);
if (with_default_doc) {
const default_doc = allowed_docs.length === 1 ? allowed_docs : filtered_perms
.filter(perm => perm.is_default)
.map(record => record.doc);
return {
allowed_records: allowed_docs,
default_doc: default_doc[0]
};
} else {
return allowed_docs;
}
}
});
});

View file

@ -98,6 +98,12 @@ class TestPermissions(unittest.TestCase):
doc = frappe.new_doc("Blog Post")
self.assertFalse(doc.get("blog_category"))
# Fetch user permission set as default from multiple user permission
add_user_permission("Blog Category", "_Test Blog Category 2", "test2@example.com", ignore_permissions=True, is_default=1)
frappe.clear_cache()
doc = frappe.new_doc("Blog Post")
self.assertEqual(doc.get("blog_category"), "_Test Blog Category 2")
def test_user_link_match_doc(self):
blogger = frappe.get_doc("Blogger", "_Test Blogger 1")
blogger.user = "test2@example.com"

View file

@ -1,14 +1,20 @@
[
{
"category_name": "_Test Blog Category",
"doctype": "Blog Category",
"parent_website_route": "blog",
"title": "_Test Blog Category"
},
{
"category_name": "_Test Blog Category 1",
"doctype": "Blog Category",
"parent_website_route": "blog",
"title": "_Test Blog Category 1"
}
{
"category_name": "_Test Blog Category",
"doctype": "Blog Category",
"parent_website_route": "blog",
"title": "_Test Blog Category"
},
{
"category_name": "_Test Blog Category 1",
"doctype": "Blog Category",
"parent_website_route": "blog",
"title": "_Test Blog Category 1"
},
{
"category_name": "_Test Blog Category 2",
"doctype": "Blog Category",
"parent_website_route": "blog",
"title": "_Test Blog Category 2"
}
]