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 36a8b48ecd..4b60181bd1 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -182,6 +182,7 @@ def init(site, sites_path=None, new_site=False): local.meta_cache = {} local.form_dict = _dict() local.session = _dict() + local.dev_server = os.environ.get('DEV_SERVER', False) setup_module_map() @@ -1109,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/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py index c09e347e71..a9a358ce5f 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py @@ -403,6 +403,7 @@ def update_reference(docname, reference): @frappe.whitelist() def generate_message_preview(reference_dt, reference_doc, message=None, subject=None): + frappe.has_permission("Auto Repeat", "write", throw=True) doc = frappe.get_doc(reference_dt, reference_doc) subject_preview = _("Please add a subject to your email") msg_preview = frappe.render_template(message, {'doc': doc}) 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/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index aab59a5a0a..e420d3b775 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -163,6 +163,7 @@ }, { "default": "0", + "depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);", "fieldname": "in_preview", "fieldtype": "Check", "label": "In Preview" @@ -475,9 +476,10 @@ } ], "idx": 1, + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-02-06 09:06:25.224413", + "modified": "2020-08-28 11:28:21.252853", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/core/doctype/doctype_action/doctype_action.json b/frappe/core/doctype/doctype_action/doctype_action.json index 7a1b845af3..0f9da802eb 100644 --- a/frappe/core/doctype/doctype_action/doctype_action.json +++ b/frappe/core/doctype/doctype_action/doctype_action.json @@ -8,7 +8,8 @@ "label", "action_type", "action", - "group" + "group", + "hidden" ], "fields": [ { @@ -31,20 +32,28 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Action Type", - "options": "Server Action", + "options": "Server Action\nRoute", "reqd": 1 }, { "columns": 4, "fieldname": "action", - "fieldtype": "Data", + "fieldtype": "Small Text", "in_list_view": 1, - "label": "Action", + "label": "Action / Route", "reqd": 1 + }, + { + "default": "0", + "fieldname": "hidden", + "fieldtype": "Check", + "label": "Hidden" } ], + "index_web_pages_for_search": 1, "istable": 1, - "modified": "2019-09-24 09:11:39.860100", + "links": [], + "modified": "2020-08-21 14:44:03.845315", "modified_by": "Administrator", "module": "Core", "name": "DocType Action", diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 316915e43a..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 @@ -359,6 +360,9 @@ class File(Document): """write file to disk with a random name (to compare)""" file_path = get_files_path(is_private=self.is_private) + if os.path.sep in self.file_name: + frappe.throw(_('File name cannot have {0}').format(os.path.sep)) + # create directory (if not exists) frappe.create_folder(file_path) # write the file @@ -938,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/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py index 765ae5fe93..fa854f579e 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -109,12 +109,14 @@ class ScheduledJobType(Document): def on_trash(self): frappe.db.sql('delete from `tabScheduled Job Log` where scheduled_job_type=%s', self.name) + @frappe.whitelist() def execute_event(doc): frappe.only_for('System Manager') doc = json.loads(doc) frappe.get_doc('Scheduled Job Type', doc.get('name')).enqueue() + def run_scheduled_job(job_type): '''This is a wrapper function that runs a hooks.scheduler_events method''' try: @@ -122,44 +124,62 @@ def run_scheduled_job(job_type): except Exception: print(frappe.get_traceback()) -def sync_jobs(): - frappe.reload_doc('core', 'doctype', 'scheduled_job_type') - all_events = [] - scheduler_events = frappe.get_hooks("scheduler_events") - insert_events(all_events, scheduler_events) - clear_events(all_events, scheduler_events) -def insert_events(all_events, scheduler_events): +def sync_jobs(hooks=None): + frappe.reload_doc("core", "doctype", "scheduled_job_type") + scheduler_events = hooks or frappe.get_hooks("scheduler_events") + all_events = insert_events(scheduler_events) + clear_events(all_events) + + +def insert_events(scheduler_events): + cron_jobs, event_jobs = [], [] for event_type in scheduler_events: events = scheduler_events.get(event_type) if isinstance(events, dict): - insert_cron_event(events, all_events) + cron_jobs += insert_cron_jobs(events) else: # hourly, daily etc - insert_event_list(events, event_type, all_events) + event_jobs += insert_event_jobs(events, event_type) + return cron_jobs + event_jobs -def insert_cron_event(events, all_events): + +def insert_cron_jobs(events): + cron_jobs = [] for cron_format in events: for event in events.get(cron_format): - all_events.append(event) - insert_single_event('Cron', event, cron_format) + cron_jobs.append(event) + insert_single_event("Cron", event, cron_format) + return cron_jobs -def insert_event_list(events, event_type, all_events): + +def insert_event_jobs(events, event_type): + event_jobs = [] for event in events: - all_events.append(event) + event_jobs.append(event) frequency = event_type.replace('_', ' ').title() insert_single_event(frequency, event) + return event_jobs -def insert_single_event(frequency, event, cron_format = None): - if not frappe.db.exists('Scheduled Job Type', dict(method=event)): - frappe.get_doc(dict( - doctype = 'Scheduled Job Type', - method = event, - cron_format = cron_format, - frequency = frequency - )).insert() -def clear_events(all_events, scheduler_events): - for event in frappe.get_all('Scheduled Job Type', ('name', 'method')): +def insert_single_event(frequency, event, cron_format=None): + cron_expr = {"cron_format": cron_format} if cron_format else {} + doc = frappe.get_doc({ + "doctype": "Scheduled Job Type", + "method": event, + "cron_format": cron_format, + "frequency": frequency + }) + + if not frappe.db.exists("Scheduled Job Type", {"method": event, "frequency": frequency, **cron_expr }): + try: + doc.insert() + except frappe.DuplicateEntryError: + doc.delete() + doc.insert() + + +def clear_events(all_events): + for event in frappe.get_all("Scheduled Job Type", ("name", "method")): if event.method not in all_events: - frappe.delete_doc('Scheduled Job Type', event.name) + frappe.delete_doc("Scheduled Job Type", event.name) diff --git a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py index ec1e70ad6a..e7db6f9045 100644 --- a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py @@ -11,11 +11,10 @@ from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs class TestScheduledJobType(unittest.TestCase): def setUp(self): - if not frappe.get_all('Scheduled Job Type', limit=1): - frappe.db.rollback() - frappe.db.sql('truncate `tabScheduled Job Type`') - sync_jobs() - frappe.db.commit() + frappe.db.rollback() + frappe.db.sql('truncate `tabScheduled Job Type`') + sync_jobs() + frappe.db.commit() def test_sync_jobs(self): all_job = frappe.get_doc('Scheduled Job Type', @@ -32,6 +31,12 @@ class TestScheduledJobType(unittest.TestCase): self.assertEqual(cron_job.frequency, 'Cron') self.assertEqual(cron_job.cron_format, '0/15 * * * *') + # check if jobs are synced after change in hooks + updated_scheduler_events = { "hourly": ["frappe.email.queue.flush"] } + sync_jobs(updated_scheduler_events) + updated_scheduled_job = frappe.get_doc("Scheduled Job Type", {"method": "frappe.email.queue.flush"}) + self.assertEqual(updated_scheduled_job.frequency, "Hourly") + def test_daily_job(self): job = frappe.get_doc('Scheduled Job Type', dict(method = 'frappe.email.queue.clear_outbox')) job.db_set('last_execution', '2019-01-01 00:00:00') diff --git a/frappe/core/doctype/server_script/server_script.json b/frappe/core/doctype/server_script/server_script.json index 3ed4076430..cc3995ad1d 100644 --- a/frappe/core/doctype/server_script/server_script.json +++ b/frappe/core/doctype/server_script/server_script.json @@ -7,12 +7,12 @@ "engine": "InnoDB", "field_order": [ "script_type", - "disabled", - "column_break_3", "reference_doctype", "doctype_event", "api_method", "allow_guest", + "column_break_3", + "disabled", "section_break_8", "script", "help_section", @@ -85,8 +85,9 @@ "fieldtype": "HTML" } ], + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-08-07 13:13:02.483963", + "modified": "2020-08-24 16:44:41.060350", "modified_by": "Administrator", "module": "Core", "name": "Server Script", diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index 539ae8eb01..839b784651 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -24,7 +24,8 @@ class ServerScript(Document): # validate if guest is allowed if frappe.session.user == 'Guest' and not self.allow_guest: raise frappe.PermissionError - safe_exec(self.script) + _globals, _locals = safe_exec(self.script) + return _globals.frappe.flags # output can be stored in flags else: # wrong report type! raise frappe.DoesNotExistError diff --git a/frappe/core/doctype/server_script/test_server_script.py b/frappe/core/doctype/server_script/test_server_script.py index 5c12858e8a..3356e584af 100644 --- a/frappe/core/doctype/server_script/test_server_script.py +++ b/frappe/core/doctype/server_script/test_server_script.py @@ -36,6 +36,15 @@ if "validate" in doc.description: allow_guest = 1, script = ''' frappe.response['message'] = 'hello' +''' + ), + dict( + name='test_return_value', + script_type = 'API', + api_method = 'test_return_value', + allow_guest = 1, + script = ''' +frappe.flags = 'hello' ''' ) ] @@ -73,3 +82,6 @@ class TestServerScript(unittest.TestCase): response = requests.post(get_site_url(frappe.local.site) + "/api/method/test_server_script") self.assertEqual(response.status_code, 200) self.assertEqual("hello", response.json()["message"]) + + def test_api_return(self): + self.assertEqual(frappe.get_doc('Server Script', 'test_return_value').execute_method(), 'hello') diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 9e6781ba64..2073f41fdd 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -356,7 +356,7 @@ "depends_on": "enabled", "fieldname": "email_settings", "fieldtype": "Section Break", - "label": "Email Settings" + "label": "Email" }, { "default": "1", @@ -382,12 +382,6 @@ "label": "Email Signature", "no_copy": 1 }, - { - "collapsible": 1, - "fieldname": "email_inbox", - "fieldtype": "Section Break", - "label": "Email Inbox" - }, { "fieldname": "user_emails", "fieldtype": "Table", @@ -651,7 +645,7 @@ } ], "max_attachments": 5, - "modified": "2020-08-06 19:48:49.677800", + "modified": "2020-08-26 19:48:49.677800", "modified_by": "Administrator", "module": "Core", "name": "User", @@ -685,4 +679,4 @@ "sort_order": "DESC", "title_field": "full_name", "track_changes": 1 -} \ No newline at end of file +} diff --git a/frappe/core/doctype/user_permission/test_user_permission.py b/frappe/core/doctype/user_permission/test_user_permission.py index 5ccc8752cf..82dd2ab27e 100644 --- a/frappe/core/doctype/user_permission/test_user_permission.py +++ b/frappe/core/doctype/user_permission/test_user_permission.py @@ -26,8 +26,7 @@ class TestUserPermission(unittest.TestCase): user = create_user('test_user_perm1@example.com', 'Website Manager') for category in ['general', 'public']: if not frappe.db.exists('Blog Category', category): - frappe.get_doc({'doctype': 'Blog Category', - 'category_name': category, 'title': category}).insert() + frappe.get_doc({'doctype': 'Blog Category', 'title': category}).insert() param = get_params(user, 'Blog Category', 'general', is_default=1) add_user_permissions(param) diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 6fa7b29161..3946568bb6 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -58,382 +58,384 @@ ], "fields": [ { - "bold": 1, - "fieldname": "dt", - "fieldtype": "Link", - "in_filter": 1, - "in_list_view": 1, - "label": "Document", - "oldfieldname": "dt", - "oldfieldtype": "Link", - "options": "DocType", - "reqd": 1, - "search_index": 1 + "bold": 1, + "fieldname": "dt", + "fieldtype": "Link", + "in_filter": 1, + "in_list_view": 1, + "label": "Document", + "oldfieldname": "dt", + "oldfieldtype": "Link", + "options": "DocType", + "reqd": 1, + "search_index": 1 }, { - "bold": 1, - "fieldname": "label", - "fieldtype": "Data", - "in_filter": 1, - "label": "Label", - "no_copy": 1, - "oldfieldname": "label", - "oldfieldtype": "Data" + "bold": 1, + "fieldname": "label", + "fieldtype": "Data", + "in_filter": 1, + "label": "Label", + "no_copy": 1, + "oldfieldname": "label", + "oldfieldtype": "Data" }, { - "fieldname": "label_help", - "fieldtype": "HTML", - "label": "Label Help", - "oldfieldtype": "HTML" + "fieldname": "label_help", + "fieldtype": "HTML", + "label": "Label Help", + "oldfieldtype": "HTML" }, { - "fieldname": "fieldname", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Fieldname", - "no_copy": 1, - "oldfieldname": "fieldname", - "oldfieldtype": "Data", - "read_only": 1 + "fieldname": "fieldname", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Fieldname", + "no_copy": 1, + "oldfieldname": "fieldname", + "oldfieldtype": "Data", + "read_only": 1 }, { - "description": "Select the label after which you want to insert new field.", - "fieldname": "insert_after", - "fieldtype": "Select", - "label": "Insert After", - "no_copy": 1, - "oldfieldname": "insert_after", - "oldfieldtype": "Select" + "description": "Select the label after which you want to insert new field.", + "fieldname": "insert_after", + "fieldtype": "Select", + "label": "Insert After", + "no_copy": 1, + "oldfieldname": "insert_after", + "oldfieldtype": "Select" }, { - "fieldname": "column_break_6", - "fieldtype": "Column Break" + "fieldname": "column_break_6", + "fieldtype": "Column Break" }, { - "bold": 1, - "default": "Data", - "fieldname": "fieldtype", - "fieldtype": "Select", - "in_filter": 1, - "in_list_view": 1, - "label": "Field Type", - "oldfieldname": "fieldtype", - "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", - "reqd": 1 + "bold": 1, + "default": "Data", + "fieldname": "fieldtype", + "fieldtype": "Select", + "in_filter": 1, + "in_list_view": 1, + "label": "Field Type", + "oldfieldname": "fieldtype", + "oldfieldtype": "Select", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", + "reqd": 1 }, { - "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", - "description": "Set non-standard precision for a Float or Currency field", - "fieldname": "precision", - "fieldtype": "Select", - "label": "Precision", - "options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9" + "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", + "description": "Set non-standard precision for a Float or Currency field", + "fieldname": "precision", + "fieldtype": "Select", + "label": "Precision", + "options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9" }, { - "fieldname": "options", - "fieldtype": "Small Text", - "in_list_view": 1, - "label": "Options", - "oldfieldname": "options", - "oldfieldtype": "Text" + "fieldname": "options", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Options", + "oldfieldname": "options", + "oldfieldtype": "Text" }, { - "fieldname": "fetch_from", - "fieldtype": "Small Text", - "label": "Fetch From" + "fieldname": "fetch_from", + "fieldtype": "Small Text", + "label": "Fetch From" }, { - "default": "0", - "description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.", - "fieldname": "fetch_if_empty", - "fieldtype": "Check", - "label": "Fetch If Empty" + "default": "0", + "description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.", + "fieldname": "fetch_if_empty", + "fieldtype": "Check", + "label": "Fetch If Empty" }, { - "fieldname": "options_help", - "fieldtype": "HTML", - "label": "Options Help", - "oldfieldtype": "HTML" + "fieldname": "options_help", + "fieldtype": "HTML", + "label": "Options Help", + "oldfieldtype": "HTML" }, { - "fieldname": "section_break_11", - "fieldtype": "Section Break" + "fieldname": "section_break_11", + "fieldtype": "Section Break" }, { - "default": "0", - "depends_on": "eval:doc.fieldtype==\"Section Break\"", - "fieldname": "collapsible", - "fieldtype": "Check", - "label": "Collapsible" + "default": "0", + "depends_on": "eval:doc.fieldtype==\"Section Break\"", + "fieldname": "collapsible", + "fieldtype": "Check", + "label": "Collapsible" }, { - "depends_on": "eval:doc.fieldtype==\"Section Break\"", - "fieldname": "collapsible_depends_on", - "fieldtype": "Code", - "label": "Collapsible Depends On" + "depends_on": "eval:doc.fieldtype==\"Section Break\"", + "fieldname": "collapsible_depends_on", + "fieldtype": "Code", + "label": "Collapsible Depends On" }, { - "fieldname": "default", - "fieldtype": "Text", - "label": "Default Value", - "oldfieldname": "default", - "oldfieldtype": "Text" + "fieldname": "default", + "fieldtype": "Text", + "label": "Default Value", + "oldfieldname": "default", + "oldfieldtype": "Text" }, { - "fieldname": "depends_on", - "fieldtype": "Code", - "label": "Depends On", - "length": 255 + "fieldname": "depends_on", + "fieldtype": "Code", + "label": "Depends On", + "length": 255 }, { - "fieldname": "description", - "fieldtype": "Text", - "label": "Field Description", - "oldfieldname": "description", - "oldfieldtype": "Text", - "print_width": "300px", - "width": "300px" + "fieldname": "description", + "fieldtype": "Text", + "label": "Field Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "print_width": "300px", + "width": "300px" }, { - "default": "0", - "fieldname": "permlevel", - "fieldtype": "Int", - "label": "Permission Level", - "oldfieldname": "permlevel", - "oldfieldtype": "Int" + "default": "0", + "fieldname": "permlevel", + "fieldtype": "Int", + "label": "Permission Level", + "oldfieldname": "permlevel", + "oldfieldtype": "Int" }, { - "fieldname": "width", - "fieldtype": "Data", - "label": "Width", - "oldfieldname": "width", - "oldfieldtype": "Data" + "fieldname": "width", + "fieldtype": "Data", + "label": "Width", + "oldfieldname": "width", + "oldfieldtype": "Data" }, { - "description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)", - "fieldname": "columns", - "fieldtype": "Int", - "label": "Columns" + "description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)", + "fieldname": "columns", + "fieldtype": "Int", + "label": "Columns" }, { - "fieldname": "properties", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "print_width": "50%", - "width": "50%" + "fieldname": "properties", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "print_width": "50%", + "width": "50%" }, { - "default": "0", - "fieldname": "reqd", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Is Mandatory Field", - "oldfieldname": "reqd", - "oldfieldtype": "Check" + "default": "0", + "fieldname": "reqd", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Mandatory Field", + "oldfieldname": "reqd", + "oldfieldtype": "Check" }, { - "default": "0", - "fieldname": "unique", - "fieldtype": "Check", - "label": "Unique" + "default": "0", + "fieldname": "unique", + "fieldtype": "Check", + "label": "Unique" }, { - "default": "0", - "fieldname": "read_only", - "fieldtype": "Check", - "label": "Read Only" + "default": "0", + "fieldname": "read_only", + "fieldtype": "Check", + "label": "Read Only" }, { - "default": "0", - "depends_on": "eval:doc.fieldtype===\"Link\"", - "fieldname": "ignore_user_permissions", - "fieldtype": "Check", - "label": "Ignore User Permissions" + "default": "0", + "depends_on": "eval:doc.fieldtype===\"Link\"", + "fieldname": "ignore_user_permissions", + "fieldtype": "Check", + "label": "Ignore User Permissions" }, { - "default": "0", - "fieldname": "hidden", - "fieldtype": "Check", - "label": "Hidden" + "default": "0", + "fieldname": "hidden", + "fieldtype": "Check", + "label": "Hidden" }, { - "default": "0", - "fieldname": "print_hide", - "fieldtype": "Check", - "label": "Print Hide", - "oldfieldname": "print_hide", - "oldfieldtype": "Check" + "default": "0", + "fieldname": "print_hide", + "fieldtype": "Check", + "label": "Print Hide", + "oldfieldname": "print_hide", + "oldfieldtype": "Check" }, { - "default": "0", - "depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1", - "fieldname": "print_hide_if_no_value", - "fieldtype": "Check", - "label": "Print Hide If No Value" + "default": "0", + "depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1", + "fieldname": "print_hide_if_no_value", + "fieldtype": "Check", + "label": "Print Hide If No Value" }, { - "fieldname": "print_width", - "fieldtype": "Data", - "hidden": 1, - "label": "Print Width", - "no_copy": 1, - "print_hide": 1 + "fieldname": "print_width", + "fieldtype": "Data", + "hidden": 1, + "label": "Print Width", + "no_copy": 1, + "print_hide": 1 }, { - "default": "0", - "fieldname": "no_copy", - "fieldtype": "Check", - "label": "No Copy", - "oldfieldname": "no_copy", - "oldfieldtype": "Check" + "default": "0", + "fieldname": "no_copy", + "fieldtype": "Check", + "label": "No Copy", + "oldfieldname": "no_copy", + "oldfieldtype": "Check" }, { - "default": "0", - "fieldname": "allow_on_submit", - "fieldtype": "Check", - "label": "Allow on Submit", - "oldfieldname": "allow_on_submit", - "oldfieldtype": "Check" + "default": "0", + "fieldname": "allow_on_submit", + "fieldtype": "Check", + "label": "Allow on Submit", + "oldfieldname": "allow_on_submit", + "oldfieldtype": "Check" }, { - "default": "0", - "fieldname": "in_list_view", - "fieldtype": "Check", - "label": "In List View" + "default": "0", + "fieldname": "in_list_view", + "fieldtype": "Check", + "label": "In List View" }, { - "default": "0", - "fieldname": "in_standard_filter", - "fieldtype": "Check", - "label": "In Standard Filter" + "default": "0", + "fieldname": "in_standard_filter", + "fieldtype": "Check", + "label": "In Standard Filter" }, { - "default": "0", - "depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)", - "fieldname": "in_global_search", - "fieldtype": "Check", - "label": "In Global Search" + "default": "0", + "depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)", + "fieldname": "in_global_search", + "fieldtype": "Check", + "label": "In Global Search" }, { - "default": "0", - "fieldname": "bold", - "fieldtype": "Check", - "label": "Bold" + "default": "0", + "fieldname": "bold", + "fieldtype": "Check", + "label": "Bold" }, { - "default": "0", - "fieldname": "report_hide", - "fieldtype": "Check", - "label": "Report Hide", - "oldfieldname": "report_hide", - "oldfieldtype": "Check" + "default": "0", + "fieldname": "report_hide", + "fieldtype": "Check", + "label": "Report Hide", + "oldfieldname": "report_hide", + "oldfieldtype": "Check" }, { - "default": "0", - "fieldname": "search_index", - "fieldtype": "Check", - "hidden": 1, - "label": "Index", - "no_copy": 1, - "print_hide": 1 + "default": "0", + "fieldname": "search_index", + "fieldtype": "Check", + "hidden": 1, + "label": "Index", + "no_copy": 1, + "print_hide": 1 }, { - "default": "0", - "description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field", - "fieldname": "ignore_xss_filter", - "fieldtype": "Check", - "label": "Ignore XSS Filter" + "default": "0", + "description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field", + "fieldname": "ignore_xss_filter", + "fieldtype": "Check", + "label": "Ignore XSS Filter" }, { - "default": "1", - "depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)", - "fieldname": "translatable", - "fieldtype": "Check", - "label": "Translatable" + "default": "1", + "depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)", + "fieldname": "translatable", + "fieldtype": "Check", + "label": "Translatable" }, { - "depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)", - "fieldname": "length", - "fieldtype": "Int", - "label": "Length" + "depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)", + "fieldname": "length", + "fieldtype": "Int", + "label": "Length" }, { - "fieldname": "mandatory_depends_on", - "fieldtype": "Code", - "label": "Mandatory Depends On", - "length": 255 + "fieldname": "mandatory_depends_on", + "fieldtype": "Code", + "label": "Mandatory Depends On", + "length": 255 }, { - "fieldname": "read_only_depends_on", - "fieldtype": "Code", - "label": "Read Only Depends On", - "length": 255 + "fieldname": "read_only_depends_on", + "fieldtype": "Code", + "label": "Read Only Depends On", + "length": 255 }, { - "default": "0", - "fieldname": "allow_in_quick_entry", - "fieldtype": "Check", - "label": "Allow in Quick Entry" + "default": "0", + "fieldname": "allow_in_quick_entry", + "fieldtype": "Check", + "label": "Allow in Quick Entry" }, { - "default": "0", - "fieldname": "in_preview", - "fieldtype": "Check", - "label": "In Preview" + "default": "0", + "depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);", + "fieldname": "in_preview", + "fieldtype": "Check", + "label": "In Preview" }, { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Duration'", - "fieldname": "hide_seconds", - "fieldtype": "Check", - "label": "Hide Seconds" + "default": "0", + "depends_on": "eval:doc.fieldtype=='Duration'", + "fieldname": "hide_seconds", + "fieldtype": "Check", + "label": "Hide Seconds" }, { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Duration'", - "fieldname": "hide_days", - "fieldtype": "Check", - "label": "Hide Days" + "default": "0", + "depends_on": "eval:doc.fieldtype=='Duration'", + "fieldname": "hide_days", + "fieldtype": "Check", + "label": "Hide Days" }, { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Section Break'", - "fieldname": "hide_border", - "fieldtype": "Check", - "label": "Hide Border" + "default": "0", + "depends_on": "eval:doc.fieldtype=='Section Break'", + "fieldname": "hide_border", + "fieldtype": "Check", + "label": "Hide Border" } ], "icon": "fa fa-glass", "idx": 1, + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-02-06 23:43:00.123575", + "modified": "2020-08-28 11:28:44.377753", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", "owner": "Administrator", "permissions": [ { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Administrator", - "share": 1, - "write": 1 + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Administrator", + "share": 1, + "write": 1 }, { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 } ], "search_fields": "dt,label,fieldtype,options", diff --git a/frappe/custom/doctype/custom_script/custom_script.json b/frappe/custom/doctype/custom_script/custom_script.json index fc086e4b0b..328b247c49 100644 --- a/frappe/custom/doctype/custom_script/custom_script.json +++ b/frappe/custom/doctype/custom_script/custom_script.json @@ -1,187 +1,91 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2013-01-10 16:34:01", - "custom": 0, - "description": "Adds a client custom script to a DocType", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, - "engine": "InnoDB", + "actions": [], + "allow_import": 1, + "creation": "2013-01-10 16:34:01", + "description": "Adds a client custom script to a DocType", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "dt", + "enabled", + "script", + "sample" + ], "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": "dt", - "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": "DocType", - "length": 0, - "no_copy": 0, - "oldfieldname": "dt", - "oldfieldtype": "Link", - "options": "DocType", - "permlevel": 0, - "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 - }, + "fieldname": "dt", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "DocType", + "oldfieldname": "dt", + "oldfieldtype": "Link", + "options": "DocType", + "reqd": 1, + "show_days": 1, + "show_seconds": 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": "script", - "fieldtype": "Code", - "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": "Script", - "length": 0, - "no_copy": 0, - "oldfieldname": "script", - "oldfieldtype": "Code", - "options": "JS", - "permlevel": 0, - "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 - }, + "fieldname": "script", + "fieldtype": "Code", + "label": "Script", + "oldfieldname": "script", + "oldfieldtype": "Code", + "options": "JS", + "show_days": 1, + "show_seconds": 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": "sample", - "fieldtype": "HTML", - "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": "Sample", - "length": 0, - "no_copy": 0, - "options": "
Custom Scripts are executed only on the client-side (i.e. in Forms). Here are some examples to get you started
\n\n\n// fetch local_tax_no on selection of customer \n// cur_frm.add_fetch(link_field, source_fieldname, target_fieldname); \ncur_frm.add_fetch('customer', 'local_tax_no', 'local_tax_no');\n\n// additional validation on dates \nfrappe.ui.form.on('Task', 'validate', function(frm) {\n if (frm.doc.from_date < get_today()) {\n msgprint('You can not select past date in From Date');\n validated = false;\n } \n});\n\n// make a field read-only after saving \nfrappe.ui.form.on('Task', {\n refresh: function(frm) {\n // use the __islocal value of doc, to check if the doc is saved or not\n frm.set_df_property('myfield', 'read_only', frm.doc.__islocal ? 0 : 1);\n } \n});\n\n// additional permission check\nfrappe.ui.form.on('Task', {\n validate: function(frm) {\n if(user=='user1@example.com' && frm.doc.purpose!='Material Receipt') {\n msgprint('You are only allowed Material Receipt');\n validated = false;\n }\n } \n});\n\n// calculate sales incentive\nfrappe.ui.form.on('Sales Invoice', {\n validate: function(frm) {\n // calculate incentives for each person on the deal\n total_incentive = 0\n $.each(frm.doc.sales_team, function(i, d) {\n // calculate incentive\n var incentive_percent = 2;\n if(frm.doc.base_grand_total > 400) incentive_percent = 4;\n // actual incentive\n d.incentives = flt(frm.doc.base_grand_total) * incentive_percent / 100;\n total_incentive += flt(d.incentives)\n });\n frm.doc.total_incentive = total_incentive;\n } \n})\n\n",
- "permlevel": 0,
- "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
+ "fieldname": "sample",
+ "fieldtype": "HTML",
+ "label": "Sample",
+ "options": "Custom Scripts are executed only on the client-side (i.e. in Forms). Here are some examples to get you started
\n\n\n// fetch local_tax_no on selection of customer \n// cur_frm.add_fetch(link_field, source_fieldname, target_fieldname); \ncur_frm.add_fetch('customer', 'local_tax_no', 'local_tax_no');\n\n// additional validation on dates \nfrappe.ui.form.on('Task', 'validate', function(frm) {\n if (frm.doc.from_date < get_today()) {\n msgprint('You can not select past date in From Date');\n validated = false;\n } \n});\n\n// make a field read-only after saving \nfrappe.ui.form.on('Task', {\n refresh: function(frm) {\n // use the __islocal value of doc, to check if the doc is saved or not\n frm.set_df_property('myfield', 'read_only', frm.doc.__islocal ? 0 : 1);\n } \n});\n\n// additional permission check\nfrappe.ui.form.on('Task', {\n validate: function(frm) {\n if(user=='user1@example.com' && frm.doc.purpose!='Material Receipt') {\n msgprint('You are only allowed Material Receipt');\n validated = false;\n }\n } \n});\n\n// calculate sales incentive\nfrappe.ui.form.on('Sales Invoice', {\n validate: function(frm) {\n // calculate incentives for each person on the deal\n total_incentive = 0\n $.each(frm.doc.sales_team, function(i, d) {\n // calculate incentive\n var incentive_percent = 2;\n if(frm.doc.base_grand_total > 400) incentive_percent = 4;\n // actual incentive\n d.incentives = flt(frm.doc.base_grand_total) * incentive_percent / 100;\n total_incentive += flt(d.incentives)\n });\n frm.doc.total_incentive = total_incentive;\n } \n})\n\n",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "enabled",
+ "fieldtype": "Check",
+ "label": "Enabled",
+ "show_days": 1,
+ "show_seconds": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-glass",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "menu_index": 0,
- "modified": "2019-03-21 14:26:57.402994",
- "modified_by": "Administrator",
- "module": "Custom",
- "name": "Custom Script",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-glass",
+ "idx": 1,
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-08-24 21:56:07.719579",
+ "modified_by": "Administrator",
+ "module": "Custom",
+ "name": "Custom Script",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Administrator",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Administrator",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_order": "ASC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "sort_order": "ASC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json
index 267213517c..1c7349ef01 100644
--- a/frappe/custom/doctype/customize_form_field/customize_form_field.json
+++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json
@@ -60,364 +60,366 @@
],
"fields": [
{
- "fieldname": "label_and_type",
- "fieldtype": "Section Break",
- "label": "Label and Type"
+ "fieldname": "label_and_type",
+ "fieldtype": "Section Break",
+ "label": "Label and Type"
},
{
- "fieldname": "label",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Label",
- "oldfieldname": "label",
- "oldfieldtype": "Data",
- "search_index": 1
+ "fieldname": "label",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Label",
+ "oldfieldname": "label",
+ "oldfieldtype": "Data",
+ "search_index": 1
},
{
- "default": "Data",
- "fieldname": "fieldtype",
- "fieldtype": "Select",
- "in_list_view": 1,
- "label": "Type",
- "oldfieldname": "fieldtype",
- "oldfieldtype": "Select",
- "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime",
- "reqd": 1,
- "search_index": 1
+ "default": "Data",
+ "fieldname": "fieldtype",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Type",
+ "oldfieldname": "fieldtype",
+ "oldfieldtype": "Select",
+ "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime",
+ "reqd": 1,
+ "search_index": 1
},
{
- "fieldname": "fieldname",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Name",
- "oldfieldname": "fieldname",
- "oldfieldtype": "Data",
- "read_only": 1,
- "search_index": 1
+ "fieldname": "fieldname",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Name",
+ "oldfieldname": "fieldname",
+ "oldfieldtype": "Data",
+ "read_only": 1,
+ "search_index": 1
},
{
- "default": "0",
- "depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)",
- "fieldname": "reqd",
- "fieldtype": "Check",
- "label": "Mandatory",
- "oldfieldname": "reqd",
- "oldfieldtype": "Check",
- "print_width": "50px",
- "width": "50px"
+ "default": "0",
+ "depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)",
+ "fieldname": "reqd",
+ "fieldtype": "Check",
+ "label": "Mandatory",
+ "oldfieldname": "reqd",
+ "oldfieldtype": "Check",
+ "print_width": "50px",
+ "width": "50px"
},
{
- "default": "0",
- "fieldname": "unique",
- "fieldtype": "Check",
- "label": "Unique"
+ "default": "0",
+ "fieldname": "unique",
+ "fieldtype": "Check",
+ "label": "Unique"
},
{
- "default": "0",
- "fieldname": "in_list_view",
- "fieldtype": "Check",
- "label": "In List View"
+ "default": "0",
+ "fieldname": "in_list_view",
+ "fieldtype": "Check",
+ "label": "In List View"
},
{
- "default": "0",
- "fieldname": "in_standard_filter",
- "fieldtype": "Check",
- "label": "In Standard Filter"
+ "default": "0",
+ "fieldname": "in_standard_filter",
+ "fieldtype": "Check",
+ "label": "In Standard Filter"
},
{
- "default": "0",
- "depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
- "fieldname": "in_global_search",
- "fieldtype": "Check",
- "label": "In Global Search"
+ "default": "0",
+ "depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
+ "fieldname": "in_global_search",
+ "fieldtype": "Check",
+ "label": "In Global Search"
},
{
- "default": "0",
- "fieldname": "bold",
- "fieldtype": "Check",
- "label": "Bold"
+ "default": "0",
+ "fieldname": "bold",
+ "fieldtype": "Check",
+ "label": "Bold"
},
{
- "default": "1",
- "depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
- "fieldname": "translatable",
- "fieldtype": "Check",
- "label": "Translatable"
+ "default": "1",
+ "depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
+ "fieldname": "translatable",
+ "fieldtype": "Check",
+ "label": "Translatable"
},
{
- "fieldname": "column_break_7",
- "fieldtype": "Column Break"
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break"
},
{
- "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
- "description": "Set non-standard precision for a Float or Currency field",
- "fieldname": "precision",
- "fieldtype": "Select",
- "label": "Precision",
- "options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
+ "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
+ "description": "Set non-standard precision for a Float or Currency field",
+ "fieldname": "precision",
+ "fieldtype": "Select",
+ "label": "Precision",
+ "options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
},
{
- "depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'], doc.fieldtype)",
- "fieldname": "length",
- "fieldtype": "Int",
- "label": "Length"
+ "depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'], doc.fieldtype)",
+ "fieldname": "length",
+ "fieldtype": "Int",
+ "label": "Length"
},
{
- "description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.",
- "fieldname": "options",
- "fieldtype": "Small Text",
- "in_list_view": 1,
- "label": "Options",
- "oldfieldname": "options",
- "oldfieldtype": "Text"
+ "description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.",
+ "fieldname": "options",
+ "fieldtype": "Small Text",
+ "in_list_view": 1,
+ "label": "Options",
+ "oldfieldname": "options",
+ "oldfieldtype": "Text"
},
{
- "fieldname": "fetch_from",
- "fieldtype": "Small Text",
- "label": "Fetch From"
+ "fieldname": "fetch_from",
+ "fieldtype": "Small Text",
+ "label": "Fetch From"
},
{
- "default": "0",
- "description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
- "fieldname": "fetch_if_empty",
- "fieldtype": "Check",
- "label": "Fetch If Empty"
+ "default": "0",
+ "description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
+ "fieldname": "fetch_if_empty",
+ "fieldtype": "Check",
+ "label": "Fetch If Empty"
},
{
- "fieldname": "permissions",
- "fieldtype": "Section Break",
- "label": "Permissions"
+ "fieldname": "permissions",
+ "fieldtype": "Section Break",
+ "label": "Permissions"
},
{
- "description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): \nmyfield\neval:doc.myfield=='My Value'\neval:doc.age>18",
- "fieldname": "depends_on",
- "fieldtype": "Code",
- "label": "Depends On",
- "oldfieldname": "depends_on",
- "oldfieldtype": "Data",
- "options": "JS"
+ "description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): \nmyfield\neval:doc.myfield=='My Value'\neval:doc.age>18",
+ "fieldname": "depends_on",
+ "fieldtype": "Code",
+ "label": "Depends On",
+ "oldfieldname": "depends_on",
+ "oldfieldtype": "Data",
+ "options": "JS"
},
{
- "default": "0",
- "fieldname": "permlevel",
- "fieldtype": "Int",
- "in_list_view": 1,
- "label": "Perm Level",
- "oldfieldname": "permlevel",
- "oldfieldtype": "Int"
+ "default": "0",
+ "fieldname": "permlevel",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Perm Level",
+ "oldfieldname": "permlevel",
+ "oldfieldtype": "Int"
},
{
- "default": "0",
- "fieldname": "hidden",
- "fieldtype": "Check",
- "label": "Hidden",
- "oldfieldname": "hidden",
- "oldfieldtype": "Check",
- "print_width": "50px",
- "width": "50px"
+ "default": "0",
+ "fieldname": "hidden",
+ "fieldtype": "Check",
+ "label": "Hidden",
+ "oldfieldname": "hidden",
+ "oldfieldtype": "Check",
+ "print_width": "50px",
+ "width": "50px"
},
{
- "default": "0",
- "fieldname": "read_only",
- "fieldtype": "Check",
- "label": "Read Only"
+ "default": "0",
+ "fieldname": "read_only",
+ "fieldtype": "Check",
+ "label": "Read Only"
},
{
- "default": "0",
- "depends_on": "eval:doc.fieldtype==\"Section Break\"",
- "fieldname": "collapsible",
- "fieldtype": "Check",
- "label": "Collapsible"
+ "default": "0",
+ "depends_on": "eval:doc.fieldtype==\"Section Break\"",
+ "fieldname": "collapsible",
+ "fieldtype": "Check",
+ "label": "Collapsible"
},
{
- "default": "0",
- "depends_on": "eval: doc.fieldtype == \"Table\"",
- "fieldname": "allow_bulk_edit",
- "fieldtype": "Check",
- "label": "Allow Bulk Edit"
+ "default": "0",
+ "depends_on": "eval: doc.fieldtype == \"Table\"",
+ "fieldname": "allow_bulk_edit",
+ "fieldtype": "Check",
+ "label": "Allow Bulk Edit"
},
{
- "depends_on": "eval:doc.fieldtype==\"Section Break\"",
- "fieldname": "collapsible_depends_on",
- "fieldtype": "Code",
- "label": "Collapsible Depends On",
- "options": "JS"
+ "depends_on": "eval:doc.fieldtype==\"Section Break\"",
+ "fieldname": "collapsible_depends_on",
+ "fieldtype": "Code",
+ "label": "Collapsible Depends On",
+ "options": "JS"
},
{
- "fieldname": "column_break_14",
- "fieldtype": "Column Break"
+ "fieldname": "column_break_14",
+ "fieldtype": "Column Break"
},
{
- "default": "0",
- "fieldname": "ignore_user_permissions",
- "fieldtype": "Check",
- "label": "Ignore User Permissions"
+ "default": "0",
+ "fieldname": "ignore_user_permissions",
+ "fieldtype": "Check",
+ "label": "Ignore User Permissions"
},
{
- "default": "0",
- "fieldname": "allow_on_submit",
- "fieldtype": "Check",
- "label": "Allow on Submit",
- "oldfieldname": "allow_on_submit",
- "oldfieldtype": "Check"
+ "default": "0",
+ "fieldname": "allow_on_submit",
+ "fieldtype": "Check",
+ "label": "Allow on Submit",
+ "oldfieldname": "allow_on_submit",
+ "oldfieldtype": "Check"
},
{
- "default": "0",
- "fieldname": "report_hide",
- "fieldtype": "Check",
- "label": "Report Hide",
- "oldfieldname": "report_hide",
- "oldfieldtype": "Check"
+ "default": "0",
+ "fieldname": "report_hide",
+ "fieldtype": "Check",
+ "label": "Report Hide",
+ "oldfieldname": "report_hide",
+ "oldfieldtype": "Check"
},
{
- "default": "0",
- "depends_on": "eval:(doc.fieldtype == 'Link')",
- "fieldname": "remember_last_selected_value",
- "fieldtype": "Check",
- "label": "Remember Last Selected Value"
+ "default": "0",
+ "depends_on": "eval:(doc.fieldtype == 'Link')",
+ "fieldname": "remember_last_selected_value",
+ "fieldtype": "Check",
+ "label": "Remember Last Selected Value"
},
{
- "fieldname": "display",
- "fieldtype": "Section Break",
- "label": "Display"
+ "fieldname": "display",
+ "fieldtype": "Section Break",
+ "label": "Display"
},
{
- "fieldname": "default",
- "fieldtype": "Text",
- "label": "Default",
- "oldfieldname": "default",
- "oldfieldtype": "Text"
+ "fieldname": "default",
+ "fieldtype": "Text",
+ "label": "Default",
+ "oldfieldname": "default",
+ "oldfieldtype": "Text"
},
{
- "default": "0",
- "fieldname": "in_filter",
- "fieldtype": "Check",
- "label": "In Filter",
- "oldfieldname": "in_filter",
- "oldfieldtype": "Check",
- "print_width": "50px",
- "width": "50px"
+ "default": "0",
+ "fieldname": "in_filter",
+ "fieldtype": "Check",
+ "label": "In Filter",
+ "oldfieldname": "in_filter",
+ "oldfieldtype": "Check",
+ "print_width": "50px",
+ "width": "50px"
},
{
- "fieldname": "column_break_21",
- "fieldtype": "Column Break"
+ "fieldname": "column_break_21",
+ "fieldtype": "Column Break"
},
{
- "fieldname": "description",
- "fieldtype": "Text",
- "label": "Description",
- "oldfieldname": "description",
- "oldfieldtype": "Text",
- "print_width": "300px",
- "width": "300px"
+ "fieldname": "description",
+ "fieldtype": "Text",
+ "label": "Description",
+ "oldfieldname": "description",
+ "oldfieldtype": "Text",
+ "print_width": "300px",
+ "width": "300px"
},
{
- "default": "0",
- "fieldname": "print_hide",
- "fieldtype": "Check",
- "label": "Print Hide",
- "oldfieldname": "print_hide",
- "oldfieldtype": "Check"
+ "default": "0",
+ "fieldname": "print_hide",
+ "fieldtype": "Check",
+ "label": "Print Hide",
+ "oldfieldname": "print_hide",
+ "oldfieldtype": "Check"
},
{
- "default": "0",
- "depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
- "fieldname": "print_hide_if_no_value",
- "fieldtype": "Check",
- "label": "Print Hide If No Value"
+ "default": "0",
+ "depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
+ "fieldname": "print_hide_if_no_value",
+ "fieldtype": "Check",
+ "label": "Print Hide If No Value"
},
{
- "description": "Print Width of the field, if the field is a column in a table",
- "fieldname": "print_width",
- "fieldtype": "Data",
- "label": "Print Width",
- "print_width": "50px",
- "width": "50px"
+ "description": "Print Width of the field, if the field is a column in a table",
+ "fieldname": "print_width",
+ "fieldtype": "Data",
+ "label": "Print Width",
+ "print_width": "50px",
+ "width": "50px"
},
{
- "depends_on": "eval:cur_frm.doc.istable",
- "description": "Number of columns for a field in a Grid (Total Columns in a grid should be less than 11)",
- "fieldname": "columns",
- "fieldtype": "Int",
- "label": "Columns"
+ "depends_on": "eval:cur_frm.doc.istable",
+ "description": "Number of columns for a field in a Grid (Total Columns in a grid should be less than 11)",
+ "fieldname": "columns",
+ "fieldtype": "Int",
+ "label": "Columns"
},
{
- "fieldname": "width",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Width",
- "oldfieldname": "width",
- "oldfieldtype": "Data",
- "print_width": "50px",
- "width": "50px"
+ "fieldname": "width",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Width",
+ "oldfieldname": "width",
+ "oldfieldtype": "Data",
+ "print_width": "50px",
+ "width": "50px"
},
{
- "default": "0",
- "fieldname": "is_custom_field",
- "fieldtype": "Check",
- "hidden": 1,
- "label": "Is Custom Field",
- "read_only": 1
+ "default": "0",
+ "fieldname": "is_custom_field",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Is Custom Field",
+ "read_only": 1
},
{
- "default": "0",
- "fieldname": "allow_in_quick_entry",
- "fieldtype": "Check",
- "label": "Allow in Quick Entry"
+ "default": "0",
+ "fieldname": "allow_in_quick_entry",
+ "fieldtype": "Check",
+ "label": "Allow in Quick Entry"
},
{
- "fieldname": "property_depends_on_section",
- "fieldtype": "Section Break",
- "label": "Property Depends On"
+ "fieldname": "property_depends_on_section",
+ "fieldtype": "Section Break",
+ "label": "Property Depends On"
},
{
- "fieldname": "mandatory_depends_on",
- "fieldtype": "Code",
- "label": "Mandatory Depends On",
- "options": "JS"
+ "fieldname": "mandatory_depends_on",
+ "fieldtype": "Code",
+ "label": "Mandatory Depends On",
+ "options": "JS"
},
{
- "fieldname": "column_break_33",
- "fieldtype": "Column Break"
+ "fieldname": "column_break_33",
+ "fieldtype": "Column Break"
},
{
- "fieldname": "read_only_depends_on",
- "fieldtype": "Code",
- "label": "Read Only Depends On",
- "options": "JS"
+ "fieldname": "read_only_depends_on",
+ "fieldtype": "Code",
+ "label": "Read Only Depends On",
+ "options": "JS"
},
{
- "default": "0",
- "fieldname": "in_preview",
- "fieldtype": "Check",
- "label": "In Preview"
+ "default": "0",
+ "depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);",
+ "fieldname": "in_preview",
+ "fieldtype": "Check",
+ "label": "In Preview"
},
{
- "default": "0",
- "depends_on": "eval:doc.fieldtype=='Duration'",
- "fieldname": "hide_seconds",
- "fieldtype": "Check",
- "label": "Hide Seconds"
+ "default": "0",
+ "depends_on": "eval:doc.fieldtype=='Duration'",
+ "fieldname": "hide_seconds",
+ "fieldtype": "Check",
+ "label": "Hide Seconds"
},
{
- "default": "0",
- "depends_on": "eval:doc.fieldtype=='Duration'",
- "fieldname": "hide_days",
- "fieldtype": "Check",
- "label": "Hide Days"
+ "default": "0",
+ "depends_on": "eval:doc.fieldtype=='Duration'",
+ "fieldname": "hide_days",
+ "fieldtype": "Check",
+ "label": "Hide Days"
},
{
- "default": "0",
- "depends_on": "eval:doc.fieldtype=='Section Break'",
- "fieldname": "hide_border",
- "fieldtype": "Check",
- "label": "Hide Border"
+ "default": "0",
+ "depends_on": "eval:doc.fieldtype=='Section Break'",
+ "fieldname": "hide_border",
+ "fieldtype": "Check",
+ "label": "Hide Border"
}
],
"idx": 1,
+ "index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2020-06-02 23:45:46.810868",
+ "modified": "2020-08-28 11:28:59.084060",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customize Form Field",
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/data_migration/doctype/data_migration_mapping/data_migration_mapping.py b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py
index e89282885f..1cc54a0d1a 100644
--- a/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py
+++ b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py
@@ -5,12 +5,12 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
-from frappe.utils import get_source_value
+from frappe.utils.safe_exec import get_safe_globals
class DataMigrationMapping(Document):
def get_filters(self):
if self.condition:
- return frappe.safe_eval(self.condition, dict(frappe=frappe))
+ return frappe.safe_eval(self.condition, get_safe_globals())
def get_fields(self):
fields = []
@@ -64,9 +64,16 @@ def get_value_from_fieldname(field_map, fieldname_field, doc):
field_name = get_source_value(field_map, fieldname_field)
if field_name.startswith('eval:'):
- value = frappe.safe_eval(field_name[5:], dict(frappe=frappe))
+ value = frappe.safe_eval(field_name[5:], get_safe_globals())
elif field_name[0] in ('"', "'"):
value = field_name[1:-1]
else:
value = get_source_value(doc, field_name)
return value
+
+def get_source_value(source, key):
+ '''Get value from source (object or dict) based on key'''
+ if isinstance(source, dict):
+ return source.get(key)
+ else:
+ return getattr(source, key)
diff --git a/frappe/data_migration/doctype/data_migration_run/data_migration_run.py b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py
index b2ce4606f8..473acfb3d0 100644
--- a/frappe/data_migration/doctype/data_migration_run/data_migration_run.py
+++ b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py
@@ -6,7 +6,8 @@ from __future__ import unicode_literals
import frappe, json, math
from frappe.model.document import Document
from frappe import _
-from frappe.utils import get_source_value, cstr
+from frappe.utils import cstr
+from frappe.data_migration.doctype.data_migration_mapping.data_migration_mapping import get_source_value
class DataMigrationRun(Document):
def run(self):
diff --git a/frappe/database/mariadb/framework_mariadb.sql b/frappe/database/mariadb/framework_mariadb.sql
index 1e3749e030..15b0bed699 100644
--- a/frappe/database/mariadb/framework_mariadb.sql
+++ b/frappe/database/mariadb/framework_mariadb.sql
@@ -128,7 +128,7 @@ CREATE TABLE `tabDocType Action` (
`label` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`group` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`action_type` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
- `action` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+ `action` text COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`name`),
KEY `parent` (`parent`),
KEY `modified` (`modified`)
diff --git a/frappe/database/mariadb/setup_db.py b/frappe/database/mariadb/setup_db.py
index 6370990098..a4e4d624ae 100644
--- a/frappe/database/mariadb/setup_db.py
+++ b/frappe/database/mariadb/setup_db.py
@@ -92,6 +92,8 @@ def bootstrap_database(db_name, verbose, source_sql=None):
sys.exit(1)
import_db_from_sql(source_sql, verbose)
+
+ frappe.connect(db_name=db_name)
if not 'tabDefaultValue' in frappe.db.get_tables():
print('''Database not installed, this can due to lack of permission, or that the database name exists.
Check your mysql root password, or use --force to reinstall''')
diff --git a/frappe/database/postgres/framework_postgres.sql b/frappe/database/postgres/framework_postgres.sql
index a946a7ee5c..eeb0eecd3f 100644
--- a/frappe/database/postgres/framework_postgres.sql
+++ b/frappe/database/postgres/framework_postgres.sql
@@ -128,7 +128,7 @@ CREATE TABLE "tabDocType Action" (
"parenttype" varchar(255) DEFAULT NULL,
"idx" bigint NOT NULL DEFAULT 0,
"label" varchar(140) NOT NULL,
- "group" varchar(140) DEFAULT NULL,
+ "group" text DEFAULT NULL,
"action_type" varchar(140) NOT NULL,
"action" varchar(140) NOT NULL,
PRIMARY KEY ("name")
diff --git a/frappe/database/postgres/schema.py b/frappe/database/postgres/schema.py
index b5129b60bb..58153ca6ce 100644
--- a/frappe/database/postgres/schema.py
+++ b/frappe/database/postgres/schema.py
@@ -49,7 +49,7 @@ class PostgresTable(DBTable):
elif col.fieldtype in ("Check"):
using_clause = "USING {}::smallint".format(col.fieldname)
- query.append("ALTER COLUMN {0} TYPE {1} {2}".format(
+ query.append("ALTER COLUMN `{0}` TYPE {1} {2}".format(
col.fieldname,
get_definition(col.fieldtype, precision=col.precision, length=col.length),
using_clause)
diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py
index 148ae87249..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)
@@ -203,7 +203,7 @@ class Workspace:
cards = cards + get_custom_reports_and_doctypes(self.doc.module)
if len(self.extended_cards):
- cards = cards + self.extended_cards
+ cards = merge_cards_based_on_label(cards + self.extended_cards)
default_country = frappe.db.get_default("country")
def _doctype_contains_a_record(name):
@@ -579,3 +579,16 @@ def update_onboarding_step(name, field, value):
"""
frappe.db.set_value("Onboarding Step", name, field, value)
+
+def merge_cards_based_on_label(cards):
+ """Merge cards with common label."""
+ cards_dict = {}
+ for card in cards:
+ if card.label in cards_dict:
+ links = loads(cards_dict[card.label].links) + loads(card.links)
+ cards_dict[card.label].update(dict(links=dumps(links)))
+ cards_dict[card.label] = cards_dict.pop(card.label)
+ else:
+ cards_dict[card.label] = card
+
+ return list(cards_dict.values())
\ No newline at end of file
diff --git a/frappe/desk/doctype/console_log/__init__.py b/frappe/desk/doctype/console_log/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/desk/doctype/console_log/console_log.js b/frappe/desk/doctype/console_log/console_log.js
new file mode 100644
index 0000000000..1ef4fdce59
--- /dev/null
+++ b/frappe/desk/doctype/console_log/console_log.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Console Log', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/frappe/desk/doctype/console_log/console_log.json b/frappe/desk/doctype/console_log/console_log.json
new file mode 100644
index 0000000000..a9ae9717fd
--- /dev/null
+++ b/frappe/desk/doctype/console_log/console_log.json
@@ -0,0 +1,52 @@
+{
+ "actions": [],
+ "autoname": "format:Log on {timestamp}",
+ "creation": "2020-08-18 19:56:12.336427",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "script",
+ "output"
+ ],
+ "fields": [
+ {
+ "fieldname": "script",
+ "fieldtype": "Code",
+ "in_list_view": 1,
+ "label": "Script",
+ "read_only": 1
+ },
+ {
+ "fieldname": "output",
+ "fieldtype": "Code",
+ "label": "Output",
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-08-18 20:07:57.587344",
+ "modified_by": "Administrator",
+ "module": "Desk",
+ "name": "Console Log",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/frappe/desk/doctype/console_log/console_log.py b/frappe/desk/doctype/console_log/console_log.py
new file mode 100644
index 0000000000..635c4c1ba7
--- /dev/null
+++ b/frappe/desk/doctype/console_log/console_log.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class ConsoleLog(Document):
+ pass
diff --git a/frappe/desk/doctype/console_log/test_console_log.py b/frappe/desk/doctype/console_log/test_console_log.py
new file mode 100644
index 0000000000..04dc4f241f
--- /dev/null
+++ b/frappe/desk/doctype/console_log/test_console_log.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestConsoleLog(unittest.TestCase):
+ pass
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/desk/doctype/system_console/__init__.py b/frappe/desk/doctype/system_console/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/desk/doctype/system_console/system_console.js b/frappe/desk/doctype/system_console/system_console.js
new file mode 100644
index 0000000000..c7eac39490
--- /dev/null
+++ b/frappe/desk/doctype/system_console/system_console.js
@@ -0,0 +1,21 @@
+// Copyright (c) 2020, Frappe Technologies and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('System Console', {
+ onload: function(frm) {
+ frappe.ui.keys.add_shortcut({
+ shortcut: 'shift+enter',
+ action: () => frm.execute_action('Execute'),
+ page: frm.page,
+ description: __('Execute Console script'),
+ ignore_inputs: true,
+ });
+ },
+
+ refresh: function(frm) {
+ frm.disable_save();
+ frm.page.set_primary_action(__("Execute"), () => {
+ frm.execute_action('Execute');
+ });
+ }
+});
diff --git a/frappe/desk/doctype/system_console/system_console.json b/frappe/desk/doctype/system_console/system_console.json
new file mode 100644
index 0000000000..14e36e6fd3
--- /dev/null
+++ b/frappe/desk/doctype/system_console/system_console.json
@@ -0,0 +1,68 @@
+{
+ "actions": [
+ {
+ "action": "#List/Console Log/List",
+ "action_type": "Route",
+ "label": "Logs"
+ },
+ {
+ "action": "frappe.desk.doctype.system_console.system_console.execute_code",
+ "action_type": "Server Action",
+ "hidden": 1,
+ "label": "Execute"
+ }
+ ],
+ "creation": "2020-08-18 17:44:35.647815",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "console",
+ "commit",
+ "output"
+ ],
+ "fields": [
+ {
+ "description": "To print output use log(text)",
+ "fieldname": "console",
+ "fieldtype": "Code",
+ "label": "Console",
+ "options": "Python"
+ },
+ {
+ "fieldname": "output",
+ "fieldtype": "Code",
+ "label": "Output",
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "commit",
+ "fieldtype": "Check",
+ "label": "Commit"
+ }
+ ],
+ "hide_toolbar": 1,
+ "index_web_pages_for_search": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2020-08-21 14:44:35.296877",
+ "modified_by": "Administrator",
+ "module": "Desk",
+ "name": "System Console",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
diff --git a/frappe/desk/doctype/system_console/system_console.py b/frappe/desk/doctype/system_console/system_console.py
new file mode 100644
index 0000000000..6c87ca8c36
--- /dev/null
+++ b/frappe/desk/doctype/system_console/system_console.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+
+import json
+
+import frappe
+from frappe.utils.safe_exec import safe_exec
+from frappe.model.document import Document
+
+class SystemConsole(Document):
+ def run(self):
+ frappe.only_for('System Manager')
+ try:
+ frappe.debug_log = []
+ safe_exec(self.console)
+ self.output = '\n'.join(frappe.debug_log)
+ except: # noqa: E722
+ self.output = frappe.get_traceback()
+
+ if self.commit:
+ frappe.db.commit()
+ else:
+ frappe.db.rollback()
+
+ frappe.get_doc(dict(
+ doctype='Console Log',
+ script=self.console,
+ output=self.output)).insert()
+ frappe.db.commit()
+
+@frappe.whitelist()
+def execute_code(doc):
+ console = frappe.get_doc(json.loads(doc))
+ console.run()
+ return console.as_dict()
\ No newline at end of file
diff --git a/frappe/desk/doctype/system_console/test_system_console.py b/frappe/desk/doctype/system_console/test_system_console.py
new file mode 100644
index 0000000000..55ef199122
--- /dev/null
+++ b/frappe/desk/doctype/system_console/test_system_console.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestSystemConsole(unittest.TestCase):
+ def test_system_console(self):
+ system_console = frappe.get_doc('System Console')
+ system_console.console = 'log("hello")'
+ system_console.run()
+
+ self.assertEqual(system_console.output, 'hello')
+
+ system_console.console = 'log(frappe.db.get_value("DocType", "DocType", "module"))'
+ system_console.run()
+
+ self.assertEqual(system_console.output, 'Core')
diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py
index ba0e5c2216..c28a40657f 100644
--- a/frappe/desk/form/meta.py
+++ b/frappe/desk/form/meta.py
@@ -130,7 +130,7 @@ class FormMeta(Meta):
def add_custom_script(self):
"""embed all require files"""
# custom script
- custom = frappe.db.get_value("Custom Script", {"dt": self.name}, "script") or ""
+ custom = frappe.db.get_value("Custom Script", {"dt": self.name, "enabled": 1}, "script") or ""
self.set("__custom_js", custom)
diff --git a/frappe/desk/link_preview.py b/frappe/desk/link_preview.py
index f380f96e26..9b4471aa8d 100644
--- a/frappe/desk/link_preview.py
+++ b/frappe/desk/link_preview.py
@@ -1,5 +1,5 @@
import frappe
-from frappe.model import no_value_fields
+from frappe.model import no_value_fields, table_fields
import json
@frappe.whitelist()
@@ -9,11 +9,13 @@ def get_preview_data(doctype, docname):
if not meta.show_preview_popup: return
preview_fields = [field.fieldname for field in meta.fields \
- if field.in_preview and field.fieldtype not in no_value_fields]
+ if field.in_preview and field.fieldtype not in no_value_fields \
+ and field.fieldtype not in table_fields]
# no preview fields defined, build list from mandatory fields
if not preview_fields:
- preview_fields = [field.fieldname for field in meta.fields if field.reqd]
+ preview_fields = [field.fieldname for field in meta.fields if field.reqd \
+ and field.fieldtype not in table_fields]
title_field = meta.get_title_field()
image_field = meta.image_field
diff --git a/frappe/desk/query_builder.py b/frappe/desk/query_builder.py
deleted file mode 100644
index 81eba35e05..0000000000
--- a/frappe/desk/query_builder.py
+++ /dev/null
@@ -1,263 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# MIT License. See license.txt
-
-from __future__ import unicode_literals
-import frappe
-
-out = frappe.response
-
-from frappe.utils import cint
-import frappe.defaults
-from six import text_type
-
-def get_sql_tables(q):
- if q.find('WHERE') != -1:
- tl = q.split('FROM')[1].split('WHERE')[0].split(',')
- elif q.find('GROUP BY') != -1:
- tl = q.split('FROM')[1].split('GROUP BY')[0].split(',')
- else:
- tl = q.split('FROM')[1].split('ORDER BY')[0].split(',')
- return [t.strip().strip('`')[3:] for t in tl]
-
-def get_parent_dt(dt):
- pdt = ''
- if frappe.db.sql('select name from `tabDocType` where istable=1 and name=%s', dt):
- import frappe.model.meta
- return frappe.model.meta.get_parent_dt(dt)
- return pdt
-
-def get_sql_meta(tl):
- std_columns = {
- 'owner':('Owner', '', '', '100'),
- 'creation':('Created on', 'Date', '', '100'),
- 'modified':('Last modified on', 'Date', '', '100'),
- 'modified_by':('Modified By', '', '', '100')
- }
-
- meta = {}
-
- for dt in tl:
- meta[dt] = std_columns.copy()
-
- # for table doctype, the ID is the parent id
- pdt = get_parent_dt(dt)
- if pdt:
- meta[dt]['parent'] = ('ID', 'Link', pdt, '200')
-
- # get the field properties from DocField
- res = frappe.db.sql("select fieldname, label, fieldtype, options, width \
- from tabDocField where parent=%s", dt)
- for r in res:
- if r[0]:
- meta[dt][r[0]] = (r[1], r[2], r[3], r[4]);
-
- # name
- meta[dt]['name'] = ('ID', 'Link', dt, '200')
-
- return meta
-
-def add_match_conditions(q, tl):
- from frappe.desk.reportview import build_match_conditions
- sl = []
- for dt in tl:
- s = build_match_conditions(dt)
- if s:
- sl.append(s)
-
- # insert the conditions
- if sl:
- condition_st = q.find('WHERE')!=-1 and ' AND ' or ' WHERE '
- condition_end = q.find('ORDER BY')!=-1 and 'ORDER BY' or 'LIMIT'
- condition_end = q.find('GROUP BY')!=-1 and 'GROUP BY' or condition_end
-
- if q.find('ORDER BY')!=-1 or q.find('LIMIT')!=-1 or q.find('GROUP BY')!=-1: # if query continues beyond conditions
- q = q.split(condition_end)
- q = q[0] + condition_st + '(' + ' OR '.join(sl) + ') ' + condition_end + q[1]
- else:
- q = q + condition_st + '(' + ' OR '.join(sl) + ')'
-
- return q
-
-def guess_type(m):
- """
- Returns fieldtype depending on the MySQLdb Description
- """
- if frappe.db.is_type_number(m):
- return 'Currency'
- elif m in frappe.is_type_datetime(m):
- return 'Date'
- else:
- return 'Data'
-
-def build_description_simple():
- colnames, coltypes, coloptions, colwidths = [], [], [], []
-
- for m in frappe.db.get_description():
- colnames.append(m[0])
- coltypes.append(guess_type[m[1]])
- coloptions.append('')
- colwidths.append('100')
-
- return colnames, coltypes, coloptions, colwidths
-
-def build_description_standard(meta, tl):
-
- desc = frappe.db.get_description()
-
- colnames, coltypes, coloptions, colwidths = [], [], [], []
-
- # merged metadata - used if we are unable to
- # get both the table name and field name from
- # the description - in case of joins
- merged_meta = {}
- for d in meta:
- merged_meta.update(meta[d])
-
- for f in desc:
- fn, dt = f[0], ''
- if '.' in fn:
- dt, fn = fn.split('.')
-
- if (not dt) and merged_meta.get(fn):
- # no "AS" given, find type from merged description
-
- desc = merged_meta[fn]
- colnames.append(desc[0] or fn)
- coltypes.append(desc[1] or '')
- coloptions.append(desc[2] or '')
- colwidths.append(desc[3] or '100')
-
- elif fn in meta.get(dt,{}):
- # type specified for a multi-table join
- # usually from Report Builder
-
- desc = meta[dt][fn]
- colnames.append(desc[0] or fn)
- coltypes.append(desc[1] or '')
- coloptions.append(desc[2] or '')
- colwidths.append(desc[3] or '100')
-
- else:
- # nothing found
- # guess
- colnames.append(fn)
- coltypes.append(guess_type(f[1]))
- coloptions.append('')
- colwidths.append('100')
-
- return colnames, coltypes, coloptions, colwidths
-
-@frappe.whitelist()
-def runquery(q='', ret=0, from_export=0):
- import frappe.utils
-
- formatted = cint(frappe.form_dict.get('formatted'))
-
- # CASE A: Simple Query
- # --------------------
- if frappe.form_dict.get('simple_query') or frappe.form_dict.get('is_simple'):
- if not q: q = frappe.form_dict.get('simple_query') or frappe.form_dict.get('query')
- if q.split()[0].lower() != 'select':
- raise Exception('Query must be a SELECT')
-
- as_dict = cint(frappe.form_dict.get('as_dict'))
- res = frappe.db.sql(q, as_dict = as_dict, as_list = not as_dict, formatted=formatted)
-
- # build colnames etc from metadata
- colnames, coltypes, coloptions, colwidths = [], [], [], []
-
- # CASE B: Standard Query
- # -----------------------
- else:
- if not q: q = frappe.form_dict.get('query')
-
- tl = get_sql_tables(q)
- meta = get_sql_meta(tl)
-
- q = add_match_conditions(q, tl)
-
- # replace special variables
- q = q.replace('__user', frappe.session.user)
- q = q.replace('__today', frappe.utils.nowdate())
-
- res = frappe.db.sql(q, as_list=1, formatted=formatted)
-
- colnames, coltypes, coloptions, colwidths = build_description_standard(meta, tl)
-
- # run server script
- # -----------------
- style, header_html, footer_html, page_template = '', '', '', ''
-
- out['colnames'] = colnames
- out['coltypes'] = coltypes
- out['coloptions'] = coloptions
- out['colwidths'] = colwidths
- out['header_html'] = header_html
- out['footer_html'] = footer_html
- out['page_template'] = page_template
-
- if style:
- out['style'] = style
-
- # just the data - return
- if ret==1:
- return res
-
- out['values'] = res
-
- # return num of entries
- qm = frappe.form_dict.get('query_max') or ''
- if qm and qm.strip():
- if qm.split()[0].lower() != 'select':
- raise Exception('Query (Max) must be a SELECT')
- if not frappe.form_dict.get('simple_query'):
- qm = add_match_conditions(qm, tl)
-
- out['n_values'] = frappe.utils.cint(frappe.db.sql(qm)[0][0])
-
-
-@frappe.whitelist()
-def runquery_csv():
- global out
-
- q = frappe.form_dict.get('query')
-
- rep_name = frappe.form_dict.get('report_name')
- if not frappe.form_dict.get('simple_query'):
-
- # Report Name
- if not rep_name:
- rep_name = get_sql_tables(q)[0]
-
- if not rep_name: rep_name = 'DataExport'
-
- rows = [[rep_name], out['colnames']] + out['values']
-
- from six import StringIO
- import csv
-
- f = StringIO()
- writer = csv.writer(f)
- for r in rows:
- # encode only unicode type strings and not int, floats etc.
- writer.writerow(map(lambda v: isinstance(v, text_type) and v.encode('utf-8') or v, r))
-
- f.seek(0)
- out['result'] = text_type(f.read(), 'utf-8')
- out['type'] = 'csv'
- out['doctype'] = rep_name
-
-def add_limit_to_query(query, args):
- """
- Add limit condition to query
- can be used by methods called in listing to add limit condition
- """
- if args.get('limit_page_length'):
- query += """
- limit %(limit_start)s, %(limit_page_length)s"""
-
- import frappe.utils
- args['limit_start'] = frappe.utils.cint(args.get('limit_start'))
- args['limit_page_length'] = frappe.utils.cint(args.get('limit_page_length'))
-
- return query, args
diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py
index a1cfd02132..08ef7ae485 100644
--- a/frappe/desk/query_report.py
+++ b/frappe/desk/query_report.py
@@ -462,6 +462,9 @@ def add_total_row(result, columns, meta = None):
@frappe.whitelist()
def get_data_for_custom_field(doctype, field):
+ if not frappe.has_permission(doctype, "read"):
+ frappe.throw(_("Not Permitted"), frappe.PermissionError)
+
value_map = frappe._dict(frappe.get_all(doctype,
fields=["name", field],
as_list=1))
diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py
index 6102be61ce..d4fc8833ae 100644
--- a/frappe/desk/reportview.py
+++ b/frappe/desk/reportview.py
@@ -36,6 +36,7 @@ def get_form_params():
data.pop('data', None)
data.pop('ignore_permissions', None)
data.pop('view', None)
+ data.pop('user', None)
if "csrf_token" in data:
del data["csrf_token"]
diff --git a/frappe/desk/treeview.py b/frappe/desk/treeview.py
index 2944f20a37..811143be03 100644
--- a/frappe/desk/treeview.py
+++ b/frappe/desk/treeview.py
@@ -6,12 +6,11 @@ import frappe
from frappe import _
@frappe.whitelist()
-def get_all_nodes(doctype, parent, tree_method, **filters):
+def get_all_nodes(doctype, label, parent, tree_method, **filters):
'''Recursively gets all data from tree nodes'''
if 'cmd' in filters:
del filters['cmd']
-
filters.pop('data', None)
tree_method = frappe.get_attr(tree_method)
@@ -20,7 +19,7 @@ def get_all_nodes(doctype, parent, tree_method, **filters):
frappe.throw(_("Not Permitted"), frappe.PermissionError)
data = tree_method(doctype, parent, **filters)
- out = [dict(parent=parent, data=data)]
+ out = [dict(parent=label, data=data)]
if 'is_root' in filters:
del filters['is_root']
diff --git a/frappe/email/doctype/newsletter/newsletter.js b/frappe/email/doctype/newsletter/newsletter.js
index 0f1e8dc57c..3277d8e9ee 100644
--- a/frappe/email/doctype/newsletter/newsletter.js
+++ b/frappe/email/doctype/newsletter/newsletter.js
@@ -14,9 +14,6 @@ frappe.ui.form.on('Newsletter', {
});
}, "fa fa-play", "btn-success");
}
- if (!doc.__islocal && cint(doc.email_sent)) {
- frm.set_df_property('schedule_send', "read_only", 1);
- }
frm.events.setup_dashboard(frm);
diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json
index 4804b3d6fa..1dd6115b43 100644
--- a/frappe/email/doctype/newsletter/newsletter.json
+++ b/frappe/email/doctype/newsletter/newsletter.json
@@ -15,7 +15,10 @@
"email_sent",
"newsletter_content",
"subject",
+ "content_type",
"message",
+ "message_md",
+ "message_html",
"send_unsubscribe_link",
"send_attachments",
"published",
@@ -37,8 +40,7 @@
"fieldname": "send_from",
"fieldtype": "Data",
"ignore_xss_filter": 1,
- "label": "Sender",
- "no_copy": 1
+ "label": "Sender"
},
{
"default": "0",
@@ -50,7 +52,8 @@
},
{
"fieldname": "newsletter_content",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Content"
},
{
"fieldname": "subject",
@@ -61,11 +64,12 @@
"reqd": 1
},
{
+ "depends_on": "eval: doc.content_type === 'Rich Text'",
"fieldname": "message",
"fieldtype": "Text Editor",
"in_list_view": 1,
"label": "Message",
- "reqd": 1
+ "mandatory_depends_on": "eval: doc.content_type === 'Rich Text'"
},
{
"default": "1",
@@ -87,16 +91,20 @@
"read_only": 1
},
{
+ "collapsible": 1,
"fieldname": "test_the_newsletter",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Testing"
},
{
"description": "A Lead with this Email Address should exist",
"fieldname": "test_email_id",
"fieldtype": "Data",
- "label": "Test Email Address"
+ "label": "Test Email Address",
+ "options": "Email"
},
{
+ "depends_on": "eval: doc.test_email_id",
"fieldname": "test_send",
"fieldtype": "Button",
"label": "Test",
@@ -117,7 +125,8 @@
"depends_on": "eval: doc.schedule_sending",
"fieldname": "schedule_send",
"fieldtype": "Datetime",
- "label": "Schedule Send"
+ "label": "Schedule Send",
+ "read_only_depends_on": "eval: doc.email_sent"
},
{
"default": "0",
@@ -125,11 +134,32 @@
"fieldtype": "Check",
"label": "Send Attachments"
},
+ {
+ "fieldname": "content_type",
+ "fieldtype": "Select",
+ "label": "Content Type",
+ "options": "Rich Text\nMarkdown\nHTML"
+ },
+ {
+ "depends_on": "eval:doc.content_type === 'Markdown'",
+ "fieldname": "message_md",
+ "fieldtype": "Markdown Editor",
+ "label": "Message (Markdown)",
+ "mandatory_depends_on": "eval:doc.content_type === 'Markdown'"
+ },
+ {
+ "depends_on": "eval:doc.content_type === 'HTML'",
+ "fieldname": "message_html",
+ "fieldtype": "HTML Editor",
+ "label": "Message (HTML)",
+ "mandatory_depends_on": "eval:doc.content_type === 'HTML'"
+ },
{
"default": "0",
"fieldname": "schedule_sending",
"fieldtype": "Check",
- "label": "Schedule Sending"
+ "label": "Schedule Sending",
+ "read_only_depends_on": "eval: doc.email_sent"
}
],
"has_web_view": 1,
@@ -139,7 +169,7 @@
"is_published_field": "published",
"links": [],
"max_attachments": 3,
- "modified": "2020-08-17 18:11:59.541686",
+ "modified": "2020-08-24 19:59:37.262500",
"modified_by": "Administrator",
"module": "Email",
"name": "Newsletter",
diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py
index 849c21f768..a4d60706eb 100755
--- a/frappe/email/doctype/newsletter/newsletter.py
+++ b/frappe/email/doctype/newsletter/newsletter.py
@@ -8,12 +8,9 @@ import frappe.utils
from frappe import throw, _
from frappe.website.website_generator import WebsiteGenerator
from frappe.utils.verified_command import get_signed_params, verify_request
-from frappe.utils.background_jobs import enqueue
from frappe.email.queue import send
from frappe.email.doctype.email_group.email_group import add_subscribers
-from frappe.utils import parse_addr, now_datetime
-from frappe.utils import validate_email_address
-
+from frappe.utils import parse_addr, now_datetime, markdown, validate_email_address
class Newsletter(WebsiteGenerator):
def onload(self):
@@ -29,8 +26,8 @@ class Newsletter(WebsiteGenerator):
def test_send(self, doctype="Lead"):
self.recipients = frappe.utils.split_emails(self.test_email_id)
- self.queue_all()
- frappe.msgprint(_("Scheduled to send to {0}").format(self.test_email_id))
+ self.queue_all(test_email=True)
+ frappe.msgprint(_("Test email sent to {0}").format(self.test_email_id))
def send_emails(self):
"""send emails to leads and customers"""
@@ -40,21 +37,13 @@ class Newsletter(WebsiteGenerator):
self.recipients = self.get_recipients()
if self.recipients:
- if getattr(frappe.local, "is_ajax", False):
- self.validate_send()
- # using default queue with a longer timeout as this isn't a scheduled task
- enqueue(send_newsletter, queue='default', timeout=6000, event='send_newsletter',
- newsletter=self.name)
-
- else:
- self.queue_all()
-
- frappe.msgprint(_("Scheduled to send to {0} recipients").format(len(self.recipients)))
+ self.queue_all()
+ frappe.msgprint(_("Email queued to {0} recipients").format(len(self.recipients)))
else:
frappe.msgprint(_("Newsletter should have atleast one recipient"))
- def queue_all(self):
+ def queue_all(self, test_email=False):
if not self.get("recipients"):
# in case it is called via worker
self.recipients = self.get_recipients()
@@ -80,7 +69,7 @@ class Newsletter(WebsiteGenerator):
frappe.throw(_("Unable to find attachment {0}").format(file.name))
send(recipients=self.recipients, sender=sender,
- subject=self.subject, message=self.message,
+ subject=self.subject, message=self.get_message(),
reference_doctype=self.doctype, reference_name=self.name,
add_unsubscribe_link=self.send_unsubscribe_link, attachments=attachments,
unsubscribe_method="/unsubscribe",
@@ -90,9 +79,18 @@ class Newsletter(WebsiteGenerator):
if not frappe.flags.in_test:
frappe.db.auto_commit_on_many_writes = False
- self.db_set("email_sent", 1)
- self.db_set("schedule_send", now_datetime())
- self.db_set("scheduled_to_send", len(self.recipients))
+ if not test_email:
+ self.db_set("email_sent", 1)
+ self.db_set("schedule_send", now_datetime())
+ self.db_set("scheduled_to_send", len(self.recipients))
+
+ def get_message(self):
+
+ return {
+ 'Rich Text': self.message,
+ 'Markdown': markdown(self.message_md),
+ 'HTML': self.message_html
+ }[self.content_type or 'Rich Text']
def get_recipients(self):
"""Get recipients from Email Group"""
diff --git a/frappe/email/doctype/newsletter/newsletter_list.js b/frappe/email/doctype/newsletter/newsletter_list.js
index e95d29545d..9ded6148e0 100644
--- a/frappe/email/doctype/newsletter/newsletter_list.js
+++ b/frappe/email/doctype/newsletter/newsletter_list.js
@@ -1,8 +1,10 @@
frappe.listview_settings['Newsletter'] = {
- add_fields: ["subject", "email_sent"],
+ add_fields: ["subject", "email_sent", "schedule_sending"],
get_indicator: function(doc) {
- if(doc.email_sent) {
+ if (doc.email_sent) {
return [__("Sent"), "green", "email_sent,=,Yes"];
+ } else if (doc.schedule_sending) {
+ return [__("Scheduled"), "orange", "email_sent,=,No|schedule_sending,=,Yes"];
} else {
return [__("Not Sent"), "orange", "email_sent,=,No"];
}
diff --git a/frappe/email/doctype/newsletter/test_newsletter.py b/frappe/email/doctype/newsletter/test_newsletter.py
index bb339165d3..ee7f123b7e 100644
--- a/frappe/email/doctype/newsletter/test_newsletter.py
+++ b/frappe/email/doctype/newsletter/test_newsletter.py
@@ -67,6 +67,7 @@ class TestNewsletter(unittest.TestCase):
"doctype": "Newsletter",
"subject": "_Test Newsletter",
"send_from": "Test Sender Print itemsFormats are rendered on the server side using the Jinja Templating Language. All forms have access to the doc object which contains information about the document that is being formatted. You can also access common utilities via the frappe module.
For styling, the Boostrap CSS framework is provided and you can enjoy the full range of classes.
\n<h3>{{ doc.select_print_heading or \"Invoice\" }}</h3>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Customer Name</div>\n\t<div class=\"col-md-9\">{{ doc.customer_name }}</div>\n</div>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Date</div>\n\t<div class=\"col-md-9\">{{ doc.get_formatted(\"invoice_date\") }}</div>\n</div>\n<table class=\"table table-bordered\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<th>Sr</th>\n\t\t\t<th>Item Name</th>\n\t\t\t<th>Description</th>\n\t\t\t<th class=\"text-right\">Qty</th>\n\t\t\t<th class=\"text-right\">Rate</th>\n\t\t\t<th class=\"text-right\">Amount</th>\n\t\t</tr>\n\t\t{%- for row in doc.items -%}\n\t\t<tr>\n\t\t\t<td style=\"width: 3%;\">{{ row.idx }}</td>\n\t\t\t<td style=\"width: 20%;\">\n\t\t\t\t{{ row.item_name }}\n\t\t\t\t{% if row.item_code != row.item_name -%}\n\t\t\t\t<br>Item Code: {{ row.item_code}}\n\t\t\t\t{%- endif %}\n\t\t\t</td>\n\t\t\t<td style=\"width: 37%;\">\n\t\t\t\t<div style=\"border: 0px;\">{{ row.description }}</div></td>\n\t\t\t<td style=\"width: 10%; text-align: right;\">{{ row.qty }} {{ row.uom or row.stock_uom }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"rate\", doc) }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"amount\", doc) }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\ndoc.get_formatted(\"[fieldname]\", [parent_doc]) | \n\t\t\tGet document value formatted as Date, Currency etc. Pass parent doc for curreny type fields. | \n\t\t
frappe.db.get_value(\"[doctype]\", \"[name]\", \"fieldname\") | \n\t\t\tGet value from another document. | \n\t\t
Print Formats are rendered on the server side using the Jinja Templating Language. All forms have access to the doc object which contains information about the document that is being formatted. You can also access common utilities via the frappe module.
For styling, the Boostrap CSS framework is provided and you can enjoy the full range of classes.
\n<h3>{{ doc.select_print_heading or \"Invoice\" }}</h3>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Customer Name</div>\n\t<div class=\"col-md-9\">{{ doc.customer_name }}</div>\n</div>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Date</div>\n\t<div class=\"col-md-9\">{{ doc.get_formatted(\"invoice_date\") }}</div>\n</div>\n<table class=\"table table-bordered\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<th>Sr</th>\n\t\t\t<th>Item Name</th>\n\t\t\t<th>Description</th>\n\t\t\t<th class=\"text-right\">Qty</th>\n\t\t\t<th class=\"text-right\">Rate</th>\n\t\t\t<th class=\"text-right\">Amount</th>\n\t\t</tr>\n\t\t{%- for row in doc.items -%}\n\t\t<tr>\n\t\t\t<td style=\"width: 3%;\">{{ row.idx }}</td>\n\t\t\t<td style=\"width: 20%;\">\n\t\t\t\t{{ row.item_name }}\n\t\t\t\t{% if row.item_code != row.item_name -%}\n\t\t\t\t<br>Item Code: {{ row.item_code}}\n\t\t\t\t{%- endif %}\n\t\t\t</td>\n\t\t\t<td style=\"width: 37%;\">\n\t\t\t\t<div style=\"border: 0px;\">{{ row.description }}</div></td>\n\t\t\t<td style=\"width: 10%; text-align: right;\">{{ row.qty }} {{ row.uom or row.stock_uom }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"rate\", doc) }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"amount\", doc) }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\ndoc.get_formatted(\"[fieldname]\", [parent_doc]) | \n\t\t\tGet document value formatted as Date, Currency, etc. Pass parent doc for currency type fields. | \n\t\t
frappe.db.get_value(\"[doctype]\", \"[name]\", \"fieldname\") | \n\t\t\tGet value from another document. | \n\t\t
diff --git a/frappe/website/doctype/blog_post/test_blog_post.py b/frappe/website/doctype/blog_post/test_blog_post.py index 15634a7caf..cdf53122b2 100644 --- a/frappe/website/doctype/blog_post/test_blog_post.py +++ b/frappe/website/doctype/blog_post/test_blog_post.py @@ -3,10 +3,14 @@ from __future__ import unicode_literals import frappe import unittest +from bs4 import BeautifulSoup +import re from frappe.utils import set_request from frappe.website.render import render from frappe.utils import random_string +from frappe.website.doctype.blog_post.blog_post import get_blog_list +from frappe.website.website_generator import WebsiteGenerator class TestBlogPost(unittest.TestCase): def test_generator_view(self): @@ -32,12 +36,62 @@ class TestBlogPost(unittest.TestCase): self.assertTrue(response.status_code, 404) -def make_test_blog(): - if not frappe.db.exists('Blog Category', 'Test Blog Category'): + def test_category_link(self): + # Make a temporary Blog Post (and a Blog Category) + blog = make_test_blog() + + # Visit the blog post page + set_request(path=blog.route) + blog_page_response = render() + blog_page_html = frappe.safe_decode(blog_page_response.get_data()) + + # On blog post page find link to the category page + soup = BeautifulSoup(blog_page_html, "lxml") + category_page_link = list(soup.find_all('a', href=re.compile(blog.blog_category)))[0] + category_page_url = category_page_link["href"] + + # Visit the category page (by following the link found in above stage) + set_request(path=category_page_url) + category_page_response = render() + category_page_html = frappe.safe_decode(category_page_response.get_data()) + + # Category page should contain the blog post title + self.assertIn(blog.title, category_page_html) + + # Cleanup afterwords + frappe.delete_doc("Blog Post", blog.name) + frappe.delete_doc("Blog Category", blog.blog_category) + + def test_blog_pagination(self): + # Create some Blog Posts for a Blog Category + category_title, blogs, BLOG_COUNT = "List Category", [], 4 + + for index in range(BLOG_COUNT): + blog = make_test_blog(category_title) + blogs.append(blog) + + filters = frappe._dict({"blog_category": scrub(category_title)}) + # Assert that get_blog_list returns results as expected + + self.assertEqual(len(get_blog_list(None, None, filters, 0, 3)), 3) + self.assertEqual(len(get_blog_list(None, None, filters, 0, BLOG_COUNT)), BLOG_COUNT) + self.assertEqual(len(get_blog_list(None, None, filters, 0, 2)), 2) + self.assertEqual(len(get_blog_list(None, None, filters, 2, BLOG_COUNT)), 2) + + # Cleanup Blog Post and linked Blog Category + for blog in blogs: + frappe.delete_doc(blog.doctype, blog.name) + frappe.delete_doc("Blog Category", blogs[0].blog_category) + +def scrub(text): + return WebsiteGenerator.scrub(None, text) + +def make_test_blog(category_title="Test Blog Category"): + category_name = scrub(category_title) + if not frappe.db.exists('Blog Category', category_name): frappe.get_doc(dict( doctype = 'Blog Category', - category_name = 'Test Blog Category', - title='Test Blog Category')).insert() + title=category_title)).insert() if not frappe.db.exists('Blogger', 'test-blogger'): frappe.get_doc(dict( doctype = 'Blogger', @@ -45,7 +99,7 @@ def make_test_blog(): full_name='Test Blogger')).insert() test_blog = frappe.get_doc(dict( doctype = 'Blog Post', - blog_category = 'Test Blog Category', + blog_category = category_name, blogger = 'test-blogger', title = random_string(20), route = random_string(20), diff --git a/frappe/website/doctype/blog_post/test_records.json b/frappe/website/doctype/blog_post/test_records.json index 79a3525801..4b29eadfa4 100644 --- a/frappe/website/doctype/blog_post/test_records.json +++ b/frappe/website/doctype/blog_post/test_records.json @@ -1,6 +1,6 @@ [ { - "blog_category": "_Test Blog Category", + "blog_category": "-test-blog-category", "blog_intro": "Test Blog Intro", "blogger": "_Test Blogger", "content": "Test Blog Content", @@ -9,7 +9,7 @@ "published": 1 }, { - "blog_category": "_Test Blog Category 1", + "blog_category": "-test-blog-category-1", "blog_intro": "Test Blog Intro", "blogger": "_Test Blogger", "content": "Test Blog Content", @@ -18,7 +18,7 @@ "published": 1 }, { - "blog_category": "_Test Blog Category 1", + "blog_category": "-test-blog-category-1", "blog_intro": "Test Blog Intro", "blogger": "_Test Blogger 1", "content": "Test Blog Content", @@ -27,7 +27,7 @@ "published": 0 }, { - "blog_category": "_Test Blog Category 1", + "blog_category": "-test-blog-category-1", "blog_intro": "Test Blog Intro", "blogger": "_Test Blogger 2", "content": "Test Blog Content", @@ -35,4 +35,4 @@ "title": "_Test Blog Post 3", "published": 0 } -] \ No newline at end of file +] diff --git a/frappe/website/doctype/web_page/web_page.json b/frappe/website/doctype/web_page/web_page.json index 955b34f44e..42ca81391e 100644 --- a/frappe/website/doctype/web_page/web_page.json +++ b/frappe/website/doctype/web_page/web_page.json @@ -126,6 +126,7 @@ "depends_on": "eval:doc.content_type==='Markdown'", "fieldname": "main_section_md", "fieldtype": "Markdown Editor", + "ignore_xss_filter": 1, "label": "Main Section (Markdown)" }, { @@ -294,7 +295,7 @@ "is_published_field": "published", "links": [], "max_attachments": 20, - "modified": "2020-08-07 10:55:54.885448", + "modified": "2020-08-31 16:55:52.015249", "modified_by": "Administrator", "module": "Website", "name": "Web Page", diff --git a/frappe/website/web_template/section_with_tabs/section_with_tabs.html b/frappe/website/web_template/section_with_tabs/section_with_tabs.html index 0c206b1c48..9a5bb20e0f 100644 --- a/frappe/website/web_template/section_with_tabs/section_with_tabs.html +++ b/frappe/website/web_template/section_with_tabs/section_with_tabs.html @@ -1,5 +1,8 @@
{{ subtitle }}
+{%- endif -%}