From 1849c4f2cf3715e7b3afabb823a066ebee845ba9 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 30 Sep 2020 11:24:08 +0200 Subject: [PATCH 001/163] fix: improve ux of standard web templates --- frappe/website/doctype/web_template/web_template.js | 12 +++++++++++- frappe/website/doctype/web_template/web_template.py | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/frappe/website/doctype/web_template/web_template.js b/frappe/website/doctype/web_template/web_template.js index 99dc02bfbb..1488dbf1be 100644 --- a/frappe/website/doctype/web_template/web_template.js +++ b/frappe/website/doctype/web_template/web_template.js @@ -2,11 +2,21 @@ // For license information, please see license.txt frappe.ui.form.on('Web Template', { - refresh(frm) { + refresh: function(frm) { if (!frappe.boot.developer_mode && frm.doc.standard) { frm.disable_form(); } frm.toggle_display('standard', frappe.boot.developer_mode); + frm.toggle_display('template', !frm.doc.standard); + }, + standard: function(frm) { + if (!frm.doc.standard) { + // If standard changes from true to false, hide template until + // the next save. Changes will get overwritten from the backend + // on save and should not be possible in the UI. + frm.toggle_display('template', false); + frm.save(); + } } }); diff --git a/frappe/website/doctype/web_template/web_template.py b/frappe/website/doctype/web_template/web_template.py index 1b6e67a9d0..811e4f5ad9 100644 --- a/frappe/website/doctype/web_template/web_template.py +++ b/frappe/website/doctype/web_template/web_template.py @@ -35,6 +35,7 @@ class WebTemplate(Document): if self.standard: export_to_files(record_list=[["Web Template", self.name]], create_init=True) self.create_template_file() + self.template = "" # standard to custom was_standard = (self.get_doc_before_save() or {}).get("standard") From a507de886316462dc15dd6f85bc268052fe18a0f Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 2 Oct 2020 17:01:58 +0200 Subject: [PATCH 002/163] fix: allow empty type for Web Templates that are not sections --- frappe/website/doctype/web_template/web_template.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/website/doctype/web_template/web_template.json b/frappe/website/doctype/web_template/web_template.json index fed7008cdb..492c265e55 100644 --- a/frappe/website/doctype/web_template/web_template.json +++ b/frappe/website/doctype/web_template/web_template.json @@ -41,7 +41,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Type", - "options": "Section\nNavbar\nFooter" + "options": "\nSection\nNavbar\nFooter" }, { "depends_on": "standard", @@ -58,7 +58,7 @@ "link_fieldname": "web_template" } ], - "modified": "2020-09-25 00:48:57.902292", + "modified": "2020-10-02 17:00:52.512209", "modified_by": "Administrator", "module": "Website", "name": "Web Template", From 4f82ebcef717665265efc2230fc936dfa206ddaf Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 5 Oct 2020 15:32:03 +0530 Subject: [PATCH 003/163] refactor: Fix excel export - Remove unnecessary code --- frappe/desk/query_report.py | 110 +++++++++++++----------------------- 1 file changed, 38 insertions(+), 72 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 5a9aae8435..95f93b612e 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -74,23 +74,25 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None) res = report.execute_script_report(filters) columns, result, message, chart, report_summary, skip_total_row = ljust_list(res, 6) + columns = [get_column_as_dict(col) for col in columns] - if report.custom_columns: - # Original query columns, needed to reorder data as per custom columns - query_columns = columns - # Reordered columns - columns = json.loads(report.custom_columns) - - result = reorder_data_for_custom_columns(columns, query_columns, result) - - result = add_data_to_custom_columns(columns, result) + # convert to list of dicts + result = normalize_result(result, columns) if custom_columns: - result = add_data_to_custom_columns(custom_columns, result) - for custom_column in custom_columns: columns.insert(custom_column["insert_after_index"] + 1, custom_column) + custom_columns = custom_columns or [] + + if isinstance(report.custom_columns, string_types): + custom_columns += json.loads(report.custom_columns) or [] + else: + custom_columns += report.custom_columns or [] + + if custom_columns: + result = add_custom_column_data(custom_columns, result) + if result: result = get_filtered_data(report.ref_doctype, columns, result, user) @@ -109,6 +111,20 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None) or 0, } +def normalize_result(result, columns): + # Converts to list of dicts from list of lists/tuples + data = [] + column_names = [column["fieldname"] for column in columns] + if result and isinstance(result[0], (list, tuple)): + for row in result: + row_obj = {} + for idx, column_name in enumerate(column_names): + row_obj[column_name] = row[idx] + data.append(row_obj) + else: + data = result + + return data @frappe.whitelist() def background_enqueue_run(report_name, filters=None, user=None): @@ -221,69 +237,17 @@ def run( return result -def add_data_to_custom_columns(columns, result): - custom_fields_data = get_data_for_custom_report(columns) +def add_custom_column_data(custom_columns, result): + custom_column_data = get_data_for_custom_report(custom_columns) - data = [] - for row in result: - row_obj = {} - if isinstance(row, tuple): - row = list(row) + for column in custom_columns: + key = (column.get('doctype'), column.get('fieldname')) + if key in custom_column_data: + for row in result: + row_reference = row[column.get('link_field')] + row[column.get('fieldname')] = custom_column_data.get(key).get(row_reference) - if isinstance(row, list): - for idx, column in enumerate(columns): - if column.get("link_field"): - row_obj[column["fieldname"]] = None - row.insert(idx, None) - else: - row_obj[column["fieldname"]] = row[idx] - data.append(row_obj) - else: - data.append(row) - - for row in data: - for column in columns: - if column.get("link_field"): - fieldname = column["fieldname"] - key = (column["doctype"], fieldname) - link_field = column["link_field"] - row[fieldname] = custom_fields_data.get(key, {}).get( - row.get(link_field) - ) - - return data - - -def reorder_data_for_custom_columns(custom_columns, columns, result): - if not result: - return [] - - columns = [get_column_as_dict(col) for col in columns] - if isinstance(result[0], list) or isinstance(result[0], tuple): - # If the result is a list of lists - custom_column_names = [col["label"] for col in custom_columns] - original_column_names = [col["label"] for col in columns] - return get_columns_from_list(custom_column_names, original_column_names, result) - else: - # columns do not need to be reordered if result is a list of dicts - return result - - -def get_columns_from_list(columns, target_columns, result): - reordered_result = [] - - for res in result: - r = [] - for col_name in columns: - try: - idx = target_columns.index(col_name) - r.append(res[idx]) - except ValueError: - pass - - reordered_result.append(r) - - return reordered_result + return result def get_prepared_report_result(report, filters, dn="", user=None): @@ -755,6 +719,8 @@ def get_column_as_dict(col): col_dict["fieldtype"], col_dict["options"] = col[1].split("/") else: col_dict["fieldtype"] = col[1] + if len(col) == 3: + col_dict["width"] = col[2] col_dict["label"] = col[0] col_dict["fieldname"] = frappe.scrub(col[0]) From f8afe32ea4e0cd610add5fef0a654f5a81fe0f49 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 5 Oct 2020 22:20:59 +0530 Subject: [PATCH 004/163] fix: Scrub fieldname [wip] --- frappe/desk/query_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 95f93b612e..e8a03a1552 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -244,7 +244,7 @@ def add_custom_column_data(custom_columns, result): key = (column.get('doctype'), column.get('fieldname')) if key in custom_column_data: for row in result: - row_reference = row[column.get('link_field')] + row_reference = row[frappe.scrub(column.get('link_field'))] row[column.get('fieldname')] = custom_column_data.get(key).get(row_reference) return result From 51f5bb4811032efa8ec468218d5a9fcd54530c66 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 5 Oct 2020 23:11:10 +0530 Subject: [PATCH 005/163] fix: Preserve column order --- frappe/desk/query_report.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index e8a03a1552..922c50b036 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -75,23 +75,25 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None) columns, result, message, chart, report_summary, skip_total_row = ljust_list(res, 6) columns = [get_column_as_dict(col) for col in columns] + report_column_names = [col["fieldname"] for col in columns] # convert to list of dicts result = normalize_result(result, columns) + if report.custom_columns: + # saved columns (with custom columns / with different column order) + columns = json.loads(report.custom_columns) + + # unsaved custom_columns if custom_columns: for custom_column in custom_columns: columns.insert(custom_column["insert_after_index"] + 1, custom_column) - custom_columns = custom_columns or [] + # all columns which are not in original report + report_custom_columns = [column for column in columns if column["fieldname"] not in report_column_names] - if isinstance(report.custom_columns, string_types): - custom_columns += json.loads(report.custom_columns) or [] - else: - custom_columns += report.custom_columns or [] - - if custom_columns: - result = add_custom_column_data(custom_columns, result) + if report_custom_columns: + result = add_custom_column_data(report_custom_columns, result) if result: result = get_filtered_data(report.ref_doctype, columns, result, user) From 6e22a48e224f60921025cba1f3f4a0136a5ee93a Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 9 Oct 2020 12:10:32 +0530 Subject: [PATCH 006/163] feat: reset scroll position on list paging --- frappe/public/js/frappe/list/base_list.js | 16 +++++++++++++--- frappe/public/js/frappe/list/list_view.js | 17 ++++++++++++++++- frappe/public/js/frappe/utils/utils.js | 22 +++++++++++++--------- frappe/public/less/list.less | 9 +++++++++ 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index bbe2fa2f95..af120b5a0b 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -308,7 +308,6 @@ frappe.views.BaseList = class BaseList { this.$paging_area.on('click', '.btn-paging, .btn-more', e => { const $this = $(e.currentTarget); - if ($this.is('.btn-paging')) { // set active button this.$paging_area.find('.btn-paging').removeClass('btn-info'); @@ -316,11 +315,22 @@ frappe.views.BaseList = class BaseList { this.start = 0; this.page_length = $this.data().value; - this.refresh(); } else if ($this.is('.btn-more')) { this.start = this.start + this.page_length; - this.refresh(); } + + // Trigger page change event so that scroll position can be set + const end = this.data.length; + const scroll_position = frappe.utils.get_scroll_position($(e.currentTarget)); + let trigger_scroll = () => { + $(document.body).trigger('pageChange', [scroll_position, end]); + }; + + frappe.run_serially([ + () => this.refresh(), + () => trigger_scroll(), + ]); + }); } diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 4d8121ebd6..06757229e6 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -243,7 +243,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } refresh(refresh_header=false) { - super.refresh().then(() => { + return super.refresh().then(() => { this.render_header(refresh_header); }); } @@ -856,6 +856,21 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { this.setup_realtime_updates(); this.setup_action_handler(); this.setup_keyboard_navigation(); + this.setup_scroll_on_page_change(); + } + + setup_scroll_on_page_change() { + if (!this.view_name == 'List') return; + + // Set scroll position to the last row and highlight the row + $(document.body).on('pageChange', (e, scroll_position, end) => { + const should_scroll = this.data.length > end; + if (should_scroll) { + const $last_row = this.$result.find('.list-row-container').eq(end-1); + frappe.utils.scroll_to(scroll_position, true, 50); + $last_row.addClass('yellow-highlight'); + } + }); } setup_keyboard_navigation() { diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index b8eeefb046..5547c52ef1 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -122,18 +122,17 @@ Object.assign(frappe.utils, {

