diff --git a/.github/frappe_linter/translation.py b/.github/frappe_linter/translation.py index d9fc98c76e..5d33355a1b 100644 --- a/.github/frappe_linter/translation.py +++ b/.github/frappe_linter/translation.py @@ -7,22 +7,28 @@ start_pattern = re.compile(r"_{1,2}\([\"']{1,3}") # skip first argument files = sys.argv[1:] -for _file in files: - if not _file.endswith(('.py', '.js')): - continue +files_to_scan = [_file for _file in files if _file.endswith(('.py', '.js'))] + +for _file in files_to_scan: with open(_file, 'r') as f: print(f'Checking: {_file}') - for num, line in enumerate(f, 1): - all_matches = start_pattern.finditer(line) - if all_matches: - for match in all_matches: - verify = pattern.search(line) - if not verify: - errors_encounter += 1 - print(f'A syntax error has been discovered at line number: {num}') - print(f'Syntax error occurred with: {line}') + file_lines = f.readlines() + for line_number, line in enumerate(file_lines, 1): + start_matches = start_pattern.search(line) + if start_matches: + match = pattern.search(line) + if not match and line.endswith(',\n'): + # concat remaining text to validate multiline pattern + line = "".join(file_lines[line_number - 1:]) + line = line[start_matches.start() + 1:] + match = pattern.match(line) + + if not match: + errors_encounter += 1 + print(f'\nTranslation syntax error at line number: {line_number + 1}\n{line.strip()[:100]}') + if errors_encounter > 0: - print('You can visit "https://frappeframework.com/docs/user/en/translations" to resolve this error.') - assert 1+1 == 3 + print('\nYou can visit "https://frappeframework.com/docs/user/en/translations" to resolve this error.') + sys.exit(1) else: - print('Good To Go!') + print('\nGood To Go!') diff --git a/cypress/integration/recorder.js b/cypress/integration/recorder.js index ed2a9c86ba..8a4aeddd0a 100644 --- a/cypress/integration/recorder.js +++ b/cypress/integration/recorder.js @@ -61,10 +61,10 @@ context('Recorder', () => { cy.visit('/desk#recorder'); - cy.get('.list-row-container span').contains('frappe.desk.reportview.get').click(); + cy.get('.list-row-container span').contains('/api/method/frappe').click(); cy.location('hash').should('contain', '#recorder/request/'); - cy.get('form').should('contain', 'frappe.desk.reportview.get'); + cy.get('form').should('contain', '/api/method/frappe'); cy.get('#page-recorder .primary-action').should('contain', 'Stop').click(); cy.get('#page-recorder .btn-secondary').should('contain', 'Clear').click(); diff --git a/frappe/__init__.py b/frappe/__init__.py index 46792e82a8..4b60181bd1 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1110,8 +1110,8 @@ def get_newargs(fn, kwargs): if (a in fnargs) or varkw: newargs[a] = kwargs.get(a) - if "flags" in newargs: - del newargs["flags"] + newargs.pop("ignore_permissions", None) + newargs.pop("flags", None) return newargs diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 721376016c..acd25eb166 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -133,6 +133,7 @@ def reset_perms(context): def execute(context, method, args=None, kwargs=None, profile=False): "Execute a function" for site in context.sites: + ret = "" try: frappe.init(site=site) frappe.connect() @@ -154,7 +155,10 @@ def execute(context, method, args=None, kwargs=None, profile=False): pr = cProfile.Profile() pr.enable() - ret = frappe.get_attr(method)(*args, **kwargs) + try: + ret = frappe.get_attr(method)(*args, **kwargs) + except Exception: + ret = frappe.safe_eval(method + "(*args, **kwargs)", eval_globals=globals(), eval_locals=locals()) if profile: pr.disable() diff --git a/frappe/core/doctype/activity_log/activity_log.json b/frappe/core/doctype/activity_log/activity_log.json index 580882968c..a1ee4dafdb 100644 --- a/frappe/core/doctype/activity_log/activity_log.json +++ b/frappe/core/doctype/activity_log/activity_log.json @@ -1,731 +1,184 @@ - { - "allow_copy": 0, - "allow_guest_to_view": 0, +{ + "actions": [], "allow_import": 1, - "allow_rename": 0, - "autoname": "", - "beta": 0, "creation": "2017-10-05 11:10:38.780133", - "custom": 0, "description": "Keep track of all update feeds", - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "subject", + "section_break_8", + "content", + "column_break_5", + "additional_info", + "communication_date", + "column_break_7", + "operation", + "status", + "reference_section", + "reference_doctype", + "reference_name", + "reference_owner", + "column_break_14", + "timeline_doctype", + "timeline_name", + "link_doctype", + "link_name", + "user", + "full_name" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "subject", "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, "in_list_view": 1, - "in_standard_filter": 0, "label": "Subject", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_8", - "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, - "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": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "content", "fieldtype": "Text Editor", - "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": "Message", - "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, "width": "400" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_5", - "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_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "additional_info", "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": "More Information", - "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": "More Information" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Now", "fieldname": "communication_date", "fieldtype": "Datetime", - "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": "Date", - "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": "Date" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_7", - "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "operation", "fieldtype": "Select", - "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": "Operation", - "length": 0, - "no_copy": 0, - "options": "\nLogin\nLogout", - "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": "\nLogin\nLogout" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "status", "fieldtype": "Select", - "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": "Status", - "length": 0, - "no_copy": 0, - "options": "\nSuccess\nFailed\nLinked\nClosed", - "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": "\nSuccess\nFailed\nLinked\nClosed" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "reference_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": "Reference", - "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": "Reference" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "reference_doctype", "fieldtype": "Link", - "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": "Reference Document Type", - "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" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "reference_name", "fieldtype": "Dynamic Link", - "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": "Reference Name", - "length": 0, - "no_copy": 0, - "options": "reference_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": "reference_doctype" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "reference_name.owner", "fieldname": "reference_owner", "fieldtype": "Read Only", - "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": "Reference Owner", - "length": 0, - "no_copy": 0, - "options": "", - "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": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_14", - "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "timeline_doctype", "fieldtype": "Link", - "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": "Timeline DocType", - "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" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "timeline_name", "fieldtype": "Dynamic Link", - "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": "Timeline Name", - "length": 0, - "no_copy": 0, - "options": "timeline_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": "timeline_doctype" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "link_doctype", "fieldtype": "Link", - "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": "Link DocType", - "length": 0, - "no_copy": 0, "options": "DocType", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "link_name", "fieldtype": "Dynamic Link", - "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": "Link Name", - "length": 0, - "no_copy": 0, "options": "link_doctype", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "__user", "fieldname": "user", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "User", - "length": 0, - "no_copy": 0, "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "full_name", "fieldtype": "Data", - "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": "Full Name", - "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": "Full Name" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-comment", - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-09-05 14:22:27.664645", + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-08-28 11:43:57.504565", "modified_by": "Administrator", "module": "Core", "name": "Activity Log", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 + "share": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 1, + "if_owner": 1, "print": 1, "read": 1, "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 1, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, "role": "All", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "share": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, "search_fields": "subject", - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", "title_field": "subject", diff --git a/frappe/core/doctype/activity_log/activity_log.py b/frappe/core/doctype/activity_log/activity_log.py index 8b7941c086..27a2892ca8 100644 --- a/frappe/core/doctype/activity_log/activity_log.py +++ b/frappe/core/doctype/activity_log/activity_log.py @@ -25,9 +25,6 @@ class ActivityLog(Document): if self.reference_doctype and self.reference_name: self.status = "Linked" - def on_trash(self): # pylint: disable=no-self-use - frappe.throw(_("Sorry! You cannot delete auto-generated comments")) - def on_doctype_update(): """Add indexes in `tabActivity Log`""" frappe.db.add_index("Activity Log", ["reference_doctype", "reference_name"]) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index a54df756dd..b8bed89a4d 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -278,25 +278,26 @@ class File(Document): base_url = os.path.dirname(self.file_url) files = [] - with zipfile.ZipFile(zip_path) as zf: - zf.extractall(os.path.dirname(zip_path)) - for info in zf.infolist(): - if not info.filename.startswith('__MACOSX'): - file_url = file_url = base_url + '/' + info.filename - file_name = frappe.db.get_value('File', dict(file_url=file_url)) - if file_name: - file_doc = frappe.get_doc('File', file_name) - else: - file_doc = frappe.new_doc("File") - file_doc.file_name = info.filename - file_doc.file_size = info.file_size - file_doc.folder = self.folder - file_doc.is_private = self.is_private - file_doc.file_url = file_url - file_doc.attached_to_doctype = self.attached_to_doctype - file_doc.attached_to_name = self.attached_to_name - file_doc.save() - files.append(file_doc) + with zipfile.ZipFile(zip_path) as z: + for file in z.filelist: + if file.is_dir() or file.filename.startswith('__MACOSX/'): + # skip directories and macos hidden directory + continue + + filename = os.path.basename(file.filename) + if filename.startswith('.'): + # skip hidden files + continue + + file_doc = frappe.new_doc('File') + file_doc.content = z.read(file.filename) + file_doc.file_name = filename + file_doc.folder = self.folder + file_doc.is_private = self.is_private + file_doc.attached_to_doctype = self.attached_to_doctype + file_doc.attached_to_name = self.attached_to_name + file_doc.save() + files.append(file_doc) frappe.delete_doc('File', self.name) return files @@ -941,7 +942,7 @@ def attach_files_to_document(doc, event): # we dont want the update to fail if file cannot be attached for some reason try: value = doc.get(df.fieldname) - if not value.startswith(("/files", "/private/files")): + if not (value or '').startswith(("/files", "/private/files")): return if frappe.db.exists("File", { diff --git a/frappe/custom/doctype/package_publish_tool/package_publish_tool.py b/frappe/custom/doctype/package_publish_tool/package_publish_tool.py index a01dd0ba47..b73f93a628 100644 --- a/frappe/custom/doctype/package_publish_tool/package_publish_tool.py +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.py @@ -100,6 +100,7 @@ def export_package(): @frappe.whitelist() def import_package(package=None): """Import package from JSON.""" + frappe.only_for("System Manager") if isinstance(package, string_types): package = json.loads(package) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 94a38a5304..72c4519120 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -40,7 +40,7 @@ class Workspace: self.doc = self.get_page_for_user() - if self.doc.module not in self.allowed_modules: + if self.doc.module and self.doc.module not in self.allowed_modules: raise frappe.PermissionError self.can_read = self.get_cached('user_perm_can_read', self.get_can_read_items) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 4ea61ec6a9..7e2d952928 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -60,11 +60,11 @@ def has_permission(doc, ptype, user): if doc.chart_type == 'Report': - allowed_reports = tuple([key.encode('UTF8') for key in get_allowed_reports()]) + allowed_reports = [key if type(key) == str else key.encode('UTF8') for key in get_allowed_reports()] if doc.report_name in allowed_reports: return True else: - allowed_doctypes = tuple(frappe.permissions.get_doctypes_with_read()) + allowed_doctypes = [frappe.permissions.get_doctypes_with_read()] if doc.document_type in allowed_doctypes: return True diff --git a/frappe/desk/doctype/desk_page/desk_page.py b/frappe/desk/doctype/desk_page/desk_page.py index cc2db53481..e92844ac0b 100644 --- a/frappe/desk/doctype/desk_page/desk_page.py +++ b/frappe/desk/doctype/desk_page/desk_page.py @@ -38,7 +38,7 @@ class DeskPage(Document): pages = frappe.get_all("Desk Page", fields=["name", "module"], filters=filters, as_list=1) - return { page[1]: page[0] for page in pages } + return { page[1]: page[0] for page in pages if page[1] } def disable_saving_as_standard(): return frappe.flags.in_install or \ diff --git a/frappe/event_streaming/doctype/event_consumer/event_consumer.json b/frappe/event_streaming/doctype/event_consumer/event_consumer.json index 85970dc277..42b47ce949 100644 --- a/frappe/event_streaming/doctype/event_consumer/event_consumer.json +++ b/frappe/event_streaming/doctype/event_consumer/event_consumer.json @@ -21,6 +21,7 @@ "fieldtype": "Data", "in_list_view": 1, "label": "Callback URL", + "read_only": 1, "reqd": 1, "unique": 1 }, @@ -28,19 +29,20 @@ "fieldname": "api_key", "fieldtype": "Data", "label": "API Key", - "read_only": 1 + "reqd": 1 }, { "fieldname": "api_secret", "fieldtype": "Password", "label": "API Secret", - "read_only": 1 + "reqd": 1 }, { "fieldname": "user", "fieldtype": "Link", "label": "Event Subscriber", "options": "User", + "read_only": 1, "reqd": 1 }, { @@ -69,7 +71,7 @@ ], "in_create": 1, "links": [], - "modified": "2020-09-06 15:42:00.746493", + "modified": "2020-09-08 16:42:39.828085", "modified_by": "Administrator", "module": "Event Streaming", "name": "Event Consumer", diff --git a/frappe/event_streaming/doctype/event_consumer/event_consumer.py b/frappe/event_streaming/doctype/event_consumer/event_consumer.py index 2e10c71d0d..1505c3a05d 100644 --- a/frappe/event_streaming/doctype/event_consumer/event_consumer.py +++ b/frappe/event_streaming/doctype/event_consumer/event_consumer.py @@ -7,6 +7,7 @@ import frappe import json import requests import os +from frappe import _ from frappe.model.document import Document from frappe.frappeclient import FrappeClient from frappe.utils.data import get_url @@ -23,6 +24,10 @@ class EventConsumer(Document): def on_update(self): if not self.incoming_change: + doc_before_save = self.get_doc_before_save() + if doc_before_save.api_key != self.api_key or doc_before_save.api_secret != self.api_secret: + return + self.update_consumer_status() else: frappe.db.set_value(self.doctype, self.name, 'incoming_change', 0) @@ -58,17 +63,26 @@ class EventConsumer(Document): return 'offline' return 'online' - -@frappe.whitelist(allow_guest=True) +@frappe.whitelist() def register_consumer(data): """create an event consumer document for registering a consumer""" data = json.loads(data) # to ensure that consumer is created only once if frappe.db.exists('Event Consumer', data['event_consumer']): return None + + user = data['user'] + if not frappe.db.exists('User', user): + frappe.throw(_('User {0} not found on the producer site').format(user)) + + if "System Manager" not in frappe.get_roles(user): + frappe.throw(_("Event Subscriber has to be a System Manager.")) + consumer = frappe.new_doc('Event Consumer') consumer.callback_url = data['event_consumer'] consumer.user = data['user'] + consumer.api_key = data['api_key'] + consumer.api_secret = data['api_secret'] consumer.incoming_change = True consumer_doctypes = json.loads(data['consumer_doctypes']) @@ -78,18 +92,13 @@ def register_consumer(data): 'status': 'Pending' }) - api_key = frappe.generate_hash(length=10) - api_secret = frappe.generate_hash(length=10) - consumer.api_key = api_key - consumer.api_secret = api_secret - consumer.insert(ignore_permissions=True) - frappe.db.commit() + consumer.insert() # consumer's 'last_update' field should point to the latest update # in producer's update log when subscribing # so that, updates after subscribing are consumed and not the old ones. last_update = str(get_last_update()) - return json.dumps({'api_key': api_key, 'api_secret': api_secret, 'last_update': last_update}) + return json.dumps({'last_update': last_update}) def get_consumer_site(consumer_url): @@ -98,8 +107,7 @@ def get_consumer_site(consumer_url): consumer_site = FrappeClient( url=consumer_url, api_key=consumer_doc.api_key, - api_secret=consumer_doc.get_password('api_secret'), - frappe_authorization_source='Event Producer' + api_secret=consumer_doc.get_password('api_secret') ) return consumer_site diff --git a/frappe/event_streaming/doctype/event_producer/event_producer.json b/frappe/event_streaming/doctype/event_producer/event_producer.json index 8eba1924f5..8fafdc3bb2 100644 --- a/frappe/event_streaming/doctype/event_producer/event_producer.json +++ b/frappe/event_streaming/doctype/event_producer/event_producer.json @@ -32,23 +32,26 @@ "read_only": 1 }, { + "description": "API Key of the user(Event Subscriber) on the producer site", "fieldname": "api_key", "fieldtype": "Data", "label": "API Key", - "read_only": 1 + "reqd": 1 }, { + "description": "API Secret of the user(Event Subscriber) on the producer site", "fieldname": "api_secret", "fieldtype": "Password", "label": "API Secret", - "read_only": 1 + "reqd": 1 }, { "fieldname": "user", "fieldtype": "Link", "label": "Event Subscriber", "options": "User", - "reqd": 1 + "reqd": 1, + "set_only_once": 1 }, { "fieldname": "column_break_6", @@ -74,7 +77,7 @@ } ], "links": [], - "modified": "2019-12-26 13:04:11.438349", + "modified": "2020-09-08 18:50:57.687979", "modified_by": "Administrator", "module": "Event Streaming", "name": "Event Producer", diff --git a/frappe/event_streaming/doctype/event_producer/event_producer.py b/frappe/event_streaming/doctype/event_producer/event_producer.py index 555b71f851..b0ec998ab9 100644 --- a/frappe/event_streaming/doctype/event_producer/event_producer.py +++ b/frappe/event_streaming/doctype/event_producer/event_producer.py @@ -12,7 +12,8 @@ from frappe import _ from frappe.model.document import Document from frappe.frappeclient import FrappeClient from frappe.utils.background_jobs import get_jobs -from frappe.utils.data import get_url +from frappe.utils.data import get_url, get_link_to_form +from frappe.utils.password import get_decrypted_password from frappe.custom.doctype.custom_field.custom_field import create_custom_field from frappe.integrations.oauth2 import validate_url @@ -20,19 +21,35 @@ from frappe.integrations.oauth2 import validate_url class EventProducer(Document): def before_insert(self): self.check_url() + self.validate_event_subscriber() self.incoming_change = True self.create_event_consumer() self.create_custom_fields() def validate(self): + self.validate_event_subscriber() if frappe.flags.in_test: for entry in self.producer_doctypes: entry.status = 'Approved' + def validate_event_subscriber(self): + if not frappe.db.get_value('User', self.user, 'api_key'): + frappe.throw(_('Please generate keys for the Event Subscriber User {0} first.').format( + frappe.bold(get_link_to_form('User', self.user)) + )) + def on_update(self): if not self.incoming_change: - self.update_event_consumer() - self.create_custom_fields() + if frappe.db.exists('Event Producer', self.name): + if not self.api_key or not self.api_secret: + frappe.throw(_('Please set API Key and Secret on the producer and consumer sites first.')) + else: + doc_before_save = self.get_doc_before_save() + if doc_before_save.api_key != self.api_key or doc_before_save.api_secret != self.api_secret: + return + + self.update_event_consumer() + self.create_custom_fields() else: # when producer doc is updated it updates the consumer doc, set flag to avoid deadlock self.db_set('incoming_change', 0) @@ -50,15 +67,18 @@ class EventProducer(Document): def create_event_consumer(self): """register event consumer on the producer site""" if self.is_producer_online(): - producer_site = FrappeClient(self.producer_url, verify=False) + producer_site = FrappeClient( + url=self.producer_url, + api_key=self.api_key, + api_secret=self.get_password('api_secret') + ) + response = producer_site.post_api( 'frappe.event_streaming.doctype.event_consumer.event_consumer.register_consumer', params={'data': json.dumps(self.get_request_data())} ) if response: response = json.loads(response) - self.api_key = response['api_key'] - self.api_secret = response['api_secret'] self.last_update = response['last_update'] else: frappe.throw(_('Failed to create an Event Consumer or an Event Consumer for the current site is already registered.')) @@ -72,10 +92,14 @@ class EventProducer(Document): else: consumer_doctypes.append(entry.ref_doctype) + user_key = frappe.db.get_value('User', self.user, 'api_key') + user_secret = get_decrypted_password('User', self.user, 'api_secret') return { 'event_consumer': get_url(), 'consumer_doctypes': json.dumps(consumer_doctypes), - 'user': self.user + 'user': self.user, + 'api_key': user_key, + 'api_secret': user_secret } def create_custom_fields(self): @@ -131,8 +155,7 @@ def get_producer_site(producer_url): producer_site = FrappeClient( url=producer_url, api_key=producer_doc.api_key, - api_secret=producer_doc.get_password('api_secret'), - frappe_authorization_source='Event Consumer' + api_secret=producer_doc.get_password('api_secret') ) return producer_site diff --git a/frappe/event_streaming/doctype/event_producer/test_event_producer.py b/frappe/event_streaming/doctype/event_producer/test_event_producer.py index 4fea55eb39..fa2461a9d8 100644 --- a/frappe/event_streaming/doctype/event_producer/test_event_producer.py +++ b/frappe/event_streaming/doctype/event_producer/test_event_producer.py @@ -8,6 +8,7 @@ import unittest import json from frappe.frappeclient import FrappeClient from frappe.event_streaming.doctype.event_producer.event_producer import pull_from_node +from frappe.core.doctype.user.user import generate_keys producer_url = 'http://test_site_producer:8000' @@ -166,16 +167,6 @@ class TestEventProducer(unittest.TestCase): def pull_producer_data(self): pull_from_node(producer_url) - def get_remote_site(self): - producer_doc = frappe.get_doc('Event Producer', producer_url) - producer_site = FrappeClient( - url=producer_doc.producer_url, - api_key=producer_doc.api_key, - api_secret=producer_doc.get_password('api_secret'), - frappe_authorization_source='Event Consumer' - ) - return producer_site - def test_mapping(self): producer = get_remote_site() event_producer = frappe.get_doc('Event Producer', producer_url, for_update=True) @@ -298,6 +289,20 @@ def create_event_producer(producer_url): event_producer.save() return + generate_keys('Administrator') + + producer_site = connect() + + response = producer_site.post_api( + 'frappe.core.doctype.user.user.generate_keys', + params={'user': 'Administrator'} + ) + + api_secret = response.get('api_secret') + + response = producer_site.get_value('User', 'api_key', {'name': 'Administrator'}) + api_key = response.get('api_key') + event_producer = frappe.new_doc('Event Producer') event_producer.producer_doctypes = [] event_producer.producer_url = producer_url @@ -310,6 +315,8 @@ def create_event_producer(producer_url): 'use_same_name': 1 }) event_producer.user = 'Administrator' + event_producer.api_key = api_key + event_producer.api_secret = api_secret event_producer.save() def reset_configuration(producer_url): @@ -331,9 +338,9 @@ def get_remote_site(): producer_doc = frappe.get_doc('Event Producer', producer_url) producer_site = FrappeClient( url=producer_doc.producer_url, - api_key=producer_doc.api_key, - api_secret=producer_doc.get_password('api_secret'), - frappe_authorization_source='Event Consumer' + username='Administrator', + password='admin', + verify=False ) return producer_site @@ -341,4 +348,17 @@ def unsubscribe_doctypes(producer_url): event_producer = frappe.get_doc('Event Producer', producer_url) for entry in event_producer.producer_doctypes: entry.unsubscribe = 1 - event_producer.save() \ No newline at end of file + event_producer.save() + +def connect(): + def _connect(): + return FrappeClient( + url=producer_url, + username='Administrator', + password='admin', + verify=False + ) + try: + return _connect() + except Exception: + return _connect() diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 1e3f127b99..7a2129e76e 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -25,7 +25,6 @@ def update_document_title(doctype, docname, title_field=None, old_title=None, ne return docname -@frappe.whitelist() def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=False, ignore_if_exists=False, show_alert=True): """ Renames a doc(dt, old) to doc(dt, new) and diff --git a/frappe/patches.txt b/frappe/patches.txt index 1279cf655f..0c871664a3 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -308,3 +308,4 @@ frappe.patches.v13_0.remove_duplicate_navbar_items frappe.patches.v13_0.set_route_for_blog_category frappe.patches.v13_0.enable_custom_script frappe.patches.v13_0.update_newsletter_content_type +frappe.patches.v13_0.delete_event_producer_and_consumer_keys \ No newline at end of file diff --git a/frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py b/frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py new file mode 100644 index 0000000000..1eba5871c2 --- /dev/null +++ b/frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py @@ -0,0 +1,11 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + if frappe.db.exists("DocType", "Event Producer"): + frappe.db.sql("""UPDATE `tabEvent Producer` SET api_key='', api_secret=''""") + if frappe.db.exists("DocType", "Event Consumer"): + frappe.db.sql("""UPDATE `tabEvent Consumer` SET api_key='', api_secret=''""") diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 1bec65e460..1514fcb070 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1259,7 +1259,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { return; } - this.export_dialog = frappe.prompt([ + let export_dialog_fields = [ { label: __('Select File Format'), fieldname: 'file_format', @@ -1267,13 +1267,18 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { options: ['Excel', 'CSV'], default: 'Excel', reqd: 1 - }, - { + } + ]; + + if (this.tree_report) { + export_dialog_fields.push({ label: __("Include indentation"), fieldname: "include_indentation", fieldtype: "Check", - } - ], ({ file_format, include_indentation }) => { + }); + } + + this.export_dialog = frappe.prompt(export_dialog_fields, ({ file_format, include_indentation }) => { this.make_access_log('Export', file_format); if (file_format === 'CSV') { const column_row = this.columns.reduce((acc, col) => { diff --git a/frappe/public/less/report.less b/frappe/public/less/report.less index f519640f7c..0a8d37b9ba 100644 --- a/frappe/public/less/report.less +++ b/frappe/public/less/report.less @@ -72,7 +72,7 @@ margin-bottom: 10px; } -.report-wrapper { +.report-wrapper, .datatable-wrapper { overflow: auto; } diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py new file mode 100644 index 0000000000..82c0cdce5c --- /dev/null +++ b/frappe/tests/test_commands.py @@ -0,0 +1,46 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors + +# imports - standard imports +import shlex +import subprocess +import unittest + +# imports - module imports +import frappe + + +def clean(value): + if isinstance(value, (bytes, str)): + value = value.decode().strip() + return value + + +class BaseTestCommands: + def execute(self, command): + command = command.format(**{"site": frappe.local.site}) + command = shlex.split(command) + self._proc = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + self.stdout = clean(self._proc.stdout) + self.stderr = clean(self._proc.stderr) + self.returncode = clean(self._proc.returncode) + + +class TestCommands(BaseTestCommands, unittest.TestCase): + def test_execute(self): + # test 1: execute a command expecting a numeric output + self.execute("bench --site {site} execute frappe.db.get_database_size") + self.assertEquals(self.returncode, 0) + self.assertIsInstance(float(self.stdout), float) + + # test 2: execute a command expecting an errored output as local won't exist + self.execute("bench --site {site} execute frappe.local.site") + self.assertEquals(self.returncode, 1) + self.assertIsNotNone(self.stderr) + + # test 3: execute a command with kwargs + # Note: + # terminal command has been escaped to avoid .format string replacement + # The returned value has quotes which have been trimmed for the test + self.execute("""bench --site {site} execute frappe.bold --kwargs '{{"text": "DocType"}}'""") + self.assertEquals(self.returncode, 0) + self.assertEquals(self.stdout[1:-1], frappe.bold(text='DocType')) diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 1da220dc30..557a2fd647 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -135,7 +135,8 @@ def validate_email_address(email_str, throw=False): if not _valid: if throw: - frappe.throw(frappe._("{0} is not a valid Email Address").format(e), + invalid_email = frappe.utils.escape_html(e) + frappe.throw(frappe._("{0} is not a valid Email Address").format(invalid_email), frappe.InvalidEmailAddressError) return None else: diff --git a/frappe/website/doctype/blog_post/blog_post.js b/frappe/website/doctype/blog_post/blog_post.js index bfff947948..97916b6fc6 100644 --- a/frappe/website/doctype/blog_post/blog_post.js +++ b/frappe/website/doctype/blog_post/blog_post.js @@ -33,9 +33,9 @@ frappe.ui.form.on('Blog Post', { }); function generate_google_search_preview(frm) { - if (!frm.doc.title) return; + if (!(frm.doc.meta_title || frm.doc.title)) return; let google_preview = frm.get_field("google_preview"); - let seo_title = (frm.doc.title).slice(0, 60); + let seo_title = (frm.doc.meta_title || frm.doc.title).slice(0, 60); let seo_description = (frm.doc.meta_description || frm.doc.blog_intro || "").slice(0, 160); let date = frm.doc.published_on ? new frappe.datetime.datetime(frm.doc.published_on).moment.format('ll') + ' - ' : ''; let route_array = frm.doc.route ? frm.doc.route.split('/') : []; diff --git a/frappe/website/doctype/blog_post/blog_post.json b/frappe/website/doctype/blog_post/blog_post.json index 25bca28e85..48e9a18e71 100644 --- a/frappe/website/doctype/blog_post/blog_post.json +++ b/frappe/website/doctype/blog_post/blog_post.json @@ -26,6 +26,7 @@ "content_html", "email_sent", "meta_tags", + "meta_title", "meta_description", "column_break_18", "meta_image", @@ -110,7 +111,6 @@ "depends_on": "eval:doc.content_type === 'Markdown'", "fieldname": "content_md", "fieldtype": "Markdown Editor", - "ignore_xss_filter": 1, "label": "Content (Markdown)" }, { @@ -185,6 +185,12 @@ "fieldtype": "Check", "hidden": 1, "label": "Hide CTA" + }, + { + "fieldname": "meta_title", + "fieldtype": "Data", + "label": "Meta Title", + "length": 60 } ], "has_web_view": 1, @@ -194,7 +200,7 @@ "is_published_field": "published", "links": [], "max_attachments": 5, - "modified": "2020-08-31 16:55:03.687862", + "modified": "2020-08-31 21:01:51.100349", "modified_by": "Administrator", "module": "Website", "name": "Blog Post", diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index 79d0754af4..a7bc81f08c 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -36,6 +36,11 @@ class BlogPost(WebsiteGenerator): if self.blog_intro: self.blog_intro = self.blog_intro[:200] + if not self.meta_title: + self.meta_title = self.title[:60] + else: + self.meta_title = self.meta_title[:60] + if not self.meta_description: self.meta_description = self.blog_intro[:140] else: @@ -88,7 +93,7 @@ class BlogPost(WebsiteGenerator): context.description = self.meta_description or self.blog_intro or strip_html_tags(context.content[:140]) context.metatags = { - "name": self.title, + "name": self.meta_title, "description": context.description, }