@@ -104,11 +104,11 @@ install: | |||
- cd ./frappe-bench | |||
- sed -i 's/watch:/# watch:/g' Procfile | |||
- sed -i 's/schedule:/# schedule:/g' Procfile | |||
- sed -i 's/^watch:/# watch:/g' Procfile | |||
- sed -i 's/^schedule:/# schedule:/g' Procfile | |||
- if [ $TYPE == "server" ]; then sed -i 's/socketio:/# socketio:/g' Procfile; fi | |||
- if [ $TYPE == "server" ]; then sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile; fi | |||
- if [ $TYPE == "server" ]; then sed -i 's/^socketio:/# socketio:/g' Procfile; fi | |||
- if [ $TYPE == "server" ]; then sed -i 's/^redis_socketio:/# redis_socketio:/g' Procfile; fi | |||
- if [ $TYPE == "ui" ]; then bench setup requirements --node; fi | |||
@@ -0,0 +1,19 @@ | |||
// Copyright (c) 2020, Frappe Technologies and contributors | |||
// For license information, please see license.txt | |||
frappe.ui.form.on('Module Profile', { | |||
refresh: function(frm) { | |||
if (has_common(frappe.user_roles, ["Administrator", "System Manager"])) { | |||
if (!frm.module_editor && frm.doc.__onload && frm.doc.__onload.all_modules) { | |||
let module_area = $('<div style="min-height: 300px">') | |||
.appendTo(frm.fields_dict.module_html.wrapper); | |||
frm.module_editor = new frappe.ModuleEditor(frm, module_area); | |||
} | |||
} | |||
if (frm.module_editor) { | |||
frm.module_editor.refresh(); | |||
} | |||
} | |||
}); |
@@ -0,0 +1,60 @@ | |||
{ | |||
"actions": [], | |||
"autoname": "field:module_profile_name", | |||
"creation": "2020-12-22 22:00:30.614475", | |||
"doctype": "DocType", | |||
"editable_grid": 1, | |||
"engine": "InnoDB", | |||
"field_order": [ | |||
"module_profile_name", | |||
"module_html", | |||
"block_modules" | |||
], | |||
"fields": [ | |||
{ | |||
"fieldname": "module_profile_name", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Module Profile Name", | |||
"reqd": 1, | |||
"unique": 1 | |||
}, | |||
{ | |||
"fieldname": "module_html", | |||
"fieldtype": "HTML", | |||
"label": "Module HTML" | |||
}, | |||
{ | |||
"fieldname": "block_modules", | |||
"fieldtype": "Table", | |||
"hidden": 1, | |||
"label": "Block Modules", | |||
"options": "Block Module", | |||
"read_only": 1 | |||
} | |||
], | |||
"index_web_pages_for_search": 1, | |||
"links": [], | |||
"modified": "2021-01-03 15:36:52.622696", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Module Profile", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"create": 1, | |||
"delete": 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 | |||
} |
@@ -0,0 +1,12 @@ | |||
# -*- coding: utf-8 -*- | |||
# Copyright (c) 2020, Frappe Technologies and contributors | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
from frappe.model.document import Document | |||
class ModuleProfile(Document): | |||
def onload(self): | |||
from frappe.config import get_modules_from_all_apps | |||
self.set_onload('all_modules', | |||
[m.get("module_name") for m in get_modules_from_all_apps()]) |
@@ -0,0 +1,32 @@ | |||
# -*- coding: utf-8 -*- | |||
# Copyright (c) 2020, Frappe Technologies and Contributors | |||
# See license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
import unittest | |||
class TestModuleProfile(unittest.TestCase): | |||
def test_make_new_module_profile(self): | |||
if not frappe.db.get_value('Module Profile', '_Test Module Profile'): | |||
frappe.get_doc({ | |||
'doctype': 'Module Profile', | |||
'module_profile_name': '_Test Module Profile', | |||
'block_modules': [ | |||
{'module': 'Accounts'} | |||
] | |||
}).insert() | |||
# add to user and check | |||
if not frappe.db.get_value('User', 'test-for-module_profile@example.com'): | |||
new_user = frappe.get_doc({ | |||
'doctype': 'User', | |||
'email':'test-for-module_profile@example.com', | |||
'first_name':'Test User' | |||
}).insert() | |||
else: | |||
new_user = frappe.get_doc('User', 'test-for-module_profile@example.com') | |||
new_user.module_profile = '_Test Module Profile' | |||
new_user.save() | |||
self.assertEqual(new_user.block_modules[0].module, 'Accounts') |
@@ -37,6 +37,25 @@ frappe.ui.form.on('User', { | |||
} | |||
}, | |||
module_profile: function(frm) { | |||
if (frm.doc.module_profile) { | |||
frappe.call({ | |||
"method": "frappe.core.doctype.user.user.get_module_profile", | |||
args: { | |||
module_profile: frm.doc.module_profile | |||
}, | |||
callback: function(data) { | |||
frm.set_value("block_modules", []); | |||
$.each(data.message || [], function(i, v) { | |||
let d = frm.add_child("block_modules"); | |||
d.module = v.module; | |||
}); | |||
frm.module_editor && frm.module_editor.refresh(); | |||
} | |||
}); | |||
} | |||
}, | |||
onload: function(frm) { | |||
frm.can_edit_roles = has_access_to_edit_user(); | |||
@@ -255,43 +274,3 @@ function get_roles_for_editing_user() { | |||
.filter(perm => perm.permlevel >= 1 && perm.write) | |||
.map(perm => perm.role) || ['System Manager']; | |||
} | |||
frappe.ModuleEditor = Class.extend({ | |||
init: function(frm, wrapper) { | |||
this.wrapper = $('<div class="row module-block-list"></div>').appendTo(wrapper); | |||
this.frm = frm; | |||
this.make(); | |||
}, | |||
make: function() { | |||
var me = this; | |||
this.frm.doc.__onload.all_modules.forEach(function(m) { | |||
$(repl('<div class="col-sm-4"><div class="checkbox">\ | |||
<label><input type="checkbox" class="block-module-check" data-module="%(module)s">\ | |||
%(module)s</label></div></div>', {module: m})).appendTo(me.wrapper); | |||
}); | |||
this.bind(); | |||
}, | |||
refresh: function() { | |||
var me = this; | |||
this.wrapper.find(".block-module-check").prop("checked", true); | |||
$.each(this.frm.doc.block_modules, function(i, d) { | |||
me.wrapper.find(".block-module-check[data-module='"+ d.module +"']").prop("checked", false); | |||
}); | |||
}, | |||
bind: function() { | |||
var me = this; | |||
this.wrapper.on("change", ".block-module-check", function() { | |||
var module = $(this).attr('data-module'); | |||
if($(this).prop("checked")) { | |||
// remove from block_modules | |||
me.frm.doc.block_modules = $.map(me.frm.doc.block_modules || [], function(d) { | |||
if (d.module != module) { | |||
return d; | |||
} | |||
}); | |||
} else { | |||
me.frm.add_child("block_modules", {"module": module}); | |||
} | |||
}); | |||
} | |||
}); |
@@ -55,6 +55,7 @@ | |||
"allowed_in_mentions", | |||
"user_emails", | |||
"sb_allow_modules", | |||
"module_profile", | |||
"modules_html", | |||
"block_modules", | |||
"home_settings", | |||
@@ -594,6 +595,12 @@ | |||
"fieldtype": "Select", | |||
"label": "Desk Theme", | |||
"options": "Light\nDark" | |||
}, | |||
{ | |||
"fieldname": "module_profile", | |||
"fieldtype": "Link", | |||
"label": "Module Profile", | |||
"options": "Module Profile" | |||
} | |||
], | |||
"icon": "fa fa-user", | |||
@@ -75,6 +75,7 @@ class User(Document): | |||
self.validate_user_email_inbox() | |||
ask_pass_update() | |||
self.validate_roles() | |||
self.validate_allowed_modules() | |||
self.validate_user_image() | |||
if self.language == "Loading...": | |||
@@ -89,6 +90,15 @@ class User(Document): | |||
self.set('roles', []) | |||
self.append_roles(*[role.role for role in role_profile.roles]) | |||
def validate_allowed_modules(self): | |||
if self.module_profile: | |||
module_profile = frappe.get_doc('Module Profile', self.module_profile) | |||
self.set('block_modules', []) | |||
for d in module_profile.get('block_modules'): | |||
self.append('block_modules', { | |||
'module': d.module | |||
}) | |||
def validate_user_image(self): | |||
if self.user_image and len(self.user_image) > 2000: | |||
frappe.throw(_("Not a valid User Image.")) | |||
@@ -1044,6 +1054,11 @@ def get_role_profile(role_profile): | |||
roles = frappe.get_doc('Role Profile', {'role_profile': role_profile}) | |||
return roles.roles | |||
@frappe.whitelist() | |||
def get_module_profile(module_profile): | |||
module_profile = frappe.get_doc('Module Profile', {'module_profile_name': module_profile}) | |||
return module_profile.get('block_modules') | |||
def update_roles(role_profile): | |||
users = frappe.get_all('User', filters={'role_profile_name': role_profile}) | |||
role_profile = frappe.get_doc('Role Profile', role_profile) | |||
@@ -3,6 +3,7 @@ | |||
# See license.txt | |||
from __future__ import unicode_literals | |||
from frappe.core.doctype.user_permission.user_permission import add_user_permissions | |||
from frappe.permissions import has_user_permission | |||
import frappe | |||
import unittest | |||
@@ -10,7 +11,12 @@ import unittest | |||
class TestUserPermission(unittest.TestCase): | |||
def setUp(self): | |||
frappe.db.sql("""DELETE FROM `tabUser Permission` | |||
WHERE `user` in ('test_bulk_creation_update@example.com', 'test_user_perm1@example.com')""") | |||
WHERE `user` in ( | |||
'test_bulk_creation_update@example.com', | |||
'test_user_perm1@example.com', | |||
'nested_doc_user@example.com')""") | |||
frappe.delete_doc_if_exists("DocType", "Person") | |||
frappe.db.sql_ddl("DROP TABLE IF EXISTS `tabPerson`") | |||
def test_default_user_permission_validation(self): | |||
user = create_user('test_default_permission@example.com') | |||
@@ -108,6 +114,45 @@ class TestUserPermission(unittest.TestCase): | |||
self.assertIsNone(removed_applicable_second) | |||
self.assertEquals(is_created, 1) | |||
def test_user_perm_for_nested_doctype(self): | |||
"""Test if descendants' visibility is controlled for a nested DocType.""" | |||
from frappe.core.doctype.doctype.test_doctype import new_doctype | |||
user = create_user("nested_doc_user@example.com", "Blogger") | |||
if not frappe.db.exists("DocType", "Person"): | |||
doc = new_doctype("Person", | |||
fields=[ | |||
{ | |||
"label": "Person Name", | |||
"fieldname": "person_name", | |||
"fieldtype": "Data" | |||
} | |||
], unique=0) | |||
doc.is_tree = 1 | |||
doc.insert() | |||
parent_record = frappe.get_doc( | |||
{"doctype": "Person", "person_name": "Parent", "is_group": 1} | |||
).insert() | |||
child_record = frappe.get_doc( | |||
{"doctype": "Person", "person_name": "Child", "is_group": 0, "parent_person": parent_record.name} | |||
).insert() | |||
add_user_permissions(get_params(user, "Person", parent_record.name)) | |||
# check if adding perm on a group record, makes child record visible | |||
self.assertTrue(has_user_permission(frappe.get_doc("Person", parent_record.name), user.name)) | |||
self.assertTrue(has_user_permission(frappe.get_doc("Person", child_record.name), user.name)) | |||
frappe.db.set_value("User Permission", {"allow": "Person", "for_value": parent_record.name}, "hide_descendants", 1) | |||
frappe.cache().delete_value("user_permissions") | |||
# check if adding perm on a group record with hide_descendants enabled, | |||
# hides child records | |||
self.assertTrue(has_user_permission(frappe.get_doc("Person", parent_record.name), user.name)) | |||
self.assertFalse(has_user_permission(frappe.get_doc("Person", child_record.name), user.name)) | |||
def create_user(email, role="System Manager"): | |||
''' create user with role system manager ''' | |||
if frappe.db.exists('User', email): | |||
@@ -119,7 +164,7 @@ def create_user(email, role="System Manager"): | |||
user.add_roles(role) | |||
return user | |||
def get_params(user, doctype, docname, is_default=0, applicable=None): | |||
def get_params(user, doctype, docname, is_default=0, hide_descendants=0, applicable=None): | |||
''' Return param to insert ''' | |||
param = { | |||
"user": user.name, | |||
@@ -127,7 +172,8 @@ def get_params(user, doctype, docname, is_default=0, applicable=None): | |||
"docname":docname, | |||
"is_default": is_default, | |||
"apply_to_all_doctypes": 1, | |||
"applicable_doctypes": [] | |||
"applicable_doctypes": [], | |||
"hide_descendants": hide_descendants | |||
} | |||
if applicable: | |||
param.update({"apply_to_all_doctypes": 0}) | |||
@@ -26,11 +26,15 @@ frappe.ui.form.on('User Permission', { | |||
() => frappe.set_route('query-report', 'Permitted Documents For User', | |||
{ user: frm.doc.user })); | |||
frm.trigger('set_applicable_for_constraint'); | |||
frm.trigger('toggle_hide_descendants'); | |||
}, | |||
allow: frm => { | |||
if(frm.doc.for_value) { | |||
frm.set_value('for_value', null); | |||
if (frm.doc.allow) { | |||
if (frm.doc.for_value) { | |||
frm.set_value('for_value', null); | |||
} | |||
frm.trigger('toggle_hide_descendants'); | |||
} | |||
}, | |||
@@ -43,6 +47,11 @@ frappe.ui.form.on('User Permission', { | |||
if (frm.doc.apply_to_all_doctypes) { | |||
frm.set_value('applicable_for', null); | |||
} | |||
}, | |||
toggle_hide_descendants: frm => { | |||
let show = frappe.boot.nested_set_doctypes.includes(frm.doc.allow); | |||
frm.toggle_display('hide_descendants', show); | |||
} | |||
@@ -1,330 +1,116 @@ | |||
{ | |||
"allow_copy": 0, | |||
"allow_events_in_timeline": 0, | |||
"allow_guest_to_view": 0, | |||
"actions": [], | |||
"allow_import": 1, | |||
"allow_rename": 0, | |||
"beta": 0, | |||
"creation": "2017-07-17 14:25:27.881871", | |||
"custom": 0, | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "", | |||
"editable_grid": 1, | |||
"engine": "InnoDB", | |||
"field_order": [ | |||
"user", | |||
"allow", | |||
"column_break_3", | |||
"for_value", | |||
"is_default", | |||
"advanced_control_section", | |||
"apply_to_all_doctypes", | |||
"applicable_for", | |||
"column_break_9", | |||
"hide_descendants" | |||
], | |||
"fields": [ | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_in_quick_entry": 0, | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fetch_if_empty": 0, | |||
"fieldname": "user", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 1, | |||
"in_standard_filter": 1, | |||
"label": "User", | |||
"length": 0, | |||
"no_copy": 0, | |||
"options": "User", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 1, | |||
"set_only_once": 0, | |||
"translatable": 0, | |||
"unique": 0 | |||
"search_index": 1 | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_in_quick_entry": 0, | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fetch_if_empty": 0, | |||
"fieldname": "allow", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 1, | |||
"in_standard_filter": 1, | |||
"label": "Allow", | |||
"length": 0, | |||
"no_copy": 0, | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"translatable": 0, | |||
"unique": 0 | |||
"reqd": 1 | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_in_quick_entry": 0, | |||
"allow_on_submit": 0, | |||
"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 | |||
"fieldtype": "Column Break" | |||
}, | |||
{ | |||
"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, | |||
"ignore_user_permissions": 1, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 1, | |||
"in_standard_filter": 1, | |||
"label": "For Value", | |||
"length": 0, | |||
"no_copy": 0, | |||
"options": "allow", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"translatable": 0, | |||
"unique": 0 | |||
"reqd": 1 | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_in_quick_entry": 0, | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fetch_if_empty": 0, | |||
"default": "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 | |||
"label": "Is Default" | |||
}, | |||
{ | |||
"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, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Advanced Control", | |||
"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 | |||
"label": "Advanced Control" | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_in_quick_entry": 0, | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"default": "1", | |||
"fetch_if_empty": 0, | |||
"fieldname": "apply_to_all_doctypes", | |||
"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": "Apply To All Document Types", | |||
"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 | |||
"label": "Apply To All Document Types" | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_in_quick_entry": 0, | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"depends_on": "eval:!doc.apply_to_all_doctypes", | |||
"fetch_if_empty": 0, | |||
"fieldname": "applicable_for", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 1, | |||
"in_standard_filter": 0, | |||
"label": "Applicable For", | |||
"length": 0, | |||
"no_copy": 0, | |||
"options": "DocType", | |||
"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 | |||
"options": "DocType" | |||
}, | |||
{ | |||
"fieldname": "column_break_9", | |||
"fieldtype": "Column Break" | |||
}, | |||
{ | |||
"default": "0", | |||
"description": "Hide descendant records of <b>For Value</b>.", | |||
"fieldname": "hide_descendants", | |||
"fieldtype": "Check", | |||
"hidden": 1, | |||
"label": "Hide Descendants" | |||
} | |||
], | |||
"has_web_view": 0, | |||
"hide_toolbar": 0, | |||
"idx": 0, | |||
"in_create": 0, | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"max_attachments": 0, | |||
"modified": "2019-04-16 19:17:23.644724", | |||
"links": [], | |||
"modified": "2021-01-21 18:14:10.839381", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "User Permission", | |||
"name_case": "", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"amend": 0, | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"export": 1, | |||
"if_owner": 0, | |||
"import": 0, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"set_user_permissions": 0, | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
} | |||
], | |||
"quick_entry": 0, | |||
"read_only": 0, | |||
"show_name_in_global_search": 0, | |||
"sort_field": "modified", | |||
"sort_order": "DESC", | |||
"title_field": "user", | |||
"track_changes": 1, | |||
"track_seen": 0, | |||
"track_views": 0 | |||
"track_changes": 1 | |||
} |
@@ -49,7 +49,8 @@ class UserPermission(Document): | |||
'name': ['!=', self.name] | |||
}, or_filters={ | |||
'applicable_for': cstr(self.applicable_for), | |||
'apply_to_all_doctypes': 1 | |||
'apply_to_all_doctypes': 1, | |||
'hide_descendants': cstr(self.hide_descendants) | |||
}, limit=1) | |||
if overlap_exists: | |||
ref_link = frappe.get_desk_link(self.doctype, overlap_exists[0].name) | |||
@@ -91,13 +92,13 @@ def get_user_permissions(user=None): | |||
try: | |||
for perm in frappe.get_all('User Permission', | |||
fields=['allow', 'for_value', 'applicable_for', 'is_default'], | |||
fields=['allow', 'for_value', 'applicable_for', 'is_default', 'hide_descendants'], | |||
filters=dict(user=user)): | |||
meta = frappe.get_meta(perm.allow) | |||
add_doc_to_perm(perm, perm.for_value, perm.is_default) | |||
if meta.is_nested_set(): | |||
if meta.is_nested_set() and not perm.hide_descendants: | |||
decendants = frappe.db.get_descendants(perm.allow, perm.for_value) | |||
for doc in decendants: | |||
add_doc_to_perm(perm, doc, False) | |||
@@ -172,8 +173,8 @@ def check_applicable_doc_perm(user, doctype, docname): | |||
"allow": doctype, | |||
"for_value":docname, | |||
}) | |||
for d in data: | |||
applicable.append(d.applicable_for) | |||
for permission in data: | |||
applicable.append(permission.applicable_for) | |||
return applicable | |||
@@ -194,7 +195,8 @@ def add_user_permissions(data): | |||
data = json.loads(data) | |||
data = frappe._dict(data) | |||
d = check_applicable_doc_perm(data.user, data.doctype, data.docname) | |||
# get all doctypes on whom this permission is applied | |||
perm_applied_docs = check_applicable_doc_perm(data.user, data.doctype, data.docname) | |||
exists = frappe.db.exists("User Permission", { | |||
"user": data.user, | |||
"allow": data.doctype, | |||
@@ -202,26 +204,27 @@ def add_user_permissions(data): | |||
"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, data.is_default, apply_to_all = 1) | |||
remove_applicable(perm_applied_docs, data.user, data.doctype, data.docname) | |||
insert_user_perm(data.user, data.doctype, data.docname, data.is_default, data.hide_descendants, apply_to_all=1) | |||
return 1 | |||
elif len(data.applicable_doctypes) > 0 and data.apply_to_all_doctypes != 1: | |||
remove_apply_to_all(data.user, data.doctype, data.docname) | |||
update_applicable(d, data.applicable_doctypes, data.user, data.doctype, data.docname) | |||
update_applicable(perm_applied_docs, 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, data.is_default, applicable = applicable) | |||
if applicable not in perm_applied_docs: | |||
insert_user_perm(data.user, data.doctype, data.docname, data.is_default, data.hide_descendants, applicable=applicable) | |||
elif exists: | |||
insert_user_perm(data.user, data.doctype, data.docname, data.is_default, applicable = applicable) | |||
insert_user_perm(data.user, data.doctype, data.docname, data.is_default, data.hide_descendants, applicable=applicable) | |||
return 1 | |||
return 0 | |||
def insert_user_perm(user, doctype, docname, is_default=0, apply_to_all=None, applicable=None): | |||
def insert_user_perm(user, doctype, docname, is_default=0, hide_descendants=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 | |||
user_perm.hide_descendants = hide_descendants | |||
if applicable: | |||
user_perm.applicable_for = applicable | |||
user_perm.apply_to_all_doctypes = 0 | |||
@@ -229,8 +232,8 @@ def insert_user_perm(user, doctype, docname, is_default=0, apply_to_all=None, ap | |||
user_perm.apply_to_all_doctypes = 1 | |||
user_perm.insert() | |||
def remove_applicable(d, user, doctype, docname): | |||
for applicable_for in d: | |||
def remove_applicable(perm_applied_docs, user, doctype, docname): | |||
for applicable_for in perm_applied_docs: | |||
frappe.db.sql("""DELETE FROM `tabUser Permission` | |||
WHERE `user`=%s | |||
AND `applicable_for`=%s | |||
@@ -19,6 +19,7 @@ frappe.listview_settings['User Permission'] = { | |||
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); | |||
dialog.set_df_property("hide_descendants", "hidden", 1); | |||
} | |||
}, | |||
{ | |||
@@ -54,6 +55,10 @@ frappe.listview_settings['User Permission'] = { | |||
} | |||
} | |||
}, | |||
{ | |||
fieldtype: "Section Break", | |||
hide_border: 1 | |||
}, | |||
{ | |||
fieldname: 'is_default', | |||
label: __('Is Default'), | |||
@@ -74,6 +79,19 @@ frappe.listview_settings['User Permission'] = { | |||
} | |||
} | |||
}, | |||
{ | |||
fieldtype: "Column Break" | |||
}, | |||
{ | |||
fieldname: 'hide_descendants', | |||
label: __('Hide Descendants'), | |||
fieldtype: 'Check', | |||
hidden: 1 | |||
}, | |||
{ | |||
fieldtype: "Section Break", | |||
hide_border: 1 | |||
}, | |||
{ | |||
label: __("Applicable Document Types"), | |||
fieldname: "applicable_doctypes", | |||
@@ -214,6 +232,9 @@ frappe.listview_settings['User Permission'] = { | |||
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); | |||
let show = frappe.boot.nested_set_doctypes.includes(dialog.get_value("doctype")); | |||
dialog.set_df_property("hide_descendants", "hidden", !show); | |||
dialog.refresh(); | |||
}, | |||
on_docname_change: function(dialog, options, applicable) { | |||
@@ -233,6 +254,7 @@ frappe.listview_settings['User Permission'] = { | |||
dialog.set_df_property("applicable_doctypes", "options", options); | |||
dialog.set_df_property("applicable_doctypes", "hidden", 1); | |||
} | |||
dialog.refresh(); | |||
}, | |||
on_apply_to_all_doctypes_change: function(dialog, options) { | |||
@@ -243,5 +265,6 @@ frappe.listview_settings['User Permission'] = { | |||
dialog.set_df_property("applicable_doctypes", "options", options); | |||
dialog.set_df_property("applicable_doctypes", "hidden", 1); | |||
} | |||
dialog.refresh_sections(); | |||
} | |||
}; | |||
}; |
@@ -455,11 +455,15 @@ class CustomizeForm(Document): | |||
self.fetch_to_customize() | |||
def reset_customization(doctype): | |||
frappe.db.sql(""" | |||
DELETE FROM `tabProperty Setter` WHERE doc_type=%s | |||
and `field_name`!='naming_series' | |||
and `property`!='options' | |||
""", doctype) | |||
setters = frappe.get_all("Property Setter", filters={ | |||
'doc_type': doctype, | |||
'field_name': ['!=', 'naming_series'], | |||
'property': ['!=', 'options'] | |||
}, pluck='name') | |||
for setter in setters: | |||
frappe.delete_doc("Property Setter", setter) | |||
frappe.clear_cache(doctype=doctype) | |||
doctype_properties = { | |||
@@ -29,6 +29,7 @@ def get_event_conditions(doctype, filters=None): | |||
def get_events(doctype, start, end, field_map, filters=None, fields=None): | |||
field_map = frappe._dict(json.loads(field_map)) | |||
fields = frappe.parse_json(fields) | |||
doc_meta = frappe.get_meta(doctype) | |||
for d in doc_meta.fields: | |||
@@ -73,7 +73,7 @@ def has_permission(doc, ptype, user): | |||
if doc.report_name in allowed_reports: | |||
return True | |||
else: | |||
allowed_doctypes = [frappe.permissions.get_doctypes_with_read()] | |||
allowed_doctypes = frappe.permissions.get_doctypes_with_read() | |||
if doc.document_type in allowed_doctypes: | |||
return True | |||
@@ -18,14 +18,14 @@ def install(): | |||
@frappe.whitelist() | |||
def update_genders(): | |||
default_genders = [_("Male"), _("Female"), _("Other"),_("Transgender"), _("Genderqueer"), _("Non-Conforming"),_("Prefer not to say")] | |||
default_genders = ["Male", "Female", "Other","Transgender", "Genderqueer", "Non-Conforming","Prefer not to say"] | |||
records = [{'doctype': 'Gender', 'gender': d} for d in default_genders] | |||
for record in records: | |||
frappe.get_doc(record).insert(ignore_permissions=True, ignore_if_duplicate=True) | |||
@frappe.whitelist() | |||
def update_salutations(): | |||
default_salutations = [_("Mr"), _("Ms"), _('Mx'), _("Dr"), _("Mrs"), _("Madam"), _("Miss"), _("Master"), _("Prof")] | |||
default_salutations = ["Mr", "Ms", 'Mx', "Dr", "Mrs", "Madam", "Miss", "Master", "Prof"] | |||
records = [{'doctype': 'Salutation', 'salutation': d} for d in default_salutations] | |||
for record in records: | |||
doc = frappe.new_doc(record.get("doctype")) | |||
@@ -54,6 +54,12 @@ def get_form_params(): | |||
fields = data["fields"] | |||
if ((isinstance(fields, string_types) and fields == "*") | |||
or (isinstance(fields, (list, tuple)) and len(fields) == 1 and fields[0] == "*")): | |||
parenttype = data.doctype | |||
data["fields"] = frappe.db.get_table_columns(parenttype) | |||
fields = data["fields"] | |||
for field in fields: | |||
key = field.split(" as ")[0] | |||
@@ -61,21 +67,24 @@ def get_form_params(): | |||
if key.startswith('sum('): continue | |||
if key.startswith('avg('): continue | |||
if "." in key: | |||
parenttype, fieldname = key.split(".")[0][4:-1], key.split(".")[1].strip("`") | |||
else: | |||
parenttype = data.doctype | |||
fieldname = field.strip("`") | |||
parenttype, fieldname = get_parent_dt_and_field(key, data) | |||
df = frappe.get_meta(parenttype).get_field(fieldname) | |||
if fieldname == "*": | |||
# * inside list is not allowed with other fields | |||
fields.remove(field) | |||
meta = frappe.get_meta(parenttype) | |||
df = meta.get_field(fieldname) | |||
fieldname = df.fieldname if df else None | |||
report_hide = df.report_hide if df else None | |||
# remove the field from the query if the report hide flag is set and current view is Report | |||
if report_hide and is_report: | |||
fields.remove(field) | |||
if df and fieldname in [df.fieldname for df in meta.get_high_permlevel_fields()]: | |||
if df.get('permlevel') not in meta.get_permlevel_access(parenttype=data.doctype) and field in fields: | |||
fields.remove(field) | |||
# queries must always be server side | |||
data.query = None | |||
@@ -83,6 +92,16 @@ def get_form_params(): | |||
return data | |||
def get_parent_dt_and_field(field, data): | |||
if "." in field: | |||
parenttype, fieldname = field.split(".")[0][4:-1], field.split(".")[1].strip("`") | |||
else: | |||
parenttype = data.doctype | |||
fieldname = field.strip("`") | |||
return parenttype, fieldname | |||
def compress(data, args = {}): | |||
"""separate keys and values""" | |||
from frappe.desk.query_report import add_total_row | |||
@@ -97,7 +97,7 @@ class AutoEmailReport(Document): | |||
if self.format == 'HTML': | |||
columns, data = make_links(columns, data) | |||
columns = update_field_types(columns) | |||
return self.get_html_table(columns, data) | |||
elif self.format == 'XLSX': | |||
@@ -252,5 +252,14 @@ def make_links(columns, data): | |||
elif col.fieldtype == "Dynamic Link": | |||
if col.options and row.get(col.fieldname) and row.get(col.options): | |||
row[col.fieldname] = get_link_to_form(row[col.options], row[col.fieldname]) | |||
elif col.fieldtype == "Currency": | |||
row[col.fieldname] = frappe.format_value(row[col.fieldname], col) | |||
return columns, data | |||
def update_field_types(columns): | |||
for col in columns: | |||
if col.fieldtype in ("Link", "Dynamic Link", "Currency") and col.options != "Currency": | |||
col.fieldtype = "Data" | |||
col.options = "" | |||
return columns |
@@ -2,34 +2,56 @@ | |||
# License: GNU General Public License v3. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe, unittest | |||
from frappe.utils import getdate, add_days | |||
from frappe.email.doctype.newsletter.newsletter import confirmed_unsubscribe, send_scheduled_email | |||
from six.moves.urllib.parse import unquote | |||
import unittest | |||
from random import choice | |||
import frappe | |||
from frappe.email.doctype.newsletter.newsletter import ( | |||
confirmed_unsubscribe, | |||
send_scheduled_email, | |||
) | |||
from frappe.email.doctype.newsletter.newsletter import get_newsletter_list | |||
from frappe.email.queue import flush | |||
from frappe.utils import add_days, getdate | |||
test_dependencies = ["Email Group"] | |||
emails = [ | |||
"test_subscriber1@example.com", | |||
"test_subscriber2@example.com", | |||
"test_subscriber3@example.com", | |||
"test1@example.com", | |||
] | |||
emails = ["test_subscriber1@example.com", "test_subscriber2@example.com", | |||
"test_subscriber3@example.com", "test1@example.com"] | |||
class TestNewsletter(unittest.TestCase): | |||
def setUp(self): | |||
self.make_email_group() | |||
frappe.set_user("Administrator") | |||
frappe.db.sql("delete from `tabEmail Group Member`") | |||
if not frappe.db.exists("Email Group", "_Test Email Group"): | |||
frappe.get_doc({"doctype": "Email Group", "title": "_Test Email Group"}).insert() | |||
for email in emails: | |||
frappe.get_doc({ | |||
"doctype": "Email Group Member", | |||
"email": email, | |||
"email_group": "_Test Email Group" | |||
}).insert() | |||
def test_send(self): | |||
name = self.send_newsletter() | |||
self.send_newsletter() | |||
email_queue_list = [frappe.get_doc('Email Queue', e.name) for e in frappe.get_all("Email Queue")] | |||
email_queue_list = [frappe.get_doc("Email Queue", e.name) for e in frappe.get_all("Email Queue")] | |||
self.assertEqual(len(email_queue_list), 4) | |||
recipients = [e.recipients[0].recipient for e in email_queue_list] | |||
for email in emails: | |||
self.assertTrue(email in recipients) | |||
recipients = set([e.recipients[0].recipient for e in email_queue_list]) | |||
self.assertTrue(set(emails).issubset(recipients)) | |||
def test_unsubscribe(self): | |||
# test unsubscribe | |||
name = self.send_newsletter() | |||
from frappe.email.queue import flush | |||
to_unsubscribe = choice(emails) | |||
group = frappe.get_all("Newsletter Email Group", filters={"parent": name}, fields=["email_group"]) | |||
flush(from_test=True) | |||
to_unsubscribe = unquote(frappe.local.flags.signed_query_string.split("email=")[1].split("&")[0]) | |||
@@ -37,10 +59,12 @@ class TestNewsletter(unittest.TestCase): | |||
confirmed_unsubscribe(to_unsubscribe, email_group) | |||
name = self.send_newsletter() | |||
email_queue_list = [frappe.get_doc('Email Queue', e.name) for e in frappe.get_all("Email Queue")] | |||
email_queue_list = [ | |||
frappe.get_doc("Email Queue", e.name) for e in frappe.get_all("Email Queue") | |||
] | |||
self.assertEqual(len(email_queue_list), 3) | |||
recipients = [e.recipients[0].recipient for e in email_queue_list] | |||
for email in emails: | |||
if email != to_unsubscribe: | |||
self.assertTrue(email in recipients) | |||
@@ -104,7 +128,7 @@ class TestNewsletter(unittest.TestCase): | |||
email_group = "_Test Email Group" | |||
if not frappe.db.exists("Email Group", email_group): | |||
frappe.get_doc("Email Group", email_group).insert() | |||
for email in emails: | |||
if not frappe.db.exists('Email Group Member', dict(email=email, email_group = email_group)): | |||
frappe.get_doc({ | |||
@@ -112,3 +136,27 @@ class TestNewsletter(unittest.TestCase): | |||
"email": email, | |||
"email_group": email_group | |||
}).insert() | |||
def test_portal(self): | |||
self.send_newsletter(1) | |||
frappe.set_user("test1@example.com") | |||
newsletters = get_newsletter_list("Newsletter", None, None, 0) | |||
self.assertEqual(len(newsletters), 1) | |||
def test_newsletter_context(self): | |||
context = frappe._dict() | |||
newsletter_name = self.send_newsletter(1) | |||
frappe.set_user("test2@example.com") | |||
doc = frappe.get_doc("Newsletter", newsletter_name) | |||
doc.get_context(context) | |||
self.assertEqual(context.no_cache, 1) | |||
self.assertTrue("attachments" not in list(context)) | |||
def test_schedule_send(self): | |||
self.send_newsletter(schedule_send=add_days(getdate(), -1)) | |||
email_queue_list = [frappe.get_doc('Email Queue', e.name) for e in frappe.get_all("Email Queue")] | |||
self.assertEqual(len(email_queue_list), 4) | |||
recipients = [e.recipients[0].recipient for e in email_queue_list] | |||
for email in emails: | |||
self.assertTrue(email in recipients) |
@@ -450,6 +450,25 @@ class Meta(Document): | |||
return self.high_permlevel_fields | |||
def get_permlevel_access(self, permission_type='read', parenttype=None): | |||
has_access_to = [] | |||
roles = frappe.get_roles() | |||
for perm in self.get_permissions(parenttype): | |||
if perm.role in roles and perm.permlevel > 0 and perm.get(permission_type): | |||
if perm.permlevel not in has_access_to: | |||
has_access_to.append(perm.permlevel) | |||
return has_access_to | |||
def get_permissions(self, parenttype=None): | |||
if self.istable and parenttype: | |||
# use parent permissions | |||
permissions = frappe.get_meta(parenttype).permissions | |||
else: | |||
permissions = self.get('permissions', []) | |||
return permissions | |||
def get_dashboard_data(self): | |||
'''Returns dashboard setup related to this doctype. | |||
@@ -398,7 +398,8 @@ 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, is_default=0): | |||
def add_user_permission(doctype, name, user, ignore_permissions=False, applicable_for=None, | |||
is_default=0, hide_descendants=0): | |||
'''Add user permission''' | |||
from frappe.core.doctype.user_permission.user_permission import user_permission_exists | |||
@@ -413,6 +414,7 @@ def add_user_permission(doctype, name, user, ignore_permissions=False, applicabl | |||
for_value=name, | |||
is_default=is_default, | |||
applicable_for=applicable_for, | |||
hide_descendants=hide_descendants, | |||
)).insert(ignore_permissions=ignore_permissions) | |||
def remove_user_permission(doctype, name, user): | |||
@@ -136,6 +136,7 @@ | |||
"public/js/frappe/router_history.js", | |||
"public/js/frappe/defaults.js", | |||
"public/js/frappe/roles_editor.js", | |||
"public/js/frappe/module_editor.js", | |||
"public/js/frappe/microtemplate.js", | |||
"public/js/frappe/ui/page.html", | |||
@@ -17,7 +17,7 @@ frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({ | |||
this.map_area.prependTo($input_wrapper); | |||
this.$wrapper.find('.control-input').addClass("hidden"); | |||
if ($input_wrapper.is(':visible')) { | |||
if (this.frm) { | |||
this.make_map(); | |||
} else { | |||
$(document).on('frappe.ui.Dialog:shown', () => { | |||
@@ -0,0 +1,39 @@ | |||
frappe.ModuleEditor = Class.extend({ | |||
init: function(frm, wrapper) { | |||
this.wrapper = $('<div class="row module-block-list"></div>').appendTo(wrapper); | |||
this.frm = frm; | |||
this.make(); | |||
}, | |||
make: function() { | |||
var me = this; | |||
this.frm.doc.__onload.all_modules.forEach(function(m) { | |||
$(repl('<div class="col-sm-6"><div class="checkbox">\ | |||
<label><input type="checkbox" class="block-module-check" data-module="%(module)s">\ | |||
%(module)s</label></div></div>', {module: m})).appendTo(me.wrapper); | |||
}); | |||
this.bind(); | |||
}, | |||
refresh: function() { | |||
var me = this; | |||
this.wrapper.find(".block-module-check").prop("checked", true); | |||
$.each(this.frm.doc.block_modules, function(i, d) { | |||
me.wrapper.find(".block-module-check[data-module='"+ d.module +"']").prop("checked", false); | |||
}); | |||
}, | |||
bind: function() { | |||
var me = this; | |||
this.wrapper.on("change", ".block-module-check", function() { | |||
var module = $(this).attr('data-module'); | |||
if ($(this).prop("checked")) { | |||
// remove from block_modules | |||
me.frm.doc.block_modules = $.map(me.frm.doc.block_modules || [], function(d) { | |||
if (d.module != module) { | |||
return d; | |||
} | |||
}); | |||
} else { | |||
me.frm.add_child("block_modules", {"module": module}); | |||
} | |||
}); | |||
} | |||
}); |
@@ -708,10 +708,19 @@ frappe.views.CommunicationComposer = Class.extend({ | |||
} | |||
}, | |||
setup_earlier_reply: function() { | |||
get_default_outgoing_email_account_signature: function() { | |||
return frappe.db.get_value('Email Account', { 'default_outgoing': 1, 'add_signature': 1 }, 'signature'); | |||
}, | |||
setup_earlier_reply: async function() { | |||
let fields = this.dialog.fields_dict; | |||
let signature = frappe.boot.user.email_signature || ""; | |||
if (!signature) { | |||
const res = await this.get_default_outgoing_email_account_signature(); | |||
signature = res.message.signature; | |||
} | |||
if(!frappe.utils.is_html(signature)) { | |||
signature = signature.replace(/\n/g, "<br>"); | |||
} | |||
@@ -792,4 +801,3 @@ frappe.views.CommunicationComposer = Class.extend({ | |||
return text.replace(/\n{3,}/g, '\n\n'); | |||
} | |||
}); | |||
@@ -7,9 +7,14 @@ import frappe, unittest | |||
from frappe.model.db_query import DatabaseQuery | |||
from frappe.desk.reportview import get_filters_cond | |||
from frappe.core.page.permission_manager.permission_manager import update, reset, add | |||
from frappe.permissions import add_user_permission, clear_user_permissions_for_doctype | |||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter | |||
from frappe.handler import execute_cmd | |||
test_dependencies = ['User', 'Blog Post'] | |||
from frappe.utils.testutils import add_custom_field, clear_custom_fields | |||
test_dependencies = ['User', 'Blog Post', 'Blog Category', 'Blogger'] | |||
class TestReportview(unittest.TestCase): | |||
def test_basic(self): | |||
@@ -355,6 +360,79 @@ class TestReportview(unittest.TestCase): | |||
owners = DatabaseQuery("DocType").execute(filters={"name": "DocType"}, pluck="owner") | |||
self.assertEqual(owners, ["Administrator"]) | |||
def test_reportview_get(self): | |||
user = frappe.get_doc("User", "test@example.com") | |||
add_child_table_to_blog_post() | |||
user_roles = frappe.get_roles() | |||
user.remove_roles(*user_roles) | |||
user.add_roles("Blogger") | |||
make_property_setter("Blog Post", "published", "permlevel", 1, "Int") | |||
reset("Blog Post") | |||
add("Blog Post", "Website Manager", 1) | |||
update("Blog Post", "Website Manager", 1, "write", 1) | |||
frappe.set_user(user.name) | |||
frappe.local.request = frappe._dict() | |||
frappe.local.request.method = "POST" | |||
frappe.local.form_dict = frappe._dict({ | |||
"doctype": "Blog Post", | |||
"fields": ["published", "title", "`tabTest Child`.`test_field`"], | |||
}) | |||
# even if * is passed, fields which are not accessible should be filtered out | |||
response = execute_cmd("frappe.desk.reportview.get") | |||
self.assertListEqual(response["keys"], ["title"]) | |||
frappe.local.form_dict = frappe._dict({ | |||
"doctype": "Blog Post", | |||
"fields": ["*"], | |||
}) | |||
response = execute_cmd("frappe.desk.reportview.get") | |||
self.assertNotIn("published", response["keys"]) | |||
frappe.set_user("Administrator") | |||
user.add_roles("Website Manager") | |||
frappe.set_user(user.name) | |||
frappe.set_user("Administrator") | |||
# Admin should be able to see access all fields | |||
frappe.local.form_dict = frappe._dict({ | |||
"doctype": "Blog Post", | |||
"fields": ["published", "title", "`tabTest Child`.`test_field`"], | |||
}) | |||
response = execute_cmd("frappe.desk.reportview.get") | |||
self.assertListEqual(response["keys"], ['published', 'title', 'test_field']) | |||
# reset user roles | |||
user.remove_roles("Blogger", "Website Manager") | |||
user.add_roles(*user_roles) | |||
def add_child_table_to_blog_post(): | |||
child_table = frappe.get_doc({ | |||
'doctype': 'DocType', | |||
'istable': 1, | |||
'custom': 1, | |||
'name': 'Test Child', | |||
'module': 'Custom', | |||
'autoname': 'Prompt', | |||
'fields': [{ | |||
'fieldname': 'test_field', | |||
'fieldtype': 'Data', | |||
'permlevel': 1 | |||
}], | |||
}) | |||
child_table.insert(ignore_permissions=True, ignore_if_duplicate=True) | |||
clear_custom_fields('Blog Post') | |||
add_custom_field('Blog Post', 'child_table', 'Table', child_table.name) | |||
def create_event(subject="_Test Event", starts_on=None): | |||
""" create a test event """ | |||
@@ -155,14 +155,22 @@ def get_time_zone(): | |||
return frappe.cache().get_value("time_zone", _get_time_zone) | |||
def convert_utc_to_user_timezone(utc_timestamp): | |||
def convert_utc_to_timezone(utc_timestamp, time_zone): | |||
from pytz import timezone, UnknownTimeZoneError | |||
utcnow = timezone('UTC').localize(utc_timestamp) | |||
try: | |||
return utcnow.astimezone(timezone(get_time_zone())) | |||
return utcnow.astimezone(timezone(time_zone)) | |||
except UnknownTimeZoneError: | |||
return utcnow | |||
def get_datetime_in_timezone(time_zone): | |||
utc_timestamp = datetime.datetime.utcnow() | |||
return convert_utc_to_timezone(utc_timestamp, time_zone) | |||
def convert_utc_to_user_timezone(utc_timestamp): | |||
time_zone = get_time_zone() | |||
return convert_utc_to_timezone(utc_timestamp, time_zone) | |||
def now(): | |||
"""return current datetime as yyyy-mm-dd hh:mm:ss""" | |||
if frappe.flags.current_date: | |||
@@ -230,12 +230,19 @@ def update_oauth_user(user, data, provider): | |||
save = True | |||
user = frappe.new_doc("User") | |||
gender = (data.get("gender") or "").title() | |||
if not frappe.db.exists("Gender", gender): | |||
doc = frappe.new_doc("Gender", {"gender": gender}) | |||
doc.insert(ignore_permissions=True) | |||
user.update({ | |||
"doctype":"User", | |||
"first_name": get_first_name(data), | |||
"last_name": get_last_name(data), | |||
"email": get_email(data), | |||
"gender": (data.get("gender") or "").title(), | |||
"gender": gender, | |||
"enabled": 1, | |||
"new_password": frappe.generate_hash(get_email(data)), | |||
"location": data.get("location"), | |||
@@ -223,6 +223,7 @@ VALID_UTILS = ( | |||
"get_last_day_of_week", | |||
"get_last_day", | |||
"get_time", | |||
"get_datetime_in_timezone", | |||
"get_datetime_str", | |||
"get_date_str", | |||
"get_time_str", | |||
@@ -49,7 +49,7 @@ pypng==0.0.20 | |||
PyQRCode==1.2.1 | |||
python-dateutil==2.8.1 | |||
pytz==2019.3 | |||
PyYAML==5.3.1 | |||
PyYAML==5.4 | |||
rauth==0.7.3 | |||
redis==3.5.3 | |||
requests-oauthlib==1.3.0 | |||