From 4df9a203f0019dfd1faacaf1be5c47cb5b2eb073 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Dec 2020 14:14:29 +0530 Subject: [PATCH 01/28] feat: fetch email account signature in email dialog --- frappe/public/js/frappe/views/communication.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 29b21242af..fe14ad4793 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -464,6 +464,7 @@ frappe.views.CommunicationComposer = Class.extend({ }, send_action: function() { + debugger; var me = this; var btn = me.dialog.get_primary_btn(); @@ -625,10 +626,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, "
"); } From b73a1a8710b4a19a166266351efbd73b2b04ef4a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 4 Jan 2021 13:42:12 +0530 Subject: [PATCH 02/28] fix: reset customizations don't get committed --- .../doctype/customize_form/customize_form.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 82513783c7..50acab46b5 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -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 = { From 1591cee1457b51d1d58765abd92afbd435b5cfac Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 6 Jan 2021 17:08:57 +0530 Subject: [PATCH 03/28] fix: remove debugger --- frappe/public/js/frappe/views/communication.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index fe14ad4793..e974cd52ed 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -464,7 +464,6 @@ frappe.views.CommunicationComposer = Class.extend({ }, send_action: function() { - debugger; var me = this; var btn = me.dialog.get_primary_btn(); @@ -719,4 +718,3 @@ frappe.views.CommunicationComposer = Class.extend({ return text.replace(/\n{3,}/g, '\n\n'); } }); - From 64f3887dce25da4d93d9bc2b0eccc4f04a744ce2 Mon Sep 17 00:00:00 2001 From: "hasnain2808@gmail.com" Date: Wed, 13 Jan 2021 16:00:23 +0530 Subject: [PATCH 04/28] fix: html download of auto download report broken --- .../doctype/auto_email_report/auto_email_report.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.py b/frappe/email/doctype/auto_email_report/auto_email_report.py index 539f6c9db8..de27fafee3 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.py +++ b/frappe/email/doctype/auto_email_report/auto_email_report.py @@ -81,7 +81,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': @@ -236,5 +236,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 \ No newline at end of file From ac2ab580db52a9dcd54ea31b1ccd0a068b30e8de Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 13 Jan 2021 23:55:18 +0530 Subject: [PATCH 05/28] feat: Hide Child Records for a Nested DocType via User Permissions --- .../user_permission/test_user_permission.py | 52 +++- .../user_permission/user_permission.js | 13 +- .../user_permission/user_permission.json | 288 +++--------------- .../user_permission/user_permission.py | 32 +- frappe/permissions.py | 4 +- 5 files changed, 117 insertions(+), 272 deletions(-) diff --git a/frappe/core/doctype/user_permission/test_user_permission.py b/frappe/core/doctype/user_permission/test_user_permission.py index 82dd2ab27e..38ffbd1490 100644 --- a/frappe/core/doctype/user_permission/test_user_permission.py +++ b/frappe/core/doctype/user_permission/test_user_permission.py @@ -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(f"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}, "exclude_descendants", 1) + frappe.cache().delete_value("user_permissions") + + # check if adding perm on a group record with exclude_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, exclude_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": [], + "exclude_descendants": exclude_descendants } if applicable: param.update({"apply_to_all_doctypes": 0}) diff --git a/frappe/core/doctype/user_permission/user_permission.js b/frappe/core/doctype/user_permission/user_permission.js index 9f824b1350..b06837aa64 100644 --- a/frappe/core/doctype/user_permission/user_permission.js +++ b/frappe/core/doctype/user_permission/user_permission.js @@ -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('show_exclude_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('show_exclude_descendants'); } }, @@ -43,6 +47,11 @@ frappe.ui.form.on('User Permission', { if (frm.doc.apply_to_all_doctypes) { frm.set_value('applicable_for', null); } + }, + + show_exclude_descendants: frm => { + let show = frappe.boot.nested_set_doctypes.includes(frm.doc.allow); + frm.toggle_display('exclude_descendants', show); } diff --git a/frappe/core/doctype/user_permission/user_permission.json b/frappe/core/doctype/user_permission/user_permission.json index 33a8d58bbb..6dc626ba04 100644 --- a/frappe/core/doctype/user_permission/user_permission.json +++ b/frappe/core/doctype/user_permission/user_permission.json @@ -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", + "exclude_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 For Value.", + "fieldname": "exclude_descendants", + "fieldtype": "Check", + "hidden": 1, + "label": "Exclude 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-13 19:31:05.618273", "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 } \ No newline at end of file diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py index ba14583c2f..48d56c8b28 100644 --- a/frappe/core/doctype/user_permission/user_permission.py +++ b/frappe/core/doctype/user_permission/user_permission.py @@ -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, + 'exclude_descendants': cstr(self.exclude_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', 'exclude_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.exclude_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,7 @@ def add_user_permissions(data): data = json.loads(data) data = frappe._dict(data) - d = check_applicable_doc_perm(data.user, data.doctype, data.docname) + 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 +203,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.exclude_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.exclude_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.exclude_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, exclude_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.exclude_descendants = exclude_descendants if applicable: user_perm.applicable_for = applicable user_perm.apply_to_all_doctypes = 0 @@ -229,8 +231,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 diff --git a/frappe/permissions.py b/frappe/permissions.py index a45fbdcd06..ce551d0617 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -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, exclude_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, + exclude_descendants=exclude_descendants, )).insert(ignore_permissions=ignore_permissions) def remove_user_permission(doctype, name, user): From 3959deb0523f7c8caa4c15ebdfa0d38d91aaa73f Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 15 Jan 2021 14:59:23 +0530 Subject: [PATCH 06/28] chore: Add 'Exclude Descendants' to add / update perms in bulk dialog --- .../user_permission/user_permission.py | 1 + .../user_permission/user_permission_list.js | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py index 48d56c8b28..6c926ba10f 100644 --- a/frappe/core/doctype/user_permission/user_permission.py +++ b/frappe/core/doctype/user_permission/user_permission.py @@ -195,6 +195,7 @@ def add_user_permissions(data): data = json.loads(data) data = frappe._dict(data) + # get all doctypes on whom this permission os applied perm_applied_docs = check_applicable_doc_perm(data.user, data.doctype, data.docname) exists = frappe.db.exists("User Permission", { "user": data.user, diff --git a/frappe/core/doctype/user_permission/user_permission_list.js b/frappe/core/doctype/user_permission/user_permission_list.js index 3e822f0007..49da74347f 100644 --- a/frappe/core/doctype/user_permission/user_permission_list.js +++ b/frappe/core/doctype/user_permission/user_permission_list.js @@ -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("exclude_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: 'exclude_descendants', + label: __('Exclude 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("exclude_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(); } }; \ No newline at end of file From b923d4e8ecbe51acc8502a9723069964d543be3f Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 15 Jan 2021 16:38:03 +0530 Subject: [PATCH 07/28] fix: Sider Issues - Removed unwanted f string in test - Space after if (client side) --- frappe/core/doctype/user_permission/test_user_permission.py | 2 +- frappe/core/doctype/user_permission/user_permission.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/user_permission/test_user_permission.py b/frappe/core/doctype/user_permission/test_user_permission.py index 38ffbd1490..6773af004f 100644 --- a/frappe/core/doctype/user_permission/test_user_permission.py +++ b/frappe/core/doctype/user_permission/test_user_permission.py @@ -16,7 +16,7 @@ class TestUserPermission(unittest.TestCase): 'test_user_perm1@example.com', 'nested_doc_user@example.com')""") frappe.delete_doc_if_exists("DocType", "Person") - frappe.db.sql_ddl(f"DROP TABLE IF EXISTS `tabPerson`") + frappe.db.sql_ddl("DROP TABLE IF EXISTS `tabPerson`") def test_default_user_permission_validation(self): user = create_user('test_default_permission@example.com') diff --git a/frappe/core/doctype/user_permission/user_permission.js b/frappe/core/doctype/user_permission/user_permission.js index b06837aa64..665d3c963c 100644 --- a/frappe/core/doctype/user_permission/user_permission.js +++ b/frappe/core/doctype/user_permission/user_permission.js @@ -31,7 +31,7 @@ frappe.ui.form.on('User Permission', { allow: frm => { if (frm.doc.allow) { - if(frm.doc.for_value) { + if (frm.doc.for_value) { frm.set_value('for_value', null); } frm.trigger('show_exclude_descendants'); From b00b325688effc75c82d6f478a77ef9c63d8411c Mon Sep 17 00:00:00 2001 From: ollyboy Date: Sun, 17 Jan 2021 17:45:08 +0000 Subject: [PATCH 08/28] fix field list passed as string so append works --- frappe/desk/calendar.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/desk/calendar.py b/frappe/desk/calendar.py index ce9fb7f177..127194671a 100644 --- a/frappe/desk/calendar.py +++ b/frappe/desk/calendar.py @@ -44,6 +44,8 @@ def get_events(doctype, start, end, field_map, filters=None, fields=None): fields = [field_map.start, field_map.end, field_map.title, 'name'] if field_map.color: + if isinstance(fields, str): + fields = json.loads ( fields ) fields.append(field_map.color) start_date = "ifnull(%s, '0001-01-01 00:00:00')" % field_map.start From 962fff15c4a350362edeedabb5cf2a452972f045 Mon Sep 17 00:00:00 2001 From: shariquerik Date: Mon, 18 Jan 2021 20:49:09 +0530 Subject: [PATCH 09/28] Link Field Validation doesn't use Filter criteria defined for link field In link field if we enter invalid i.e not available record it will clear field but if we enter valid record that is not available in filter but available in db than it will accept that record --- frappe/public/js/frappe/form/controls/link.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 4c0fe39f60..019ea5ca58 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -455,6 +455,11 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ fetch = this.frm.fetch_dict[df.fieldname].columns.join(', '); } + // check if value exist in the filtered dropdown values + if(this.$input.cache[doctype][""] && !this.$input.cache[doctype][""].some(d => d.value === value)){ + value = "" + } + return frappe.call({ method:'frappe.desk.form.utils.validate_link', type: "GET", From ebcb8438de691eb085528912253a0b17ee03dee9 Mon Sep 17 00:00:00 2001 From: shariquerik Date: Mon, 18 Jan 2021 21:45:07 +0530 Subject: [PATCH 10/28] fix: semantic commit and sider issues --- frappe/public/js/frappe/form/controls/link.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 019ea5ca58..6ac10c8534 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -456,8 +456,8 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ } // check if value exist in the filtered dropdown values - if(this.$input.cache[doctype][""] && !this.$input.cache[doctype][""].some(d => d.value === value)){ - value = "" + if (this.$input.cache[doctype][""] && !this.$input.cache[doctype][""].some(d => d.value === value)) { + value = ""; } return frappe.call({ From 83822b14c6e6aded305c00fefee679833c4ee2ab Mon Sep 17 00:00:00 2001 From: shariquerik Date: Tue, 19 Jan 2021 14:07:19 +0530 Subject: [PATCH 11/28] fix: Updated if condition --- frappe/public/js/frappe/form/controls/link.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 6ac10c8534..77716ee60a 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -456,7 +456,7 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ } // check if value exist in the filtered dropdown values - if (this.$input.cache[doctype][""] && !this.$input.cache[doctype][""].some(d => d.value === value)) { + if (this.$input.cache[doctype] && !this.$input.cache[doctype][""].some(d => d.value === value)) { value = ""; } From bcae1cc294fac0aeeb90f68d57f7db1f673865aa Mon Sep 17 00:00:00 2001 From: shariquerik Date: Tue, 19 Jan 2021 22:29:23 +0530 Subject: [PATCH 12/28] revert --- frappe/public/js/frappe/form/controls/link.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 77716ee60a..019ea5ca58 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -456,8 +456,8 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ } // check if value exist in the filtered dropdown values - if (this.$input.cache[doctype] && !this.$input.cache[doctype][""].some(d => d.value === value)) { - value = ""; + if(this.$input.cache[doctype][""] && !this.$input.cache[doctype][""].some(d => d.value === value)){ + value = "" } return frappe.call({ From 099474a441f922fab40e8ac1aa1fc26c851b7344 Mon Sep 17 00:00:00 2001 From: shariquerik Date: Tue, 19 Jan 2021 22:29:30 +0530 Subject: [PATCH 13/28] Revert "Link Field Validation doesn't use Filter criteria defined for link field" This reverts commit 962fff15c4a350362edeedabb5cf2a452972f045. --- frappe/public/js/frappe/form/controls/link.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 019ea5ca58..4c0fe39f60 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -455,11 +455,6 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ fetch = this.frm.fetch_dict[df.fieldname].columns.join(', '); } - // check if value exist in the filtered dropdown values - if(this.$input.cache[doctype][""] && !this.$input.cache[doctype][""].some(d => d.value === value)){ - value = "" - } - return frappe.call({ method:'frappe.desk.form.utils.validate_link', type: "GET", From bddb7034cfe67f3669c1a97b915621b03cab422d Mon Sep 17 00:00:00 2001 From: shariquerik Date: Tue, 19 Jan 2021 22:36:33 +0530 Subject: [PATCH 14/28] fix: Geolocation field with Column Break & Section Break (Collapsible) does not seems to work. --- frappe/public/js/frappe/form/controls/geolocation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/controls/geolocation.js b/frappe/public/js/frappe/form/controls/geolocation.js index 9e4d1d82ec..dfd0f4d174 100644 --- a/frappe/public/js/frappe/form/controls/geolocation.js +++ b/frappe/public/js/frappe/form/controls/geolocation.js @@ -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', () => { From 19c6e0218db9b1dd95132693a96a8174fac2dc94 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Wed, 20 Jan 2021 22:57:25 +0000 Subject: [PATCH 15/28] fix: requirements.txt to reduce vulnerabilities The following vulnerabilities are fixed by pinning transitive dependencies: - https://snyk.io/vuln/SNYK-PYTHON-PYYAML-590151 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3cc92264a2..e128790e45 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 From 33ea496a8bd5953714941cfde4d2c1da1982c0b3 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 21 Jan 2021 13:12:42 +0530 Subject: [PATCH 16/28] feat: Added get_datetime_in_timezone in frappe.utils to get datetime in specific timezones * Added util in safe_exec to access via Server Scripts and System Console --- frappe/utils/data.py | 12 ++++++++++-- frappe/utils/safe_exec.py | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index c24b9f186e..c60e64b015 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -154,14 +154,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: diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 2aacf5eda8..06a192c05e 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -222,6 +222,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", From 16f2b29cb3c1395e8fbaae50d3076b410b4555b7 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 21 Jan 2021 13:15:14 +0530 Subject: [PATCH 17/28] style: Trim extra whitespace --- frappe/utils/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index c60e64b015..da2c910e20 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -377,7 +377,7 @@ def format_duration(seconds, hide_days=False): example: converts 12885 to '3h 34m 45s' where 12885 = seconds in float """ - + seconds = cint(seconds) total_duration = { From 4887dd1d5a20c952dea1fc8d4f5ce49aeb5136cb Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 21 Jan 2021 18:48:25 +0530 Subject: [PATCH 18/28] chore: Rename 'Exclude Descendants' to 'Hide Descendants' - Rename 'Exclude Descendants' to 'Hide Descendants' - Rename js trigger to 'toggle_hide_descendants' --- .../user_permission/test_user_permission.py | 8 ++++---- .../doctype/user_permission/user_permission.js | 8 ++++---- .../user_permission/user_permission.json | 8 ++++---- .../doctype/user_permission/user_permission.py | 18 +++++++++--------- .../user_permission/user_permission_list.js | 6 +++--- frappe/permissions.py | 4 ++-- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/frappe/core/doctype/user_permission/test_user_permission.py b/frappe/core/doctype/user_permission/test_user_permission.py index 6773af004f..7e0b4a49c6 100644 --- a/frappe/core/doctype/user_permission/test_user_permission.py +++ b/frappe/core/doctype/user_permission/test_user_permission.py @@ -145,10 +145,10 @@ class TestUserPermission(unittest.TestCase): 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}, "exclude_descendants", 1) + 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 exclude_descendants enabled, + # 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)) @@ -164,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, exclude_descendants=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, @@ -173,7 +173,7 @@ def get_params(user, doctype, docname, is_default=0, exclude_descendants=0, appl "is_default": is_default, "apply_to_all_doctypes": 1, "applicable_doctypes": [], - "exclude_descendants": exclude_descendants + "hide_descendants": hide_descendants } if applicable: param.update({"apply_to_all_doctypes": 0}) diff --git a/frappe/core/doctype/user_permission/user_permission.js b/frappe/core/doctype/user_permission/user_permission.js index 665d3c963c..4c3f5b4eb8 100644 --- a/frappe/core/doctype/user_permission/user_permission.js +++ b/frappe/core/doctype/user_permission/user_permission.js @@ -26,7 +26,7 @@ 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('show_exclude_descendants'); + frm.trigger('toggle_hide_descendants'); }, allow: frm => { @@ -34,7 +34,7 @@ frappe.ui.form.on('User Permission', { if (frm.doc.for_value) { frm.set_value('for_value', null); } - frm.trigger('show_exclude_descendants'); + frm.trigger('toggle_hide_descendants'); } }, @@ -49,9 +49,9 @@ frappe.ui.form.on('User Permission', { } }, - show_exclude_descendants: frm => { + toggle_hide_descendants: frm => { let show = frappe.boot.nested_set_doctypes.includes(frm.doc.allow); - frm.toggle_display('exclude_descendants', show); + frm.toggle_display('hide_descendants', show); } diff --git a/frappe/core/doctype/user_permission/user_permission.json b/frappe/core/doctype/user_permission/user_permission.json index 6dc626ba04..9cea0856c9 100644 --- a/frappe/core/doctype/user_permission/user_permission.json +++ b/frappe/core/doctype/user_permission/user_permission.json @@ -15,7 +15,7 @@ "apply_to_all_doctypes", "applicable_for", "column_break_9", - "exclude_descendants" + "hide_descendants" ], "fields": [ { @@ -83,14 +83,14 @@ { "default": "0", "description": "Hide descendant records of For Value.", - "fieldname": "exclude_descendants", + "fieldname": "hide_descendants", "fieldtype": "Check", "hidden": 1, - "label": "Exclude Descendants" + "label": "Hide Descendants" } ], "links": [], - "modified": "2021-01-13 19:31:05.618273", + "modified": "2021-01-21 18:14:10.839381", "modified_by": "Administrator", "module": "Core", "name": "User Permission", diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py index 6c926ba10f..e04000c0b3 100644 --- a/frappe/core/doctype/user_permission/user_permission.py +++ b/frappe/core/doctype/user_permission/user_permission.py @@ -50,7 +50,7 @@ class UserPermission(Document): }, or_filters={ 'applicable_for': cstr(self.applicable_for), 'apply_to_all_doctypes': 1, - 'exclude_descendants': cstr(self.exclude_descendants) + 'hide_descendants': cstr(self.hide_descendants) }, limit=1) if overlap_exists: ref_link = frappe.get_desk_link(self.doctype, overlap_exists[0].name) @@ -92,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', 'exclude_descendants'], + 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() and not perm.exclude_descendants: + 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) @@ -195,7 +195,7 @@ def add_user_permissions(data): data = json.loads(data) data = frappe._dict(data) - # get all doctypes on whom this permission os applied + # 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, @@ -205,26 +205,26 @@ def add_user_permissions(data): }) if data.apply_to_all_doctypes == 1 and not exists: remove_applicable(perm_applied_docs, data.user, data.doctype, data.docname) - insert_user_perm(data.user, data.doctype, data.docname, data.is_default, data.exclude_descendants, apply_to_all=1) + 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(perm_applied_docs, data.applicable_doctypes, data.user, data.doctype, data.docname) for applicable in data.applicable_doctypes : if applicable not in perm_applied_docs: - insert_user_perm(data.user, data.doctype, data.docname, data.is_default, data.exclude_descendants, applicable=applicable) + 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, data.exclude_descendants, 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, exclude_descendants=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.exclude_descendants = exclude_descendants + user_perm.hide_descendants = hide_descendants if applicable: user_perm.applicable_for = applicable user_perm.apply_to_all_doctypes = 0 diff --git a/frappe/core/doctype/user_permission/user_permission_list.js b/frappe/core/doctype/user_permission/user_permission_list.js index 49da74347f..49ec81984b 100644 --- a/frappe/core/doctype/user_permission/user_permission_list.js +++ b/frappe/core/doctype/user_permission/user_permission_list.js @@ -19,7 +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("exclude_descendants", "hidden", 1); + dialog.set_df_property("hide_descendants", "hidden", 1); } }, { @@ -83,7 +83,7 @@ frappe.listview_settings['User Permission'] = { fieldtype: "Column Break" }, { - fieldname: 'exclude_descendants', + fieldname: 'hide_descendants', label: __('Exclude Descendants'), fieldtype: 'Check', hidden: 1 @@ -233,7 +233,7 @@ frappe.listview_settings['User Permission'] = { 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("exclude_descendants", "hidden", !show); + dialog.set_df_property("hide_descendants", "hidden", !show); dialog.refresh(); }, diff --git a/frappe/permissions.py b/frappe/permissions.py index ce551d0617..abb1f6653a 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -399,7 +399,7 @@ def set_user_permission_if_allowed(doctype, name, user, with_message=False): add_user_permission(doctype, name, user) def add_user_permission(doctype, name, user, ignore_permissions=False, applicable_for=None, - is_default=0, exclude_descendants=0): + is_default=0, hide_descendants=0): '''Add user permission''' from frappe.core.doctype.user_permission.user_permission import user_permission_exists @@ -414,7 +414,7 @@ def add_user_permission(doctype, name, user, ignore_permissions=False, applicabl for_value=name, is_default=is_default, applicable_for=applicable_for, - exclude_descendants=exclude_descendants, + hide_descendants=hide_descendants, )).insert(ignore_permissions=ignore_permissions) def remove_user_permission(doctype, name, user): From 5c9cc655cfd9b09a40a7f8b5ba555b94899a8225 Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Fri, 22 Jan 2021 11:50:04 +0530 Subject: [PATCH 19/28] fix: Insufficient Permission for Leads > Dashboard Chart (#12243) Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- frappe/desk/doctype/dashboard_chart/dashboard_chart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 2fa36b5514..b19f6cf9f0 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -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 From aad9610b78ff91cc429f4b7532785e75b8b5e990 Mon Sep 17 00:00:00 2001 From: Marica Date: Fri, 22 Jan 2021 12:18:25 +0530 Subject: [PATCH 20/28] fix: checkbox label in UP List View Dialog Co-authored-by: Rucha Mahabal --- frappe/core/doctype/user_permission/user_permission_list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/user_permission/user_permission_list.js b/frappe/core/doctype/user_permission/user_permission_list.js index 49ec81984b..5539a26438 100644 --- a/frappe/core/doctype/user_permission/user_permission_list.js +++ b/frappe/core/doctype/user_permission/user_permission_list.js @@ -84,7 +84,7 @@ frappe.listview_settings['User Permission'] = { }, { fieldname: 'hide_descendants', - label: __('Exclude Descendants'), + label: __('Hide Descendants'), fieldtype: 'Check', hidden: 1 }, @@ -267,4 +267,4 @@ frappe.listview_settings['User Permission'] = { } dialog.refresh_sections(); } -}; \ No newline at end of file +}; From 9b59b59e44211812fe24443ba66cfd56fd9e02cf Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 22 Jan 2021 13:13:50 +0530 Subject: [PATCH 21/28] test: Update TestNewsletter.test_unsubscribe * Update selecting email to unsubscribe email logic * style fixes --- .../doctype/newsletter/test_newsletter.py | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/frappe/email/doctype/newsletter/test_newsletter.py b/frappe/email/doctype/newsletter/test_newsletter.py index ee7f123b7e..bd8fadc29c 100644 --- a/frappe/email/doctype/newsletter/test_newsletter.py +++ b/frappe/email/doctype/newsletter/test_newsletter.py @@ -2,58 +2,66 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe, unittest -from frappe.utils import getdate, add_days +import unittest +from random import choice -from frappe.email.doctype.newsletter.newsletter import confirmed_unsubscribe, send_scheduled_email -from six.moves.urllib.parse import unquote +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): frappe.set_user("Administrator") - frappe.db.sql('delete from `tabEmail Group Member`') + 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() - group_exist=frappe.db.exists("Email Group", "_Test Email Group") - if len(group_exist) == 0: - 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() + 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]) - group = frappe.get_all("Newsletter Email Group", filters={"parent" : name}, fields=["email_group"]) confirmed_unsubscribe(to_unsubscribe, group[0].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) @@ -86,7 +94,6 @@ class TestNewsletter(unittest.TestCase): def test_portal(self): self.send_newsletter(1) frappe.set_user("test1@example.com") - from frappe.email.doctype.newsletter.newsletter import get_newsletter_list newsletters = get_newsletter_list("Newsletter", None, None, 0) self.assertEqual(len(newsletters), 1) @@ -106,4 +113,4 @@ class TestNewsletter(unittest.TestCase): 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) \ No newline at end of file + self.assertTrue(email in recipients) From e9e99f20c3c16487964f9aed80c9965d45a45c4a Mon Sep 17 00:00:00 2001 From: pateljannat Date: Sat, 23 Jan 2021 19:11:45 +0530 Subject: [PATCH 22/28] fix: user mandatory in change user dialog --- frappe/desk/page/user_profile/user_profile.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/desk/page/user_profile/user_profile.js b/frappe/desk/page/user_profile/user_profile.js index 5f91b376e8..1057cce2f3 100644 --- a/frappe/desk/page/user_profile/user_profile.js +++ b/frappe/desk/page/user_profile/user_profile.js @@ -76,6 +76,7 @@ class UserProfile { fieldname: 'user', options: 'User', label: __('User'), + reqd: 1 } ], primary_action_label: __('Go'), From a2fba77116609c089f281ee92c15cccb13c0e610 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Mon, 25 Jan 2021 15:37:37 +0530 Subject: [PATCH 23/28] fix: Skip translation of gender while creating it (#12260) --- frappe/desk/page/setup_wizard/install_fixtures.py | 4 ++-- frappe/utils/oauth.py | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/frappe/desk/page/setup_wizard/install_fixtures.py b/frappe/desk/page/setup_wizard/install_fixtures.py index 60e1f3242a..6d3aaee22b 100644 --- a/frappe/desk/page/setup_wizard/install_fixtures.py +++ b/frappe/desk/page/setup_wizard/install_fixtures.py @@ -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")) diff --git a/frappe/utils/oauth.py b/frappe/utils/oauth.py index d090aabffc..e7672cedb3 100644 --- a/frappe/utils/oauth.py +++ b/frappe/utils/oauth.py @@ -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"), From a4396a83d080e7b07d3fca67159025c981f64562 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bonicoli Date: Mon, 25 Jan 2021 11:06:02 +0100 Subject: [PATCH 24/28] ci: fix a regex used in Travis config Fix this line in Procfile: redis_# socketio: redis-server config/redis_socketio.conf --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2331217363..23fb525138 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 From b2fbcacfd489dbdae2458e7678f4a5e166516730 Mon Sep 17 00:00:00 2001 From: ollyboy Date: Tue, 26 Jan 2021 18:32:34 +0000 Subject: [PATCH 25/28] #12224 improve --- frappe/desk/calendar.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/desk/calendar.py b/frappe/desk/calendar.py index 127194671a..ed227e1dc3 100644 --- a/frappe/desk/calendar.py +++ b/frappe/desk/calendar.py @@ -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 = json.loads ( fields ) doc_meta = frappe.get_meta(doctype) for d in doc_meta.fields: @@ -44,8 +45,6 @@ def get_events(doctype, start, end, field_map, filters=None, fields=None): fields = [field_map.start, field_map.end, field_map.title, 'name'] if field_map.color: - if isinstance(fields, str): - fields = json.loads ( fields ) fields.append(field_map.color) start_date = "ifnull(%s, '0001-01-01 00:00:00')" % field_map.start From 36fcc1d55b365cad93af0f091555ee8e38088269 Mon Sep 17 00:00:00 2001 From: Prssanna Desai Date: Thu, 28 Jan 2021 13:58:01 +0530 Subject: [PATCH 26/28] fix: use frappe.parse_json --- frappe/desk/calendar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/calendar.py b/frappe/desk/calendar.py index ed227e1dc3..064d870092 100644 --- a/frappe/desk/calendar.py +++ b/frappe/desk/calendar.py @@ -29,7 +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 = json.loads ( fields ) + fields = frappe.parse_json(fields) doc_meta = frappe.get_meta(doctype) for d in doc_meta.fields: From bcd1ad446d1651d6d6a20e9f243a76c65107ffe6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 28 Jan 2021 14:58:49 +0530 Subject: [PATCH 27/28] feat: Module Profiles (#12148) Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- .../core/doctype/module_profile/__init__.py | 0 .../doctype/module_profile/module_profile.js | 19 ++++++ .../module_profile/module_profile.json | 60 +++++++++++++++++++ .../doctype/module_profile/module_profile.py | 12 ++++ .../module_profile/test_module_profile.py | 32 ++++++++++ frappe/core/doctype/user/user.js | 59 ++++++------------ frappe/core/doctype/user/user.json | 10 +++- frappe/core/doctype/user/user.py | 23 +++++-- frappe/public/build.json | 1 + frappe/public/js/frappe/module_editor.js | 39 ++++++++++++ 10 files changed, 209 insertions(+), 46 deletions(-) create mode 100644 frappe/core/doctype/module_profile/__init__.py create mode 100644 frappe/core/doctype/module_profile/module_profile.js create mode 100644 frappe/core/doctype/module_profile/module_profile.json create mode 100644 frappe/core/doctype/module_profile/module_profile.py create mode 100644 frappe/core/doctype/module_profile/test_module_profile.py create mode 100644 frappe/public/js/frappe/module_editor.js diff --git a/frappe/core/doctype/module_profile/__init__.py b/frappe/core/doctype/module_profile/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/module_profile/module_profile.js b/frappe/core/doctype/module_profile/module_profile.js new file mode 100644 index 0000000000..9c92042dda --- /dev/null +++ b/frappe/core/doctype/module_profile/module_profile.js @@ -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 = $('
') + .appendTo(frm.fields_dict.module_html.wrapper); + + frm.module_editor = new frappe.ModuleEditor(frm, module_area); + } + } + + if (frm.module_editor) { + frm.module_editor.refresh(); + } + } +}); diff --git a/frappe/core/doctype/module_profile/module_profile.json b/frappe/core/doctype/module_profile/module_profile.json new file mode 100644 index 0000000000..0e4e56962e --- /dev/null +++ b/frappe/core/doctype/module_profile/module_profile.json @@ -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 +} \ No newline at end of file diff --git a/frappe/core/doctype/module_profile/module_profile.py b/frappe/core/doctype/module_profile/module_profile.py new file mode 100644 index 0000000000..4f392353ac --- /dev/null +++ b/frappe/core/doctype/module_profile/module_profile.py @@ -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()]) diff --git a/frappe/core/doctype/module_profile/test_module_profile.py b/frappe/core/doctype/module_profile/test_module_profile.py new file mode 100644 index 0000000000..400053d22c --- /dev/null +++ b/frappe/core/doctype/module_profile/test_module_profile.py @@ -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') diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index b8e16bfe25..5493baf553 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -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 = $('
').appendTo(wrapper); - this.frm = frm; - this.make(); - }, - make: function() { - var me = this; - this.frm.doc.__onload.all_modules.forEach(function(m) { - $(repl('
\ -
', {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}); - } - }); - } -}); diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 7d91e8cfe0..53e05bb916 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -51,9 +51,9 @@ "send_me_a_copy", "allowed_in_mentions", "email_signature", - "email_inbox", "user_emails", "sb_allow_modules", + "module_profile", "modules_html", "block_modules", "home_settings", @@ -577,6 +577,12 @@ "fieldtype": "Password", "label": "API Secret", "read_only": 1 + }, + { + "fieldname": "module_profile", + "fieldtype": "Link", + "label": "Module Profile", + "options": "Module Profile" } ], "icon": "fa fa-user", @@ -684,4 +690,4 @@ "sort_order": "DESC", "title_field": "full_name", "track_changes": 1 -} +} \ No newline at end of file diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index da4026d8fd..dcca4f4a25 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -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...": @@ -85,9 +86,18 @@ class User(Document): def validate_roles(self): if self.role_profile_name: - role_profile = frappe.get_doc('Role Profile', self.role_profile_name) - self.set('roles', []) - self.append_roles(*[role.role for role in role_profile.roles]) + role_profile = frappe.get_doc('Role Profile', self.role_profile_name) + 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: @@ -108,7 +118,7 @@ class User(Document): ) if self.name not in ('Administrator', 'Guest') and not self.user_image: frappe.enqueue('frappe.core.doctype.user.user.update_gravatar', name=self.name, now=now) - + # Set user selected timezone if self.time_zone: frappe.defaults.set_default("time_zone", self.time_zone, self.name) @@ -1042,6 +1052,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) diff --git a/frappe/public/build.json b/frappe/public/build.json index d744da98d1..ebc96e6f6b 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -161,6 +161,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", diff --git a/frappe/public/js/frappe/module_editor.js b/frappe/public/js/frappe/module_editor.js new file mode 100644 index 0000000000..35037a3e62 --- /dev/null +++ b/frappe/public/js/frappe/module_editor.js @@ -0,0 +1,39 @@ +frappe.ModuleEditor = Class.extend({ + init: function(frm, wrapper) { + this.wrapper = $('
').appendTo(wrapper); + this.frm = frm; + this.make(); + }, + make: function() { + var me = this; + this.frm.doc.__onload.all_modules.forEach(function(m) { + $(repl('
\ +
', {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}); + } + }); + } +}); \ No newline at end of file From 6f774d6d0c7f1163600b07238bebced3a7f392b1 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Thu, 28 Jan 2021 17:14:47 +0530 Subject: [PATCH 28/28] fix: Check for fieldlevel permission for report query (#12163) Co-authored-by: Prssanna Desai --- frappe/desk/reportview.py | 33 ++++++++++++--- frappe/model/meta.py | 19 +++++++++ frappe/tests/test_db_query.py | 80 ++++++++++++++++++++++++++++++++++- 3 files changed, 124 insertions(+), 8 deletions(-) diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 9f5a5d84c8..36870d40bb 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -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 diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 88ed1a7e78..5dc7ca2d4d 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -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. diff --git a/frappe/tests/test_db_query.py b/frappe/tests/test_db_query.py index 4d2bef9479..836bb4bbf5 100644 --- a/frappe/tests/test_db_query.py +++ b/frappe/tests/test_db_query.py @@ -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 """