'); return content.html(); }, - scroll_to: function(element, animate, additional_offset, element_to_be_scrolled) { + scroll_to: function(element, animate=true, additional_offset, element_to_be_scrolled) { element_to_be_scrolled = element_to_be_scrolled || $("html, body"); - var y = 0; - if (element && typeof element==="number") { - y = element; - } else if(element) { - var header_offset = $(".navbar").height() + $(".page-head").height(); - var y = $(element).offset().top - header_offset - cint(additional_offset); + let y = 0; + if (element) { + y = typeof element == "number" + ? element - cint(additional_offset) + : this.get_scroll_position(element, additional_offset); } - if(y < 0) { + if (y < 0) { y = 0; } @@ -142,13 +141,18 @@ Object.assign(frappe.utils, { return; } - if (animate !== false) { + if (animate) { element_to_be_scrolled.animate({ scrollTop: y }); } else { element_to_be_scrolled.scrollTop(y); } }, + get_scroll_position: function(element, additional_offset) { + let header_offset = $(".navbar").height() + $(".page-head").height(); + let scroll_top = $(element).offset().top - header_offset - cint(additional_offset); + return scroll_top; + }, filter_dict: function(dict, filters) { var ret = []; if(typeof filters=='string') { diff --git a/frappe/public/less/list.less b/frappe/public/less/list.less index 4c7d04406d..51409db82e 100644 --- a/frappe/public/less/list.less +++ b/frappe/public/less/list.less @@ -50,6 +50,15 @@ } } +@keyframes yellow-highlight { + 0% {background-color: @light-yellow;} + 100% {background-color: transparent;} +} + +.yellow-highlight { + animation: yellow-highlight 2s ease-in-out; +} + body.no-list-sidebar { [data-page-route^="List/"] { @media (min-width: @screen-md) { From 50d2f6bb0816888ef4dd9c5103259125de84c9ae Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 10 Oct 2020 09:54:17 +0530 Subject: [PATCH 007/163] test: Use .get to avoid KeyError --- frappe/core/doctype/report/test_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/report/test_report.py b/frappe/core/doctype/report/test_report.py index 805b903300..78904afc5f 100644 --- a/frappe/core/doctype/report/test_report.py +++ b/frappe/core/doctype/report/test_report.py @@ -27,7 +27,7 @@ class TestReport(unittest.TestCase): columns, data = report.get_data(filters={'user': 'Administrator', 'doctype': 'DocType'}) self.assertEqual(columns[0].get('label'), 'Name') self.assertEqual(columns[1].get('label'), 'Module') - self.assertTrue('User' in [d[0] for d in data]) + self.assertTrue('User' in [d.get('name') for d in data]) def test_report_permissions(self): frappe.set_user('test@example.com') From 5dea5e227cf191dac63c7f914cf2ed339c1dc8e9 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 13 Oct 2020 16:08:59 +0530 Subject: [PATCH 008/163] test: Add tests to validate custom report data --- frappe/core/doctype/report/test_report.py | 47 +++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/frappe/core/doctype/report/test_report.py b/frappe/core/doctype/report/test_report.py index 78904afc5f..2d2290e448 100644 --- a/frappe/core/doctype/report/test_report.py +++ b/frappe/core/doctype/report/test_report.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe, json, os import unittest +from frappe.desk.query_report import run, save_report, get_report_doc test_records = frappe.get_test_records('Report') test_dependencies = ['User'] @@ -29,6 +30,52 @@ class TestReport(unittest.TestCase): self.assertEqual(columns[1].get('label'), 'Module') self.assertTrue('User' in [d.get('name') for d in data]) + def test_custom_report(self): + custom_report_name = save_report( + 'Permitted Documents For User', + 'Permitted Documents For User Custom', + json.dumps([{ + 'fieldname': 'email', + 'fieldtype': 'Data', + 'label': 'Email', + 'insert_after_index': 0, + 'link_field': 'name', + 'doctype': 'User', + 'options': 'Email', + 'width': 100, + 'id':'email', + 'name': 'Email' + }])) + custom_report = frappe.get_doc('Report', custom_report_name) + columns, result = custom_report.run_query_report( + filters={ + 'user': 'Administrator', + 'doctype': 'User' + }, user=frappe.session.user) + + self.assertListEqual(['email'], [column.get('fieldname') for column in columns]) + self.assertDictEqual({'name': 'Administrator', 'user_type': 'System User', 'email': 'admin@example.com'}, result[0]) + + def test_report_with_custom_column(self): + response = run('Permitted Documents For User', + filters={'user': 'Administrator', 'doctype': 'User'}, + custom_columns=[{ + 'fieldname': 'email', + 'fieldtype': 'Data', + 'label': 'Email', + 'insert_after_index': 0, + 'link_field': 'name', + 'doctype': 'User', + 'options': 'Email', + 'width': 100, + 'id':'email', + 'name': 'Email' + }]) + result = response.get('result') + columns = response.get('columns') + self.assertListEqual(['name', 'email', 'user_type'], [column.get('fieldname') for column in columns]) + self.assertDictEqual({'name': 'Administrator', 'user_type': 'System User', 'email': 'admin@example.com'}, result[0]) + def test_report_permissions(self): frappe.set_user('test@example.com') frappe.db.sql("""delete from `tabHas Role` where parent = %s From ab368994b05c1071c0d7460c73af36406c9ae27f Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 13 Oct 2020 16:09:29 +0530 Subject: [PATCH 009/163] fix: Use get to avoid key error --- frappe/desk/query_report.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 922c50b036..4e7cbd9604 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -195,14 +195,7 @@ def get_script(report_name): @frappe.whitelist() @frappe.read_only() -def run( - report_name, - filters=None, - user=None, - ignore_prepared_report=False, - custom_columns=None, -): - +def run(report_name, filters=None, user=None, ignore_prepared_report=False, custom_columns=None): report = get_report_doc(report_name) if not user: user = frappe.session.user @@ -246,7 +239,8 @@ def add_custom_column_data(custom_columns, result): key = (column.get('doctype'), column.get('fieldname')) if key in custom_column_data: for row in result: - row_reference = row[frappe.scrub(column.get('link_field'))] + row_reference = row.get(scrub(column.get('link_field'))) + if not row_reference: continue row[column.get('fieldname')] = custom_column_data.get(key).get(row_reference) return result From f7e45d1acddd76fac171ad7ee290663f47efc8ef Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Tue, 13 Oct 2020 15:28:06 +0200 Subject: [PATCH 010/163] fix: show headline instead of save --- frappe/website/doctype/web_template/web_template.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/doctype/web_template/web_template.js b/frappe/website/doctype/web_template/web_template.js index 1488dbf1be..def4e2dab1 100644 --- a/frappe/website/doctype/web_template/web_template.js +++ b/frappe/website/doctype/web_template/web_template.js @@ -16,7 +16,7 @@ frappe.ui.form.on('Web Template', { // the next save. Changes will get overwritten from the backend // on save and should not be possible in the UI. frm.toggle_display('template', false); - frm.save(); + frm.dashboard.set_headline(__('Please save to edit the template.')); } } }); From 80fad21311482ea67699613b0ba4c43d4879fe63 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 14 Oct 2020 15:54:06 +0530 Subject: [PATCH 011/163] chore: add as_list parameter to msgprint --- frappe/__init__.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 554f1f9747..ec032fef19 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -312,7 +312,7 @@ def log(msg): debug_log.append(as_unicode(msg)) -def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, alert=False, primary_action=None, is_minimizable=None, wide=None): +def msgprint(msg, title=None, raise_exception=0, as_table=False, as_list=False, indicator=None, alert=False, primary_action=None, is_minimizable=None, wide=None): """Print a message to the user (via HTTP response). Messages are sent in the `__server_messages` property in the response JSON and shown in a pop-up / modal. @@ -321,6 +321,7 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, :param title: [optional] Message title. :param raise_exception: [optional] Raise given exception and show message. :param as_table: [optional] If `msg` is a list of lists, render as HTML table. + :param as_list: [optional] If `msg` is a list, render as un-ordered list. :param primary_action: [optional] Bind a primary server/client side action. :param is_minimizable: [optional] Allow users to minimize the modal :param wide: [optional] Show wide modal @@ -356,6 +357,16 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, out.message = '''{}
'''.format(table_rows) + + if as_list and type(msg) in (list, tuple): + if len(msg) > 1: + list_rows = '' + for row in msg: + list_rows += '
  • {}
  • '.format(row) + + out.message = '''
      {}
    '''.format(list_rows) + elif len(msg) == 1: + out.message = msg[0] if flags.print_messages and out.message: print(f"Message: {repr(out.message).encode('utf-8')}") From 350bc0258fc677ca0a64727d0f7423abc14c1a3e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 14 Oct 2020 16:02:02 +0530 Subject: [PATCH 012/163] fix: pass as_list to frappe.throw --- frappe/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index ec032fef19..8e6aaf71af 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -416,12 +416,12 @@ def clear_last_message(): if len(local.message_log) > 0: local.message_log = local.message_log[:-1] -def throw(msg, exc=ValidationError, title=None, is_minimizable=None, wide=None): +def throw(msg, exc=ValidationError, title=None, is_minimizable=None, wide=None, **kwargs): """Throw execption and show message (`msgprint`). :param msg: Message. :param exc: Exception class. Default `frappe.ValidationError`""" - msgprint(msg, raise_exception=exc, title=title, indicator='red', is_minimizable=is_minimizable, wide=wide) + msgprint(msg, raise_exception=exc, title=title, indicator='red', is_minimizable=is_minimizable, wide=wide, **kwargs) def emit_js(js, user=False, **kwargs): if user == False: From 2ad5f1ea24afad2242c66ae9f4bd051f36ae8416 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 14 Oct 2020 19:13:52 +0530 Subject: [PATCH 013/163] fix: handle FileAlreadyAttachedException while pulling email --- frappe/email/receive.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 9ba080bfda..03aedec066 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -540,6 +540,8 @@ class Email: except MaxFileSizeReachedError: # WARNING: bypass max file size exception pass + except frappe.FileAlreadyAttachedException: + pass except frappe.DuplicateEntryError: # same file attached twice?? pass From d04e6cc95f1b10e35cde634c7f21db458b77d321 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 14 Oct 2020 21:35:58 +0530 Subject: [PATCH 014/163] fix: handle msgprint html on client side --- frappe/__init__.py | 22 +++------------------- frappe/public/js/frappe/ui/messages.js | 13 +++++++++++++ 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 8e6aaf71af..1144b228c0 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -347,26 +347,10 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, as_list=False, return if as_table and type(msg) in (list, tuple): - - table_rows = '' - for row in msg: - table_row_data = '' - for data in row: - table_row_data += '{}'.format(data) - table_rows += '{}'.format(table_row_data) - - out.message = '''{}
    '''.format(table_rows) + out.as_table = 1 - if as_list and type(msg) in (list, tuple): - if len(msg) > 1: - list_rows = '' - for row in msg: - list_rows += '
  • {}
  • '.format(row) - - out.message = '''
      {}
    '''.format(list_rows) - elif len(msg) == 1: - out.message = msg[0] + if as_list and type(msg) in (list, tuple) and len(msg) > 1: + out.as_list = 1 if flags.print_messages and out.message: print(f"Message: {repr(out.message).encode('utf-8')}") diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index c37cc41650..bf1b13b424 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -128,6 +128,19 @@ frappe.msgprint = function(msg, title, is_minimizable) { data.indicator = 'blue'; } + if (data.as_list) { + const list_rows = data.message.map(m => `
  • ${m}
  • `).join(''); + data.message = `
      ${list_rows}
    `; + } + + if (data.as_table) { + const rows = data.message.map(row => { + const cols = row.map(col => `${col}`).join(''); + return `${cols}` + }).join(''); + data.message = `${rows}
    `; + } + if(data.message instanceof Array) { data.message.forEach(function(m) { frappe.msgprint(m); From 2e74cf2c77b275d25ae10fe48aaa5e13bb963258 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 14 Oct 2020 21:38:18 +0530 Subject: [PATCH 015/163] fix: missing semicolon --- frappe/public/js/frappe/ui/messages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index bf1b13b424..9f40c59819 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -136,7 +136,7 @@ frappe.msgprint = function(msg, title, is_minimizable) { if (data.as_table) { const rows = data.message.map(row => { const cols = row.map(col => `${col}`).join(''); - return `${cols}` + return `${cols}`; }).join(''); data.message = `${rows}
    `; } From c0494aac5abc57e9a6cf4fde23518f4169a98777 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 16 Oct 2020 10:46:56 +0530 Subject: [PATCH 016/163] fix: Handle empty row --- frappe/desk/query_report.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 4e7cbd9604..c577426be5 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -239,8 +239,10 @@ def add_custom_column_data(custom_columns, result): key = (column.get('doctype'), column.get('fieldname')) if key in custom_column_data: for row in result: - row_reference = row.get(scrub(column.get('link_field'))) - if not row_reference: continue + row_reference = row.get(column.get('link_field')) + # possible if the row is empty + if not row_reference: + continue row[column.get('fieldname')] = custom_column_data.get(key).get(row_reference) return result From 5173b21bdbb061b24c07bba4a35f26911e61aaa1 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 16 Oct 2020 13:40:32 +0530 Subject: [PATCH 017/163] refactor: Make export_query code more readable --- frappe/desk/query_report.py | 60 +++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index c577426be5..dd318c8de6 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -12,6 +12,7 @@ from frappe.modules import scrub, get_module_path from frappe.utils import ( flt, cint, + cstr, get_html_format, get_url_to_form, gzip_decompress, @@ -305,31 +306,27 @@ def get_prepared_report_result(report, filters, dn="", user=None): @frappe.whitelist() def export_query(): """export from query reports""" - data = frappe._dict(frappe.local.form_dict) - - del data["cmd"] - if "csrf_token" in data: - del data["csrf_token"] + data.pop("cmd", None) + data.pop("csrf_token", None) if isinstance(data.get("filters"), string_types): filters = json.loads(data["filters"]) - if isinstance(data.get("report_name"), string_types): + + if data.get("report_name"): report_name = data["report_name"] frappe.permissions.can_export( frappe.get_cached_value("Report", report_name, "ref_doctype"), raise_exception=True, ) - if isinstance(data.get("file_format_type"), string_types): - file_format_type = data["file_format_type"] - custom_columns = frappe.parse_json(data["custom_columns"]) + file_format_type = data.get("file_format_type") + custom_columns = frappe.parse_json(data.get("custom_columns", "[]")) + include_indentation = data.get("include_indentation") + visible_idx = data.get("visible_idx") - include_indentation = data["include_indentation"] - if isinstance(data.get("visible_idx"), string_types): - visible_idx = json.loads(data.get("visible_idx")) - else: - visible_idx = None + if isinstance(visible_idx, string_types): + visible_idx = json.loads(visible_idx) if file_format_type == "Excel": data = run(report_name, filters, custom_columns=custom_columns) @@ -384,28 +381,27 @@ def handle_duration_fieldtype_values(result, columns): def build_xlsx_data(columns, data, visible_idx, include_indentation): result = [[]] - # add column headings - for idx in range(len(data.columns)): - if not columns[idx].get("hidden"): - result[0].append(columns[idx]["label"]) + for column in data.columns: + if column.get("hidden"): + continue + result[0].append(column["label"]) # build table from result - for i, row in enumerate(data.result): + for row_idx, row in enumerate(data.result): # only pick up rows that are visible in the report - if i in visible_idx: + if row_idx in visible_idx: row_data = [] - - if isinstance(row, dict) and row: - for idx in range(len(data.columns)): - # check if column is not hidden - if not columns[idx].get("hidden"): - label = columns[idx]["label"] - fieldname = columns[idx]["fieldname"] - cell_value = row.get(fieldname, row.get(label, "")) - if cint(include_indentation) and "indent" in row and idx == 0: - cell_value = (" " * cint(row["indent"])) + cell_value - row_data.append(cell_value) - else: + if isinstance(row, dict): + for col_idx, column in enumerate(data.columns): + if column.get("hidden"): + continue + label = column.get("label") + fieldname = column.get("fieldname") + cell_value = row.get(fieldname, row.get(label, "")) + if cint(include_indentation) and "indent" in row and col_idx == 0: + cell_value = (" " * cint(row["indent"])) + cstr(cell_value) + row_data.append(cell_value) + elif row: row_data = row result.append(row_data) From f1cb8f4e5ceedbad9d14481f502e98096fe376d1 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 16 Oct 2020 13:42:00 +0530 Subject: [PATCH 018/163] feat: Pass custom widths for excel columns --- frappe/desk/query_report.py | 11 ++++++++--- frappe/utils/xlsxutils.py | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index dd318c8de6..b9be0ad683 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -345,8 +345,8 @@ def export_query(): data["result"] = handle_duration_fieldtype_values( data.get("result"), data.get("columns") ) - xlsx_data = build_xlsx_data(columns, data, visible_idx, include_indentation) - xlsx_file = make_xlsx(xlsx_data, "Query Report") + xlsx_data, column_widths = build_xlsx_data(columns, data, visible_idx, include_indentation) + xlsx_file = make_xlsx(xlsx_data, "Query Report", column_widths=column_widths) frappe.response["filename"] = report_name + ".xlsx" frappe.response["filecontent"] = xlsx_file.getvalue() @@ -380,11 +380,16 @@ def handle_duration_fieldtype_values(result, columns): def build_xlsx_data(columns, data, visible_idx, include_indentation): result = [[]] + column_widths = [] for column in data.columns: if column.get("hidden"): continue result[0].append(column["label"]) + column_width = column.get('width', 0) + # to convert into scale accepted by openpyxl + column_width /= 10 + column_widths.append(column_width) # build table from result for row_idx, row in enumerate(data.result): @@ -406,7 +411,7 @@ def build_xlsx_data(columns, data, visible_idx, include_indentation): result.append(row_data) - return result + return result, column_widths def add_total_row(result, columns, meta=None): diff --git a/frappe/utils/xlsxutils.py b/frappe/utils/xlsxutils.py index 2814c5ff40..3c7b027470 100644 --- a/frappe/utils/xlsxutils.py +++ b/frappe/utils/xlsxutils.py @@ -9,19 +9,24 @@ import xlrd import re from openpyxl.styles import Font from openpyxl import load_workbook +from openpyxl.utils import get_column_letter from six import BytesIO, string_types ILLEGAL_CHARACTERS_RE = re.compile(r'[\000-\010]|[\013-\014]|[\016-\037]') # return xlsx file object -def make_xlsx(data, sheet_name, wb=None): - +def make_xlsx(data, sheet_name, wb=None, column_widths=None): + column_widths = column_widths or [] if wb is None: wb = openpyxl.Workbook(write_only=True) ws = wb.create_sheet(sheet_name, 0) + for i, column_width in enumerate(column_widths): + if column_width: + ws.column_dimensions[get_column_letter(i + 1)].width = column_width + row1 = ws.row_dimensions[1] - row1.font = Font(name='Calibri',bold=True) + row1.font = Font(name='Calibri', bold=True) for row in data: clean_row = [] From 44b1336a885483ed6e535b72080ffdc02e870f9b Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 16 Oct 2020 13:43:35 +0530 Subject: [PATCH 019/163] style: Remove unused import --- frappe/core/doctype/report/test_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/report/test_report.py b/frappe/core/doctype/report/test_report.py index 2d2290e448..fc47e3a8a8 100644 --- a/frappe/core/doctype/report/test_report.py +++ b/frappe/core/doctype/report/test_report.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, json, os import unittest -from frappe.desk.query_report import run, save_report, get_report_doc +from frappe.desk.query_report import run, save_report test_records = frappe.get_test_records('Report') test_dependencies = ['User'] From 51d50ff7afab78891a2071ed77d7570c54eb3c3e Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 1 Oct 2020 16:25:35 +0530 Subject: [PATCH 020/163] feat: rule in assignment rule to assign based on field value --- .../assignment_rule/assignment_rule.js | 30 +++++++-- .../assignment_rule/assignment_rule.json | 8 ++- .../assignment_rule/assignment_rule.py | 35 ++++++----- .../assignment_rule_user.json | 62 +++---------------- 4 files changed, 61 insertions(+), 74 deletions(-) diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.js b/frappe/automation/doctype/assignment_rule/assignment_rule.js index e8d17527bf..dd5e1f84f4 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.js +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.js @@ -9,11 +9,33 @@ frappe.ui.form.on('Assignment Rule', { // refresh description frm.events.rule(frm); }, + + document_type: function(frm) { + frm.trigger('set_field_options'); + }, + rule: function(frm) { - if (frm.doc.rule === 'Round Robin') { - frm.get_field('rule').set_description(__('Assign one by one, in sequence')); - } else { - frm.get_field('rule').set_description(__('Assign to the one who has the least assignments')); + const description_map = { + 'Round Robin': __('Assign one by one, in sequence'), + 'Load Balancing': __('Assign to the one who has the least assignments'), + 'Based on Field': __('Assign to the user set in this field') + } + frm.get_field('rule').set_description(description_map[frm.doc.rule]); + }, + + set_field_options(frm) { + let doctype = frm.doc.document_type; + let user_link_fields = [{label: 'Owner', value: 'owner'}]; + if (doctype) { + frappe.model.with_doctype(doctype, () => { + // get all date and datetime fields + frappe.get_meta(doctype).fields.map(df => { + if (df.fieldtype == 'Link' && df.options == 'User') { + user_link_fields.push({label: df.label, value: df.fieldname}); + } + }); + frm.set_df_property('field', 'options', user_link_fields); + }); } }, document_type: (frm) => { diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.json b/frappe/automation/doctype/assignment_rule/assignment_rule.json index 858ad8aac4..415ebb1c45 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.json +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.json @@ -24,6 +24,7 @@ "assignment_days", "assign_to_users_section", "rule", + "field", "users", "last_user" ], @@ -93,15 +94,16 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Rule", - "options": "Round Robin\nLoad Balancing", + "options": "Round Robin\nLoad Balancing\nBased on Field", "reqd": 1 }, { + "depends_on": "eval: doc.rule !== 'Based on Field'", "fieldname": "users", "fieldtype": "Table MultiSelect", "label": "Users", - "options": "Assignment Rule User", - "reqd": 1 + "mandatory_depends_on": "eval: doc.rule !== 'Based on Field'", + "options": "Assignment Rule User" }, { "fieldname": "last_user", diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py index cd70799361..623df0a41e 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.py @@ -38,27 +38,30 @@ class AssignmentRule(Document): def apply_assign(self, doc): if self.safe_eval('assign_condition', doc): - self.do_assignment(doc) - return True + return self.do_assignment(doc) def do_assignment(self, doc): # clear existing assignment, to reassign assign_to.clear(doc.get('doctype'), doc.get('name')) - user = self.get_user() + user = self.get_user(doc) - assign_to.add(dict( - assign_to = [user], - doctype = doc.get('doctype'), - name = doc.get('name'), - description = frappe.render_template(self.description, doc), - assignment_rule = self.name, - notify = True, - date = doc.get(self.due_date_based_on) if self.due_date_based_on else None - )) + if user: + assign_to.add(dict( + assign_to = [user], + doctype = doc.get('doctype'), + name = doc.get('name'), + description = frappe.render_template(self.description, doc), + assignment_rule = self.name, + notify = True, + date = doc.get(self.due_date_based_on) if self.due_date_based_on else None + )) - # set for reference in round robin - self.db_set('last_user', user) + # set for reference in round robin + self.db_set('last_user', user) + return True + else: + return False def clear_assignment(self, doc): '''Clear assignments''' @@ -70,7 +73,7 @@ class AssignmentRule(Document): if self.safe_eval('close_condition', doc): return assign_to.close_all_assignments(doc.get('doctype'), doc.get('name')) - def get_user(self): + def get_user(self, doc): ''' Get the next user for assignment ''' @@ -78,6 +81,8 @@ class AssignmentRule(Document): return self.get_user_round_robin() elif self.rule == 'Load Balancing': return self.get_user_load_balancing() + elif self.rule == 'Based on Field': + return doc[self.field] def get_user_round_robin(self): ''' diff --git a/frappe/automation/doctype/assignment_rule_user/assignment_rule_user.json b/frappe/automation/doctype/assignment_rule_user/assignment_rule_user.json index f529772c8e..5a159c8267 100644 --- a/frappe/automation/doctype/assignment_rule_user/assignment_rule_user.json +++ b/frappe/automation/doctype/assignment_rule_user/assignment_rule_user.json @@ -1,76 +1,34 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, + "actions": [], + "allow_read": 1, "creation": "2019-02-27 11:41:46.602400", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "user" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "user", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "User", - "length": 0, - "no_copy": 0, "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, + "index_web_pages_for_search": 1, "istable": 1, - "max_attachments": 0, - "modified": "2019-02-27 17:16:41.399261", + "links": [], + "modified": "2020-09-29 20:12:14.456785", "modified_by": "Administrator", "module": "Automation", "name": "Assignment Rule User", - "name_case": "", "owner": "Administrator", "permissions": [], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file From ce1b6bde25cdde7b03302dfa7aaeb100027c0155 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 1 Oct 2020 18:08:37 +0530 Subject: [PATCH 021/163] feat: buttons to fetch assignment days in table --- .../assignment_rule/assignment_rule.js | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.js b/frappe/automation/doctype/assignment_rule/assignment_rule.js index dd5e1f84f4..bb262a7f58 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.js +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.js @@ -5,42 +5,76 @@ frappe.ui.form.on('Assignment Rule', { onload: (frm) => { frm.trigger('set_due_date_field_options'); }, + setup: function(frm) { + frm.trigger('setup_assignment_days_buttons'); + }, refresh: function(frm) { // refresh description frm.events.rule(frm); + frm.trigger('set_field_options'); }, document_type: function(frm) { frm.trigger('set_field_options'); + frm.trigger('set_due_date_field_options'); + }, + + setup_assignment_days_buttons: function(frm) { + const labels = ['Weekends', 'Weekdays', 'All Days']; + let get_days = (label) => { + const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; + const weekends = ['Saturday', 'Sunday']; + return { + 'All Days': weekdays.concat(weekends), + 'Weekdays': weekdays, + 'Weekends': weekends, + }[label]; + }; + + let get_button_html = (label) => ``; + + const $wrapper = frm.get_field('assignment_days').$wrapper; + $(`
    + ${labels.map(get_button_html).join('')} +
    `).prependTo($wrapper); + + frm.$wrapper.find('.fetch-days-buttons').on('click', '.btn', (e) => { + frm.clear_table('assignment_days'); + const label = $(e.currentTarget).text(); + get_days(label).forEach((day) => + frm.add_child('assignment_days', { day: day }) + ); + frm.refresh_field('assignment_days'); + }); }, rule: function(frm) { const description_map = { 'Round Robin': __('Assign one by one, in sequence'), 'Load Balancing': __('Assign to the one who has the least assignments'), - 'Based on Field': __('Assign to the user set in this field') - } + 'Based on Field': __('Assign to the user set in this field'), + }; frm.get_field('rule').set_description(description_map[frm.doc.rule]); }, set_field_options(frm) { let doctype = frm.doc.document_type; - let user_link_fields = [{label: 'Owner', value: 'owner'}]; + let user_link_fields = [{ label: 'Owner', value: 'owner' }]; if (doctype) { frappe.model.with_doctype(doctype, () => { - // get all date and datetime fields - frappe.get_meta(doctype).fields.map(df => { + frappe.get_meta(doctype).fields.map((df) => { if (df.fieldtype == 'Link' && df.options == 'User') { - user_link_fields.push({label: df.label, value: df.fieldname}); + user_link_fields.push({ label: df.label, value: df.fieldname }); } }); frm.set_df_property('field', 'options', user_link_fields); }); } }, - document_type: (frm) => { - frm.trigger('set_due_date_field_options'); - }, set_due_date_field_options: (frm) => { let doctype = frm.doc.document_type; let datetime_fields = []; From 5f20e85fe6be4374598bfba51b32314c06fb38fc Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 1 Oct 2020 18:33:42 +0530 Subject: [PATCH 022/163] fix: formatting issues --- frappe/automation/doctype/assignment_rule/assignment_rule.js | 5 ++--- frappe/automation/doctype/assignment_rule/assignment_rule.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.js b/frappe/automation/doctype/assignment_rule/assignment_rule.js index bb262a7f58..dcdfa65985 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.js +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.js @@ -33,9 +33,8 @@ frappe.ui.form.on('Assignment Rule', { let get_button_html = (label) => ``; + style="margin-bottom: 10px; margin-right: 5px">${__(label)} + `; const $wrapper = frm.get_field('assignment_days').$wrapper; $(`
    diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py index 623df0a41e..0187e87edf 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.py @@ -60,8 +60,8 @@ class AssignmentRule(Document): # set for reference in round robin self.db_set('last_user', user) return True - else: - return False + + return False def clear_assignment(self, doc): '''Clear assignments''' From fbc5f8c67ea3cd36c20d272353b777994d264221 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 2 Oct 2020 16:14:39 +0530 Subject: [PATCH 023/163] test: test for based on field assignment rule --- .../assignment_rule/test_assignment_rule.py | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/frappe/automation/doctype/assignment_rule/test_assignment_rule.py b/frappe/automation/doctype/assignment_rule/test_assignment_rule.py index dab842ad83..cb1e0ff8f4 100644 --- a/frappe/automation/doctype/assignment_rule/test_assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/test_assignment_rule.py @@ -88,6 +88,30 @@ class TestAutoAssign(unittest.TestCase): for user in ('test@example.com', 'test1@example.com', 'test2@example.com'): self.assertEqual(len(frappe.get_all('ToDo', dict(owner = user, reference_type = 'Note'))), 10) + def test_based_on_field(self): + self.assignment_rule.rule = 'Based on Field' + self.assignment_rule.field = 'owner' + self.assignment_rule.save() + + frappe.set_user('test1@example.com') + note = make_note(dict(public=1)) + # check if auto assigned to doc owner, test1@example.com + self.assertEqual(frappe.db.get_value('ToDo', dict( + reference_type = 'Note', + reference_name = note.name, + status = 'Open' + ), 'owner'), 'test1@example.com') + + frappe.set_user('test2@example.com') + note = make_note(dict(public=1)) + # check if auto assigned to doc owner, test2@example.com + self.assertEqual(frappe.db.get_value('ToDo', dict( + reference_type = 'Note', + reference_name = note.name, + status = 'Open' + ), 'owner'), 'test2@example.com') + + frappe.set_user('Administrator') def test_assign_condition(self): # check condition @@ -287,4 +311,4 @@ def make_note(values=None): note.insert() - return note \ No newline at end of file + return note From 9fe03b5ef33520fe5cdd9fb0e140ec33edd162dc Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 2 Oct 2020 17:02:16 +0530 Subject: [PATCH 024/163] fix: assignment days button label --- frappe/automation/doctype/assignment_rule/assignment_rule.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.js b/frappe/automation/doctype/assignment_rule/assignment_rule.js index dcdfa65985..fbadf2c7a6 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.js +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.js @@ -33,8 +33,7 @@ frappe.ui.form.on('Assignment Rule', { let get_button_html = (label) => ``; + style="margin-bottom: 10px; margin-right: 5px">${__(label)}`; const $wrapper = frm.get_field('assignment_days').$wrapper; $(`
    @@ -72,6 +71,8 @@ frappe.ui.form.on('Assignment Rule', { }); frm.set_df_property('field', 'options', user_link_fields); }); + } else { + frm.set_df_property('field', 'options', user_link_fields); } }, set_due_date_field_options: (frm) => { From 492bab9f5f4f1347bc03220141784914b5798495 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 8 Oct 2020 17:18:17 +0530 Subject: [PATCH 025/163] fix: extend add_custom_button in grid add option to add button to the top section --- .../assignment_rule/assignment_rule.js | 21 +++++++++--------- frappe/public/js/frappe/form/grid.js | 22 +++++++++++-------- frappe/public/less/form_grid.less | 14 ++++++++++++ 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.js b/frappe/automation/doctype/assignment_rule/assignment_rule.js index fbadf2c7a6..ffff5ccd56 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.js +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.js @@ -31,23 +31,22 @@ frappe.ui.form.on('Assignment Rule', { }[label]; }; - let get_button_html = (label) => ``; - - const $wrapper = frm.get_field('assignment_days').$wrapper; - $(`
    - ${labels.map(get_button_html).join('')} -
    `).prependTo($wrapper); - - frm.$wrapper.find('.fetch-days-buttons').on('click', '.btn', (e) => { + let set_days = (e) => { frm.clear_table('assignment_days'); const label = $(e.currentTarget).text(); get_days(label).forEach((day) => frm.add_child('assignment_days', { day: day }) ); frm.refresh_field('assignment_days'); - }); + }; + + labels.forEach(label => + frm.fields_dict['assignment_days'].grid.add_custom_button( + label, + set_days, + true + ) + ); }, rule: function(frm) { diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 394af9e3c6..106dacd035 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -51,10 +51,12 @@ export default class Grid { make() { - let template = `
    + let template = `
    +
    +
    @@ -112,6 +114,7 @@ export default class Grid { this.custom_buttons = {}; this.grid_buttons = this.wrapper.find('.grid-buttons'); + this.grid_custom_buttons = this.wrapper.find('.grid-custom-buttons'); this.remove_rows_button = this.grid_buttons.find('.grid-remove-rows'); this.remove_all_rows_button = this.grid_buttons.find('.grid-remove-all-rows'); @@ -861,18 +864,19 @@ export default class Grid { }); } - add_custom_button(label, click) { + add_custom_button(label, click, in_custom_section) { // add / unhide a custom button - var btn = this.custom_buttons[label]; - if (!btn) { - btn = $('') - .css('margin-right', '4px') - .prependTo(this.grid_buttons) + const $wrapper = in_custom_section ? this.grid_custom_buttons : this.grid_buttons; + let $btn = this.custom_buttons[label]; + if (!$btn) { + $btn = $(``) + .prependTo($wrapper) .on('click', click); - this.custom_buttons[label] = btn; + this.custom_buttons[label] = $btn; } else { - btn.removeClass('hidden'); + $btn.removeClass('hidden'); } + return $btn; } clear_custom_buttons() { diff --git a/frappe/public/less/form_grid.less b/frappe/public/less/form_grid.less index 3c9e096444..17e612caf8 100644 --- a/frappe/public/less/form_grid.less +++ b/frappe/public/less/form_grid.less @@ -1,6 +1,10 @@ @import "variables.less"; @import "mixins.less"; +.grid-field { + position: relative; +} + .form-grid { border: 1px solid @border-color; border-radius: 3px; @@ -294,6 +298,16 @@ html.chrome .grid-row .grid-row-check { display: flow-root; } +.grid-custom-buttons { + position: absolute; + right: 0; + top: -5px; +} + +.btn-custom:not(:last-child) { + margin-right: 5px; +} + .grid-buttons { display: inline-flex; } From 5031ba9dff083570c90c45a19facda608479d233 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 13 Oct 2020 11:50:21 +0530 Subject: [PATCH 026/163] feat: function to set field options for select field --- .../assignment_rule/assignment_rule.js | 60 ++++++------------- frappe/public/js/frappe/form/form.js | 15 +++++ frappe/public/js/frappe/form/grid.js | 4 +- 3 files changed, 36 insertions(+), 43 deletions(-) diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.js b/frappe/automation/doctype/assignment_rule/assignment_rule.js index ffff5ccd56..f621891cda 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.js +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.js @@ -2,21 +2,18 @@ // For license information, please see license.txt frappe.ui.form.on('Assignment Rule', { - onload: (frm) => { - frm.trigger('set_due_date_field_options'); - }, - setup: function(frm) { - frm.trigger('setup_assignment_days_buttons'); + onload: function(frm) { + frm.trigger('set_options'); }, + refresh: function(frm) { + frm.trigger('setup_assignment_days_buttons'); // refresh description frm.events.rule(frm); - frm.trigger('set_field_options'); }, document_type: function(frm) { - frm.trigger('set_field_options'); - frm.trigger('set_due_date_field_options'); + frm.trigger('set_options'); }, setup_assignment_days_buttons: function(frm) { @@ -44,7 +41,7 @@ frappe.ui.form.on('Assignment Rule', { frm.fields_dict['assignment_days'].grid.add_custom_button( label, set_days, - true + 'top' ) ); }, @@ -58,37 +55,18 @@ frappe.ui.form.on('Assignment Rule', { frm.get_field('rule').set_description(description_map[frm.doc.rule]); }, - set_field_options(frm) { - let doctype = frm.doc.document_type; - let user_link_fields = [{ label: 'Owner', value: 'owner' }]; - if (doctype) { - frappe.model.with_doctype(doctype, () => { - frappe.get_meta(doctype).fields.map((df) => { - if (df.fieldtype == 'Link' && df.options == 'User') { - user_link_fields.push({ label: df.label, value: df.fieldname }); - } - }); - frm.set_df_property('field', 'options', user_link_fields); - }); - } else { - frm.set_df_property('field', 'options', user_link_fields); - } + set_options(frm) { + const doctype = frm.doc.document_type; + frm.set_fields_as_options( + 'field', + doctype, + (df) => df.fieldtype == 'Link' && df.options == 'User', + [{ label: 'Owner', value: 'owner' }] + ); + frm.set_fields_as_options( + 'due_date_based_on', + doctype, + (df) => ['Date', 'Datetime'].includes(df.fieldtype) + ).then(options => frm.set_df_property('due_date_based_on', 'hidden', !options.length)); }, - set_due_date_field_options: (frm) => { - let doctype = frm.doc.document_type; - let datetime_fields = []; - if (doctype) { - frappe.model.with_doctype(doctype, () => { - frappe.get_meta(doctype).fields.map((df) => { - if (['Date', 'Datetime'].includes(df.fieldtype)) { - datetime_fields.push({ label: df.label, value: df.fieldname }); - } - }); - if (datetime_fields) { - frm.set_df_property('due_date_based_on', 'options', datetime_fields); - } - frm.set_df_property('due_date_based_on', 'hidden', !datetime_fields.length); - }); - } - } }); diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 9e6d3f0bdb..23b4df75b0 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1658,6 +1658,21 @@ frappe.ui.form.Form = class FrappeForm { frappe.route.on('change', () => driver.reset()); driver.start(); } + + set_fields_as_options(fieldname, doctype, condition, default_options) { + if (!doctype) return; + let options = default_options || []; + + return new Promise(resolve => { + frappe.model.with_doctype(doctype, () => { + frappe.get_meta(doctype).fields.map(df => { + condition(df) && options.push({ label: df.label, value: df.fieldname }); + }); + options && this.set_df_property(fieldname, 'options', options); + resolve(options); + }); + }) + } }; frappe.validated = 0; diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 106dacd035..9c916ccc4a 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -864,9 +864,9 @@ export default class Grid { }); } - add_custom_button(label, click, in_custom_section) { + add_custom_button(label, click, position='bottom') { // add / unhide a custom button - const $wrapper = in_custom_section ? this.grid_custom_buttons : this.grid_buttons; + const $wrapper = position === 'top' ? this.grid_custom_buttons : this.grid_buttons; let $btn = this.custom_buttons[label]; if (!$btn) { $btn = $(``) From f7fc85203d322ae5667b2a550b08ea635b6e36ab Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 13 Oct 2020 20:24:51 +0530 Subject: [PATCH 027/163] fix: make function more clear --- frappe/public/js/frappe/form/form.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 23b4df75b0..fafdcec57b 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1659,19 +1659,20 @@ frappe.ui.form.Form = class FrappeForm { driver.start(); } - set_fields_as_options(fieldname, doctype, condition, default_options) { - if (!doctype) return; - let options = default_options || []; + // Filters fields from the reference doctype and sets them as options for a Select field + set_fields_as_options(fieldname, reference_doctype, filter_function, default_options=[]) { + if (!reference_doctype) return; + let options = default_options; return new Promise(resolve => { - frappe.model.with_doctype(doctype, () => { - frappe.get_meta(doctype).fields.map(df => { - condition(df) && options.push({ label: df.label, value: df.fieldname }); + frappe.model.with_doctype(reference_doctype, () => { + frappe.get_meta(reference_doctype).fields.map(df => { + filter_function(df) && options.push({ label: df.label, value: df.fieldname }); }); options && this.set_df_property(fieldname, 'options', options); resolve(options); }); - }) + }); } }; From 605444e7e84b50b693e2bb128727b245681e9bc8 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 14 Oct 2020 17:23:46 +0530 Subject: [PATCH 028/163] feat: support for childtable fields in set_fields_as_options --- .../doctype/assignment_rule/assignment_rule.js | 17 ++++++++--------- frappe/public/js/frappe/form/form.js | 15 +++++++-------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.js b/frappe/automation/doctype/assignment_rule/assignment_rule.js index f621891cda..774befc15e 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.js +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.js @@ -2,12 +2,9 @@ // For license information, please see license.txt frappe.ui.form.on('Assignment Rule', { - onload: function(frm) { - frm.trigger('set_options'); - }, - refresh: function(frm) { frm.trigger('setup_assignment_days_buttons'); + frm.trigger('set_options'); // refresh description frm.events.rule(frm); }, @@ -63,10 +60,12 @@ frappe.ui.form.on('Assignment Rule', { (df) => df.fieldtype == 'Link' && df.options == 'User', [{ label: 'Owner', value: 'owner' }] ); - frm.set_fields_as_options( - 'due_date_based_on', - doctype, - (df) => ['Date', 'Datetime'].includes(df.fieldtype) - ).then(options => frm.set_df_property('due_date_based_on', 'hidden', !options.length)); + if (doctype) { + frm.set_fields_as_options( + 'due_date_based_on', + doctype, + (df) => ['Date', 'Datetime'].includes(df.fieldtype) + ).then(options => frm.set_df_property('due_date_based_on', 'hidden', !options.length)); + } }, }); diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index fafdcec57b..9b41c312f3 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1265,17 +1265,17 @@ frappe.ui.form.Form = class FrappeForm { set_df_property(fieldname, property, value, docname, table_field) { var df; - if (!docname && !table_field) { + if (!docname || !table_field) { df = this.get_docfield(fieldname); } else { - var grid = this.fields_dict[table_field].grid, - fname = frappe.utils.filter_dict(grid.docfields, {'fieldname': fieldname}); + var grid = this.fields_dict[fieldname].grid, + fname = frappe.utils.filter_dict(grid.docfields, {'fieldname': table_field}); if (fname && fname.length) - df = frappe.meta.get_docfield(fname[0].parent, fieldname, docname); + df = frappe.meta.get_docfield(fname[0].parent, table_field, docname); } if (df && df[property] != value) { df[property] = value; - refresh_field(fieldname, table_field); + this.refresh_field(fieldname); } } @@ -1660,16 +1660,15 @@ frappe.ui.form.Form = class FrappeForm { } // Filters fields from the reference doctype and sets them as options for a Select field - set_fields_as_options(fieldname, reference_doctype, filter_function, default_options=[]) { + set_fields_as_options(fieldname, reference_doctype, filter_function, default_options=[], table_fieldname) { if (!reference_doctype) return; let options = default_options; - return new Promise(resolve => { frappe.model.with_doctype(reference_doctype, () => { frappe.get_meta(reference_doctype).fields.map(df => { filter_function(df) && options.push({ label: df.label, value: df.fieldname }); }); - options && this.set_df_property(fieldname, 'options', options); + options && this.set_df_property(fieldname, 'options', options, this.doc.name, table_fieldname); resolve(options); }); }); From b726a8769a665a20ae03a18549b30d805133d841 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 18 Oct 2020 11:20:04 +0530 Subject: [PATCH 029/163] test: Fix query report test --- frappe/tests/test_query_report.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frappe/tests/test_query_report.py b/frappe/tests/test_query_report.py index 8e62e03064..eaf4dc7070 100644 --- a/frappe/tests/test_query_report.py +++ b/frappe/tests/test_query_report.py @@ -23,7 +23,11 @@ class TestQueryReport(unittest.TestCase): # Create mock data data = frappe._dict() - data.columns = ["column_a", "column_b", "column_c"] + data.columns = [ + {"label": "Column A", "fieldname": "column_a"}, + {"label": "Column B", "fieldname": "column_b", "width": 150}, + {"label": "Column C", "fieldname": "column_c", "width": 100} + ] data.result = [ [1.0, 3.0, 5.5], {"column_a": 22.1, "column_b": 21.8, "column_c": 30.2}, @@ -35,10 +39,12 @@ class TestQueryReport(unittest.TestCase): visible_idx = [0, 2, 3] # Build the result - xlsx_data = build_xlsx_data(columns, data, visible_idx, include_indentation=0) + xlsx_data, column_widths = build_xlsx_data(columns, data, visible_idx, include_indentation=0) self.assertEqual(type(xlsx_data), list) self.assertEqual(len(xlsx_data), 4) # columns + data + # column widths are divided by 10 to match the scale that is supported by openpyxl + self.assertListEqual(column_widths, [0, 15, 10]) for row in xlsx_data: self.assertEqual(type(row), list) From 9e4f079877ac32b1ee01bd0b1affac4fa55dd77f Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 18 Oct 2020 11:21:47 +0530 Subject: [PATCH 030/163] chore: Disable test verbosity --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2331217363..63895675ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,12 +31,12 @@ matrix: - name: "Python 3.7 MariaDB" python: 3.7 env: DB=mariadb TYPE=server - script: bench --verbose --site test_site run-tests --coverage + script: bench --site test_site run-tests --coverage - name: "Python 3.7 PostgreSQL" python: 3.7 env: DB=postgres TYPE=server - script: bench --verbose --site test_site run-tests --coverage + script: bench --site test_site run-tests --coverage - name: "Cypress" python: 3.7 From c48ddafde97d013e0676708d385ff4a3e813967b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 19 Oct 2020 11:02:00 +0530 Subject: [PATCH 031/163] fix: email aggregation --- frappe/core/doctype/communication/communication.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index fc929351d4..d893e80617 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -260,10 +260,8 @@ class Communication(Document): # Timeline Links def set_timeline_links(self): contacts = [] - if (self.email_account and frappe.db.get_value("Email Account", self.email_account, "create_contact")) or \ - frappe.flags.in_test: - - contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc]) + create_contact_enabled = self.email_account and frappe.db.get_value("Email Account", self.email_account, "create_contact") + contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc], auto_create_contact=create_contact_enabled) for contact_name in contacts: self.add_link('Contact', contact_name) @@ -342,7 +340,7 @@ def get_permission_query_conditions_for_communication(user): return """`tabCommunication`.email_account in ({email_accounts})"""\ .format(email_accounts=','.join(email_accounts)) -def get_contacts(email_strings): +def get_contacts(email_strings, auto_create_contact=False): email_addrs = [] for email_string in email_strings: @@ -357,7 +355,7 @@ def get_contacts(email_strings): email = get_email_without_link(email) contact_name = get_contact_name(email) - if not contact_name and email: + if not contact_name and email and auto_create_contact: email_parts = email.split("@") first_name = frappe.unscrub(email_parts[0]) From dc98dba97f7f9f5f97e50e7d57c4975e90a242a7 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 19 Oct 2020 14:48:49 +0200 Subject: [PATCH 032/163] fix: "Component" istead of empty type --- frappe/website/doctype/web_template/web_template.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/website/doctype/web_template/web_template.json b/frappe/website/doctype/web_template/web_template.json index 492c265e55..2728f5a1a7 100644 --- a/frappe/website/doctype/web_template/web_template.json +++ b/frappe/website/doctype/web_template/web_template.json @@ -41,7 +41,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Type", - "options": "\nSection\nNavbar\nFooter" + "options": "Component\nSection\nNavbar\nFooter" }, { "depends_on": "standard", @@ -58,7 +58,7 @@ "link_fieldname": "web_template" } ], - "modified": "2020-10-02 17:00:52.512209", + "modified": "2020-10-19 14:44:16.694730", "modified_by": "Administrator", "module": "Website", "name": "Web Template", From aba8bf28bc7727e9b0f5f8b76a7c8c11c2d2dfd2 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 19 Oct 2020 15:52:01 +0200 Subject: [PATCH 033/163] feat: set filter on Web Page Blocks Component is not allowed as a page section, only as part of another Web Template. --- frappe/website/doctype/web_page/web_page.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/frappe/website/doctype/web_page/web_page.js b/frappe/website/doctype/web_page/web_page.js index ab4f534c61..78b42856c9 100644 --- a/frappe/website/doctype/web_page/web_page.js +++ b/frappe/website/doctype/web_page/web_page.js @@ -19,6 +19,9 @@ frappe.ui.form.on('Web Page', { insert_code: function(frm) { frm.events.layout(frm); }, + onload: function(frm) { + frm.trigger('set_filters_on_web_page_blocks'); + }, refresh: function(frm) { if (frm.doc.template_path) { frm.set_read_only(); @@ -40,7 +43,20 @@ frappe.ui.form.on('Web Page', { frm.set_value('end_date', end_date); } }, - + content_type: function(frm) { + frm.trigger('set_filters_on_web_page_blocks'); + }, + set_filters_on_web_page_blocks: function(frm) { + if (frm.doc.content_type === 'Page Builder') { + frm.set_query('web_template', 'page_blocks', function() { + return { + "filters": { + "type": ['!=', 'Component'] + } + }; + }); + } + }, set_meta_tags(frm) { frappe.utils.set_meta_tag(frm.doc.route); } From ed66a2f53db66de102473dee65fcce8bb49d348f Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 19 Oct 2020 20:21:57 +0530 Subject: [PATCH 034/163] fix: Update child values for existing rows Earlier if you tried to update a child table field which already existed, it would not result in an update --- frappe/core/doctype/data_import/importer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index 5271690527..7880648b6f 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -616,7 +616,9 @@ class Row: id_field = get_id_field(doctype) id_value = doc.get(id_field.fieldname) if id_value and frappe.db.exists(doctype, id_value): - doc = frappe.get_doc(doctype, id_value) + existing_doc = frappe.get_doc(doctype, id_value) + existing_doc.update(doc) + doc = existing_doc else: # for table rows being inserted in update # create a new doc with defaults set From 89f212d4956c0f70d5d220d7fbd58c009f61e250 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 20 Oct 2020 10:41:39 +0530 Subject: [PATCH 035/163] test: Clear any customizations on user doctype --- frappe/core/doctype/report/test_report.py | 5 ++++- .../custom/doctype/customize_form/customize_form.py | 13 +++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/frappe/core/doctype/report/test_report.py b/frappe/core/doctype/report/test_report.py index fc47e3a8a8..69e52e9be3 100644 --- a/frappe/core/doctype/report/test_report.py +++ b/frappe/core/doctype/report/test_report.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe, json, os import unittest from frappe.desk.query_report import run, save_report +from frappe.custom.doctype.customize_form.customize_form import reset_customization test_records = frappe.get_test_records('Report') test_dependencies = ['User'] @@ -57,6 +58,7 @@ class TestReport(unittest.TestCase): self.assertDictEqual({'name': 'Administrator', 'user_type': 'System User', 'email': 'admin@example.com'}, result[0]) def test_report_with_custom_column(self): + reset_customization('User') response = run('Permitted Documents For User', filters={'user': 'Administrator', 'doctype': 'User'}, custom_columns=[{ @@ -74,7 +76,8 @@ class TestReport(unittest.TestCase): result = response.get('result') columns = response.get('columns') self.assertListEqual(['name', 'email', 'user_type'], [column.get('fieldname') for column in columns]) - self.assertDictEqual({'name': 'Administrator', 'user_type': 'System User', 'email': 'admin@example.com'}, result[0]) + admin_dict = frappe.core.utils.find(result, lambda d: d['name'] == 'Administrator') + self.assertDictEqual({'name': 'Administrator', 'user_type': 'System User', 'email': 'admin@example.com'}, admin_dict) def test_report_permissions(self): frappe.set_user('test@example.com') diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index d4eeba3f93..c8c3182729 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -425,8 +425,13 @@ class CustomizeForm(Document): if not self.doc_type: return - frappe.db.sql("""DELETE FROM `tabProperty Setter` WHERE doc_type=%s - and `field_name`!='naming_series' - and `property`!='options'""", self.doc_type) - frappe.clear_cache(doctype=self.doc_type) + reset_customization(self.doctype) self.fetch_to_customize() + +def reset_customization(doctype): + frappe.db.sql(""" + DELETE FROM `tabProperty Setter` WHERE doc_type=%s + and `field_name`!='naming_series' + and `property`!='options' + """, doctype) + frappe.clear_cache(doctype=doctype) \ No newline at end of file From 6292b38692fcf9a17f97aaec185a5298ccd94d09 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 20 Oct 2020 11:17:41 +0530 Subject: [PATCH 036/163] test: Clear user customization before test --- frappe/core/doctype/report/test_report.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/report/test_report.py b/frappe/core/doctype/report/test_report.py index 69e52e9be3..d76a1470e4 100644 --- a/frappe/core/doctype/report/test_report.py +++ b/frappe/core/doctype/report/test_report.py @@ -32,6 +32,7 @@ class TestReport(unittest.TestCase): self.assertTrue('User' in [d.get('name') for d in data]) def test_custom_report(self): + reset_customization('User') custom_report_name = save_report( 'Permitted Documents For User', 'Permitted Documents For User Custom', @@ -55,7 +56,8 @@ class TestReport(unittest.TestCase): }, user=frappe.session.user) self.assertListEqual(['email'], [column.get('fieldname') for column in columns]) - self.assertDictEqual({'name': 'Administrator', 'user_type': 'System User', 'email': 'admin@example.com'}, result[0]) + admin_dict = frappe.core.utils.find(result, lambda d: d['name'] == 'Administrator') + self.assertDictEqual({'name': 'Administrator', 'user_type': 'System User', 'email': 'admin@example.com'}, admin_dict) def test_report_with_custom_column(self): reset_customization('User') From 5b97d66778b2a4026c3f29f3eb3f4ab6c2e8da30 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 20 Oct 2020 11:20:56 +0530 Subject: [PATCH 037/163] fix: Typo --- frappe/custom/doctype/customize_form/customize_form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index c8c3182729..5c4e16fad7 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -425,7 +425,7 @@ class CustomizeForm(Document): if not self.doc_type: return - reset_customization(self.doctype) + reset_customization(self.doc_type) self.fetch_to_customize() def reset_customization(doctype): From 91babec0c2ab492b7b8dbe26f26746891fb66fb2 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 20 Oct 2020 14:50:33 +0530 Subject: [PATCH 038/163] fix: add removed field --- .../doctype/assignment_rule/assignment_rule.json | 13 ++++++++++--- .../doctype/assignment_rule/assignment_rule.py | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.json b/frappe/automation/doctype/assignment_rule/assignment_rule.json index 415ebb1c45..0a57e06da6 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.json +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.json @@ -136,15 +136,22 @@ }, { "depends_on": "document_type", + "description": "Value from this field will be set as the due date in the ToDo", "fieldname": "due_date_based_on", "fieldtype": "Select", - "label": "Due Date Based On", - "description": "Value from this field will be set as the due date in the ToDo" + "label": "Due Date Based On" + }, + { + "depends_on": "eval: doc.rule == 'Based on Field'", + "fieldname": "field", + "fieldtype": "Select", + "label": "Field", + "mandatory_depends_on": "eval: doc.rule == 'Based on Field'" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-10-13 06:48:54.019735", + "modified": "2020-10-20 14:47:20.662954", "modified_by": "Administrator", "module": "Automation", "name": "Assignment Rule", diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py index 0187e87edf..c85cb149ea 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.py @@ -82,7 +82,7 @@ class AssignmentRule(Document): elif self.rule == 'Load Balancing': return self.get_user_load_balancing() elif self.rule == 'Based on Field': - return doc[self.field] + return doc.get(self.field) def get_user_round_robin(self): ''' From 7d57d8030b332eee6cbb3b5767fbb1bd80f540ae Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 20 Oct 2020 15:07:10 +0530 Subject: [PATCH 039/163] fix: minor changes Clear headline to avoid duplicate headlines. Dont show headline for new forms --- frappe/website/doctype/web_template/web_template.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/website/doctype/web_template/web_template.js b/frappe/website/doctype/web_template/web_template.js index def4e2dab1..df5b3cb2c1 100644 --- a/frappe/website/doctype/web_template/web_template.js +++ b/frappe/website/doctype/web_template/web_template.js @@ -8,14 +8,14 @@ frappe.ui.form.on('Web Template', { } frm.toggle_display('standard', frappe.boot.developer_mode); - frm.toggle_display('template', !frm.doc.standard); }, standard: function(frm) { - if (!frm.doc.standard) { + if (!frm.doc.standard && !frm.is_new()) { // If standard changes from true to false, hide template until // the next save. Changes will get overwritten from the backend // on save and should not be possible in the UI. frm.toggle_display('template', false); + frm.dashboard.clear_headline(); frm.dashboard.set_headline(__('Please save to edit the template.')); } } From b86e1e905d646e3eb5d8fb8e8f6a55d06655eb7b Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 20 Oct 2020 15:09:40 +0530 Subject: [PATCH 040/163] fix: Add mandatory depends on and remove python validation --- frappe/website/doctype/web_template/web_template.json | 3 ++- frappe/website/doctype/web_template/web_template.py | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/frappe/website/doctype/web_template/web_template.json b/frappe/website/doctype/web_template/web_template.json index fed7008cdb..d50266b7b1 100644 --- a/frappe/website/doctype/web_template/web_template.json +++ b/frappe/website/doctype/web_template/web_template.json @@ -48,6 +48,7 @@ "fieldname": "module", "fieldtype": "Link", "label": "Module", + "mandatory_depends_on": "standard", "options": "Module Def" } ], @@ -58,7 +59,7 @@ "link_fieldname": "web_template" } ], - "modified": "2020-09-25 00:48:57.902292", + "modified": "2020-10-20 15:08:57.027962", "modified_by": "Administrator", "module": "Website", "name": "Web Template", diff --git a/frappe/website/doctype/web_template/web_template.py b/frappe/website/doctype/web_template/web_template.py index 811e4f5ad9..30db38eb74 100644 --- a/frappe/website/doctype/web_template/web_template.py +++ b/frappe/website/doctype/web_template/web_template.py @@ -26,9 +26,6 @@ class WebTemplate(Document): if not field.fieldname: field.fieldname = frappe.scrub(field.label) - if self.standard and not self.module: - frappe.throw(_("Please select which module this Web Template belongs to.")) - def on_update(self): if frappe.conf.developer_mode: # custom to standard From 5d3178ed19b075d7fbb73fbf4a395274f7b79c71 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 20 Oct 2020 14:53:23 +0200 Subject: [PATCH 041/163] fix: disable padding and container by default --- frappe/utils/jinja.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index 6eb9d98971..7fc8f48173 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -212,9 +212,9 @@ def web_blocks(blocks): 'doctype': 'Web Page Block', 'web_template': block['template'], 'web_template_values': block.get('values', {}), - 'add_top_padding': 1, - 'add_bottom_padding': 1, - 'add_container': 1, + 'add_top_padding': 0, + 'add_bottom_padding': 0, + 'add_container': 0, 'hide_block': 0, 'css_class': '' }) From ae5de5f4a7515ed2cc9d679829477d8f31653e6e Mon Sep 17 00:00:00 2001 From: KanchanChauhan Date: Tue, 20 Oct 2020 20:07:19 +0530 Subject: [PATCH 042/163] refactor: Login Code Size too Small on Mobile Problem: Currently, when a Login Code is received on a phone, it appears very small and cannot be copied. Solution: Increased the size from default 14 px to 18 px. --- frappe/twofactor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/twofactor.py b/frappe/twofactor.py index dfa3f394e5..bb6c0a97d8 100644 --- a/frappe/twofactor.py +++ b/frappe/twofactor.py @@ -225,7 +225,7 @@ def get_email_subject_for_2fa(kwargs_dict): def get_email_body_for_2fa(kwargs_dict): '''Get email body for 2fa.''' - body_template = 'Enter this code to complete your login:

    {{otp}}' + body_template = 'Enter this code to complete your login:

    {{otp}}' body = frappe.render_template(body_template, kwargs_dict) return body From a0df4ed8629eb10e3ec38c9faf6a212207ba5af3 Mon Sep 17 00:00:00 2001 From: Abhishek Balam Date: Wed, 21 Oct 2020 15:13:31 +0530 Subject: [PATCH 043/163] fix: add noreferrer policy to footer links --- frappe/templates/includes/footer/footer_grouped_links.html | 2 +- frappe/templates/includes/footer/footer_links.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/templates/includes/footer/footer_grouped_links.html b/frappe/templates/includes/footer/footer_grouped_links.html index 0383409090..06fa777a40 100644 --- a/frappe/templates/includes/footer/footer_grouped_links.html +++ b/frappe/templates/includes/footer/footer_grouped_links.html @@ -15,7 +15,7 @@