From 2ea74dee36f1bbd094a7fa967fc50be26ba5e09d Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Tue, 26 May 2020 18:47:17 +0530 Subject: [PATCH 001/213] fix: check for whitelist before calling from search search widget takes query as an input, but does not check whether the query function that is called is whitelisted, basically allowing anyone logged-in to call any function regardless of the whitelist. Signed-off-by: Chinmay D. Pai --- frappe/desk/search.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/desk/search.py b/frappe/desk/search.py index c70b650945..1da2a3d0b5 100644 --- a/frappe/desk/search.py +++ b/frappe/desk/search.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe, json from frappe.utils import cstr, unique, cint from frappe.permissions import has_permission +from frappe.handler import is_whitelisted from frappe import _ from six import string_types import re @@ -74,6 +75,7 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0, if query and query.split()[0].lower()!="select": # by method + is_whitelisted(query) frappe.response["values"] = frappe.call(query, doctype, txt, searchfield, start, page_length, filters, as_dict=as_dict) elif not query and doctype in standard_queries: From c4d4fc3574dcb0f716fc8ad5edb162f171a990fe Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Tue, 26 May 2020 18:49:12 +0530 Subject: [PATCH 002/213] chore: add standard queries hooks to whitelist standard queries are used within the search widget, and now require to be whitelisted before they can be executed through the search widget. Signed-off-by: Chinmay D. Pai --- frappe/core/doctype/user/user.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 0c5ebc3ede..f571240454 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -811,6 +811,7 @@ def reset_password(user): frappe.clear_messages() return 'not found' +@frappe.whitelist() def user_query(doctype, txt, searchfield, start, page_len, filters): from frappe.desk.reportview import get_match_cond From cae267723f7a4d8b1a9cd0f9f421b44d0977010c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 4 Jun 2020 13:40:28 +0530 Subject: [PATCH 003/213] fix: Incorrect currency precision --- frappe/public/js/frappe/form/formatters.js | 8 ++++++-- frappe/utils/data.py | 9 ++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index d178c59100..6e08255e17 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -69,8 +69,12 @@ frappe.form.formatters = { var decimals = parts.length > 1 ? parts[1] : ""; // parts.length == 2 ??? if ( decimals.length < 3 || decimals.length < precision ) { - const fraction = frappe.model.get_value(":Currency", currency, "fraction_units") || 100; // if not set, minimum 2. - precision = cstr(fraction).length - 1; + const fraction = frappe.model.get_value(":Currency", currency, "smallest_currency_fraction_value") || 100; // if not set, minimum 2. + let fraction_parts = cstr(fraction).split("."); + + if (fraction_parts.length > 1) { + precision = fraction_parts[1].length; + } } } diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 9796aa3c4a..fe198b7f36 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -442,7 +442,7 @@ def remainder(numerator, denominator, precision=2): else: _remainder = numerator % denominator - return flt(_remainder, precision); + return flt(_remainder, precision) def safe_div(numerator, denominator, precision=2): """ @@ -524,8 +524,11 @@ def fmt_money(amount, precision=None, currency=None): if precision > 2: if len(decimals) < 3: if currency: - fraction = frappe.db.get_value("Currency", currency, "fraction_units", cache=True) or 100 - precision = len(cstr(fraction)) - 1 + fraction = frappe.db.get_value("Currency", currency, "smallest_currency_fraction_value", cache=True) or 100 + fraction_parts = cstr(fraction).split(".") + + if len(fraction_parts) > 1: + precision = len(fraction_parts[1]) else: precision = number_format_precision elif len(decimals) < precision: From 59a1ee6a823ad6d29f4cb8951304ae67947f1941 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 4 Jun 2020 18:49:01 +0530 Subject: [PATCH 004/213] feat: date range in leaderboard --- frappe/desk/leaderboard.py | 13 +++-- frappe/desk/page/leaderboard/leaderboard.js | 57 ++++++++++----------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/frappe/desk/leaderboard.py b/frappe/desk/leaderboard.py index 1ebf32febe..e5654c853f 100644 --- a/frappe/desk/leaderboard.py +++ b/frappe/desk/leaderboard.py @@ -14,13 +14,16 @@ def get_leaderboards(): return leaderboards @frappe.whitelist() -def get_energy_point_leaderboard(from_date, company = None, field = None, limit = None): +def get_energy_point_leaderboard(date_range, company = None, field = None, limit = None): + filters = [ + ['type', '!=', 'Review'], + ] + if date_range: + date_range = frappe.parse_json(date_range) + filters.append(['creation', 'between', [date_range[0], date_range[1]]]) energy_point_users = frappe.db.get_all('Energy Point Log', fields = ['user as name', 'sum(points) as value'], - filters = [ - ['type', '!=', 'Review'], - ['creation', '>', from_date] - ], + filters = filters, group_by = 'user', order_by = 'value desc' ) diff --git a/frappe/desk/page/leaderboard/leaderboard.js b/frappe/desk/page/leaderboard/leaderboard.js index 4472a2978a..ecec7f4805 100644 --- a/frappe/desk/page/leaderboard/leaderboard.js +++ b/frappe/desk/page/leaderboard/leaderboard.js @@ -49,7 +49,7 @@ class Leaderboard { this.timespans = [ "This Week", "This Month", "This Quarter", "This Year", "Last Week", "Last Month", "Last Quarter", "Last Year", - "All Time", "Select From Date" + "All Time", "Select Date Range" ]; // for saving current selected filters @@ -113,7 +113,7 @@ class Leaderboard { return {"label": __(d), value: d }; }) ); - this.create_from_date_field(); + this.create_date_range_field(); this.type_select = this.page.add_select(__("Field"), this.options.selected_filter.map(d => { @@ -123,12 +123,12 @@ class Leaderboard { this.timespan_select.on("change", (e) => { this.options.selected_timespan = e.currentTarget.value; - if (this.options.selected_timespan === 'Select From Date') { - this.from_date_field.show(); + if (this.options.selected_timespan === 'Select Date Range') { + this.date_range_field.show(); } else { - this.from_date_field.hide(); - this.make_request(); + this.date_range_field.hide(); } + this.make_request(); }); this.type_select.on("change", (e) => { @@ -137,21 +137,21 @@ class Leaderboard { }); } - create_from_date_field() { + create_date_range_field() { let timespan_field = $(this.parent).find(`.frappe-control[data-original-title='Timespan']`); - this.from_date_field = $(`
`).insertAfter(timespan_field).hide(); + this.date_range_field = $(`
`).insertAfter(timespan_field).hide(); let date_field = frappe.ui.form.make_control({ df: { - fieldtype: 'Date', - fieldname: 'selected_from_date', - placeholder: frappe.datetime.month_start(), - default: frappe.datetime.month_start(), + fieldtype: 'DateRange', + fieldname: 'selected_date_range', + placeholder: "Date Range", + default: [frappe.datetime.month_start(), frappe.datetime.now_date()], input_class: 'input-sm', reqd: 1, change: () => { - this.selected_from_date = date_field.get_value(); - if (this.selected_from_date) this.make_request(); + this.selected_date_range = date_field.get_value(); + if (this.selected_date_range) this.make_request(); } }, parent: $(this.parent).find('.from-date-field'), @@ -225,7 +225,7 @@ class Leaderboard { frappe.call( this.leaderboard_config[this.options.selected_doctype].method, { - 'from_date': this.get_from_date(), + 'date_range': this.get_date_range(), 'company': this.options.selected_company, 'field': this.options.selected_filter_item, 'limit': this.leaderboard_limit, @@ -375,23 +375,22 @@ class Leaderboard { `); } - get_from_date() { + get_date_range() { let timespan = this.options.selected_timespan.toLowerCase(); let current_date = frappe.datetime.now_date(); - let get_from_date = { - "this week": frappe.datetime.week_start(), - "this month": frappe.datetime.month_start(), - "this quarter": frappe.datetime.quarter_start(), - "this year": frappe.datetime.year_start(), - "last week": frappe.datetime.add_days(current_date, -7), - "last month": frappe.datetime.add_months(current_date, -1), - "last quarter": frappe.datetime.add_months(current_date, -3), - "last year": frappe.datetime.add_months(current_date, -12), - "all time": "", - "select from date": this.selected_from_date || frappe.datetime.month_start() + let date_range_map = { + "this week": [frappe.datetime.week_start(), frappe.datetime.now_date()], + "this month": [frappe.datetime.month_start(), frappe.datetime.now_date()], + "this quarter": [frappe.datetime.quarter_start(), frappe.datetime.now_date()], + "this year": [frappe.datetime.year_start(), frappe.datetime.now_date()], + "last week": [frappe.datetime.add_days(current_date, -7), frappe.datetime.now_date()], + "last month": [frappe.datetime.add_months(current_date, -1), frappe.datetime.now_date()], + "last quarter": [frappe.datetime.add_months(current_date, -3), frappe.datetime.now_date()], + "last year": [frappe.datetime.add_months(current_date, -12), frappe.datetime.now_date()], + "all time": null, + "select date range": this.selected_date_range || [frappe.datetime.month_start(), frappe.datetime.now_date()] } - - return get_from_date[timespan]; + return date_range_map[timespan]; } } From 8455d93e5ba44a3f02c3940ae8d905c391135c67 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 4 Jun 2020 19:45:17 +0530 Subject: [PATCH 005/213] fix: better validation message if fetch from field doesn't exist --- frappe/model/base_document.py | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 106d21eb51..604d13b830 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -504,19 +504,7 @@ class BaseDocument(object): for _df in fields_to_fetch: if self.is_new() or self.docstatus != 1 or _df.allow_on_submit: - fetch_from_fieldname = _df.fetch_from.split('.')[-1] - value = values[fetch_from_fieldname] - if _df.fieldtype == 'Small Text' or _df.fieldtype == 'Text' or _df.fieldtype == 'Data': - if fetch_from_fieldname in default_fields: - from frappe.model.meta import get_default_df - fetch_from_df = get_default_df(fetch_from_fieldname) - else: - fetch_from_df = frappe.get_meta(doctype).get_field(fetch_from_fieldname) - - fetch_from_ft = fetch_from_df.get('fieldtype') - if fetch_from_ft == 'Text Editor' and value: - value = unescape_html(strip_html(value)) - setattr(self, _df.fieldname, value) + self.set_fetch_from_value(doctype, _df, values) notify_link_count(doctype, docname) @@ -531,6 +519,27 @@ class BaseDocument(object): return invalid_links, cancelled_links + def set_fetch_from_value(self, doctype, df, values): + fetch_from_fieldname = df.fetch_from.split('.')[-1] + value = values[fetch_from_fieldname] + if df.fieldtype == 'Small Text' or df.fieldtype == 'Text' or df.fieldtype == 'Data': + if fetch_from_fieldname in default_fields: + from frappe.model.meta import get_default_df + fetch_from_df = get_default_df(fetch_from_fieldname) + else: + fetch_from_df = frappe.get_meta(doctype).get_field(fetch_from_fieldname) + + if not fetch_from_df: + frappe.throw( + _('Please check the value of Fetch From set in {}').format(df.label), + title = 'Wrong Fetch From Value' + ) + + fetch_from_ft = fetch_from_df.get('fieldtype') + if fetch_from_ft == 'Text Editor' and value: + value = unescape_html(strip_html(value)) + setattr(self, df.fieldname, value) + def _validate_selects(self): if frappe.flags.in_import: return From 253e73ed66db97d21f2690ad8c62bb547cbb4fdc Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 5 Jun 2020 18:23:50 +0530 Subject: [PATCH 006/213] fix(text-editor): List UI - Remove hacks for list render - Use quill styles for read-only mode and print-format to keep style consistent --- .../js/frappe/form/controls/text_editor.js | 94 +---------- frappe/public/less/print.less | 16 +- frappe/public/less/quill.less | 158 +++++++++--------- .../print_formats/standard_macros.html | 2 +- 4 files changed, 85 insertions(+), 185 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/text_editor.js b/frappe/public/js/frappe/form/controls/text_editor.js index 5e73889490..3c0f7d5110 100644 --- a/frappe/public/js/frappe/form/controls/text_editor.js +++ b/frappe/public/js/frappe/form/controls/text_editor.js @@ -1,10 +1,5 @@ import Quill from 'quill'; -// replace

tag with

-const Block = Quill.import('blots/block'); -Block.tagName = 'DIV'; -Quill.register(Block, true); - const CodeBlockContainer = Quill.import('formats/code-block-container'); CodeBlockContainer.tagName = 'PRE'; Quill.register(CodeBlockContainer, true); @@ -17,7 +12,8 @@ Table.create = (value) => { node.classList.add('table'); node.classList.add('table-bordered'); return node; -} +}; + Quill.register(Table, true); // link without href @@ -28,7 +24,7 @@ class MyLink extends Link { let node = super.create(value); value = this.sanitize(value); node.setAttribute('href', value); - if(value.startsWith('/') || value.indexOf(window.location.host)) { + if (value.startsWith('/') || value.indexOf(window.location.host)) { // no href if internal link node.removeAttribute('target'); } @@ -73,7 +69,7 @@ Quill.register(CustomColor, true); frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ make_wrapper() { this._super(); - this.$wrapper.find(".like-disabled-input").addClass('text-editor-print'); + this.$wrapper.find(".like-disabled-input").addClass('ql-editor'); }, make_input() { @@ -203,91 +199,9 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ get_input_value() { let value = this.quill ? this.quill.root.innerHTML : ''; - // quill keeps ol as a common container for both type of lists - // and uses css for appearances, this is not semantic - // so we convert ol to ul if it is unordered - const $value = $(`
${value}
`); - $value.find('ol li[data-list=bullet]:first-child').each((i, li) => { - let $li = $(li); - let $parent = $li.parent(); - let $children = $parent.children(); - let $ul = $('